本文基于mini2440开发板,Linux版本号是:linux-2.6.32.2
#define S3C2410_PA_IIC (0x54000000)
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
},
};
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 s3c2410_platform_i2c default_i2c_data0 __initdata = {
.flags = 0,
.slave_addr = 0x10,
.frequency = 100*1000,
.sda_delay = 100,
};
其中,IIC寄存器的基地址为0x54000000。
s3c3440芯片的寄存器的地址如下:
IIC总线device包含在mini2440_devices中,如下图所示:
将包含usb,lcd,i2c,nand等设备的mini2440_devices数组当成platform设备注册到内核
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))
IIC总线driver的注册调用的是platform_driver_register函数,IIC总线driver注册是platform总线会去自动匹配相应的device,匹配的规则是:
这个IICdriver可以匹配两种IIC device,如下所示:
IIC总线的driver和device匹配上后,会执行driver的probe函数,probe函数中执行了如下动作:
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;
}
clk_enable(i2c->clk);
i2c->regs = ioremap(res->start, resource_size(res));
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);
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 :获取platform 设备数据
pdata = i2c->dev->platform_data;
/* inititalise the gpio */
if (pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev));
/* write slave address */
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
// IIC-bus acknowledge enable
//IC-Bus Tx/Rx interrupt enable
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);
return 0;
}
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;
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
i2c->adap.nr = pdata->bus_num;
ret = i2c_add_numbered_adapter(&i2c->adap);
#define S3C2410_IICCON_ACKEN (1<<7)
static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c)
{
unsigned long tmp;
tmp = readl(i2c->regs + S3C2410_IICCON);
writel(tmp | S3C2410_IICCON_ACKEN, i2c->regs + S3C2410_IICCON);
}
#define S3C2410_IICCON_IRQEN (1<<5)
static inline void s3c24xx_i2c_enable_irq(struct s3c24xx_i2c *i2c)
{
unsigned long tmp;
tmp = readl(i2c->regs + S3C2410_IICCON);
writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON);
}
#define S3C2410_IICSTAT_BUSBUSY (1<<5)
iicstat = readl(i2c->regs + S3C2410_IICSTAT);
return (iicstat & S3C2410_IICSTAT_BUSBUSY);
3.中断处理过程中又来了新的IIC数据
产生硬件中断后,会设置中断挂起flag,同时将SCL拉低,IIC传输暂停,以防在中断处理过程中又来了新的IIC信号。
等待中断函数处理完成后,清除中断挂起flag,释放SCL,IIC继续发送和接收数据。
清除中断挂起flag,设置 IICCON寄存器的第4位为0。
#define S3C2410_IICCON_IRQPEND (1<<4)
tmp = readl(i2c->regs + S3C2410_IICCON);
tmp &= ~S3C2410_IICCON_IRQPEND;
writel(tmp, i2c->regs + S3C2410_IICCON);
s3c24xx_i2c_enable_ack(i2c);
2.使能IIC数据输入输出
#define S3C2410_IICSTAT_TXRXEN (1<<4)
stat |= S3C2410_IICSTAT_TXRXEN;
#define S3C2410_IICSTAT_MASTER_RX (2<<6)
#define S3C2410_IICSTAT_MASTER_TX (3<<6)
if (msg->flags & I2C_M_RD)
{
stat |= S3C2410_IICSTAT_MASTER_RX;
addr |= 1;
}
else
stat |= S3C2410_IICSTAT_MASTER_TX;
writel(stat, i2c->regs + S3C2410_IICSTAT);
4.写从设备地址到IICDS寄存器
writeb(addr, i2c->regs + S3C2410_IICDS);
5.开始IIC传输
stat |= S3C2410_IICSTAT_START;
writel(stat, i2c->regs + S3C2410_IICSTAT);
unsigned long iicstat = readl(i2c->regs + S3C2410_IICSTAT);
/* stop the transfer */
iicstat &= ~S3C2410_IICSTAT_START;
writel(iicstat, i2c->regs + S3C2410_IICSTAT);
2.禁止IIC中断
s3c24xx_i2c_disable_irq(i2c);
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
s3c24xx_i2c_algorithm赋值给adap.algo
i2c->adap.algo = &s3c24xx_i2c_algorithm;
该adap添加进内核,后面内核通过该adap控制这个IIC总线的数据传输。
i2c_add_numbered_adapter(&i2c->adap);
if (pdata->cfg_gpio)
pdata->cfg_gpio(to_platform_device(i2c->dev));
2.调用s3c24xx_i2c_doxfer函数传输数据,最多重复操作2次,成功了就退出,失败了就继续操作。
adap->retries = 2。
for (retry = 0; retry < adap->retries; retry++)
{
ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
if (ret != -EAGAIN)
return ret;
udelay(100);
}
IIC总线调用s3c24xx_i2c_xfer发送数据,s3c24xx_i2c_xfer函数又调用s3c24xx_i2c_doxfer。
1.等待IIC不忙。
ret = s3c24xx_i2c_set_master(i2c);
2.设置IIC状态标志位
i2c->state = STATE_START;
3.使能IIC中断
s3c24xx_i2c_enable_irq(i2c);
s3c24xx_i2c_message_start(i2c, msgs);
5.进入睡眠,等待IIC的等待队列唤醒。
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
6.等待中断发生,中断函数在s3c24xx_i2c_probe里面注册。
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED, dev_name(&pdev->dev), i2c);
8.进入case STATE_START,判断最后接收到1bit数据是不是ASK, 没有收到ask,退出IIC传输(前提是没有设置I2C_M_IGNORE_NAK模式)
if (iicstat & S3C2410_IICSTAT_LASTBIT && !(i2c->msg->flags & I2C_M_IGNORE_NAK))
{
/* ack was not received... */
dev_dbg(i2c->dev, "ack was not received\n");
s3c24xx_i2c_stop(i2c, -ENXIO);
goto out_ack;
}
9.判断是读IIC消息还是写IIC消息
if (i2c->msg->flags & I2C_M_RD)
i2c->state = STATE_READ;
else
i2c->state = STATE_WRITE;
在这里是写IIC,i2c->state = STATE_WRITE;
10.判断该消息是不是最后一个消息,若是,停止IIC传输,否则,进入case STATE_WRITE。这里通常是i2c匹配设备的时候使用。
if (is_lastmsg(i2c) && i2c->msg->len == 0)
{
s3c24xx_i2c_stop(i2c, 0);
goto out_ack;
}
11.若这个msg的数据没有发完,继续发,每次发一个byte,就是给寄存器S3C2410_IICDS赋值。
if (!is_msgend(i2c))
{
byte = i2c->msg->buf[i2c->msg_ptr++];
writeb(byte, i2c->regs + S3C2410_IICDS);
ndelay(i2c->tx_setup);
}
else if (!is_lastmsg(i2c))
{
/* we need to go to the next i2c message */
dev_dbg(i2c->dev, "WRITE: Next Message\n");
i2c->msg_ptr = 0;
i2c->msg_idx++;
i2c->msg++;
/* check to see if we need to do another message */
if (i2c->msg->flags & I2C_M_NOSTART)
{
if (i2c->msg->flags & I2C_M_RD)
{
/* cannot do this, the controller
* forces us to send a new START
* when we change direction */
s3c24xx_i2c_stop(i2c, -EINVAL);
}
goto retry_write;
}
else
{
/* send the new start */
s3c24xx_i2c_message_start(i2c, i2c->msg);
i2c->state = STATE_START;
}
else
{
/* send stop */
s3c24xx_i2c_stop(i2c, 0);
}
iicstat &= ~S3C2410_IICSTAT_START;
writel(iicstat, i2c->regs + S3C2410_IICSTAT);
i2c->msg_ptr = 0;
i2c->msg = NULL;
i2c->msg_idx++;
i2c->msg_num = 0;
wake_up(&i2c->wait);
17.禁止中断产生。
s3c24xx_i2c_disable_irq(i2c);
18.清除中断挂起标志位。
#define S3C2410_IICCON_IRQPEND (1<<4)
tmp = readl(i2c->regs + S3C2410_IICCON);
tmp &= ~S3C2410_IICCON_IRQPEND;
writel(tmp, i2c->regs + S3C2410_IICCON);
IIC总线调用s3c24xx_i2c_xfer收数据,s3c24xx_i2c_xfer函数又调用s3c24xx_i2c_doxfer。
1.等待IIC不忙。
ret = s3c24xx_i2c_set_master(i2c);
2.设置IIC状态标志位
i2c->state = STATE_START;
3.使能IIC中断
s3c24xx_i2c_enable_irq(i2c);
s3c24xx_i2c_message_start(i2c, msgs);
5.进入睡眠,等待IIC的等待队列唤醒。
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
6.等待中断发生,中断函数在s3c24xx_i2c_probe里面注册。
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED, dev_name(&pdev->dev), i2c);
8.进入case STATE_START,判断最后接收到1bit数据是不是ASK, 没有收到ask,退出IIC传输(前提是没有设置I2C_M_IGNORE_NAK模式)
if (iicstat & S3C2410_IICSTAT_LASTBIT && !(i2c->msg->flags & I2C_M_IGNORE_NAK))
{
/* ack was not received... */
dev_dbg(i2c->dev, "ack was not received\n");
s3c24xx_i2c_stop(i2c, -ENXIO);
goto out_ack;
}
9.判断是读IIC消失还是写IIC消息
if (i2c->msg->flags & I2C_M_RD)
i2c->state = STATE_READ;
else
i2c->state = STATE_WRITE;
在这里是读IIC,i2c->state = STATE_READ,跳到prepare_read执行。这里为什么跳过byte = readb(i2c->regs + S3C2410_IICDS);i2c->msg->buf[i2c->msg_ptr++] = byte 直接到prepare_read执行始终没有想明白。
case STATE_READ:
/* we have a byte of data in the data register, do
* something with it, and then work out wether we are
* going to do any more read/write
*/
byte = readb(i2c->regs + S3C2410_IICDS);
i2c->msg->buf[i2c->msg_ptr++] = byte;
prepare_read:
if (is_msglast(i2c))
{
/* last byte of buffer */
if (is_lastmsg(i2c))
s3c24xx_i2c_disable_ack(i2c);
}
else if (is_msgend(i2c))
{
/* ok, we've read the entire buffer, see if there
* is anything else we need to do */
if (is_lastmsg(i2c))
{
/* last message, send stop and complete */
dev_dbg(i2c->dev, "READ: Send Stop\n");
s3c24xx_i2c_stop(i2c, 0);
}
else
{
/* go to the next transfer */
dev_dbg(i2c->dev, "READ: Next Transfer\n");
i2c->msg_ptr = 0;
i2c->msg_idx++;
i2c->msg++;
}
}
10.下面的分析同写数据一样。
1.设置从设备地址
info.addr = setup->i2c_address;
2.设置从设备名称
strlcpy(info.type, "wm8510", I2C_NAME_SIZE);
3.根据IIC index获取IIC adapter
adapter = i2c_get_adapter(setup->i2c_bus);
4.注册IIC device
client = i2c_new_device(adapter, &info);
static const struct i2c_device_id ds1682_id[] = {
{ "ds1682", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ds1682_id);
static struct i2c_driver ds1682_driver = {
.driver = {
.name = "ds1682",
},
.probe = ds1682_probe,
.remove = ds1682_remove,
.id_table = ds1682_id,
};
static int __init ds1682_init(void)
{
return i2c_add_driver(&ds1682_driver);
}
static int ds1682_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int rc;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_I2C_BLOCK)) {
dev_err(&client->dev, "i2c bus does not support the ds1682\n");
rc = -ENODEV;
goto exit;
}
rc = sysfs_create_group(&client->dev.kobj, &ds1682_group);
if (rc)
goto exit;
在sys目录下创建bin节点文件,用户可以同此节点文件来操作eeprom,并提供操作方法(read,write)
rc = sysfs_create_bin_file(&client->dev.kobj, &ds1682_eeprom_attr);
if (rc)
goto exit_bin_attr;
return 0;
exit_bin_attr:
sysfs_remove_group(&client->dev.kobj, &ds1682_group);
exit:
return rc;
}
static struct bin_attribute ds1682_eeprom_attr = {
.attr = {
.name = "eeprom",
.mode = S_IRUGO | S_IWUSR,
},
.size = DS1682_EEPROM_SIZE,
.read = ds1682_eeprom_read,
.write = ds1682_eeprom_write,
};
获取i2c_client对象的函数,获取了i2c_client就可以在driver中对IIC从设备进行读写。
struct i2c_client *client = to_i2c_client(dev);
struct i2c_client *client = kobj_to_i2c_client(kobj);
ds1682_eeprom_write
i2c_smbus_write_i2c_block_data
i2c_smbus_xfer
adapter->algo->smbus_xfer
ds1682_eeprom_read
i2c_smbus_read_i2c_block_data
i2c_smbus_xfer
adapter->algo->smbus_xfer