20.1 platform 设备驱动

一、Linux 驱动的分离与分层

1. 驱动的分隔和分离

  现在有三个平台,A、B 和 C,这三个平台都有 MPU6050 设备。编写最简单的驱动框架如下图:

  每个平台下都有一个主机驱动和设备驱动,主机驱动是必要的,因为不同的平台 I2C 控制器不同。但设备驱动不建议这样。最好的方法是:每个平台的 IC2 控制器都提供一个统一的接口(主机驱动),每个设备只提供一个驱动程序(设备驱动):

  因为不止这一个 MPU6050 这一个设备,所以实际的驱动框架如下:

  这就是驱动的分隔,将主机驱动和设备驱动分隔开。一般 I2C 的主机驱动已经由半导体厂商写好,设备驱动一般由设备厂家写好,我们只需要提供设备信息即可,比如 I2C 设备就提供设备连接到哪个 I2C 接口上,I2C 的速度是多少等等。 相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根据获取到的设备信息来初始化设备。这相当于驱动只负责驱动,设备只负责设备。

  当我们需要向系统注册一个驱动的时候,总线就会在右侧的设备中去寻找,看有没有与之匹配的设备,如果有则关联起来。同样的,向系统注册一个设备的时候,总线会在左侧的驱动中查找看有没有与之匹配的设备,有的话也连接起来。

 

2. 驱动的分层

  驱动的分层的目的是为了在不同的层处理不同的内容。

  input 子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等等,最底层的就是设备原始驱动,负责获取输入设备的原始值,并把获取到的输入事件上报给 input 核心层。input 核心层会处理各种 IO 模型,并且提供 file_opeartions 操作集合。我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可。

 

二、platform 平台驱动模型简介 

  驱动分离里有 总线(bus)、驱动(driver)和设备(device)模型。有些 SOC 外设没有总线,所有 Linux 提出了 platform 虚拟总线,相应就有 platform_bus、platform_driver 和 platform_device。

 

1. platform 总线

  Linux 系统内核使用 bus_type 结构体表示总线,bus_type 结构体内容如下:

struct bus_type {
    const char *name;
    const char *dev_name;
    struct device *dev_root;
    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);
    int (*num_vf)(struct device *dev);
    int (*dma_configure)(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;
    bool need_parent_lock;
};

  其中,match 函数很重要,它是用来完成设备和驱动之间相互匹配。总线是使用 match 函数来根据注册的设备查找对应的的驱动,或者根据驱动查找对应的设备,所以每条总线都要用这个函数。

  match 函数有两个参数:devdrv,两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

  设备和驱动如何匹配?来看一下 platform_match 函数:

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);    // 将device结构体转化为platform_device结构体
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* 当 driver_override 设置时,只与匹配的驱动程序绑定 */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* 首先尝试 OF 样式匹配,of_match_table有个成员变量compatible,里面存放驱动的匹配表
       设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,
       如果有的话就表示设备和此驱动匹配*/
    if (of_driver_match_device(dev, drv))
        return 1;

    /* 然后尝试 ACPI 样式匹配 */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* 尝试使用 id 表进行匹配,因为之前把device_driver转化为platform_driver结构体,它里面有个
       id_table的成员变量,变量里保存了很多的id信息,id信息存放着platformd驱动所支持的驱动类型*/
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* 回退到驱动程序名称匹配,第三种方式不行之间比较驱动和设备name字段,看是否匹配 */
    return (strcmp(pdev->name, drv->name) == 0);
}

 

2. platform 驱动

  platform_driver 结构体表示 platform 驱动,以下是 platform_driver 结构体内容:

struct platform_driver {
    int (*probe)(struct platform_device *);    // 当驱动与设备匹配成功后probe函数会执行,如果自己要编全新的驱动,probe需要自行实现
    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;    // driver是device_deriver的结构体变量,Linux内核大量使用面向对象的思想。
                                    // device_driver相当于基类,提供最基础的驱动框架,platform_driver继承这个基类,又在原有的基础上添加成员变量
    const struct platform_device_id *id_table;    // id_table类似于表,每个元素类型都是platform_device_id
    bool prevent_deferred_probe;
};

   在编写 platform 驱动的时候,首先得定义一个 platform_driver 结构体变量,虽然每个成员变量都要实现,但重点实现匹配方法probe 函数。当驱动和设备匹配成功后 probe 函数会自动执行,具体的驱动程序在 probe 函数中编写。

  当定义好 platform_driver 结构体变量后,需要在驱动入口处调用 platform_driver_register 函数向 Linux 内核注册 platform 驱动。

