一、结构体分析;
(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号