9.5I2C适配器驱动

此章节利用GPIO模拟I2C时序实现一个I2C适配器驱动,有关I2C时序相关内容可以参考博文深入浅出理解I2C协议

硬件连接

这里利用GPIO A11和GPIO A12引脚来模拟I2C总线的SCL和SDA,用于驱动外设AP3216C,有关AP3216C的驱动已经在9.4在内核空间使用I2C总线章节中实现,可以直接使用,因此本章节只实现一个利用GPIO模拟I2C控制器的驱动。
在这里插入图片描述在这里插入图片描述

编写设备树

按如下步骤在设备树中添加GPIO I2C适配器的描述

  1. 在顶层设备树的根节点中添加如下节点
i2c_gpio@sfi2c0 {
   compatible = "alientek,gpio-i2c";
   sda-gpio = <&gpioa 12 GPIO_ACTIVE_HIGH>;
   scl-gpio = <&gpioa 11 GPIO_ACTIVE_HIGH>;
   delay-us = <5>;
   #address-cells = <1>;
   #size-cells = <0>;

   ap3216c@1e {
   	compatible = "alientek,ap3216c";
   	labe = "ap3216c_0";
   	reg = <0x1e>;
   };
};
  1. 在顶层设备树中引用i2c5节点,将状态修改为disable,因为GPIO模拟的IIC适配器使用了I2C5控制器的引脚
&i2c5 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c5_pins_a>;
	pinctrl-1 = <&i2c5_pins_sleep_a>;
	status = "disable";

	ap3216c@1e {
		compatible = "alientek,ap3216c";
		labe = "ap3216c_0";
		reg = <0x1e>;
	};
};

编写驱动代码

内核空间I2C驱动主要包括以下部分:

  1. 实现master_xfer函数和functionality函数,master_xfer函数用于传输i2c_msg,functionality函数用于返回I2C控制器所支持的功能
static const struct i2c_algorithm adapter_algorithm = {
	.master_xfer = adapter_master_xfer,
	.functionality = adapter_func,
};
  1. 分配并初始化i2c_adapter
	i2c_bit->adapter.owner = THIS_MODULE;
	i2c_bit->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_DDC | I2C_CLASS_SPD;
	i2c_bit->adapter.nr = -1;
	i2c_bit->adapter.algo = &adapter_algorithm;
	i2c_bit->adapter.dev.of_node = pdev->dev.of_node;
	i2c_bit->adapter.dev.parent = &pdev->dev;
	snprintf(i2c_bit->adapter.name, sizeof(i2c_bit->adapter.name), "gpio-i2c-bus");
  1. 注册i2c_adapter
i2c_add_adapter(&i2c_bit->adapter);

完整的GPIO模拟I2C适配器的代码如下:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c-algo-bit.h>
#include <linux/i2c.h>

#ifndef I2C_BIT_OUTPUT_OD
#define SDA_INPUT(i2c_bit)		gpiod_direction_input((i2c_bit)->sda)
#define SDA_OUTPUT(i2c_bit)		gpiod_direction_output((i2c_bit)->sda, 1)
#else
#define SDA_INPUT(i2c_bit) 
#define SDA_OUTPUT(i2c_bit)
#endif

#define SDA_H(i2c_bit)			gpiod_set_value((i2c_bit)->sda, 1)
#define SDA_L(i2c_bit)			gpiod_set_value((i2c_bit)->sda, 0)

#define SCL_H(i2c_bit)			gpiod_set_value((i2c_bit)->scl, 1)
#define SCL_L(i2c_bit)			gpiod_set_value((i2c_bit)->scl, 0)

#define SET_SDA(i2c_bit, val)	((val)==0 ? SDA_L(i2c_bit) : SDA_H(i2c_bit))
#define GET_SDA(i2c_bit)		gpiod_get_value((i2c_bit)->sda)

#define DELAY(i2c_bit)			udelay((i2c_bit)->delay_us)
#define DELAY2(i2c_bit)			udelay((i2c_bit)->delay_us >> 1)

typedef struct gpio_adapter {
	struct i2c_adapter adapter;
	struct gpio_desc *scl;
	struct gpio_desc *sda;
	uint32_t delay_us;
}gpio_adapter_t;

