iic总线驱动(适配器驱动)详解

linux系统下,不管什么设备(总线驱动总线设备驱动..),挂接在总线上都分两种资源:1.驱动设备资源(驱动设备资源注册)2.驱动(针对设备的驱动注册)

所以在实现和学习驱动的时候,就分这两块来进行。

一:我会查找i2c驱动设备资源添加(i2c适配器平台资源)

//Mach-mini2440.c (arch\arm\mach-s3c24xx)

(1)

//平台数据

struct s3c2410_platform_i2c default_i2c_data __initdata = {
.flags= 0,
.slave_addr= 0x10,//从机地址
.frequency= 100*1000,//总线工作频率
.sda_delay= 100, //数据传输延时
};

s3c_i2c0_set_platdata(NULL);//平台数据设置

s3c_i2c0_set_platdata()函数将S3C2440上的I2C控制器进行了一些初始化,但是并没有写入硬件寄存器,仅仅是保存在了s3c2410_platform_i2c结构体中

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd) {
pd = &default_i2c_data;
pd->bus_num = 0;
}
npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
      &s3c_device_i2c0);

/* s3c_i2c0_cfg_gpio为 配置I2C控制器GPIO函数指针 */
if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;
}

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;
}
/*最后将struct device 中的platform_data指针直指向了初始化后的 s3c2410_platform_i2c结构体  */
pdev->dev.platform_data = npd;
return npd;
}


(2)

platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));

主要是将开发平台的设备注册进了系统,也就是将device注册到了platform虚拟的总线上,并进行了一些初始化的工作

-》platform_device_register(devs[i]);-》device_initialize(&pdev->dev)-》

将设备加入到平台中。mini2440_devices包含所有的划分为平台的总线驱动设备的资源,其中包括&s3c_device_i2c0

//Devs.c (arch\arm\plat-samsung)

//平台资源

static struct resource s3c_i2c0_resource[] = {
[0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),//i2c控制器寄存器的地址范围
[1] = DEFINE_RES_IRQ(IRQ_IIC),//中断号
};
//平台资源
struct platform_device s3c_device_i2c0 = {

/*设备名,platform总线的match函数中会用设备名和驱动名的比较来绑定设备和驱动程序*/
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources= ARRAY_SIZE(s3c_i2c0_resource),
.resource = s3c_i2c0_resource,
};


以上就是编写i2c总线驱动的时候,需要构造的总线驱动设备资源。

上面是一些板级的硬件设备资源向系统的注册,没有设计到具体的硬件操作,在加载驱动程序时,驱动程序会根据已经注册到系统的具体设备的硬件资源进行初始化,也就是进行一些硬件操作,控制硬件设备的正常工作,下面来分析驱动程序的加载过程。

二:i2c驱动注册

在 kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为 I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.
对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.

//I2c-s3c2410.c (drivers\i2c\busses)

/* device driver for platform bus bits */
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,
.of_match_table = of_match_ptr(s3c24xx_i2c_match),
},
};


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

其中probe是加载driver后调用以初始化设备的,结构体中的id_table中的name是用来和device也就是适配器设备配对的,两者名字一样即配对成功,在有的版本的内核中,driver结构体中没有.id_table的赋值,与device的配对是通过driver中的name来进行配对的。

1:i2c总线初始化函数s3c24xx_i2c_probe

这个函数的主要目的是对I2C总线进行初始化,方法是:1、先声明一个s3c24xx_i2c结构体 然后对其初始化一些基本信息,我们可以看到里面就包含信息 .name = "s3c2410-i2c", 正好和之前驱动结构体里面的name吻合(这个是i2c_adper结构中的成员,也就是说一个适配器与一个平台设备相匹配)。2、将传入的平台结构体(其他程序调用驱动的时候应该会付给一个平台结构体)中的一些不用修改的信息付给i2c(也就是s3c24xx_i2c结构体),这样就构造出一个我们想要的i2c结构体,其中包含的是完整信息,然后再将i2c付给平台结构体,使用platform_set_drvdata(pdev, i2c);这条语句,这样平台结构就关联上了I2C数据。

在设备与驱动匹配成功后,会执行s3c24xx_i2c_probe()函数,其源码如下:

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = NULL;
struct resource *res;
int ret;
if (!pdev->dev.of_node) {

//s3c_i2c0_set_platdata(NULL);->s3c_set_platdata;->pdev->dev.platform_data = npd;
pdata = pdev->dev.platform_data;

if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata) {
dev_err(&pdev->dev, "no memory for platform data\n");
return -ENOMEM;
}
i2c->quirks = s3c24xx_get_device_quirks(pdev);
if (pdata)
memcpy(i2c->pdata, pdata, sizeof(*pdata));
else
s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);
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;//maybe the num of retry
i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup     = 50;/* delay time here to ensure the data byte has gotten onto the bus
* before the transaction is started */

