Linux驱动:用platform平台总线驱动编写一个led驱动

1、平台总线

1.1 简介

  platform平台总线并不是像usb、i2c那些物理的总线,它是在软件上实现的一种“虚拟”总线。它的定义在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,
};

  它的出现是为了将驱动程序进行分离或说分层,也就是把驱动的操作设备的信息分隔开来,然后分别维护平台驱动的链表和平台设备的链表。其中,将驱动操作的共性提取出来到平台驱动部分,将硬件的差异部分放到平台设备里,这样有利于减少驱动程序的耦合性,一个平台驱动可以兼容多个不同的平台设备,减少重复的代码,并且容易修改移植。
  每当有新的设备或驱动加载时,平台总线结构体中的platform_match配对函数就会通过两边的id_tablename等方式将“驱动”和“设备”匹配起来,构成一个完整的设备驱动。其中platform_match函数定义如下:

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.2 相关API函数

1.2.1 平台设备

int platform_device_register(struct platform_device *pdev);		// 注册(加入到链表中)
void platform_device_unregister(struct platform_device *pdev)	// 注销(从链表中删除)

// 平台设备结构体
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;
	struct mfd_cell *mfd_cell;
	struct pdev_archdata	archdata;
};

// 其中,resource资源的结构体:include/linux/ioport.h
struct resource {
    
    
	resource_size_t start;	// 起始地址
	resource_size_t end;	// 结束地址
	const char *name;		// 名字,无所谓
	unsigned long flags;	// flags取值如下
	struct resource *parent, *sibling, *child;
};
// flags取值如下:
#define IORESOURCE_IO		0x00000100	/* PCI/ISA I/O ports */
#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_REG		0x00000300	/* Register offsets */
#define IORESOURCE_DMA		0x00000800
...

1.2.2 平台驱动

// 平台驱动结构体
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;
};

int platform_driver_register (struct platform_driver *driver);	// 注册(加入到链表中)
void platform_driver_unregister(struct platform_driver *drv);	// 注销(从链表中删除)

// 平台驱动从dev平台设备获取索引值为num的资源,类型为type
struct resource *platform_get_resource(struct platform_device *dev,
									   unsigned int type, unsigned int num);

// 获取res资源大小的值
resource_size_t resource_size(const struct resource *res);

1.3 驱动程序框架

1.3.1 平台设备

如果使用设备树写法,则可以不需要平台设备,因为设备的信息可以从设备树的节点中读取。

#define CCM_CCGR1                               0x020C406C
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04        0x020E006C
#define REGISTER_LENGTH 4

