前言
本篇章在rk3399平台上,基于设备树的i2c驱动开发。i2c直接使用硬件i2c总线,体系结构分为3部分:I2C 核心、I2C 总线驱动和I2C 设备驱动。I2C 核心(i2c-core.c)提供了I2C 总线驱动和设备驱动的注册、注销方法等。我们主要了解Linux中i2c的基本框架,分为i2c主机驱动开发和i2c设备驱动开发。主机驱动一般由芯片原厂开发,通常需要我们做的就是针对具体某个设备的设备驱动开发,硬件设备信息通过设备树描述。
1. i2c主机驱动框架
1.1 结构体描述
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
};
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。
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;
}
2. i2c设备驱动开发
2.1 设备驱动结构体描述
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;
};
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;
&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
};
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用于传统、无设备树下的匹配。
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);
3.3 修改kernel配置
设备驱动文件编写好后,还需要配置下kernel,把之前系统用的 ft5x06 驱动屏蔽掉,
在 kernel/ 下输入“make menuconfig”,找到文件位置选择不编译即可;配置好后查看 .config 文件是否修改完成。
3.4 烧录测试
在 i2c 设备文件中,有个 1-0038 (i2c1-0x38)就是我们需要找的节点;
加载运行驱动文件,在probe函数中打印读取的结果,验证完成。文章来源:https://www.toymoban.com/news/detail-433290.html
文章来源地址https://www.toymoban.com/news/detail-433290.html
到了这里,关于Linux驱动开发之i2c框架讲解到例程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!