54 platform设备驱动

一、驱动-设备-总线

  • 根据驱动的分离和分层衍生出了:驱动(driver)- 总线(bus)- 设备(device)
    总线代码不需要我们编写,这是linux kernel 提供给我们使用的。
    我们需要编写的是驱动和设备:
    当向总线注册驱动的时候,总线会从现有的所有设备中查找,看看哪个设备与此驱动匹配。
    当向总线注册设备的时候,总线也会在现有的驱动中查看与之匹配的驱动。

  • 驱动就是具体的设备驱动
    设备是设备具体属性的集合,如地址范围、iic器件地址、速度
    总线是驱动和设备的中间联系人

1、总线
  • 总线数据类型为 struct bus_type
    向内核注册总线 使用 bus_register,卸载总线使用bus_unregister。
    int bus_register(struct bus_type *bus)
    void bus_unregister(struct bus_type *bus)
    总线一般都是由内核提供,很少需要用户自己添加总线进去。
    注册总线一般都是在 xxx-core.c 文件中。但是很少需要用户自己去添加、注册总线
此结构体定义在文件 include/linux/device.h
struct bus_type {
	/* shell 下, ls /sys/bus 可以看到 name 的值 */
	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;
};
  • 查看系统中的总线ls /sys/bus
    shell 返回的:就是 bus->name
/ # 
查看系统中的总线
/ # ls -F /sys/bus/
clockevents/  event_source/ mmc/          sdio/         usb/
clocksource/  hid/          platform/     serio/        virtio/
container/    i2c/          rpmsg/        soc/          workqueue/
cpu/          mdio_bus/     scsi/         spi/
/ # 
查看 i2c 总线下的驱动和设备
/ # ls -F /sys/bus/i2c/  
devices/           drivers_autoprobe  uevent
drivers/           drivers_probe
/ # 
/ # 
/ # 
查看 i2c 总线下的设备
/ # ls -F /sys/bus/i2c/devices/
0-000e@ 0-001e@ 1-001a@ 1-003c@ i2c-0@  i2c-1@
/ # 
/ # 
查看 i2c 总线下的驱动
/ # ls /sys/bus/i2c/drivers -F
at24/               mma8450/            tlv320aic23-codec/
da9052/             ov2640/             tsc2007/
dummy/              pca953x/            vtl_ts/
egalax_ts/          pfuze100-regulator/ wm8962/
ir-kbd-i2c/         sgtl5000/
mc13xxx/            stmpe-i2c/
/ # 
/ # 
/ # 
  • 总线简单介绍,重点是驱动和设备
2、设备

设备数据类型为 struct device
数据类型定义在 include/linux/device.h
向总线注册设备int device_register(struct device*)
注销设备void device_unregister(struct device*)

struct device {
	struct device		*parent;
	struct device_private	*p;
	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;
	struct mutex		mutex;	/* mutex to synchronize calls to
								 * its driver.
								 */
	/* 此设备所在的总线 */
	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   					device */
	void		*platform_data;	/* Platform specific data, device
					  				 core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					  				dev_set/get_drvdata */
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     		alloc_coherent mappings as
					     		not all hardware supports
					     		64 bit addresses for consistent
					     		allocations such descriptors. */
	unsigned long	dma_pfn_offset;
	struct device_dma_parameters *dma_parms;
	struct list_head	dma_pools;	/* dma pools (if dma'ble) */
	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;
	/* 只想设备树对应的设备节点 */
	struct device_node	*of_node; /* associated device tree node ,nd type*/
	struct fwnode_handle	*fwnode; /* firmware device node */
	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */
	spinlock_t		devres_lock;
	struct list_head	devres_head;
	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */
	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	bool			offline_disabled:1;
	bool			offline:1;
};

重点关注 bus 、 driver 和 of_node 三个成员变量
向总线注册设备时,使用函数 device_register。此函数的调用流程如下:

