目录
一,为什么有平台总线
【1】用于平台升级,比如三星的:2410,2440,6410,S5pv210,4412…硬件平台升级的时候,部分模块的控制方式基本上类似的,但是不同平台模块的地址是不一样的。
【2】比如GPIO的控制逻辑:
- 配置GPIO的输入输出功能------>配置GPxxCON寄存器
- 给GPIO数据寄存器设置高低电平------->配置GPxxDATA寄存器
不同平台逻辑操作基本是一样的,但是地址不一样
【3】问题:(如果不用平台总线)
当平台升级的时候对于相似的设备驱动,需要编写很多次,这样就会重复编写大量相似代码。
【4】解决:(引入平台总线)
device(中断和地址)和driver(操作逻辑)分离。在升级的时候,只要修改device中的信息即可;实现一个driver代码能够驱动多个平台相似的模块,且修改的代码量很少。
二,平台总线三元素
1.总线对象platform_bus
【1】platform_bus不需要自己创建,系统自动创建
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
【2】匹配方法:
- 优先匹配pdriver中的id_table,里面包含了支持不同平台的名字
- 直接匹配pdevice和driver中的名字(因为pdriver中没用直接成员是表示名字的,用的是pdriver中继承的父类driver中的成员const char *name)
【3】内核自带的匹配函数
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* match against the id table first */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
【4】匹配函数中的原理:
struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv);
2.platform_device对象
【1】platform_device
struct platform_device {
const char * name; //用于做匹配
int id; //一般都直接给-1
struct device dev; //继承了device父类
u32 num_resources; //资源个数
struct resource * resource; //资源:包括了一个设备的地址和中断
const struct platform_device_id *id_entry;
};
【2】注册和注销
int platform_device_register(struct platform_device *pdev)
void platform_device_unregister(struct platform_device *pdev)
3.platform_driver对象
【1】platform_driver
struct platform_driver {
int (*probe)(struct platform_device *); //匹配成功之后被调用的函数
int (*remove)(struct platform_device *); //device移除的时候被调用的函数
struct device_driver driver; //继承了driver父类
const struct platform_device_id *id_table; //如果driver支持多个平台,在列表中写出来
};
【2】注册和注销
int platform_driver_register(struct platform_driver *drv)
void platform_driver_unregister(struct platform_driver *drv)
三,编写代码:一个能在多平台下使用的led驱动
1.platform_device
【1】LED原理图和寄存器表
【2】定义地址和中断资源
struct resource {
resource_size_t start; //地址开始
resource_size_t end; //地址结束
const char *name; //描述,自定义
unsigned long flags; //标志,区分当前资源描述的是中断还是内存;比如IORESOURCE_MEM表示内存资源,IORESOURCE_IRQ表示中断资源
struct resource *parent, *sibling, *child; //资源是父类,兄弟,子类
};
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_IRQ 0x00000400
【3】注册platform_device,代码编写:plat_led_dev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define GPJ0_CON_addr 0xE0200240
#define GPJ0_SIZE 24
//一个设备可能有多个资源
struct resource led_res[] = { //可扩展数组
[0] = {
.start = GPJ0_CON_addr,
.end = GPJ0_CON_addr+ GPJ0_SIZE -1,
.flags = IORESOURCE_MEM,
},
//有些设备也有中断资源,用于说明中断资源的使用,本驱动中没有太多意义
[1] = {
.start = 45, //中断号
.end = 45,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device led_pdev = {
.name = "s5pv210", //用于匹配,优先匹配id_table中的
.id = -1,
.num_resources = ARRAY_SIZE(led_res), //资源个数
.resource = led_res,
};
static int __init plat_led_dev_init(void)
{
printk("-----%s------\n",__FUNCTION__);
//注册一个platform_device
return platform_device_register(&led_pdev);
}
static void __exit plat_led_dev_exit(void)
{
printk("-----%s------\n",__FUNCTION__);
platform_device_unregister(&led_pdev);
}
module_init(plat_led_dev_init);
module_exit(plat_led_dev_exit);
MODULE_LICENSE("GPL");
2.platform_driver
【1】注册一个platform_driver,实现操作设备的代码
【2】注册完毕同时,如果和pdev匹配成功,就会自动调用probe方法,其中,probe方法的内容为:对硬件进行操作
- 注册设备号,并注册fops------->为用户提供一个设备标识,同时提供文件操作io接口
- 创建设备结点
- 初始化硬件:1.ioremap(地址)------>地址从pdev获取 2.readl/writel();
- 实现各种io接口:xx_open(); xx_read()…
【3】获取资源的方式
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
参数:
- 参数1:从哪个pdev中获取资源
- 参数2:资源类型;比如比如IORESOURCE_MEM表示内存资源,IORESOURCE_IRQ表示中断资源
- 参数3:表示获取同种资源的第几个
【4】代码编写:plat_led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/uaccess.h>
struct led_dev{ //描述LED的信息
unsigned int dev_major; //主设备号
struct class *cls;
struct device* dev;
struct resource *res; //获取到的内存资源
void * reg_base; //表示物理地址映射之后的虚拟地址
};
struct led_dev *samsung_led;
ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int val;
int ret;
ret = copy_from_user(&val, buf,count);
if(ret > 0)
{
printk("copy_from_user error\n");
return -EFAULT;
}
if(val) //亮
{
writel(readl(samsung_led->reg_base + 4)| (0x3 << 4) ,samsung_led->reg_base + 4);
}
else
{
writel(readl(samsung_led->reg_base + 4) & ~(0x3 << 4) ,samsung_led->reg_base + 4);
}
return count;
}
int led_pdrv_open (struct inode *inode, struct file *filp)
{
return 0;
}
int led_pdrv_close (struct inode *inode, struct file *filp)
{
return 0;
}
const struct file_operations led_fops = {
.open = led_pdrv_open,
.release = led_pdrv_close,
.write = led_pdrv_write,
};
int led_drv_probe(struct platform_device *pdev)
{
printk("-----%s------\n",__FUNCTION__);
samsung_led = kzalloc(sizeof(struct led_dev), GFP_KERNEL);
if(NULL == samsung_led )
{
printk(" kzalloc error\n");
return -ENOMEM;
}
samsung_led->dev_major = register_chrdev(0, "led_drv", & led_fops);
printk("dev_major = %d",samsung_led->dev_major);
samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
if(samsung_led->cls == NULL)
{
printk("class_create error\n");
return -EFAULT;
}
samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major,0), NULL, "led0");
if(samsung_led->dev == NULL)
{
printk("device_create error\n");
return -EFAULT;
}
//获取资源
//参数1:从哪个pdev中获取资源
//参数2:资源类型
//参数3:表示获取同种 资源的第几个:数组下标从0开始
samsung_led->res = platform_get_resource(pdev,IORESOURCE_MEM, 0);
int irqno = platform_get_irq(pdev, 0);
printk("---irq = %d---\n",irqno);
samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res));
//对寄存器进行配置---输出功能
writel((readl(samsung_led->reg_base) & ~(0xff<<16))|(0x11<<16),samsung_led->reg_base);
return 0;
}
int led_drv_remove(struct platform_device *pdev)
{
printk("-----%s------\n",__FUNCTION__);
iounmap(samsung_led->reg_base);
device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major,0));
class_destroy(samsung_led->cls);
unregister_chrdev(samsung_led->dev_major, "led_drv");
kfree(samsung_led);
return 0;
}
const struct platform_device_id led_id_table[] = {
{"exynos4412",0x4444},
{"s5pv210",0x3333},
{"s3c2410",0x2222},
{"s3c6410",0x1111},
};
struct platform_driver led_pdrv = {
.probe = led_drv_probe,
.remove = led_drv_remove,
.driver = {
.name = "samsung_led_drv", //用于做匹配 // /sys/bus/platform/driver/samsung_led_drv
},
.id_table = led_id_table,
};
static int __init plat_led_pdrv_init(void)
{
printk("-----%s------\n",__FUNCTION__);
//注册一个platform_device
return platform_driver_register(&led_pdrv);
}
static void __exit plat_led_pdrv_exit(void)
{
printk("-----%s------\n",__FUNCTION__);
platform_driver_unregister(&led_pdrv);
}
module_init(plat_led_pdrv_init);
module_exit(plat_led_pdrv_exit);
MODULE_LICENSE("GPL");
3.应用程序编写
【1】应用代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
int fd;
int on = 0;
fd = open("/dev/led0",O_RDWR);
if(fd < 0)
{
perror("open");
exit(-1);
}
while(1)
{
on = 1;
write(fd,&on,4);
sleep(1);
on = 0;
write(fd,&on,4);
sleep(1);
}
close(fd);
return 0;
}
【2】实验结果
LED3和LED4实现闪烁。