荔枝派zero驱动开发06:GPIO操作(platform框架)

参考:
正点原子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");
相关推荐
疯狂飙车的蜗牛2 小时前
从零玩转CanMV-K230(4)-小核Linux驱动开发参考
linux·运维·驱动开发
嵌入式进阶行者14 小时前
【驱动开发初级】内核模块静态和动态添加功能的步骤
驱动开发
逝灮15 小时前
【蓝桥杯——物联网设计与开发】拓展模块3 - 温度传感器模块
驱动开发·stm32·单片机·嵌入式硬件·物联网·蓝桥杯·温度传感器
__NULL__USER2 天前
petalinux-adi ---添加AD9361驱动(二)
linux·驱动开发
7yewh2 天前
嵌入式驱动RK3566 HDMI eDP MIPI 背光 屏幕选型与调试提升篇-eDP屏
linux·arm开发·驱动开发·嵌入式硬件·嵌入式linux·rk·edp
少年、潜行3 天前
树莓派3B+驱动开发(8)- i2c控制PCF8591
驱动开发·树莓派·3b+
千千道4 天前
深入理解 Linux 内核启动流程
linux·arm开发·驱动开发
SunshineBooming4 天前
qemu源码解析【05】qemu启动初始化流程
c++·驱动开发·源码软件
嵌入式大圣4 天前
单片机MQTT通信
驱动开发·单片机·嵌入式硬件·物联网
嵌入(师)5 天前
嵌入式驱动开发详解19(regmap驱动架构)
驱动开发·架构