linux I2C驱动框架之总线驱动(二)

目录

总线驱动注册与加载

添加设备到系统中

总线驱动侧处理

I2C总线通信方式


总线驱动注册与加载

以S3C-2410为例:

由于总线控制器通常是在内存上的,所以它本身也连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。S3C-2410的i2c_adapter框架是基于 platform_bus_type 的,当设备或驱动注册到 platform_bus_type 时,首先会查找驱动是否有id_table,如果有根据id_table进行匹配(就是匹配设备的名字),否则匹配设备名字和驱动名字。匹配成功则调用驱动里的probe函数。

总线驱动、也叫适配器驱动、也叫主机控制器驱动,主要的工作是产生总线上的波形

添加设备到系统中

        根据设备总线驱动模型的分层思想,将一个驱动程序分为 device 和 driver 两层,那么 device 里提供底层的硬件资源,在 driver 中取出这些资源进行使用。一般会提供以下资源

1、寄存器地址

2、中断号

下面看下具体信息为何:

static struct platform_device *smdk2410_devices[] __initdata = {
	&s3c_device_i2c0,
};

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 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,
	},
};

上面是提供的寄存器信息,我们还需要调用函数platform_add_devices将设备添加到系统中

static void __init smdk2410_init(void)
{
	s3c_i2c0_set_platdata(NULL);
	platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
	smdk_machine_init();
}

static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
	.flags		= 0,
	.slave_addr	= 0x10,
	.frequency	= 100*1000,
	.sda_delay	= 100,
};	

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
	struct s3c2410_platform_i2c *npd;

	if (!pd)
		pd = &default_i2c_data;

	npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
			       &s3c_device_i2c0);
    //s3c_device_i2c0->dev.platform_data = &default_i2c_data;

	if (!npd->cfg_gpio)
		npd->cfg_gpio = s3c_i2c0_cfg_gpio;//配置sda和scl为I2C
}

void __init *s3c_set_platdata(void *pd, size_t pdsize,
			      struct platform_device *pdev)
{
	void *npd;

	if (!pd) {
		/* too early to use dev_name(), may not be registered */
		printk(KERN_ERR "%s: no platform data supplied\n", pdev->name);
		return NULL;
	}

	npd = kmemdup(pd, pdsize, GFP_KERNEL);
	if (!npd) {
		printk(KERN_ERR "%s: cannot clone platform data\n", pdev->name);
		return NULL;
	}

	pdev->dev.platform_data = npd;
	return npd;
}

void s3c_i2c0_cfg_gpio(struct platform_device *dev)
{
	s3c_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);
	s3c_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);
}

void __init s3c244x_map_io(void)
{
	/* register our io-tables */

	iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));

	/* rename any peripherals used differing from the s3c2410 */

	s3c_device_i2c0.name  = "s3c2440-i2c";
}

总线驱动侧处理

因为总线类型是platform总线,所以驱动首先会调用platform_bus_type->match来匹配 driver->id_table 和device的,前面讲了s3c_device_i2c0.name  = "s3c2440-i2c",因此,匹配成功会调用 s3c24xx_i2c_driver->probe 函数,也就是s3c24xx_i2c_probe ,它是个重点。

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

	/* find the clock and enable it */

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

	/* map the registers */

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

	/* setup info block for the i2c core */

	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;

	/* find the IRQ for this unit (note, this relies on the init call to
	 * ensure no current IRQs pending
	 */

	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;
	}

	/* Note, previous versions of the driver used i2c_add_adapter()
	 * to add the bus at any number. We now pass the bus number via
	 * the platform data, so if unset it will now default to always
	 * being bus 0.
	 */

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

	dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
	clk_disable(i2c->clk);
	return 0;

 err_cpufreq:
	s3c24xx_i2c_deregister_cpufreq(i2c);

 err_irq:
	free_irq(i2c->irq, i2c);

 err_iomap:
	iounmap(i2c->regs);

 err_ioarea:
	release_resource(i2c->ioarea);
	kfree(i2c->ioarea);

 err_clk:
	clk_disable(i2c->clk);
	clk_put(i2c->clk);

 err_noclk:
	kfree(i2c);
	return ret;
}

