Chinaunix首页 | 论坛 | 博客
  • 博客访问: 213773
  • 博文数量: 78
  • 博客积分: 3169
  • 博客等级: 中校
  • 技术积分: 805
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-25 13:50
文章分类
文章存档

2012年(1)

2011年(77)

分类: LINUX

2011-04-19 14:58:58

内核版本:2.6.31.6

 

首先在S3C2440平台的初始化函数中,主要是将开发平台的设备注册进了系统,也就是将device注册到了platform虚拟的总线上,并进行了一些初始化的工作,这里我们只关注I2C的部分。

 

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_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_data0;

 

       npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);

       if (!npd)

              printk(KERN_ERR "%s: no memory for platform data\n", __func__);

       else if (!npd->cfg_gpio)

              npd->cfg_gpio = s3c_i2c0_cfg_gpio;

       /* s3c_i2c0_cfg_gpio 配置I2C控制器GPIO函数指针 */

       s3c_device_i2c0.dev.platform_data = npd;

    /*最后将struct device 中的platform_data指针直指向了初始化后的 s3c2410_platform_i2c结构体  */

}

 

函数s3c_i2c0_cfg_gpio()很简单,实际上就是配置GPIOI2C的工作模式

 

void s3c_i2c0_cfg_gpio(struct platform_device *dev)

{

       s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);

       s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);

}

s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)函数实际上就是把初始化数据段中的default_i2c_data0结构体复制过来,然后对GPIO进行配置的函数指针进行了初始化。default_i2c_data0结构体如下:

static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {

       .flags             = 0,

       .slave_addr     = 0x10,

       .frequency      = 100*1000,

       .sda_delay      = 100,

};

 

s3c2410_platform_i2c结构体原型如下,根据英文注释即可大致理解其意思

/**

 *    struct s3c2410_platform_i2c - Platform data for s3c I2C.

 *    @bus_num: The bus number to use (if possible).

 *    @flags: Any flags for the I2C bus (E.g. S3C_IICFLK_FILTER).

 *    @slave_addr: The I2C address for the slave device (if enabled).

 *    @frequency: The desired frequency in Hz of the bus.  This is

 *                  guaranteed to not be exceeded.  If the caller does

 *                  not care, use zero and the driver will select a

 *                  useful default.

 *    @sda_delay: The delay (in ns) applied to SDA edges.

 *    @cfg_gpio: A callback to configure the pins for I2C operation.

 */

struct s3c2410_platform_i2c {

       int           bus_num;

       unsigned int    flags;

       unsigned int    slave_addr;

       unsigned long  frequency;

       unsigned int    sda_delay;

 

       void (*cfg_gpio)(struct platform_device *dev);

};

 

在函数smdk2440_machine_init(void)中,调用了

platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

即将smdk2440_devices结构体数组中platform_device添加到了系统中,也就是添加到了platform总线上。smdk2440_devices的具体内容如下:

 

static struct platform_device *smdk2440_devices[] __initdata = {

       &s3c_device_usb,

       &s3c_device_lcd,

       &s3c_device_wdt,

       &s3c_device_i2c0,

       &s3c_device_iis,

       &s3c_device_dm9000,

       &s3c_device_rtc,

};

 

其中s3c_device_i2c0保存了S3C2440中的I2C控制器的一些内部资源等信息,具体内容如下:

struct platform_device s3c_device_i2c0 = {

  .name             = "s3c2410-i2c",     

/*设备名,platform总线的match函数中会用设备名和驱动名的比较来绑定设备和驱动程序*/

#ifdef CONFIG_S3C_DEV_I2C1

       .id             = 0,

#else

       .id             = -1,

#endif

       .num_resources       = ARRAY_SIZE(s3c_i2c_resource),

       .resource   = s3c_i2c_resource,

};

 

其中s3c_i2c_resource结构体保存了S3C2440I2C控制器寄存器的物理地址和中断号等具体的硬件信息。

 

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,

       },

};

在后面注册具体设备驱动时也会添加到paltform总线上,platform总线会将具体的设备和驱动进行绑定,这样驱动就可以操作具体的设备了。platform实际上是一个虚拟的总线,本质上也是一个设备。

 

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

 

S3C2440平台上的I2C的驱动程序在linux/drivers/i2c/busses/i2c-s3c2410.c文件中,

在驱动的加载程序中,将platform_driver类型的s3c24xx_i2c_driver注册到了系统中。

static int __init i2c_adap_s3c_init(void)

