背景
所有驱动开发都是基于全志T507(Android 10)进行开发,用于记录驱动开发过程。
简介
什么是platform驱动自己上网搜索了解。
在driver/linux/platform_device.h中定义了platform_driver结构体。
cpp
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;
};
驱动实现
步骤一、实现platform_driver 结构体。
在longan/kernel/linux-4.9/drivers目录下创建自己的文件夹,定义一个led_drv.c文件。
定义platform结构体:
cpp
/* platform驱动结构体 */
static struct platform_driver gpio_led_driver = {
.driver = {
.name = "devled", // 无设备树时,用于设备和驱动间的匹配
.of_match_table = gpio_led_of_match, // 有设备树后,利用设备树匹配表
},
.probe = gpio_led_probe,
.remove = gpio_led_remove,
};
1、实现的是以下两个方法
cpp
//驱动匹配成功后会回调这个函数,用来给驱动进行初始化操作
.probe = xxx,
//驱动被移除或被卸载后会回调这个函数,用来释放资源
.remove = xxx,
2、用于和指定的设备树驱动进行匹配
cpp
.driver = {
.name = "devled", // 无设备树时,用于设备和驱动间的匹配
.of_match_table = gpio_led_of_match, // 有设备树后,利用设备树匹配表
},
因此需要定义匹配列表,这里compatible就是设备树里面要定义的名称,这样才能正确识别到设备树配置的驱动。
cpp
/* 匹配列表 */
static const struct of_device_id gpio_led_of_match[] = {
{ .compatible = "devled" },
{}
};
在设备树中定义一个设备节点,注意这里led子节点根据实际情况进行配置。这里以PI1为例
cpp
led_test {
compatible = "devled";
pinctrl-names = "default";
status = "okay";
pinctrl-0 = <&pinctrl_led_test>;
led {
gpios = <&pio PI 1 1 2 0 1>;
state = "on";
};
};
在PIO节点下定义pinctrl_led_test节点,主要用于配置IO口。
cpp
soc@03000000 {
pio: pinctrl@0300b000 {
...
pinctrl_led_test: led_test_grp@0 {
allwinner,pins = "PI1";
allwinner,function = "led_test_grp";
allwinner,muxsel = <0x01>;
allwinner,drive = <0x00>;
allwinner,pull = <0x01>;
allwinner,data = <0x01>;
};
...
}
}
步骤2、定义设备节点
主要定义一下gpio_leds_priv,用来存放节点信息,用于生成/dev/xxx节点。
cpp
/* 存放led信息的结构体 */
struct gpio_led_data
{
char name[16]; // 设备名字
int pin; // gpio编号
int active; // 控制亮灭的标志
};
/* 存放led的私有属性 */
struct gpio_leds_priv
{
struct cdev cdev; // cdev结构体
struct class *dev_class; // 自动创建设备节点的类
int num_leds; // led的数量
struct gpio_led_data led; // 存放led信息的结构体数组
};
步骤3、初始化设备
定义一个初始化方法gpio_led_probe,并绑定到步骤1中的.probe
cpp
static int gpio_led_probe(struct platform_device *pdev)
{
struct gpio_leds_priv *priv; // 临时存放私有属性的结构体
struct device *dev; // 设备结构体
dev_t devno; // 设备的主次设备号
int i, rv = 0;
/* 1)解析设备树并初始化led状态 */
rv = parser_dt_init_led(pdev);
if( rv < 0 )
return rv;
/* 将之前存入的私有属性,放入临时的结构体中 */
priv = platform_get_drvdata(pdev);
/* 2)分配主次设备号 */
if (0 != dev_major)
{
/* 静态分配主次设备号 */
devno = MKDEV(dev_major, 0);
rv = register_chrdev_region(devno, priv->num_leds, "devled");
}
else
{
/* 动态分配主次设备号 */
rv = alloc_chrdev_region(&devno, 0, priv->num_leds, "devled");
dev_major = MAJOR(devno);
}
if (rv < 0)
{
dev_err(&pdev->dev, "major can't be allocated\n");
return rv;
}
/* 3)分配cdev结构体 */
cdev_init(&priv->cdev, &led_fops);
priv->cdev.owner = THIS_MODULE;
rv = cdev_add (&priv->cdev, devno , priv->num_leds);
if( rv < 0)
{
dev_err(&pdev->dev, "struture cdev can't be allocated\n");
goto undo_major;
}
/* 4)创建类,实现自动创建设备节点 */
priv->dev_class = class_create(THIS_MODULE, "led");
if( IS_ERR(priv->dev_class) )
{
dev_err(&pdev->dev, "fail to create class\n");
rv = -ENOMEM;
goto undo_cdev;
}
/* 5)创建设备 */
for(i=0; i<priv->num_leds; i++)
{
devno = MKDEV(dev_major, i);
dev = device_create(priv->dev_class, NULL, devno, NULL, "led");
if( IS_ERR(dev) )
{
dev_err(&pdev->dev, "fail to create device\n");
rv = -ENOMEM;
goto undo_class;
}
}
printk("success to install driver[major=%d]!\n", dev_major);
return 0;
undo_class:
class_destroy(priv->dev_class);
undo_cdev:
cdev_del(&priv->cdev);
undo_major:
unregister_chrdev_region(devno, priv->num_leds);
return rv;
}
parser_dt_init_led用于获取设备树中的节点信息,并进行初始化。
cpp
/* 解析设备树并初始化led属性 */
static int parser_dt_init_led(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node; // 当前设备节点
struct device_node *child; // 当前设备节点的子节点
struct gpio_leds_priv *priv; // 存放私有属性
int num_leds, gpio; // led数量和gpio编号
/* 获取该节点下子节点的数量 */
num_leds = 1;
if(num_leds <= 0)
{
dev_err(&pdev->dev, "fail to find child node\n");
return -EINVAL;
}
/* devm_kzalloc是内核内存分配函数,跟设备有关的,驱动卸载时,内存会被自动释放
* void * devm_kzalloc (struct device * dev, size_t size, gfp_t gfp)
* dev:申请内存的目标设备 size:申请的内存大小 gfp:申请内存的类型标志
* GFP_KERNEL是分配内核空间的内存时的一个标志位,无内存可用时可引起休眠
*/
priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(num_leds), GFP_KERNEL);
if (!priv)
{
return -ENOMEM;
}
priv->num_leds = 1;
/* 找到子节点并传给child */
child = of_get_child_by_name(np, "led");
/* 解析dts并且获取gpio口,函数返回值就得到gpio号,并且读取gpio现在的标志 */
gpio = of_get_named_gpio(child, "gpios", 0);
/* 将子节点的名字,传给私有属性结构体中的led信息结构体中的name属性 */
strncpy(priv->led.name, child->name, sizeof(priv->led.name));
printk(">>>>> init_led gpio=%d name=%s\n", gpio, priv->led.name);
/* 将gpio编号和控制亮灭的标志传给结构体
* active属性,1代表亮,0代表灭,初始属性为亮
*/
priv->led.active = 1;
priv->led.pin = gpio;
/* 申请gpio口,相较于gpio_request增加了gpio资源获取与释放功能 */
gpio_request(priv->led.pin, "led");
/* 设置gpio为输出模式,并设置初始状态 */
gpio_direction_output(priv->led.pin, 1);
/* 将led的私有属性放入platform_device结构体的device结构体中的私有数据中 */
platform_set_drvdata(pdev, priv);
return 0;
}
步骤4、移除设备
常规操作,将所有设备依次注销回收。
cpp
static int gpio_led_remove(struct platform_device *pdev)
{
struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
int i;
dev_t devno = MKDEV(dev_major, 0);
/* 注销设备结构体,class结构体和cdev结构体 */
for(i=0; i<priv->num_leds; i++)
{
devno = MKDEV(dev_major, i);
device_destroy(priv->dev_class, devno);
}
class_destroy(priv->dev_class);
cdev_del(&priv->cdev);
unregister_chrdev_region(MKDEV(dev_major, 0), priv->num_leds);
/* 将led的状态设置为灭 */
for (i = 0; i < priv->num_leds; i++)
{
gpio_set_value(priv->led.pin, ~priv->led.active);
}
printk("success to remove driver[major=%d]!\n", dev_major);
return 0;
}
步骤5、字符设备描述file_operations
cpp
static struct file_operations led_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
.compat_ioctl = led_ioctl,
};
实现led_open、led_release、led_ioctl。
cpp
static int led_open(struct inode *inode, struct file *file)
{
struct gpio_leds_priv *priv;
priv = container_of(inode->i_cdev, struct gpio_leds_priv, cdev);
file->private_data = priv;
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct gpio_leds_priv *priv;
priv = file->private_data;
printk(">>>>> led_ioctl command=%d arg=%d\n", cmd, arg);
switch (cmd)
{
case LED_ON:
printk("LED_ON:%d state: %d\n", priv->led.pin, gpio_get_value(priv->led.pin));
gpio_set_value(priv->led.pin, 1);
priv->led.active = 1;
printk("gpio_pin: %d\n", priv->led.pin);
break;
case LED_OFF:
printk("LED_OFF:%d state: %d\n", priv->led.pin, gpio_get_value(priv->led.pin));
gpio_set_value(priv->led.pin, 0);
priv->led.active = 0;
printk("gpio_pin: %d\n", priv->led.pin);
break;
default:
printk("Ioctl command=%d can't be supported\n", cmd);
break;
}
return 0;
}
步骤6、编译环境MakeFile
在自定义目录下创建Makefile文件。这里默认加载设备。
cpp
obj-y += led_drv.o
也可以创建Kconfig文件,添加配置项后就可以在驱动中进行配置。
cpp
config CHR_DEV_BASE
tristate "First chr device"
default y
help
This is first che device.
cpp
obj-${CONFIG_CHR_DEV_BASE} += led_drv.o
源码
附上全部驱动代码
cpp
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#define LED_OFF 0
#define LED_ON 1
static int dev_major = 0;
/* 存放led信息的结构体 */
struct gpio_led_data
{
char name[16]; // 设备名字
int pin; // gpio编号
int active; // 控制亮灭的标志
};
/* 存放led的私有属性 */
struct gpio_leds_priv
{
struct cdev cdev; // cdev结构体
struct class *dev_class; // 自动创建设备节点的类
int num_leds; // led的数量
struct gpio_led_data led; // 存放led信息的结构体数组
};
/* 为led私有属性开辟存储空间的函数 */
static inline int sizeof_gpio_leds_priv(int num_leds)
{
return sizeof(struct gpio_leds_priv) + (sizeof(struct gpio_led_data) * num_leds);
}
/* 解析设备树并初始化led属性 */
static int parser_dt_init_led(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node; // 当前设备节点
struct device_node *child; // 当前设备节点的子节点
struct gpio_leds_priv *priv; // 存放私有属性
int num_leds, gpio; // led数量和gpio编号
/* 获取该节点下子节点的数量 */
num_leds = 1;
if(num_leds <= 0)
{
dev_err(&pdev->dev, "fail to find child node\n");
return -EINVAL;
}
/* devm_kzalloc是内核内存分配函数,跟设备有关的,驱动卸载时,内存会被自动释放
* void * devm_kzalloc (struct device * dev, size_t size, gfp_t gfp)
* dev:申请内存的目标设备 size:申请的内存大小 gfp:申请内存的类型标志
* GFP_KERNEL是分配内核空间的内存时的一个标志位,无内存可用时可引起休眠
*/
priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(num_leds), GFP_KERNEL);
if (!priv)
{
return -ENOMEM;
}
priv->num_leds = 1;
/* 找到子节点并传给child */
child = of_get_child_by_name(np, "led");
/* 解析dts并且获取gpio口,函数返回值就得到gpio号,并且读取gpio现在的标志 */
gpio = of_get_named_gpio(child, "gpios", 0);
/* 将子节点的名字,传给私有属性结构体中的led信息结构体中的name属性 */
strncpy(priv->led.name, child->name, sizeof(priv->led.name));
printk(">>>>> init_led gpio=%d name=%s\n", gpio, priv->led.name);
/* 将gpio编号和控制亮灭的标志传给结构体
* active属性,1代表亮,0代表灭,初始属性为亮
*/
priv->led.active = 1;
priv->led.pin = gpio;
/* 申请gpio口,相较于gpio_request增加了gpio资源获取与释放功能 */
gpio_request(priv->led.pin, "led");
/* 设置gpio为输出模式,并设置初始状态 */
gpio_direction_output(priv->led.pin, 1);
/* 将led的私有属性放入platform_device结构体的device结构体中的私有数据中 */
platform_set_drvdata(pdev, priv);
return 0;
}
static int led_open(struct inode *inode, struct file *file)
{
struct gpio_leds_priv *priv;
priv = container_of(inode->i_cdev, struct gpio_leds_priv, cdev);
file->private_data = priv;
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct gpio_leds_priv *priv;
priv = file->private_data;
printk(">>>>> led_ioctl command=%d arg=%d\n", cmd, arg);
switch (cmd)
{
case LED_ON:
printk("LED_ON:%d state: %d\n", priv->led.pin, gpio_get_value(priv->led.pin));
gpio_set_value(priv->led.pin, 1);
priv->led.active = 1;
printk("gpio_pin: %d\n", priv->led.pin);
break;
case LED_OFF:
printk("LED_OFF:%d state: %d\n", priv->led.pin, gpio_get_value(priv->led.pin));
gpio_set_value(priv->led.pin, 0);
priv->led.active = 0;
printk("gpio_pin: %d\n", priv->led.pin);
break;
default:
printk("Ioctl command=%d can't be supported\n", cmd);
break;
}
return 0;
}
static struct file_operations led_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
/**
* 注: 20250403 yozad.von
* 如果是32位内核+32位用户空间或者64位内核+64位用户空间只配置unlocked_ioctl就行
*/
.unlocked_ioctl = led_ioctl,
/**
* 注: 20250403 yozad.von
* adb shell getprop ro.product.cpu.abi查安卓版本是armeabi-v7a,即32位系统。
* compat_ioctl,是为兼容32位的用户空间程序在64位内核上执行ioctl操作
* 所以测试发现只有写1是正常的,写其他值都返回-1
*/
.compat_ioctl = led_ioctl,
};
static int gpio_led_probe(struct platform_device *pdev)
{
struct gpio_leds_priv *priv; // 临时存放私有属性的结构体
struct device *dev; // 设备结构体
dev_t devno; // 设备的主次设备号
int i, rv = 0;
/* 1)解析设备树并初始化led状态 */
rv = parser_dt_init_led(pdev);
if( rv < 0 )
return rv;
/* 将之前存入的私有属性,放入临时的结构体中 */
priv = platform_get_drvdata(pdev);
/* 2)分配主次设备号 */
if (0 != dev_major)
{
/* 静态分配主次设备号 */
devno = MKDEV(dev_major, 0);
rv = register_chrdev_region(devno, priv->num_leds, "devled");
}
else
{
/* 动态分配主次设备号 */
rv = alloc_chrdev_region(&devno, 0, priv->num_leds, "devled");
dev_major = MAJOR(devno);
}
if (rv < 0)
{
dev_err(&pdev->dev, "major can't be allocated\n");
return rv;
}
/* 3)分配cdev结构体 */
cdev_init(&priv->cdev, &led_fops);
priv->cdev.owner = THIS_MODULE;
rv = cdev_add (&priv->cdev, devno , priv->num_leds);
if( rv < 0)
{
dev_err(&pdev->dev, "struture cdev can't be allocated\n");
goto undo_major;
}
/* 4)创建类,实现自动创建设备节点 */
priv->dev_class = class_create(THIS_MODULE, "led");
if( IS_ERR(priv->dev_class) )
{
dev_err(&pdev->dev, "fail to create class\n");
rv = -ENOMEM;
goto undo_cdev;
}
/* 5)创建设备 */
for(i=0; i<priv->num_leds; i++)
{
devno = MKDEV(dev_major, i);
dev = device_create(priv->dev_class, NULL, devno, NULL, "led");
if( IS_ERR(dev) )
{
dev_err(&pdev->dev, "fail to create device\n");
rv = -ENOMEM;
goto undo_class;
}
}
printk("success to install driver[major=%d]!\n", dev_major);
return 0;
undo_class:
class_destroy(priv->dev_class);
undo_cdev:
cdev_del(&priv->cdev);
undo_major:
unregister_chrdev_region(devno, priv->num_leds);
return rv;
}
static int gpio_led_remove(struct platform_device *pdev)
{
struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
int i;
dev_t devno = MKDEV(dev_major, 0);
/* 注销设备结构体,class结构体和cdev结构体 */
for(i=0; i<priv->num_leds; i++)
{
devno = MKDEV(dev_major, i);
device_destroy(priv->dev_class, devno);
}
class_destroy(priv->dev_class);
cdev_del(&priv->cdev);
unregister_chrdev_region(MKDEV(dev_major, 0), priv->num_leds);
/* 将led的状态设置为灭 */
for (i = 0; i < priv->num_leds; i++)
{
gpio_set_value(priv->led.pin, ~priv->led.active);
}
printk("success to remove driver[major=%d]!\n", dev_major);
return 0;
}
/* 匹配列表 */
static const struct of_device_id gpio_led_of_match[] = {
{ .compatible = "devled" },
{}
};
MODULE_DEVICE_TABLE(of, gpio_led_of_match);
/* platform驱动结构体 */
static struct platform_driver gpio_led_driver = {
.driver = {
.name = "devled", // 无设备树时,用于设备和驱动间的匹配
.of_match_table = gpio_led_of_match, // 有设备树后,利用设备树匹配表
},
.probe = gpio_led_probe,
.remove = gpio_led_remove,
};
module_platform_driver(gpio_led_driver);
MODULE_LICENSE("GPL");