【Hi3559V200 BSP 开发】I2C 驱动调试

一、 前言

       上一篇博文成功调通了 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", &reg_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", &reg_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》

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论