{

       return platform_driver_register(&s3c24xx_i2c_driver);

}

 

分析platform_driver_register(&s3c24xx_i2c_driver);的源代码可知,实际上是将s3c24xx_i2c_driver注册到了platform总线上。

int platform_driver_register(struct platform_driver *drv)

{

       drv->driver.bus = &platform_bus_type;

/*device_driver中的proberemoveshutdown函数指针指向platform_driver中的函数,后面进行驱动和设备绑定后会调用probe函数 */

       if (drv->probe)

              drv->driver.probe = platform_drv_probe;

       if (drv->remove)

              drv->driver.remove = platform_drv_remove;

       if (drv->shutdown)

              drv->driver.shutdown = platform_drv_shutdown;

 

       return driver_register(&drv->driver);

}

 

下图即为Linux 2.6中引入的设备驱动模型的结构图(只是个总体框架,并不是指这的platform总线,设备和驱动)。

 

 

 

 

总线上包括设备和驱动的集合,总线上所有设备组成双向循环链表,包含在platform_device的设备集合中,总线上所有驱动组成双向循环链表,包含在platform_dirver的驱动集合中。

platform_driver_register(struct platform_driver *drv)函数实际上是对driver_register(struct device_driver *drv)函数的一个简单封装。driver_register()函数的调用关系如下

 

driver_register()

>bus_add_driver(drv);

       —> driver_attach(drv);

        —> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

 

bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)函数会遍历总线上所有的设备,并调用__driver_attach函数,判断驱动是否和设备匹配,若匹配则将struct device中的 struct device_driver *driver指向此驱动,也就是进行了驱动和设备的绑定,若不匹配,则继续遍历下一个设备。事实上,在向总线注册设备时,同样会进行类似的操作,遍历总线上所有驱动程序,找到则进行设备与驱动程序的绑定。

static int __driver_attach(struct device *dev, void *data)

{

       struct device_driver *drv = data;

       /*

        * Lock device and try to bind to it. We drop the error

        * here and always return 0, because we need to keep trying

        * to bind to devices and some drivers will return an error

        * simply if it didn't support the device.

        *

        * driver_probe_device() will spit a warning if there

        * is an error.

        */

/*调用platform总线的match()函数,即platform_match函数,判断设备和驱动是否匹配,若匹配则返真,找到对应的设备,继续执行后面的程序,若没有找到,则返回假,函数执行结束 。这里我们的I2C驱动找到了可以驱动的设备,所以会继续执行*/

       if (!driver_match_device(drv, dev))

              return 0;

       if (dev->parent)      /* Needed for USB */

              down(&dev->parent->sem);

       down(&dev->sem);

/*设备是否已经找到驱动?显然,这里没有找到驱动,因为设备在向系统中platform总线注册时还没有驱动注册到platform总线上,所以dev->drive = NULL */

       if (!dev->driver)

              driver_probe_device(drv, dev);

       up(&dev->sem);

       if (dev->parent)            

up(&dev->parent->sem);

 

       return 0;

}

 

driver_probe_device(drv, dev)函数进行驱动与设备的绑定。

/**

 * driver_probe_device - attempt to bind device & driver together

 * @drv: driver to bind a device to

 * @dev: device to try to bind to the driver

 *

 * This function returns -ENODEV if the device is not registered,

 * 1 if the device is bound sucessfully and 0 otherwise.

 *

 * This function must be called with @dev->sem held.  When called for a

 * USB interface, @dev->parent->sem must be held as well.

 */

int driver_probe_device(struct device_driver *drv, struct device *dev)

{

       int ret = 0;

 

       if (!device_is_registered(dev))   //判断设备是否已经注册

              return -ENODEV;

 

       pr_debug("bus: '%s': %s: matched device %s with driver %s\n",

               drv->bus->name, __func__, dev_name(dev), drv->name);

       ret = really_probe(dev, drv);

 

       return ret;

}

 

really_probe函数中 进行devicedriver的绑定,并调用用户在device_driver 中注册的probe()例程。

static int really_probe(struct device *dev, struct device_driver *drv)

{

       int ret = 0;

 

       atomic_inc(&probe_count);

       pr_debug("bus: '%s': %s: probing driver %s with device %s\n",

               drv->bus->name, __func__, drv->name, dev_name(dev));

       WARN_ON(!list_empty(&dev->devres_head));

 

/*device中的device_driver指针指向了这个driver,即完成devicedriver的绑定*/

dev->driver = drv; 

f (driver_sysfs_add(dev)) {

              printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",

                     __func__, dev_name(dev));

              goto probe_failed;

       }