/*
 * @description : 注册platform驱动
 * @param - handler : 信号的处理函数
 * @return : 负数,失败; 0,成功
 */
int platform_driver_register (struct platform_driver *driver);

  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 函数
 * 驱动与设备匹配成功后会执行这个函数
 * 之前驱动入口init函数里的字符驱动程序全部放在probe里面
 */
static int xxx_probe(struct platform_device *dev)
{
    ......
    cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
    /* 具体的操作 */
    return 0;
}

/*
 * platform_driver里的remove成员变量
 * 关闭platform驱动的时候,此函数执行
 * 之前的exit函数里的字节程序全部放到此处来
 */
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" },    // 当设备树中设备节点的 compatible 属性值为“xxx-gpio”的时候此设备就会与此驱动匹配
    { /* Sentinel */ }    // 这行是一个标记, of_device_id 表最后一个匹配项必须是空的
};

/*
 * platform 平台驱动结构体
 * 提供有设备树和无设备树两种匹配方式
 */
static struct platform_driver xxx_driver = {
    .driver = {
        .name = "xxx",        // 传统检查驱动和设备的name是否相投
        .of_match_table = xxx_of_match,    // 用于设备树下的驱动与设备检查
    },
    .probe = xxx_probe,
    .remove = xxx_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("zuozhongkai"); /* 模块作者 */

  platform 驱动并不是独立于字符、块、网络设备驱动的其他种类的驱动,而是为了驱动的分离与分层提出来的一种框架。

  总之,platform还是基于传统的字符、块、网络驱动设备,只是套上了platform,目的是使用总线、驱动、设备来实现驱动的分层和分离。

 

3. platform 设备

  platform_device 结构体表示 platform 设备。注意,如果内核支持设备树就不要使用 platform_device 来描述设备,改用设备树去描述。以下是 platform_device 结构体内容:

struct platform_device {
    const char *name;        // 表示设备名字,在platform驱动中的name字段一样,否则无法匹配到正确的驱动
    int id;
    bool id_auto;
    struct device dev;
    u64 platform_dma_mask;
    u32 num_resources;    // 资源数量
    struct resource *resource;    // 资源,也是设备信息,比如外设寄存器等。

    const struct platform_device_id *id_entry;
    char *driver_override; /* 要强制匹配的驱动程序名称 */

    /* MFD单元格指针 */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata archdata;
};

  以下是 resource 结构体内容:

struct resource {
    resource_size_t start;    // 资源的起始信息
    resource_size_t end;      // 资源的终止信息
    const char *name;         // 资源名字
    unsigned long flags;      // 资源类型
    unsigned long desc;
    struct resource *parent, *sibling, *child;
};

  可选的资源类型都定义在 include/linux/ioport.h 里面:

#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* 表示 IO 口的资源 */
#define IORESOURCE_MEM 0x00000200 /* 表示内存地址 */
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400 /* 中断号 */
#define IORESOURCE_DMA 0x00000800 /* DMA 通道号 */
#define IORESOURCE_BUS 0x00001000 /* 总线号 */
...

#define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

  注册和销毁设备信息函数如下:

/*
 * @description : 注册设备信息到Linux内核
 * @param - pdev : 要注册的 platform 设备
 * @return : 负数,失败; 0,成功
 */
int platform_device_register(struct platform_device *pdev);

/************************* 分割线 *************************/
/*
 * @description : 注销设备信息
 * @param - pdev : 要注销的 platform 设备
 * @return : 负数,失败; 0,成功
 */
void platform_device_unregister(struct platform_device *pdev);

  platfrom 设备信息框架如下:

/* 寄存器地址定义*/
#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,    // 这里flag表示内存地址
    },
    [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字段必须和驱动中的name字段一样
    .id = -1,
    .num_resources = ARRAY_SIZE(xxx_resources),        // 资源大小,ARRAY_SIZE()测量一个数组的元素个数
    .resource = xxx_resources,  
};

