赞
踩
本篇章在rk3399平台上,基于设备树的i2c驱动开发。i2c直接使用硬件i2c总线,体系结构分为3部分:I2C 核心、I2C 总线驱动和I2C 设备驱动。I2C 核心(i2c-core.c)提供了I2C 总线驱动和设备驱动的注册、注销方法等。我们主要了解Linux中i2c的基本框架,分为i2c主机驱动开发和i2c设备驱动开发。主机驱动一般由芯片原厂开发,通常需要我们做的就是针对具体某个设备的设备驱动开发,硬件设备信息通过设备树描述。
i2c适配器驱动开发中,要用到两个重要的数据结构: i2c_adapter和 i2c_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(系统管理)总线的传输函数。
- 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 */
- struct rt_mutex bus_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;
- };
-
-
- 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 *);
-
- #if IS_ENABLED(CONFIG_I2C_SLAVE)
- int (*reg_slave)(struct i2c_client *client);
- int (*unreg_slave)(struct i2c_client *client);
- #endif
- };
注册函数:填充好 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)
在内核中我们要怎么去查找到源码文件呢?可以通过设备树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。
- static int rk3x_i2c_probe(struct platform_device *pdev)
- {
- struct device_node *np = pdev->dev.of_node;
- const struct of_device_id *match;
- struct rk3x_i2c *i2c;
- struct resource *mem;
- int ret = 0;
- int bus_nr;
- u32 value;
- int irq;
- unsigned long clk_rate;
-
- i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
- if (!i2c)
- return -ENOMEM;
-
- match = of_match_node(rk3x_i2c_match, np);
- i2c->soc_data = (struct rk3x_i2c_soc_data *)match->data;
-
- /* use common interface to get I2C timing properties */
- i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
-
- strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));
- i2c->adap.owner = THIS_MODULE;
- i2c->adap.algo = &rk3x_i2c_algorithm;
- i2c->adap.retries = 3;
- i2c->adap.dev.of_node = np;
- i2c->adap.algo_data = i2c;
- i2c->adap.dev.parent = &pdev->dev;
-
- i2c->dev = &pdev->dev;
-
- spin_lock_init(&i2c->lock);
- init_waitqueue_head(&i2c->wait);
-
- i2c->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
- i2c->i2c_restart_nb.priority = 128;
- ret = register_i2c_restart_handler(&i2c->i2c_restart_nb);
- if (ret) {
- dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
- return ret;
- }
-
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
- if (IS_ERR(i2c->regs))
- return PTR_ERR(i2c->regs);
-
- /* Try to set the I2C adapter number from dt */
- bus_nr = of_alias_get_id(np, "i2c");
-
- /*
- * Switch to new interface if the SoC also offers the old one.
- * The control bit is located in the GRF register space.
- */
- if (i2c->soc_data->grf_offset >= 0) {
- struct regmap *grf;
-
- grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
- if (IS_ERR(grf)) {
- dev_err(&pdev->dev,
- "rk3x-i2c needs 'rockchip,grf' property\n");
- return PTR_ERR(grf);
- }
-
- if (bus_nr < 0) {
- dev_err(&pdev->dev, "rk3x-i2c needs i2cX alias");
- return -EINVAL;
- }
-
- /* 27+i: write mask, 11+i: value */
- value = BIT(27 + bus_nr) | BIT(11 + bus_nr);
-
- ret = regmap_write(grf, i2c->soc_data->grf_offset, value);
- if (ret != 0) {
- dev_err(i2c->dev, "Could not write to GRF: %d\n", ret);
- return ret;
- }
- }
-
- /* IRQ setup */
- irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
- dev_err(&pdev->dev, "cannot find rk3x IRQ\n");
- return irq;
- }
-
- ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
- 0, dev_name(&pdev->dev), i2c);
- if (ret < 0) {
- dev_err(&pdev->dev, "cannot request IRQ\n");
- return ret;
- }
-
- platform_set_drvdata(pdev, i2c);
-
- if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
- /* Only one clock to use for bus clock and peripheral clock */
- i2c->clk = devm_clk_get(&pdev->dev, NULL);
- i2c->pclk = i2c->clk;
- } else {
- i2c->clk = devm_clk_get(&pdev->dev, "i2c");
- i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
- }
-
- if (IS_ERR(i2c->clk)) {
- ret = PTR_ERR(i2c->clk);
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
- return ret;
- }
- if (IS_ERR(i2c->pclk)) {
- ret = PTR_ERR(i2c->pclk);
- if (ret != -EPROBE_DEFER)
- dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
- return ret;
- }
-
- ret = clk_prepare(i2c->clk);
- if (ret < 0) {
- dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
- return ret;
- }
- ret = clk_prepare(i2c->pclk);
- if (ret < 0) {
- dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
- goto err_clk;
- }
-
- i2c->clk_rate_nb.notifier_call = rk3x_i2c_clk_notifier_cb;
- ret = clk_notifier_register(i2c->clk, &i2c->clk_rate_nb);
- if (ret != 0) {
- dev_err(&pdev->dev, "Unable to register clock notifier\n");
- goto err_pclk;
- }
-
- clk_rate = clk_get_rate(i2c->clk);
- rk3x_i2c_adapt_div(i2c, clk_rate);
-
- ret = i2c_add_adapter(&i2c->adap);
- if (ret < 0) {
- dev_err(&pdev->dev, "Could not register adapter\n");
- goto err_clk_notifier;
- }
-
- dev_info(&pdev->dev, "Initialized RK3xxx I2C bus at %p\n", i2c->regs);
-
- return 0;
-
- err_clk_notifier:
- clk_notifier_unregister(i2c->clk, &i2c->clk_rate_nb);
- err_pclk:
- clk_unprepare(i2c->pclk);
- err_clk:
- clk_unprepare(i2c->clk);
- return ret;
- }
i2c设备驱动重点关注两个数据结构: i2c_client和 i2c_driver。 i2c_client描述设备信息,一个设备对应一个i2c_client变量,i2c_driver类似platform_driver,描述驱动方法。
如果使用设备树的话,需要设置 i2c_driver中的device_driver的of_match_table成员变量,跟设备树的 (compatible)属性对应。
- struct i2c_client {
- unsigned short flags; /* div., see below */
- unsigned short addr; /* i2c芯片地址(低7位) */
-
- char name[I2C_NAME_SIZE];
- struct i2c_adapter *adapter; /* 指向i2c适配器 */
- struct device dev; /* the device structure */
- int irq; /* irq issued by device */
- struct list_head detected;
- #if IS_ENABLED(CONFIG_I2C_SLAVE)
- i2c_slave_cb_t slave_cb; /* callback for slave mode */
- #endif
- };
-
-
- struct i2c_driver {
- unsigned int class;
-
- int (*attach_adapter)(struct i2c_adapter *) __deprecated;
-
- /* 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 *);
-
- /* 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);
-
-
- 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;
- };
注册函数: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)
对I2C 设备寄存器进行读写操作用到 i2c_transfer 函数,i2c_transfer 函数最终会调用I2C 适配器中i2c_algorithm 里面的master_xfer 函数。
i2c_msg结构体如下,flags设置为I2C_M_RD则为读操作,设置为 0 则为写操作。
还有两个API 函数分别用于I2C 数据的收发操作,这两个函数最终都会调用i2c_transfer。
I2C数据发送函数原型如下:
I2C数据接收函数原型如下:
这里使用的是迅为7寸的LVDS屏,查看硬件原理图可知使用的是 i2c1;
查看触摸IC的data sheep可知,可以操作的寄存器;
设备树下的设备信息描述如下:挂载在 i2c1 节点下,compatible 为"edt,ft5x0x_ts"; 设备访问地址为0x38;
- &i2c1 {
- status = "okay";
- i2c-scl-rising-time-ns = <140>;
- i2c-scl-falling-time-ns = <30>;
-
- ........
-
- ft5x06@38 {
- compatible = "edt,ft5x0x_ts";
- reg = <0x38>;
- touch-gpio = <&gpio1 20 IRQ_TYPE_EDGE_RISING>;
- interrupt-parent = <&gpio1>;
- interrupts = <20 IRQ_TYPE_LEVEL_LOW>;
- pinctrl-names = "default";
- pinctrl-0 = <>911_gpio>;
- reset-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
- #if defined(LCD_TYPE_9_7)
- touch_type = <0>; /*0:9.7, 1: 7.0*/
- #elif defined(LCD_TYPE_7_0)
- touch_type = <1>;
- #elif defined(LCD_TYPE_MIPI_7_0_NEW)|| defined(LCD_TYPE_MIPI_7_0_OLD)
- touch_type = <1>;
- #endif
- };
在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用于传统、无设备树下的匹配。
- static struct i2c_client *ft5x06_client;
-
- //读寄存器函数
- static int ft5x06_read_reg(uint8_t reg_addr)
- {
- uint8_t data;
- struct i2c_msg msgs[] = {
- [0] = {
- .addr = ft5x06_client->addr,
- .flags = 0, //写
- .len = sizeof(reg_addr),
- .buf = ®_addr,
- },
-
- [1] = {
- .addr = ft5x06_client->addr,
- .flags = 1, //读
- .len = sizeof(data),
- .buf = &data,
- },
- };
- i2c_transfer(ft5x06_client->adapter, msgs, 2);
- return data;
- }
- //写寄存器函数
- static void ft5x06_write_reg(uint8_t reg_addr, uint8_t data, uint8_t len)
- {
- uint8_t buff[64];
- struct i2c_msg msgs;
- buff[0] = reg_addr;
- memcpy(&buff[1], &data, len);
-
- msgs.addr = ft5x06_client->addr,
- msgs.flags = 0,
- msgs.len = len + 1, //addr+data
- msgs.buf = buff,
-
- i2c_transfer(ft5x06_client->adapter, &msgs, 1);
- }
-
- static int i2c_touch_irq_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
- {
- int ret = 0;
- printk("i2c_touch_irq_probe\n");
- ft5x06_client = i2c_client;
- ft5x06_write_reg(0x80, 0x20, 1); //写入值0x20到0x80寄存器
- ret = ft5x06_read_reg(0x80); //读出0x80寄存器的值
- printk("0x80 reg value is %X\n", ret);
- ret = ft5x06_read_reg(0x01); //读出0x01寄存器的值
- printk("0x01 reg value is %X\n", ret);
- return 0;
- }
-
- static int i2c_touch_remove(struct i2c_client *i2c_client)
- {
- printk("i2c_touch_remove \n");
- return 0;
- }
-
- static const struct i2c_device_id ft5x0x_id[] = {
- {"ft5x0x_ts", 0},
- {}
- };
-
- const struct of_device_id of_match_table_test[] = {
- {.compatible = "edt,ft5x0x_ts"},
- {},
- };
-
- static struct i2c_driver i2c_touch_driver =
- {
- .probe = i2c_touch_irq_probe,
- .remove = i2c_touch_remove,
- .driver = {
- .owner = THIS_MODULE,
- .name = "i2c_touch_test",
- .of_match_table = of_match_table_test
- },
- .id_table = ft5x0x_id,
- };
- static int i2c_test_init(void)
- {
- i2c_add_driver(&i2c_touch_driver);
- printk("i2c_test_init \n");
- return 0;
- }
- static void i2c_test_exit(void)
- {
- printk("i2c_test_exit \n");
- i2c_del_driver(&i2c_touch_driver);
- }
-
- MODULE_LICENSE("GPL");
- module_init(i2c_test_init);
- module_exit(i2c_test_exit);
设备驱动文件编写好后,还需要配置下kernel,把之前系统用的 ft5x06 驱动屏蔽掉,
在 kernel/ 下输入“make menuconfig”,找到文件位置选择不编译即可;配置好后查看 .config 文件是否修改完成。
在 i2c 设备文件中,有个 1-0038 (i2c1-0x38)就是我们需要找的节点;
加载运行驱动文件,在probe函数中打印读取的结果,验证完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。