device_register
	->device_add
	 	->bus_add_device
	 		bus_probe_device
	 			->device_attach(dd.c)
	 				->bus_for_each_drv(dev->bus, NULL, dev, __device_attach)
	 					->__device_attach 	// 查找bus下的每一个驱动
	 										// 都执行此函数
	 						->driver_match_device(drv, dev)  	// 判断每一个驱动设备是否匹配
	 															// 实际上内部调用的就是 drv->bus->match
	 						  若匹配则执行driver_probe_match
	 											->really_probe
	 												-> drv->probe 

不管是先注册设备还是先注册驱动,驱动和设备匹配以后,drv->probe就会执行。
此probe由驱动编写人员编写的,以后会将常与此函数打交道。

3、驱动

驱动结构体类型如下:
定义在 include/linux/device.h

struct device_driver {
	const char		*name;
	/* 记录这个driver属于哪一个总线 */
	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;
	// 驱动和设备匹配以后,驱动里面的probe函数就会执行
	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;
};

向总线注册驱动时,会检查当前总线下的所有设备,是否存在与此驱动匹配的设备,若有则执行驱动里面的probe函数(需要用户去写此函数)。注册设备也是,匹配也要执行驱动的probe函数。

使用 driver_register (struct device_driver*) 注册驱动,但是直接调用此函数的情况很少
匹配过程如下:

 ->bus_add_driver // add a driver to a bus;
 	 -> driver_attach // 查找bus下所有设备,找与其匹配的
  		 -> bus_for_each_dev(drv->bus,NULL,drv,__driver_attach)
			  -> __driver_arrach // 检测每一个设备
 			  		-> driver_match_device  	// 每个设备都调用此函数,判断是否与当前驱动匹配。
 			  									// 实际上是执行 dev->bus->match
					(匹配成功执行)driver_probe_device
										->really_probe 
											-> drv->probe // 驱动与设备匹配时,执行drive->probe
向总线注册驱动时,会检查当前总线下的所有设备,有没有与此驱动匹配的设备。
若有则执行驱动结构体里面的 probe 成员函数

二、platform平台驱动模型

1、platform总线

根据总线-驱动-设备模型,IIC、SPI、USB这样实实在在的总线是完全匹配的,但是要有一些外设是无法归结为具体的总线:如定时器、RTC、LCD等。为此linux内核创造了一个虚拟的总线:platform总线。

platform总线注册:
platform_bus_init
-> bus_register
已经注册好了,可以在 /sys/bus 目录下有一个 platform 目录

platform 总线是 bus_type 的一个具体实例
定义在文件 drivers/base/platform.c
struct bus_type platform_bus_type = {
	/* 总线名字  /sys/bus/platform */
	.name		= "platform", 
	.dev_groups	= platform_dev_groups,
	/* pdev 和 pdrv 的匹配函数*/
	.match		= platform_match, 
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};

对于platform平台而言,函数 platform_match 负责匹配 platform驱动platform设备

2、platform驱动

platform驱动结构体类型:struct platform_driver
如下:

此结构体定义在文件 include/linux/platform_device.h 中
struct platform_driver {
	/*pdev 和 pdrv 匹配成功后会执行 pdrv->probe 函数 */
	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;
};

向内核注册驱动:使用 platform_driver_register(&drv) (platform_device.h)是个宏,返回值是 int 型,注册的就是上面类型的结构体。定义、初始化然后注册。
注销驱动:使用 platform_driver_unregister(struct platform_driver *);

platform_driver_register(drv) 的函数调用过程:

__platform_driver_register(struct platform_driver *drv,
							struct module *owner)
	-> 设置driver的probe函数为platform_drv_probe(前提是platform_driver的probe函数有效的话)
		driver_register,匹配成功的话最终会执行 device_driver->probe 函数,就是上一行赋值的。
		即对于 platform总线 就是 platform_drv_probe
		而 platform_drv_probe 
			-> platform_driver->probe

结论:向内核注册 platform 驱动时,若驱动和设备匹配成功的话,最终会执行 platform_driver 的 probe 函数
		此函数由驱动人员编写
3、platform设备

platform设备结构体类型:struct platform_device
如下:

struct platform_device {
	/* 用于无设备树的匹配,和 pdrv->drv->name */
	const char	*name; 
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	/* 相比于 dev 新增加的内容 */
	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;
};

struct resource 结构体定义在 ioport.h 中

