linux 驱动 --- I2C总线及驱动
I2C总线
I2C总线和 platform 总线非常类似,platform 总线初始化位置如下:
kernel_init();
kernel_init_freeable();
do_basic_setup();
driver_init();
platform_bus_init();
I2C总线是作为一个模块进行初始化
postcore_initcall(i2c_init);
#define postcore_initcall(fn) __define_initcall(fn, 2)
由此可见,platform 总线不可或缺,I2C总线是可以被裁剪的。
I2C总线的全局变量如下:
I2C适配器(控制器)设备、I2C设备
每一个soc上基本都会有一个或者多个I2C控制器,每一个I2C控制器下面接的就是SCL 和 SDA 两条线。I2C设备都是挂在这两条线下面的,这就是就硬件层的基本结构。
硬件层的上面就是Linux内核中的适配器驱动层,每一个soc厂家都会在Linux框架上实现自家I2C适配器设备的的驱动程序,这部分是由soc厂家的bsp工程师提供的。适配器的的驱动程序其实就是根据I2C控制器的寄存器操作实现了如何将数据送到I2C总线下的设备中去,以及怎么读取数据。厂家将这一层封装好,驱动工程师写驱动的时候就只需要根据具体的I2C设备来读写数据,不需要自己来实现I2C时序。简单点就说设备驱动只负责准备要发的数据,告诉适配器就可以了。至于怎么发就是厂家自己去实现了,毕竟只有他们才最熟悉自家的芯片。
I2C适配器就是根据设备树提供的I2C控制器信息配置芯片,如下i2c1
i2c1: i2c@21a0000 { #address-cells = <1>; #size-cells = <0>; compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; reg = <0x021a0000 0x4000>; interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_I2C1>; status = "disabled"; };
&i2c1 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; magnetometer@e { compatible = "fsl,mag3110"; reg = <0x0e>; vdd-supply = <®_peri_3v3>; vddio-supply = <®_peri_3v3>; position = <2>; }; fxls8471@1e { compatible = "fsl,fxls8471"; reg = <0x1e>; position = <0>; interrupt-parent = <&gpio5>; interrupts = <0 8>; }; };
static const struct of_device_id i2c_imx_dt_ids[] = { { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, }, { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, }, { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, }, { .compatible = "fsl,imx7d-i2c", .data = &imx7d_i2c_hwdata, }, { /* sentinel */ } }; static struct platform_driver i2c_imx_driver = { .probe = i2c_imx_probe, .remove = i2c_imx_remove, .driver = { .name = DRIVER_NAME, .pm = &i2c_imx_pm_ops, .of_match_table = i2c_imx_dt_ids, .acpi_match_table = i2c_imx_acpi_ids, }, .id_table = imx_i2c_devtype, };
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);
i2c_adap_imx_init() 是芯片厂家提供的适配器驱动,设备和驱动匹配执行 i2c_imx_probe() ,内容有:
i2c_register_adapter() 添加 i2c_adapter_type 类型设备到 I2C 总线 (bus_add_device),即添加到链表 i2c_bus_type->p->klist_devices,此设备是适配器,即以上设备树的 i2c1
of_i2c_register_devices() --->i2c_new_client_device 添加 i2c_client_type 类型设备到 I2C 总线,即以上设备树的 magnetometer 和 fxls8471,是实际的I2C接口的外部芯片,具体的I2C设备。一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。
总结:设备树上有I2C适配器设备和I2C设备,都需要对应的驱动程序,I2C适配器设备的驱动程序由厂家提供,I2C设备由开发者自行实现。
I2C驱动
驱动程序实例
#include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/mutex.h> #include <linux/sysfs.h> #include <linux/mod_devicetable.h> #include <linux/log2.h> #include <linux/bitops.h> #include <linux/jiffies.h> #include <linux/of.h> #include <linux/acpi.h> #include <linux/device.h> #include <linux/i2c.h> #include <linux/platform_device.h> #include <asm/ioctl.h> #include <asm/uaccess.h> #define MY_DRV_NAME "at24cxx" #define MY_CLASS "at24cxxClass" static int AT24CXX_SIZE = 256; struct my_at24_dev { struct i2c_client * m_client; struct class * m_eeprom_class; int m_major; struct mutex m_mutex; }; static struct my_at24_dev * my_at24_devp; unsigned int i2c_RxAddr; static const struct i2c_device_id my_at24_ids[] = { {"my_24cxx", 0}, //和设备树节点 .compatible 属性值匹配 { }, }; static ssize_t my_at24c_read(struct file * flip, char __user * buff, size_t count, loff_t * offset) { ssize_t retval = 0; unsigned char addr = i2c_RxAddr&0xff; unsigned char recv_buff[512]; struct i2c_msg msgs[2]; struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data; //printk(KERN_INFO "%s enter,count=%d,i2c_RxAddr[0]=%d,i2c_RxAddr[1]=%d\r\n", __FUNCTION__,count,i2c_RxAddr[0],i2c_RxAddr[1]); mutex_lock(&at24_dev->m_mutex); /*first write the register offset address */ msgs[0].addr = at24_dev->m_client->addr; // address of slave device msgs[0].flags = 0; // 0:write, 1:read msgs[0].len = 1; msgs[0].buf = &addr; msgs[1].addr = at24_dev->m_client->addr; msgs[1].flags = 1; msgs[1].len = sizeof(recv_buff); msgs[1].buf = recv_buff; retval = i2c_transfer(at24_dev->m_client->adapter, msgs, ARRAY_SIZE(msgs)); //printk(KERN_INFO "%s: i2c_transfer return %d\r\n", __FUNCTION__,retval); if(retval != 2){ printk(KERN_INFO "my_at24c_read failed"); goto err; } retval = copy_to_user(buff, recv_buff, count); retval = count; err: mutex_unlock(&at24_dev->m_mutex); return retval; } static ssize_t my_at24c_write(struct file *flip, const char __user *buff, size_t count, loff_t *offset) { ssize_t retval = 0; unsigned char send_buff[512]; struct i2c_msg msgs[1]; unsigned int len = 0; struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data; mutex_lock(&at24_dev->m_mutex); if(count > AT24CXX_SIZE){ retval = -1; goto err; } //offset address send_buff[len++] = i2c_RxAddr&0xff; retval = copy_from_user(&send_buff[len], buff, count); msgs[0].addr = at24_dev->m_client->addr; // address of slave device msgs[0].flags = 0; // 0:write, 1:read msgs[0].len = len+count; msgs[0].buf = send_buff; retval = i2c_transfer(at24_dev->m_client->adapter, msgs, ARRAY_SIZE(msgs)); if(retval > 0){ retval = 0; //printk(KERN_INFO "my_at24c_write successful\r\n"); } err: mutex_unlock(&at24_dev->m_mutex); return retval; } static loff_t my_at24c_llseek(struct file * flip, loff_t offset, int flag) { loff_t retval = 0; struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data; //printk(KERN_INFO "%s enter,offset=%d, flag=%d\r\n", __FUNCTION__, (int)offset, flag); mutex_lock(&at24_dev->m_mutex); if(flag == SEEK_SET){ if(offset >= AT24CXX_SIZE){ i2c_RxAddr = 0; }else{ i2c_RxAddr = offset; } } mutex_unlock(&at24_dev->m_mutex); return retval; } static int my_at24c_open(struct inode * node, struct file * flip) { mutex_init(&my_at24_devp->m_mutex); i2c_RxAddr = 0; flip->private_data = my_at24_devp; //将at24结构存到flip中,便于后续读写使用 return 0; } static long my_at24c_ioctl(struct file *flip, unsigned int cmd, unsigned long arg) { return 0; } static int my_at24c_release (struct inode *inodep, struct file *flip) { flip->private_data = NULL; i2c_RxAddr = 0; return 0; } struct file_operations my_at24_fops = { .owner = THIS_MODULE, .llseek = my_at24c_llseek, .read = my_at24c_read, .write= my_at24c_write, .open = my_at24c_open, .compat_ioctl = my_at24c_ioctl, .release = my_at24c_release, }; /*will be called when the device and driver matched success*/ static int my_at24_probe(struct i2c_client *client, const struct i2c_device_id *id) { int retval = 0; my_at24_devp = kmalloc(sizeof(struct my_at24_dev), GFP_KERNEL); if(!my_at24_devp){ retval = -ENOMEM; goto err; } memset (my_at24_devp, 0, sizeof (struct my_at24_dev)); my_at24_devp->m_client = client; //register char device my_at24_devp->m_major = register_chrdev(0, MY_DRV_NAME, &my_at24_fops); if(my_at24_devp->m_major < 0){ printk (KERN_NOTICE "register_chrdev failed return %d",my_at24_devp->m_major); goto err; } //create class my_at24_devp->m_eeprom_class = class_create(THIS_MODULE, MY_CLASS); if (IS_ERR(my_at24_devp->m_eeprom_class)) { printk(KERN_ERR "%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(my_at24_devp->m_major, MY_DRV_NAME); retval = -1; goto err; } device_create(my_at24_devp->m_eeprom_class, NULL, MKDEV(my_at24_devp->m_major,0), NULL, "%s%d",MY_DRV_NAME, 0); // 在 /dev下创建 MY_DRV_NAME 设备文件(/dev/Myled0, 1, ...) err: return retval; } static int my_at24_remove(struct i2c_client *client) { int retval = 0; device_destroy(my_at24_devp->m_eeprom_class, MKDEV(my_at24_devp->m_major, 0)); class_destroy(my_at24_devp->m_eeprom_class); unregister_chrdev(my_at24_devp->m_major, MY_DRV_NAME); kfree (my_at24_devp); return retval; } static struct i2c_driver my_at24_driver = { .driver = { .name = "at24cxx", .owner = THIS_MODULE, }, .probe = my_at24_probe, .remove = my_at24_remove, .id_table = my_at24_ids, }; static int __init my_at24_init(void) { return i2c_add_driver(&my_at24_driver); } module_init(my_at24_init); static void __exit my_at24_exit(void) { i2c_del_driver(&my_at24_driver); } module_exit(my_at24_exit); MODULE_DESCRIPTION("Driver for I2C EEPROM"); MODULE_AUTHOR("Daniel.Dai"); MODULE_LICENSE("GPL");