当前位置:   article > 正文

Linux驱动开发之i2c框架讲解到例程_请画出i2c驱动框架

请画出i2c驱动框架

前言

        本篇章在rk3399平台上,基于设备树的i2c驱动开发。i2c直接使用硬件i2c总线,体系结构分为3部分:I2C 核心、I2C 总线驱动和I2C 设备驱动。I2C 核心(i2c-core.c)提供了I2C 总线驱动和设备驱动的注册、注销方法等。我们主要了解Linux中i2c的基本框架,分为i2c主机驱动开发i2c设备驱动开发。主机驱动一般由芯片原厂开发,通常需要我们做的就是针对具体某个设备的设备驱动开发,硬件设备信息通过设备树描述。

1. i2c主机驱动框架

1.1 结构体描述

        i2c适配器驱动开发中,要用到两个重要的数据结构: i2c_adapteri2c_algorithm,结构体定义在 include/linux/i2c.h文件中。

i2c_adapter结构体中主要关注const struct i2c_algorithm *algo和struct device dev;dev对应具体i2c设备,查询设备树的对应节点。

i2c_algorithm结构体对外提供读写API函数;master_xfer就是 I2C适配器的传输函数,可以通过此函数来完成与 IIC设备之间的通信。smbus_xfer就是 SMBUS(系统管理)总线的传输函数。

  1. struct i2c_adapter {
  2. struct module *owner;
  3. unsigned int class; /* classes to allow probing for */
  4. const struct i2c_algorithm *algo; /* the algorithm to access the bus */
  5. void *algo_data;
  6. /* data fields that are valid for all devices */
  7. struct rt_mutex bus_lock;
  8. int timeout; /* in jiffies */
  9. int retries;
  10. struct device dev; /* the adapter device */
  11. int nr;
  12. char name[48];
  13. struct completion dev_released;
  14. struct mutex userspace_clients_lock;
  15. struct list_head userspace_clients;
  16. struct i2c_bus_recovery_info *bus_recovery_info;
  17. const struct i2c_adapter_quirks *quirks;
  18. };
  19. struct i2c_algorithm {
  20. /* If an adapter algorithm can't do I2C-level access, set master_xfer
  21. to NULL. If an adapter algorithm can do SMBus access, set
  22. smbus_xfer. If set to NULL, the SMBus protocol is simulated
  23. using common I2C messages */
  24. /* master_xfer should return the number of messages successfully
  25. processed, or a negative value on error */
  26. int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
  27. int num);
  28. int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
  29. unsigned short flags, char read_write,
  30. u8 command, int size, union i2c_smbus_data *data);
  31. /* To determine what the adapter supports */
  32. u32 (*functionality) (struct i2c_adapter *);
  33. #if IS_ENABLED(CONFIG_I2C_SLAVE)
  34. int (*reg_slave)(struct i2c_client *client);
  35. int (*unreg_slave)(struct i2c_client *client);
  36. #endif
  37. };

1.2 相关函数

注册函数:填充好 i2c_adapter结构体变量和设置完 i2c_algorithm中的 master_xfer函数后,需要向系统注册适配器驱动,函数原型如下(都可以注册,二选一):

int i2c_add_adapter(struct i2c_adapter *adapter)        //使用动态的总线号

int i2c_add_numbered_adapter(struct i2c_adapter *adap)    //使用静态的总线号

返回值: 0,成功;负值,失败。

注销函数:如果要删除 I2C适配器的话使用 i2c_del_adapter函数即可,函数原型如下:

void i2c_del_adapter(struct i2c_adapter * adap)

1.3 浅析i2c适配器驱动源码

        在内核中我们要怎么去查找到源码文件呢?可以通过设备树i2c节点中compatible字符串查找。例如i2c1中的"rockchip,rk3399-i2c",Linux内核中全局搜索该字符串可找到适配器驱动文件为