/*
 * Resources are tree-like, allowing
 * nesting etc..
 */
struct resource 
{
    resource_size_t start;
    resource_size_t end;
    const char *name;
    /* 表示 resource 类型 
     * 定义在 ioport.h
     */
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};

平台设备分两种情况:有无设备树。
1、无设备树时,注册设备需要向内核注册这个 platform_device 结构体;
需要驱动开发人员编写设备注册文件,使用函数 platform_device_register 注册设备。
若匹配,则最终会执行 由驱动人员编写的 platform_driver->probe

/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);

2、有设备树时,修改设备树的设备节点即可。

当设备与platform的驱动匹配以后,最终也会执行 platform_driver的probe成员函数

4、platform匹配过程

有上面分析,驱动和设备的匹配是通过 bus->match 函数;platform总线下的 match 函数是 platform_match

platform_match 匹配过程如下

platform_match 
	->	of_driver_match_device 			设备树情况下 pdrv->drv->of_match_table->compatible
										与设备树某节点的 compatible 属性
		acpi_driver_match_device	 	Then try ACPI style match (少用)
		platform_match_id				Then try to match against the id table(少用)
		strcmp(pdev->name, drv->name)	driver name match,最终的方法就是比较字符串,就是
										pdev->name 和  pdrv->drv->name
										字符串相同,则匹配成功。无设备树情况下常用此方法

1、有设备树情况下

of_driver_match_device(struct device *dev, struct device_driver *drv)
	-> of_match_device(drv->of_match_table, dev)  of_match_table驱动里面的字符串列表,类型为of_device_id
												此类型有个成员变量 compatible 属性 device.c
		-> __of_match_node base.c
			-> __of_device_is_compatible	base.c
				-> __of_find_property 取出属性值,并与match table中的compatible相比较

上面的调用过程分析
不用设备树时,要注册 platform_driverplatform_device
使用设备树时,只需要注册 platform_driverplatform_device 是通过设备树去描述。
匹配最终完成后,platform_driver下的成员函数probe就会执行

三、实验程序编写

1、无设备树

两部分:platform_driver,platform_device
bus 是核心层,框架。
以点灯为例。

步骤:
1、定义 struct resource 结构体,里面保存着寄存器的地址、地址长度;
定义 struct platform_device 结构体,内嵌 struct resource
(编写驱动需要寄存器地址信息。地址信息使用设备信息,设备信息定义在 platform_device 里面。
因此需要在驱动 里面获取设备的资源 / 信息。
使用函数
struct resource *platform_get_resource(struct platform_device \*, unsigned int type, unsigned int num) )

2、向内核注册自己定义的 struct platform_device 设备。

  • leddevice.c
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.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/gpio.h>
#include<linux/of_gpio.h>
#include<linux/atomic.h>
#include<linux/timer.h>
#include<linux/string.h>
#include<linux/jiffies.h>
#include<linux/irq.h>
#include<asm/mach/map.h>
#include<asm/uaccess.h>
#include<asm/io.h>
#include<linux/interrupt.h>
#include<linux/fcntl.h>
#include<linux/poll.h>
#include<linux/ide.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)
/* 此寄存器用来控制 gpio 的高低电平 */
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)

#define REGISTER_LENG   4

void leddevice_release(struct device *dev);

struct resource led_resource[] = 
{
    [0] = 
    {
        .start = CCM_CCGR1_BASE,
        .end = CCM_CCGR1_BASE + REGISTER_LENG - 1,
        /* 表示 resource 类型 */
        .flags = IORESOURCE_MEM,
    },
    [1] = 
    {
        .start = SW_MUX_GPIO1_IO03_BASE,
        .end = SW_MUX_GPIO1_IO03_BASE + REGISTER_LENG - 1,
        .flags = IORESOURCE_MEM,
    },
    [2] = 
    {
        .start = SW_PAD_GPIO1_IO03_BASE,
        .end = SW_PAD_GPIO1_IO03_BASE + REGISTER_LENG - 1,
        .flags = IORESOURCE_MEM,
    },
    [3] = 
    {
        .start = GPIO1_DR_BASE,
        .end = GPIO1_DR_BASE + REGISTER_LENG - 1,
        .flags = IORESOURCE_MEM,
    },
    [4] = 
    {
        .start = GPIO1_GDIR_BASE,
        .end = GPIO1_GDIR_BASE + REGISTER_LENG - 1,
        .flags = IORESOURCE_MEM,
    },
};

