linux驱动中的platform总线架构(含具体IIC设备驱动)

转自: http://www.cnblogs.com/andrew-wang/archive/2012/12/17/2822345.html 

在设备驱动程序中经常会见到和platform相关的字段,分布在驱动程序的多个角落,这也是2.6内核中比较重要的一种机制,把它的原理弄懂了,对以后分析驱动程序很有帮助,下面简单介绍一下:

    在linux2.6设备模型中,关心总线,设备,驱动这三个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动。相反,在系统每注册一个驱动的时候,寻找与之匹配的设备,匹配是由总线来完成的。
    一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC 系统中集成的独立的外设控制器、挂接在SoC 内存空间的外设等确不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为platform 总线
    SOC系统中集成的独立外设单元(I2C,LCD,SPI,RTC等)都被当作平台设备来处理,而它们本身是字符型设备。
    从Linux2.6内核起,引入一套新的驱动管理和注册机制:platform_device 和 platform_driver 。Linux 中大部分的设备驱动,都可以使用这套机制,设备用 platform_device 表示;驱动用platform_driver 进行注册。
 
platform_device 结构体       include/linux/platform_device.h
  1. struct platform_device
  2. {
  3.     const char *name; //设备名
  4.     u32 id;
  5.     struct device dev;
  6.     u32 num_resources; //设备所使用的各类资源数量
  7.     struct resource *resource; //使用的资源
  8. }
platform_driver 结构体       include/linux/platform_device.h
  1. struct platform_driver
  2. {
  3.     int (*probe)(struct platform_device *);
  4.     int (*remove)(struct platform_device *);
  5.     void (*shutdown)(struct platform_device *);
  6.     int (*suspend)(struct platform_device *, pm_message_t state);
  7.     int (*suspend_late)(struct platform_device *, pm_message_t state);
  8.     int (*resume_early)(struct platform_device *);
  9.     int (*resume)(struct platform_device *);
  10.     struct pm_ext_ops *pm;
  11.     struct device_driver driver;
  12. };
   
    系统为platform总线定义一个bus_type的实例platform_bus_type,通过其成员函数match(),确定device和driver如何匹配。
    匹配platform_device和platform_driver主要看二者的name字段是否相同。(name必须要相同才能匹配)
 
用platform_device_register()函数注册单个的平台设备。
一般是在平台的BSP文件中定义platform_device,通过platform_add_devices()函数将平台设备注册到系统中
 
platform_driver 的注册与注销:
 platform_driver_register()
 platform_driver_unregister()
 

以s3c2440 LCD驱动为例:
在BSP文件中:
  1. struct platform_device s3c_device_lcd = {
  2.     .name         = "s3c2410-lcd",
  3.     .id         = -1,
  4.     .num_resources     = ARRAY_SIZE(s3c_lcd_resource),
  5.     .resource     = s3c_lcd_resource,
  6.     .dev = {
  7.         .dma_mask        = &s3c_device_lcd_dmamask,
  8.         .coherent_dma_mask    = 0xffffffffUL
  9.     }
  10. };

为了完成LCD设备的注册,将其放进/arch/arm/mach-s3c2440/mach-smdk2440.c中定义的smdk2440_devices数组中:
  1. static struct platform_device *smdk2440_devices[] __initdata = {
  2.     ........
  3.     ........
  4.     &s3c_device_lcd,
  5. };
由platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));注册
在相应的driver文件中:(/drivers/video/s3c2410fb.c)
  1. static struct platform_driver s3c2410fb_driver = {
  2.     .probe        = s3c2410fb_probe, //驱动探测
  3.     .remove        = __devexit_p(s3c2410fb_remove), //驱动移除
  4.     .suspend    = s3c2410fb_suspend,
  5.     .resume        = s3c2410fb_resume,
  6.     .driver        = {
  7.         .name    = "s3c2410-lcd", //和platform_device中的name相同
  8.         .owner    = THIS_MODULE,
  9.     },
  10. };
  11. static int __devinit s3c2410_fb_init(void)
  12. {
  13.     return platform_driver_register(&s3c2410fb_driver);
  14. }
  15. static void __exit s3c2410fb_cleanup(void)
  16. {
  17.     platform_driver_unregister(&s3c2410fb_driver);
  18. }
 
注册成功后会在下面两个目录下看到设备节点:
/sys/bus/platform/devices/
/sys/devices/platform/
 
