I2C驱动的框架实现分析

前面讲了一下I2C的基本概念以及一个怎么去运用它的一个流程,但是没有做具体的分析,这篇博客转自http://m.blog.csdn.net/Guet_Kite/article/details/77870905,写得很明了,于是忍不住就转载啦。

这里写图片描述

首先这张图片很好的说明了我们内核的I2C驱动的一个框架,现在从上到下看起,先看我们的平台文件:mach-smdk2440.c,这里内核为3.0.54版本。

static struct platform_device *smdk2440_devices[] __initdata = {
     &s3c_device_ohci,
     &s3c_device_lcd,
     &s3c_device_wdt,
     &s3c_device_i2c0,
     &s3c_device_iis,
     &s3c_device_dm9000,
     &s3c_device_adc,
     &s3c_device_ts,
     &uda1340_codec,
     &s3c24xx_uda134x,
     &samsung_asoc_dma,
     &s3c_device_timer,
     &smdk2440_beeper_device,
     &s3c_device_rtc,
     &s3c_device_usbgadget,
     &s3c_device_sdi,
 };

static void __init smdk2440_map_io(void)
 {
     s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
     s3c24xx_init_clocks(12000000);
     s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
     usb_s3c2440_init();
 }

static void __init smdk2440_machine_init(void)
 {
     s3c24xx_fb_set_platdata(&smdk2440_fb_info);
     s3c_i2c0_set_platdata(NULL);
     s3c24xx_ts_set_platdata(&smdk2440_ts_cfg);
     gpio_request(S3C2410_GPB(0),"beeper");
     s3c_gpio_setpull(S3C2410_GPB(0), S3C_GPIO_PULL_NONE);
     s3c_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPB0_TOUT0);
     i2c_register_board_info(0, smdk2440_i2c_devs, ARRAY_SIZE(smdk2440_i2c_devs));
     platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
     smdk_machine_init();
     s3c24xx_udc_set_platdata(&s3c_udc_cfg);
 }

在smdk2440_map_io函数里面,调用了s3c24xx_init_io函数:

void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
 {
     unsigned long idcode = 0x0;

     /* initialise the io descriptors we need for initialisation */
     iotable_init(mach_desc, size);
     iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));

     if (cpu_architecture() >= CPU_ARCH_ARMv5) {
         idcode = s3c24xx_read_idcode_v5();
     } else {
         idcode = s3c24xx_read_idcode_v4();
     }

     arm_pm_restart = s3c24xx_pm_restart;

     s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
 }   

其中s3c_init_cpu的第二个参数cpu_ids在:

static struct cpu_table cpu_ids[] __initdata = {
      {
          .idcode     = 0x32440000,
          .idmask     = 0xffffffff,
          .map_io     = s3c2440_map_io,
          .init_clocks    = s3c244x_init_clocks,
          .init_uarts = s3c244x_init_uarts,
          .init       = s3c2440_init,
          .name       = name_s3c2440
      },
         ................
}

因为我们的板子是2440的,因此就截取了这个字段。然后继续追踪s3c_init_cpu函数:

void __init s3c_init_cpu(unsigned long idcode,
               struct cpu_table *cputab, unsigned int cputab_size)
  {       
      cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);

      if (cpu == NULL) {
          printk(KERN_ERR "Unknown CPU type 0x%08lx\n", idcode);
          panic("Unknown S3C24XX CPU");
      }

      printk("CPU %s (id 0x%08lx)\n", cpu->name, idcode);

      if (cpu->map_io == NULL || cpu->init == NULL) {
          printk(KERN_ERR "CPU %s support not enabled\n", cpu->name);
          panic("Unsupported Samsung CPU");
      }

      cpu->map_io();
  }       

可以看见最后一行调用了map_io函数,就是前面的s3c2440_map_io函数。这里在刚才的cpu_table结构体中说明的。

void __init s3c2440_map_io(void)
  {       
      s3c244x_map_io(); 

      s3c24xx_gpiocfg_default.set_pull = s3c_gpio_setpull_1up;
      s3c24xx_gpiocfg_default.get_pull = s3c_gpio_getpull_1up;
  }  

自然进入s3c24x_map_io函数:

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_sdi.name  = "s3c2440-sdi";
      s3c_device_i2c0.name  = "s3c2440-i2c";
      s3c_nand_setname("s3c2440-nand");
      s3c_device_ts.name = "s3c2440-ts";
      s3c_device_usbgadget.name = "s3c2440-usbgadget";
  }   

最终在这里将s3c_device_i2c0结构体的名字改为了s3c2440-i2c,然后回到我们的平台文件mach-smdk2440.c文件中,进入smdk2440_machine_init函数,这里通过s3c_i2c0_set_platdata函数,设置default_i2c_data结构体的bus_num为0,以及设置i2c的IO口,npd->cfg_gpio = s3c_i2c0_cfg_gpio,接着就调用paltform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)),在platform平台下进行设备注册,设备名字为“s3c2440-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_device_i2c0.dev.platform_data = npd;
  }   

到这里,平台设备已经注册完毕,接下来就是驱动咯:i2c-s3c2410.c

static struct platform_device_id s3c24xx_driver_ids[] = {
     {
         .name       = "s3c2410-i2c",
         .driver_data    = TYPE_S3C2410,
     }, {
         .name       = "s3c2440-i2c",
         .driver_data    = TYPE_S3C2440,
     }, { },
 };
 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);
 }