struct platform_device leddevice = 
{
    .name = "imx6ull-led",
    .id = -1,
    .dev = 
    {
        .release = leddevice_release,
    },
    .num_resources = 5,
    .resource = led_resource,
};

/* 执行 rmmod leddevice.ko 时,会执行此函数 */
void leddevice_release(struct device *dev)
{
    printk("%s(%d):%s\n", __FILE__, __LINE__, __func__);
}

/* 执行 modprobe leddevice.ko 时,会执行此函数 */
static int __init leddevice_init(void)
{
    // int platform_device_register(struct platform_device *pdev);
    // 注册 platform device
    return platform_device_register(&leddevice);
}

static void __exit leddevice_exit(void)
{
    // void platform_device_unregister(struct platform_device *pdev)
    // 卸载 platform device 
    platform_device_unregister(&leddevice);
}


module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
  • leddriver.c
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.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/gpio.h>
#include<linux/of_gpio.h>
#include<linux/atomic.h>
#include<linux/timer.h>
#include<linux/string.h>
#include<linux/jiffies.h>
#include<linux/irq.h>
#include<asm/mach/map.h>
#include<asm/uaccess.h>
#include<asm/io.h>
#include<linux/interrupt.h>
#include<linux/fcntl.h>
#include<linux/poll.h>
#include<linux/ide.h>
#include<linux/platform_device.h>

#define PLED_CNT			1		  	/* 设备号个数 */
#define PLED_NAME			"pled"	    /* 名字 */
#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* 定义 pled 设备结构体类型 */
struct pled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct pled_dev pled;	/* led设备 */

static int led_probe(struct platform_device* pdev);
static int led_remove(struct platform_device* pdev);

struct platform_driver leddriver = 
{
    .driver = 
    {
    	/* 在无设备树情况,pdev 和 pdrv的匹配是通过
    	 * pdev->name 和 pdrv->driver.name来进行的。
    	 */
        .name = "imx6ull-led", 
    },
    .probe = led_probe,
    /* 注意只有在完成 pdev 和 pdrv 的匹配后,
     * 卸载 leddriver.ko时,此函数才会执行。
     */
    .remove = led_remove,
};

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &pled; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* 关闭LED灯 */
	}
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations pled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

static int led_probe(struct platform_device* pdev)
{
    struct resource *ledsource[5];
    int i = 0;
    //int ret = 0;
    unsigned int val = 0;
    printk("%s(%d):%s\n", __FILE__, __LINE__, __func__);
    // init the led,chrdev

    /* 1、获取资源,保存在 pdev->resource */
    for(i=0; i<5; i++)
    {
        ledsource[i] = platform_get_resource\
                        (\
                            pdev, \
                            IORESOURCE_MEM, \
                            i \
                        );
        if(ledsource[i] == NULL)
        {
            return -1;
        }
    }
    // io remap
    IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, resource_size(ledsource[0]));
	SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, resource_size(ledsource[1]));
  	SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, resource_size(ledsource[2]));
	GPIO1_DR = ioremap(ledsource[3]->start, resource_size(ledsource[3]));
	GPIO1_GDIR = ioremap(ledsource[4]->start, resource_size(ledsource[4]));

	/* 2、使能GPIO1时钟 */
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	/* 清除以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 3、设置GPIO1_IO03的复用功能,将其复用为
	 *    GPIO1_IO03,最后设置IO属性。
	 */
	writel(5, SW_MUX_GPIO1_IO03);
	
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 4、设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	/* 清除以前的设置 */
	val |= (1 << 3);	/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 5、默认关闭LED */
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);

	/* 注册字符设备驱动 */
	/* 获取设备号 */
	if (pled.major) {		/*  定义了设备号 */
		pled.devid = MKDEV(pled.major, 0);
		register_chrdev_region(pled.devid, PLED_CNT, PLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&pled.devid, 0, PLED_CNT, PLED_NAME);	/* 申请设备号 */
		pled.major = MAJOR(pled.devid);	/* 获取分配号的主设备号 */
		pled.minor = MINOR(pled.devid);	/* 获取分配号的次设备号 */
	}
	printk("newcheled major=%d,minor=%d\r\n",pled.major, pled.minor);	
	
	/* 初始化cdev */
	pled.cdev.owner = THIS_MODULE;
	cdev_init(&pled.cdev, &pled_fops);
	
	/* 添加一个cdev */
	cdev_add(&pled.cdev, pled.devid, PLED_CNT);

	/* 创建类 */
	pled.class = class_create(THIS_MODULE, PLED_NAME);
	if (IS_ERR(pled.class)) {
		return PTR_ERR(pled.class);
	}

	/* 创建设备 */
	pled.device = device_create(pled.class, NULL, pled.devid, NULL, PLED_NAME);
	if (IS_ERR(pled.device)) {
		return PTR_ERR(pled.device);
	}
	/* 注意只有 pdev 和 pdrv 匹配成功以后,
	 * 才能在 /dev 目录下看到对应设备
	 */
    return 0;
}

