Zynq-Linux移植学习笔记之13-i2c驱动配置

来源:互联网 发布:知世故而不世故 下联 编辑:程序博客网 时间:2024/05/16 01:52

1、 背景介绍

板子上通过I2C总线与zynq相连的是三片1848


如上图所示,zynq通过I2C总线与3片CPS-1848交换芯片相连,3片1848芯片的I2C地址分别为2,4,8.

目前zynq上linux I2C驱动采用的是i2c-cadence(drivers/i2c/buses),对应于i2c驱动中的bus driver(总线驱动,也称为适配器驱动)。需要实现的是i2c驱动中的设备驱动,类似于eeprom驱动(drivers/misc/eeprom)。

 

2、devicetree配置

706的devicetree中关于I2C的部分如下:

i2c@e0004000 {compatible = "cdns,i2c-r1p10";status = "okay";clocks = <0x1 0x26>;interrupt-parent = <0x3>;interrupts = <0x0 0x19 0x4>;reg = <0xe0004000 0x1000>;#address-cells = <0x1>;#size-cells = <0x0>;clock-frequency = <0x61a80>;pinctrl-names = "default";pinctrl-0 = <0xb>;i2cswitch@74 {compatible = "nxp,pca9548";#address-cells = <0x1>;#size-cells = <0x0>;reg = <0x74>;i2c@0 {#address-cells = <0x1>;#size-cells = <0x0>;reg = <0x0>;clock-generator@5d {#clock-cells = <0x0>;compatible = "silabs,si570";temperature-stability = <0x32>;reg = <0x5d>;factory-fout = <0x9502f90>;clock-frequency = <0x8d9ee20>;};};i2c@1 {#address-cells = <0x1>;#size-cells = <0x0>;reg = <0x1>;hdmi-tx@39 {compatible = "adi,adv7511";reg = <0x39>;adi,input-depth = <0x8>;adi,input-colorspace = "yuv422";adi,input-clock = "1x";adi,input-style = <0x3>;adi,input-justification = "evenly";};};i2c@2 {#address-cells = <0x1>;#size-cells = <0x0>;reg = <0x2>;eeprom@54 {compatible = "at,24c08";reg = <0x54>;};};i2c@3 {#address-cells = <0x1>;#size-cells = <0x0>;reg = <0x3>;gpio@21 {compatible = "ti,tca6416";reg = <0x21>;gpio-controller;#gpio-cells = <0x2>;};};i2c@4 {#address-cells = <0x1>;#size-cells = <0x0>;reg = <0x4>;rtc@51 {compatible = "nxp,pcf8563";reg = <0x51>;};};i2c@7 {#address-cells = <0x1>;#size-cells = <0x0>;reg = <0x7>;ucd90120@65 {compatible = "ti,ucd90120";reg = <0x65>;};};};};

参考706中的devicetree,通过i2c控制三片1848, devicetree格式修改如下:

i2c@e0004000 {compatible = "cdns,i2c-r1p10";status = "okay";clocks = <0x1 0x26>;interrupt-parent = <0x3>;interrupts = <0x0 0x19 0x4>;reg = <0xe0004000 0x1000>;#address-cells = <0x1>;#size-cells = <0x0>;clock-frequency = <0x61a80>;cps1848@2 {compatible = "cps1848";reg = <0x2>;};cps1848@4 {compatible = "cps1848";reg = <0x4>;};cps1848@8 {compatible = "cps1848";reg = <0x8>;};};

如上就配置好了三个地址分别为2,4,8的设备,暂时给它们起名叫cps1848.


3、 kernel配置

kernel中xilinx已经实现了i2c的bus driver,我们只需要实现device driver。由于eeprom为标准i2c设备,可以将eeprom为模板实现1848的设备驱动。修改过程中注意匹配设备的name和驱动id_table中的name,设备name就是devicetree中的cps1848.

实现后的cps1848代码如下:

/* * CPS1848 bus driver * * Copyright (C) 2014 CGT Corp. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA * */#define DEBUG#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/i2c.h>#include <linux/mutex.h>#include <linux/delay.h>#include <linux/serial_core.h>/* Each client has this additional data */#define USER_EEPROM_SIZE0xFFFF48#define USER_XFER_MAX_COUNT0x8/* Addresses to scan */static const unsigned short cps1848_i2c[] = { 0x3, I2C_CLIENT_END };static unsigned read_timeout = 25;module_param(read_timeout, uint, 0);MODULE_PARM_DESC(read_timeout, "Time (in ms) to try reads (default 25)");static unsigned write_timeout = 25;module_param(write_timeout, uint, 0);MODULE_PARM_DESC(write_timeout, "Time (in ms) to try writes (default 25)");struct cps1848_data {struct mutexlock;u8*data;};static ssize_t cps1848_eeprom_read( struct i2c_client *client,char *buf, unsigned offset, size_t count){struct i2c_msg msg[2];u8 msgbuf[4];unsigned long timeout, transfer_time;int status;memset(msg, 0, sizeof(msg));msgbuf[0] = (u8)((offset >> 18) & 0x3f);msgbuf[1] = (u8)((offset >> 10) & 0xff);msgbuf[2] = (u8)((offset >>  2) & 0xff);msg[0].addr = client->addr;msg[0].buf = msgbuf;msg[0].len = 3;msg[1].addr = client->addr;msg[1].flags = I2C_M_RD;msg[1].buf = buf;msg[1].len = count;/* * Reads fail if the previous write didn't complete yet. We may * loop a few times until this one succeeds, waiting at least * long enough for one entire page write to work. */timeout = jiffies + msecs_to_jiffies(read_timeout);do {transfer_time = jiffies;status = i2c_transfer(client->adapter, msg, 2);if (status == 2)status = count;dev_dbg(&client->dev, "read %ld@0x%lx --> %d (%ld)\n",count, (unsigned long)offset, status, jiffies);if (status == count)return count;/* REVISIT: at HZ=100, this is sloooow */msleep(1);} while (time_before(transfer_time, timeout));return -ETIMEDOUT; }static ssize_t cps1848_read(struct file *filp, struct kobject *kobj,    struct bin_attribute *bin_attr,    char *buf, loff_t offset, size_t count){struct i2c_client *client = kobj_to_i2c_client(kobj);struct cps1848_data *data = i2c_get_clientdata(client);ssize_t retval = 0;if (offset > USER_EEPROM_SIZE)return 0;if (offset + count > USER_EEPROM_SIZE)count = USER_EEPROM_SIZE - offset;mutex_lock(&data->lock);dev_dbg(&client->dev, "cps1848 start read %ld@0x%lx ..\n", count, (unsigned long)offset);while (count > 0) {ssize_tstatus = count>USER_XFER_MAX_COUNT?USER_XFER_MAX_COUNT:count;status = cps1848_eeprom_read(client, buf, offset, status);if (status <= 0) {if (retval == 0)retval = status;break;}buf += status;offset += status;count -= status;retval += status;}dev_dbg(&client->dev, "cps1848 end read %ld@0x%lx  !\n", retval, (unsigned long)offset);mutex_unlock(&data->lock);return retval; }static ssize_t cps1848_eeprom_write(struct i2c_client *client,struct cps1848_data *data,char *buf, unsigned offset, size_t count){struct i2c_msg msg[1];u8 *msgbuf;unsigned long timeout, transfer_time;int status;memset(msg, 0, sizeof(msg));msgbuf = data->data;msgbuf[0] = (u8)((offset >> 18) & 0x3f);msgbuf[1] = (u8)((offset >> 10) & 0xff);msgbuf[2] = (u8)((offset >>  2) & 0xff);memcpy(msgbuf+3, buf, count);msg[0].addr = client->addr;msg[0].buf = msgbuf;msg[0].len = 3 + count;/* * Reads fail if the previous write didn't complete yet. We may * loop a few times until this one succeeds, waiting at least * long enough for one entire page write to work. */timeout = jiffies + msecs_to_jiffies(write_timeout);do {transfer_time = jiffies;status = i2c_transfer(client->adapter, msg, 1);if (status == 1)status = count;dev_dbg(&client->dev, "write %ld@0x%lx --> %d (%ld)\n",count, (unsigned long)offset, status, jiffies);if (status == count)return count;/* REVISIT: at HZ=100, this is sloooow */msleep(1);} while (time_before(transfer_time, timeout));return -ETIMEDOUT; }static ssize_t cps1848_write(struct file *filp, struct kobject *kobj,    struct bin_attribute *bin_attr,    char *buf, loff_t offset, size_t count){struct i2c_client *client = kobj_to_i2c_client(kobj);struct cps1848_data *data = i2c_get_clientdata(client);ssize_t retval = 0;if (offset > USER_EEPROM_SIZE)return 0;if (offset + count > USER_EEPROM_SIZE)count = USER_EEPROM_SIZE - offset;mutex_lock(&data->lock);dev_dbg(&client->dev, "cps1848 start write %ld@0x%lx ..\n", count, (unsigned long)offset);while (count > 0) {ssize_tstatus = count>USER_XFER_MAX_COUNT?USER_XFER_MAX_COUNT:count;status = cps1848_eeprom_write(client, data, buf, offset, status);if (status <= 0) {if (retval == 0)retval = status;break;}buf += status;offset += status;count -= status;retval += status;}dev_dbg(&client->dev, "cps1848 end write %ld@0x%lx  !\n", retval, (unsigned long)offset);mutex_unlock(&data->lock);return retval; }static struct bin_attribute user_eeprom_attr = {.attr = {.name = "eeprom",.mode = (S_IRUSR | S_IWUSR),},.size = USER_EEPROM_SIZE,.read = cps1848_read,.write = cps1848_write,};/* Return 0 if detection is successful, -ENODEV otherwise */static int cps1848_detect(struct i2c_client *client, struct i2c_board_info *info){struct i2c_adapter *adapter = client->adapter;if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {dev_dbg(&client->dev, "cps1848 detect error for BYTE access !\n");return -ENODEV;}strlcpy(info->type, "eeprom", I2C_NAME_SIZE);return 0; }static int cps1848_probe(struct i2c_client *client, const struct i2c_device_id *id){struct i2c_adapter *adapter = client->adapter;struct cps1848_data *data;int err ;dev_notice(&client->dev, "CPS1848 driver: " __DATE__ " " __TIME__ " \n" );if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {dev_err(&client->dev, "CPS1848 driver:  BYTE DATA not supported! \n" );return -ENODEV;}if (!(data = kzalloc(sizeof(struct cps1848_data), GFP_KERNEL))) {dev_err(&client->dev, "CPS1848 driver:  Memory alloc error ! \n" );return -ENOMEM;}/* alloc buffer */data->data = devm_kzalloc(&client->dev, USER_XFER_MAX_COUNT + 8, GFP_KERNEL);if (!data->data) {dev_err(&client->dev, "CPS1848 driver:  Memory alloc error ! \n" );err = -ENOMEM;goto exit_kfree;}/* Init real i2c_client */i2c_set_clientdata(client, data);mutex_init(&data->lock);err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr);if (err) {dev_err(&client->dev, "CPS1848 driver:  sysfs create error ! \n" );goto exit_kfree;}return 0;exit_kfree:if(data->data)kfree(data->data);kfree(data);return err; }static int cps1848_remove(struct i2c_client *client){struct cps1848_data *data = i2c_get_clientdata(client);sysfs_remove_bin_file(&client->dev.kobj, &user_eeprom_attr);if(data->data)kfree(data->data);kfree(data);return 0; }static const struct i2c_device_id cps1848_id[] = {{ "cps1848", 0 },{ }};MODULE_DEVICE_TABLE(i2c, cps1848_id);static struct i2c_driver cps1848_driver = {.driver = {.name= "cps1848",},.probe= cps1848_probe,.remove= cps1848_remove,.id_table= cps1848_id,.class= I2C_CLASS_SPD,.detect= cps1848_detect,.address_list= cps1848_i2c,};module_i2c_driver(cps1848_driver);MODULE_AUTHOR("RobinLee");MODULE_DESCRIPTION("CPS1848 driver");MODULE_LICENSE("GPL");

将该代码命名为i2c-1848放在drivers/i2c/muxes下



修改muxes的Kconfig文件以及Makefile文件,加入针对1848的配置选项



在编译内核菜单中能看到新增加配置选项




选择以后进行编译,这样kernel配置就完成了。


4、 i2c驱动测试

系统上电启动,加载devicetree,kernel,uramdisk,能看到kernel启动时已经加载了cps1848驱动。



上图中列出了系统检测到的三个i2c设备,名称为cps1848,地址为0002,0004,0008.

为了针对cps1848进行测试,首先要知道三片1848在系统中的位置(在linux中所有设备都是以文件形式挂载)。最终在sys/class/i2c-dev/i2c-0/device下找到了三个设备。



根据该设备修改测试程序(app-cps1848.tar.gz 下载地址见:http://download.csdn.net/detail/jj12345jj198999/9837954)中的文件位置

app-cps1848/cps1848/app/cps1848.c


编译测试程,得到cps1848可执行文件



将cps1848放进uramdisk中,参考:rootfs修改




产生新的uramdisk.image.gz,重新加载linux

在/usr/sbin下运行cps1848,进入cps1848控制界面



输入get 0,获取1848地址为0寄存器的值,该值为0x38007403

查看cps1848的datasheet,发现值确实是这个,大小端颠倒一下。



重复上述测试过程,再测试0002和0004位置的1848,最终实现对1848驱动的测试。


0 1
原创粉丝点击