与前面普通设备驱动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");
以上要说明的是为什么insmod at24c08.ko之后会在/dev/目录下自动创建/dev/at24c08_dev文件节点
主要归功于两个函数
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是动态加载的,如果在重启后还要有文件节点,则要将加载命令写到启动脚本中去。