/* 注意只有在 pdrv 和 pdev 成功匹配后,
 * 卸载 leddriver.ko,此函数才会执行。
 * 否则卸载此驱动时此函数也不会执行。
 */
static int led_remove(struct platform_device* pdev)
{
    printk("%s(%d):%s\n", __FILE__, __LINE__, __func__);
    /* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* 注销字符设备驱动,删除cdev */
	cdev_del(&pled.cdev);
	/* 注销设备号 */
	unregister_chrdev_region(pled.devid, PLED_CNT); 

	device_destroy(pled.class, pled.devid);
	class_destroy(pled.class);
    return 0;
}

/* 驱动入口函数 */
static int __init leddriver_init(void)
{
    return platform_driver_register(&leddriver);

}

// unload the driver
static void __exit leddriver_exit(void)
{
    platform_driver_unregister(&leddriver);
}


module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");

pdrv->probe 做的事情:获取资源初始化gpio;设备号;cdev相关;device、class相关。

  • pledAPP.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 	0
#define LEDON 	1

int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开led驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */

	/* 向/dev/led文件写入数据 */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

  • 测试相关
    1、只有在 pdev 和 pdrv 匹配成功以后,卸载 pdrv模块时,才会执行 pdrv->remove 函数。
    2、若只加载了 pdev模块,可以在 /sys/bus/platform/devices 目录下看到 pdev->name 目录。
    3、若只加载了 pdrv模块,可以在 /sys/bus/platform/drivers 目录下看到 pdrv->drv.name 目录。
    在 /dev 目录下看不到对应的设备名,只有加载了 pdev 和 pdrv 模块 且 二者相匹配后才能在 /dev 目录下看到对应的设备名。
2、有设备树

此时设备由设备树来描述的,不再需要我们使用 platform_device 向总线注册,而是直接修改设备树。
只需要修改设备树,然后编写驱动即可。
驱动和设备匹配成功以后,设备信息就会从设备树节点传递到 pdev->dev->of_node
platform 提供了很多 api 去获取设备相关信息

  • leddriver.c
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include <linux/fs.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.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/gpio.h>
#include<linux/of_gpio.h>
#include<linux/atomic.h>
#include<linux/timer.h>
#include<linux/string.h>
#include<linux/jiffies.h>
#include<linux/irq.h>
#include<asm/mach/map.h>
#include<asm/uaccess.h>
#include<asm/io.h>
#include<linux/interrupt.h>
#include<linux/fcntl.h>
#include<linux/poll.h>
#include<linux/ide.h>
#include<linux/platform_device.h>

/*
struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};
*/

#define GPIOLED_CNT			1		  	/* 设备号个数 */
#define GPIOLED_NAME		"gpioled"	/* 名字 */
#define LEDOFF 				0			/* 关灯 */
#define LEDON 				1			/* 开灯 */

/* 自定义 gpioled 设备结构体 */
struct gpioled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	int led_gpio;			/* led所使用的GPIO编号		*/
};

