本文以s3c2440的I2C子系统为例, 分析其代码实现
本人学习驱动不久, 如有瑕疵纰漏, 欢迎指教, 谢谢
从硬件的角度看, I2C子系统由总线适配器和挂在总线上的设备组成
因此, 很容易想到, Linux的I2C子系统至少要提供:
总线上设备的支持, 以及其驱动
总线适配器的支持, 以及其驱动
1. S3C2440的I2C总线作为一个平台设备, 来看下添加平台设备的代码: /arch/arm/mach-s3c2440/mach-smdk2440.c
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
};
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
其中, s3c_device_i2c0即为I2C的平台设备, 看下它是怎么定义的: /arch/arm/plat-s3c/dev-i2c0.c
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,
};
设备名称为s3c2410-i2c, 同时定义了两个资源, 一段IO内存, 一个中断号
在smdk2440_machine_init中, 即初始化过程中, 调用了s3c_i2c0_set_platdata(NULL)
static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {
.flags = 0,
.slave_addr = 0x10,
.bus_freq = 100*1000,
.max_freq = 400*1000,
.sda_delay = S3C2410_IICLC_SDA_DELAY5 | S3C2410_IICLC_FILTER_ON,
};
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd)
pd = &default_i2c_data0;
npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform datan", __func__);
else if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;
s3c_device_i2c0.dev.platform_data = npd;
}
阅读s3c_i2c0_set_platdata函数, 是对s3c_device_i2c0添加一些设备参数(default_i2c_data0)
简单的看下系统默认提供的这套参数, slave_addr应该是作为从机的地址, bus_freq应该是总线频率, 为100kHz, ...
所以, 设备结构体s3c_device_i2c0最后应该是这个样子:
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
s3c_device_i2c0.dev.platform_data = {
.flags = 0,
.slave_addr = 0x10,
.bus_freq = 100*1000,
.max_freq = 400*1000,
.sda_delay = S3C2410_IICLC_SDA_DELAY5 | S3C2410_IICLC_FILTER_ON,
.cfg_gpio = s3c_i2c0_cfg_gpio;
};
其中有个函数指针cfg_gpio, 指向了s3c_i2c0_cfg_gpio, 作用是配置I2C相应IO接口:
void s3c_i2c0_cfg_gpio(struct platform_device *dev)
{
s3c2410_gpio_cfgpin(S3C2410_GPE15, S3C2410_GPE15_IICSDA);
s3c2410_gpio_cfgpin(S3C2410_GPE14, S3C2410_GPE14_IICSCL);
}
自此, I2C作为平台设备, 被添加到了平台总线上.
2. 设备添加完了, 就该驱动了, 来看下I2C设备驱动定义: /drivers/i2c/buses/i2c-s3c2410.c
static struct platform_driver s3c2410_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.suspend_late = s3c24xx_i2c_suspend_late,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-i2c",
},
};
驱动名字为s3c2410-i2c, 和设备名字一样
然后将注册驱动, 将驱动添加到平台总线上:
static int __init i2c_adap_s3c_init(void)
{
int ret;
ret = platform_driver_register(&s3c2410_i2c_driver);
if (ret == 0) {
ret = platform_driver_register(&s3c2440_i2c_driver);
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
}
return ret;
}
回想一下, 当添加一个平台设备或者平台驱动时, 都会调用总线的match函数来将设备和驱动配对
而平台总线的match是根据设备和驱动的名字, 当相同时, 即匹配成功, 并调用驱动的probe函数
那么, 接着来看下s3c24xx_i2c_probe函数, 为了看着清晰些, 我把错误检测和错误恢复的代码删了
/* s3c24xx_i2c_probe
*
* called by the bus driver when a suitable device is found
*/
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;
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
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");
dev_dbg(&pdev->dev, "clock source %pn", i2c->clk);
clk_enable(i2c->clk);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,
pdev->name);
i2c->regs = ioremap(res->start, (res->end-res->start)+1);
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);
/* 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);
ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
dev_name(&pdev->dev), i2c);
ret = s3c24xx_i2c_register_cpufreq(i2c);
/* 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);
platform_set_drvdata(pdev, i2c);
dev_info(&pdev->dev, "%s: S3C I2C adaptern", dev_name(&i2c->adap.dev));
return 0;
}
比较常规的probe, 使能时钟, 申请IO内存, 映射IO内存, 申请中断就不说了, 删去, 得到以下:
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;
i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
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;
/* 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);
ret = s3c24xx_i2c_register_cpufreq(i2c);
/* 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);
platform_set_drvdata(pdev, i2c);
return 0;
}
是不是清爽多了
函数定义了一个struct s3c24xx_i2c *i2c, 然后对其初始化
而且可以看出, 主要是对i2c->adap进行初始化
i2c->adap指向一个struct i2c_adapter结构体, 可以翻译为i2c适配器, 来看下
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* --- administration stuff. */
int (*client_register)(struct i2c_client *);
int (*client_unregister)(struct i2c_client *);
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
struct mutex clist_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
struct list_head clients; /* DEPRECATED */
char name[48];
struct completion dev_released;
};
struct i2c_adapter中还有个const struct i2c_algorithm *algo
将i2c_adapter简称为adp, i2c_algorithm简称为algo
具体讲一下这两个数据结构有什么用:
I2C子系统主要有三部分组成: core, adap, algo
core是i2c子系统的核心层, 将与具体硬件相关的代码交给adap
而adap含有一个algo, algo定义了与具体硬件通讯的代码所以
如果有一个应用程序希望与i2c设备通讯, 将依次通过core, adap, algo, 最终将数据发到总线上
继续看probe初始化过程, ret = s3c24xx_i2c_init(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 */
pdata = i2c->dev->platform_data;
/* inititalise the gpio */
if (pdat
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』