init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");//clk_get从一个时钟list链表中以字符id名称来查找一个时钟clk结构体并且返回
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_get_rate函数可以从clk_get得到的某设备结构体中获得该设备的时钟频率
clk_enable(i2c->clk);//使能对应的外设时钟源,系统启动的时候暂时不需要的时钟是disable的,需要的时候enable

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

//request_mem_region函数并没有做实际性的映射工作,只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源
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;

/* inititalise the i2c gpio lines */
if (i2c->pdata->cfg_gpio) {
i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
} else if (s3c24xx_i2c_parse_dt_gpio(i2c)) {
ret = -EINVAL;
goto err_iomap;
}

/* initialise the i2c controller */
ret = s3c24xx_i2c_init(i2c);
if (ret != 0) {
dev_err(&pdev->dev, "I2C controller init failed\n");
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, 0,
 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);//给i2c中注册一个cpufreq(变频通知机制)驱动
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 = i2c->pdata->bus_num;//The bus number to use。使用的总线地址
i2c->adap.dev.of_node = pdev->dev.of_node;//Associated device tree node
//使用静态总线号来声明IIC适配器
ret = i2c_add_numbered_adapter(&i2c->adap);//注册I2C的adapter,否则i2c设备无法被监测到
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
goto err_cpufreq;
}
//of_i2c_register_devices接口会解析i2c总线节点的子节点(挂载在该总线上的i2c设备节点),
//获取i2c设备的地址、中断号等硬件信息。
//然后调用request_module()加载设备对应的驱动文件,调用i2c_new_device(),生成i2c设备

of_i2c_register_devices(&i2c->adap);
platform_set_drvdata(pdev, i2c);

pm_runtime_enable(&pdev->dev);
pm_runtime_enable(&i2c->adap.dev);

dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
clk_disable(i2c->clk);//??为什么要disable clock??,知道的可以留言,谢谢了
return 0;

 err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);
 err_irq:
free_irq(i2c->irq, i2c);
 err_iomap:
if (i2c->gpios[0])
s3c24xx_i2c_dt_gpio_free(i2c);
iounmap(i2c->regs);
 err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
 err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);
 err_noclk:
return ret;
}

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->pdata;
/* write slave address */
/* 写入从设备的地址  */
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
/* 使能接收发送中断和I2C总线应答信号  */
writel(iicon, i2c->regs + S3C2410_IICCON);
/* we need to work out the divisors for the clock... */
/*这里freq用来获取实际的I2C时钟频率,具体指为97KHZ,后面会分析*/
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;
}


static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
{
struct s3c2410_platform_i2c *pdata = i2c->pdata;
unsigned long clkin = clk_get_rate(i2c->clk);/*从系统平台时钟队列中获取pclk的时钟频率,大小为50MHZ */
unsigned int divs, div1;
unsigned long target_frequency;
u32 iiccon;
int freq;
i2c->clkrate = clkin;
clkin /= 1000; /* clkin now in KHz */
dev_dbg(i2c->dev, "pdata desired frequency %lu\n", pdata->frequency);
target_frequency = pdata->frequency ? pdata->frequency : 100000;
target_frequency /= 1000; /* Target frequency now in KHz */
/* 目标频率在前面default_i2c_data0中frequency为100KHZ,根据PCLK和目标频率计算分频系数,计算后实际频率为97KHZ,即freq 为97K*/
freq = s3c24xx_i2c_calcdivisor(clkin, target_frequency, &div1, &divs);
if (freq > target_frequency) {
dev_err(i2c->dev,
"Unable to achieve desired frequency %luKHz." \
" Lowest achievable %dKHz\n", target_frequency, freq);
return -EINVAL;
}
*got = freq;/*通过传入的指针返回实际频率 */
/* 根据时钟选择和分频系数配置对应硬件寄存器 */
iiccon = readl(i2c->regs + S3C2410_IICCON);
iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512);
iiccon |= (divs-1);
if (div1 == 512)
iiccon |= S3C2410_IICCON_TXDIV_512;
writel(iiccon, i2c->regs + S3C2410_IICCON);
/*  判断是否为S3C2440  */
if (i2c->quirks & QUIRK_S3C2440) {
unsigned long sda_delay;
if (pdata->sda_delay) {
sda_delay = clkin * pdata->sda_delay;
sda_delay = DIV_ROUND_UP(sda_delay, 1000000);
sda_delay = DIV_ROUND_UP(sda_delay, 5);
if (sda_delay > 3)
sda_delay = 3;
sda_delay |= S3C2410_IICLC_FILTER_ON;
} else
sda_delay = 0;
dev_dbg(i2c->dev, "IICLC=%08lx\n", sda_delay);
writel(sda_delay, i2c->regs + S3C2440_IICLC);
}
return 0;
}


你可能感兴趣的:(iic总线驱动(适配器驱动)详解)