i2c-rk3x.c 。

        解析rk3x_i2c_probe函数:of_match_node查找节点,后面填充adapter结构体变量,platform_get_resource获取节点IORESOURCE_MEM属性资源,devm_ioremap_resource对寄存器基地址进行内存映射,platform_get_irq、devm_request_irq获取并申请中断,rk3x_i2c_adapt_div设置i2c时钟,最后是进行注册i2c_add_adapter。

  1. static int rk3x_i2c_probe(struct platform_device *pdev)
  2. {
  3. struct device_node *np = pdev->dev.of_node;
  4. const struct of_device_id *match;
  5. struct rk3x_i2c *i2c;
  6. struct resource *mem;
  7. int ret = 0;
  8. int bus_nr;
  9. u32 value;
  10. int irq;
  11. unsigned long clk_rate;
  12. i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
  13. if (!i2c)
  14. return -ENOMEM;
  15. match = of_match_node(rk3x_i2c_match, np);
  16. i2c->soc_data = (struct rk3x_i2c_soc_data *)match->data;
  17. /* use common interface to get I2C timing properties */
  18. i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
  19. strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));
  20. i2c->adap.owner = THIS_MODULE;
  21. i2c->adap.algo = &rk3x_i2c_algorithm;
  22. i2c->adap.retries = 3;
  23. i2c->adap.dev.of_node = np;
  24. i2c->adap.algo_data = i2c;
  25. i2c->adap.dev.parent = &pdev->dev;
  26. i2c->dev = &pdev->dev;
  27. spin_lock_init(&i2c->lock);
  28. init_waitqueue_head(&i2c->wait);
  29. i2c->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
  30. i2c->i2c_restart_nb.priority = 128;
  31. ret = register_i2c_restart_handler(&i2c->i2c_restart_nb);
  32. if (ret) {
  33. dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
  34. return ret;
  35. }
  36. mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  37. i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
  38. if (IS_ERR(i2c->regs))
  39. return PTR_ERR(i2c->regs);
  40. /* Try to set the I2C adapter number from dt */
  41. bus_nr = of_alias_get_id(np, "i2c");
  42. /*
  43. * Switch to new interface if the SoC also offers the old one.
  44. * The control bit is located in the GRF register space.
  45. */
  46. if (i2c->soc_data->grf_offset >= 0) {
  47. struct regmap *grf;
  48. grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
  49. if (IS_ERR(grf)) {
  50. dev_err(&pdev->dev,
  51. "rk3x-i2c needs 'rockchip,grf' property\n");
  52. return PTR_ERR(grf);
  53. }
  54. if (bus_nr < 0) {
  55. dev_err(&pdev->dev, "rk3x-i2c needs i2cX alias");
  56. return -EINVAL;
  57. }
  58. /* 27+i: write mask, 11+i: value */
  59. value = BIT(27 + bus_nr) | BIT(11 + bus_nr);
  60. ret = regmap_write(grf, i2c->soc_data->grf_offset, value);
  61. if (ret != 0) {
  62. dev_err(i2c->dev, "Could not write to GRF: %d\n", ret);
  63. return ret;
  64. }
  65. }
  66. /* IRQ setup */
  67. irq = platform_get_irq(pdev, 0);
  68. if (irq < 0) {
  69. dev_err(&pdev->dev, "cannot find rk3x IRQ\n");
  70. return irq;
  71. }
  72. ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
  73. 0, dev_name(&pdev->dev), i2c);
  74. if (ret < 0) {
  75. dev_err(&pdev->dev, "cannot request IRQ\n");
  76. return ret;
  77. }
  78. platform_set_drvdata(pdev, i2c);
  79. if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
  80. /* Only one clock to use for bus clock and peripheral clock */
  81. i2c->clk = devm_clk_get(&pdev->dev, NULL);
  82. i2c->pclk = i2c->clk;
  83. } else {
  84. i2c->clk = devm_clk_get(&pdev->dev, "i2c");
  85. i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
  86. }
  87. if (IS_ERR(i2c->clk)) {
  88. ret = PTR_ERR(i2c->clk);
  89. if (ret != -EPROBE_DEFER)
  90. dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
  91. return ret;
  92. }
  93. if (IS_ERR(i2c->pclk)) {
  94. ret = PTR_ERR(i2c->pclk);
  95. if (ret != -EPROBE_DEFER)
  96. dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
  97. return ret;
  98. }
  99. ret = clk_prepare(i2c->clk);
  100. if (ret < 0) {
  101. dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
  102. return ret;
  103. }
  104. ret = clk_prepare(i2c->pclk);
  105. if (ret < 0) {
  106. dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
  107. goto err_clk;
  108. }
  109. i2c->clk_rate_nb.notifier_call = rk3x_i2c_clk_notifier_cb;
  110. ret = clk_notifier_register(i2c->clk, &i2c->clk_rate_nb);
  111. if (ret != 0) {
  112. dev_err(&pdev->dev, "Unable to register clock notifier\n");
  113. goto err_pclk;
  114. }
  115. clk_rate = clk_get_rate(i2c->clk);
  116. rk3x_i2c_adapt_div(i2c, clk_rate);
  117. ret = i2c_add_adapter(&i2c->adap);
  118. if (ret < 0) {
  119. dev_err(&pdev->dev, "Could not register adapter\n");
  120. goto err_clk_notifier;
  121. }
  122. dev_info(&pdev->dev, "Initialized RK3xxx I2C bus at %p\n", i2c->regs);
  123. return 0;
  124. err_clk_notifier:
  125. clk_notifier_unregister(i2c->clk, &i2c->clk_rate_nb);
  126. err_pclk:
  127. clk_unprepare(i2c->pclk);
  128. err_clk:
  129. clk_unprepare(i2c->clk);
  130. return ret;
  131. }

