④tiny4412 Linux驱动开发之I2C子系统EEPROM篇

本次写一下基于Linux的i2c子系统的简单驱动程序的编写.首先来了解一下i2c子系统的框架(i2c协议相关知识请自行网上找资料),如下图:

    ④tiny4412 Linux驱动开发之I2C子系统EEPROM篇_第1张图片

上图大概可以反应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电路图如下:

④tiny4412 Linux驱动开发之I2C子系统EEPROM篇_第2张图片

从上图看出设备接在i2c的第0组上面,因为EEPROM的A0, A1, A23个管脚都接地,所以地址是1010000 == 0x50.我们现在已经掌握了关键数据了,现在去把这些信息添加到mach-tiny4412.c里面,添加的位置如下图红框部分:

    ④tiny4412 Linux驱动开发之I2C子系统EEPROM篇_第3张图片

添加完之后,我们修改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都说明一下:

我在<>一节有提供一个U-boot-->uboot-tiny4412-1506,下载文件,然后放到Linux下,执行如下操作:

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
下面是测试步骤和结果:
④tiny4412 Linux驱动开发之I2C子系统EEPROM篇_第4张图片

你可能感兴趣的:(Linux)