然后可以看见我们的名字“s3c2440-i2c”,和上面的设备匹配成功,调用probe函数:

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
    ....................
    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;
      .................
      ret = i2c_add_numbered_adapter(&i2c->adap);
}

因为代码太长,这里给出部分重要信息代码。

i2c->adap.algo = &s3c24xx_i2c_algorithm;

其中:

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
      .master_xfer        = s3c24xx_i2c_xfer,
      .functionality      = s3c24xx_i2c_func,
  };

正如开始的图,i2c_adapter和i2c_algorithm都是操作i2c_bus的结构体,前者定义一个i2c模块,后者顶一个操作模块的方法,或者前者对应于物理上的一个适配器,而后者对应一套通信方法。

i2c_algorithm是i2c的底层实现函数,已封装,.master_xfer用于I2C总线传输,传送给它的i2c_msg数组中每个I2C消息,.functionality用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE等。

ret=i2c_add_numbered_adapter(&i2c->ddap);

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
  {       
     int id;
      int status;

      if (adap->nr & ~MAX_ID_MASK)
          return -EINVAL;

  retry:
      if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
          return -ENOMEM;

      mutex_lock(&core_lock);
      /* "above" here means "above or equal to", sigh;
       * we need the "equal to" result to force the result
       */
      status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
      if (status == 0 && id != adap->nr) {
          status = -EBUSY;
          idr_remove(&i2c_adapter_idr, id);
      }
      mutex_unlock(&core_lock);
      if (status == -EAGAIN)
          goto retry;

      if (status == 0)
          status = i2c_register_adapter(adap);
      return status;
  }

可以看见最后调用i2c_register_adapter(adap)函数,在i2c_bus总线上进行注册,名字为dev_set_name(&adap->dev, “i2c-%d”, adap->nr); 再说下i2c_adapter与i2c_client的关系:

i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个I2C设备,所以一个i2c_adapter也可以被多个i2c_client依附,i2c_adapter中包含依附于它的i2c_client的链表。

i2c的框架就到这里,下面以我们的eeprom为例看下i2c的实现:

static struct i2c_driver at24_driver = {
     .driver = {
         .name = "at24",
         .owner = THIS_MODULE,
     },
     .probe = at24_probe,
     .remove = __devexit_p(at24_remove),
    .id_table = at24_ids,
 };

 static int __init at24_init(void)
 {
     if (!io_limit) {
         pr_err("at24: io_limit must not be 0!\n")
         return -EINVAL;
     }

     io_limit = rounddown_pow_of_two(io_limit);
     return i2c_add_driver(&at24_driver);
 }

这里调用i2c_add_driver函数在i2c_bus总线下注册,然后看下他的读写函数,

static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
         unsigned offset, size_t count)
 {
     struct i2c_msg msg[2];
     u8 msgbuf[2];
     struct i2c_client *client;
     unsigned long timeout, read_time;
     int status, i;

     memset(msg, 0, sizeof(msg));
     client = at24_translate_offset(at24, &offset);

     if (count > io_limit)
         count = io_limit;

     switch (at24->use_smbus) {
     case I2C_SMBUS_I2C_BLOCK_DATA:
        /* Smaller eeproms can work given some SMBus extension calls */
         if (count > I2C_SMBUS_BLOCK_MAX)
             count = I2C_SMBUS_BLOCK_MAX;
         break;
     case I2C_SMBUS_WORD_DATA:
         count = 2;
         break;
     case I2C_SMBUS_BYTE_DATA:
         count = 1;
         break
     default:
     i = 0;
         if (at24->chip.flags & AT24_FLAG_ADDR16)
             msgbuf[i++] = offset >> 8;
         msgbuf[i++] = offset;

         msg[0].addr = client->addr;
         msg[0].buf = msgbuf;
         msg[0].len = i;

         msg[1].addr = client->addr;
         msg[1].flags = I2C_M_RD;
         msg[1].buf = buf;
         msg[1].len = count
     }
     timeout = jiffies + msecs_to_jiffies(write_timeout);
     do {
         read_time = jiffies;
         switch (at24->use_smbus) {  
            case I2C_SMBUS_I2C_BLOCK_DATA:
             status = i2c_smbus_read_i2c_block_data(client, offset,
                     count, buf);
             break;
         case I2C_SMBUS_WORD_DATA:
             status = i2c_smbus_read_word_data(client, offset);
             if (status >= 0) {
                 buf[0] = status & 0xff;
                buf[1] = status >> 8;
                 status = count;
             }
             break;
         case I2C_SMBUS_BYTE_DATA:
             status = i2c_smbus_read_byte_data(client, offset);
             if (status >= 0) {
                 buf[0] = status;
                 status = count;
             }
            break;
         default:
         status = i2c_transfer(client->adapter, msg, 2);
             if (status == 2)
                 status = count;
         }
         dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
                 count, offset, status, jiffies);

         if (status == count)
             return count;

         /* REVISIT: at HZ=100, this is sloooow */
         msleep(1);
     } while (time_before(read_time, timeout));

     return -ETIMEDOUT;
 }

这里面调用i2c_transfer函数,里面以i2c_msg为单位通信:代码就不搬上来了这里就是ret=adap->algo->master_xfer(adap, msgs, num); 这个就是前面的.master_xfer函数,实现i2c总线传输函数。

猜你喜欢

转载自blog.csdn.net/Peter_tang6/article/details/77880770
今日推荐