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

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_SIZE	0xFFFF48
#define USER_XFER_MAX_COUNT	0x8

/* 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 mutex	lock;
	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_t	status = 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_t	status = 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驱动的测试。


### 回答1: "Zynq-Linux移植学习笔记"是一篇关于在Zynq SoC平台上移植Linux操作系统的学习笔记。该笔记主要介绍了如何在Zynq平台上搭建交叉编译环境、编译内核、制作根文件系统以及启动Linux系统等方面的知识。通过学习这篇笔记,读者可以了解到如何将Linux操作系统移植Zynq平台上,并且可以通过实践来深入理解相关的知识和技 ### 回答2: Zynq Linux移植是搭建Zynq硬件平台和在该平台上运行Linux系统的过程。它包括了硬件的设计和软件的开发,这有助于实现在Zynq平台上开发Linux应用程序的目标。 首先,进行Zynq Linux移植前需要研究设备的结构和硬件构造。zynq硬件平台包含两个主要组成部分:PS和PL。PS负责处理器系统和外设的管理和控制,PL则是可编程逻辑,支持FPGA逻辑的定制化,同时也支持外设的实现。在移植时,需要设计PS的硬件架构和软件驱动程序,同时也需要配置PL。一般情况下,需要进行如(Xilinx SDK)和(Petalinux)等的软件开发环境安装和配置。 接下来,进行Linux系统的移植。这部分工作需要了解Linux内核的结构、功能和特性,然后根据硬件架构,对Linux系统进行调整和定制,构建出适合硬件平台的Linux系统。这个过程需要进行的工作包括:解编译适合SOC的内核、制定设备树、调整内核参数等。 最后,这些工作完成后,就可以在Zynq平台上编译和运行Linux应用程序了。开发者可以尝试通过开发板上的GPIO、I2C、SPI或UART等接口,学习Linux的设备驱动程序、网络编程、文件系统管理等知识点。可逐步学习如何掌握Linux的shell、打包工具、交叉编译工具等。 总结来说,Zynq Linux移植工作是基础的硬件构造、嵌入式软件和Linux知识的综合应用,需要开发者有至少三方面的技能。需要熟练编写硬件设计,熟练掌握Linux内核编程以及Linux系统软件的维护和管理。这些技能的应用能力对于移植Zynq平台Linux系统和应用程序的开发工程师来说是非常必要的。 ### 回答3: 随着嵌入式系统的广泛应用,zynq-linux移植成为了嵌入式开发的一个重要研究课题。zynq-linux移植涉及到许多方面的知识,在学习过程中需要注意以下几点: 一、zynq-linux移植前需要了解的基础知识 在开始进行zynq-linux移植之前,需要对Linux系统、ARM技术、FPGA开发、C语言等基础知识有一定的了解。同时需要熟悉zynq系列的基本架构和应用场景。 二、zynq-linux移植必要的步骤 zynq-linux移植的过程主要分为以下几个步骤:首先是确定硬件平台和环境搭建;其次是进行内核编译和配置;然后是uboot编译和烧录;最后是Linux文件系统的制作和烧录。在整个移植过程中,需要注意各个步骤的顺序和详细操作,确保每一步都正确完成。 三、zynq-linux移植中可能会遇到的问题 在zynq-linux移植过程中,可能会遇到各种问题,例如硬件平台的兼容性、内核配置的错误、uboot烧录问题、文件系统制作出错等。在遇到这些问题时,需要耐心地进行排查和解决,同时也可以借助搜索引擎和社区的技术支持。 四、zynq-linux移植之后的应用与拓展 zynq-linux移植成功之后,可以将其应用于各种嵌入式系统中,例如网络设备、智能家居、工业控制等领域。同时,还可以进行拓展和优化,例如添加各种驱动程序、优化系统性能等。 总之,在进行zynq-linux移植学习和实践中,需要认真学习基础知识,仔细操作每个步骤,及时排查并解决问题,并在成功移植之后持续进行应用与拓展。
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值