Linux内核很多驱动都使用到I2C子系统。EEPROM、RTC,电池,tp等。
inux内核的i2c-gpio是使用GPIO模拟I2C协议的驱动,在内核中已经实现了,我们要做的只需要配置2根GPIO即可。
GPIO模拟I2C协议的驱动位于drivers/i2c/busses目录。驱动名称为“i2c-gpio”,驱动文件为drivers/i2c/busses/i2c-gpio.c
1. GPIO支持要先保证是选上的
2. 先配置内核
Device Drivers->
I2C support --->
I2C Hardware Bus support --->
<*> GPIO-based bitbanging I2C
3. 修改内核文件
static struct i2c_gpio_platform_data da850_gpio_i2c_pdata = {
.sda_pin = DA850_GPIO_I2C_SDA, //通用的gpio引脚
.scl_pin = DA850_GPIO_I2C_SCL, //通用的gpio引脚
.udelay = 10, /* 50 KHz */
.timeout = 100,
};
static struct platform_device da850_gpio_i2c = {
.name = "i2c-gpio",
.id = 2, //生成 i2c-gpio.2
.dev = {
.platform_data = &da850_gpio_i2c_pdata,
},
};
平台初始化
static __init void da850_evm_init(void)
{
..........
..........
/*
* Though bootloader takes care to set emif clock at allowed
* possible rate. Kernel needs to reconfigure this rate to
* support platforms requiring fixed emif clock rate.
*/
ret = da850_set_emif_clk_rate();
if (ret)
pr_warning("da850_evm_init: Failed to set rate of pll0_sysclk3/emif clock: %d\n",
ret);
ret = da850_register_edma(da850_edma_rsv);
if (ret)
pr_warning("da850_evm_init: edma registration failed: %d\n",
ret);
ret = davinci_cfg_reg_list(da850_i2c0_pins);
if (ret)
pr_warning("da850_evm_init: i2c0 mux setup failed: %d\n",
ret);
platform_device_register(&da850_gpio_i2c); //注册平台设备
ret = da8xx_register_i2c(0, &da850_evm_i2c_pdata);
if (ret) {
pr_warning("da850_evm_init: i2c0 mux setup failed: %d\n",
ret);
return;
}
.........
i2c_register_board_info(2, da850_gpio_i2c_devices, //gpio-i2c
ARRAY_SIZE(da850_gpio_i2c_devices));
........
}
i2c_board_info :这是个枚举类型的数组,把要加入的GPIO加入到这个数组里面。
static struct i2c_board_info __initdata da850_gpio_i2c_devices[] = {
{
I2C_BOARD_INFO("bq20z75", 0x0b),
},
};
这样就实现了gpio-i2c功能了,但是在使用前需要先确保当期使用的io口是作为GPIO使用的,我就踩了这个坑。
对于引脚配置成GPIO过程
OMAP-L138 C6000 DSP+ARM Processor Technical Reference Manual (Rev.A).pdf 244页有相应的管脚配置说明
引脚配置复用的位置在\linux-3.3\arch\arm\mach-davinci\da850.c的 结构体static const struct mux_config da850_pins[] = {}
static const struct mux_config da850_pins[] = {
#ifdef CONFIG_DAVINCI_MUX
..............
MUX_CFG(DA850, EMA_A_22, 10, 4, 15, 1, false)
MUX_CFG(DA850, EMA_A_20, 10, 12, 15, 1, false)
...............
}
参数说明: MUX_CFG(DA850, EMA_A_22, 4, 24, 15, 2, false)
MUX_CFG :这个宏在arch/arm/mach-davinci/mux.h定义
#define MUX_CFG(soc, desc, muxreg, mode_offset, mode_mask, mux_mode, dbg)
DA850: 平台
EMA_A_22: 管脚描述
4: 第4组pinmux寄存器(引脚复用寄存器的名称,展开为 PINMUX4 )
24: 在寄存器PINMUX4中的bit24到bit27位,相应位的偏移值
15: 就是pinmux相应位全为1的值(掩码值)
2 : mux_mode(设置这个复用寄存器的GPIO引脚功能)
现在需要将da850.c使用为EMA_A的默认的引脚复用为GPIO引脚,查看管脚配置pdf说明是否支持,
//需要注意将使用该寄存器复用功能的地方全注释,不然编译会报错
da850.c , include/mach/mux.h , board-da850-evm.c
管脚描述改为当前的修改后的描述,我当前修改描述为GPIO4_4
///
管脚配置完成修改完成后对内核修改验证方式
用i2cdetect检测有几组i2c总线在系统上,输入:./i2cdetect -l
用i2cdetect检测挂载在i2c总线上器件,
i2cdetect -r -y 2(检测i2c-1上的挂载情况)
由上图可知 0x0b地址有挂载。而这几个分别是0x0b(bq20z75)
i2cset -f -y 1 0x0b 0x09 0x3f (设置i2c-1上0x0b 器件的0x09 寄存器值为0x3f)
i2cget -f -y 1 0x0b 0x09 (读取i2c-1上0x0b 器件的0x09 寄存器值)
i2cdetect -r -y 2 这个命令能确定i2c-2总线上那个地址是挂载设备的
此方式验证i2c功能的从设备地址是否正确,如果写入的从设备地址不是实际地址则写入时是不生效的
i2c功能数据读写
//test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#include <string.h>
unsigned char i2c_read(unsigned char device_addr, unsigned char sub_addr, unsigned char *buff, int ByteNo)
{
int fd, ret, i;
unsigned char buftmp[32];
struct i2c_rdwr_ioctl_data i2c_data;
const char *i2c_dev = "/dev/i2c-2";
device_addr >>= 1;
//init
fd = open(i2c_dev, O_RDWR);
if (fd<0)
{
printf("not have /dev/i2c-1 t\r\n");
return -1;
}
i2c_data.nmsgs = 2;
i2c_data.msgs = (struct i2c_msg *)malloc(i2c_data.nmsgs *sizeof(struct i2c_msg));
if (i2c_data.msgs == NULL)
{
printf("malloc error");
close(fd);
return -1;
}
ioctl(fd, I2C_TIMEOUT, 1);
ioctl(fd, I2C_RETRIES, 2);
写i2c寄存器直接写,读i2c寄存器之前需要先向要读取的寄存器写,我这个只有读操作
//write reg
memset(buftmp, 0, 32);
buftmp[0] = sub_addr;
i2c_data.msgs[0].len = 1;
i2c_data.msgs[0].addr = device_addr;
i2c_data.msgs[0].flags = 0; // 0: write 1:read
i2c_data.msgs[0].buf = buftmp;
//read data
i2c_data.msgs[1].len = ByteNo;
i2c_data.msgs[1].addr = device_addr;
i2c_data.msgs[1].flags = 1; // 0: write 1:read
i2c_data.msgs[1].buf = buff;
ret = ioctl(fd, I2C_RDWR, (unsigned long)&i2c_data);
if (ret < 0)
{
printf("read data %x %x error\r\n", device_addr, sub_addr);
close(fd);
free(i2c_data.msgs);
return 1;
}
free(i2c_data.msgs);
close(fd);
printf("i2c_read from: 0x%02x:\t",buftmp[0]);
//读取的是2byte数据,先低8位,后高8位,拼接起来就是实际数据,根据需要对数据进行操作
int data = buff[1] *256 + buff[0];
printf(" %d\n",data);
for (i = 0; i < ByteNo; i++)
{
printf(" 0x%02x",buff[i]);
}
printf("\n");
return 0;
}
int main(void)
{
unsigned char buf[10]= {0};
i2c_read(0x16, 0x16, buf, 2); //10 is on charging, 50 is not charging
}