本次写一下基于Linux的i2c子系统的简单驱动程序的编写.首先来了解一下i2c子系统的框架(i2c协议相关知识请自行网上找资料),如下图:
上图大概可以反应Linux中i2c子系统的一个框架,包括①用户层, ②内核驱动层, ③物理硬件层.本次主要是写内核驱动层的内容,如上图i2c子系统的内核驱动层包括:(1)i2c设备驱动层, (2)i2c设备总线层, (3)i2c适配器层.
其中(2)i2c设备总线层代码(由Linux内核提供), (3)i2c适配器层代码(由芯片原厂提供),我们需要客制化开发的是(1)i2c设备驱动层,因为i2c协议是一个固定的情况,只要对于对于专门的硬件修改相应的硬件驱动程序即可,是相对比较稳定的代码,而支持i2c的设备是千变万化的,所以Linux内核留出(1)i2c设备驱动层给用户开发时比较好的一个考虑.本例程是基于exynos4412来写的,三星的CPU种类比较多,所以大多数驱动程序都会采用平台总线,以使驱动代码的适用性得到提高.实际上只要包含操作寄存器的驱动都可以采用平台总线的方式,这里的i2c程序也是使用平台总线,我使用的Linux版本是友善之臂提供的Linux-3.5.里面的mach-tiny4412.c就是定义了许多平台总线设备层数据,i2c的设备数据也定义在这里,在这里添加我们自己定义的设备数据,下面我们来看一下开发板的EEPROM资源之后,来确定要添加什么数据,1506的底板的EEPROM电路图如下:
从上图看出设备接在i2c的第0组上面,因为EEPROM的A0, A1, A23个管脚都接地,所以地址是1010000 == 0x50.我们现在已经掌握了关键数据了,现在去把这些信息添加到mach-tiny4412.c里面,添加的位置如下图红框部分:
添加完之后,我们修改menuconfig,有如下选项:
make menuconfig
Device Drivers --->
<*> I2C support --->//i2c-core.c
<*> I2C device interface//通用i2c从设备驱动--主要用于调试(可选)
I2C Hardware Bus support --->
<*> S3C2410 I2C Driver //i2c-s3c2410.c
之后重新编译内核:
make -j4
编译完之后会生成新的zImage,我们这里直接用zImage就好了,因为U-boot默认直接识别为ARM平台,所以可以不用包装成uImage,由于之前移植的U-boot-->uboot-tiny4412-1506好像没有移植网卡驱动,从电路图上看此网卡用的是USB接口,所以U-boot目前不支持,需要将Linux内核里的相关驱动移植过来,现在还没移植,所以现在还用不了tftp服务,无法远程网络下载内核,现在没空移植这个驱动,所以这里先把更新好的内核放到SD卡里面,然后直接从SD卡启动这样的方式来验证本此的驱动程序.
这里还把怎么把U-boot刷进SD卡,把zImage放入SD都说明一下:
我在<
unzip uboot-tiny4412-1506.zip
cd uboot-tiny4412-1506
unzip uboot_tiny4412-master.zip
cd uboot_tiny4412-master
make tiny4412_config
make
编译完成之后,接着执行如下命令:
cd sd_fuse
make
编译之后,我们插入SD卡,假设SD卡被Linux识别为/dev/sdb,执行如下命令:
cd sd_fuse/tiny4412
sudo ./sd_fusing.sh /dev/sdb
执行上面内容之后就可以把U-boot刷进SD卡了,然后把zImage复制进SD卡里即可.然后把卡插进开发板之后,把boot开关切到SD卡启动模式启动,设置一下基本的u-boot环境,就可以在加载完内核之后通过网络挂载文件系统(此时已有网卡驱动了).至于怎么设置,直接去看<下面,直接上代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_STYLE_ONE TRUE // 注册设备节点风格1
struct eeprom_i2c_driver {
int major;
#ifndef DEVICE_STYLE_ONE
struct class *cls;
#endif
struct device *dev;
struct i2c_client *client;
};
struct eeprom_i2c_driver *at24_drv;
#if defined (DEVICE_STYLE_ONE)
struct class at24_class = {
.name = "at24_cls",
};
void at24_dev_release(struct device *dev)
{
// do nothing
}
#endif
int
at24_drv_open(struct inode *inode, struct file *filp)
{
// 硬件初始化,eeprom属于上电即可工作的器件,不需要初始化
return 0;
}
ssize_t
at24_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
int ret = -1;
if(0 > count || 2048 < count)
return -EFAULT;
char *temp = kzalloc(count, GFP_KERNEL);
if(NULL == temp){
printk("read kzalloc failed !\n");
return -ENOMEM;
}
ret = i2c_master_recv(at24_drv->client, temp, count);
if(0 > ret){
printk("i2c_master_recv failed !\n");
kfree(temp);
return ret;
}
ret = copy_to_user(buf, temp, count);
if(0 < ret){
printk("copy to user failed !\n");
ret = -EBUSY;
}
kfree(temp);
return count;
}
ssize_t
at24_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
int ret = -1;
if(0 > count || 2048 < count)
return -EFAULT;
char *temp = kzalloc(count, GFP_KERNEL);
if(NULL == temp){
printk("read kzalloc failed !\n");
return -ENOMEM;
}
ret = copy_from_user(temp, buf, count);
if(0 < ret){
printk("copy from user failed !\n");
kfree(temp);
return -EBUSY;
}
ret = i2c_master_send(at24_drv->client, temp, count);
if(0 > ret){
printk("i2c write failed !\n");
kfree(temp);
return ret;
}
kfree(temp);
return count;
}
/**
* 我们可以通过llseek来指定读取时的偏移地址
* 为了代码简洁,这里暂不做处理
*/
loff_t
at24_drv_llseek(struct file *filp, loff_t fops, int count)
{
// do nothing
return 0;
}
int
at24_drv_release(struct inode *inode, struct file *filp)
{
// do nothing
return 0;
}
struct file_operations at24_fops = {
.open = at24_drv_open,
.read = at24_drv_read,
.write = at24_drv_write,
.llseek = at24_drv_llseek,
.release = at24_drv_release,
};
int
eeprom_i2c_probe(struct i2c_client *client, const struct i2c_device_id *devid)
{
int ret = -1;
printk("-----%s active------\n", __func__);
// 0, 申请设备对象空间
at24_drv = kzalloc(sizeof(struct eeprom_i2c_driver), GFP_KERNEL);
if(NULL == at24_drv){
printk("kzalloc failed !\n");
return -ENOMEM;
}
// 1, 申请设备号
at24_drv->major = register_chrdev(0, "at24_chrdev", &at24_fops);
if(at24_drv->major < 0){
printk("register char device failed !\n");
ret = -ENODEV;
goto err1;
}
#if defined (DEVICE_STYLE_ONE) // 代码走这里
// 2, 创建设备类
ret = class_register(&at24_class);
if(ret){
printk("class register failed !\n");
goto err2;
}
// 3, 创建设备节点
dev_set_name(&at24_drv->dev, "at24_dev");
at24_drv->dev.devt = MKDEV(at24_drv->major, 0);
at24_drv->dev.class = &at24_class,
at24_drv->dev.parent = NULL,
at24_drv->dev.release = at24_dev_release,
device_initialize(&at24_drv->dev);
device_add(&at24_drv->dev);
#else
// 2, 创建设备类
at24_drv->cls = class_create(THIS_MODULE, "at24_cls");
if(IS_ERR(at24_drv->cls)){
printk("class create failed !\n");
ret = PTR_ERR(at24_drv->cls);
goto err2;
}
// 3, 创建设备节点
at24_drv->dev = device_create(at24_drv->cls, NULL, MKDEV(at24_drv->major, 0), NULL, "at24_dev");
if(IS_ERR(at24_drv->dev)){
printk("device create failed !\n");
ret = PTR_ERR(at24_drv->dev);
class_destroy(at24_drv->cls);
goto err2;
}
#endif
// 4, 记录当前设备对象
at24_drv->client = client;
return 0;
err2:
unregister_chrdev(at24_drv->major, "at24_chrdev");
err1:
kfree(at24_drv);
return ret;
}
int
eeprom_i2c_remove(struct i2c_client *client)
{
#if defined(DEVICE_STYLE_ONE)
device_del(&at24_drv->dev);
class_unregister(&at24_class);
#else
device_destroy(at24_drv->cls, MKDEV(at24_drv->major, 0));
class_destroy(at24_drv->cls);
#endif
unregister_chrdev(at24_drv->major, "at24_chrdev");
kfree(at24_drv);
return 0;
}
const struct i2c_device_id eeprom_table[] = {
{"at24c02a", 0x2},
{"at24c04a", 0x4},
};
struct i2c_driver eeprom_driver = {
.probe = eeprom_i2c_probe,
.remove = eeprom_i2c_remove,
.driver = {
.name = "at24_drv",
},
.id_table = eeprom_table,
};
static void __exit
eeprom_i2c_exit(void)
{
i2c_del_driver(&eeprom_driver);
}
static int __init
eeprom_i2c_init(void)
{
return i2c_add_driver(&eeprom_driver);
}
module_init(eeprom_i2c_init);
module_exit(eeprom_i2c_exit);
MODULE_LICENSE("GPL");
应用层测试代码是:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define I2C_SLAVE 0x0703
void print_usage(char *str)
{
printf("%s r : read at24c02 addresss 0\n", str);
printf("%s w val : write at24c02 addresss 0\n", str);
}
int main(int argc,char **argv)
{
int fd;
unsigned char val;//字节
char register_addr = 0x08; /* Device register to access 片内地址*/
int res;
char wbuf[10];
char rbuf[10];
if (argc < 2){
print_usage(argv[0]);
exit(1);
}
/*打开设备文件*/
fd = open("/dev/at24_dev", O_RDWR);
if (fd < 0)
{
perror("open failed");
exit(1);
}
if (strcmp(argv[1], "r") == 0){
if (write(fd, ®ister_addr, 1)!=1) {
perror("write failed");
exit(1);
}
if (read(fd, rbuf, 1) != 1) {
perror("read failed");
exit(1);
} else {
printf("rbuf =0x%x\n",rbuf[0]);
}
}
else if ((strcmp(argv[1], "w") == 0) && (argc == 3))
{
// ./test w 0x99
val = strtoul(argv[2], NULL, 0);
wbuf[0] = register_addr; // 片内地址0x08
wbuf[1] = val;
if (write(fd, wbuf, 2)!=2) {
perror("write failed");
exit(1);
}
printf("write data ok!\n");
}
close(fd);
return 0;
}
Makefile是:
#指定内核源码路径
KERNEL_DIR = /home/george/1702/exynos/linux-3.5
#指定当前路径
CUR_DIR = $(shell pwd)
MYAPP = at24_app
MODULE = exynos4412_i2c_drv
all:
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c
clean:
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
$(RM) $(MYAPP)
install:
cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702
#指定编译当前目录下哪个源文件
obj-m = $(MODULE).o
下面是测试步骤和结果: