Linux I2C驱动详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_38696651/article/details/88937890

本文基于mini2440开发板,Linux版本号是:linux-2.6.32.2

一.IIC总线device 硬件信息
#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注册

IIC总线device包含在mini2440_devices中,如下图所示:
在这里插入图片描述
将包含usb,lcd,i2c,nand等设备的mini2440_devices数组当成platform设备注册到内核

platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))
三.IIC总线driver的注册

IIC总线driver的注册调用的是platform_driver_register函数,IIC总线driver注册是platform总线会去自动匹配相应的device,匹配的规则是:

  1. 先根据id_table来匹配
  2. 再根据device的名称来匹配
    在这里插入图片描述

这个IICdriver可以匹配两种IIC device,如下所示:
在这里插入图片描述

四. IIC总线driver和device的匹配

IIC总线的driver和device匹配上后,会执行driver的probe函数,probe函数中执行了如下动作:

  • 获取i2c时钟,并使能时钟
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);
  • 获取IIC寄存器的虚拟地址
i2c->regs = ioremap(res->start, resource_size(res));
  • 获取IIC中断,并申请IIC中断
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);
  • IIC初始化,设置时钟,使能中断,使能ask, 设置寄存器
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;
}
  • 注册 IIC adapter,在注册adapter之前要设置adap的名字,算法,父节点等信息
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);
五.使能IIC总线应答

在这里插入图片描述
需要把 IICCON寄存器的第7位设置为1。

#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);
}
六. 使能IIC总线中断

在这里插入图片描述
需要把 IICCON寄存器的第5位设置为1。

#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);
}
七.获取IIC总线的忙状态

在这里插入图片描述
读 IICSTAT寄存器的第5位,1表示忙,0表示空闲

#define S3C2410_IICSTAT_BUSBUSY		(1<<5)

iicstat = readl(i2c->regs + S3C2410_IICSTAT);
return (iicstat & S3C2410_IICSTAT_BUSBUSY);
八. IIC硬件中断
  1. IIC中断产生的条件
    ①一个字节的数据发送或者接收完成产生硬件中断。
    ②发出从设备地址,成功匹配到从设备后产生硬件中断。
    ③总线仲裁失败产生中断。
  2. 正常情况下IIC中断产生的时机
    发送数据时,IICDS移位寄存器向SDA一位一位发出电平信号,先发高位,循环8次后,数据发送完成, 接收到ASK信号后产生中断。
    读数据时,IICDS移位寄存器从SDA线一位一位读入电平信号,先读高位,循环8次后,数据接收完成,产生中断。

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);
九. IIC总线启动
  1. 使能IIC应答
s3c24xx_i2c_enable_ack(i2c);

2.使能IIC数据输入输出

#define S3C2410_IICSTAT_TXRXEN		(1<<4)
stat |=  S3C2410_IICSTAT_TXRXEN;

3.配置主机读写模式,若是读,设备地址最低位或上1
在这里插入图片描述

#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);

在这里插入图片描述

十. IIC总线停止传输

1.需要向IICSTART寄存器的第5位写0
在这里插入图片描述

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);
十一. IIC总线的algo
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);
十二. IIC的数据传输函数s3c24xx_i2c_xfer
  1. 配置IIC引脚
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 总线发数据

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);
  1. IIC总线启动传输
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);

7.中断发生,进入中断
在这里插入图片描述

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);
}
  1. 若这个msg发完了,看看是不是最后一个msg。如果不是,指向下一个msg。判断下一个msg是不是变成读了,就是msg改变方向了。如果是读,要设置I2C_M_NOSTART标志,表示IIC重新发起一个起始信号,否则IIC停止传输。如果还是写,继续写下一个消息。
 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;
        }
  1. 若是所有的msg都发送完了,停止IIC传输
 else 
 {
        /* send stop */
        s3c24xx_i2c_stop(i2c, 0);
}
  1. 写寄存器停止IIC传输
iicstat &= ~S3C2410_IICSTAT_START;
writel(iicstat, i2c->regs + S3C2410_IICSTAT);
  1. IIC msg 清零
i2c->msg_ptr = 0;
i2c->msg = NULL;
i2c->msg_idx++;
i2c->msg_num = 0;
  1. 唤醒等待队列
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总线收数据

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);
  1. IIC总线启动传输
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);

7.中断发生,进入中断
在这里插入图片描述

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.下面的分析同写数据一样。

十五. 从设备device注册

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);
十六. IIC从设备driver注册
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);
}
十七. IIC从设备device和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,
};
十八. 对IIC从设备的读写,最终调用的是smbus_xfer函数

获取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

猜你喜欢

转载自blog.csdn.net/weixin_38696651/article/details/88937890