/*若总线设置了probe函数,则调用总线的probe函数,然而platform总线并没有设置 */

       if (dev->bus->probe) {

              ret = dev->bus->probe(dev);

              if (ret)

                     goto probe_failed;

       }

/* 否则,调用驱动注册在device_driver里的probe,这个函数中一般进行获得硬件资源,初始化硬件等操作,这里实际调用了s3c24xx_i2c_probe函数*/

else if (drv->probe) {

              ret = drv->probe(dev);

              if (ret)

                     goto probe_failed;

       }

/*将设备添加到driver所支持的设备列表中(因为一个驱动可以支持多个设备),并通知bus上的设备,表明BUS_NOTIFY_BOUND_DRIVER   */

       driver_bound(dev);

       ret = 1;

       pr_debug("bus: '%s': %s: bound device %s to driver %s\n",

               drv->bus->name, __func__, dev_name(dev), drv->name);

       goto done;

 

probe_failed:

       devres_release_all(dev);

       driver_sysfs_remove(dev);

       dev->driver = NULL;

 

       if (ret != -ENODEV && ret != -ENXIO) {

              /* driver matched but the probe failed */

              printk(KERN_WARNING

                     "%s: probe of %s failed with error %d\n",

                     drv->name, dev_name(dev), ret);

       }

       /*

        * Ignore errors returned by ->probe so that the next driver can try

        * its luck.

        */

       ret = 0;

done:

       atomic_dec(&probe_count);

       wake_up(&probe_waitqueue);

       return ret;

}

 

到这里,I2C设备软件层次上的驱动模型已经建立好了,接着会执行s3c24xx_i2c_probe函数,获取系统开始注册的一些硬件资源信息,进行硬件上的一些操作,以及真正的涉及到数据传输驱动程序的注册等操作。

下面开始分析linux/drivers/i2c/busses/i2c-s3c2410.c,在设备与驱动匹配成功后,会执行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;

/*这里pdev->dev.platform_data s3c_i2c0_set_platdata()函数中设置,指向了系统初始化时的设置过的s3c2410_platform_i2c结构体*/

       pdata = pdev->dev.platform_data;

       if (!pdata) {

              dev_err(&pdev->dev, "no platform data\n");

              return -EINVAL;

       }

    /*申请一段sizeof(struct s3c24xx_i2c)的内存,并清0 */

       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总线的通信函数 */

       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设备的时钟,并使能I2C控制器时钟,后面会具体分析*/

       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总线(总线号)注册adapter */

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

       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;

}

 

系统在初始化时会将系统硬件中的时钟注册进系统,用双向循环连接起来,在linux/arch/arm/plat-s3c24xx/s3c244x.c中,s3c244x_init_clocks ()函数完成这个操作这个操作。

 

void __init s3c244x_init_clocks(int xtal)

{

       /* initialise the clocks here, to allow other things like the

        * console to use them, and to add new ones after the initialisation

        */

 

       s3c24xx_register_baseclocks(xtal);

       s3c244x_setup_clocks();

       s3c2410_baseclk_add();

}

其中s3c2410_baseclk_add()的源码如下:

/* s3c2410_baseclk_add()

 * Add all the clocks used by the s3c2410 or compatible CPUs

 * such as the S3C2440 and S3C2442.

 * We cannot use a system device as we are needed before any

 * of the init-calls that initialise the devices are actually

 * done.*/

int __init s3c2410_baseclk_add(void)