static int s3c24xx_i2c_remove(struct platform_device *pdev)
{
	struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);

	s3c24xx_i2c_deregister_cpufreq(i2c);

	i2c_del_adapter(&i2c->adap);
	free_irq(i2c->irq, i2c);

	clk_disable(i2c->clk);
	clk_put(i2c->clk);

	iounmap(i2c->regs);

	release_resource(i2c->ioarea);
	kfree(i2c->ioarea);
	kfree(i2c);

	return 0;
}


static struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",
		.driver_data	= TYPE_S3C2410,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= TYPE_S3C2440,
	}, {
		.name		= "s3c2440-hdmiphy-i2c",
		.driver_data	= TYPE_S3C2440_HDMIPHY,
	}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);

static struct platform_driver s3c24xx_i2c_driver = {
	.probe		= s3c24xx_i2c_probe,
	.remove		= s3c24xx_i2c_remove,
	.id_table	= s3c24xx_driver_ids,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c-i2c",
		.pm	= S3C24XX_DEV_PM_OPS,
	},
};

static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);

s3c24xx_i2c_probe函数(总线驱动模块的加载函数)中完成两个工作。

  • 初始化 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 消息,如下所示为 xxx 设备的master_xfer()函数模板。

static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,int num)
{
    ...
    for (i = 0; i < num; i++)
    {
        i2c_adapter_xxx_start(); /*产生开始位*/
        /*是读消息*/
        if (msgs[i]->flags &I2C_M_RD)
        {
            i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*发送从设备读地址*/
            i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
            i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); 
            /*读取msgs[i] ->len长的数据到msgs[i]->buf*/
        }
        else
        /*是写消息*/
        {
            i2c_adapter_xxx_setaddr(msg->addr << 1); /*发送从设备写地址*/
            i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
            i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); 
            /*读取msgs[i] ->len长的数据到msgs[i]->buf*/
        }
    }
    i2c_adapter_xxx_stop(); /*产生停止位*/
}

上述代码实际上给出了一个master_xfer()函数处理I2C 消息数组的流程,对于数组中的每个消息,判断消息类型,若为读消息,则赋从设备地址为(msg->addr << 1)|1,否则为msg->addr << 1。对每个消息产生一个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,对最后的消息还需产生一个停止位。如下图所示为整个master_xfer()完成的时序。

linux I2C驱动框架之总线驱动(二)_第1张图片

master_xfer() 函数模板中的i2c_adapter_xxx_start() 、i2c_adapter_xxx_setaddr() 、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与I2C 适配器和CPU 的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。

i2c_adapter_xxx_readbytes()用于从从设备上接收一串数据,i2c_adapter_xxx_writebytes()用于向从设备写入一串数据,这两个函数的内部也会涉及I2C 总线协议中的ACK 应答。

master_xfer()函数的实现在形式上会很多样,即便是Linux 内核源代码中已经给出的一些I2C 总线驱动的master_xfer()函数,由于由不同的组织或个人完成,风格上的差别也非常大,不一定能与模板完全对应,如master_xfer()函数模板给出的消息处理是顺序进行的,而有的驱动以中断方式来完成这个流程。不管具体怎么实施,流程的本质都是不变的。因为这个流程不以驱动工程师的意志为转移,最终由I2C 总线硬件上的通信协议决定。

多数 I2C 总线驱动会定义一个xxx_i2c 结构体,作为i2c_adapter 的algo_data(类似“私有数据”),其中包含I2C 消息数组指针、数组索引及I2C 适配器algorithm 访问控制用的自旋锁、等待队列等,而master_xfer()函数完成消息数组中消息的处理也可通过对xxx_i2c 结构体相关成员的访问来控制。

你可能感兴趣的:(linux设备驱动)