问答
直播中

中科院

10年用户 208经验值
擅长:可编程逻辑 电源/新能源 MEMS/传感技术 嵌入式技术 连接器 光电显示 存储技术 接口/总线/驱动 控制/MCU RF/无线
私信 关注

【Z-turn Board试用体验】+ Zynq linux的I2C驱动学习笔记(二)

本帖最后由 中科院 于 2015-6-23 18:52 编辑

首先介绍一下I2C的操作及特点:I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。
写操作
写操作分为字节写和页面写两种操作,对于页面写根据芯片的一次装载的字节不同有所不同。
读操作
读操作有三种基本操作:当前地址读、随机读和顺序读。

在xilinx-linux中,i2c从设备是通过dts文件传递给内核的,内核通过zynq_init_machine函数注册所有的i2c从设备,i2c_client.

在linux的设备和驱动管理体系中,所有的非热插拔设备默认是在 init_machine函数成员中加入相应维护设备的双向链表中,包括platform_device和其他的设备。当一个特定的设备驱动通过driver_register加入对应的总线下时,回去遍历对应总线下的设备双向链表,当驱动和设备匹配时,会触发驱动的probe函数。
DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
.smp        = smp_ops(zynq_smp_ops),
.map_io        = zynq_map_io,
.init_irq        = zynq_irq_init,
.init_machine        = zynq_init_machine,
.init_late        = zynq_init_late,
.init_time        = zynq_timer_init,
.dt_compat        = zynq_dt_match,
.reserve        = zynq_memory_init,
.restart        = zynq_system_reset,
MACHINE_END

参考arch/arm/mach-zynq初始化代码

卸除:释放硬件资源,调用i2c_del_adapter卸载i2c适配器
void i2c_del_adapter(struct i2c_adapter *adap)
{

list_for_each_entry_safe(client, next, &adap->userspace_clients,
detected) {
dev_dbg(&adap->dev, "Removing %s at 0x%xn", client->name,
client->addr);
list_del(&client->detected);
i2c_unregister_device(client);
}

//卸载所有的从i2c设备

device_unregister(&adap->dev);

//卸载i2c适配器
}

2、编写I2C的总线通讯方法algorithm
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);

主要实现上面的两个函数。

大部分时间,我们需要定义一个XXX_i2c结构体,比如drivers/i2c/busses/i2c-s3c2410.c中的struct s3c24xx_i2c。
XXX_i2c结构体中包含struct i2c_msg *msg;struct i2c_adapter adap;void __iomem *regs;等
struct i2c_msg *msg接收用户层的数据发到i2c总线,或从i2c总线读取数据到用户层。
在适配器的probe函数中:
struct xi2cps *id;
platform_set_drvdata(pdev, id);
id->adap.dev.of_node = pdev->dev.of_node;
id->adap.algo = (struct i2c_algorithm *) &xi2cps_algo;
id->adap.timeout = 0x1F;
/* Default timeout value */
id->adap.retries = 3;
/* Default retry value. */
id->adap.algo_data = id;
id->adap.dev.parent = &pdev->dev;

四、linux i2c从设备驱动
硬件方面,I2C主设备已经集成在主芯片内,软件方面,linux也为我们提供了相应的驱动程序,位于drivers/i2c/bus下。那么接下来I2C从设备驱动就变得容易得多。既然系统加载I2C主设备驱动时已经注册了i2c_adapter和i2c_client,那么I2C从设备主要完成三大任务:
系统初始化时添加以i2c_board_info为结构的I2C从设备的信息(针对不使用dts描述硬件信息的威廉希尔官方网站 板)
在I2C从设备驱动程序里使用i2c_adapter里所提供的算法,即实现I2C通信

将I2C从设备的特有数据结构挂在到i2c_client.dev->driver_data下。

以/driver/misc/eeprom/eeprom.c为例:

static struct i2c_driver eeprom_driver = {
.driver = {
.name        = "eeprom",
},
.probe        = eeprom_probe,
.remove        = eeprom_remove,
.id_table        = eeprom_id,

.class        = I2C_CLASS_DDC | I2C_CLASS_SPD,
.detect        = eeprom_detect,
.address_list        = normal_i2c,
};

