14_platform设备驱动

一、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,
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值