static void _i2c_start(gpio_adapter_t *i2c_bit)
{
	SDA_OUTPUT(i2c_bit);
	
	SDA_L(i2c_bit);
	DELAY(i2c_bit);
	SCL_L(i2c_bit);
	DELAY2(i2c_bit);
}

static void _i2c_restart(gpio_adapter_t *i2c_bit)
{
	SDA_OUTPUT(i2c_bit);
	
	SDA_H(i2c_bit);
	DELAY2(i2c_bit);
	SCL_H(i2c_bit);
	DELAY(i2c_bit);
	
	SDA_L(i2c_bit);
	DELAY(i2c_bit);
	SCL_L(i2c_bit);
	DELAY2(i2c_bit);
}

static void _i2c_stop(gpio_adapter_t *i2c_bit)
{
	SDA_OUTPUT(i2c_bit);
	
	SDA_L(i2c_bit);
	SCL_L(i2c_bit);
	DELAY2(i2c_bit);
	
	SCL_H(i2c_bit);
	DELAY(i2c_bit);
	SDA_H(i2c_bit);
	DELAY(i2c_bit);
}

static int _i2c_waitack(gpio_adapter_t *i2c_bit, int ignore_nack)
{
	int ack;
	
	SDA_INPUT(i2c_bit);
	
	SDA_H(i2c_bit);
	DELAY2(i2c_bit);
	SCL_H(i2c_bit);
	DELAY(i2c_bit);
	ack = GET_SDA(i2c_bit);
	SCL_L(i2c_bit);
	DELAY2(i2c_bit);
	if(ignore_nack)
		return 0;
	
	return (ack==0) ? 0 : -EIO;
}

static void _i2c_sendack(gpio_adapter_t *i2c_bit, int ack)
{
	SDA_OUTPUT(i2c_bit);
	
	SET_SDA(i2c_bit, ack);
	DELAY2(i2c_bit);
	SCL_H(i2c_bit);
	DELAY(i2c_bit);
	SCL_L(i2c_bit);
	DELAY2(i2c_bit);
}

static int _i2c_writeb(gpio_adapter_t *i2c_bit, uint8_t data, int ignore_nack)
{
	int8_t i;
	uint8_t bit;
	
	SDA_OUTPUT(i2c_bit);
	for(i = 7; i >= 0; i--)
	{
		bit = (data >> i) & 1;
		SET_SDA(i2c_bit, bit);
		DELAY2(i2c_bit);
		SCL_H(i2c_bit);
		DELAY(i2c_bit);
		SCL_L(i2c_bit);
		DELAY2(i2c_bit);
	}

	return _i2c_waitack(i2c_bit, ignore_nack);
}

static uint8_t _i2c_readb(gpio_adapter_t *i2c_bit, int ack)
{
	int8_t i;
	uint8_t data;
	
	SDA_INPUT(i2c_bit);
	SDA_H(i2c_bit);
	DELAY2(i2c_bit);
	for(data = 0, i = 7; i >= 0; i--)
	{
		SCL_H(i2c_bit);
		DELAY(i2c_bit);
		data |= GET_SDA(i2c_bit) << i;
		SCL_L(i2c_bit);
		if(i != 0)
			DELAY(i2c_bit);
		else
			DELAY2(i2c_bit);
	}
	_i2c_sendack(i2c_bit, ack);
	
	return data;
}

static int _i2c_send_bytes(gpio_adapter_t *i2c_bit, struct i2c_msg *msg)
{
	int result;
	int bytes;
	uint16_t ignore_nack = msg->flags & I2C_M_IGNORE_NAK;

	for(bytes = 0; bytes < msg->len; )
	{
		result = _i2c_writeb(i2c_bit, msg->buf[bytes], ignore_nack);
		if(result == 0)
			bytes++;
		else
			break;
	}

	return bytes;
}

static int _i2c_recv_bytes(gpio_adapter_t *i2c_bit, struct i2c_msg *msg)
{
	int ack;
	int bytes;

	for(bytes = 0; bytes < msg->len; bytes++)
	{
		if((msg->flags & I2C_M_NO_RD_ACK) || (bytes >= (msg->len-1)))
			ack = 1;
		else
			ack = 0;
		msg->buf[bytes] = _i2c_readb(i2c_bit, ack);
	}
	
	return bytes;
}

