zoukankan      html  css  js  c++  java
  • TQ2440学习笔记——Linux上I2C驱动的两种实现方法(1)

     

    作者:彭东林

    邮箱:pengdonglin137@163.com


    内核版本:Linux-3.14

    u-boot版本:U-Boot 2015.04

    硬件:TQ2440 (NorFlash:2M   NandFlash:256M  内存:64M)

     

    摘要

    这里并不深入分析Linux下I2C驱动的实现,只是以TQ2440硬件平台为例分析I2C驱动的两种方法。

    第一种方法:

    使用S3C2440自带的I2C控制器实现,这个kernel已经支持,我们只需要配置即可。

    第二种方法:

    使用GPIO模拟,这个在kernel中已经集成,实现代码在drivers/i2c/busses/i2c-gpio.c。

    在TQ2440平台上有一个EEPROM,型号是:AT24C02C。我们就以驱动at24c02c为例。

    硬件原理

    EEPROM:

    image

     

    引脚:

    image

     

    可以看到:

    I2CSDA  ---- GPE15

    I2CSCL  ---- GPE14

    实现

    下面开始介绍这两种方法。

    方法一、使用I2C控制器实现

    kernel已经提供了这种实现,我们需要做的仅仅是配置,此外,kernel中也提供了at24c02的驱动,我们也是只要配置就行。

    驱动框架

    image

    配置I2C控制器驱动

    文件:arch/arm/mach-s3c24xx/mach-tq2440.c:

       1: static struct platform_device *tq2440_devices[] __initdata = {
       2:     ......
       3:     &s3c_device_i2c0,
       4:     ......
       5: };
       6:  
       7: static void __init tq2440_machine_init(void)
       8: {
       9:     .......
      10:     platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices));
      11:     ......
      12: }
      13:  
      14: MACHINE_START(TQ2440, "TQ2440")
      15:     .atag_offset    = 0x100,
      16:     .init_irq    = s3c2440_init_irq,
      17:     .map_io        = tq2440_map_io,
      18:     .init_machine    = tq2440_machine_init,
      19:     .init_time    = samsung_timer_init,
      20:     .restart    = s3c244x_restart,
      21: MACHINE_END

    第3行的变量s3c_device_i2c0是Samsung已经定义好的,代码位置 arch/arm/plat-samsung/devs.c

       1: static struct resource s3c_i2c0_resource[] = {
       2:     [0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),
       3:     [1] = DEFINE_RES_IRQ(IRQ_IIC),
       4: };
       5:  
       6: struct platform_device s3c_device_i2c0 = {
       7:     .name        = "s3c2410-i2c",
       8:     .id        = 0,
       9:     .num_resources    = ARRAY_SIZE(s3c_i2c0_resource),
      10:     .resource    = s3c_i2c0_resource,
      11: };
      12:  
      13: struct s3c2410_platform_i2c default_i2c_data __initdata = {
      14:     .flags        = 0,
      15:     .slave_addr    = 0x10,
      16:     .frequency    = 100*1000,
      17:     .sda_delay    = 100,
      18: };

    第13行的结构体default_i2c_data的存放的是I2C的时序信息。

    配置好上面,那么kernel在启动时就会将I2C控制器的硬件信息注册到系统。此外,还需要配置I2C控制器驱动。

    代码位置:drivers/i2c/busses/i2c-s3c2410.c

       1: static struct platform_device_id s3c24xx_driver_ids[] = {
       2:     {
       3:         .name        = "s3c2410-i2c",
       4:         .driver_data    = 0,
       5:     }, {
       6:         .name        = "s3c2440-i2c",
       7:         .driver_data    = QUIRK_S3C2440,
       8:     }, {
       9:         .name        = "s3c2440-hdmiphy-i2c",
      10:         .driver_data    = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
      11:     }, { },
      12: };
      13: MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
      14:  
      15: /* device driver for platform bus bits */
      16:  
      17: static struct platform_driver s3c24xx_i2c_driver = {
      18:     .probe        = s3c24xx_i2c_probe,
      19:     .remove        = s3c24xx_i2c_remove,
      20:     .id_table    = s3c24xx_driver_ids,
      21:     .driver        = {
      22:         .owner    = THIS_MODULE,
      23:         .name    = "s3c-i2c",
      24:         .pm    = S3C24XX_DEV_PM_OPS,
      25:         .of_match_table = of_match_ptr(s3c24xx_i2c_match),
      26:     },
      27: };
      28:  
      29: static int __init i2c_adap_s3c_init(void)
      30: {
      31:     return platform_driver_register(&s3c24xx_i2c_driver);
      32: }
      33: subsys_initcall(i2c_adap_s3c_init);
      34:  
      35: static void __exit i2c_adap_s3c_exit(void)
      36: {
      37:     platform_driver_unregister(&s3c24xx_i2c_driver);
      38: }
      39: module_exit(i2c_adap_s3c_exit);

    第20行里的id_table存放的是要匹配的名称。

    在drivers/i2c/busses/Makefile中:

    obj-$(CONFIG_I2C_S3C2410)    += i2c-s3c2410.o

    所以需要配置CONFIG_I2C_S3C2410

    make menuconfig

    然后搜索CONFIG_I2C_S3C2410

    image

    可以看到下面红色框住的地方,意思是我们直接点击键盘上的1就可以跳转到搜索到的地方。

    image

    下图就是搜索到的位置:

    image

    至此,I2C控制器驱动(即I2C adapter驱动)就配置完成了。

    EEPROM驱动配置

    在内核中已经支持了at24c02c的驱动,我们需要做的是填充at24c02c的硬件信息。

    文件arch/arm/mach-s3c24xx/mach-tq2440.c:

       1: /* AT24C02 */
       2: static struct at24_platform_data tq2440_platform_data = {
       3:     .byte_len = SZ_1K*2/8,
       4:     .page_size = 8,
       5:     .flags = AT24_FLAG_TAKE8ADDR,
       6: };
       7:  
       8: static struct i2c_board_info tq2440_at24c02[] = {
       9:     {
      10:         I2C_BOARD_INFO("24c02", 0x50),
      11:         .platform_data = &tq2440_platform_data,
      12:     },
      13: };
      14:  
      15: static void __init tq2440_machine_init(void)
      16: {
      17:     ......
      18:     i2c_register_board_info(0, tq2440_at24c02, 1);
      19:     ......
      20: }
      21:  
      22: MACHINE_START(TQ2440, "TQ2440")
      23:     /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
      24:     .atag_offset    = 0x100,
      25:  
      26:     .init_irq    = s3c2440_init_irq,
      27:     .map_io        = tq2440_map_io,
      28:     .init_machine    = tq2440_machine_init,
      29:     .init_time    = samsung_timer_init,
      30:     .restart    = s3c244x_restart,
      31: MACHINE_END

    第18行注册at24c02的板级信息(硬件信息的填写可以参考at24c02驱动解析板级信息的过程),可以看到,使用的busnum是0,即i2c_adapter的编号。

    接下来需要配置at24c02的驱动:drivers/misc/eeprom/at24.c

       1: static const struct i2c_device_id at24_ids[] = {
       2: ......
       3:     { "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
       4:     { "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
       5:     ......
       6:     { "at24", 0 },
       7: };
       8: MODULE_DEVICE_TABLE(i2c, at24_ids);
       9:  
      10: static struct i2c_driver at24_driver = {
      11:     .driver = {
      12:         .name = "at24",
      13:         .owner = THIS_MODULE,
      14:     },
      15:     .probe = at24_probe,
      16:     .remove = at24_remove,
      17:     .id_table = at24_ids,
      18: };
      19:  
      20: static int __init at24_init(void)
      21: {
      22:     if (!io_limit) {
      23:         pr_err("at24: io_limit must not be 0!
    ");
      24:         return -EINVAL;
      25:     }
      26:  
      27:     io_limit = rounddown_pow_of_two(io_limit);
      28:     return i2c_add_driver(&at24_driver);
      29: }
      30: module_init(at24_init);

    在drivers/misc/eeprom/Makefile中:

    obj-$(CONFIG_EEPROM_AT24)    += at24.o

    所以需要配置CONFIG_EEPROM_AT24,搜索过程前面已经说过。

    image

    至此,就配置完成了,重新编译内核。

    验证

       1: [root@TQ2440 /]# cd /sys/bus/i2c/drivers/at24/0-0050/
       2: [root@TQ2440 0-0050]# ls
       3: driver     eeprom     modalias   name       power      subsystem  uevent
       4: [root@TQ2440 0-0050]# dd if=/dev/zero of=eeprom 
       5: ^C
       6: [root@TQ2440 0-0050]# cat eeprom 
       7: [root@TQ2440 0-0050]# echo "pengdonglin" > eeprom 
       8: [root@TQ2440 0-0050]# cat eeprom 
       9: pengdonglin
      10: [root@TQ2440 0-0050]# 

     

    方法二、使用GPIO模拟

    这部分在kernel中也已经支持了,我们只需要配置即可。

    驱动框架

    image

     

    跟上面的对比,EEPROM驱动不用修改,配置方法参考方法一,我们需要配置的只是将原来的I2C控制器驱动替换为I2C-GPIO驱动即可,其实kernel是利用这个两个GPIO引脚(一个是SDA(对应GPIOE15),另一个是SCL(对应GPIOE14))模拟出来一个I2C控制器,然后将这个控制器注册到系统,这一点对于EEPROM驱动来说是完全透明的,这里需要保证使用GPIO模拟出来的I2C控制器的编号必须跟at24c02注册板级信息时指定的busnum一致,而且不能再注册方法一中的I2C控制器硬件信息了,否则会冲突。

    配置I2C_GPIO硬件信息

    这个主要是告诉I2C_GPIO控制器使用哪两个GPIO作为SDA和SCL,如何填写配置信息可以参考i2c-gpio.c解析配置信息的过程。

    文件 arch/arm/mach-s3c24xx/mach-tq2440.c:

       1: static struct i2c_gpio_platform_data s3c_gpio_i2c_platdata = {
       2:     .sda_pin = S3C2410_GPE(15),
       3:     .scl_pin = S3C2410_GPE(14),
       4:     //.sda_is_open_drain = 1,
       5:     //.scl_is_open_drain = 1,
       6:     //.scl_is_output_only = 1,
       7:     //.udelay = 100,  // 低电平和高电平的持续时间都是100us,那么频率就是5KHz
       8: };
       9:  
      10: struct platform_device s3c_device_gpio_i2c = {
      11:     .name        = "i2c-gpio",
      12:     .id            = 0,
      13:     .dev            = {
      14:         .platform_data = &s3c_gpio_i2c_platdata,
      15:     }
      16: };
      17: #endif
      18:  
      19: static struct platform_device *tq2440_devices[] __initdata = {
      20:     ......
      21: #ifdef CONFIG_TQ2440_EEPROM_USE_GPIO_I2C
      22:     &s3c_device_gpio_i2c,
      23: #else
      24:     &s3c_device_i2c0,
      25: #endif
      26:     ......
      27: };
      28:  
      29: static void __init tq2440_machine_init(void)
      30: {
      31:     ......
      32:     platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices));
      33:     i2c_register_board_info(0, tq2440_at24c02, 1);
      34:     ......
      35: }
      36:  
      37: MACHINE_START(TQ2440, "TQ2440")
      38:     /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
      39:     .atag_offset    = 0x100,
      40:  
      41:     .init_irq    = s3c2440_init_irq,
      42:     .map_io        = tq2440_map_io,
      43:     .init_machine    = tq2440_machine_init,
      44:     .init_time    = samsung_timer_init,
      45:     .restart    = s3c244x_restart,
      46: MACHINE_END

    第1行结构体s3c_gpio_i2c_platdata中指定了要用的GPIO引脚,以及udelay信息,用于控制I2C通信时的时序,因为SCL拉高拉低都需要延迟一段时间等待数据稳定;

    第12行中的id表示的模拟出的i2c控制器的编号;

    配置I2C-GPIO驱动

    文件drivers/i2c/busses/i2c-gpio.c:

       1: static struct platform_driver i2c_gpio_driver = {
       2:     .driver        = {
       3:         .name    = "i2c-gpio",
       4:         .owner    = THIS_MODULE,
       5:         .of_match_table    = of_match_ptr(i2c_gpio_dt_ids),
       6:     },
       7:     .probe        = i2c_gpio_probe,
       8:     .remove        = i2c_gpio_remove,
       9: };
      10:  
      11: static int __init i2c_gpio_init(void)
      12: {
      13:     int ret;
      14:  
      15:     ret = platform_driver_register(&i2c_gpio_driver);
      16:     if (ret)
      17:         printk(KERN_ERR "i2c-gpio: probe failed: %d
    ", ret);
      18:  
      19:     return ret;
      20: }

     

    文件drivers/i2c/busses/Makefile:

    obj-$(CONFIG_I2C_GPIO)        += i2c-gpio.o

    所以需要配置CONFIG_I2C_GPIO。

    image

     

    至此,就配置完成了,重新编译内核。

    验证

       1: [root@TQ2440 /]# cd /sys/bus/i2c/drivers/at24/0-0050/
       2: [root@TQ2440 0-0050]# ls
       3: driver     eeprom     modalias   name       power      subsystem  uevent
       4: [root@TQ2440 0-0050]# echo "pengdonglin137@163.com" > eeprom 
       5: [root@TQ2440 0-0050]# cat eeprom 
       6: pengdonglin137@163.com
       7: [root@TQ2440 0-0050]# 

     

    完!

  • 相关阅读:
    OutOfMemoryError异常
    synchronized四种锁状态的升级
    题解LeetCode--三数之和
    JDK 8的HashMap源码解析
    LinkedList原理分析
    队列与LinkedList原理实现
    Java中的递归以及不死神兔实例(斐波那契数列)
    递归问题1
    排序的第二天_快速排序与归并排序
    yum源遇到的问题
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/4623169.html
Copyright © 2011-2022 走看看