{

       unsigned long clkslow = __raw_readl(S3C2410_CLKSLOW);

       unsigned long clkcon  = __raw_readl(S3C2410_CLKCON);

       struct clk *clkp;

       struct clk *xtal;

       int ret;

       int ptr;

       clk_upll.enable = s3c2410_upll_enable;

       if (s3c24xx_register_clock(&clk_usb_bus) < 0)

              printk(KERN_ERR "failed to register usb bus clock\n");

       /* register clocks from clock array */

       clkp = init_clocks;

       for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {

              /* ensure that we note the clock state */

              clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0;

              ret = s3c24xx_register_clock(clkp);

              if (ret < 0) {

                     printk(KERN_ERR "Failed to register clock %s (%d)\n",

                            clkp->name, ret);

              }

       }

       /* We must be careful disabling the clocks we are not intending to

        * be using at boot time, as subsystems such as the LCD which do

        * their own DMA requests to the bus can cause the system to lockup

        * if they where in the middle of requesting bus access.

        *

        * Disabling the LCD clock if the LCD is active is very dangerous,

        * and therefore the bootloader should be careful to not enable

        * the LCD clock if it is not needed.

       */

       /* install (and disable) the clocks we do not need immediately */

       clkp = init_clocks_disable;

       for (ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) {

              ret = s3c24xx_register_clock(clkp);

              if (ret < 0) {

                     printk(KERN_ERR "Failed to register clock %s (%d)\n",

                            clkp->name, ret);

              }

              s3c2410_clkcon_enable(clkp, 0);

       }

       /* show the clock-slow value */

       xtal = clk_get(NULL, "xtal");

       printk("CLOCK: Slow mode (%ld.%ld MHz), %s, MPLL %s, UPLL %s\n",

              print_mhz(clk_get_rate(xtal) /

                      ( 2 * S3C2410_CLKSLOW_GET_SLOWVAL(clkslow))),

              (clkslow & S3C2410_CLKSLOW_SLOW) ? "slow" : "fast",

              (clkslow & S3C2410_CLKSLOW_MPLL_OFF) ? "off" : "on",

              (clkslow & S3C2410_CLKSLOW_UCLK_OFF) ? "off" : "on");

 

       s3c_pwmclk_init();

       return 0;

}

根据注释中给的提示,时钟被分成了两部分,init_clocksinit_clocks_disable,其中init_clocks中的时钟是系统启动时会开启的,而init_clocks_disable中的时钟则在系统启动时会关闭。其中函数s3c24xx_register_clock()就是实现讲系统中的时钟插入到双向循环链表中。比如我们这里I2C的时钟的是定义在init_clocks_disable数组中,定义如下:

{

              .name             = "i2c",

              .id           = -1,

              .parent           = &clk_p,

              .enable           = s3c2410_clkcon_enable,

              .ctrlbit     = S3C2410_CLKCON_IIC,

}

结构中保存了I2C控制器中时钟时能位的位置偏移,时钟名字已经时钟时能的函数等信息。

s3c24xx_i2c_probe函数中有一段程序就是用来获取时钟信息,并使能I2C时钟,即:

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

 

clk_get(&pdev->dev, "i2c")函数用于获取时钟信息,函数内部会将传入的“i2c”字符串和系统中各时钟的名字进行比较,看是否匹配,看上面的分析可知,I2C控制器时钟注册时的时钟名也是“i2c”,这个过程实际上和devicedriver的匹配过程是类似的。clk_get源码如下:

struct clk *clk_get(struct device *dev, const char *id)

{

       struct clk *p;

       struct clk *clk = ERR_PTR(-ENOENT);

       int idno;

       if (dev == NULL || dev->bus != &platform_bus_type)

              idno = -1;

       else

              idno = to_platform_device(dev)->id;

 

       spin_lock(&clocks_lock);

       list_for_each_entry(p, &clocks, list) {

              if (p->id == idno &&

                  strcmp(id, p->name) == 0 &&

                  try_module_get(p->owner)) {

                     clk = p;

                     break;

              }

       }

 

s3c24xx_i2c_probe函数还调用了s3c24xx_i2c_init(i2c)函数进行了S3C2440I2C控制器硬件上的初始化,源码如下:

/* s3c24xx_i2c_init initialise the controller, set the IO lines and frequency*/

 

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 (pdata->cfg_gpio)

              pdata->cfg_gpio(to_platform_device(i2c->dev));  /*I2C控制器IO的初始化

      

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

}

 

 

/* s3c24xx_i2c_clockrate

 *

 * work out a divisor for the user requested frequency setting,

 * either by the requested frequency, or scanning the acceptable

 * range of frequencies until something is found

*/

 

static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)

{

       struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data;

/*从系统平台时钟队列中获取pclk的时钟频率,大小为50MHZ */

       unsigned long clkin = clk_get_rate(i2c->clk);

       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_data0frequency100KHZ,根据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 (s3c24xx_i2c_is2440(i2c)) {

              unsigned long sda_delay;

 

              if (pdata->sda_delay) {

                     sda_delay = (freq / 1000) * pdata->sda_delay;

                     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;

}

 

       到这里,I2C控制器的硬件初始化操作基本上分析完了,接下来该分析Linux内核I2C总线的通信机制了~~

阅读(1264) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~