1、i2c总线简介
I2C BUS(Inter IC BUS)是NXP推出的芯片间串行传输总线,它以2根连线实现了完善的双向数据传送,可以很方便地构成朵机系统和外围器件扩展系统。I2C总线的2根线(串行数据SDA,串行时钟SCL)连接到总线上的任何一个器件,每个器件都应有一个唯一的地址,而且都可以作为一个发送器或者一个接受器。此外,器件在执行数据传输时也可以被看作是主机或从机。
发送器:本次传送中发送数据(不包括地址和命令)到总线的器件。
接受器:本次传送中从总线接受数据(不包括地址和命令)的器件。
主机: 初始化发送、产生时钟信号和终止发送的器件,它可以是发送器或接受器。主机通常是微控制器。
从机: 被主机寻址的器件,它可以是发送器或接收器。
SDA和SCL都是双向线路。连接到总线的器件的输出级必须是漏级开路或集电极开路,都通过一个电流源或上拉电阻连接到正的电源电压,这样才能实现“线与”功能。当总线空闲时,这2条线路都是高电平。
在标准模式下,总线数据传输的速度为0-100kbit/s,在高速模式下,可以达到0-400kbit/s。总线速率与总线上拉电阻的关系:总线速率越高,总线上拉电阻要越小。100kbit/s总线速率,通常使用5.1k的上拉电阻。
2、数据传输格式
2.1、空闲状态
I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2.2、起始信号和停止信号
起始信号(重复起始信号):在SCL为高电平时,SDA从高电平向低电平切换。
停止信号:在SCL为高电平时,SDA由低电平向高电平切换。
起始信号和停止信号一般由主机产生。起始信号作为一次传输的开始,在起始信号后,总线被认为处于忙的状态。停止信号作为一次传送的结束,在停止信号的某时段后,总线被认为再次处于空闲状态。重复传送信号即作为上次传送的结束,也作为下次传送的开始。
2.3、ACK
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。
2.4、数据传输
2.4.1、主设备向从设备发送数据(即写数据)
- 主设备发送起始信号;
- 主设备发送一个8位地址信号(这里就是我们平常说的设备I2C地址),地址信号第8位为低电平,表示写信号;
- 主设备会在发送一个Word address(这里的地址就是我们平常用到写数据进去的寄存器地址);
- 发送数据;
- 发送停止信号
2.4.2、主设备读从设备数据(即读数据)
读数据比写数据要复杂,在读数据前,首先要告诉从设备哪个寄存器是需要读的,所以读数据前,先会有一个写数据过程。
- 主设备发送起始信号;
- 主设备发送一个8位地址信号(这里就是我们平常说的设备I2C地址),地址信号第8位为低电平,表示写信号;
- 发送内部寄存器地址;
- 重复起始信号,重新发送设备地址,此时第8位变成高电平,表示读信号;
- 读取数据;
- 主设备发送停止信号。
3、代码分析
Linux的I2C体系结构分为3个组成部分:
I2C核心:I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探 测设备,检测设备地址的上层代码等。
I2C总线驱动:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
I2C设备驱动:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过 I2C适配器与CPU交换数据。
具体框架如下所示:
I2c利用platform-bus总线进行注册的,device信息在mach-mini210.c中填充,i2c-s3c2410.c通过name匹配,注册device,并且注册i2c_adapter通过__i2c_board_list全局链表匹配版级设备,版级设备在mach-mini210.c中填充,设备驱动通过设备name跟版级设备匹配,具体流程如下图所示:
3.1、device部分
I2c总线跟其他总线类似,device部分在mini210_machine_init函数中注册,同时会将io资源进行注册,mini210_machine_init开机会运行,mini210_machine_int函数在linux-3.0.8/arch/arm/mach-s5pv210/mini210-lcds.c中定义,如下所示:
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
static struct platform_device *mini210_devices[] __initdata = {
………
&s3c_device_i2c0,
&s3c_device_i2c1,
&s3c_device_i2c2,
………
}
static void __init mini210_machine_init(void)
{
arm_pm_restart = smdkc110_pm_restart;
s3c_pm_init();
mini210_wifi_init();
#ifdef CONFIG_DM9000
mini210_dm9000_init();
#endif
/* Disable USB PHY for soft reboot */
if (__raw_readl(S5P_RST_STAT) & (1 << 3)) {
__raw_writel(0, S5P_USB_PHY_CONTROL);
}
platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));
设备的版级信息也是在mini210_machine_int函数进行注册:
static struct i2c_board_info mini210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("24c08", 0x50), }, /* Samsung S524AD0XD1 */
#ifdef CONFIG_SND_SOC_WM8580
{ I2C_BOARD_INFO("wm8580", 0x1b), },
#endif
#ifdef CONFIG_SND_SOC_WM8960_MINI210
{
I2C_BOARD_INFO("wm8960", 0x1a),
.platform_data = &wm8960_pdata,
},
#endif
#ifdef CONFIG_SENSORS_MMA7660
{
I2C_BOARD_INFO("mma7660", 0x4c),
.platform_data = &mma7660_pdata,
},
#endif
};
static void __init mini210_machine_init(void)
{
s3c_i2c2_set_platdata(&i2c2_data);
i2c_register_board_info(0, mini210_i2c_devs0,
ARRAY_SIZE(mini210_i2c_devs0));
i2c_register_board_info(1, mini210_i2c_devs1,
ARRAY_SIZE(mini210_i2c_devs1));
i2c_register_board_info(2, mini210_i2c_devs2,
ARRAY_SIZE(mini210_i2c_devs2));
#if defined(CONFIG_TOUCHSCREEN_1WIRE)
onewire_i2c_bdi.irq = gpio_to_irq(S5PV210_GPH1(6));
i2c_register_board_info(2, &onewire_i2c_bdi, 1);
#endif
#ifdef CONFIG_TOUCHSCREEN_EGALAX
i2c_register_board_info(5, i2c_devs5, ARRAY_SIZE(i2c_devs5));
#endif
i2c_register_board_info位注册版级设备,函数中填充好版级信息,然后搜索全局链表__i2c_board_list,如果不存在相应版级信息,将新的版级信息注册到__i2c_board_list链表中:
int __init i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);
}
up_write(&__i2c_board_lock);
return status;
}
3.2 i2c-core部分
I2c-core是I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。Tiny210该部分代码在i2c-s3c2410.c中。
s3c24xx_i2c_probe中会指定i2c通信函数s3c24xx_i2c_algorithm,,初始化平台相关的硬件设备,然后注册一个i2c-adapter,这里先看i2c-adapter是如何注册的。
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
res = -EAGAIN;
goto out_list;
}
/* Sanity checks */
if (unlikely(adap->name[0] == '\0')) {
pr_err("i2c-core: Attempt to register an adapter with "
"no name!\n");
return -EINVAL;
}
if (unlikely(!adap->algo)) {
pr_err("i2c-core: Attempt to register adapter '%s' with "
"no algo!\n", adap->name);
return -EINVAL;
}
rt_mutex_init(&adap->bus_lock);
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients);
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0)
adap->timeout = HZ;
//设置 adap->dev.kobj.name 为 i2c-0 ,它将出现在 sysfs 中
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
//设置所属的总线
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
//将 adap->dev注册到i2c bus总线
res = device_register(&adap->dev);
if (res)
goto out_list;
dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
#ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
if (res)
dev_warn(&adap->dev,
"Failed to create compatibility class link\n");
#endif
/* create pre-declared device nodes */
//扫描 __i2c_board_list 链表里的设备信息,自动创建 client ,并注册到 i2c_bus_type
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
/* Notify drivers */
mutex_lock(&core_lock);
//遍历 i2c_bus_type的driver 链表,取出每一个driver 调用 i2c_do_add_adapter
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
mutex_unlock(&core_lock);
return 0;
out_list:
mutex_lock(&core_lock);
idr_remove(&i2c_adapter_idr, adap->nr);
mutex_unlock(&core_lock);
return res;
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
//遍历__i2c_board_list链表,取出每一个devinfo
list_for_each_entry(devinfo, &__i2c_board_list, list) { //每一个设备都注册一个
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
//遍历__i2c_board_list链表,取出每一个devinfo
list_for_each_entry(devinfo, &__i2c_board_list, list) { //每一个设备都注册一个
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
i2c_scan_static_board_info会扫描__i2c_board_list 链表中i2c_register_board_info填充的版级信息,然后作为device设备注册成i2c bus设备。
3.3 i2c_add_driver注册driver
i2c_add_driver函数是i2c-core暴露出来提供给设备驱动注册i2c driver的,传入的参数是一个i2c_driver类型结构体:
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this, it will be removed in a
* near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
/*探测函数,匹配成功之后执行,会将匹配到的i2c_client对象传入,
完成申请资源,初始化,提供接口等工作*/
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
/*移除函数,设备消失时会调用,驱动模块被rmmod时也会先被调用,
完成和probe相反的操作*/
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
//电源管理相关的接口
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
//设备驱动类,用于匹配设备树的of_match_table域在这里
struct device_driver driver;
//用于使用平台文件或模块编写设备信息时进行匹配使用,相当于platform_driver中的id_table
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
//用于将所有i2c_driver联系到一起的节点
struct list_head clients;
};
在设备驱动中,不需要每个变量都定义,只需定义部分变量,下面是goodix TP驱动中注册driver实例:
#ifdef GTP_CONFIG_OF
static const struct of_device_id goodix_match_table[] = {
{.compatible = "goodix,gt9xx",},
{ },
};
#endif
static const struct i2c_device_id goodix_ts_id[] = {
{ GTP_I2C_NAME, 0 },
{ }
};
static struct i2c_driver goodix_ts_driver = {
.probe = goodix_ts_probe,
.remove = goodix_ts_remove,
.id_table = goodix_ts_id,
.driver = {
.name = GTP_I2C_NAME,
.owner = THIS_MODULE,
#ifdef GTP_CONFIG_OF
.of_match_table = goodix_match_table,
#endif
#if !defined(CONFIG_FB) && defined(CONFIG_PM)
.pm = >p_pm_ops,
#endif
},
};
ret = i2c_add_driver(&goodix_ts_driver);
上面注册的driver name跟device注册的name是相同的,i2c_add_driver函数会调用到i2c-core.c中的i2c_register_driver函数:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;
/* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p)))
return -EAGAIN;
/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;
/* Drivers should switch to dev_pm_ops instead. */
if (driver->suspend)
pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
driver->driver.name);
if (driver->resume)
pr_warn("i2c-core: driver [%s] using legacy resume method\n",
driver->driver.name);
pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
INIT_LIST_HEAD(&driver->clients);
/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
i2c_register_driver中会调用driver_register去注册driver,然后通过i2c_for_each_dev(driver, __process_new_driver);将i2c_driver与i2c_adapter关联起来。
driver_register主要完成下面工作:
- 查找bus总线是否注册了driver
- bus_add_driver中调用driver_attach,通过__driver_attach函数中的driver_match_device调用bus中的match函数,也是i2c_device_match函数,通过比较i2c_driver->id_table->name和client->name,如果相同,则匹配上,匹配上之后,运行driver_register调用driver_probe_device进行设备与驱动绑定。
- __driver_attach中调用driver_probe_device进入不同设备函数中的probe进行匹配。
- 在sys下面创建对应的driver节点
- 3.4s3c24xx_i2c_algorithm通讯函数
- 不同平台的i2c函数都会有自己的通讯函数,tiny210开发板的通讯函数是s3c24xx_i2c_algorithm,linux会将通讯函数进行封装,提供给驱动使用都是i2c_transfer函数。
i2c_transferàadap->algo->master_xfer(adap, msgs, num)à s3c24xx_i2c_algorithm(s3c24xx_i2c_probe中有填充)
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
int retry;
int ret;
//使能clk
clk_enable(i2c->clk);
for (retry = 0; retry < adap->retries; retry++) {
ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
if (ret != -EAGAIN) {
clk_disable(i2c->clk);
return ret;
}
dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);
udelay(100);
}
//关闭clk
clk_disable(i2c->clk);
return -EREMOTEIO;
}
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
struct i2c_msg *msgs, int num)
{
unsigned long timeout;
int ret;
if (i2c->suspended)
return -EIO;
//设置开始传输寄存器
ret = s3c24xx_i2c_set_master(i2c);
if (ret != 0) {
dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
ret = -EAGAIN;
goto out;
}
spin_lock_irq(&i2c->lock);
i2c->msg = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state = STATE_START;
//使能传输中断
s3c24xx_i2c_enable_irq(i2c);
s3c24xx_i2c_message_start(i2c, msgs); //数据传输
spin_unlock_irq(&i2c->lock);
//超时等待
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
ret = i2c->msg_idx;
/* having these next two as dev_err() makes life very
* noisy when doing an i2cdetect */
if (timeout == 0)
dev_dbg(i2c->dev, "timeout\n");
else if (ret != num)
dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);
/* ensure the stop has been through the bus */
udelay(10);
out:
return ret;
}
- 、s3c24xx_i2c_set_master
writel(iicstat & ~S3C2410_IICSTAT_TXRXEN, i2c->regs + S3C2410_IICSTAT); // 1<<4 I2C-bus data output enable/ disable bit
if (!(readl(i2c->regs + S3C2410_IICSTAT) & S3C2410_IICSTAT_BUSBUSY)// 判断总线是否忙
2、s3c24xx_i2c_enable_irq(i2c)
readl(i2c->regs + S3C2410_IICCON) //1<<5 I2C-Bus Tx/Rx interrupt enable/ disable bit.
writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON); //第5位置1
3、s3c24xx_i2c_message_start(i2c, msgs)
s3c24xx_i2c_enable_ack(i2c); // 0xE180_0000写入1<<7位,使能ack位
writel(stat, i2c->regs + S3C2410_IICSTAT); // 2<<6 Master receive mode 3<<6 Master transmit mode
writeb(addr, i2c->regs + S3C2410_IICDS);
/* I2C-Bus busy signal status bit.
0 = read) Not busy (If read) write) STOP signal generation
1 = read) Busy (If read) write) START signal generation. The data in I2 CDS is transferred automatically just after the start signal
*/
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT); //1<<4 i2c-bus data output enable
3.5硬件寄存器设置
在上面s3c24xx_i2c_probe和s3c24xx_i2c_doxfer都会有一些寄存器设置,下面来看看具体含义。
static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
struct s3c2410_platform_i2c *pdata;
unsigned int freq;
/* get the plafrom data */
pdata = i2c->dev->platform_data;
/* inititalise the gpio */
if (pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev));
/* write slave address */
//write 0xE1800008 Specifies the I2C-Bus Interface0 address register P894
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
//write 0xE1800000; data: 1<<5 | 1<<7 Specifies the I2C-Bus Interface0 control register
writel(iicon, i2c->regs + S3C2410_IICCON);
/* we need to work out the divisors for the clock... */
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
writel(0, i2c->regs + S3C2410_IICCON);
dev_err(i2c->dev, "cannot meet bus frequency required\n");
return -EINVAL;
}
/* todo - check that the i2c lines aren't being dragged anywhere */
dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
//Specifies the I2C-Bus Interface0 multi-master line control register
writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
return 0;
}
1、writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
指定i2c总线0寄存器地址
2、writel(iicon, i2c->regs + S3C2410_IICCON);
0xE1800000写1<<7,1<<5
3、writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);
来源:CSDN
作者:lansehai2014
链接:https://blog.csdn.net/bsxiaomage/article/details/85227089