2. i2c设备驱动开发

2.1 设备驱动结构体描述

        i2c设备驱动重点关注两个数据结构: i2c_clienti2c_driver。 i2c_client描述设备信息,一个设备对应一个i2c_client变量,i2c_driver类似platform_driver,描述驱动方法。

        如果使用设备树的话,需要设置 i2c_driver中的device_driver的of_match_table成员变量,跟设备树的 (compatible)属性对应。

  1. struct i2c_client {
  2. unsigned short flags; /* div., see below */
  3. unsigned short addr; /* i2c芯片地址(低7位) */
  4. char name[I2C_NAME_SIZE];
  5. struct i2c_adapter *adapter; /* 指向i2c适配器 */
  6. struct device dev; /* the device structure */
  7. int irq; /* irq issued by device */
  8. struct list_head detected;
  9. #if IS_ENABLED(CONFIG_I2C_SLAVE)
  10. i2c_slave_cb_t slave_cb; /* callback for slave mode */
  11. #endif
  12. };
  13. struct i2c_driver {
  14. unsigned int class;
  15. int (*attach_adapter)(struct i2c_adapter *) __deprecated;
  16. /* Standard driver model interfaces */
  17. int (*probe)(struct i2c_client *, const struct i2c_device_id *);
  18. int (*remove)(struct i2c_client *);
  19. /* driver model interfaces that don't relate to enumeration */
  20. void (*shutdown)(struct i2c_client *);
  21. /* Alert callback, for example for the SMBus alert protocol.
  22. * The format and meaning of the data value depends on the protocol.
  23. * For the SMBus alert protocol, there is a single bit of data passed
  24. * as the alert response's low bit ("event flag").
  25. */
  26. void (*alert)(struct i2c_client *, unsigned int data);
  27. int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
  28. struct device_driver driver;
  29. const struct i2c_device_id *id_table;
  30. /* Device detection callback for automatic device creation */
  31. int (*detect)(struct i2c_client *, struct i2c_board_info *);
  32. const unsigned short *address_list;
  33. struct list_head clients;
  34. };

2.2  相关函数

注册函数:i2c_driver注册函数为 i2c_register_driver,此函数原型如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver) 

owner 一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
返回值: 0,成功;负值,失败。

#define i2c_add_driver(driver)   i2c_register_driver(THIS_MODULE, driver)

注销函数

void i2c_del_driver(struct i2c_driver *driver)

2.3  设备数据收发

        对I2C 设备寄存器进行读写操作用到 i2c_transfer 函数,i2c_transfer 函数最终会调用I2C 适配器中i2c_algorithm 里面的master_xfer 函数。

 i2c_msg结构体如下,flags设置为I2C_M_RD则为读操作,设置为 0 则为写操作。

 还有两个API 函数分别用于I2C 数据的收发操作,这两个函数最终都会调用i2c_transfer。

I2C数据发送函数原型如下:

 I2C数据接收函数原型如下:

3. I2C设备驱动编写

