一、结构体分析; (1)struct i2c_adapter { struct module *owner; unsigned int id __deprecated; unsigned int class; 该I2C bus支持哪些类型的slave device,只有匹配的slave device才能和bus绑定。具体的类型包括(可参考include/linux/i2c.h中的定义和注释): I2C_CLASS_HWMON,硬件监控类,如lm_sensors等; I2C_CLASS_DDC,DDC是数字显示通道(Digital Display Channel)的意思, 通常用于显示设备信息的获取; I2C_CLASS_SPD,存储类的模组; I2C_CLASS_DEPRECATED,不再使用的class。 const struct i2c_algorithm *algo; 该总线上的通信方法 :时钟控制,s/p,中断等 void *algo_data; 通信方法的附加数据 struct rt_mutex bus_lock; 对所有设备的锁结构 int timeout; 超时时间,在iic总线上发送信号多久没回的超时时间 int retries; 重复的次数 struct device dev; 适配器设备 int nr; 总线的编号 char name[48]; 名字 struct completion dev_released; struct mutex userspace_clients_lock; struct list_head userspace_clients; }; (2)struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num); 消息发送函数指针 ,不同适配器其实现不一样 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);//系统管理总线 u32 (*functionality) (struct i2c_adapter *); 适配器所支持的功能,functionality,通过一个bitmap,告诉调用者该I2C adapter支持的功能,包括(具体可参考include/uapi/linux/i2c.h中的定义和注释): I2C_FUNC_I2C,支持传统的I2C功能; I2C_FUNC_10BIT_ADDR,支持10bit地址; I2C_FUNC_PROTOCOL_MANGLING,支持非标准的协议行为 I2C_FUNC_NOSTART,支持不需要发送START信号的I2C传输 I2C_FUNC_SMBUS_xxx,SMBUS相关的功能,不再详细介绍。 }; (3)struct i2c_driver { unsigned int class; int (*attach_adapter)(struct i2c_adapter *); int (*detach_adapter)(struct i2c_adapter *); 标准的驱动模型 int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); void (*shutdown)(struct i2c_client *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); void (*alert)(struct i2c_client *, unsigned int data); int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);类似ioctl struct device_driver driver;用于设备驱动模型 const struct i2c_device_id *id_table; 列出该设备驱动所支持的设备 int (*detect)(struct i2c_client *, struct i2c_board_info *);设备探测的回调函数 const unsigned short *address_list;用于探测的i2c设备地址 struct list_head clients; /* 链接所有探测出的i2c设备 */ }; (4)struct i2c_client { i2c_client 代表i2c从设备 unsigned short flags; I2C_CLIENT_TEN表示设备使用的是10位的地址,I2C_CLIENT_PEC表示使用SMBus包用在错误检查 unsigned short addr; I2C设备在总线上的的地址 char name[I2C_NAME_SIZE]; 设备名 struct i2c_adapter *adapter; 指向该I2C设备挂载的I2C适配器 struct i2c_driver *driver; 指向支持该I2C设备的驱动 struct device dev; 用于总线设备驱动模型 int irq; 该设备能产生的中断号 struct list_head detected; }; (5)struct i2c_msg { I2C总线上传输信息的小单位 __u16 addr i2c 设备地址,可以是10位的地址,如果是10位的地址flags标志中需设置I2C_M_TEN __u16 flags I2C_M_RD 读标志,所有的适配器都必须支持,其他的标志见I2C_FUNC_* #define I2C_M_TEN 0x0010 10位的从设备的地址 #define I2C_M_RD 0x0001 从I2C设备中读数据 #define I2C_M_NOSTART 0x4000 读写混合操作的情况下,假如要传输多个msg(以2个为例),如果第二个msg携带了该标志,则不再发送'S Addr Wr/Rd [A]'信号,即从 S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P 变为 S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P S Addr Rd [A] [Data] NA Data [A] P #define I2C_M_REV_DIR_ADDR 0x2000 将读写flag翻转,即读的时候发Wr信号,写的时候发Rd信号。至于为什么这么用,只有天知道。 #define I2C_M_IGNORE_NAK 0x1000 读操作的时候,忽略slave返回的NA,把它当做ACK信号,继续读取。还别说,那真有那比较贱的slave,比如电视(通过I2C读取EDID的时候)。 #define I2C_M_NO_RD_ACK 0x0800 读操作的时候,忽略所有的NACK/ACK信号。 #define I2C_M_RECV_LEN 0x0400 SMBUS的一个flag,意义不明。 __u16 len; /* 消息长度 */ __u8 *buf; /* 消息指针*/ }; (6)struct i2c_board_info { 创建I2C设备的模版 char type[I2C_NAME_SIZE]; 设备类型,用于填充i2c_client.name unsigned short flags; 用于填充i2c_client.flags unsigned short addr; 用于填充i2c_client.addr void *platform_data; 存储i2c_client.dev.platform_data struct dev_archdata *archdata; 拷贝到i2c_client.dev.archdata #ifdef CONFIG_OF 指向打开固件的设备节点 struct device_node *of_node; #endif int irq; 存储到i2c_client.irq }; 二、框架分析:目录 核心层i2c-core.c, 总线驱动i2c-s3c2410.c , 通用设备驱动 ------------------------------------------------------------------------------------------------------------------- (1)目录 I2C core使用I2C adapter和I2C algorithm两个子模块抽象I2C controller的功能, 使用I2C client和I2C driver抽象I2C slave device的功能(对应设备模型中的 device和device driver)。另外,基于I2C协议,通过smbus模块实现SMBus(System Management Bus,系统管理总线)的功能。 /driver/i2c/busses:i2c控制器驱动 /driver/i2c/algos :i2c总线通信方法 /driver/i2c/muxes :用于实现I2C bus的多路复用功能,属于奇葩的冷门功能。 ------------------------------------------------------------------------------------------------------------------- (2)i2c-core.c分析 [1]大框架i2c_bus postcore_initcall(i2c_init); retval = bus_register(&i2c_bus_type); //注册i2c_bus:/sys/bus/i2c i2c_adapter_compat_class = class_compat_register("i2c-adapter");//注册适配器class :/sys/class/i2c-adapter retval = i2c_add_driver(&dummy_driver); //注册i2c驱动dummy_driver:/sys/bus/i2c/driver/dummy 提供的功能函数: 注册注销i2c_adapter: int i2c_add_adapter(struct i2c_adapter *adapter); void i2c_del_adapter(struct i2c_adapter *adap); 注册注销i2c_driver: int i2c_register_driver(struct module *owner, struct i2c_driver *driver); void i2c_del_driver(struct i2c_driver *driver); 数据传输函数: int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int i2c_master_send(const struct i2c_client *client, const char *buf, int count); int i2c_master_recv(const struct i2c_client *client, char *buf, int count); 备注1: struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm = &i2c_device_pm_ops, }; 备注2: static struct i2c_driver dummy_driver = { .driver.name = "dummy", .probe = dummy_probe, .remove = dummy_remove, .id_table = dummy_id, }; ------------------------------------------------------------------------------------------------------------------- (3)总线适配器驱动i2c-s3c2410.c分析:主要功能:分配设置并初始化i2c相关设备结构体成员,初始化iic适配器,并注册i2c_adapter, 从设备树、acpi(高级配置与电源管理接口),i2c_board_list中获取该i2c_adapter所支持的iic从设备并生成i2c_client [1]大框架i2c_adapter: subsys_initcall(i2c_adap_s3c_init); i2c_adap_s3c_init(void) platform_driver_register(&s3c24xx_i2c_driver); 备注1: static struct platform_driver s3c24xx_i2c_driver = { .probe = s3c24xx_i2c_probe, .remove = s3c24xx_i2c_remove, .id_table = s3c24xx_driver_ids, .driver = { .owner = THIS_MODULE, .name = "s3c-i2c", .pm = S3C24XX_DEV_PM_OPS, .of_match_table = of_match_ptr(s3c24xx_i2c_match), }, }; adapter匹配方式: [1.1].id_table = s3c24xx_driver_ids, static struct platform_device_id s3c24xx_driver_ids[] = { { .name = "s3c2410-i2c", .driver_data = 0, }, { .name = "s3c2440-i2c", .driver_data = QUIRK_S3C2440, }, { .name = "s3c2440-hdmiphy-i2c", .driver_data = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO, }, { }, }; [1.2].name = "s3c-i2c", [1.3].of_match_table = of_match_ptr(s3c24xx_i2c_match), #ifdef CONFIG_OF static const struct of_device_id s3c24xx_i2c_match[] = { { .compatible = "samsung,s3c2410-i2c", .data = (void *)0 }, { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 }, { .compatible = "samsung,s3c2440-hdmiphy-i2c", .data = (void *)(QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO) }, { .compatible = "samsung,exynos5440-i2c", .data = (void *)(QUIRK_S3C2440 | QUIRK_NO_GPIO) }, { .compatible = "samsung,exynos5-sata-phy-i2c", .data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) }, {}, }; MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match); #endif [2]匹配成功.probe = s3c24xx_i2c_probe,:主要功能:分配设置并初始化i2c相关设备结构体成员,初始化iic适配器,并注册i2c_adapter static int s3c24xx_i2c_probe(struct platform_device *pdev) /*准备好指针变量,准备从传入的对象提取数据*/ struct s3c24xx_i2c *i2c; struct s3c2410_platform_i2c *pdata = NULL; struct resource *res; if (!pdev->dev.of_node) { /*如果pdev->dev.of_node为空*/ /*则从platform->dev->platform_data进行相应的提取, 该platform_data在platform_device设置是已经进行了相应的填充*/----------------------------》先主线,再找 pdata = dev_get_platdata(&pdev->dev); /*分配struct s3c24xx_i2c、s3c2410_platform_i2c相应的结构体空间*/ /*剖析devm_kzalloc与kzalloc的区别:一个专用于设备空间分配,当设备移除或驱动移除时自动释放空间,另一个需要手动释放*/ i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL); i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); /*如果s3c2410_platform_i2c空间被分配成功,则将pdata拷贝至i2c->pdata,反之从设备树获取并解析数据存储到i2c->pdata*/ if (pdata) memcpy(i2c->pdata, pdata, sizeof(*pdata)); else s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c); /*填充结构体成员*/ strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &s3c24xx_i2c_algorithm;//i2c数据传输算法----------------------------》先主线,再找 i2c->adap.retries = 2; i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; i2c->tx_setup = 50; /*初始化等待队列头*/ init_waitqueue_head(&i2c->wait); i2c->dev = &pdev->dev; /*获取i2c时钟*/ i2c->clk = devm_clk_get(&pdev->dev, "i2c"); /*获取i2c_adapter寄存器基地址并映射*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); i2c->regs = devm_ioremap_resource(&pdev->dev, res); /*将i2c赋值到i2c->adap.algo_data为了xfer接口函数调用*/ i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; /*获取管脚信息*/ i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev); /*如果i2c->pdata->cfg_gpio存在则调用cfg_gpio配置相应管脚*/ if (i2c->pdata->cfg_gpio) { i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));----------------------------》先主线,再找 } else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) { return -EINVAL; } ret = s3c24xx_i2c_init(i2c);//初始化iic控制器,时钟频率等 i2c->adap.nr = i2c->pdata->bus_num; i2c->adap.dev.of_node = pdev->dev.of_node; /*注册适配器*/ ret = i2c_add_numbered_adapter(&i2c->adap); /*将i2c设置为platform私有数据:platform_device->dev->p->driver_data*/ platform_set_drvdata(pdev, i2c); /*设置dev/adapter电源管理*/ pm_runtime_enable(&pdev->dev); pm_runtime_enable(&i2c->adap.dev); [3] 注册适配器: ret = i2c_add_numbered_adapter(&i2c->adap); if (adap->nr == -1) /*如果 adap->nr = -1 ,则动态分配总线id:i2c-0bus*/ return i2c_add_adapter(adap); 走这条线--------> return __i2c_add_numbered_adapter(adap);//静态 /*调用idr_alloc,动态分配一个id号,并将该id号做为i2c_adapter的I2C总线号。关于idr机制,我们不详细分析, 只需要知道它是一种快速索引机制,它将一个整数ID与一个需要被索引的指针建立联系,方便进行查找。*/ id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1,GFP_KERNEL); return i2c_register_adapter(adap); INIT_LIST_HEAD(&adap->userspace_clients);//初始化i2c_adapter上连接i2c_client的链表 adap->dev.bus = &i2c_bus_type; //i2c_adapter也算是一个设备,其挂载在i2c_bus上,此处指定挂载总线 adap->dev.type = &i2c_adapter_type; //指定设备类型为i2c_adapter_type,即当对i2c_adapter进行相应操作时执行 //i2c_adapter_type中相应的操作方法----------->关联应用层创建i2c_client res = device_register(&adap->dev); //将i2c_adapter注册进i2c_bus上 创建i2c_client的几种方式: (1)of_i2c_register_devices(adap); for_each_available_child_of_node(adap->dev.of_node, node);//查找i2c_adapter下的指代i2c_cient的子节点 if (of_modalias_node(node, info.type, sizeof(info.type)) < 0)//获取i2c_adapter子节点下compatible的“,”后的字符串,存放至i2c_board_info.type中 addr = of_get_property(node, "reg", &len);//获取i2c_adapter子节点下获取reg info.addr = be32_to_cpup(addr);//大小端转换,存放至i2c_board_info.addr中 info.irq = irq_of_parse_and_map(node, 0);//获取i2c_adapter子节点下获取irq,存放至i2c_board_info.irq中 info.of_node = of_node_get(node);//获取i2c_adapter子节点下的子节点,存放至i2c_board_info.of_node中 info.archdata = &dev_ad;//填充i2c_board_info.archdata if (of_get_property(node, "wakeup-source", NULL))//如果i2c_adapter属性wakeup-source存在键值,则将i2c_board_info.flags设置为I2C_CLIENT_WAKE info.flags |= I2C_CLIENT_WAKE; //在i2c_adapter上根据i2c_baord_info信息创建i2c_client result = i2c_new_device(adap, &info); (2)acpi_i2c_register_devices(adap); (3)if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap);//扫描板级信息 //如果devinfo->busnum == adapter->nr的总线号相等,则尝试根据i2c_board_info的信息创建i2c_client if (devinfo->busnum == adapter->nr&& !i2c_new_device(adapter,&devinfo->board_info)) #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) --------------------------------------------------------------- #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ((TYPE *)0):将0强制转换为type类型的一个指针; &((TYPE *)0)->MEMBER:获取以0地址为开头的type类型结构体成员member的地址; ((size_t) &((TYPE *)0)->MEMBER):强制转换为long unsigned int类型:即一个数字也就是MEMBER相对于TYPE的地址偏移 --------------------------------------------------------------- ((type *)0):将0强制转换为type类型的一个指针; (((type *)0)->member):获取以0地址为开头的type类型结构体成员member的地址; (((type *)0)->member) * __mptr = (ptr):将ptr指针赋值给__mptr typeof(((type *)0)->member) * __mptr = (ptr);//提取该指针指向的变量的类型 -------------------------------------------------------------------------- (type *)((char *)__mptr - offsetof(type, member)); //强制转换__mptr为char*,加减一为一 (4)/* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); //对于挂接在i2c_bus上的每一个设备,均可调用__process_new_adapter while ((drv = next_driver(&i)) && !error) error = fn(drv, data); static int __process_new_adapter(struct device_driver *d, void *data)分析 i2c_do_add_adapter(to_i2c_driver(d), data); i2c_detect(adap, driver); int adap_id = i2c_adapter_id(adapter);//获取i2c_adapter的id号 address_list = driver->address_list; //获取i2c_driver支持的设备地址数组 temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);//分配i2c_client空间 temp_client->adapter = adapter;//绑定设备与适配器 temp_client->addr = address_list[i];//将i2c_driver支持的设备地址赋值 err = i2c_detect_address(temp_client, driver);//探测该设备地址的设备是否真实存在? err = i2c_check_addr_validity(addr);//设备地址是否有效 if (i2c_check_addr_busy(adapter, addr));//设备地址是否被占用? memset(&info, 0, sizeof(struct i2c_board_info));//清空i2c_board_info info.addr = addr;//赋值地址 err = driver->detect(temp_client, &info);//探测 if (info.type[0] == '\0') //如果设备名称无则报错,相反则: client = i2c_new_device(adapter, &info);//创建相应的i2c_client if (client) list_add_tail(&client->detected, &driver->clients);//将i2c_client挂接到i2c_driver支持的链表上 ========================================== 函数宏DEVICE_ATTR内封装的是__ATTR(_name,_mode,_show,_stroe)方法,_show表示的是读方法,_stroe表示的是写方法。 当然_ATTR不是独生子女,他还有一系列的姊妹__ATTR_RO宏只有读方法,__ATTR_NULL等等 如 对设备的使用 DEVICE_ATTR ,对总线使用 BUS_ATTR ,对驱动使用 DRIVER_ATTR ,对类 别 (class) 使用 CLASS_ATTR, 这四个高级的宏来自于<include/linux/device.h> DEVICE_ATTR 宏声明有四个参数,分别是名称、权限位、读函数、写函数。其中读函数和写函数是读写功能函数的函数名。 如果你完成了DEVICE_ATTR函数宏的填充,下面就需要创建接口了 例如: static DEVICE_ATTR(polling, S_IRUGO | S_IWUSR, show_polling, set_polling); static struct attribute *dev_attrs[] = { &dev_attr_polling.attr, NULL, }; 当你想要实现的接口名字是polling的时候,需要实现结构体struct attribute *dev_attrs[] 其中成员变量的名字必须是&dev_attr_polling.attr 然后再封装 static struct attribute_group dev_attr_grp = { .attrs = dev_attrs, }; 在利用sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp);创建接口 ========================================== 备注1: struct s3c24xx_i2c { wait_queue_head_t wait;//等待队列 struct i2c_msg *msg;//i2c_msg unsigned int irq; //中断号 struct clk *clk; //时钟 struct device *dev;//设备模型 struct i2c_adapter adap;//适配器结构体 }; ------------------------------------------------------------------------------------------------------------------- (4)通用设备驱动分析i2c-dev.c: [1]代码分析 module_init(i2c_dev_init); res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);//注册主设备号为89的字符设备驱动,相应 i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");//创建i2c_class res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);//事件通知链相关 i2c_for_each_dev(NULL, i2cdev_attach_adapter);//查找i2c_bus上的相关设备 res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn); while ((dev = next_device(&i)) && !error)//找到设备时执行回调函数fn error = fn(dev, data); i2cdev_attach_adapter if (dev->type != &i2c_adapter_type)//如果i2c_bus上不是adapter,则返回0 adap = to_i2c_adapter(dev);//获取i2c_adapter i2c_dev = get_free_i2c_dev(adap);//分配i2c_dev i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL); i2c_dev->adap = adap;//绑定adapter与i2c_adapter list_add_tail(&i2c_dev->list, &i2c_dev_list);//将i2c_dev添加至i2c_dev_list链表 i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d", adap->nr);//创建对应的i2c_adapter节点 备注一: static const struct file_operations i2cdev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = i2cdev_read, .write = i2cdev_write, .unlocked_ioctl = i2cdev_ioctl, .open = i2cdev_open, .release = i2cdev_release, }; [2] 通用iic代码使用配置: i2c-dev驱动的使用 3.1配置内核 Device Drivers ---> -*- I2C support ---> <*> I2C device interface [3]程序编写 (1)编写配置,读取寄存器数据的子函数 (2)打开 (3)设置从机地址 + 超时时间 + 重试次数 (4)设置寄存器 (5)读取数据 |
热点新闻
讲师博文
一、结构体分析; (1)struct i2c_adapter { struct module *owner; unsigned int id __deprecated; unsigned int class; 该I2C bus支持哪些类型的slave device,只有匹配的slave device才能和bus绑定。具体的类型...
一、结构体分析; (1)struct i2c_adapter { struct module *owner; unsigned int id __deprecated; unsigned int class; 该I2C bus支持哪些类型的slave device,只有匹配的slave device才能和bus绑定。具体的类型包括(可参考include/linux/i2c.h中的定义和注释): I2C_CLASS_HWMON,硬件监控类,如lm_sensors等; I2C_CLASS_DDC,DDC是数字显示通道(Digital Display Channel)的意思, 通常用于显示设备信息的获取; I2C_CLASS_SPD,存储类的模组; I2C_CLASS_DEPRECATED,不再使用的class。 const struct i2c_algorithm *algo; 该总线上的通信方法 :时钟控制,s/p,中断等 void *algo_data; 通信方法的附加数据 struct rt_mutex bus_lock; 对所有设备的锁结构 int timeout; 超时时间,在iic总线上发送信号多久没回的超时时间 int retries; 重复的次数 struct device dev; 适配器设备 int nr; 总线的编号 char name[48]; 名字 struct completion dev_released; struct mutex userspace_clients_lock; struct list_head userspace_clients; }; (2)struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num); 消息发送函数指针 ,不同适配器其实现不一样 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);//系统管理总线 u32 (*functionality) (struct i2c_adapter *); 适配器所支持的功能,functionality,通过一个bitmap,告诉调用者该I2C adapter支持的功能,包括(具体可参考include/uapi/linux/i2c.h中的定义和注释): I2C_FUNC_I2C,支持传统的I2C功能; I2C_FUNC_10BIT_ADDR,支持10bit地址; I2C_FUNC_PROTOCOL_MANGLING,支持非标准的协议行为 I2C_FUNC_NOSTART,支持不需要发送START信号的I2C传输 I2C_FUNC_SMBUS_xxx,SMBUS相关的功能,不再详细介绍。 }; (3)struct i2c_driver { unsigned int class; int (*attach_adapter)(struct i2c_adapter *); int (*detach_adapter)(struct i2c_adapter *); 标准的驱动模型 int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); void (*shutdown)(struct i2c_client *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); void (*alert)(struct i2c_client *, unsigned int data); int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);类似ioctl struct device_driver driver;用于设备驱动模型 const struct i2c_device_id *id_table; 列出该设备驱动所支持的设备 int (*detect)(struct i2c_client *, struct i2c_board_info *);设备探测的回调函数 const unsigned short *address_list;用于探测的i2c设备地址 struct list_head clients; /* 链接所有探测出的i2c设备 */ }; (4)struct i2c_client { i2c_client 代表i2c从设备 unsigned short flags; I2C_CLIENT_TEN表示设备使用的是10位的地址,I2C_CLIENT_PEC表示使用SMBus包用在错误检查 unsigned short addr; I2C设备在总线上的的地址 char name[I2C_NAME_SIZE]; 设备名 struct i2c_adapter *adapter; 指向该I2C设备挂载的I2C适配器 struct i2c_driver *driver; 指向支持该I2C设备的驱动 struct device dev; 用于总线设备驱动模型 int irq; 该设备能产生的中断号 struct list_head detected; }; (5)struct i2c_msg { I2C总线上传输信息的小单位 __u16 addr i2c 设备地址,可以是10位的地址,如果是10位的地址flags标志中需设置I2C_M_TEN __u16 flags I2C_M_RD 读标志,所有的适配器都必须支持,其他的标志见I2C_FUNC_* #define I2C_M_TEN 0x0010 10位的从设备的地址 #define I2C_M_RD 0x0001 从I2C设备中读数据 #define I2C_M_NOSTART 0x4000 读写混合操作的情况下,假如要传输多个msg(以2个为例),如果第二个msg携带了该标志,则不再发送'S Addr Wr/Rd [A]'信号,即从 S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P 变为 S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P S Addr Rd [A] [Data] NA Data [A] P #define I2C_M_REV_DIR_ADDR 0x2000 将读写flag翻转,即读的时候发Wr信号,写的时候发Rd信号。至于为什么这么用,只有天知道。 #define I2C_M_IGNORE_NAK 0x1000 读操作的时候,忽略slave返回的NA,把它当做ACK信号,继续读取。还别说,那真有那比较贱的slave,比如电视(通过I2C读取EDID的时候)。 #define I2C_M_NO_RD_ACK 0x0800 读操作的时候,忽略所有的NACK/ACK信号。 #define I2C_M_RECV_LEN 0x0400 SMBUS的一个flag,意义不明。 __u16 len; /* 消息长度 */ __u8 *buf; /* 消息指针*/ }; (6)struct i2c_board_info { 创建I2C设备的模版 char type[I2C_NAME_SIZE]; 设备类型,用于填充i2c_client.name unsigned short flags; 用于填充i2c_client.flags unsigned short addr; 用于填充i2c_client.addr void *platform_data; 存储i2c_client.dev.platform_data struct dev_archdata *archdata; 拷贝到i2c_client.dev.archdata #ifdef CONFIG_OF 指向打开固件的设备节点 struct device_node *of_node; #endif int irq; 存储到i2c_client.irq }; 二、框架分析:目录 核心层i2c-core.c, 总线驱动i2c-s3c2410.c , 通用设备驱动 ------------------------------------------------------------------------------------------------------------------- (1)目录 I2C core使用I2C adapter和I2C algorithm两个子模块抽象I2C controller的功能, 使用I2C client和I2C driver抽象I2C slave device的功能(对应设备模型中的 device和device driver)。另外,基于I2C协议,通过smbus模块实现SMBus(System Management Bus,系统管理总线)的功能。 /driver/i2c/busses:i2c控制器驱动 /driver/i2c/algos :i2c总线通信方法 /driver/i2c/muxes :用于实现I2C bus的多路复用功能,属于奇葩的冷门功能。 ------------------------------------------------------------------------------------------------------------------- (2)i2c-core.c分析 [1]大框架i2c_bus postcore_initcall(i2c_init); retval = bus_register(&i2c_bus_type); //注册i2c_bus:/sys/bus/i2c i2c_adapter_compat_class = class_compat_register("i2c-adapter");//注册适配器class :/sys/class/i2c-adapter retval = i2c_add_driver(&dummy_driver); //注册i2c驱动dummy_driver:/sys/bus/i2c/driver/dummy 提供的功能函数: 注册注销i2c_adapter: int i2c_add_adapter(struct i2c_adapter *adapter); void i2c_del_adapter(struct i2c_adapter *adap); 注册注销i2c_driver: int i2c_register_driver(struct module *owner, struct i2c_driver *driver); void i2c_del_driver(struct i2c_driver *driver); 数据传输函数: int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int i2c_master_send(const struct i2c_client *client, const char *buf, int count); int i2c_master_recv(const struct i2c_client *client, char *buf, int count); 备注1: struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm = &i2c_device_pm_ops, }; 备注2: static struct i2c_driver dummy_driver = { .driver.name = "dummy", .probe = dummy_probe, .remove = dummy_remove, .id_table = dummy_id, }; ------------------------------------------------------------------------------------------------------------------- (3)总线适配器驱动i2c-s3c2410.c分析:主要功能:分配设置并初始化i2c相关设备结构体成员,初始化iic适配器,并注册i2c_adapter, 从设备树、acpi(高级配置与电源管理接口),i2c_board_list中获取该i2c_adapter所支持的iic从设备并生成i2c_client [1]大框架i2c_adapter: subsys_initcall(i2c_adap_s3c_init); i2c_adap_s3c_init(void) platform_driver_register(&s3c24xx_i2c_driver); 备注1: static struct platform_driver s3c24xx_i2c_driver = { .probe = s3c24xx_i2c_probe, .remove = s3c24xx_i2c_remove, .id_table = s3c24xx_driver_ids, .driver = { .owner = THIS_MODULE, .name = "s3c-i2c", .pm = S3C24XX_DEV_PM_OPS, .of_match_table = of_match_ptr(s3c24xx_i2c_match), }, }; adapter匹配方式: [1.1].id_table = s3c24xx_driver_ids, static struct platform_device_id s3c24xx_driver_ids[] = { { .name = "s3c2410-i2c", .driver_data = 0, }, { .name = "s3c2440-i2c", .driver_data = QUIRK_S3C2440, }, { .name = "s3c2440-hdmiphy-i2c", .driver_data = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO, }, { }, }; [1.2].name = "s3c-i2c", [1.3].of_match_table = of_match_ptr(s3c24xx_i2c_match), #ifdef CONFIG_OF static const struct of_device_id s3c24xx_i2c_match[] = { { .compatible = "samsung,s3c2410-i2c", .data = (void *)0 }, { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 }, { .compatible = "samsung,s3c2440-hdmiphy-i2c", .data = (void *)(QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO) }, { .compatible = "samsung,exynos5440-i2c", .data = (void *)(QUIRK_S3C2440 | QUIRK_NO_GPIO) }, { .compatible = "samsung,exynos5-sata-phy-i2c", .data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) }, {}, }; MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match); #endif [2]匹配成功.probe = s3c24xx_i2c_probe,:主要功能:分配设置并初始化i2c相关设备结构体成员,初始化iic适配器,并注册i2c_adapter static int s3c24xx_i2c_probe(struct platform_device *pdev) /*准备好指针变量,准备从传入的对象提取数据*/ struct s3c24xx_i2c *i2c; struct s3c2410_platform_i2c *pdata = NULL; struct resource *res; if (!pdev->dev.of_node) { /*如果pdev->dev.of_node为空*/ /*则从platform->dev->platform_data进行相应的提取, 该platform_data在platform_device设置是已经进行了相应的填充*/----------------------------》先主线,再找 pdata = dev_get_platdata(&pdev->dev); /*分配struct s3c24xx_i2c、s3c2410_platform_i2c相应的结构体空间*/ /*剖析devm_kzalloc与kzalloc的区别:一个专用于设备空间分配,当设备移除或驱动移除时自动释放空间,另一个需要手动释放*/ i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL); i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); /*如果s3c2410_platform_i2c空间被分配成功,则将pdata拷贝至i2c->pdata,反之从设备树获取并解析数据存储到i2c->pdata*/ if (pdata) memcpy(i2c->pdata, pdata, sizeof(*pdata)); else s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c); /*填充结构体成员*/ strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &s3c24xx_i2c_algorithm;//i2c数据传输算法----------------------------》先主线,再找 i2c->adap.retries = 2; i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; i2c->tx_setup = 50; /*初始化等待队列头*/ init_waitqueue_head(&i2c->wait); i2c->dev = &pdev->dev; /*获取i2c时钟*/ i2c->clk = devm_clk_get(&pdev->dev, "i2c"); /*获取i2c_adapter寄存器基地址并映射*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); i2c->regs = devm_ioremap_resource(&pdev->dev, res); /*将i2c赋值到i2c->adap.algo_data为了xfer接口函数调用*/ i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; /*获取管脚信息*/ i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev); /*如果i2c->pdata->cfg_gpio存在则调用cfg_gpio配置相应管脚*/ if (i2c->pdata->cfg_gpio) { i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));----------------------------》先主线,再找 } else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) { return -EINVAL; } ret = s3c24xx_i2c_init(i2c);//初始化iic控制器,时钟频率等 i2c->adap.nr = i2c->pdata->bus_num; i2c->adap.dev.of_node = pdev->dev.of_node; /*注册适配器*/ ret = i2c_add_numbered_adapter(&i2c->adap); /*将i2c设置为platform私有数据:platform_device->dev->p->driver_data*/ platform_set_drvdata(pdev, i2c); /*设置dev/adapter电源管理*/ pm_runtime_enable(&pdev->dev); pm_runtime_enable(&i2c->adap.dev); [3] 注册适配器: ret = i2c_add_numbered_adapter(&i2c->adap); if (adap->nr == -1) /*如果 adap->nr = -1 ,则动态分配总线id:i2c-0bus*/ return i2c_add_adapter(adap); 走这条线--------> return __i2c_add_numbered_adapter(adap);//静态 /*调用idr_alloc,动态分配一个id号,并将该id号做为i2c_adapter的I2C总线号。关于idr机制,我们不详细分析, 只需要知道它是一种快速索引机制,它将一个整数ID与一个需要被索引的指针建立联系,方便进行查找。*/ id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1,GFP_KERNEL); return i2c_register_adapter(adap); INIT_LIST_HEAD(&adap->userspace_clients);//初始化i2c_adapter上连接i2c_client的链表 adap->dev.bus = &i2c_bus_type; //i2c_adapter也算是一个设备,其挂载在i2c_bus上,此处指定挂载总线 adap->dev.type = &i2c_adapter_type; //指定设备类型为i2c_adapter_type,即当对i2c_adapter进行相应操作时执行 //i2c_adapter_type中相应的操作方法----------->关联应用层创建i2c_client res = device_register(&adap->dev); //将i2c_adapter注册进i2c_bus上 创建i2c_client的几种方式: (1)of_i2c_register_devices(adap); for_each_available_child_of_node(adap->dev.of_node, node);//查找i2c_adapter下的指代i2c_cient的子节点 if (of_modalias_node(node, info.type, sizeof(info.type)) < 0)//获取i2c_adapter子节点下compatible的“,”后的字符串,存放至i2c_board_info.type中 addr = of_get_property(node, "reg", &len);//获取i2c_adapter子节点下获取reg info.addr = be32_to_cpup(addr);//大小端转换,存放至i2c_board_info.addr中 info.irq = irq_of_parse_and_map(node, 0);//获取i2c_adapter子节点下获取irq,存放至i2c_board_info.irq中 info.of_node = of_node_get(node);//获取i2c_adapter子节点下的子节点,存放至i2c_board_info.of_node中 info.archdata = &dev_ad;//填充i2c_board_info.archdata if (of_get_property(node, "wakeup-source", NULL))//如果i2c_adapter属性wakeup-source存在键值,则将i2c_board_info.flags设置为I2C_CLIENT_WAKE info.flags |= I2C_CLIENT_WAKE; //在i2c_adapter上根据i2c_baord_info信息创建i2c_client result = i2c_new_device(adap, &info); (2)acpi_i2c_register_devices(adap); (3)if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap);//扫描板级信息 //如果devinfo->busnum == adapter->nr的总线号相等,则尝试根据i2c_board_info的信息创建i2c_client if (devinfo->busnum == adapter->nr&& !i2c_new_device(adapter,&devinfo->board_info)) #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) --------------------------------------------------------------- #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) ((TYPE *)0):将0强制转换为type类型的一个指针; &((TYPE *)0)->MEMBER:获取以0地址为开头的type类型结构体成员member的地址; ((size_t) &((TYPE *)0)->MEMBER):强制转换为long unsigned int类型:即一个数字也就是MEMBER相对于TYPE的地址偏移 --------------------------------------------------------------- ((type *)0):将0强制转换为type类型的一个指针; (((type *)0)->member):获取以0地址为开头的type类型结构体成员member的地址; (((type *)0)->member) * __mptr = (ptr):将ptr指针赋值给__mptr typeof(((type *)0)->member) * __mptr = (ptr);//提取该指针指向的变量的类型 -------------------------------------------------------------------------- (type *)((char *)__mptr - offsetof(type, member)); //强制转换__mptr为char*,加减一为一 (4)/* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); //对于挂接在i2c_bus上的每一个设备,均可调用__process_new_adapter while ((drv = next_driver(&i)) && !error) error = fn(drv, data); static int __process_new_adapter(struct device_driver *d, void *data)分析 i2c_do_add_adapter(to_i2c_driver(d), data); i2c_detect(adap, driver); int adap_id = i2c_adapter_id(adapter);//获取i2c_adapter的id号 address_list = driver->address_list; //获取i2c_driver支持的设备地址数组 temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);//分配i2c_client空间 temp_client->adapter = adapter;//绑定设备与适配器 temp_client->addr = address_list[i];//将i2c_driver支持的设备地址赋值 err = i2c_detect_address(temp_client, driver);//探测该设备地址的设备是否真实存在? err = i2c_check_addr_validity(addr);//设备地址是否有效 if (i2c_check_addr_busy(adapter, addr));//设备地址是否被占用? memset(&info, 0, sizeof(struct i2c_board_info));//清空i2c_board_info info.addr = addr;//赋值地址 err = driver->detect(temp_client, &info);//探测 if (info.type[0] == '\0') //如果设备名称无则报错,相反则: client = i2c_new_device(adapter, &info);//创建相应的i2c_client if (client) list_add_tail(&client->detected, &driver->clients);//将i2c_client挂接到i2c_driver支持的链表上 ========================================== 函数宏DEVICE_ATTR内封装的是__ATTR(_name,_mode,_show,_stroe)方法,_show表示的是读方法,_stroe表示的是写方法。 当然_ATTR不是独生子女,他还有一系列的姊妹__ATTR_RO宏只有读方法,__ATTR_NULL等等 如 对设备的使用 DEVICE_ATTR ,对总线使用 BUS_ATTR ,对驱动使用 DRIVER_ATTR ,对类 别 (class) 使用 CLASS_ATTR, 这四个高级的宏来自于<include/linux/device.h> DEVICE_ATTR 宏声明有四个参数,分别是名称、权限位、读函数、写函数。其中读函数和写函数是读写功能函数的函数名。 如果你完成了DEVICE_ATTR函数宏的填充,下面就需要创建接口了 例如: static DEVICE_ATTR(polling, S_IRUGO | S_IWUSR, show_polling, set_polling); static struct attribute *dev_attrs[] = { &dev_attr_polling.attr, NULL, }; 当你想要实现的接口名字是polling的时候,需要实现结构体struct attribute *dev_attrs[] 其中成员变量的名字必须是&dev_attr_polling.attr 然后再封装 static struct attribute_group dev_attr_grp = { .attrs = dev_attrs, }; 在利用sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp);创建接口 ========================================== 备注1: struct s3c24xx_i2c { wait_queue_head_t wait;//等待队列 struct i2c_msg *msg;//i2c_msg unsigned int irq; //中断号 struct clk *clk; //时钟 struct device *dev;//设备模型 struct i2c_adapter adap;//适配器结构体 }; ------------------------------------------------------------------------------------------------------------------- (4)通用设备驱动分析i2c-dev.c: [1]代码分析 module_init(i2c_dev_init); res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);//注册主设备号为89的字符设备驱动,相应 i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");//创建i2c_class res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);//事件通知链相关 i2c_for_each_dev(NULL, i2cdev_attach_adapter);//查找i2c_bus上的相关设备 res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn); while ((dev = next_device(&i)) && !error)//找到设备时执行回调函数fn error = fn(dev, data); i2cdev_attach_adapter if (dev->type != &i2c_adapter_type)//如果i2c_bus上不是adapter,则返回0 adap = to_i2c_adapter(dev);//获取i2c_adapter i2c_dev = get_free_i2c_dev(adap);//分配i2c_dev i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL); i2c_dev->adap = adap;//绑定adapter与i2c_adapter list_add_tail(&i2c_dev->list, &i2c_dev_list);//将i2c_dev添加至i2c_dev_list链表 i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d", adap->nr);//创建对应的i2c_adapter节点 备注一: static const struct file_operations i2cdev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = i2cdev_read, .write = i2cdev_write, .unlocked_ioctl = i2cdev_ioctl, .open = i2cdev_open, .release = i2cdev_release, }; [2] 通用iic代码使用配置: i2c-dev驱动的使用 3.1配置内核 Device Drivers ---> -*- I2C support ---> <*> I2C device interface [3]程序编写 (1)编写配置,读取寄存器数据的子函数 (2)打开 (3)设置从机地址 + 超时时间 + 重试次数 (4)设置寄存器 (5)读取数据 |
相关推荐
全国咨询热线:400-611-6270
?2004-2018华清远见教育科技集团 版权所有 京ICP备16055225号 京公海网安备11010802025203号