参考Linux设备驱动开发详解第十五章
一、I2C体系结构
I2C和SMBus设备使用7位地址,总线上最多支持127个设备,最高100kbit/s的传输率, 快速模式下可达400kbit/s,半双工.每个I2C/SMBus客户都分配有一个从地址,作为 设备标识,I2C总线被非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中 在Linux系统中,I2C驱动由3部分组成,即I2C核心、I2C总线驱动和I2C设备驱动。 这3部分相互协作,形成了非常通用、可适应性很强的I2C框架。 I2C核心 I2C 核心提供了I2C总线驱动和设备驱动的注册、注销方法, I2C通信方法(即“algorithm)上层的、与具体适配器无关的代码以及探测设备、 检测设备地址的上层代码等 I2C总线驱动 I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制, 甚至直接集成在CPU内部。I2C总线驱动主要包含了I2C适配器数据结构 i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm 和控制I2C适配器产生通信信号的函数。 经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生 开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。 I2C设备驱动 I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受 CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据 I2C设备驱动主要包含了数据结构i2c_driver和i2c_client, 我们需要根据具体设备实现其中的成员函数。 drivers/i2c/i2c-core.c 这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口 drivers/i2c/i2c-dev.c 实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。 通过适配器访问设备时的主设备号都为89, 次设备号为0~255。应用程序通过 “i2c-%d” (i2c-0, i2c-1, ..., i2c-10, ...) 文件名并使用文件操作接口open() write()、read()、ioctl()和close()等来访问这个设备 i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口 用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或 寄存器并控制I2C设备的工作方式 busses文件夹 这个文件中包含了一些I2C总线的驱动,如S3C2410 S3C6410的I2C控制器驱动为i2c-s3c2410.c。 algos文件夹 实现了一些I2C总线适配器的algorithm。 /include/linux/i2c.h /** *i2c_adapter 是用于标识物理I2C总线的结构 *要使用它的算法 */ struct i2c_adapter { struct module *owner;//所属模块 unsigned int id;//algorithm的类型,定义于include/linux/i2c-id.h unsigned int class; /* 允许探测的*/ const struct i2c_algorithm *algo; /* 总线通信方法结构体指针 */ void *algo_data;//algorithm数据 /* 适用于所有设备的数据 */ struct rt_mutex bus_lock; int timeout; /* in jiffies */ int retries;//重复次数 struct device dev; /* 适配器设备*/ int nr; char name[48];//适配器名称 struct completion dev_released;//用于同步 struct list_head userspace_clients; }; /** *下面的结构是为那些想实施新的总线驱动: *i2c_algorithm是一类硬件解决方案的接口 * */ struct i2c_algorithm { /** *如果适配器算法不能访问I2C,设置master_xfer为NULL,如果适配器 *算法可以做SMBus访问,设置smbus_xfer,如果设置为NULL,SMBus协议 *是模拟使用普通的I2C消息 */ int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);//i2c传输函数指针 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); //smbus传输函数指针 /* 返回适配器支持的功能 */ u32 (*functionality) (struct i2c_adapter *); }; /** *结构体i2c_driver代表一个I2C设备驱动程序 *@class:什么样的I2C设备,用于检测 *@attach_adapter:总线添加回调 *@detach_adapter:总线移除回调(for legacy drivers) *@probe:设备绑定回调 *@remove:设备解脱回调 *@shutdown:设备关机回调 *@suspend:设备挂起回调 *@resume:设备唤醒回调 *@command:总线信号回调(可选) *@driver:设备模型驱动 *@id_table:这个驱动支持的I2C器件 *@detect:设备检测回调 *@address_list:I2C地址探测 *@clients:我们创建了检测到的客户名单(I2C核心使用) *@ */ struct i2c_driver { unsigned int class; //通知一个新的总线驱动将会出现,你应避免使用这个,它可能 //会在将来被删除 int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针 int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针 /* 标准的驱动程序接口 */ int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); /* 驱动模型接口,不涉及到枚举 */ void (*shutdown)(struct i2c_client *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); //回调警告,例如SMBus报警协议,数据值的格式和意义取决于协议 void (*alert)(struct i2c_client *, unsigned int data); //像ioctl一样,可用于执行特定功能的命令与设备 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver; const struct i2c_device_id *id_table;//该驱动所支持的设备ID表 //设备自动创建设备的检测回调函数 int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; struct list_head clients; }; /** *结构体i2c_client:代表一个I2C从设备 *@flags:I2C_CLIENT_TEN表示该设备采用了10位芯片的地址 *I2C_CLIENT_PEC:表明它使用的SMBus数据包错误检查 *@addr:父适配器连接到I2C总线上的地址 *@name:表示设备的类型,通常这是芯片的名称 *@adapter:管理I2C总线设备 *@driver:设备的驱动程序,指针来访问例程 *@dev:驱动模型从设备的节点 *@irq:表示此装置所产生的IRQ *@detected:i2c_driver.clients或I2C核心成员列表 *i2c_client标识连接到一个单一的设备(芯片) *i2c_bus由驱动定义 */ struct i2c_client { unsigned short flags; /* 标志 */ unsigned short addr; /* 低7位的芯片地址 */ char name[I2C_NAME_SIZE]; //设备名称 struct i2c_adapter *adapter; /* 依附的i2c_adapter */ struct i2c_driver *driver; /* 依附的i2c_driver */ struct device dev; /* 设备结构体 */ int irq; /* 设备使用的中断号 */ struct list_head detected; }; i2c_adapter与i2c_algorithm i2c_adapter 对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。 一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。 缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的 i2c_algorithm的指针。 i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位 /** *i2c_msg:一个I2C传输起始 *@addr:从地址,七,或十位,当是十位的时候I2C_M_TEN必须被设置,必须支持I2C_FUNC_10BIT_ADDR *@flags:I2C_M_RD适合所有适配器处理 *@len:消息长度 *@buf:消息数据 */ struct i2c_msg { __u16 addr; /* slave address */ __u16 flags; #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ #define I2C_M_RD 0x0001 /* read data, from slave to master */ #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ __u16 len; /* msg length */ __u8 *buf; /* pointer to msg data */ }; i2c_driver与i2c_client i2c_driver对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。i2c_client对应 于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在i2c字符设备的私有信息结 构体中 i2c_driver 与i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测物 理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter driver指针指向该i2c_driver,并会调用i2c_adapter的client_register()函数 相反的过程发生在 i2c_driver 的detach_client()函数被调用的时候。 i2c_adpater与i2c_client i2c_adpater 与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由 于一个适配器上可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附, i2c_adpater中包括依附于它的i2c_client的链表。 工程师要实现的主要工作如下 提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适 配器从硬件上产生各种信号以及处理I2C中断等 提供I2C适配器的algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针, 并把i2c_algorithm指针赋值给i2c_adapter的algo指针。 实现I2C设备驱动与i2c_driver接口,用具体设备yyy的yyy_attach_adapter()函数指针、 yyy_detach_client()函 数指针和yyy_command()函数指针的赋值给i2c_driver的attach_adapter、 detach_adapter和detach_client指针。 实现I2C设备驱动的文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等。
二、I2C核心
drivers/i2c/i2c-core.c I2C核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数, 这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键, 因为I2C总线驱动和设备驱动之间依赖于I2C核心作为纽带。I2C核心中的主要函数包括: 增加删除i2c_adapter int i2c_add_adapter(struct i2c_adapter *adapter) int i2c_del_adapter(struct i2c_adapter *adap) 增加删除i2c_driver int i2c_register_driver(struct module *owner, struct i2c_driver *driver) void i2c_del_driver(struct i2c_driver *driver) 当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册 相反地,在client被取消关联的时候,sysfs文件和设备也被注销 I2C传输,发送和接收 //执行单一或联合的I2C消息 int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) //主传输模式发送一个单一的I2C消息 int i2c_master_send(struct i2c_client *client, const char *buf, int count) //主控I2C消息接收模式 int i2c_master_recv(struct i2c_client *client, char *buf, int count) i2c_transfer ()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和 i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息 i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是需找到 i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程
三、I2C总线驱动
I2C适配器驱动加载与卸载 I2C总线驱动模块的加载函数要完成两个工作: 初始化I2C适配器所使用的硬件资源,申请I/O地址、中断号等。 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员 已经被xxx适配器的相应函数指针所初始化。 I2C总线驱动模块的卸载函数要完成的工作与加载函数的相反: 释放I2C适配器所使用的硬件资源,释放I/O地址、中断号等 通过i2c_del_adapter()删除i2c_adapter的数据结构 I2C总线通信方法 我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数 和functionality()函数 functionality ()函数非常简单,用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、 I2C_FUNC_10BIT_ADDR、 I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等 master_xfer()函数在I2C适配器上完成传递给它的i2c_msg数组中的每个I2C消息 总线驱动master_xfer函数模板
四、I2C设备驱动
I2C 设备驱动要使用i2c_driver和i2c_client数据结构并填充其中的成员函数。 i2c_client一般被包含在设备的私有信息结构体 yyy_data中, 而i2c_driver则适宜被定义为全局变量并初始化 被初始化的i2c_driver static struct i2c_driver ov9640_i2c_driver = { .driver = { .name = "ov9640", }, .probe = ov9640_probe, .remove = ov9640_remove, .id_table = ov9640_id, }; Linux I2C设备驱动模块加载与卸载 I2C设备驱动模块加载函数通用的方法是在I2C设备驱动模块加载函数中完成两件事: 通过register_chrdev()函数将I2C设备注册为一个字符设备。 通过I2C核心的i2c_add_driver()函数添加i2c_driver 在模块卸载函数中需要做相反的两件事: 通过I2C核心的i2c_del_driver()函数删除i2c_driver 通过unregister_chrdev()函数注销字符设备 static int __init ov9640_module_init(void) { return i2c_add_driver(&ov9640_i2c_driver); } static void __exit ov9640_module_exit(void) { i2c_del_driver(&ov9640_i2c_driver); } Linux I2C设备驱动的数据传输
五、S3C6410 I2C总线驱动实例
S3C6410 处理器内部集成了一个I2C控制器,通过4个寄存器就可以方便地对其进行控制 这四个寄存器如下 IICCON:I2C控制寄存器 IICSTAT:I2C状态寄存器 IICDS:I2C收发数据移位寄存器 IICADD:I2C 地址寄存器 通过对IICCON,IICDS和IICADD的操作,可在I2C总线上产生开始位,停止位,数据和地址 而传输状态则通过IICSTAT寄存器获取 S3C6410 I2C的总线驱动drivers/i2c/busses/i2c-s3c2410.c I2C总线驱动设计主要要实现的工作包括: 设计对应于i2c_adapter_xxx_init()模板的S3C2410的模块加载函数和对应于 i2c_adapter_xxx_exit()函数模板的模块卸载函数。 设计对应于i2c_adapter_xxx_xfer()模板的S3C6410适配器的通信方法函数 functionality()函数只需简单的返回 I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING 表明其支持的功能 static int __init i2c_adap_s3c_init(void) { return platform_driver_register(&s3c24xx_i2c_driver); } subsys_initcall(i2c_adap_s3c_init); static void __exit i2c_adap_s3c_exit(void) { platform_driver_unregister(&s3c24xx_i2c_driver); } /* s3c24xx_i2c_probe * * called by the bus driver when a suitable device is found */ static int s3c24xx_i2c_probe(struct platform_device *pdev) { struct s3c24xx_i2c *i2c; struct s3c2410_platform_i2c *pdata; struct resource *res; int ret; pdata = pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "no platform data\n"); return -EINVAL; } i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); if (!i2c) { dev_err(&pdev->dev, "no memory for state\n"); return -ENOMEM; } strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &s3c24xx_i2c_algorithm; i2c->adap.retries = 2; i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; i2c->tx_setup = 50; spin_lock_init(&i2c->lock); init_waitqueue_head(&i2c->wait); /* 发现时钟并使能它 */ i2c->dev = &pdev->dev; i2c->clk = clk_get(&pdev->dev, "i2c"); if (IS_ERR(i2c->clk)) { dev_err(&pdev->dev, "cannot get clock\n"); ret = -ENOENT; goto err_noclk; } dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk); clk_enable(i2c->clk); /* 映射寄存器 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "cannot find IO resource\n"); ret = -ENOENT; goto err_clk; } i2c->ioarea = request_mem_region(res->start, resource_size(res), pdev->name); if (i2c->ioarea == NULL) { dev_err(&pdev->dev, "cannot request IO\n"); ret = -ENXIO; goto err_clk; } i2c->regs = ioremap(res->start, resource_size(res)); if (i2c->regs == NULL) { dev_err(&pdev->dev, "cannot map IO\n"); ret = -ENXIO; goto err_ioarea; } dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs, i2c->ioarea, res); /* 设置i2c核心需要的信息*/ i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; /* initialise the i2c controller */ ret = s3c24xx_i2c_init(i2c); if (ret != 0) goto err_iomap; /* *申请中断 */ i2c->irq = ret = platform_get_irq(pdev, 0); if (ret <= 0) { dev_err(&pdev->dev, "cannot find IRQ\n"); goto err_iomap; } ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED, dev_name(&pdev->dev), i2c); if (ret != 0) { dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq); goto err_iomap; } ret = s3c24xx_i2c_register_cpufreq(i2c); if (ret < 0) { dev_err(&pdev->dev, "failed to register cpufreq notifier\n"); goto err_irq; } //bus_num是platform数据 i2c->adap.nr = pdata->bus_num; ret = i2c_add_numbered_adapter(&i2c->adap); if (ret < 0) { dev_err(&pdev->dev, "failed to add bus to i2c core\n"); goto err_cpufreq; } platform_set_drvdata(pdev, i2c); clk_disable(i2c->clk); dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev)); return 0; } S3C6410 I2C总线通信方法 static const struct i2c_algorithm s3c24xx_i2c_algorithm = { .master_xfer = s3c24xx_i2c_xfer, .functionality = s3c24xx_i2c_func, }; /drivers/misc/eeprom/at24.c不依赖于具体的CPU和I2C控制器硬件特性 如果电路板包含该外设,只需要在板文件中添加对应的i2c_board_info arch/arm/mach-s3c64xx/mach-smdk6410.c static struct i2c_board_info i2c_devs0[] __initdata = { { I2C_BOARD_INFO("24c08", 0x50), }, { I2C_BOARD_INFO("wm8580", 0x1b), }, }
六、I2C读写函数的封装
static int ov9650_reg_read(struct i2c_client *client, u8 reg, u8 *val) { char buf[1]; int retval = 0; buf[0] = reg; retval = i2c_master_send(client, buf, 1); if (retval != 1) { dev_err(&client->dev, "%s read reg:%x error\n", __func__, reg); return retval; } retval = i2c_master_recv(client, buf, 1); if (retval != 1) { dev_err(&client->dev, "%s read reg:%x error\n", __func__, reg); return retval; } *val = buf[0]; return retval; } static int ov9650_reg_write(struct i2c_client *client, u8 reg, u8 val) { int retval = 0; if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) retval = i2c_smbus_write_byte_data(client, reg, val); else dev_err(&client->dev, "%s write reg:%x error\n", __func__, reg); return retval; } if (1) { u8 pid, ver; ov9650_reg_read(client, 0x0A, &pid); ov9650_reg_read(client, 0x0B, &ver); dev_info(&client->dev, "%s:PID=%x, VER=%x\n", client->name, pid, ver); } -------------------------------------------------------------------------------------- u8 s3c_fimc_i2c_read(struct i2c_client *client, u8 subaddr) { u8 buf[1]; struct i2c_msg msg = {client->addr, 0, 1, buf}; int ret; buf[0] = subaddr; ret = i2c_transfer(client->adapter, &msg, 1) == 1 ? 0 : -EIO; if (ret == -EIO) { err("i2c transfer error\n"); return -EIO; } msg.flags = I2C_M_RD; ret = i2c_transfer(client->adapter, &msg, 1) == 1 ? 0 : -EIO; return buf[0]; } int s3c_fimc_i2c_write(struct i2c_client *client, u8 subaddr, u8 val) { u8 buf[2]; struct i2c_msg msg = {client->addr, 0, 2, buf}; buf[0] = subaddr; buf[1] = val; return i2c_transfer(client->adapter, &msg, 1) == 1 ? 0 : -EIO; } ---------------------------------------------------- I2C发送和接收 drivers/i2c/i2c-core.c里实现 int i2c_master_send(struct i2c_client *client, const char *buf, int count) int i2c_master_recv(struct i2c_client *client, char *buf, int count) 第一个参数是i2c_client对象指针,第二个参数是要传输的数据buffer指针,第三个参数为ubffer 的大小 对于写I2C寄存器,给i2c_master_send函数传入两个字节的数据即可,第一个字节 为寄存器地址,第二个字节为要写入寄存器的数据 static int ov9650_reg_write(struct i2c_client* client, u8 reg, u8 data) { unsigned char buffer[2]; buffer[0] = reg; buffer[1] = data; if ( 2 != i2c_master_send(client, buffer, 2)){ printk(KERN_ERR "ov9650_i2c_reg_write fail!\n"); return -1; } return 0; } 读I2C时序要做的操作是,先向I2C总线上写入需要读的寄存器地址,然后读I2C总线上的值 u8 ov9650_reg_read(struct i2c_client* client, u8 reg, u8 *data) { //write reg addr if ( 1 != i2c_master_send(client, ®, 1)) { printk(KERN_ERR "ov9650_i2c_reg_read fail!\n"); return -1; } //wait msleep(10); //#include <linux/delay.h> //read if ( 1 != i2c_master_recv(client, data, 1)) { printk(KERN_ERR "ov9650_i2c_reg_read fail!\n"); return -1; } return 0; } 测试 --------------------------------------------------------- u8 pid; ov9650_reg_read(client, 0x0A, &pid);读寄存器ID PID=96 printk("PID=%x\n", pid); --------------------------------------------------------- u8 pid; ov9650_reg_write(client, 0x0C, 0xCD); ov9650_reg_read(client, 0x0C, &pid); printk("PID=%x\n", pid);//PID=cd