3.1  硬件设备基本信息获取

        这里使用的是迅为7寸的LVDS屏,查看硬件原理图可知使用的是 i2c1;

        查看触摸IC的data sheep可知,可以操作的寄存器;

         设备树下的设备信息描述如下:挂载在 i2c1 节点下,compatible 为"edt,ft5x0x_ts";          设备访问地址为0x38;

  1. &i2c1 {
  2. status = "okay";
  3. i2c-scl-rising-time-ns = <140>;
  4. i2c-scl-falling-time-ns = <30>;
  5. ........
  6. ft5x06@38 {
  7. compatible = "edt,ft5x0x_ts";
  8. reg = <0x38>;
  9. touch-gpio = <&gpio1 20 IRQ_TYPE_EDGE_RISING>;
  10. interrupt-parent = <&gpio1>;
  11. interrupts = <20 IRQ_TYPE_LEVEL_LOW>;
  12. pinctrl-names = "default";
  13. pinctrl-0 = <&gt911_gpio>;
  14. reset-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
  15. #if defined(LCD_TYPE_9_7)
  16. touch_type = <0>; /*0:9.7, 1: 7.0*/
  17. #elif defined(LCD_TYPE_7_0)
  18. touch_type = <1>;
  19. #elif defined(LCD_TYPE_MIPI_7_0_NEW)|| defined(LCD_TYPE_MIPI_7_0_OLD)
  20. touch_type = <1>;
  21. #endif
  22. };

3.2  设备驱动编写

        在kernel/driver下搜索“edt,ft5x0x_ts”字符串,找到对应的 touch 驱动文件,文件名为tf5x06_ts.c,设备驱动的编写可以参考下该文件,但由于我们刚开始不熟悉该 IC ,所以只需要简单化驱动程序,重在熟悉基本开发框架。

        寄存器的读写函数需要填写 i2c_msg 结构体变量,addr为设备地址,传入值为0x38;       flag设为0表示写操作,设为1表示读操作;设备与驱动匹配成功后,调用probe函数,在probe函数里仅做设备寄存器读写操作;在 i2c_driver 结构体变量中,of_match_table用于与设备树匹配,id_table用于传统、无设备树下的匹配。

  1. static struct i2c_client *ft5x06_client;
  2. //读寄存器函数
  3. static int ft5x06_read_reg(uint8_t reg_addr)
  4. {
  5. uint8_t data;
  6. struct i2c_msg msgs[] = {
  7. [0] = {
  8. .addr = ft5x06_client->addr,
  9. .flags = 0, //写
  10. .len = sizeof(reg_addr),
  11. .buf = &reg_addr,
  12. },
  13. [1] = {
  14. .addr = ft5x06_client->addr,
  15. .flags = 1, //读
  16. .len = sizeof(data),
  17. .buf = &data,
  18. },
  19. };
  20. i2c_transfer(ft5x06_client->adapter, msgs, 2);
  21. return data;
  22. }
  23. //写寄存器函数
  24. static void ft5x06_write_reg(uint8_t reg_addr, uint8_t data, uint8_t len)
  25. {
  26. uint8_t buff[64];
  27. struct i2c_msg msgs;
  28. buff[0] = reg_addr;
  29. memcpy(&buff[1], &data, len);
  30. msgs.addr = ft5x06_client->addr,
  31. msgs.flags = 0,
  32. msgs.len = len + 1, //addr+data
  33. msgs.buf = buff,
  34. i2c_transfer(ft5x06_client->adapter, &msgs, 1);
  35. }
  36. static int i2c_touch_irq_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
  37. {
  38. int ret = 0;
  39. printk("i2c_touch_irq_probe\n");
  40. ft5x06_client = i2c_client;
  41. ft5x06_write_reg(0x80, 0x20, 1); //写入值0x20到0x80寄存器
  42. ret = ft5x06_read_reg(0x80); //读出0x80寄存器的值
  43. printk("0x80 reg value is %X\n", ret);
  44. ret = ft5x06_read_reg(0x01); //读出0x01寄存器的值
  45. printk("0x01 reg value is %X\n", ret);
  46. return 0;
  47. }
  48. static int i2c_touch_remove(struct i2c_client *i2c_client)
  49. {
  50. printk("i2c_touch_remove \n");
  51. return 0;
  52. }
  53. static const struct i2c_device_id ft5x0x_id[] = {
  54. {"ft5x0x_ts", 0},
  55. {}
  56. };
  57. const struct of_device_id of_match_table_test[] = {
  58. {.compatible = "edt,ft5x0x_ts"},
  59. {},
  60. };
  61. static struct i2c_driver i2c_touch_driver =
  62. {
  63. .probe = i2c_touch_irq_probe,
  64. .remove = i2c_touch_remove,
  65. .driver = {
  66. .owner = THIS_MODULE,
  67. .name = "i2c_touch_test",
  68. .of_match_table = of_match_table_test
  69. },
  70. .id_table = ft5x0x_id,
  71. };
  72. static int i2c_test_init(void)
  73. {
  74. i2c_add_driver(&i2c_touch_driver);
  75. printk("i2c_test_init \n");
  76. return 0;
  77. }
  78. static void i2c_test_exit(void)
  79. {
  80. printk("i2c_test_exit \n");
  81. i2c_del_driver(&i2c_touch_driver);
  82. }
  83. MODULE_LICENSE("GPL");
  84. module_init(i2c_test_init);
  85. module_exit(i2c_test_exit);

3.3  修改kernel配置

        设备驱动文件编写好后,还需要配置下kernel,把之前系统用的 ft5x06 驱动屏蔽掉,

在 kernel/ 下输入“make menuconfig”,找到文件位置选择不编译即可;配置好后查看 .config 文件是否修改完成。

3.4  烧录测试

        在 i2c 设备文件中,有个 1-0038 (i2c1-0x38)就是我们需要找的节点;

         加载运行驱动文件,在probe函数中打印读取的结果,验证完成。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/205813
推荐阅读
相关标签
  

闽ICP备14008679号