static int _i2c_send_address(gpio_adapter_t *i2c_bit, struct i2c_msg *msg)
{
	uint8_t addr1;
	uint8_t addr2;
	int result;
	uint16_t ignore_nack = msg->flags & I2C_M_IGNORE_NAK;

	if(msg->flags & I2C_M_TEN)
	{
		addr1 = 0xf0 | ((msg->addr >> 7) & 0x06);
		addr2 = msg->addr & 0xff;

		result = _i2c_writeb(i2c_bit, addr1, ignore_nack);
		if(result != 0)
			return result;
		result = _i2c_writeb(i2c_bit, addr2, ignore_nack);
		if(result != 0)
			return result;

		if(msg->flags & I2C_M_RD)
		{
			_i2c_restart(i2c_bit);
			addr1 |= 0x01;
			result = _i2c_writeb(i2c_bit, addr1, ignore_nack);
			if(result != 0)
				return result;
		}
	}
	else
	{
		/* 7-bit addr */
		addr1 = msg->addr << 1;
		if(msg->flags & I2C_M_RD)
			addr1 |= 1;
		result = _i2c_writeb(i2c_bit, addr1, ignore_nack);
		if(result != 0)
			return result;
	}

	return 0;
}

static int adapter_master_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
{
	struct i2c_msg *msg;
	int result;
	gpio_adapter_t *i2c_bit = container_of(i2c_adap, gpio_adapter_t, adapter);

	for(result=0; result<num; result++)
	{
		msg = &msgs[result];

		//发送开始信号或重启信号
		if((!(msg->flags & I2C_M_NOSTART)) || (result == 0))
		{
			if(result == 0)
			{
				//产生开始信号
				_i2c_start(i2c_bit);
			}
			else
			{
				//产生重启信号
				_i2c_restart(i2c_bit);
			}
			//发送设备地址
			if(_i2c_send_address(i2c_bit, msg) != 0)
			{
				_i2c_stop(i2c_bit);
				break;
			}
		}

		//传输数据
		if(msg->flags & I2C_M_RD)
		{
			//读数据
			if(_i2c_recv_bytes(i2c_bit, msg) != msg->len)
			{
				_i2c_stop(i2c_bit);
				printk("recv data failed");
				break;
			}
		}
		else {
			//写数据
			if(_i2c_send_bytes(i2c_bit, msg) != msg->len)
			{
				_i2c_stop(i2c_bit);
				printk("send data failed");
				break;
			}
		}

		//产生停止信号
		if(((msg->flags & I2C_M_STOP)) || (result >= (num-1)))
			_i2c_stop(i2c_bit);
	}

	return result;
}

static u32 adapter_func(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_NOSTART
		| I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_READ_BLOCK_DATA
		| I2C_FUNC_SMBUS_BLOCK_PROC_CALL | I2C_FUNC_PROTOCOL_MANGLING;
}