/* 设备模块加载 */
static int __init xxxdevice_init(void)
{
    return platform_device_register(&xxxdevice);    // 注册platform设备
}

/* 设备模块注销 */
static void __exit xxxdevice_exit(void)
{
    platform_device_unregister(&xxxdevice);        // 注销platform设备
}

module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

  上面这一段代码是不支持设备树的 Linux 版本使用,如果支持设备树后,就不需要用户手动注册 platform 设备,因为设备信息都放到了设备树中去描述,Linux 内核启动的时候就会去设备树中读取设备信息,将其变为 platform_device 形式。

 

三、程序编写

  在 /linux/atk-mpl/Drivers/ 文件夹下新建 17_platform,并且在里面创建 Vscode 工程,新建 leddevice.cleddriver.c

  在 leddevice.c 输入以下内容:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 寄存器物理地址 */
#define PERIPH_BASE     		     	(0x40000000)
#define MPU_AHB4_PERIPH_BASE			(PERIPH_BASE + 0x10000000)
#define RCC_BASE        		    	(MPU_AHB4_PERIPH_BASE + 0x0000)	
#define RCC_MP_AHB4ENSETR				(RCC_BASE + 0XA28)
#define GPIOI_BASE						(MPU_AHB4_PERIPH_BASE + 0xA000)	
#define GPIOI_MODER      			    (GPIOI_BASE + 0x0000)	
#define GPIOI_OTYPER      			    (GPIOI_BASE + 0x0004)	
#define GPIOI_OSPEEDR      			    (GPIOI_BASE + 0x0008)	
#define GPIOI_PUPDR      			    (GPIOI_BASE + 0x000C)	
#define GPIOI_BSRR      			    (GPIOI_BASE + 0x0018)
#define REGISTER_LENGTH					4

/* @description		: 释放flatform设备模块的时候此函数会执行	
 * @param - dev 	: 要释放的设备 
 * @return 			: 无
 */
static void	led_release(struct device *dev)
{
	printk("led device released!\r\n");	
}

/*  
 * 设备资源信息,也就是LED0所使用的所有寄存器
 */
