与前面普通设备驱动1方法不同,这里的普通设备驱动利用i2c-core.c提供的i2c_transfer方法来实现设备驱动,而不是普通设备驱动1里面的通过操纵s3c2440的i2c寄存器来与设备通信
一、采用友善之臂的2.6.32.2内核,需要修改/linux-2.6.32.2/arch/arm/mach-s3c2440/mach-mini2440.c文件
1)添加 #include<linux/i2c.h>
2)在static struct platform_device *mini2440_devices[]前添加struct i2c_board_info i2c_devices[]结构体
//add static struct i2c_board_info i2c_devices[] __initdata={ {I2C_BOARD_INFO("at24c08", 0x50), }, }; /* devices we initialise */ static struct platform_device *mini2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_rtc, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c0,//没有修改 &s3c_device_iis, &mini2440_device_eth, &s3c24xx_uda134x, &s3c_device_nand, &s3c_device_sdi, &s3c_device_usbgadget, };
3)在mini2440_machine_init(void)函数内添加i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));函数
static void __init mini2440_machine_init(void) { i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices)); //add #if defined (LCD_WIDTH) s3c24xx_fb_set_platdata(&mini2440_fb_info); #endif s3c_i2c0_set_platdata(NULL); s3c2410_gpio_cfgpin(S3C2410_GPC(0), S3C2410_GPC0_LEND); s3c_device_nand.dev.platform_data = &friendly_arm_nand_info; s3c_device_sdi.dev.platform_data = &mini2440_mmc_cfg; platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices)); s3c_pm_init(); }
二、在linux2.6.32.2/drivers/i2c/chips/目录下添加at24c08.c设备驱动文件,并对应的修改Kconfig和Makefile文件
#include <linux/init.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/kdev_t.h> #include <asm/uaccess.h> #define major 155 static struct i2c_driver at24c08_driver; static struct i2c_adapter *at24c08_adapter; static unsigned short addr; static struct class at24c08_cls = { .name = "at24c08_cls", }; ssize_t at24c08_read (struct file *filp, char __user *buf, size_t sz, loff_t *off) { struct i2c_msg msg[2]; unsigned char args, data; if (sz != 1) return -EINVAL; copy_from_user((void *)&args, buf, 1); /* 先传读地址 */ msg[0].addr = addr; msg[0].buf = &args; msg[0].len = 1; msg[0].flags = 0; /* 再 读 */ msg[1].addr = addr; msg[1].buf = &data; msg[1].len = 1; msg[1].flags = 1; /* 读 */ if (2 == i2c_transfer(at24c08_adapter, msg, 2)) { /* 读成功 */ copy_to_user((void *)buf, &data, 1); return 1; } else return -EIO; } ssize_t at24c08_write (struct file *filp, const char __user *buf, size_t sz, loff_t *off) { struct i2c_msg msg; unsigned char args[2]; copy_from_user((void *)&args, buf, 2); /* args[0] = addr, args[1] = val */ msg.addr = addr; msg.buf = args; msg.len = 2; msg.flags = 0; /* 写 */ if(1 == i2c_transfer(at24c08_adapter,&msg, 1)) return 2; else return -EIO; } static struct file_operations at24c08_fops = { .owner = THIS_MODULE, .read = at24c08_read, .write = at24c08_write, }; static struct cdev cdev; static int at24c08_probe(struct i2c_client *client, const struct i2c_device_id *dev_id) { register_chrdev_region(major, 1, "at24c08"); cdev_init(&cdev, &at24c08_fops); cdev_add(&cdev, MKDEV(major, 0), 1); class_register(&at24c08_cls); device_create(&at24c08_cls, NULL, MKDEV(major, 0), NULL, "at24c08_dev"); return 0; } static int at24c08_remove(struct i2c_client *client) { device_destroy(&at24c08_cls, MKDEV(major, 0)); class_destroy(&at24c08_cls); cdev_del(&cdev); unregister_chrdev_region(MKDEV(major, 0), 1); return 0; } static int at24c08_detect(struct i2c_client *client, int kind, struct i2c_board_info *bd_info) { strcpy(bd_info->type, "at24c08"); addr = bd_info->addr = client->addr; at24c08_adapter = client->adapter; return 0; } static const struct i2c_device_id at24c08_id[] = { {"at24c08", 0}, {} }; static unsigned short ignore[] = { I2C_CLIENT_END }; static const unsigned short normal_i2c[] = {0x50, I2C_CLIENT_END}; static const struct i2c_client_address_data addr_data = { .normal_i2c = normal_i2c, .probe = ignore, .ignore = ignore, }; static struct i2c_driver at24c08_driver= { .probe = at24c08_probe, .remove = at24c08_remove, .driver = { .name = "at24c08", }, .id_table = at24c08_id, .detect = at24c08_detect, .address_data = &addr_data, .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, }; static int at24c08_init(void) { i2c_add_driver(&at24c08_driver); return 0; } static void at24c08_exit(void) { i2c_del_driver(&at24c08_driver); return ; } module_init(at24c08_init); module_exit(at24c08_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("100ask.net Young");
主要归功于两个函数
static struct class at24c08_cls = { .name = "at24c08_cls", };
class_register(&at24c08_cls);
device_create(&at24c08_cls, NULL, MKDEV(major, 0), NULL, "at24c08_dev");
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
与class_create等价的函数是class_register,只是他们对应的注销函数不同class_unregister和class_destory。
三、在linux2.6.32.2目录下make menuconfig进行内核定制之后再make则会生成i2c-core.ko,i2c-s3c2440.ko,at24c08.ko等文件,将三个文件都insmod到内核中,在/dev/目录下会自动生成at24c08_dev文件节点,但是在重启之后会消失,因为insmod是动态加载的,如果在重启后还要有文件节点,则要将加载命令写到启动脚本中去。