平台设备资源和数据:
resource结构体:
  1. struct resource 
  2. {
  3.     resource_size_t start;
  4.     resource_size_t end;
  5.     const char *name;
  6.     unsigned long flags;
  7.     struct resource *parent, *sibling, *child;
  8. };
 
我们通常关心start、end 和flags 这3 个字段,分别标明资源的开始值、结束值和类型,flags
可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA 等。
如LCD资源:
  1. static struct resource s3c_lcd_resource[] = {
  2.     [0] = {
  3.         .start = S3C24XX_PA_LCD, //LCD的IO资源起始地始(LCD控制器寄存器地址)
  4.         .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1, //结束地址
  5.         .flags = IORESOURCE_MEM,
  6.     },
  7.     [1] = {
  8.         .start = IRQ_LCD, //LCD中断号
  9.         .end = IRQ_LCD,
  10.         .flags = IORESOURCE_IRQ,
  11.     }
  12. };

在driver中用platform_get_resource()或platform_get_irq()等函数获取设备资源
  1. struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned
  2. int);
  3. int platform_get_irq(struct platform_device *dev, unsigned int num);

获取到的内存或IO资源,需要ioremap后才能使用
获取到的IRQ资源,需要request_irq
 
    设备除了可以在BSP 中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存、DMA 通道以外,可能还会有一些配置信息,而这些配置信息也依赖于板,不适宜直接放置在设备驱动本身,因此,platform 也提供了platform_data 的支持。

platform_data可以自定义,比如DM9000驱动,用platform_data描述它的一些属性:
  1. static struct dm9000_plat_data s3c_dm9000_platdata = {
  2.     .flags = DM9000_PLATF_16BITONLY,
  3. };
  4. static struct platform_device s3c_device_dm9000 = {
  5.     .name = "dm9000",
  6.     .id = 0,
  7.     .num_resources = ARRAY_SIZE(s3c_dm9000_resource),
  8.     .resource = s3c_dm9000_resource,
  9.     .dev = {
  10.         .platform_data = &s3c_dm9000_platdata,
  11.     }
  12. };
 
在相应的驱动中使用:
struct dm9000_plat_data *pdata = pdev->dev.platform_data;
可获取platform_data
 
找一个和平台相关的驱动程序,从 BSP文件开始分析它的结构,一直分析到它的最底层的硬件操作,这样很快就能熟悉platform的工作原理。
 
*注:这里的BSP文件一般指ARCH和Board目录中关于平台硬件相关的文件。
================================================================================
 
实例:
linux中的IIC设备驱动
linux中的IIC驱动很庞大,驱动中随处可见
 
智能手机和平板电脑上用的sensor几乎都是IIC设备,比如:camera,电容触摸屏,重力/加速度sensor,环境光sensor,指南针sensor
 
IIC驱动主要分为Master和Slave,Master就是主机控制器,像S3C2440内部的IIC控制器就是一个Master
Slave就是IIC从机设备,它要被挂接到Master上才能工作
 
Master驱动这里就不说了,每种带有IIC控制器的处理器都有相应的驱动,假设我的内核已经支持了Master的IIC驱动
 
最近在android下调camera驱动,下面整理一下IIC设备驱动的写法,编写camera驱动的IIC部分,IIC设备驱动的写法很灵活,这里只介绍最简单基本的框架
 
首先在每个驱动文件中都要包含IIC的头文件
#include <linux/i2c.h>
 
重要的结构体有:
i2c_board_info  i2c_client  i2c_msg
主要的函数:
i2c_register_board_info
i2c_add_driver
i2c_check_functionality
i2c_transfer
 
在bsp文件中:
  1. //In machine init code:
  2. i2c_register_board_info(i2c_bus_num, cam_i2c_board_info, ARRAY_SIZE(cam_i2c_board_info));
 
i2c_bus_num是指定设备挂接在哪个IIC总线上,用0,1,2....数字表示(有的平台只有一路IIC,有的平台有多路)
  1. #define I2C_CAM_SLAVE_ADDRESS        0x42 //device address
  2. //自己构建的平台数据结构
  3. struct i2c_slave_platform_data cam_pdata = {
  4.     //your code
  5. };
  6. //i2c_board_info
  7. static struct i2c_board_info __initdata cam_i2c_board_info[] = {
  8.     {
  9.         I2C_BOARD_INFO("i2c_dev_name", (I2C_CAM_SLAVE_ADDRESS >> 1)),
  10.         .platform_data = (void *)&cam_pdata,
  11.         .irq = IRQ_CAM,
  12.     },    
  13.     {
  14.         //other i2c device code
  15.     },
  16. };
