一、platform总线
1、bus_type结构体
Linux 系统内核使用 bus_type 结构体表示总线,结构体定义在文件 include/linux/device.h:
struct bus_type {
const char *name; /* 总线名字 */
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups; /* 总线属性 */
const struct attribute_group **dev_groups; /* 设备属性 */
const struct attribute_group **drv_groups; /* 驱动属性 */
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
第十行 match 函数用于匹配设备和驱动,每一条总线都必须实现这个函数。
2、platform_bus_type结构体(platform总线)
platform 总线是 bus_type 的一个具体例子,定义在 drivers/base/platform.c,总线定义如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match, //匹配函数
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
3、platform_match函数
platform_match 函数定义在 drivers/base/platform.c ,函数定义如下:
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);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
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);
}
驱动和设备的匹配方法有四种:
1、of_driver_match_device:of 类型匹配,采用设备树的方式描述设备时使用。函数定义在 include/linux/of_device.h 中。device_driver 结构体中有一个 of_match_table 的成员变量,保存着驱动的 compatible 匹配表。设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,如果有相同的条目,表示匹配成功,成功后 probe 函数会执行。
2、acpi_driver_match_device:ACPI匹配方式。
3、platform_match_id:id_table 匹配,每个 platform_driver 结构体有一个 id_table 成员变量,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所指示的驱动类型。
4、strcmp:如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
二、platform驱动
1、platform_driver结构体
platform_driver 结构体表示 platform 驱动,结构体定义在文件 include/linux/platform_device.h:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
第二行:probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行。
第七行:driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver相当于基类,提供了最基础的驱动框架。
第八行:id_table 表,每个元素的类型为 platform_device_id,结构体定义如下:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
2、device_driver 结构体
device_driver 结构体定义在 include/linux/device.h,结构体内容如下:
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
第10行:of_match_table 就是采用设备树的时候驱动使用的匹配表,每个匹配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中:
struct of_device_id {
char name[32];
char type[32];
char compatible[128]; //与设备树的compatible属性比较
const void *data;
};
3、platform_driver_register函数
定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用 platform_driver_register 函数向 Linux内核注册一个 platform驱动,函数定义如下:
int platform_driver_register (struct platform_driver *driver)
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。
4、platform_driver_unregister函数
还需要在驱动出口函数中通过 platform_driver_unregister 函数卸载 platform 驱动,函数原型如下:
void platform_driver_unregister(struct platform_driver *drv)
drv:要卸载的 platform 驱动。
5、platform_get_resource函数
platform_get_resource 函数用于获取 platrorm 设备的资源,函数原型如下:
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
dev:probe 函数的 dev 参数。
type:资源类型,和设备的资源类型相同。
num:哪一个资源节点。
返回值:resource 结构体指针,各个资源节点,成功;NULL,失败。
6、resource_size函数
resource_size 函数用于获取 platform 设备资源节点中的地址范围,函数原型如下:
static inline resource_size_t resource_size(const struct resource *res)
res:通过 platform_get_resource 函数获取到的资源节点。
返回值:地址范围大小,单位:字节。
7、platform驱动框架
/* 设备结构体 */
struct xxx_dev{
struct cdev cdev;
/* 设备结构体其他具体内容 */
};
struct xxx_dev xxxdev; /* 定义个设备结构体变量 */
static int xxx_open(struct inode *inode, struct file *filp)
{
/* 函数具体内容 */
return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
/* 函数具体内容 */
return 0;
}
/*
* 字符设备驱动操作集
*/
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
};
/*
* platform驱动的probe函数
* 驱动与设备匹配成功以后此函数就会执行
*/
static int xxx_probe(struct platform_device *dev)
{
......
cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
/* 函数具体内容(处理设备号、注册字符设备、创建类等) */
return 0;
}
static int xxx_remove(struct platform_device *dev)
{
......
cdev_del(&xxxdev.cdev);/* 删除cdev */
/* 函数具体内容(注销设备号、删除字符设备等) */
return 0;
}
/* 匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx-gpio" },
{ /* Sentinel */ } //最后一个匹配项一定要是空的
};
/*
* platform平台驱动结构体
*/
static struct platform_driver xxx_driver = {
.driver = {
.name = "xxx", //用于不使用设备树时匹配设备
.of_match_table = xxx_of_match, //使用设备树时的匹配项
},
.probe = xxx_probe, //注册probe函数
.remove = xxx_remove, //注册remove函数
};
/* 入口函数 */
static int __init xxxdriver_init(void)
{
return platform_driver_register(&xxx_driver); //注册platform驱动
}
/* 出口函数 */
static void __exit xxxdriver_exit(void)
{
platform_driver_unregister(&xxx_driver); //卸载platform驱动
}
module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
三、platform设备
如果内核支持设备树,则不使用 platform 设备。
1、platform_device结构体
platform_device 结构体用于表示 platform 设备,结构体定义在 include/linux/platform_device.h 中:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
第23行:name 表示设备名字,要和所使用的 platform 驱动的 name 匹配。
第27行:num_resources 表示资源数量,一般是28行 resource 的大小。
第28行:resource 表示资源,也就是设备信息,resource 结构体内容如下:
struct resource {
resource_size_t start; //资源起始信息(内存起始地址)
resource_size_t end; //资源终止信息(内存终止地址)
const char *name; //资源名字
unsigned long flags; //资源类型
struct resource *parent, *sibling, *child;
};
可选的资源类型定义在 include/linux/ioport.h 中:
/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
#define IORESOURCE_READONLY 0x00004000
#define IORESOURCE_CACHEABLE 0x00008000
#define IORESOURCE_RANGELENGTH 0x00010000
#define IORESOURCE_SHADOWABLE 0x00020000
#define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */
#define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */
#define IORESOURCE_MEM_64 0x00100000
#define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */
#define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */
#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000 /* No address assigned yet */
#define IORESOURCE_AUTO 0x40000000
#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
/* PnP IRQ specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IRQ_HIGHEDGE (1<<0)
#define IORESOURCE_IRQ_LOWEDGE (1<<1)
#define IORESOURCE_IRQ_HIGHLEVEL (1<<2)
#define IORESOURCE_IRQ_LOWLEVEL (1<<3)
#define IORESOURCE_IRQ_SHAREABLE (1<<4)
#define IORESOURCE_IRQ_OPTIONAL (1<<5)
/* PnP DMA specific bits (IORESOURCE_BITS) */
#define IORESOURCE_DMA_TYPE_MASK (3<<0)
#define IORESOURCE_DMA_8BIT (0<<0)
#define IORESOURCE_DMA_8AND16BIT (1<<0)
#define IORESOURCE_DMA_16BIT (2<<0)
#define IORESOURCE_DMA_MASTER (1<<2)
#define IORESOURCE_DMA_BYTE (1<<3)
#define IORESOURCE_DMA_WORD (1<<4)
#define IORESOURCE_DMA_SPEED_MASK (3<<6)
#define IORESOURCE_DMA_COMPATIBLE (0<<6)
#define IORESOURCE_DMA_TYPEA (1<<6)
#define IORESOURCE_DMA_TYPEB (2<<6)
#define IORESOURCE_DMA_TYPEF (3<<6)
/* PnP memory I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_MEM_WRITEABLE (1<<0) /* dup: IORESOURCE_READONLY */
#define IORESOURCE_MEM_CACHEABLE (1<<1) /* dup: IORESOURCE_CACHEABLE */
#define IORESOURCE_MEM_RANGELENGTH (1<<2) /* dup: IORESOURCE_RANGELENGTH */
#define IORESOURCE_MEM_TYPE_MASK (3<<3)
#define IORESOURCE_MEM_8BIT (0<<3)
#define IORESOURCE_MEM_16BIT (1<<3)
#define IORESOURCE_MEM_8AND16BIT (2<<3)
#define IORESOURCE_MEM_32BIT (3<<3)
#define IORESOURCE_MEM_SHADOWABLE (1<<5) /* dup: IORESOURCE_SHADOWABLE */
#define IORESOURCE_MEM_EXPANSIONROM (1<<6)
/* PnP I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IO_16BIT_ADDR (1<<0)
#define IORESOURCE_IO_FIXED (1<<1)
/* PCI ROM control bits (IORESOURCE_BITS) */
#define IORESOURCE_ROM_ENABLE (1<<0) /* ROM is enabled, same as PCI_ROM_ADDRESS_ENABLE */
#define IORESOURCE_ROM_SHADOW (1<<1) /* ROM is copy at C000:0 */
#define IORESOURCE_ROM_COPY (1<<2) /* ROM is alloc'd copy, resource field overlaid */
#define IORESOURCE_ROM_BIOS_COPY (1<<3) /* ROM is BIOS copy, resource field overlaid */
/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */
2、platform_device_register函数
platform_device_register 函数用于将 platform 设备注册到内核中,函数原型如下:
int platform_device_register(struct platform_device *pdev)
pdev:要注册的 platform设备。
返回值: 负数,失败; 0,成功。
3、platform_device_unregister函数
platform_device_unregister 函数用于注销 platform 设备,函数原型如下:
void platform_device_unregister(struct platform_device *pdev)
pdev:要注销的 platform设备。
4、platform设备框架
/* 寄存器地址定义*/
#define PERIPH1_REGISTER_BASE (0X20000000) /* 外设1寄存器首地址 */
#define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设2寄存器首地址 */
#define REGISTER_LENGTH 4
/* 资源 */
static struct resource xxx_resources[] = {
[0] = {
.start = PERIPH1_REGISTER_BASE, //内存起始地址
.end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1), //内存终止地址
.flags = IORESOURCE_MEM, //内存资源
},
[1] = {
.start = PERIPH2_REGISTER_BASE, //内存起始地址
.end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1), //内存终止地址
.flags = IORESOURCE_MEM, //内存资源
},
};
/* platform设备结构体 */
static struct platform_device xxxdevice = {
.name = "xxx-gpio", //要和驱动的name相同
.id = -1,
.num_resources = ARRAY_SIZE(xxx_resources), //resource资源大小
.resource = xxx_resources, //资源
};
/* 设备模块加载 */
static int __init xxxdevice_init(void)
{
return platform_device_register(&xxxdevice); //注册设备
}
/* 设备模块注销 */
static void __exit xxx_resourcesdevice_exit(void)
{
platform_device_unregister(&xxxdevice); //注销设备
}
module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
四、platform设备实验
1、platform设备
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_GDIR_BASE (0X0209C004)
#define GPIO1_DR_BASE (0X0209C000)
#define REGISTER_LENGTH 4
/* 资源 */
static struct resource led_register[] = {
{
.start = CCM_CCGR1_BASE, //寄存器起始地址
.end = CCM_CCGR1_BASE + REGISTER_LENGTH - 1, //终止地址
.flags = IORESOURCE_MEM, //资源类型
},
{
.start = SW_MUX_GPIO1_IO03_BASE,
.end = SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
{
.start = SW_PAD_GPIO1_IO03_BASE,
.end = SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
{
.start = GPIO1_GDIR_BASE,
.end = GPIO1_GDIR_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
{
.start = GPIO1_DR_BASE,
.end = GPIO1_DR_BASE + REGISTER_LENGTH - 1,
.flags = IORESOURCE_MEM,
},
};
/* platform设备结构体 */
static struct platform_device led_device = {
.name = "led_driver", //platform设备名字,必须和驱动的name字段相同
.id = -1,
.resource = led_register, //资源
.num_resources = ARRAY_SIZE(led_register), //资源大小
};
/* 入口函数 */
static int __init led_device_init(void)
{
platform_device_register(&led_device); //注册platform设备
return 0;
}
/* 出口函数 */
static void __exit led_device_exit(void)
{
platform_device_unregister(&led_device); //注销platform设备
}
/* 模块注册函数 */
module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LZK");
2、platform驱动
1)添加设备结构体
/* 设备结构体 */
typedef struct{
dev_t devid; //设备号
int major; //主节点
int minor; //次节点
struct cdev cdev; //字符设备
struct class *class; //类
struct device *device; //设备
}led_dev;
led_dev led;
2)编写加载和卸载注册函数
加载和卸载注册函数如下:
/* 模块注册函数 */
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");
入口函数:
/* 入口函数 */
static int __init led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register (&led_driver); //注册platform驱动
if(ret < 0){
printk("fail to register platform driver\r\n");
return -EINVAL;
}
return 0;
}
出口函数:
/* 出口函数 */
static void __exit led_driver_exit(void)
{
platform_driver_unregister(&led_driver); //注销platform驱动
}
3)编写操作函数
/* open函数 */
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
/* write函数 */
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int led_state = 0;
copy_from_user(&led_state, buf, count);
if(led_state == LED_ON)
led_switch(LED_ON);
if(led_state == LED_OFF)
led_switch(LED_OFF);
return 0;
}
/* 操作函数集合 */
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
4)添加头文件
参考 linux 内核的驱动代码时,找到可能用到的头文件,添加进工程。在调用系统调用函数或库函数时,在终端使用 man 命令可查看调用的函数需要包含哪些头文件。
man 命令数字含义:1:标准命令 2:系统调用 3:库函数
添加以下头文件:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#define DEVICE_NAME "led_driver"
#define DEVICE_CNT 1
#define LED_ON 1
#define LED_OFF 0
/* 寄存器映射虚拟地址 */
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_GDIR;
static void __iomem *GPIO1_DR;
5)编写led切换函数
/* led状态切换函数 */
void led_switch(int state)
{
u32 register_data = 0;
if(state == LED_ON){
register_data = readl(GPIO1_DR);
register_data &= ~(1 << 3);
writel(register_data, GPIO1_DR);
}
else{
register_data = readl(GPIO1_DR);
register_data |= (1 << 3);
writel(register_data, GPIO1_DR);
}
}
6)编写probe函数
在驱动和设备匹配成功时,probe 函数被调用
/* probe函数 */
static int led_probe(struct platform_device *dev)
{
int ret = 0;
int i = 0;
struct resource *ledresource[5];
int size[5];
u32 register_data = 0;
printk("driver and device match\r\n");
for(i=0; i<5; i++){
ledresource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); //获取设备资源
if(!ledresource[i]){
dev_err(&dev->dev, "No MEM resource for always on\n");
return -ENXIO;
}
size[i] = resource_size(ledresource[i]); //获取每个资源节点资源的地址范围
}
for(i=0; i<5; i++){
printk("ledresource[%d] = %x\n", i, ledresource[i]->start);
printk("size[%d] = %d\n", i, size[i]);
}
/* 寄存器地址映射 */
CCM_CCGR1 = ioremap(ledresource[0]->start, size[0]);
SW_MUX_GPIO1_IO03 = ioremap(ledresource[1]->start, size[1]);
SW_PAD_GPIO1_IO03 = ioremap(ledresource[2]->start, size[2]);
GPIO1_GDIR = ioremap(ledresource[3]->start, size[3]);
GPIO1_DR = ioremap(ledresource[4]->start, size[4]);
/* 初始化GPIO1时钟 */
register_data = readl(CCM_CCGR1); //读出CCM_CCGR1寄存器数据
register_data |= (3 << 26); //打开GPIO1时钟
writel(register_data, CCM_CCGR1);
/* 设置IO复用 */
writel(0x5, SW_MUX_GPIO1_IO03);
/* 设置电气属性 */
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 初始化GPIO */
register_data = readl(GPIO1_GDIR);
register_data |= (1 << 3);
writel(register_data, GPIO1_GDIR); //GPIO1_IO03输出
led_switch(LED_ON);
/* 设备号处理 */
led.major = 0;
led.minor = 0;
if(led.major){
led.devid = MKDEV(led.major, led.minor);
ret = register_chrdev_region(led.devid, DEVICE_CNT, DEVICE_NAME);
if(ret < 0){
printk("fail to register devid\r\n");
return -EINVAL;
}
}else{
ret = alloc_chrdev_region(&led.devid, led.minor, DEVICE_CNT, DEVICE_NAME);
if(ret < 0){
printk("fail to alloc devid\r\n");
return -EINVAL;
}
}
/* 注册字符设备 */
led.cdev.owner = THIS_MODULE;
cdev_init(&led.cdev, &led_fops);
ret = cdev_add(&led.cdev, led.devid, 1);
if(ret){
printk("fail to add cdev\r\n");
ret = -EINVAL;
goto fail_add_cdev;
}
/* 创建类 */
led.class = NULL;
led.class = class_create(THIS_MODULE, DEVICE_NAME);
if(led.class == NULL){
printk("fail to create class\r\n");
ret = -EINVAL;
goto fail_create_class;
}
/* 创建设备 */
led.device = NULL;
led.device = device_create(led.class, NULL, led.devid, NULL, DEVICE_NAME);
if(led.device == NULL){
printk("fail to create device\r\n");
ret = -EINVAL;
goto fail_create_device;
}
printk("led driver init\r\n");
return 0;
fail_add_cdev:
unregister_chrdev_region(led.devid, DEVICE_CNT);
fail_create_class:
cdev_del(&led.cdev);
unregister_chrdev_region(led.devid, DEVICE_CNT);
fail_create_device:
class_destroy(led.class);
cdev_del(&led.cdev);
unregister_chrdev_region(led.devid, DEVICE_CNT);
return ret;
}
7)编写remove函数
remove 函数在卸载设备或驱动的时候(失去匹配)都会被调用。
/* remove函数 */
static int led_remove(struct platform_device *dev)
{
printk("asdasd\r\n");
led_switch(LED_OFF);
/* 取消映射 */
iounmap(CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_GDIR);
iounmap(GPIO1_DR);
device_destroy(led.class, led.devid);
class_destroy(led.class);
cdev_del(&led.cdev);
unregister_chrdev_region(led.devid, DEVICE_CNT);
return 0;
}
8)编写匹配列表
匹配列表用于保存匹配设备树节点所用的信息。
/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "led-gpio" },
{/* 保留 */},
};
9)添加platform驱动结构体
/* paltform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.of_match_table = led_of_match, //用于匹配设备树
.name = DEVICE_NAME, //用于匹配platform设备
},
.probe = led_probe,
.remove = led_remove,
};
3、编写测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include "linux/ioctl.h"
#include "poll.h"
#include "sys/select.h"
#include "signal.h"
#include "fcntl.h"
static int fd = 0;
static void signal_func(int arg)
{
static key_state = 1;
read(fd, &key_state, sizeof(key_state));
if(key_state == 0)
printf("key press\r\n");
key_state = 1;
}
int main(int argc, char *argv[])
{
int flags = 0;
char *filename;
int led_state = 0;
if(argc != 3){
printf("missing parameter!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR); //阻塞方式打开驱动
if(fd < 0){
printf("open file %s failed\r\n", filename);
return -1;
}
if(atoi(argv[2]) == 1){
led_state = 1;
write(fd, &led_state, sizeof(led_state));
}else{
led_state = 0;
write(fd, &led_state, sizeof(led_state));
}
close(fd);
return 0;
}
4、运行测试
1)修改makefile
添加多一个目标。
KERNELDIR := /home/liuzhikai/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := platformled_driver.o platformled_device.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
rm -f *APP
2)加载模块
depmod //第一次加载驱动的时候需要运行此命令
modprobe platformled_device.ko //加载设备模块
modprobe platformled_driver.ko //加载驱动模块
根文件系统中 /sys/bus/platform/ 目录下保存着当前板子 platform 总线下的设备和驱动,其中 devices 子目录为 platform 设备, drivers 子目录为 plartofm 驱动。可以查看是否存在文件判断模块是否加载成功。
五、platform设备树实验
1、添加设备结构体
/* 设备结构体 */
typedef struct{
dev_t devid; //设备号
int major; //主设备号
int minor; //次设备号
struct cdev cdev; //字符设备
struct class *class; //类
struct device *device; //设备
struct device_node *nd; //设备树节点
int gpio_num; //gpio编号
}platformdts_dev;
platformdts_dev led;
2、编写加载和卸载注册函数
/* 模块注册函数 */
module_init(platformdts_init);
module_exit(platformdts_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LZK");
入口函数:
/* 入口函数 */
static int __init platformdts_init(void)
{
platform_driver_register (&led_driver);
return 0;
}
出口函数:
/* 出口函数 */
static void __exit platformdts_exit(void)
{
platform_driver_unregister(&led_driver);
}
3、编写操作函数
/* open函数 */
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
/* write函数 */
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int led_state = LED_OFF;
int ret = 0;
ret = copy_from_user(&led_state, buf, count);
if(led_state == LED_ON)
led_switch(LED_ON);
if(led_state == LED_OFF)
led_switch(LED_OFF);
return 0;
}
/* 操作函数集合 */
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
4、添加头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/platform_device.h>
#define DEVICE_NAME "platformdts"
#define DEVICE_CNT 1
#define LED_ON 1
#define LED_OFF 0
5、编写led切换函数
/* led状态切换函数 */
void led_switch(int led_state)
{
if(led_state == LED_ON)
gpio_set_value(led.gpio_num, 0);
else
gpio_set_value(led.gpio_num, 1);
}
6、编写probe函数
/* probe函数 */
static int led_probe(struct platform_device *dev)
{
int ret = 0;
printk("match success\r\n");
/* 获取设备节点 */
led.nd = of_find_node_by_name(NULL, "gpioled");
/* 读取gpio编号 */
led.gpio_num = of_get_named_gpio(led.nd, "gpios", 0);
/* 注册gpio */
ret = gpio_request(led.gpio_num, "led_gpio");
/* 设置gpio输出 */
ret = gpio_direction_output(led.gpio_num, 1);
led_switch(LED_OFF);
/* 设备号处理 */
led.major = 0;
led.minor = 0;
if(led.major){
led.devid = MKDEV(led.major, led.minor);
ret = register_chrdev_region(led.devid, DEVICE_CNT, DEVICE_NAME);
}else{
ret = alloc_chrdev_region(&led.devid, led.minor, DEVICE_CNT, DEVICE_NAME);
}
/* 注册字符设备 */
led.cdev.owner = THIS_MODULE;
cdev_init(&led.cdev, &led_fops);
ret = cdev_add(&led.cdev, led.devid, 1);
/* 创建类 */
led.class = class_create(THIS_MODULE, "LED");
/* 创建设备 */
led.device = device_create(led.class, NULL, led.devid, NULL, DEVICE_NAME);
printk("driver init\r\n");
return 0;
}
7、编写remove函数
/* remove函数 */
static int led_remove(struct platform_device *dev)
{
led_switch(LED_OFF);
/* 释放gpio */
gpio_free(led.gpio_num);
/* 删除设备 */
device_destroy(led.class, led.devid);
/* 删除类 */
class_destroy(led.class);
/* 注销字符设备 */
cdev_del(&led.cdev);
/* 注销设备号 */
unregister_chrdev_region(led.devid, DEVICE_CNT);
return 0;
}
8、编写匹配列表
/* 匹配列表 */
const struct of_device_id led_of_match[] = {
{ .compatible = "gpioled_test", },
{ /* 保留 */ },
};
9、添加platform驱动结构体
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "led", //就算使用设备树也要添加name字段
.of_match_table = led_of_match,
},
.probe = led_probe,
.remove = led_remove,
};