单片机的IIC编程中,如果我们直接一点,只需要控制IIC硬件GPIO脚,然后根据IIC协议模拟各种电平时序实现与IIC设备的通信。但是这种编程方法,移植性较差(假如新加了一种IIC设备,同样的代码,又要重新复制一份)。这种做法完全不适应Linux的通用性的设计理念,对于Linux来讲:同样的事情我只做一遍,向外提供接口,不管你是什么IIC设备挂载那条IIC总线上,都可以用。因此,这就需要Linux在代码架构上有非常严谨的模块化设计。
架构设计在Linux设计中,将I2C代码框架分为三个部分:I2C总线、I2C核心、I2C驱动。
- 「I2C核心(i2c-core):」 主要定义i2c驱动所用到的通用API,高内聚的代码会放到i2c-core.c。
- 「I2C总线驱动(i2c adapter):」 根据平台定制的i2c驱动,其中包含i2c传输的算法设计。主要工作负责生成i2c_client,注册适配器,以及i2c_client与i2c_driver的匹配。
- 「I2C设备驱动(i2c client driver):」 驱动I2C设备的代码。I2C设备驱动定义了外设的交互方式,与不同的I2C外设需要不同的设备驱动。I2C设备驱动对上和用户应用程序打交道,对下和I2C核心对接。
本篇主要对IIC总线驱动的总结。
i2c总线结构体Linux在分层中,必不可少的将每一层模块封装成一个结构体,然后将结构体作为一个与外接交互的桥梁。I2C总线驱动也一样被抽象成结构体:
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/struct i2c_adapter { struct module *owner; 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 */ const struct i2c_lock_operations *lock_ops; struct rt_mutex bus_lock; struct rt_mutex mux_lock;
int timeout; /* in jiffies */ int retries; struct device dev; /* the adapter device */
int nr; char name[48]; struct completion dev_released;
struct mutex userspace_clients_lock; struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info; const struct i2c_adapter_quirks *quirks;};
总线驱动采用platform虚拟总线架构,其中包括一些platform常规注册流程,主要关注probe中的代码。
//linux/drivers/i2c/busses/i2c-imx.c ……struct imx_i2c_struct { struct i2c_adapter adapter; struct clk *clk; void __iomem *base; wait_queue_head_t queue; unsigned long i2csr; unsigned int disable_delay; int stopped; unsigned int ifdr; /* IMX_I2C_IFDR */ unsigned int cur_clk; unsigned int bitrate; const struct imx_i2c_hwdata *hwdata; struct i2c_bus_recovery_info rinfo;
struct pinctrl *pinctrl; struct pinctrl_state *pinctrl_pins_default; struct pinctrl_state *pinctrl_pins_gpio;
struct imx_i2c_dma *dma;};……
static struct i2c_algorithm i2c_imx_algo = { .master_xfer = i2c_imx_xfer, .functionality = i2c_imx_func,};
static int i2c_imx_probe(struct platform_device *pdev){ const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids, &pdev->dev); struct imx_i2c_struct *i2c_imx; struct resource *res; struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev); void __iomem *base; int irq, ret; dma_addr_t phy_addr;
irq = platform_get_irq(pdev, 0);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res);
phy_addr = (dma_addr_t)res->start; i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL); if (!i2c_imx) return -ENOMEM;
if (of_id) i2c_imx->hwdata = of_id->data; else i2c_imx->hwdata = (struct imx_i2c_hwdata *) platform_get_device_id(pdev)->driver_data;
/* Setup i2c_imx driver structure */ strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name)); i2c_imx->adapter.owner = THIS_MODULE; i2c_imx->adapter.algo = &i2c_imx_algo; i2c_imx->adapter.dev.parent = &pdev->dev; i2c_imx->adapter.nr = pdev->id; i2c_imx->adapter.dev.of_node = pdev->dev.of_node; i2c_imx->base = base;
/* Get I2C clock */ i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
ret = clk_prepare_enable(i2c_imx->clk);
/* Request IRQ */ ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr, IRQF_NO_SUSPEND, pdev->name, i2c_imx);
/* Init queue */ init_waitqueue_head(&i2c_imx->queue);
/* Set up adapter data */ i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
/* Set up platform driver data */ platform_set_drvdata(pdev, i2c_imx);
ret = pm_runtime_get_sync(&pdev->dev);
/* Set up clock divider */ i2c_imx->bitrate = IMX_I2C_BIT_RATE; ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &i2c_imx->bitrate); if (ret < 0 && pdata && pdata->bitrate) i2c_imx->bitrate = pdata->bitrate;
/* Set up chip registers to defaults */ imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN, i2c_imx, IMX_I2C_I2CR); imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);
/* Init optional bus recovery function */ ret = i2c_imx_init_recovery_info(i2c_imx, pdev); /* Give it another chance if pinctrl used is not ready yet */
/* Add I2C adapter */ ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
/* Init DMA config if supported */ i2c_imx_dma_request(i2c_imx, phy_addr);
return 0; /* Return OK */
}……
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, }, .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);
static void __exit i2c_adap_imx_exit(void){ platform_driver_unregister(&i2c_imx_driver);}module_exit(i2c_adap_imx_exit);
以上代码有删减
内核实现分析在进入probe中,先填充i2c_imx成员adapter结构体:
i2c_imx->adapter.owner = THIS_MODULE; i2c_imx->adapter.algo = &i2c_imx_algo; //i2c数据传输接口 i2c_imx->adapter.dev.parent = &pdev->dev; i2c_imx->adapter.nr = pdev->id; //适配器编号 i2c_imx->adapter.dev.of_node = pdev->dev.of_node;
使用i2c_set_adapdata(&i2c_imx->adapter, i2c_imx)
将i2c_imx数据存入到adapter内部私有数据中,然后利用i2c-core中APIi2c_add_numbered_adapter(&i2c_imx->adapter)
将适配器adapter注册到内核中,在此过程中还会生成i2c_client,具体放到下一篇I2C核心文章总结。
总线驱动注册流程图如下:
--- drivers --- i2c --- i2c_core.c --- i2c_add_numbered_adapter( --- if (adap->nr == -1) | | struct i2c_adapter *adap) | i2c_add_adapter(adap); | | |- __i2c_add_numbered_adapter(adap); | |- __i2c_add_numbered_adapter( --- id = idr_alloc(&i2c_adapter_idr,adap,adap->nr,adap->nr+1,GFP_KERNEL) | | struct i2c_adapter *adap) |- i2c_register_adapter(adap) | |- i2c_register_adapter( --- INIT_LIST_HEAD(&adap->userspace_clients) | | struct i2c_adapter *adap) |- dev_set_name(&adap->dev, "i2c-%d", adap->nr) | | |- adap->dev.bus = &i2c_bus_type | | |- adap->dev.type = &i2c_adapter_type | | |- device_register(&adap->dev) | | |- bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); | |- __process_new_adapter( --- i2c_do_add_adapter(to_i2c_driver(d), data) | | struct device_driver *d, | | void *data) | |- i2c_do_add_adapter( --- i2c_detect(adap, driver) | | struct i2c_driver *driver, |- 这里是废弃的driver->attach_adapter方法 | | struct i2c_adapter *adap) | |- i2c_detect( --- address_list = driver->address_list | | struct i2c_adapter *adapter, |- if (!driver->detect || !address_list) | | struct i2c_driver *driver) | return 0; | | |- temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL) | | |- temp_client->adapter = adapter | | |- for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) { | | | temp_client->addr = address_list[i]; | | | err = i2c_detect_address(temp_client, driver); | | | err不是错误码,则break; | | | } | | |- kfree(temp_client) | |- i2c_detect_address( --- struct i2c_board_info info | | struct i2c_client *temp_client, |- adapter = temp_client->adapter | | struct i2c_driver *driver) |- addr = temp_client->addr | | |- info.addr = addr | | |- driver->detect(temp_client, &info) | | |- if (info.type[0] == '\0') | | | 报错; | | | else { | | | struct i2c_client *client; | | | client = i2c_new_device(adapter, &info); | | | list_add_tail(&client->detected, &driver->clients); | | | } | |- base --- bus.c --- bus_for_each_drv( --- while ((drv = next_driver(&i)) && !error) struct bus_type *bus, |- error = fn(drv, data); struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *))
- i2c总线驱动,由系统或者厂家实现,开机就会自动注册。
- 总线驱动会被抽象成adapter结构体,代码中实例其结构体成员,利用i2c-core的API将此结构体注册到内核。