static const struct i2c_algorithm adapter_algorithm = {
	.master_xfer = adapter_master_xfer,
	.functionality = adapter_func,
};
//设备和驱动匹配成功执行
static int adapter_probe(struct platform_device *pdev)
{
	int result;
	gpio_adapter_t *i2c_bit;

	printk("%s\r\n", __FUNCTION__);

	//分配设备句柄
	i2c_bit = devm_kzalloc(&pdev->dev, sizeof(gpio_adapter_t), GFP_KERNEL);
	if(!i2c_bit)
	{
		printk("alloc memory failed\r\n");
		return -ENOMEM;
	}
	//复位设备句柄
	memset(i2c_bit, 0, sizeof(gpio_adapter_t));

	//获取时钟延时
	result = of_property_read_u32(pdev->dev.of_node, "delay-us", &i2c_bit->delay_us);
	if(result < 0)
	{
		printk("get dev id failed\r\n");
		return result;
	}

#ifndef I2C_BIT_OUTPUT_OD
	//获取SCL引脚,并输出高电平
	i2c_bit->scl = devm_gpiod_get(&pdev->dev, "scl", GPIOD_OUT_HIGH);
	if(IS_ERR(i2c_bit->scl))
	{
		printk("get scl gpio failed\r\n");
		return PTR_ERR(i2c_bit->scl);
	}
	//获取SDA引脚,并输出高电平
	i2c_bit->sda = devm_gpiod_get(&pdev->dev, "sda", GPIOD_OUT_HIGH);
	if(IS_ERR(i2c_bit->sda))
	{
		printk("get sda gpio failed\r\n");
		return PTR_ERR(i2c_bit->sda);
	}
#else
	//获取SCL引脚,并输出高电平
	i2c_bit->scl = devm_gpiod_get(&pdev->dev, "scl", GPIOD_OUT_HIGH);
	if(IS_ERR(i2c_bit->scl))
	{
		printk("get scl gpio failed\r\n");
		return PTR_ERR(i2c_bit->scl);
	}
	//获取SDA引脚,并设置为输入
	i2c_bit->sda = devm_gpiod_get(&pdev->dev, "sda", GPIOD_IN);
	if(IS_ERR(i2c_bit->sda))
	{
		printk("get sda gpio failed\r\n");
		return PTR_ERR(i2c_bit->sda);
	}
#endif

	//初始化I2C适配器
	i2c_bit->adapter.owner = THIS_MODULE;
	i2c_bit->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_DDC | I2C_CLASS_SPD;
	i2c_bit->adapter.nr = -1;
	i2c_bit->adapter.algo = &adapter_algorithm;
	i2c_bit->adapter.dev.of_node = pdev->dev.of_node;
	i2c_bit->adapter.dev.parent = &pdev->dev;
	snprintf(i2c_bit->adapter.name, sizeof(i2c_bit->adapter.name), "gpio-i2c-bus");
	//添加I2C适配器
	result = i2c_add_adapter(&i2c_bit->adapter);
	if(result < 0)
	{
		printk("add adapter failed\r\n");
		return result;
	}

	//设置平台设备的驱动私有数据
	pdev->dev.driver_data = (void*)i2c_bit;

	return 0;
}

//设备或驱动卸载时执行
static int adapter_remove(struct platform_device *pdev)
{
	gpio_adapter_t *i2c_bit;

	printk("%s\r\n", __FUNCTION__);

	i2c_bit = (gpio_adapter_t*)pdev->dev.driver_data;

	i2c_del_adapter(&i2c_bit->adapter);

	return 0;
}

/* 匹配列表,用于设备树和平台驱动匹配 */
static const struct of_device_id adapter_of_match[] = {
	{.compatible = "alientek,gpio-i2c"},
	{ /* Sentinel */ }
};
//平台驱动
static struct platform_driver adapter_drv = {
	.driver = {
		.name = "gpio_adapter",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = adapter_of_match,
	},
	.probe = adapter_probe,
	.remove = adapter_remove,
};
static int __init adapter_drv_init(void)
{
	int result;

	printk("%s\r\n", __FUNCTION__);

	//注册平台驱动
	result = platform_driver_register(&adapter_drv);
	if(result != 0)
	{
		printk("add cdev failed\r\n");
		return result;
	}

	return result;
}

static void __exit adapter_drv_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	//注销平台驱动
	platform_driver_unregister(&adapter_drv);
}

module_init(adapter_drv_init);
module_exit(adapter_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("LF");
MODULE_DESCRIPTION("adapter_dev");

编写驱动测试程序

驱动测试程序可以使用在9.4在内核空间使用I2C总线章节中编写的AP3216C驱动及其对应的测试程序。

上机测试

  1. 根据硬件原理图对设备树进行修改,然后用命令make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,并用新的设备树启动目标板。
  2. 这里下载代码并进行编译,得到GPIO模拟I2C适配器的驱动,然后将编译出来的.ko文件拷贝到目标板根文件系统的root目录中。
  3. 这里下载代码并进行编译,得到AP3216C的驱动及其测试程序,然后将编译出来的.ko文件和.out文件拷贝到目标板根文件系统的root目录中。
    在这里插入图片描述
  4. 执行命令insmod i2c_adapter.ko加载I2C适配器驱动。
    在这里插入图片描述
  5. 执行命令insmod ap3216c.ko加载AP3216C驱动,此时在/dev/目录下生成ap3216c_0设备文件。
    在这里插入图片描述
  6. 执行命令./app.out /dev/ap3216c_0启动ap3216c的测试程序。测试程序会通过系统的read函数调用AP3216C驱动的read函数,AP3216C驱动的read函数又通过GPIO模拟I2C适配器驱动控制GPIO读取AP3216C的寄存器,然后将寄存器的值返回应用层。
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值