此章节利用GPIO模拟I2C时序实现一个I2C适配器驱动,有关I2C时序相关内容可以参考博文深入浅出理解I2C协议
硬件连接
这里利用GPIO A11和GPIO A12引脚来模拟I2C总线的SCL和SDA,用于驱动外设AP3216C,有关AP3216C的驱动已经在9.4在内核空间使用I2C总线章节中实现,可以直接使用,因此本章节只实现一个利用GPIO模拟I2C控制器的驱动。
编写设备树
按如下步骤在设备树中添加GPIO I2C适配器的描述
- 在顶层设备树的根节点中添加如下节点
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>;
};
};
- 在顶层设备树中引用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驱动主要包括以下部分:
- 实现master_xfer函数和functionality函数,master_xfer函数用于传输i2c_msg,functionality函数用于返回I2C控制器所支持的功能
static const struct i2c_algorithm adapter_algorithm = {
.master_xfer = adapter_master_xfer,
.functionality = adapter_func,
};
- 分配并初始化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");
- 注册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驱动及其对应的测试程序。
上机测试
- 根据硬件原理图对设备树进行修改,然后用命令make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,并用新的设备树启动目标板。
- 从这里下载代码并进行编译,得到GPIO模拟I2C适配器的驱动,然后将编译出来的.ko文件拷贝到目标板根文件系统的root目录中。
- 从这里下载代码并进行编译,得到AP3216C的驱动及其测试程序,然后将编译出来的.ko文件和.out文件拷贝到目标板根文件系统的root目录中。
- 执行命令insmod i2c_adapter.ko加载I2C适配器驱动。
- 执行命令insmod ap3216c.ko加载AP3216C驱动,此时在/dev/目录下生成ap3216c_0设备文件。
- 执行命令./app.out /dev/ap3216c_0启动ap3216c的测试程序。测试程序会通过系统的read函数调用AP3216C驱动的read函数,AP3216C驱动的read函数又通过GPIO模拟I2C适配器驱动控制GPIO读取AP3216C的寄存器,然后将寄存器的值返回应用层。