struct gpioled_dev gpioled;	/* led设备 */

static int led_probe(struct platform_device* pdev);
static int led_remove(struct platform_device* pdev);

struct of_device_id led_of_match[] = 
{
	/* 必须要和设备数对应节点的 compatible 值一致 */
	{.compatible = "gpio-leds"},
	{/*sentinel*/},
};

struct platform_driver leddriver = 
{
    .driver = 
    {
    	/* 使用设备树的情况下, pdrv->drv.name 不再用于匹配,
    	 * 这个名字和  /sys/bus/platform/drivers 目录下的驱动
    	 * 名对应。
    	 */
        .name = "imx6ull-led", 
        /* 设备树compatible属性匹配表*/
		.of_match_table = led_of_match, 
		
    },
    .probe = led_probe,
    .remove = led_remove,
};

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &gpioled; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;
	struct gpioled_dev *dev = filp->private_data;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		gpio_set_value(dev->led_gpio, 0);	/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		gpio_set_value(dev->led_gpio, 1);	/* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

static int led_probe(struct platform_device* pdev)
{
	int ret = 0;
    printk("%s(%d):%s\n", __FILE__, __LINE__, __func__);
	
#if 0
	/* 设置LED所使用的GPIO */
	/* 1、获取设备节点:gpioled */
	gpioled.nd = of_find_node_by_path("/gpioled");
	if(gpioled.nd == NULL) {
		printk("gpioled node not find!\r\n");
		return -EINVAL;
	} else {
		printk("gpioled node find!\r\n");
	}
#else
	/* 设备树节点信息保存在 pdev->dev.of_node */
	gpioled.nd = pdev->dev.of_node;
#endif

	/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
	if(gpioled.led_gpio < 0) {
		printk("can't get led-gpio");
		return -EINVAL;
	}
	printk("led-gpio num = %d\r\n", gpioled.led_gpio);

	/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
	ret = gpio_direction_output(gpioled.led_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (gpioled.major) {		/*  定义了设备号 */
		gpioled.devid = MKDEV(gpioled.major, 0);
		register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);	/* 申请设备号 */
		gpioled.major = MAJOR(gpioled.devid);	/* 获取分配号的主设备号 */
		gpioled.minor = MINOR(gpioled.devid);	/* 获取分配号的次设备号 */
	}
	printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);	
	
	/* 2、初始化cdev */
	gpioled.cdev.owner = THIS_MODULE;
	cdev_init(&gpioled.cdev, &gpioled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

	/* 4、创建类 */
	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
	if (IS_ERR(gpioled.class)) {
		return PTR_ERR(gpioled.class);
	}

	/* 5、创建设备 */
	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
	if (IS_ERR(gpioled.device)) {
		return PTR_ERR(gpioled.device);
	}
	
    return 0;
}

static int led_remove(struct platform_device* pdev)
{
    printk("%s(%d):%s\n", __FILE__, __LINE__, __func__);
	/* 注销字符设备驱动 */
	cdev_del(&gpioled.cdev);/*  删除cdev */
	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */

	device_destroy(gpioled.class, gpioled.devid);
	class_destroy(gpioled.class);
    return 0;
}

// load the driver
static int __init leddriver_init(void)
{
    // register platform driver
    return platform_driver_register(&leddriver);

}

// unload the driver
static void __exit leddriver_exit(void)
{
    // remove platform driver
    platform_driver_unregister(&leddriver);
}


module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");

系统启动后,查看设备树的节点名称

/ # ls proc/device-tree/
#address-cells                 gpioled
#size-cells                    interrupt-controller@00a01000
aliases                        memory
alpha_led                      model
backlight                      name
chosen                         pxp_v4l2
clocks                         regulators
compatible                     reserved-memory
cpus                           soc
gpiobeep                       sound
gpiokey                        spi4

查看 gpioled 节点下的各个属性 / 属性值

/ # 
/ # ls proc/device-tree/gpioled/
compatible     name           pinctrl-names
led-gpios      pinctrl-0      status
/ # cat  proc/device-tree/gpioled/compatible 
gpio-leds/ # 
/ # 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值