Linux的I2C子系统
i2c子系统中内容很多,相互交织,一下子讲不清楚。下面使用对象的思想,把一些硬件,算法,驱动等抽象为对象,使用对象来表达,然后由不同的对象组成一个整体。
下面先介绍各个部分抽象出来的对象。
对象结构体列举
1、设备结构体对象 i2c_client
这是设备信息对象描述的结构体,即设备i2c_device,它是对设备的硬件信息记录和描述,是要注册到系统中的,和i2c_driver 配对,即把硬件描述信息交给驱动,由驱动操作硬件完成功能。
struct i2c_client
{
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE]; //设备名称
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
2、驱动结构体对象 i2c_driver
从机设备驱动。和 i2c_client 匹配,通过硬件提供的信息操作硬件。这样做就是把驱动和设备进行分离。
struct i2c_driver
{
unsigned int class;
/* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this if you can, it will probably
* be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;//其中就有驱动的名称,将来和设备名称进行匹配
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
3、适配器结构体对象 i2c_adapter
这个结构体描述了SOC内部的I2C控制器对象的数据和操作方法。即控制器的控制代码,寄存器操作等。
struct i2c_adapter
{
struct module *owner;
unsigned int id;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48]; //适配器名称,可通过/sys/bus/i2c/devices/i2c-x/name访问
struct completion dev_released;
struct list_head userspace_clients;
};
4、算法结构体对象 i2c_algorithm
这个结构体对象描述了I2C算法,如时序的控制,通讯算法等。它一般是包含在 i2c_adapter 内部的。
struct i2c_algorithm
{
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
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);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);//当前的i2c控制器支持的功能
};
I2C 程序框架
经过抽象出来的对象,他们可以通过下面的框图进行组织。
I2C核心 i2c-core分析
I2C核心的代码在 /deivers/i2c/i2c-core.c。核心代码在系统初始化时,作为一个模块加载。
postcore_initcall(i2c_init);
module_exit(i2c_exit);
i2c_init 代码如下:
static int __init i2c_init(void)
{
retval = bus_register(&i2c_bus_type);//I2C总线注册
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
#endif
retval = i2c_add_driver(&dummy_driver);
}
bus_register(&i2c_bus_type) 是在注册I2C总线,结构体 i2c_bus_type 中有操作总线的函数。注册后就在 /sys/bus 目录下出现 i2c 目录。这个初始化主要任务就是注册总线。
总线的结构体定义如下:
struct bus_type i2c_bus_type =
{
.name = "i2c", //总线名称,/sys/bus/下出现这个名称
.match = i2c_device_match, //匹配函数
.probe = i2c_device_probe, //探测函数
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
i2c_add_driver(&dummy_driver) 注册驱动,这里了注册了一个空驱动 dummy。驱动出现在在/sys/bus/ 的dummy。
static struct i2c_driver dummy_driver =
{
.driver.name = "dummy", //空的驱动
.probe = dummy_probe,
.remove = dummy_remove,
.id_table = dummy_id,
};
下面对 i2c_bus_type 中的实现函数解析:
i2c_device_match
- 将设备和驱动进行匹配。使用设备和驱动的名字进行匹配。驱动维护了一个链表,存储所有的I2C驱动,设备就名称和驱动数组中的驱动名称进行配对,名称相同就匹配成功。
i2c_device_probe
- 当设备和驱动匹配成功了,就执行这个函数,这个函数的功能是探测设备的,对设备初始化设置,如果成功设置,就探测成功。
- 注意,当调用这个函数时,在它内部实际上是调用了 driver中的 probe函数,在这里是总线中的 probe。
i2c_device_remove
i2c_device_shutdown
I2C-client 和 I2C-driver 的注册函数
I2C核心提供了对I2C-client 和 I2C-driver 的注册函数;,面这些对象在不同的时期进行注册到系统中才能使用。注册函数如下。
I2C-driver 的注册函数:
static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver);//
}
I2C设备注册函数:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
status = device_register(&client->dev);//设备注册
}
i2c_adapter的注册:
i2c_add_adapter
i2c_add_numbered_adapter
I2C_adapter 分析
i2c-adapter 是SOC内部的i2c控制器,i2c属于一种总线,所以下面的代码使用了平台总线组织i2c控制器。平台总线用来管理驱动和设备的匹配等任务的。下面是i2c适配器驱动代码,文件名称i2c-s3c2410.c,它作为模块注册到系统中的,它属于平台总线驱动代码。
subsys_initcall(i2c_adap_s3c_init);
i2c_adap_s3c_init函数:
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);//用平台总线注册的
}
s3c24xx_i2c_driver结构体:
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,
},
};
上面的s3c24xx_i2c_probe初始化函数,是总线驱动和设备(I2C控制器 即device)进行匹配成功后执行的。执行probe函数的参数是匹配(mach)时传递过来的。这里的驱动是adapter总线 驱动,设备是adapter 设备。
s3c24xx_i2c_probe 主要完成的工作是adapter结构体的赋值和adapter接口注册。
下面是s3c24xx_i2c_probe大概的分析:
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c; //定义s3c24xx_i2c类型的指针,这个类型就包含了adapter结构体。
struct s3c2410_platform_i2c *pdata;
struct resource *res;
pdata = pdev->dev.platform_data;//设备具体的信息取出来赋值给pdata
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);//分配内存,填充
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //时序控制结构体,重要
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = 50;
......
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//对资源的申请,后面还有设置等。
ret = i2c_add_numbered_adapter(&i2c->adap);//注册adapter函数
}
s3c24xx_i2c_algorithm 时序控制结构体的实例化
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,//
.functionality = s3c24xx_i2c_func,//所支持的功能列举
};
I2C-client 和 I2C-driver 实例化
以触摸屏680为例,说明实例化。
- 驱动注册
static int __init gsl_ts_init(void)
{
ret = i2c_add_driver(&gsl_ts_driver);//注册驱动
}
驱动结构体:
static struct i2c_driver gsl_ts_driver =
{
.driver = {
.name = GSLX680_I2C_NAME,
.owner = THIS_MODULE,
},
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = gsl_ts_suspend,
.resume = gsl_ts_resume,
#endif
.probe = gsl_ts_probe,
.remove = __devexit_p(gsl_ts_remove),
.id_table = gsl_ts_id,
};
- 设备注册
smdkc110_machine_init()中对设备结构体信息填充,在 i2c_register_adapter 中完成注册设备。
static void __init smdkc110_machine_init(void)
{
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
}
i2c_devs0、i2c_devs1
/* I2C0 */
static struct i2c_board_info i2c_devs0[] __initdata = {
#ifdef CONFIG_SND_SOC_WM8580
{
I2C_BOARD_INFO("wm8580", 0x1b),
},
#endif
};
/* I2C1 */
static struct i2c_board_info i2c_devs1[] __initdata = {
#ifdef CONFIG_VIDEO_TV20
{
I2C_BOARD_INFO("s5p_ddc", (0x74>>1)),
},
#endif
};
i2c_board_info
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr; //i2c设备从从地址
void *platform_data; //i2c私有数据
struct dev_archdata *archdata;
#ifdef CONFIG_OF
struct device_node *of_node;
#endif
int irq; //外部中断, 和i2c中的中断没关系,即发生触摸动作时,通知主机,i2c取数据吧
};
如何注册device
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
{
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);//这里仅仅是维护了一个设备链表结构,还没有真正填充内容。
}
在 i2c_register_adapter 中真正创建了设备,调用关系如下:
i2c_register_adapter
—> i2c_scan_static_board_info //遍历设备链表,找出注册的代表设备的那个结构体
—>i2c_new_device
—>device_register