static struct resource led_resources[] = {
	[0] = {
		.start 	= RCC_MP_AHB4ENSETR,
		.end 	= (RCC_MP_AHB4ENSETR + REGISTER_LENGTH - 1),
		.flags 	= IORESOURCE_MEM,
	},	
	[1] = {
		.start	= GPIOI_MODER,
		.end	= (GPIOI_MODER + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
	[2] = {
		.start	= GPIOI_OTYPER,
		.end	= (GPIOI_OTYPER + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
	[3] = {
		.start	= GPIOI_OSPEEDR,
		.end	= (GPIOI_OSPEEDR + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
	[4] = {
		.start	= GPIOI_PUPDR,
		.end	= (GPIOI_PUPDR + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
	[5] = {
		.start	= GPIOI_BSRR,
		.end	= (GPIOI_BSRR + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
};


/*
 * platform设备结构体 
 */
static struct platform_device leddevice = {
	.name = "stm32mp1-led",		// 这里很重要,设备name和驱动name要相匹配
	.id = -1,
	.dev = {
		.release = &led_release,
	},
	.num_resources = ARRAY_SIZE(led_resources),
	.resource = led_resources,
};
		
/*
 * @description	: 设备模块加载 
 * @param 		: 无
 * @return 		: 无
 */
static int __init leddevice_init(void)
{
	return platform_device_register(&leddevice);	// 注册设备
}

/*
 * @description	: 设备模块注销
 * @param 		: 无
 * @return 		: 无
 */
static void __exit leddevice_exit(void)
{
	platform_device_unregister(&leddevice);			// 注销设备
}

module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

  编写 leddriver.c 文件: 

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT		1			/* 设备号长度 	*/
#define LEDDEV_NAME		"platled"	/* 设备名字 	*/
#define LEDOFF 			0
#define LEDON 			1

/* 映射后的寄存器虚拟地址指针 */	// 这里没有用gpio子系统
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;

/* leddev设备结构体 */
struct leddev_dev{
	dev_t devid;			/* 设备号	*/
	struct cdev cdev;		/* cdev		*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备		*/		
};

struct leddev_dev leddev; 	/* led设备 */

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

/*
 * @description		: 取消映射
 * @return 			: 无
 */
void led_unmap(void)
{
		/* 取消映射 */
	iounmap(MPU_AHB4_PERIPH_RCC_PI);
	iounmap(GPIOI_MODER_PI);
	iounmap(GPIOI_OTYPER_PI);
	iounmap(GPIOI_OSPEEDR_PI);
	iounmap(GPIOI_PUPDR_PI);
	iounmap(GPIOI_BSRR_PI);
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	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 struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.write = led_write,
};

/*
 * @description		: flatform驱动的probe函数,当驱动与设备匹配以后此函数就会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int led_probe(struct platform_device *dev)
{	
	int i = 0, ret;
	int ressize[6];
	u32 val = 0;
	struct resource *ledsource[6];

	printk("led driver and device has matched!\r\n");
	/* 1、获取资源 */
	for (i = 0; i < 6; i++) {
		ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */
		if (!ledsource[i]) {
			dev_err(&dev->dev, "No MEM resource for always on\n");
			return -ENXIO;
		}
		ressize[i] = resource_size(ledsource[i]);	
	}	

	/* 2、初始化LED */
	/* 寄存器地址映射 */
 	MPU_AHB4_PERIPH_RCC_PI = ioremap(ledsource[0]->start, ressize[0]);
	GPIOI_MODER_PI = ioremap(ledsource[1]->start, ressize[1]);
  	GPIOI_OTYPER_PI = ioremap(ledsource[2]->start, ressize[2]);
	GPIOI_OSPEEDR_PI = ioremap(ledsource[3]->start, ressize[3]);
	GPIOI_PUPDR_PI = ioremap(ledsource[4]->start, ressize[4]);
	GPIOI_BSRR_PI = ioremap(ledsource[5]->start, ressize[5]);
	
	/* 3、使能PI时钟 */
    val = readl(MPU_AHB4_PERIPH_RCC_PI);
    val &= ~(0X1 << 8); /* 清除以前的设置 */
    val |= (0X1 << 8);  /* 设置新值 */
    writel(val, MPU_AHB4_PERIPH_RCC_PI);

    /* 4、设置PI0通用的输出模式。*/
    val = readl(GPIOI_MODER_PI);
    val &= ~(0X3 << 0); /* bit0:1清零 */
    val |= (0X1 << 0);  /* bit0:1设置01 */
    writel(val, GPIOI_MODER_PI);

    /* 5、设置PI0为推挽模式。*/
    val = readl(GPIOI_OTYPER_PI);
    val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/
    writel(val, GPIOI_OTYPER_PI);

    /* 6、设置PI0为高速。*/
    val = readl(GPIOI_OSPEEDR_PI);
    val &= ~(0X3 << 0); /* bit0:1 清零 */
    val |= (0x2 << 0); /* bit0:1 设置为10*/
    writel(val, GPIOI_OSPEEDR_PI);

    /* 7、设置PI0为上拉。*/
    val = readl(GPIOI_PUPDR_PI);
    val &= ~(0X3 << 0); /* bit0:1 清零*/
    val |= (0x1 << 0); /*bit0:1 设置为01*/
    writel(val,GPIOI_PUPDR_PI);

    /* 8、默认关闭LED */
    val = readl(GPIOI_BSRR_PI);
    val |= (0x1 << 0);
    writel(val, GPIOI_BSRR_PI);
	
	/* 注册字符设备驱动 */
	/* 1、申请设备号 */
	ret = alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);	
	if(ret < 0)
		goto fail_map;
	
	/* 2、初始化cdev */
	leddev.cdev.owner = THIS_MODULE;
	cdev_init(&leddev.cdev, &led_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
	if(ret < 0)
		goto del_unregister;
		
	/* 4、创建类 */
	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(leddev.class)) {
		goto del_cdev;
	}

	/* 5、创建设备 */
	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
	if (IS_ERR(leddev.device)) {
		goto destroy_class;
	}
	return 0;
	
destroy_class:
	class_destroy(leddev.class);
del_cdev:
	cdev_del(&leddev.cdev);
del_unregister:
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
fail_map:
	led_unmap();
	return -EIO;
}

/*
 * @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int led_remove(struct platform_device *dev)
{
	led_unmap();	/* 取消映射 */
	cdev_del(&leddev.cdev);/*  删除cdev */
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
	device_destroy(leddev.class, leddev.devid);	/* 注销设备 */
	class_destroy(leddev.class);	/* 注销类 */
	return 0;
}

/* 由于没有设备树,所以匹配列表是不用的 */

/* platform驱动结构体 */
static struct platform_driver led_driver = {
	.driver		= {
		.name	= "stm32mp1-led",			/* 驱动名字,用于和设备匹配 */
		//.of_match_table = xxx_of_match	// 这里也是用于设备树下的驱动和设备检查
	},
	.probe		= led_probe,
	.remove		= led_remove,
};
		
/*
 * @description	: 驱动模块加载函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init leddriver_init(void)
{
	return platform_driver_register(&led_driver);
}

/*
 * @description	: 驱动模块卸载函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

  新建 ledplatformApp.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

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
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]);	/* 要执行的操作:打开或关闭 */
	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;
}

 

四、运行测试

  编写 Makefile文件:

KERNELDIR := /home/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)

obj-m := leddevice.o
obj-m += leddriver.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  编译 leddevice.c 、leddriver.c 和 ledpaltformApp.c 文件:

make
arm-none-linux-gnueabihf-gcc ledplatformApp.c -o ledplatformApp

  将编译好的 leddevice.ko、leddriver.ko 和 ledplatformApp 复制:

sudo cp ledplatformApp leddevice.ko leddriver.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f

  开启开发板,输入以下命令:

cd lib/modules/5.4.31/
depmod
modprobe leddevice.ko    # 加载设备模块
modprobe leddriver.ko    # 加载驱动模块

   之后会看到这样的消息:

  led 驱动和设备驱动匹配成功。

  在 /sys/bus/platform/driver 目录下可以看到 stm32mp1-led 驱动文件:

  在 /sys/bus/platform/device 目录下可以看到 stm32mp1-led 设备文件:

   测试 LED 驱动:

./ledplatformApp /dev/platled 1        # 点亮
./ledplatformApp /dev/platled 0        # 熄灭

   卸载驱动:

rmmod leddevice.ko
rmmod leddriver.ko

 

总结

  这章起始不必太过关注没有设备树部分,关注为什么要有 platform 平台,因为需要把设备和驱动分开,一种更好的框架。

本文作者:烟儿公主

本文链接:https://www.cnblogs.com/toutiegongzhu/p/17677319.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   烟儿公主  阅读(123)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 夏日大冒险 暴躁的兔子
夏日大冒险 - 暴躁的兔子
00:00 / 00:00
An audio error has occurred.

作词 : 暴躁的兔子

作曲 : 暴躁的兔子

编曲 : IOF

混音:Gfanfan

出品:网易飓风

夏天 不要再浪费时间

实现 你承诺过的改变

别再 找一堆借口拖延

现在就和我一起飞向海边

人生苦短 你应该学会如何作乐

低着头还怎么应对挫折

人应该为自己活着

不用去迎合

要去寻欢作乐

撮合我的浪漫和悲欢

把这荒诞人生都塞满

生活难免磕磕绊绊

对抗生活的平庸就是浪漫

学会取悦自己逆风翻盘

去反抗变态的三观

把条条框框都砸烂

建立新的规则推翻谈判

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

去冒险

我真的不care你是否会喜欢我

不跟风被定义的美 全都是灾祸

我才不讨好大多数绝不与示弱

过好你的生活

你管我应该怎么快活

没有人能有资格审判

别人的生活和牵绊

快闭上你的高谈阔论

乘风破浪吧 理想的风帆

我就是肆意张扬又如何

我就是锋芒毕露又如何

我就是离经叛道又如何

我就是要出格 你管我要如何

我就是与众不同又如何

我就是特立独行又如何

我就是不知好歹又如何

你管我怎样出格 你管我如何

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

不知进退的人呐

和我一起去海边

聊聊曾经的理想 一起想当年

那曾想改变世界的人

是否还满腔热忱

不羁的我们放肆着

反抗那命运的指针

解放灵魂

推广:网易飓风

企划:贾焱祺

监制:徐思灵

出品人:谢奇笛