i2c总线是一种十分常见的板级总线,它包括i2c板级文件和i2c的总线驱动,和i2c的设备驱动,设备驱动可以通过适配器或者driver结构体中的名字和总线匹配,总线也是靠名字和板级文件匹配的
i2c子系统框架
下图就是我理解的i2c驱动框架示意图, 类似中断子系统, i2c子系统中也使用一个对象来描述一个物理实体, 设备对象与驱动分离, 驱动结合设备对象对硬件设备的描述才可以驱动一个具体的物理设备, 体现了分离的设计思想, 实现了代码的复用, 比如:
- 一个i2c控制器就对应一个i2c_board_info, 它驱动就是s3c2410_i2c_driver, 他们通过platform_bus_type协调工作。
- 一个i2c总线上的设备就对应内核中的一个i2c_client类型的对象, 它的驱动就是的i2c_driver, 二者通过i2c_bus_type协调工作。
- 同样是抽象的思路, 对于i2c总线本身, 内核也使用i2c_bus_type来描述。
事实上, 对于任何一种总线, 内核都有一个bus_type类型的对象与之对应, 但是platform_bus_type并没有对应的实际的物理总线, 这也就是platform总线也叫虚拟总线的原因.
除了分离,i2c子系统也体现的软件分层的设计思想, 整个i2c子系统由3层构成:设备驱动层--i2c核心--控制器驱动
除了经典的分层与分离模型,我们也可以看到一个有意思的现象——Linux 的应用程序不但可以通过设备驱动来访问i2c从设备,还可以通过一个并没有直接挂接到i2c_bus_type的i2c_cleint来找到主机控制器进而访问任意一个i2c设备, 这是怎么回事呢? 我会在下一篇说^-^
核心结构和方法简述
核心结构
- i2c_adapter对象实现了一组通过一个i2c控制器发送消息的所有信息, 包括时序, 地址等等, 即封装了i2c控制器的"控制信息"。它被i2c主机驱动创建, 通过clien域和i2c_client和i2c_driver相连, 这样设备端驱动就可以通过其中的方法以及i2c物理控制器来和一个i2c总线的物理设备进行交互
- i2c_algorithm描述一个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调。
- i2c_client描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。
- i2c_driver描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象以及i2c_adapter对象相连
- i2c_msg描述一个在设备端和主机端之间进行流动的数据, 在设备驱动中打包并通过i2c_transfer()发送。相当于skbuf之于网络设备,urb之于USB设备。
核心方法
- i2c_transfer()是i2c核心提供给设备驱动的发送方法, 通过它发送的数据需要被打包成i2c_msg, 这个函数最终会回调相应i2c_adapter->i2c_algorithm->master_xfer()接口将i2c_msg对象发送到i2c物理控制器
核心结构与方法详述
i2c_adapter
我首先说i2c_adapter, 并不是编写一个i2c设备驱动需要它, 通常我们在配置内核的时候已经将i2c控制器的设备信息和驱动已经编译进内核了, 就是这个adapter对象已经创建好了, 但是了解其中的成员对于理解i2c驱动框架非常重要, 所有的设备驱动都要经过这个对象的处理才能和物理设备通信
//include/linux/i2c.h
425 struct i2c_adapter {
426 struct module *owner;
427 unsigned int class; /* classes to allow probing for */
428 const struct i2c_algorithm *algo; /* the algorithm to access the bus */
429 void *algo_data;
430
431 /* data fields that are valid for all devices */
432 struct rt_mutex bus_lock;
433
434 int timeout; /* in jiffies */
435 int retries;
436 struct device dev; /* the adapter device */
437
438 int nr;
439 char name[48];
440 struct completion dev_released;
441
442 struct mutex userspace_clients_lock;
443 struct list_head userspace_clients;
444
445 struct i2c_bus_recovery_info *bus_recovery_info;
446 };
struct i2c_adapter --428-->这个i2c控制器需要的控制算法, 其中最重要的成员是master_xfer()接口, 这个接口是硬件相关的, 里面的操作都是基于具体的SoCi2c寄存器的, 它将完成将数据发送到物理i2c控制器的"最后一公里" --436-->表示这个一个device, 会挂接到内核中的链表中来管理, 其中的 --443-->这个节点将一个i2c_adapter对象和它所属的i2c_client对象以及相应的i2c_driver对象连接到一起
下面是2个i2c-core.c提供的i2c_adapter直接相关的操作API, 通常也不需要设备驱动开发中使用,
i2c_add_adapter
这个API可以将一个i2c_adapter类型的对象注册到内核中, 源码我就不贴了, 下面是他们的调用关系,我们可以从中看到一个adapter对象和系统中的i2c_driver对象以及i2c_client对象的匹配流程。 首先,我们在驱动中构造一个i2c_adapter对象的时候,对其中的相关域进行初始化,这里我们最关心它的父设备
//drivers/i2c/buses/i2c-s3c2410.c
1072 static int s3c24xx_i2c_probe(struct platform_device *pdev)
1073 {
1140 i2c->adap.dev.parent = &pdev->dev;
1210 }
得到了这样一个i2c_adapter对象,我们就可以调用这个API将它注册到内核,调用关系如下:
i2c_add_adapter() 1 └──i2c_register_adapter(adapter) 2 ├──adap->dev.bus = &i2c_bus_type; 3 ├──adap->dev.type = &i2c_adapter_type; 4 │ └──i2c_adapter_attr_groups 5 │ └── i2c_adapter_attr_group 6 │ └── i2c_adapter_attrs 7 │ └── &dev_attr_new_device.attr 8 │ └──DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device); 9 │ └──i2c_sysfs_new_device() 10 │ └──list_add_tail(&client->detected, &adap->userspace_clients); 11 └──device_register(&adap-dev); 12 ├──device_initialize(dev); 13 │ ├──/* /sys/devices/ / 14 │ ├──struct kset devices_kset; 15 │ ├──dev->kobj.kset = devices_kset; 16 │ ├──kobject_init(&dev->kobj, &device_ktype); 17 │ └──set_dev_node(dev, -1); 18 └──device_add(dev); 19 ├──parent=get_device(dev->parent); 20 ├──kobj = get_device_parent(dev, parent); 21 │ └──return &parent->kobj; 22 ├──dev->kobj.parent = kobj; 23 ├──set_dev_node(dev, dev_to_node(parent)); 24 ├──kobject_add(&dev->kobj, dev->kobj.parent, NULL); 25 │ ├──kobject_add_varg(kobj, parent, fmt, args); 26 │ ├──kobj->parent = parent; 27 │ ├──kobject_add_internal(kobj); 28 │ ├──parent = kobject_get(kobj->parent); 29 │ ├──kobj_kset_join(kobj); 30 │ │ ├──kset_get(kobj->kset) 31 │ │ └──list_add_tail(&kobj->entry, &kobj->kset->list); 32 │ ├──kobj->parent = parent; 33 │ └──create_dir(kobj); 34 ├──device_create_file(dev, &dev_attr_uevent); 35 ├──device_create_sys_dev_entry(dev); 36 ├──devtmpfs_create_node(dev); 37 ├──device_add_class_symlinks(dev); 38 ├──device_add_attrs(dev); 39 ├──bus_add_device(dev); 40 ├──bus_probe_device(dev); 41 ├──klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children); 42 └──klist_add_tail(&dev->knode_class,&dev->class->p->klist_devices);
调用关系就是这样了,下面我简单解释一下这个树
i2c_add_adapter() --10-->将i2c_adapter对象中的userspace_clients与匹配到的client对象中detected连接到一起 --15-->将/sys/devices的kset赋值给i2c_adapter->device->kobject->parent,即建立i2c_adapter对象和/sys/devices的父子关系, 参见"Linux设备管理(一)_kobject, kset,ktype分析" --20-->获取device->parent的kobject对象 --22-->将device->parent的kobject对象作为device->kobject->parent, 形成device->kobject->parent == device->parent->kobject --31-->将这个device->kobject挂接到device->kset->list链表中, 由此可见, kobject->kset指向的kset对象和kobject->entry挂接到的kset对象可以不是一个, 与"Linux设备管理(一)_kobject, kset,ktype分析"那种情况不同.
i2c_del_adapter()
从内核中删除一个adapter
i2c_client
在i2c设备端,驱动开发的主要工作和平台总线一样:构建设备对象和驱动对象,我用的开发板上的i2c总线上挂接的设备是mpu6050,接下来我就以我的板子为例,讨论如何编写i2c设备端的驱动。 同样这里的设备对象也可以使用三种方式构建:平台文件,模块和设备树。 本文采用设备树的方式构建设备对象,我们可以参考内核文档"Documentations/devicetree/bindings/i2c/i2c-s3c2410.txt"以及设备树中的样板来编写我们的设备树节点,** 我们在设备树中可不会写mpu6050内部寄存器的地址,因为这些寄存器地址SoC看不到**。
/{ 109 i2c@138B0000 { 110 #address-cells = <1>; 111 #size-cells = <0>; 112 samsung,i2c-sda-delay = <100>; 113 samsung,i2c-max-bus-freq = <20000>; 114 pinctrl-0 =<&i2c5_bus>; 115 pinctrl-names="default"; 116 status="okay"; 117 mpu6050@68{ 118 compatible="invensense,mpu6050"; 119 reg=<0x68>; 120 }; 121 };
/ --109-->即我们SoC上的i2c控制器的地址 --116-->这个一定要okay,其实是对"./arch/arm/boot/dts/exynos4.dtsi +387"处的status = "disabled"的重写,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写 --117-->设备子节点,/表示板子,它的子节点node1表示SoC上的某个控制器,控制器中的子节点node2表示挂接在这个控制器上的设备(们)。68即是设备地址。 --118-->这个属性就是我们和驱动匹配的钥匙,一个字符都不能错 --119-->这个属性是从设备的地址,我们可以通过查阅手册"MPU-6050_DataSheet_V3_4"得到
写了这个设备节点,内核就会为我们在内核中构造一个i2c_client对象并挂接到i2c总线对象的设备链表中以待匹配,这个设备类如下
//include/linux/i2c.h
217 struct i2c_client {
218 unsigned short flags; /* div., see below */
219 unsigned short addr; /* chip address - NOTE: 7bit */
220 /* addresses are stored in the */
221 /* _LOWER_ 7 bits */
222 char name[I2C_NAME_SIZE];
223 struct i2c_adapter *adapter; /* the adapter we sit on */
224 struct device dev; /* the device structure */
225 int irq; /* irq issued by device */
226 struct list_head detected;
227 };
--219-->设备地址 --223-->表示这个client从属的i2c主机对应的adapter对象,驱动方法中使用这个指针发送数据 --224-->表示这是一个device --225-->中断使用的中断号 --226-->将所有i2c_client连在一起的节点
i2c_driver
和平台总线类似,i2c驱动对象使用i2c_driver结构来描述,所以,编写一个i2c驱动的本质工作就是构造一个i2c_driver对象并将其注册到内核。我们先来认识一下这个对象
//include/linux/i2c.h
161 struct i2c_driver {
162 unsigned int class;
167 int (*attach_adapter)(struct i2c_adapter *) __deprecated;
170 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
171 int (*remove)(struct i2c_client *);
174 void (*shutdown)(struct i2c_client *);
175 int (*suspend)(struct i2c_client *, pm_message_t mesg);
176 int (*resume)(struct i2c_client *);
183 void (*alert)(struct i2c_client *, unsigned int data);
188 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
190 struct device_driver driver;
191 const struct i2c_device_id *id_table;
194 int (*detect)(struct i2c_client *, struct i2c_board_info *);
195 const unsigned short *address_list;
196 struct list_head clients;
197 };
struct i2c_driver --170-->探测函数,匹配成功之后执行,会将匹配到的i2c_client对象传入,完成申请资源,初始化,提供接口等工作。 --171-->移除函数,设备消失时会调用,驱动模块被rmmod时也会先被调用,完成和probe相反的操作。 --174-->这三个方法都是电源管理相关的接口 --190-->表明这是一个设备的驱动类,和platform一样,用于匹配设备树的of_match_table域在这里 --191-->用于使用平台文件或模块编写设备信息时进行匹配使用,相当于platform_driver中的id_table。 --197-->用于将所有i2c_driver联系到一起的节点
那么接下来就是填充对象了,我们这里使用的是设备树匹配,所以of_match_table被填充如下。
struct of_device_id mpu6050_dt_match[] = {
{.compatible = "invensense,mpu6050"},
{},
};
struct i2c_device_id mpu6050_dev_match[] = {};
然后将这两个成员填充到i2c_driver对象如下,这个阶段我们可以在mpu6050_probe中只填写prink来测试我们的驱动方法对象是否有问题。
struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050drv",
.of_match_table = of_match_ptr(mpu6050_dt_match),
},
.id_table = mpu6050_dev_match,
};
使用下述API注册/注销驱动对象,这个宏和module_platform_driver一样是内核提供给我们一个用于快速实现注册注销接口的快捷方式,写了这句以及模块授权,我们就可以静待各种信息被打印了
module_i2c_driver(mpu6050_driver);
i2c_msg
如果测试通过,我们就要研究如何找到adapter以及如何通过找到的adapter将数据发送出去。没错,我说的i2c_msg
68 struct i2c_msg {
69 __u16 addr; /* slave address */
70 __u16 flags;
71 #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
72 #define I2C_M_RD 0x0001 /* read data, from slave to master */
73 #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
74 #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
75 #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
76 #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
77 #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
78 #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
79 __u16 len; /* msg length */
80 __u8 *buf; /* pointer to msg data */
81 };
struct i2c_msg --69-->从机地址 --70-->操作标志,I2C_M_RD为读(0),写为1 --79-->有效数据长度 --80-->装载有效数据的头指针
我们知道,i2c总线上传入数据是以字节为单位的,而我们的通信类别分为两种:读and写,对于写,通常按照下面的时序
Mater |
S |
I2CAddr+WriteBit |
|
InternalRegisterAddr |
|
DATA |
|
DATA |
|
P |
Slave |
|
|
ACK |
|
ACK |
|
ACK |
|
ACK |
|
对于读,通常是按照下面的时序
Mater |
S |
I2CAddr+WriteBit |
|
InternalRegisterAddr |
|
S |
I2CAddr+ReadBit |
|
|
ACK |
|
NACK |
P |
Slave |
|
|
ACK |
|
ACK |
|
|
ACK |
DATA |
|
DATA |
|
|
i2c子系统为了实现这种通信方法,为我们封装了i2c_msg结构,对于每一个START信号,都对应一个i2c_msg对象,实际操作中我们会将所有的请求封装成一个struct i2c_msg[],一次性将所有的请求通过i2c_transfer()发送给匹配到的client的从属的adapter,由adapter根据相应的algo域以及master_xfer域通过主机驱动来将这些请求发送给硬件上的设备
i2c驱动实例部分:
所用硬件是mips核
1.在make menuconfig中选择i2c的总线驱动和板级文件的匹配
2.在以下目录中我们可以看见相应文件已经被编译进了内核中
3.接下来我们看板级文件,i2c总线和设备驱动的源码
1) 在代码中
static u64 jz_i2c_dmamask = ~(u32)0;
#endif
#define DEF_I2C(NO, SPEED) \
static struct resource jz_i2c##NO##_resources[] = { \
[0] = { \
.start = I2C##NO##_IOBASE, \
.end = I2C##NO##_IOBASE + 0x1000 - 1, \
.flags = IORESOURCE_MEM, \
}, \
[1] = { \
.start = IRQ_I2C##NO, \
.end = IRQ_I2C##NO, \
.flags = IORESOURCE_IRQ, \
}, \
[2] = { \
.start = JZDMA_REQ_I2C##NO, \
.flags = IORESOURCE_DMA, \
}, \
[3] = { \
.start = SPEED, \
.flags = IORESOURCE_BUS, \
}, \
}; \
struct platform_device jz_i2c##NO##_device = { \
.name = "jz-i2c", \
.id = NO, \
.dev = { \
.dma_mask = &jz_i2c_dmamask, \
.coherent_dma_mask = 0xffffffff, \
}, \
.num_resources = ARRAY_SIZE(jz_i2c##NO##_resources), \
.resource = jz_i2c##NO##_resources, \
};
#ifdef CONFIG_I2C0_V12_JZ
i2c 的板级文件的名字是"jz-i2c"
所以在i2c总线驱动中我们也要用这个名字去匹配
2).总线驱动代码
/* drivers/i2c/busses/i2c-v12-jz.c
*
* Copyright (C) 2014 Ingenic Semiconductor Co., Ltd.
* http://www.ingenic.com
* Sun Jiwei
*
* I2C adapter driver for the Ingenic I2C controller
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define I2C_CTRL (0x00) #define I2C_TAR (0x04) #define I2C_SAR (0x08) #define I2C_DC (0x10) #define I2C_SHCNT (0x14) #define I2C_SLCNT (0x18) #define I2C_FHCNT (0x1C) #define I2C_FLCNT (0x20) #define I2C_INTST (0x2C) #define I2C_INTM (0x30) #define I2C_RXTL (0x38) #define I2C_TXTL (0x3c) #define I2C_CINTR (0x40) #define I2C_CRXUF (0x44) #define I2C_CRXOF (0x48) #define I2C_CTXOF (0x4C) #define I2C_CRXREQ (0x50) #define I2C_CTXABRT (0x54) #define I2C_CRXDONE (0x58) #define I2C_CACT (0x5C) #define I2C_CSTP (0x60) #define I2C_CSTT (0x64) #define I2C_CGC (0x68) #define I2C_ENB (0x6C) #define I2C_STA (0x70) #define I2C_TXFLR (0x74) #define I2C_RXFLR (0x78) #define I2C_SDAHD (0x7C) #define I2C_TXABRT (0x80) #define I2C_DMACR (0x88) #define I2C_DMATDLR (0x8c) #define I2C_DMARDLR (0x90) #define I2C_SDASU (0x94) #define I2C_ACKGC (0x98) #define I2C_ENSTA (0x9C) #define I2C_FLT (0xA0) /* I2C Control Register (I2C_CTRL) */ #define I2C_CTRL_SLVDIS (1 << 6) /* after reset slave is disabled */ #define I2C_CTRL_REST (1 << 5) #define I2C_CTRL_MATP (1 << 4) /* 1: 10bit address 0: 7bit addressing */ #define I2C_CTRL_SATP (1 << 3) /* 1: 10bit address 0: 7bit address */ #define I2C_CTRL_SPDF (2 << 1) /* fast mode 400kbps */ #define I2C_CTRL_SPDS (1 << 1) /* standard mode 100kbps */ #define I2C_CTRL_MD (1 << 0) /* master enabled */ /* I2C Status Register (I2C_STA) */ #define I2C_STA_SLVACT (1 << 6) /* Slave FSM is not in IDLE state */ #define I2C_STA_MSTACT (1 << 5) /* Master FSM is not in IDLE state */ #define I2C_STA_RFF (1 << 4) /* RFIFO if full */ #define I2C_STA_RFNE (1 << 3) /* RFIFO is not empty */ #define I2C_STA_TFE (1 << 2) /* TFIFO is empty */ #define I2C_STA_TFNF (1 << 1) /* TFIFO is not full */ #define I2C_STA_ACT (1 << 0) /* I2C Activity Status */ /* i2c interrupt status (I2C_INTST) */ #define I2C_INTST_IGC (1 << 11) #define I2C_INTST_ISTT (1 << 10) #define I2C_INTST_ISTP (1 << 9) #define I2C_INTST_IACT (1 << 8) #define I2C_INTST_RXDN (1 << 7) #define I2C_INTST_TXABT (1 << 6) #define I2C_INTST_RDREQ (1 << 5) #define I2C_INTST_TXEMP (1 << 4) #define I2C_INTST_TXOF (1 << 3) #define I2C_INTST_RXFL (1 << 2) #define I2C_INTST_RXOF (1 << 1) #define I2C_INTST_RXUF (1 << 0) /* i2c interrupt mask status (I2C_INTM) */ #define I2C_INTM_MIGC (1 << 11) #define I2C_INTM_MISTT (1 << 10) #define I2C_INTM_MISTP (1 << 9) #define I2C_INTM_MIACT (1 << 8) #define I2C_INTM_MRXDN (1 << 7) #define I2C_INTM_MTXABT (1 << 6) #define I2C_INTM_MRDREQ (1 << 5) #define I2C_INTM_MTXEMP (1 << 4) #define I2C_INTM_MTXOF (1 << 3) #define I2C_INTM_MRXFL (1 << 2) #define I2C_INTM_MRXOF (1 << 1) #define I2C_INTM_MRXUF (1 << 0) #define I2C_DC_REST (1 << 10) #define I2C_DC_STP (1 << 9) #define I2C_DC_READ (1 << 8) #define I2C_ENB_I2CENB (1 << 0) /* Enable the i2c */ #define I2C_FIFO_LEN (CONFIG_I2C_FIFO_LEN) #define TX_LEVEL (I2C_FIFO_LEN / 2) #define RX_LEVEL (I2C_FIFO_LEN / 2 - 1) #define TIMEOUT 0xff #define DEBUG_INFO 2 #define DEBUG_WARN 1 /* * msg_end_type: The bus control which need to be send at end of transfer. * @MSG_END_STOP: Send stop pulse at end of transfer. * @MSG_END_REPEAT_START: Send repeat start at end of transfer. */ enum msg_end_type { MSG_END_STOP, MSG_END_CONTINUE, MSG_END_REPEAT_START, }; /* I2C Transmit Abort Status Register (I2C_TXABRT) */ static const char *abrt_src[] = { "I2C_TXABRT_ABRT_7B_ADDR_NOACK", "I2C_TXABRT_ABRT_10ADDR1_NOACK", "I2C_TXABRT_ABRT_10ADDR2_NOACK", "I2C_TXABRT_ABRT_XDATA_NOACK", "I2C_TXABRT_ABRT_GCALL_NOACK", "I2C_TXABRT_ABRT_GCALL_READ", "I2C_TXABRT_ABRT_HS_ACKD", "I2C_TXABRT_SBYTE_ACKDET", "I2C_TXABRT_ABRT_HS_NORSTRT", "I2C_TXABRT_SBYTE_NORSTRT", "I2C_TXABRT_ABRT_10B_RD_NORSTRT", "I2C_TXABRT_ABRT_MASTER_DIS", "I2C_TXABRT_ARB_LOST", "I2C_TXABRT_SLVFLUSH_TXFIFO", "I2C_TXABRT_SLV_ARBLOST", "I2C_TXABRT_SLVRD_INTX", }; /* I2C standard mode high count register(I2CSHCNT) */ #define I2CSHCNT_ADJUST(n) (((n) - 8) < 6 ? 6 : ((n) - 8)) /* I2C standard mode low count register(I2CSLCNT) */ #define I2CSLCNT_ADJUST(n) (((n) - 1) < 8 ? 8 : ((n) - 1)) /* I2C fast mode high count register(I2CFHCNT) */ #define I2CFHCNT_ADJUST(n) (((n) - 8) < 6 ? 6 : ((n) - 8)) /* I2C fast mode low count register(I2CFLCNT) */ #define I2CFLCNT_ADJUST(n) (((n) - 1) < 8 ? 8 : ((n) - 1)) struct i2c_jz { void __iomem *iomem; int irq; struct clk *clk; struct i2c_adapter adap; enum msg_end_type w_end_type; enum msg_end_type r_end_type; unsigned char *rbuf; unsigned char *wbuf; unsigned int rd_len; int len; struct completion complete; int debug; unsigned int rate; }; static inline unsigned short i2c_readl(struct i2c_jz *i2c, unsigned short offset); #ifdef I2C_DEBUG #define PRINT_REG_WITH_ID(reg_name, id) \ dev_info(&(i2c->adap.dev),"--"#reg_name " 0x%08x\n",i2c_readl(id,reg_name)) static void i2c_jz_dump_regs(struct i2c_jz *i2c) { struct i2c_jz *i2c_id = i2c; PRINT_REG_WITH_ID(I2C_CTRL, i2c_id); PRINT_REG_WITH_ID(I2C_INTM, i2c_id); PRINT_REG_WITH_ID(I2C_RXTL, i2c_id); PRINT_REG_WITH_ID(I2C_TXTL, i2c_id); PRINT_REG_WITH_ID(0x78, i2c_id); return; PRINT_REG_WITH_ID(I2C_CTRL, i2c_id); PRINT_REG_WITH_ID(I2C_TAR, i2c_id); PRINT_REG_WITH_ID(I2C_SAR, i2c_id); // PRINT_REG_WITH_ID(I2C_DC, i2c_id); PRINT_REG_WITH_ID(I2C_SHCNT, i2c_id); PRINT_REG_WITH_ID(I2C_SLCNT, i2c_id); PRINT_REG_WITH_ID(I2C_FHCNT, i2c_id); PRINT_REG_WITH_ID(I2C_FLCNT, i2c_id); PRINT_REG_WITH_ID(I2C_INTST, i2c_id); PRINT_REG_WITH_ID(I2C_INTM, i2c_id); PRINT_REG_WITH_ID(I2C_RXTL, i2c_id); PRINT_REG_WITH_ID(I2C_TXTL, i2c_id); PRINT_REG_WITH_ID(I2C_CINTR, i2c_id); PRINT_REG_WITH_ID(I2C_CRXUF, i2c_id); PRINT_REG_WITH_ID(I2C_CRXOF, i2c_id); PRINT_REG_WITH_ID(I2C_CTXOF, i2c_id); PRINT_REG_WITH_ID(I2C_CRXREQ, i2c_id); PRINT_REG_WITH_ID(I2C_CTXABRT, i2c_id); PRINT_REG_WITH_ID(I2C_CRXDONE, i2c_id); PRINT_REG_WITH_ID(I2C_CACT, i2c_id); PRINT_REG_WITH_ID(I2C_CSTP, i2c_id); PRINT_REG_WITH_ID(I2C_CSTT, i2c_id); PRINT_REG_WITH_ID(I2C_CGC, i2c_id); PRINT_REG_WITH_ID(I2C_ENB, i2c_id); PRINT_REG_WITH_ID(I2C_STA, i2c_id); /*debug trans & recive fifo count */ PRINT_REG_WITH_ID(0x74, i2c_id); PRINT_REG_WITH_ID(0x78, i2c_id); PRINT_REG_WITH_ID(I2C_TXABRT, i2c_id); PRINT_REG_WITH_ID(I2C_DMACR, i2c_id); PRINT_REG_WITH_ID(I2C_DMATDLR, i2c_id); PRINT_REG_WITH_ID(I2C_DMARDLR, i2c_id); PRINT_REG_WITH_ID(I2C_SDASU, i2c_id); PRINT_REG_WITH_ID(I2C_ACKGC, i2c_id); PRINT_REG_WITH_ID(I2C_ENSTA, i2c_id); PRINT_REG_WITH_ID(I2C_SDAHD, i2c_id); } #endif static inline unsigned short i2c_readl(struct i2c_jz *i2c, unsigned short offset) { return readl(i2c->iomem + offset); } static inline void i2c_writel(struct i2c_jz *i2c, unsigned short offset, unsigned short value) { writel(value, i2c->iomem + offset); } static int i2c_jz_disable(struct i2c_jz *i2c) { int timeout = TIMEOUT; i2c_writel(i2c, I2C_ENB, 0); while ((i2c_readl(i2c, I2C_ENSTA) & I2C_ENB_I2CENB) && (--timeout > 0)) msleep(1); if (timeout) return 0; printk("enable i2c%d failed\n", i2c->adap.nr); return -ETIMEDOUT; } static int i2c_jz_enable(struct i2c_jz *i2c) { int timeout = TIMEOUT; i2c_writel(i2c, I2C_ENB, 1); while (!(i2c_readl(i2c, I2C_ENSTA) & I2C_ENB_I2CENB) && (--timeout > 0)) msleep(1); if (timeout) return 0; printk("enable i2c%d failed\n", i2c->adap.nr); return -ETIMEDOUT; } static void i2c_jz_reset(struct i2c_jz *i2c) { i2c_readl(i2c, I2C_CTXABRT); i2c_readl(i2c, I2C_INTST); i2c_jz_disable(i2c); udelay(10); i2c_jz_enable(i2c); } /* function: send read command * return: 0, successful * 1, txfifo valid entry is more than receive fifo, before send read command, * must be read. * 2, txfifo count is 0 or rxfifo count is 0. * */ static inline unsigned int i2c_send_rcmd(struct i2c_jz *i2c) { unsigned int tx_count, rx_count, count, tx_valid, rx_valid; tx_valid = i2c_readl(i2c, I2C_TXFLR); rx_valid = i2c_readl(i2c, I2C_RXFLR); tx_count = I2C_FIFO_LEN - tx_valid; rx_count = I2C_FIFO_LEN - rx_valid; #ifdef CONFIG_I2C_DEBUG_INFO if(i2c->debug > DEBUG_INFO) dev_info(&(i2c->adap.dev), "%s, tx_valid = %d, rx_valid = %d," " tx_count = %d, rx_count = %d\n", __func__, tx_valid, rx_valid, tx_count, rx_count); #endif if (tx_valid > rx_count) { dev_warn(&(i2c->adap.dev), "\n\n###Warrning: I2C transfer fifo valid entry is more receive fifo, " "before send read cmd, please read data from " "the read fifo.\n\n"); return 1; } if (!tx_count || !rx_count) { dev_warn(&(i2c->adap.dev), "\n\n###Warrning: I2C receive fifo or transfer fifo is full, " "before send read cmd, please read data from " "the read fifo or wait some time.\n\n"); return 2; } count = min3(i2c->rd_len, tx_count, rx_count); #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_INFO) dev_info(&(i2c->adap.dev), "%s, Before send read cmd, " "need_send = %d, left_send = %d\n", __func__, count ,i2c->rd_len); #endif i2c->rd_len -= count; if (!i2c->rd_len) { while (count > 1) { i2c_writel(i2c, I2C_DC, I2C_DC_READ); count--; } if (i2c->r_end_type == MSG_END_STOP) { i2c_writel(i2c, I2C_DC, I2C_DC_READ | I2C_DC_STP); } else { i2c_writel(i2c, I2C_DC, I2C_DC_READ); } } else { while (count > 0) { i2c_writel(i2c, I2C_DC, I2C_DC_READ); count--; } } #ifdef CONFIG_I2C_DEBUG_INFO if(i2c->debug > DEBUG_INFO) dev_info(&(i2c->adap.dev), "%s, After send read cmd, " "left_send = %d\n", __func__, i2c->rd_len); #endif return 0; } #ifdef CONFIG_I2C_DEBUG_INFO static ssize_t enable_debug(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int rc; unsigned long enable; struct i2c_jz *i2c = (struct i2c_jz *)dev_get_drvdata(dev); rc = strict_strtoul(buf, 0, &enable); if (rc) return rc; if ((enable >= 0) && (enable <= DEBUG_INFO + 1)) i2c->debug = enable; else goto err; return size; err: pr_err("Please input correct number(enable >= 0 && enable <= 5)" " to disable or enable debug info print\n"); return -EAGAIN; } static struct device_attribute attributes[] = { __ATTR(debug_info, 0666, NULL, enable_debug), }; static int create_debug_sysfs_interface(struct device *dev) { int i; for (i = 0; i < ARRAY_SIZE(attributes); i++) if (device_create_file(dev, attributes + i)) goto err; return 0; err: for( ; i >= 0; i--) device_remove_file(dev, attributes + i); return -1; } #endif static irqreturn_t i2c_jz_irq(int irqno, void *dev_id) { unsigned short tmp, intst, intmsk; struct i2c_jz *i2c = dev_id; intst = i2c_readl(i2c, I2C_INTST); intmsk = i2c_readl(i2c, I2C_INTM); #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_INFO) dev_info(&(i2c->adap.dev), "--I2C irq register INTST:0x%08x\n", intst); #endif if ((intst & I2C_INTST_TXABT) && (intmsk & I2C_INTM_MTXABT)) { dev_err(&(i2c->adap.dev), "%s %d, I2C transfer error, ABORT interrupt\n", __func__, __LINE__); goto END_TRSF_IRQ_HND; } if ((intst & I2C_INTST_ISTP) && (intmsk & I2C_INTM_MISTP)) { i2c_readl(i2c, I2C_CSTP); /* clear STP bit */ #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_INFO) { dev_info(&(i2c->adap.dev), "%s, Now stop condition has occurred," "and left data length is %d\n", __func__, i2c->len); } #endif if (i2c->len == 0) goto END_TRSF_IRQ_HND; } if ((intmsk & I2C_INTM_MTXEMP) && (intst & I2C_INTST_TXEMP)) { if (!i2c->len) { if (i2c->w_end_type == MSG_END_REPEAT_START) { goto END_TRSF_IRQ_HND; } else { tmp = i2c_readl(i2c, I2C_INTM); tmp &= ~I2C_INTM_MTXEMP; i2c_writel(i2c, I2C_INTM, tmp); } } else { while ((i2c->len > 0) && (i2c_readl(i2c, I2C_STA) & I2C_STA_TFNF)) { tmp = *i2c->wbuf++; if (i2c->len == 1) { if (i2c->w_end_type == MSG_END_STOP) tmp |= I2C_DC_STP; } i2c_writel(i2c, I2C_DC, tmp); i2c->len -= 1; } if (i2c->len == 0) { i2c_writel(i2c, I2C_TXTL, 0); } } } if ((intst & I2C_INTST_RXFL) && (intmsk & I2C_INTM_MRXFL)) { #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug >= DEBUG_INFO) { dev_info(&(i2c->adap.dev), "%s, Before read I2C_DC, " "left_send_cmd = %d, left_read_cnt = %d," " rx_valid = %d, tx_valid = %d\n", __func__, i2c->rd_len, i2c->len, i2c_readl(i2c, I2C_RXFLR), i2c_readl(i2c, I2C_TXFLR)); } #endif while ((i2c_readl(i2c, I2C_STA) & I2C_STA_RFNE) && (i2c->len > 0)) { tmp = i2c_readl(i2c, I2C_DC) & 0xff; *i2c->rbuf++ = tmp; i2c->len--; } #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug >= DEBUG_INFO) dev_info(&(i2c->adap.dev), "%s, After read I2C_DC, " "left_read_cnt = %d," " rx_valid = %d, tx_valid = %d\n", __func__, i2c->len, i2c_readl(i2c, I2C_RXFLR), i2c_readl(i2c, I2C_TXFLR)); #endif if (i2c->len == 0) { goto END_RECE_IRQ_HND; } if (i2c->len <= I2C_FIFO_LEN) { i2c_writel(i2c, I2C_RXTL, i2c->len - 1); } if (i2c_send_rcmd(i2c)) { dev_err(&(i2c->adap.dev), "%s %d, I2C controller has BUG," " RXFLR or TXFLR can not clear\n", __func__, __LINE__); BUG(); } #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_INFO) dev_info(&(i2c->adap.dev), "%s, After send read command, " "left_send_cmd = %d, " "left_read_cnt = %d\n", __func__, i2c->rd_len, i2c->len); #endif } if ((intst & I2C_INTST_RXOF) && (intmsk & I2C_INTM_MRXOF)) { dev_err(&(i2c->adap.dev), "%s %d, I2C transfer error, RXFIFO over full\n", __func__, __LINE__); i2c_readl(i2c, I2C_CRXOF); /* clear RXOF bit */ } if ((intst & I2C_INTST_TXOF) && (intmsk & I2C_INTM_MTXOF)) { dev_err(&(i2c->adap.dev), "%s %d, I2C transfer error, TXFIFO over full\n", __func__, __LINE__); i2c_readl(i2c, I2C_CTXOF); /* clear TXOF bit */ goto END_TRSF_IRQ_HND; } return IRQ_HANDLED; END_RECE_IRQ_HND: END_TRSF_IRQ_HND: i2c_writel(i2c, I2C_INTM, 0); complete(&i2c->complete); return IRQ_HANDLED; } static void txabrt(struct i2c_jz *i2c, int src) { int i; dev_err(&(i2c->adap.dev), "--I2C txabrt:\n"); for (i = 0; i < 16; i++) { if (src & (0x1 << i)) dev_info(&(i2c->adap.dev), "--I2C TXABRT[%d]=%s\n", i, abrt_src[i]); } } static inline int xfer_read(struct i2c_jz *i2c, unsigned char *buf, int len, enum msg_end_type end_type) { int ret = 0; long timeout; unsigned short tmp; unsigned int wait_complete_timeout_ms; wait_complete_timeout_ms = len * 1000 * 9 * 2 / i2c->rate + CONFIG_I2C_JZV10_WAIT_MS; #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_WARN) dev_info(&(i2c->adap.dev), "%s, Begin read msg, want to read length is %d\n", __func__, len); #endif memset(buf, 0, len); i2c->rd_len = len; i2c->len = len; i2c->rbuf = buf; i2c->r_end_type = end_type; i2c_readl(i2c, I2C_CSTP); /* clear STP bit */ i2c_readl(i2c, I2C_CTXOF); /* clear TXOF bit */ i2c_readl(i2c, I2C_CTXABRT); /* clear TXABRT bit */ if (len <= I2C_FIFO_LEN) { i2c_writel(i2c, I2C_RXTL, len - 1); } else { i2c_writel(i2c, I2C_RXTL, RX_LEVEL); } while (i2c_readl(i2c, I2C_STA) & I2C_STA_RFNE) { i2c_readl(i2c, I2C_DC); } if (i2c_send_rcmd(i2c)) BUG(); tmp = I2C_INTM_MRXFL | I2C_INTM_MTXABT; if (end_type == MSG_END_STOP) tmp |= I2C_INTM_MISTP; i2c_writel(i2c, I2C_INTM, tmp); timeout = wait_for_completion_timeout(&i2c->complete, msecs_to_jiffies (wait_complete_timeout_ms)); if (!timeout) { dev_err(&(i2c->adap.dev), "--I2C irq read timeout\n"); #ifdef I2C_DEBUG i2c_jz_dump_regs(i2c); #endif ret = -ETIMEDOUT; } tmp = i2c_readl(i2c, I2C_TXABRT); if (tmp) { txabrt(i2c, tmp); if (tmp > 0x1 && tmp < 0x10) ret = -ENXIO; else ret = -EIO; // ABRT_GCALL_READ if (tmp & (1 << 5)) { ret = -EAGAIN; } i2c_readl(i2c, I2C_CTXABRT); } if (ret < 0) i2c_jz_reset(i2c); #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_WARN) dev_info(&(i2c->adap.dev), "%s, Reading msg over\n", __func__); #endif return ret; } static inline int xfer_write(struct i2c_jz *i2c, unsigned char *buf, int len, enum msg_end_type end_type) { int ret = 0; long timeout = TIMEOUT; unsigned short reg_tmp; unsigned int wait_complete_timeout_ms; wait_complete_timeout_ms = len * 1000 * 9 * 2 / i2c->rate + CONFIG_I2C_JZV10_WAIT_MS; #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_WARN) dev_info(&(i2c->adap.dev), "%s, Begin write msg, want to write length is %d\n", __func__, len); #endif i2c->wbuf = buf; i2c->len = len; i2c_writel(i2c, I2C_TXTL, TX_LEVEL); i2c_readl(i2c, I2C_CSTP); /* clear STP bit */ i2c_readl(i2c, I2C_CTXOF); /* clear TXOF bit */ i2c_readl(i2c, I2C_CTXABRT); /* clear TXABRT bit */ i2c->w_end_type = end_type; while ((i2c_readl(i2c, I2C_STA) & I2C_STA_TFNF) && (i2c->len > 0)) { reg_tmp = *i2c->wbuf++; if (i2c->len == 1) { if (end_type == MSG_END_STOP) { reg_tmp |= I2C_DC_STP; } } i2c_writel(i2c, I2C_DC, reg_tmp); i2c->len -= 1; } if (i2c->len == 0) { i2c_writel(i2c, I2C_TXTL, 0); } reg_tmp = I2C_INTM_MTXEMP | I2C_INTM_MTXABT | I2C_INTM_MTXOF; if (end_type == MSG_END_STOP) reg_tmp |= I2C_INTM_MISTP; i2c_writel(i2c, I2C_INTM, reg_tmp); timeout = wait_for_completion_timeout(&i2c->complete, msecs_to_jiffies (wait_complete_timeout_ms)); if (!timeout) { dev_err(&(i2c->adap.dev), "--I2C pio write wait timeout\n"); #ifdef I2C_DEBUG i2c_jz_dump_regs(i2c); #endif ret = -ETIMEDOUT; } reg_tmp = i2c_readl(i2c, I2C_TXABRT); if (reg_tmp) { txabrt(i2c, reg_tmp); if (reg_tmp > 0x1 && reg_tmp < 0x10) ret = -ENXIO; else ret = -EIO; //after I2C_TXABRT_ABRT_XDATA_NOACK error,this required core to resend if (reg_tmp & 8) { ret = -EAGAIN; } i2c_readl(i2c, I2C_CTXABRT); } if (ret < 0) i2c_jz_reset(i2c); #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_WARN) dev_info(&(i2c->adap.dev), "%s, Write msg over\n", __func__); #endif return ret; } static int i2c_disable_clk(struct i2c_jz *i2c) { int timeout = 10; int tmp = i2c_readl(i2c, I2C_STA); while ((tmp & I2C_STA_MSTACT) && (--timeout > 0)) { udelay(90); tmp = i2c_readl(i2c, I2C_STA); } if (timeout > 0) { clk_disable(i2c->clk); return 0; } else { dev_err(&(i2c->adap.dev), "--I2C disable clk timeout, I2C_STA = 0x%x\n", tmp); i2c_jz_reset(i2c); clk_disable(i2c->clk); return -ETIMEDOUT; } } static int i2c_jz_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, int count) { int i, ret = 0; struct i2c_jz *i2c = adap->algo_data; #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_WARN) { printk("\n\n\n"); dev_info(&(i2c->adap.dev), "%s, Begin master xfer, want to transfer msg count is %d\n", __func__, count); } #endif clk_enable(i2c->clk); i2c_writel(i2c, I2C_TAR, msg->addr); for (i = 0; i < count; i++, msg++) { enum msg_end_type end_type = MSG_END_STOP; if (i < (count - 1)) { if (msg[i + 1].flags & I2C_M_NOSTART) { end_type = MSG_END_CONTINUE; /* have no STOP and START */ } else { end_type = MSG_END_REPEAT_START; /* have no STOP but have RESTART */ } } INIT_COMPLETION(i2c->complete); #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_WARN) dev_info(&(i2c->adap.dev), "%s, Now transfer msg: %d\n", __func__, i); #endif if (msg->flags & I2C_M_RD) { ret = xfer_read(i2c, msg->buf, msg->len, end_type); } else { ret = xfer_write(i2c, msg->buf, msg->len, end_type); } if (ret < 0) { clk_disable(i2c->clk); goto ERR; } } if (i2c_disable_clk(i2c)) { ret = -ETIMEDOUT; goto ERR; } #ifdef CONFIG_I2C_DEBUG_INFO if (i2c->debug > DEBUG_WARN) dev_info(&(i2c->adap.dev), "%s, Transfer msg over\n\n\n", __func__); #endif ERR: return ret ? : i; } static u32 i2c_jz_functionality(struct i2c_adapter *adap) { unsigned int ret; ret = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR; return ret; } static const struct i2c_algorithm i2c_jz_algorithm = { .master_xfer = i2c_jz_xfer, .functionality = i2c_jz_functionality, }; static int i2c_set_speed(struct i2c_jz *i2c, int rate) { /*ns */ long dev_clk = clk_get_rate(i2c->clk); long cnt_high = 0; /* HIGH period count of the SCL clock */ long cnt_low = 0; /* LOW period count of the SCL clock */ long setup_time = 0; long hold_time = 0; unsigned short tmp; i2c->rate = rate; if (i2c_jz_disable(i2c)) dev_info(&(i2c->adap.dev), "i2c not disable\n"); if (rate <= 100000) { tmp = 0x43 | (1 << 5); /* standard speed mode */ i2c_writel(i2c, I2C_CTRL, tmp); } else { tmp = 0x45 | (1 << 5); /* fast speed mode */ i2c_writel(i2c, I2C_CTRL, tmp); } /* high * ____ ____ ____ ____ * clk __| | |___| |____| |____| |___ * | | | * | | | * |_|_| _________ ____ * data __/ | |\___/ \____/ \____ * setup->| |<| * ->| |<-hold */ //setup_time = (10 000 000/(rate*4)) + 1; setup_time = (dev_clk / (rate * 4)); if (setup_time > 1) setup_time -= 1; //hold_time = (10000000/(rate*4)) - 1; hold_time = (dev_clk / (rate * 4)); /* high * ____ ____ * clk __| |___| |____ * low * |<--period--->| * */ cnt_high = dev_clk / (rate * 2); cnt_low = dev_clk / (rate * 2); dev_info(&(i2c->adap.dev), "set:%ld hold:%ld dev=%ld h=%ld l=%ld\n", setup_time, hold_time, dev_clk, cnt_high, cnt_low); if (setup_time > 255) setup_time = 255; if (setup_time <= 0) setup_time = 1; if (hold_time > 0xFFFF) hold_time = 0xFFFF; if (rate <= 100000) { i2c_writel(i2c, I2C_SHCNT, I2CSHCNT_ADJUST(cnt_high)); i2c_writel(i2c, I2C_SLCNT, I2CSLCNT_ADJUST(cnt_low)); } else { i2c_writel(i2c, I2C_FHCNT, I2CFHCNT_ADJUST(cnt_high)); i2c_writel(i2c, I2C_FLCNT, I2CFLCNT_ADJUST(cnt_low)); } i2c_writel(i2c, I2C_SDASU, setup_time & 0xff); i2c_writel(i2c, I2C_SDAHD, hold_time); return 0; } static int i2c_jz_probe(struct platform_device *dev) { int ret = 0; struct i2c_jz *i2c; struct resource *res; unsigned int reg_tmp; i2c = kzalloc(sizeof(struct i2c_jz), GFP_KERNEL); if (!i2c) { printk("Error: Now we can not malloc memory for I2C!\n"); ret = -ENOMEM; goto ERR0; } i2c->adap.owner = THIS_MODULE; i2c->adap.algo = &i2c_jz_algorithm; i2c->adap.retries = 5; i2c->adap.timeout = 5; i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &dev->dev; i2c->adap.nr = dev->id; sprintf(i2c->adap.name, "i2c%u", dev->id); i2c->clk = clk_get(&dev->dev, i2c->adap.name); if (!i2c->clk) { printk("Error: Now we can not get i2c%d clock!\n", dev->id); ret = -ENODEV; goto clk_failed; } res = platform_get_resource(dev, IORESOURCE_MEM, 0); i2c->iomem = ioremap(res->start, resource_size(res)); if (!i2c->iomem) { printk("Error: Now we can remap IO for I2C%d!\n", dev->id); ret = -ENOMEM; goto io_failed; } i2c->irq = platform_get_irq(dev, 0); ret = request_irq(i2c->irq, i2c_jz_irq, IRQF_DISABLED, dev_name(&dev->dev), i2c); if (ret) { printk("Error: Now we can request irq for I2C%d!\n", dev->id); ret = -ENODEV; goto irq_failed; } clk_enable(i2c->clk); res = platform_get_resource(dev, IORESOURCE_BUS, 0); i2c_set_speed(i2c, res->start * 1000); #if 0 reg_tmp = i2c_readl(i2c, I2C_DC); reg_tmp &= ~I2C_DC_STP; i2c_writel(i2c, I2C_DC, reg_tmp); #endif reg_tmp = i2c_readl(i2c, I2C_CTRL); reg_tmp |= I2C_CTRL_REST; i2c_writel(i2c, I2C_CTRL, reg_tmp); // for jgao WHY? // i2c_writel(i2c, I2C_FLT, 0xF); /*set filter*/ i2c_writel(i2c, I2C_INTM, 0x0); init_completion(&i2c->complete); ret = i2c_add_numbered_adapter(&i2c->adap); if (ret < 0) { dev_err(&(i2c->adap.dev), KERN_INFO "I2C: Failed to add bus\n"); goto adapt_failed; } platform_set_drvdata(dev, i2c); i2c_jz_enable(i2c); clk_disable(i2c->clk); #ifdef CONFIG_I2C_DEBUG_INFO ret = create_debug_sysfs_interface(&dev->dev); if (ret < 0) dev_err(&i2c->adap.dev, "create debug sysfs interface failed\n"); #endif return 0; adapt_failed: free_irq(i2c->irq, i2c); irq_failed: iounmap(i2c->iomem); io_failed: clk_put(i2c->clk); clk_failed: kfree(i2c); ERR0: return ret; } static int i2c_jz_remove(struct platform_device *dev) { struct i2c_jz *i2c = platform_get_drvdata(dev); free_irq(i2c->irq, i2c); iounmap(i2c->iomem); clk_put(i2c->clk); i2c_del_adapter(&i2c->adap); kfree(i2c); return 0; } static struct platform_driver i2c_jz_driver = { .probe = i2c_jz_probe, .remove = i2c_jz_remove, .driver = { .name = "jz-i2c", }, }; static int __init i2c_jz_init(void) { return platform_driver_register(&i2c_jz_driver); } subsys_initcall(i2c_jz_init); static void __exit i2c_jz_exit(void) { platform_driver_unregister(&i2c_jz_driver); } module_exit(i2c_jz_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("i2c driver for JZ SoCs");
static struct platform_driver i2c_jz_driver = {
.probe = i2c_jz_probe,
.remove = i2c_jz_remove,
.driver = {
.name = "jz-i2c",
},
};
在匹配之后会自动调用prob函数,然后可以从platfrom中得到硬件资源,i2c地址中断等
3).设备驱动代码
/*
* i2c-smbus.c - SMBus extensions to the I2C protocol
*
* Copyright (C) 2008 David Brownell
* Copyright (C) 2010 Jean Delvare
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define I2C_WRITE_LEN 1 #define I2C_READ_4S 4 #define I2C_READ_2S 2 struct battery_info { unsigned int voltage; unsigned int curren; unsigned int rsoc; unsigned int rc; unsigned int fcc; unsigned int descap; unsigned int desvol; short temp; unsigned short vol_cell[4]; }; struct i2c_client *client; struct i2c_smbus_alert { unsigned int alert_edge_triggered:1; int irq; struct work_struct alert; struct i2c_client *ara; /* Alert response address */ }; struct alert_data { unsigned short addr; u8 flag:1; }; int flag = 0; //proc read function static int i2c_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; len += sprintf(page + len, "addr = %#x ***\n", flag); len += sprintf(page + len, "name = %s ***\n", client->name); return len; } /* If this is the alerting device, notify its driver */ static int smbus_do_alert(struct device *dev, void *addrp) { struct i2c_client *client = i2c_verify_client(dev); struct alert_data *data = addrp; if (!client || client->addr != data->addr) return 0; if (client->flags & I2C_CLIENT_TEN) return 0; /* * Drivers should either disable alerts, or provide at least * a minimal handler. Lock so client->driver won't change. */ device_lock(dev); if (client->driver) { if (client->driver->alert) client->driver->alert(client, data->flag); else dev_warn(&client->dev, "no driver alert()!\n"); } else dev_dbg(&client->dev, "alert with no driver\n"); device_unlock(dev); /* Stop iterating after we find the device */ return -EBUSY; } /* * The alert IRQ handler needs to hand work off to a task which can issue * SMBus calls, because those sleeping calls can't be made in IRQ context. */ static void smbus_alert(struct work_struct *work) { struct i2c_smbus_alert *alert; struct i2c_client *ara; unsigned short prev_addr = 0; /* Not a valid address */ alert = container_of(work, struct i2c_smbus_alert, alert); ara = alert->ara; for (;;) { s32 status; struct alert_data data; /* * Devices with pending alerts reply in address order, low * to high, because of slave transmit arbitration. After * responding, an SMBus device stops asserting SMBALERT#. * * Note that SMBus 2.0 reserves 10-bit addresess for future * use. We neither handle them, nor try to use PEC here. */ status = i2c_smbus_read_byte(ara); if (status < 0) break; data.flag = status & 1; data.addr = status >> 1; if (data.addr == prev_addr) { dev_warn(&ara->dev, "Duplicate SMBALERT# from dev " "0x%02x, skipping\n", data.addr); break; } dev_dbg(&ara->dev, "SMBALERT# from dev 0x%02x, flag %d\n", data.addr, data.flag); /* Notify driver for the device which issued the alert */ device_for_each_child(&ara->adapter->dev, &data, smbus_do_alert); prev_addr = data.addr; } /* We handled all alerts; re-enable level-triggered IRQs */ if (!alert->alert_edge_triggered) enable_irq(alert->irq); } static irqreturn_t smbalert_irq(int irq, void *d) { struct i2c_smbus_alert *alert = d; /* Disable level-triggered IRQs until we handle them */ if (!alert->alert_edge_triggered) disable_irq_nosync(irq); schedule_work(&alert->alert); return IRQ_HANDLED; } /* Setup SMBALERT# infrastructure */ static int smbalert_probe(struct i2c_client *ara, const struct i2c_device_id *id) { struct i2c_smbus_alert_setup *setup = ara->dev.platform_data; struct i2c_smbus_alert *alert; struct i2c_adapter *adapter = ara->adapter; int res; client = ara; flag = 600; printk("*** i2c_smbus的地址: %#x ***\n", client->addr); alert = kzalloc(sizeof(struct i2c_smbus_alert), GFP_KERNEL); if (!alert) return -ENOMEM; alert->alert_edge_triggered = setup->alert_edge_triggered; alert->irq = setup->irq; INIT_WORK(&alert->alert, smbus_alert); alert->ara = ara; if (setup->irq > 0) { res = devm_request_irq(&ara->dev, setup->irq, smbalert_irq, 0, "smbus_alert", alert); if (res) { kfree(alert); return res; } } i2c_set_clientdata(ara, alert); dev_info(&adapter->dev, "supports SMBALERT#, %s trigger\n", setup->alert_edge_triggered ? "edge" : "level"); return 0; } /* IRQ resource is managed so it is freed automatically */ static int smbalert_remove(struct i2c_client *ara) { struct i2c_smbus_alert *alert = i2c_get_clientdata(ara); cancel_work_sync(&alert->alert); kfree(alert); return 0; } static const struct i2c_device_id smbalert_ids[] = { { "jz-i2c", 0 }, { "jz-i2c", 1 }, { "jz-i2c", 2 }, { "jz-i2c", 3 }, {}, }; MODULE_DEVICE_TABLE(i2c, smbalert_ids); static struct i2c_driver smbalert_driver = { .probe = smbalert_probe, .remove = smbalert_remove, .id_table = smbalert_ids, .driver = { .name = "i2c", .owner = THIS_MODULE, }, }; struct i2c_board_info ara_board_info[] = { [0] = { .type = "jz-i2c", .addr = 0x0b, }, }; static int i2c_read(char *buf, int count) { int ret; if (count > 64) count = 64; ret = i2c_master_recv(client, buf, count); return ret; } static int i2c_write(char *buf, int count) { int ret; if (count > 64) count = 64; ret = i2c_master_send(client, buf, count); return ret; } static long i2c_smbus_ioctl(struct file *file, unsigned int cmd1, unsigned long arg) { char cmd; void __user *argp = (void __user *)arg; struct battery_info info; int i = 0; unsigned int data[7]; unsigned short tmp; int ret; for (cmd = 11; cmd < 20; cmd++) { if (cmd == 14 || cmd == 17) continue; i2c_write(&cmd, I2C_WRITE_LEN); ret = i2c_read((char *)&data[i], I2C_READ_4S); if (ret != I2C_READ_4S) { goto out; } i++; } info.voltage = data[0]; info.curren = data[1]; info.rsoc = data[2]; info.rc = data[3]; info.fcc = data[4]; info.descap = data[5]; info.desvol = data[6]; cmd = 24; i2c_write(&cmd, I2C_WRITE_LEN); ret = i2c_read((char *)&tmp, I2C_READ_2S); if (ret != I2C_READ_2S) { goto out; } info.temp = ( short)tmp; i = 0; for (cmd = 25; cmd < 29; cmd++) { i2c_write(&cmd, I2C_WRITE_LEN); ret = i2c_read((char *)&tmp, I2C_READ_2S); if (ret != I2C_READ_2S) { goto out; } info.vol_cell[i] = tmp; i++; } return copy_to_user(argp, &info, sizeof(info)) ? -1 : 0; out: printk("I2c resd data error.\n"); return -1; } static int i2c_smbus_release(struct inode *inode, struct file *file) { return 0; } static const struct file_operations i2c_smbus = { .owner = THIS_MODULE, .release = i2c_smbus_release, .unlocked_ioctl = i2c_smbus_ioctl, .open = NULL, }; static struct miscdevice i2c_smbus_mis = { .minor = MISC_DYNAMIC_MINOR, .name = "i2c_smbus", .fops = &i2c_smbus, }; /** * i2c_handle_smbus_alert - Handle an SMBus alert * @ara: the ARA client on the relevant adapter * Context: can't sleep * * Helper function to be called from an I2C bus driver's interrupt * handler. It will schedule the alert work, in turn calling the * corresponding I2C device driver's alert function. * * It is assumed that ara is a valid i2c client previously returned by * i2c_setup_smbus_alert(). */ int i2c_handle_smbus_alert(struct i2c_client *ara) { struct i2c_smbus_alert *alert = i2c_get_clientdata(ara); return schedule_work(&alert->alert); } EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert); static int __init i2c_smbus_init(void) { int ret; struct proc_dir_entry *res = NULL, *jz_proc = NULL, *p = NULL; struct i2c_adapter *adapter = NULL; //int board_info_size = ARRAY_SIZE(ara_board_info); ret = misc_register(&i2c_smbus_mis); if (ret) { printk("cannot register miscdev on minor (err=%d)\n",ret); } //创建proc文件 jz_proc = get_jz_proc_root(); if (!jz_proc) { pr_warning("create proc entry failed.\n"); return -1; } p = jz_proc_mkdir("i2c"); if (!p) { pr_warning("create proc entry dir i2c failed.\n"); return -1; } res = create_proc_entry("i2c", 0600, p); if (res) { res->read_proc = i2c_read_proc; //res->write_proc = i2c_write_proc; res->data = NULL; } //用适配器适配client adapter = i2c_get_adapter(0); client = i2c_new_device(adapter, ara_board_info); i2c_put_adapter(adapter); //return i2c_add_driver(&smbalert_driver); } return 0; } static void __exit i2c_smbus_exit(void) { misc_deregister(&i2c_smbus_mis); i2c_del_driver(&smbalert_driver); i2c_unregister_device(client); } module_init(i2c_smbus_init); module_exit(i2c_smbus_exit); MODULE_AUTHOR("Jean Delvare
"); MODULE_DESCRIPTION("SMBus protocol extensions support"); MODULE_LICENSE("GPL");
|