Linux驱动开发之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(系统管理)总线的传输函数。

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_clienti2c_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 = <&gt911_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 = &reg_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函数中打印读取的结果,验证完成。

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32F1系列是意法半导体(STMicroelectronics)推出的一款32位MCU系列产品,具有丰富的外设和强大的性能。其中,I2C总线是一种常见的串行通信总线,用于连接微控制器和其他设备进行数据传输。 在STM32F1的HAL库中,提供了I2C的程,以方便开发者快速使用I2C总线进行数据通信。下面简要介绍一下STM32F1 HAL库中I2C程的主要功能和使用方法: 1. 初始化I2C总线 (HAL_I2C_Init函数):通过配置I2C的时钟速度、地址模式等参数,初始化I2C总线。在初始化过程中,需要指定I2C的外设号以及总线的时钟源。 2. 写入数据到I2C总线 (HAL_I2C_Master_Transmit函数):通过指定I2C的外设号、目标设备地址、发送的数据缓冲区和数据长度,可以将数据写入到I2C总线。在写入数据之前,需要确保目标设备已经准备好接收数据。 3. 读取I2C总线上的数据 (HAL_I2C_Master_Receive函数):通过指定I2C的外设号、目标设备地址、接收数据的缓冲区和数据长度,可以从I2C总线上读取数据。在读取数据之前,需要确保目标设备已经准备好发送数据。 4. 发送和接收数据(HAL_I2C_Master_Transmit_Receive函数):通过指定I2C的外设号、目标设备地址、发送的数据缓冲区、发送数据的长度、接收数据的缓冲区和接收数据的长度,可以在一次函数调用中同时发送和接收数据。 以上是STM32F1 HAL库中I2C程的基本功能和使用方法。开发者可以根据具体需求,使用这些程来实现自己的应用。注意,在使用I2C程之前,需要在CubeMX工具中正确配置I2C外设的引脚映射和时钟源。 总而言之,STM32F1 HAL库提供了丰富的I2C程,方便开发者在STM32F1系列MCU上使用I2C总线进行数据通信。通过合理使用这些程,可以快速开发出各种I2C设备的应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值