一、 前言
上一篇博文成功调通了 GPIO,接下来调试 I2C 接口,Hi3559V200 平台 I2C 使用频率最高的应该是作为 Sensor 的控制线。
本篇博文介绍通过平台设备来使用 I2C 的步骤。使用的硬件平台是 Hi3559V200DMEB VER.A,使用的 SDK 版本是 Hi3559V200_MobileCam_SDK_V1.0.1.5。
二、 硬件资源申请及分配
2.1 Hi3559V200 平台 I2C 介绍
Hi3559V200 平台一共有 8 组 I2C,硬件平台原配的 Sensor IMX458 挂载到第 0 组 I2C 总线上,所以接下来要实现检测到 IMX458 这个设备并能对其寄存器进行读写。海思平台可以通过 DTS 文件对硬件设备资源进行申请及分配,DTS 文件所在目录为 osdrv\opensource\kernel\linux-4.9.y\arch\arm\boot\dts,需要修改的文件有两个,分别是 hi3559v200.dtsi 和 hi3559v200-demb.dts。
2.2 修改 hi3559v200.dtsi
由于我们要在 Linux 系统这边操作 I2C0,但是在 hi3559v200.dtsi 中 I2C0 默认是分配在 LiteOS 系统那边的,所以在这里需要把 I2C0总线上面的 #ifndef CONFIG_ARCH_HISI_BVT_AMP 注释掉,注释的地方有两处:
2.3 修改 hi3559v200-demb.dts
这里仍然要找到 I2C0 总线,把上面的 #ifndef CONFIG_ARCH_HISI_BVT_AMP 注释掉,另外需要在这里添加 IMX458 这个设备,查找 IMX458 的 datasheet 得知其设备地址是 0x34,需要注意的是,这里使用的是 7 位地址,而 0x34 是 8 位地址,向右移一位后的 7 位地址是 0x1a,所以 IMX458 的设备地址应填写 0x1a。
注:完成此步骤后需要重新编译 kernel 并更新到板端。
三、 管脚复用
3.1 查询管脚复用寄存器
在 《Hi3559V200_PINOUT_CN.xlsx》 中查询I2C0 SDA 和 SCL 管脚的控制寄存器地址:
3.2 配置管脚复用
接下来需要把以上的三个引脚复用成 GPIO,海思提供了一个表格工具对引脚复用进行初始化配置,表格工具位于 osdrv\tools\pc\uboot_tools,对于 Hi3559V200DMEB 板,适用 Hi3559V200-DMEB_6L_T-DDR3_1800M_512MB_16bitx2-A7_900M-SYSBUS_300M.xlsm 表格,只需按照上面的例子把刚才查到的寄存器名称、地址、要写入的数据填进去即可:
注:添加复用后要重新编译 u-boot 并更新到板端。
四、 编写 I2C 平台驱动
4.1 编写驱动代码 i2c_driver.c
① 使用 i2c_driver 结构体设置 device 信息。
static const struct i2c_device_id imx458_id_table[] = {
{ "imx458", 0 },
{}
};
static struct i2c_driver imx458_driver = {
.driver = {
.name = "100ask",
.owner = THIS_MODULE,
},
.probe = imx458_probe,
.remove = __devexit_p(imx458_remove),
.id_table = imx458_id_table,
};
② 使用 i2c_add_driver() 函数注册驱动。
static int imx458_drv_init(void)
{
i2c_add_driver(&imx458_driver);
return 0;
}
static void imx458_drv_exit(void)
{
i2c_del_driver(&imx458_driver);
}
③ 与 device 匹配成功后在 probe 函数中创建设备节点绑定 IMX458。
static int __devinit imx458_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
imx458_client = client;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "imx458", &imx458_fops);
class = class_create(THIS_MODULE, "imx458");
device_create(class, NULL, MKDEV(major, 0), NULL, "imx458"); /* /dev/imx458 */
return 0;
}
④ 参考 《外围设备驱动设计指南》 的 i2c_read 和 i2c_write 函数编写读写 IMX458 寄存器的函数,IMX458 的寄存器都是 16bit 地址和 16bit 数据的,
所以读写函数只需要处理 16bit 的地址和数据。
static ssize_t imx458_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
int ret = 0;
int idx = 0;
int reg_addr = 0;
unsigned char data;
unsigned char ker_buf[2];
unsigned char read_buf[2];
struct i2c_client *client;
struct i2c_msg *msg;
client = imx458_client;
msg = &g_msg[0];
memset(msg, 0x0, sizeof(struct i2c_msg) * 2);
copy_from_user(ker_buf, buf, 2);
msg[0].addr = imx458_client->addr;
msg[0].flags = client->flags & I2C_M_TEN;
msg[0].len = 2;
msg[0].buf = read_buf;
read_buf[idx++] = ker_buf[0];
read_buf[idx++] = ker_buf[1];
reg_addr = read_buf[1] | (read_buf[0] << 8);
printk("addr = 0x%02x\n", reg_addr);
msg[1].addr = imx458_client->addr;
msg[1].flags = client->flags & I2C_M_TEN;
msg[1].flags |= I2C_M_RD;
msg[1].len = 2;
msg[1].buf = read_buf;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2)
{
reg_addr = read_buf[1] | (read_buf[0] << 8);
printk("addr = 0x%x\n", reg_addr);
copy_to_user(buf, read_buf, 2);
}
else
{
copy_to_user(buf, "read error", 10);
}
return 1;
}
static ssize_t imx458_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
int ret = 0;
int idx = 0;
int reg_addr,data;
unsigned char write_buf[8];
unsigned char ker_buf[4];
copy_from_user(ker_buf, buf, 4);
write_buf[idx++] = ker_buf[0];
write_buf[idx++] = ker_buf[1];
write_buf[idx++] = ker_buf[2];
write_buf[idx++] = ker_buf[3];
reg_addr = write_buf[1] | (write_buf[0] << 8);
data = write_buf[3] | (write_buf[2] << 8);
printk("addr = 0x%02x, data = 0x%02x\n", reg_addr, data);
ret = i2c_master_send(imx458_client, write_buf, idx);
return ret;
}
static struct file_operations imx458_fops = {
.owner = THIS_MODULE,
.read = imx458_read,
.write = imx458_write,
};
4.2 编写用程序 i2c_test.c
应用程序负责打开驱动文件,读写驱动文件,同样也只需要处理 16bit 的地址和数据。
int main(int argc,char *argv[])
{
int reg_addr = 0;
int write_data = 0;
int read_data = 0;
char input_buf[50] = {0};
unsigned char read_buf[2] = {0};
unsigned char write_buf[4] = {0};
int fd = open("/dev/imx458", O_RDWR);
if(fd < 0)
{
perror("Open file failed!!!\r\n");
return -1;
}
while(1)
{
printf("Please input or :\n");
scanf("%s", input_buf);
if(!(memcmp(input_buf, "write", 5)))
{
printf("input reg_addr:\n");
scanf("%x", ®_addr);
printf("input data:\n");
scanf("%x", &write_data);
write_buf[0] = (reg_addr >> 8);
write_buf[1] = reg_addr;
write_buf[2] = (write_data >> 8);
write_buf[3] = write_data;
int ret = write(fd, write_buf, strlen(write_buf));
if(ret < 0)
{
perror("Failed to write!!");
}
else
{
printf("\n----------write success!----------\n\n");
}
}
else if(!(memcmp(input_buf, "read", 4)))
{
printf("input reg_addr:\n");
scanf("%x", ®_addr);
read_buf[0] = (reg_addr >> 8);
read_buf[1] = reg_addr;
int ret = read(fd, read_buf, 2);
if(ret < 0)
{
perror("Failed to read!!");
}
else
{
read_data = read_buf[1] | (read_buf[0] << 8);
printf("0x%x\n", read_data);
printf("\n----------read success!----------\n\n");
}
}
else
{
printf("Ivalid input!\n");
}
}
close(fd);
return 0;
}
五、 验证过程
5.1 把驱动文件编译成 ko 模块后拷贝到板端,安装模块:
5.2 读写 I2C
使用官方应用 i2c_read 0 0x34 0x222 0x222 2 2 命令先读取一次地址为 0x222 的寄存器的数据,然后执行 i2c_test 应用程序验证是否能读取正确的值,然后再写一遍 0x222 寄存器,验证是否成功写入正确的值:
可以看到,第一次读到的数据是 0x100,与官方程序读到的数据一样,向寄存器写入 0x300 后,读到的数据变为 0x300。
根据以上结果可以得出结论,编写的驱动程序成功的实现了 I2C 设备寄存器的读写功能。
参考资料:
【1】《外围设备驱动 操作指南.doc》
【2】 《SUNNICSMX458WNPLCCDatasheet.pdf》
【3】 《HI3559V200DMEB_VER_A_SCH.pdf》
评论