前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >I2C总线架构 之 总线驱动

I2C总线架构 之 总线驱动

作者头像
开源519
发布2020-08-28 11:14:27
1.4K0
发布2020-08-28 11:14:27
举报
文章被收录于专栏:开源519开源519

引言

单片机的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总线驱动也一样被抽象成结构体:

代码语言:javascript
复制
/*
 * 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中的代码。

代码语言:javascript
复制
//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结构体:

代码语言:javascript
复制
 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核心文章总结。

总线驱动注册流程图如下:

代码语言:javascript
复制
--- 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 *))

总结

  1. i2c总线驱动,由系统或者厂家实现,开机就会自动注册。
  2. 总线驱动会被抽象成adapter结构体,代码中实例其结构体成员,利用i2c-core的API将此结构体注册到内核。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源519 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 架构设计
  • i2c总线结构体
  • 驱动代码
  • 内核实现分析
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档