这样就完成了IIC设备在这个平台的IIC总线上的注册
 
在驱动文件中:
  1. //in init code:
  2. i2c_add_driver(&i2c_driver_cam);
  3. struct i2c_driver i2c_driver_cam = {
  4.     .driver = {
  5.          .name = "i2c_dev_name",
  6.          },
  7.     .id_table = cam_i2c_id_table,
  8.     .probe = cam_i2c_probe,
  9.     .remove = cam_i2c_remove,
  10.     .command = cam_i2c_command,
  11. };
  12. struct i2c_device_id cam_i2c_id_table[] = {
  13.     {"i2c_dev_name", 0},
  14.     {}
  15. };
“i2c_dev_name”是一个字符串,代表IIC的设备名,要保持一致才能成功注册
 
定义一个全局变量,用于获得i2c_client
  1. static struct i2c_client *this_client;
  2. //probe
  3. static int cam_i2c_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
  4. {
  5.     struct cam_data *ddata;
  6.     if(dev_id)
  7.     {
  8.         printk("cam_i2c_probe, dev_id.name (%s) \r\n",dev_id->name);        
  9.     }
  10.     //检查IIC设备的功能
  11.     if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
  12.         err = -ENODEV;
  13.         goto exit_check_functionality_failed;
  14.     }
  15.     ddata = kzalloc(sizeof(struct cam_data), GFP_KERNEL);
  16.     if (!akm) {
  17.         err = -ENOMEM;
  18.         goto exit_alloc_data_failed;
  19.     }
  20.     i2c_set_clientdata(client, ddata);
  21.     this_client = client; //将client赋给全局变量
  22.     //other init code
  23.     return 0;
  24. }
  25. //remove
  26. static int cam_i2c_remove(struct i2c_client *client)
  27. {
  28.     struct cam_data *ddata = i2c_get_clientdata(client);
  29.     //remove code    
  30.     return 0;
  31. }
i2c_command函数一般用不到,不用实现
 
IIC的读写函数:
  1. //IIC write
  2. static int CamI2C_WriteData(char *txData, int length)
  3. {
  4.     struct i2c_msg msg[] = {
  5.         {
  6.          .addr = this_client->addr,
  7.          .flags = 0,
  8.          .len = length,
  9.          .buf = txData,
  10.          },
  11.     };
  12.     if (i2c_transfer(this_client->adapter, msg, 1) < 0) {
  13.         printk(KERN_ERR "CamI2C_WriteData: transfer error\n");
  14.         return -EIO;
  15.     } else
  16.         return 0;
  17. }    
  18. //IIC Read
  19. static int CAMI2C_ReadData(char *rxData, int length)
  20. {
  21.     struct i2c_msg msgs[] = {
  22.         {
  23.          .addr = this_client->addr,
  24.          .flags = 0,
  25.          .len = 1,
  26.          .buf = rxData,
  27.         },
  28.         {
  29.          .addr = this_client->addr,
  30.          .flags = I2C_M_RD,
  31.          .len = length,
  32.          .buf = rxData,
  33.         },
  34.     };
  35.     if (i2c_transfer(this_client->adapter, msgs, 2) < 0) {
  36.         printk(KERN_ERR "CAMI2C_RxData: transfer error\n");
  37.         return -EIO;
  38.     } else
  39.         return 0;
  40. }
 
如果IIC设备支持smbus,可以使用smbus方式:
  1. if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
  2.     printk(KERN_ERR "I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE\n");
  3.     return -EIO;
  4. }
  5. //smbus read
  6. static int Read_Data(struct i2c_client *client, const unsigned char reg)
  7. {
  8.     unsigned char ret = i2c_smbus_read_byte_data(client, reg);
  9.     return ret;
  10. }
  11. //smbus write
  12. static int Write_Data(struct i2c_client *client, const unsigned char reg, const unsigned char data)
  13. {
  14.     return i2c_smbus_write_bype_data(client, reg, data);
  15. }
 
如果是16位数据,同样有i2c_smbus_read_word_data和i2c_smbus_read_word_data
 
在sensor设备驱动中,IIC只是做为一条总线传输数据,驱动的主体部分主要还是由input子层来完成的,在android中这是一种很常见的驱动模式
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值