static struct i2c_driver eeprom_driver = {
.driver = {
.name        = "eeprom",
},
.probe        = eeprom_probe,
.remove        = eeprom_remove,
.id_table        = eeprom_id,

.class        = I2C_CLASS_DDC | I2C_CLASS_SPD,
.detect        = eeprom_detect,
.address_list        = normal_i2c,
};

i2c_driver 中的driver.name 不一定要和i2c_client一致,因为这只是他们配备的依据之一。id_table 是i2c_device_id结构体的一个对象,里面定义了i2c驱动对应设备的i2c地址。struct i2c_device_id里面的字符串与 I2C_BOARD_INFO里面的匹配后,xxx_led_probe也会调用,这是设备和驱动匹配的依据之二。

使用Device Tree后,驱动需要与.dts中描述的设备结点进行匹配,从而引发驱动的probe()函数执行。对于platform_driver而言,需要添加一个OF匹配表
static const struct i2c_device_id pcf8563_id[] = {
{ "pcf8563", 0 },
{ "rtc8564", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, pcf8563_id);
#ifdef CONFIG_OF
static const struct of_device_id pcf8563_of_match[] = {
{ .compatible = "nxp,pcf8563" },
{}
};
MODULE_DEVICE_TABLE(of, pcf8563_of_match);
#endif
static struct i2c_driver pcf8563_driver = {
.driver        = {
.name        = "rtc-pcf8563",
.owner        = THIS_MODULE,
.of_match_table = of_match_ptr(pcf8563_of_match),
},
.probe        = pcf8563_probe,
.id_table        = pcf8563_id,
};
/* Each client has this additional data */

不过这边有一点需要提醒的是,I2C和SPI外设驱动和Device Tree中设备结点的compatible 属性还有一种弱式匹配方法,就是别名匹配。compatible 属性的组织形式为,,别名其实就是去掉compatible 属性中逗号前的manufacturer前缀。关于这一点,可查看drivers/spi/spi.c的源代码,函数spi_match_device()暴露了更多的细节,如果别名出现在设备spi_driver的id_table里面,或者别名与spi_driver的name字段相同,SPI设备和驱动都可以匹配上:
struct eeprom_data {
struct mutex update_lock;
u8 valid;        /* bitfield, bit!=0 if slice is valid */
unsigned long last_updated[8];        /* In jiffies, 8 slices */
u8 data[EEPROM_SIZE];        /* Register values */
enum eeprom_nature nature;
};

static int eeprom_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct eeprom_data *data;
i2c_set_clientdata(client, data);//将设备的数据结构挂到i2c_client.dev->p->driver_data下
name[0] = i2c_smbus_read_byte_data(client, 0x80);//i2c-core提供的接口,利用i2c_adapter的算法实现I2C通信

}

i2c_adapter是一个i2c总线控制器,i2c_add_driver会把i2c_driver挂到i2c总线上,并搜索总线上所有和它匹配的i2c_client,成功的话i2c_driver的probe函数就会被调用,搜索到的i2c_client会作为参数传递给probe函数。因为一个i2c_driver可能被多个i2c_client使用,因此就了解i2c_set_clientdata(client, data);调用的必要性了。就是说多个clients可以用一个driver,但是各自有自己的私有数据。

注:module_i2c_driver是一个针对i2c的宏定义,
定义位于/include/linux/i2c.h/

#define module_i2c_driver(__i2c_drive)
module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)

使用module_i2c_driver(xxx_i2c_driver)
可以取代
static int __init xxx_i2c_init(void)
{return i2c_add_driver(&xxx_i2c_driver);
}
static void __exit xxx_i2c_exit(void)
{i2c_del_driver(&xxx_i2c_driver);
}
module_init(xxx_i2c_init);
module_exit(xxx_i2c_exit);





更多回帖

发帖
×
20
完善资料,
赚取积分