参考:
正点原子Linux第五十四章 platform设备驱动实验
一张图掌握 Linux platform 平台设备驱动框架
上一篇:荔枝派zero驱动开发05:GPIO操作(使用GPIO子系统)
下一篇:更新中...
概述
platform是一种分层思想,所谓的 platform 驱动并不是独立于字符设备驱动、块设备驱动和网络设备驱动之外的其他种类的驱动;platform 只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动。
-
初学对具体设计的意图无需完全掌握,大致掌握运行流,在内核驱动中可以找到和读懂相关代码即可,用户编写驱动可以参考模板
-
参考下图,一图流,完全弄懂这张图就能完全掌握platform框架的思想 😃
图源:一张图掌握 Linux platform 平台设备驱动框架!
设备树修改
设备树直接使用上一章即可,无需修改
简要分析
在上一章源码基础上修改,添加platform相关的数据结构和匹配表,实现led_probe(即原驱动初始化函数),实现led_remove(原退出函数),并将驱动初始化改为platform_driver_register和platform_driver_unregister
//platform相关
// 匹配列表
static const struct of_device_id led_of_match[] = {
{ .compatible = "user,led" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "platform-led", // 驱动名字,将在/sys/bus/platform/drivers/下生成
.of_match_table = led_of_match, // 设备树匹配表
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
如上,设备树与led_of_match的属性值匹配后,即执行led_probe函数 ,同理:卸载驱动时会执行led_remove函数,卸载操作无需改动
函数原型:int (*probe)(struct platform_device *);
led_probe函数参考实现:
static int led_probe(struct platform_device *pdev)
{
int ret;
const char *str;
printk("led driver and device was matched!\r\n");
// 获取 LED 灯的 GPIO 号
// 进probe说明设备树已匹配,直接使用设备树节点即可
platform_led.led_gpio = of_get_named_gpio(pdev->dev.of_node, "gpios", 0);
if (platform_led.led_gpio < 0)
{
printk("can't get gpios");
return -EINVAL;
}
printk("gpio num = %d\r\n", platform_led.led_gpio);
// 向 gpio 子系统申请使用 GPIO
ret = gpio_request(platform_led.led_gpio, "green");
// 设置 PI0 为输出,并且输出低电平,默认打开 LED 灯
ret = gpio_direction_output(platform_led.led_gpio, 0);
...
// 注册字符设备
...
}
这里直接使用pdev->dev.of_node
引用设备节点即可
后续注册字符设备、操作函数ops等无改动,不再赘述
测试
chardevApp同样使用上一章的测试APP,功能正常实现
源码
platform_led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/printk.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
struct platform_led_dev
{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
int led_gpio;
};
struct platform_led_dev platform_led = {
.major = 0,
};
#define PIN_N 0 // 第0个引脚,PG0,绿色
#define DEV_NAME "platform_led"
#define LED_ON 0 // 上拉,低电平亮
#define LED_OFF 1
static int led_gpio_open(struct inode *inode, struct file *file)
{
file->private_data = &platform_led;
return 0;
}
static int led_gpio_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static int led_gpio_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t led_gpio_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
int ret = 0;
unsigned char databuf;
struct platform_led_dev *dev = file->private_data;
ret = copy_from_user(&databuf, user_buf, sizeof(databuf));
if (ret < 0)
{
pr_err("copy_from_user failed\r\n");
return -EFAULT;
}
if (databuf == 0 || databuf == '0') // LED_OFF
gpio_set_value(dev->led_gpio, 1);
if (databuf == 1 || databuf == '1') // LED_ON
gpio_set_value(dev->led_gpio, 0);
return 1;
}
static const struct file_operations platform_led_fops = {
.open = led_gpio_open,
.read = led_gpio_read,
.release = led_gpio_release,
.write = led_gpio_write,
};
static int led_probe(struct platform_device *pdev)
{
int ret;
const char *str;
printk("led driver and device was matched!\r\n");
// 获取 LED 灯的 GPIO 号
// 进probe说明设备树已匹配,直接使用设备树节点即可
platform_led.led_gpio = of_get_named_gpio(pdev->dev.of_node, "gpios", 0);
if (platform_led.led_gpio < 0)
{
printk("can't get gpios");
return -EINVAL;
}
printk("gpio num = %d\r\n", platform_led.led_gpio);
// 向 gpio 子系统申请使用 GPIO
ret = gpio_request(platform_led.led_gpio, "green");
if (ret)
{
printk(KERN_ERR "Failed to request gpio\n");
return ret;
}
// 设置 PI0 为输出,并且输出低电平,默认打开 LED 灯
ret = gpio_direction_output(platform_led.led_gpio, 0);
if (ret < 0)
{
printk("can't set gpio!\r\n");
}
// 注册字符设备
if (platform_led.major) // 定义了设备号,静态设备号
{
platform_led.devid = MKDEV(platform_led.major, 0);
ret = register_chrdev_region(platform_led.major, 1, DEV_NAME);
if (ret < 0)
{
pr_err("cannot register %s char driver.ret:%d\r\n", DEV_NAME, ret);
goto exit;
}
}
else // 没有定义设备号,动态申请设备号
{
ret = alloc_chrdev_region(&platform_led.devid, 0, 1, DEV_NAME);
if (ret < 0)
{
pr_err("cannot alloc_chrdev_region,ret:%d\r\n", ret);
goto exit;
}
platform_led.major = MAJOR(platform_led.devid);
platform_led.minor = MINOR(platform_led.devid);
}
printk("led major=%d,minor=%d\r\n", platform_led.major, platform_led.minor);
platform_led.cdev.owner = THIS_MODULE;
cdev_init(&platform_led.cdev, &platform_led_fops);
ret = cdev_add(&platform_led.cdev, platform_led.devid, 1);
if (ret < 0)
goto del_unregister;
platform_led.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(platform_led.class))
goto del_cdev;
platform_led.device = device_create(platform_led.class, NULL, platform_led.devid, NULL, DEV_NAME);
if (IS_ERR(platform_led.device))
goto destroy_class;
return 0;
// 注意 goto后的标签没有return操作,将顺序执行多个label直至return,这里反向写
destroy_class:
class_destroy(platform_led.class);
del_cdev:
cdev_del(&platform_led.cdev);
del_unregister:
unregister_chrdev_region(platform_led.devid, 1);
exit:
printk("init failed\r\n");
return -EIO;
}
static int led_remove(struct platform_device *pdev)
{
if (platform_led.led_gpio)
{
gpio_set_value(platform_led.led_gpio, 1);
gpio_free(platform_led.led_gpio);
}
cdev_del(&platform_led.cdev);
unregister_chrdev_region(platform_led.devid, 1);
device_destroy(platform_led.class, platform_led.devid);
class_destroy(platform_led.class);
return 0;
}
//platform相关
// 匹配列表
static const struct of_device_id led_of_match[] = {
{ .compatible = "user,led" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "platform-led", // 驱动名字,将在/sys/bus/platform/drivers/下生成
.of_match_table = led_of_match, // 设备树匹配表
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("USER");
MODULE_INFO(intree, "Y");