荔枝派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");
相关推荐
gopher951117 小时前
linux驱动开发-设备树
linux·驱动开发
三菱-Liu1 天前
三菱变频器以模拟量电流进行频率设定(电流输入)
驱动开发·单片机·嵌入式硬件·硬件工程·制造
三菱-Liu2 天前
三菱FX5U CPU 内置以太网功能
网络·驱动开发·硬件工程·制造·mr
让开,我要吃人了2 天前
OpenHarmony鸿蒙( Beta5.0)摄像头实践开发详解
驱动开发·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·openharmony
OH五星上将3 天前
如何更换OpenHarmony SDK API 10
驱动开发·嵌入式硬件·sdk·harmonyos·openharmony·鸿蒙开发
OH五星上将4 天前
OpenHarmony(鸿蒙南向开发)——标准系统移植指南(二)Linux内核
linux·驱动开发·嵌入式硬件·移动开发·harmonyos·鸿蒙开发·鸿蒙内核
芊言芊语4 天前
蓝牙驱动开发详解
驱动开发
让开,我要吃人了5 天前
OpenHarmony鸿蒙( Beta5.0)RTSPServer实现播放视频详解
驱动开发·嵌入式硬件·华为·移动开发·harmonyos·鸿蒙·openharmony
OH五星上将5 天前
OpenHarmony(鸿蒙南向开发)——轻量和小型系统三方库移植指南(二)
驱动开发·移动开发·harmonyos·内存管理·openharmony·鸿蒙内核·鸿蒙移植
CS_素锦少年5 天前
Linux_kernel驱动开发11
linux·运维·驱动开发