static struct resource xxx_resources[] = {
    
    
	[0] = {
    
    
		.start = CCM_CCGR1,
		.end = (CCM_CCGR1 + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[1] = {
    
    
		.start = IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04,
		.end = (IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
};

void xxx_device_release(struct device *dev)
{
    
    
	// ...
}

static struct platform_device xxx_platform_device = {
    
    
	.name = "xxx-gpio",
	.id = 0,
	.num_resources = ARRAY_SIZE(xxx_resources),
	.resource = xxx_resources,
	.dev = {
    
    
		.release = xxx_device_release,			// 防止卸载驱动后内核报错
	},
};

static int __init xxx_device_init(void)
{
    
    
	return platform_device_register(&xxx_platform_device);
}

static void __exit xxx_device_exit(void)
{
    
    
	platform_device_unregister(&xxx_platform_device);
}

module_init(xxx_device_init);
module_exit(xxx_device_exit);
MODULE_LICENSE("GPL");

1.3.2 平台驱动

static int xxx_open(struct inode *inode, struct file *filp)
{
    
    	
	// ...
	return 0;
}

static int xxx_close(struct inode *inode, struct file *filp)
{
    
    
	// ...
	return 0;
}

static struct file_operations xxx_fops = {
    
    
	.owner 	 = THIS_MODULE,
	.open  	 = xxx_open,
	.release = xxx_close,
};

static int xxx_probe(struct platform_device *dev)
{
    
    
	cdev_init(&cdev, &xxx_fops);
	// ...
}

static void xxx_remove(struct platform_device *dev)
{
    
    
	cdev_del(&cdev);
	// ...
}

static const struct of_device_id xxx_of_match[] = {
    
    
	{
    
     .compatible = "xxx" },
	{
    
    }
}

struct platform_driver xxx_platform_driver = {
    
    
	.driver = {
    
    
		.name = "xxx",	// 用于老式不使用设备树的匹配
		.of_match_table = xxx_of_match,	// 用于设备树的匹配
	},
	.probe = xxx_probe,
	.remove = xxx_remove,
};

static int __init xxx_driver_init(void)
{
    
    
	platform_driver_register(&xxx_platform_driver);
}

static void __exit xxx_driver_exit(void)
{
    
    
	platform_driver_unregister(&xxx_platform_driver);
}

module_init(xxx_driver_init);
module_exit(xxx_driver_exit);
MODULE_LICENSE("GPL");

3、平台驱动和平台设备老式写法(不使用设备树写法)

(注:程序不严谨,只是演示使用方法。)

3.1 平台设备

#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.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_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)

#define REGISTER_LENGTH				4


static struct resource led_resources[] = {
    
    
	[0] = {
    
    
		.start = CCM_CCGR1_BASE,
		.end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[1] = {
    
    
		.start = SW_MUX_GPIO1_IO03_BASE,
		.end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[2] = {
    
    
		.start = SW_PAD_GPIO1_IO03_BASE,
		.end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[3] = {
    
    
		.start = GPIO1_DR_BASE,
		.end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
	[4] = {
    
    
		.start = GPIO1_GDIR_BASE,
		.end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
		.flags = IORESOURCE_MEM,
	},
};

void led_device_release(struct device *dev)
{
    
    

}

static struct platform_device led_platform_device = {
    
    
	.name = "red-led",							// 用于和platform_driver匹配
	.id = 0,
	.num_resources = ARRAY_SIZE(led_resources),
	.resource = led_resources,
	.dev = {
    
    
		.release = led_device_release,			// 防止卸载驱动后内核报错
	},
};

static int __init led_device_init(void)
{
    
    
	return platform_device_register(&led_platform_device);
}

static void __exit led_device_exit(void)
{
    
    
	platform_device_unregister(&led_platform_device);
}

module_init(led_device_init);
module_exit(led_device_exit);
MODULE_LICENSE("GPL");


3.2 平台驱动

#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>


static void __iomem *CCM;
static void __iomem *MUX;
static void __iomem *PAD;
static void __iomem *DR;
static void __iomem *GDIR;


#define LED_CNT	1

struct led_drv_info{
    
    
	int 			 major;
	struct class 	*cls;
	struct cdev 	 cdev;
	struct device_node *node;
	int 			 gpio;
};

static struct led_drv_info led_drv;


static int led_open(struct inode *inode, struct file *filp)
{
    
    	
	filp->private_data = &led_drv;
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    
    
	u32 val;
	char databuf[10];
	unsigned long ret;
	
	ret = copy_from_user(databuf, buf, cnt);
	if(ret){
    
    
		printk("\t Kernel space: copy_from_user error!\n");
		return -1;
	}

	if(strcmp(databuf, "on") == 0){
    
    
		val = readl(DR);
		val &= ~(1 << 3);
		writel(val, DR);
	}else if(strcmp(databuf, "off") == 0){
    
    
		val = readl(DR);
		val |= (1 << 3);	
		writel(val, DR);
	}else{
    
    
		printk("\t Kernel space: led_write error!\n");
		return -1;
	}

	return 0;
}

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

static struct file_operations led_fops = {
    
    
	.owner 	 = THIS_MODULE,
	.open  	 = led_open,
	.write 	 = led_write,
	.release = led_release,
};

static int led_probe(struct platform_device *dev)
{
    
    
	int i;
	int res_size[5];
	u32 val;
	dev_t devid;
	struct resource *dev_resource[5];

	printk("LED driver and device probe!\n");

	for(i = 0; i < 5; i++){
    
    
		dev_resource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
		res_size[i] = resource_size(dev_resource[i]);
	}

	CCM = ioremap(dev_resource[0]->start, res_size[0]);
	MUX = ioremap(dev_resource[1]->start, res_size[1]);
	PAD = ioremap(dev_resource[2]->start, res_size[2]);
	DR  = ioremap(dev_resource[3]->start, res_size[3]);
	GDIR= ioremap(dev_resource[4]->start, res_size[4]);

	val = readl(CCM);
	val &= ~(3 << 26);
	val |= (3 << 26);
	writel(val, CCM);

	writel(5, MUX);

	writel(0x10B0, PAD);

	val = readl(GDIR);
	val &= ~(1 << 3);
	val |= (1 << 3);
	writel(val, GDIR);

	val = readl(DR);
	val |= (1 << 3);	
	writel(val, DR);


	if(led_drv.major){
    
    
		devid = MKDEV(led_drv.major , 0);
		register_chrdev_region(devid, LED_CNT, "led");
	}
	else{
    
    
		alloc_chrdev_region(&devid, 0, LED_CNT, "led");
		led_drv.major = MAJOR(devid);
	}

	cdev_init(&led_drv.cdev, &led_fops);
	cdev_add(&led_drv.cdev, devid, LED_CNT);
	
	led_drv.cls = class_create(THIS_MODULE, "led");
	device_create(led_drv.cls, NULL, MKDEV(led_drv.major, 0), NULL, "led0");

	return 0;
}

static int led_remove(struct platform_device *dev)
{
    
    
	printk("LED driver or device remove!\n");
	
	device_destroy(led_drv.cls, MKDEV(led_drv.major, 0));
	class_destroy(led_drv.cls);
	
	cdev_del(&led_drv.cdev);
	unregister_chrdev_region(MKDEV(led_drv.major, 0), LED_CNT);

	iounmap(CCM);
	iounmap(MUX);
	iounmap(PAD);
	iounmap(DR);
	iounmap(GDIR);

	return 0;
}

static const struct of_device_id led_of_match[] = {
    
    
	{
    
     .compatible = "red-led" },
	{
    
    }
};

static struct platform_driver led_platform_driver = {
    
    
	.driver = {
    
    
		.name = "red-led",
	},
	.probe = led_probe,
	.remove = led_remove,
};

static int __init led_driver_init(void)
{
    
    
	return platform_driver_register(&led_platform_driver);
}

static void __exit led_driver_exit(void)
{
    
    
	platform_driver_unregister(&led_platform_driver);
}

module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");

(注:加载驱动之后可以在/sys/bus/platform/查看devicesdrivers目录内容即可看到“red-led”相关内容)

4、平台驱动和平台设备新式写法(使用设备树写法)

4.1 平台设备(设备树节点)

/{
    
    
...
	led0 {
    
    
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "red-led";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led0>;
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
		status = "okay";
	};
...

4.2 平台驱动(驱动代码)

#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>


#define LED_CNT	1

struct led_drv_info{
    
    
	int 			 major;
	struct class 	*cls;
	struct cdev 	 cdev;
	struct device_node *node;
	int 			 gpio;
};

static struct led_drv_info led_drv;


static int led_open(struct inode *inode, struct file *file)
{
    
    
	file->private_data = &led_drv;
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    
    
	char databuf[10];
	unsigned long ret;
	struct led_drv_info *drv_priv_data = filp->private_data;
	
	ret = copy_from_user(databuf, buf, cnt);
	if(ret){
    
    
		printk("\t Kernel space: copy_from_user error!\n");
		return -1;
	}

	if(strcmp(databuf, "on") == 0){
    
    
		gpio_set_value(drv_priv_data->gpio, 0);
	}else if(strcmp(databuf, "off") == 0){
    
    
		gpio_set_value(drv_priv_data->gpio, 1);
	}else{
    
    
		printk("\t Kernel space: led_write error!\n");
		return -1;
	}

	return 0;
}

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

// 配置驱动操作的结构体
static struct file_operations led_fops = {
    
    
	.owner 	= THIS_MODULE,
	.open  	= led_open,
	.write 	= led_write,
	.release = led_release,
};


static int led_probe(struct platform_device *dev)
{
    
    
	dev_t devid;
	int ret;
	
	led_drv.node = of_find_node_by_path("/led0");			// 通过节点名称查找
	if(!led_drv.node){
    
    
		printk("There is no \"led0\" node in your dtb file!\n");
		return -1;
	}

	led_drv.gpio = of_get_named_gpio(led_drv.node, "led-gpio", 0);
	if (led_drv.gpio < 0){
    
    
		printk("Get gpio number error!\n");
		return -1;
	}

	ret = gpio_request(led_drv.gpio, "led0");
	if(ret){
    
    
		printk("Request gpio error!\n");
		return -1;
	}

	ret = gpio_direction_output(led_drv.gpio, 1);
	if(ret){
    
    
		printk("Set gpio output error!\n");
		return -1;
	}

	if(led_drv.major){
    
    
		devid = MKDEV(led_drv.major , 0);
		register_chrdev_region(devid, LED_CNT, "led");	// 如果前面指定了主设备号major,则不用alloc,而是直接register即可
	}
	else{
    
    
		alloc_chrdev_region(&devid, 0, LED_CNT, "led");	// 如果没指定主设备号则先alloc分配:次设备号从“0”申请LED_CNT个,“led”名字能区分即可,devid作为传出参数。
		led_drv.major = MAJOR(devid);					// 赋值给全局的major,其他地方会用到主设备号major
	}

	cdev_init(&led_drv.cdev, &led_fops);				// 初始化cdev,将结构体led_fops赋给cdev的成员
	cdev_add(&led_drv.cdev, devid, LED_CNT);			// cdev与devid绑定,有LED_CNT个设备与之绑定

	led_drv.cls = class_create(THIS_MODULE, "led");							// 创建类,名字"led"为类的名字,不是设备节点的名字,能区分即可,也可以和设备节点同名
	device_create(led_drv.cls, NULL, MKDEV(led_drv.major, 0), NULL, "led0");// 创建设备节点,次设备号为0,这里的名字才是设备节点的名称/dev/led0

	return 0;	
}


static int led_remove(struct platform_device *dev)
{
    
    
	gpio_set_value(led_drv.gpio, 1);
	
	device_destroy(led_drv.cls, MKDEV(led_drv.major, 0));		// 销毁设备节点
	class_destroy(led_drv.cls);									// 销毁类

	cdev_del(&led_drv.cdev);									// 删除类
	unregister_chrdev_region(MKDEV(led_drv.major, 0), LED_CNT);	// 注销

	gpio_free(led_drv.gpio);
	return 0;
}


static const struct of_device_id led_of_match[] = {
    
    
	{
    
     .compatible = "red-led" },	/* 设备树中led节点的属性 */
	{
    
    }
};


static struct platform_driver led_platform_driver = {
    
    
	.driver = {
    
    
		.name = "red-led",				/* 用于传统不使用设备树的设备名称 */
		.of_match_table = led_of_match,	/* 用于匹配设备树节点的属性 */
	},
	.probe 	= led_probe,
	.remove	= led_remove,
};


static int __init led_driver_init(void)
{
    
    
	return platform_driver_register(&led_platform_driver);
}

static void __exit led_driver_exit(void)
{
    
    
	platform_driver_unregister(&led_platform_driver);
}

module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");


5、测试程序

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc, char *argv[])
{
    
    
	int fd;
	int ret;

	if(argc != 3 ){
    
    
		printf("\t Usage: ./ledAPP /dev/ledx <on/off>\n");
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if(fd < 0){
    
    
		printf("\t User space: open error!\n");
		return -1;
	}

	ret = write(fd, argv[2], sizeof(argv[2]));
	if(ret){
    
    
		printf("\t User space: write error!\n");
		return -1;
	}

	ret = close(fd);
	if(ret){
    
    
		printf("\t User space: close error!\n");
		return -1;
	}

	return 0;
}


6、内核中的led驱动

  其实LED驱动在内核中已经有现成的了,只需要在make menuconfig中打开即可,路径如下:

Device Drivers  --->
	[*] LED Support  ---> 
		<*>   LED Support for GPIO connected LEDs 

  选项对应的内核源文件在drivers/leds/leds-gpio.c,查看内容就会发现它的实现就是上面所讲的平台总线驱动加设备树的方式,可以参考内核文档Documentation/devicetree/bindings/leds/leds-gpio.txt来添加或修改作为平台设备部分的设备树节点。

猜你喜欢

转载自blog.csdn.net/weixin_44498318/article/details/110231162