STM32MP157驱动开发——platform设备驱动(下)


相关文章:
STM32MP157驱动开发——pinctrl和gpio子系统
STM32MP157驱动开发——platform设备驱动(上)
STM32MP157驱动开发——platform设备驱动(中)

0.前言

  在前两节中,分别对platform设备驱动框架进行了一些了解,以及针对无设备树的Linux内核开发字符型设备驱动。本节就针对设备树模式下的Linux内核进行字符驱动开发,也是现在比较常用的驱动开发方式。在之后的一些复杂设备驱动开发中,也大多使用这种框架开发。

一、设备树下的platform

1.简介

  platform 驱动框架分为总线、设备和驱动,其中总线不需要驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动时只要关注于设备和驱动的具体实现即可。在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和 platform_driver,分别代表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。

2.开发步骤

①修改 pinctrl-stm32.c 文件

  在之前章节中有了解过 pinctrl 与 gpio子系统,但是后续的一些字符型驱动开发都是用的gpio子系统。按道理来讲,在我们使用某个引脚的时候需要先配置其电气属性,比如复用、输入还是输入、默认上下拉等!但是在前面的实验中均没有配置引脚的电气属性,也就是引脚的 pinctrl 配置。这是因为 ST 针对 STM32MP1 提供的 Linux 系统中,其 pinctrl 配置的电气属性只能在 platform 平台下被引用,前面的实验都没用到 platform,所以 pinctrl 配置是不起作用的。
注:原子哥教程在使用 NXP 的 I.MX6ULL 芯片时,Linux 系统启动运行过程中会自动解析设备树下的 pinctrl 配置,然后初始化引脚的电气属性,不需要 platform 驱动框架。所以 pinctrl 什么时候有效,不同的芯片厂商有不同的处理方法,一切以实际所使用的芯片为准。
  对于 STM32MP1 来说,在使用 pinctrl 的时候需要修改一下 pinctrl-stm32.c 这个文件,否则当某个引脚用作 GPIO 的时候会提示此引脚无法申请到,该文件在内核源码目录下的 drivers/pinctrl/stm32目录中:
在这里插入图片描述
上图中就提示 PI0 这个 IO 已经被其他外设申请走了,不能再申请。在 pinctrl-stm32.c 中:

static const struct pinmux_ops stm32_pmx_ops = {
	.get_functions_count = stm32_pmx_get_funcs_cnt,
	.get_function_name = stm32_pmx_get_func_name,
	.get_function_groups = stm32_pmx_get_func_groups,
	.set_mux = stm32_pmx_set_mux,
	.gpio_set_direction = stm32_pmx_gpio_set_direction,
	.strict = true,
};

.strict变量的值改为 false,然后使用make uImage LOADADDR=0XC2000040 -j16重新编译内核,使用新编译出的uImage启动开发板即可。

②创建设备的pinctrl节点

  在platform驱动框架下必须使用 pinctrl 来配置引脚复用功能。在 stm32mp15-pinctrl.dtsi 中,STM32MP1 的所有引脚 pinctrl 配置都是在这个文件里面完成的,以LED为例,在 pinctrl 节点下添加如下所示内容:

led_pins_a: gpioled-0 {
	pins {
		pinmux = <STM32_PINMUX('I', 0, GPIO)>;
		drive-push-pull;
		bias-pull-up;
		output-high;
		slew-rate = <0>;
	};
};

led_pins_a 节点就是 LED 的 pinctrl 配置。设置 PI0 复用为 GPIO 功能、设置 PI0 为推挽输出、设置 PI0 内部上拉、设置 PI0 默认输出高电平、设置 PI0 的速度为 0 档,也就是最慢。
在这里插入图片描述

③在设备树中创建设备节点

  接下来要在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动。
  修改之前在设备树 stm32mp157d-atk.dts 中创建的 gpioled 节点:

gpioled {
		compatible = "amonter,led";
		pinctrl-names = "default";
		status = "okay";
		pinctrl-0 = <&led_pins_a>;
		led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;
};

compatible 属性值为“ amonter,led”,因此之后在编写 platform 驱动时 of_match_table 属性表中要有“amonter,led”。
pinctrl-0 属性设置 LED 的 PIN 对应的 pinctrl 节点,也就是在 stm32mp15-pinctrl.dtsi 中编写的 led_pins_a。

④编写 platform 驱动时要注意兼容属性

  在使用设备树时 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以,of_match_table 将会尤为重要,比如本节驱动开发的 platform 驱动中,platform_driver 就可以按照如下所示设置:

static const struct of_device_id led_of_match[] = {
	{ .compatible = "amonter,led" }, /* 兼容属性 */
	{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, led_of_match);

static struct platform_driver led_platform_driver = {
	.driver = {
		.name = "stm32mp1-led",
		.of_match_table = led_of_match,
	},
	.probe = led_probe,
	.remove = led_remove,
};

of_device_id 表:也就是驱动的兼容表,是一个数组,每个数组元素为 of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备,也就是在设备树中创建的 gpioled 这个设备。
compatible:值为“amonter,led”,驱动中的 compatible 属性和设备中的 compatible 属性相匹配,然后驱动中对应的 probe 函数就会执行。led_of_match 表最后是一个空元素,在编写 of_device_id 时最后一个元素一定要为空!
MODULE_DEVICE_TABLE:用来声明一下 led_of_match 这个设备匹配表。
platform_driver:其中 of_match_table 匹配表为上面创建的 led_of_match,至此就设置好 platform 驱动的匹配表了。还有一些其他的操作函数和参数匹配等。
最后就是编写驱动程序,基于设备树的 platform 驱动和上一章无设备树的 platform 驱动基本一样,都是当驱动和设备匹配成功以后先根据设备树里的 pinctrl 属性设置 PIN 的电气特性,再去执行 probe 函数。需要在 probe 函数里面执行字符设备驱动那一套,当注销驱动模块的时候 remove 函数就会执行,步骤基本相同。

3.检查引脚复用配置

①检查引脚pinctrl配置

  STM32MP1 的一个引脚可以复用为多种功能,比如 PI0 可以作为 GPIO、TIM5_CH4、SPI2_NSS、DCMI_D13、LCD_G5 等。在做 STM32 单片机开发时,一个 IO 可以被多个外设使用,比如 PI0 同时作为 TIM5_CH4、LCD_G5,但是同一时刻只能用做一个功能,比如做 LCD_G5 的时候就不能做 TIM5_CH4!在嵌入式 Linux 下,要严格按照一个引脚对应一个功能来设计硬件,比如 PI0 现在要用作 GPIO 来驱动 LED 灯,那么就不能将 PI0 作为其他功能,比如你在设计硬件的时候就不能再将 PI0 作为 LCD_G5。
  正点原子 STM32MP1 开发板上将 PI0 连接到了 LED0 上,也就是将其用作普通的 GPIO,对应的 pinctrl 配置就如上文 .dtsi 中配置的。但是 stm32mp15-pinctrl.dtsi 是 ST 根据官方 EVK开发板编写的,因此 PI0 就可能被 ST 官方用作其他功能。
stm32mp15-pinctrl.dtsi:
在这里插入图片描述
在官方的 pinctrl 文件中,将 PI0 引脚作为了 LCD 的 G5 引脚。为了保证引脚功能的唯一性,所以需要将此文件中有关 PI0 所有的其他功能定义全部屏蔽掉。

②检查GPIO占用

上一小节是检查 PI0 引脚有没有被复用为多个设备,这一小节则检查官方有没有将 PI0 这个 GPIO 分配给其他设备。所以需要检查 .dts 文件中是否有其他文件使用了这个 GPIO,在本节驱动开发中没有使用,但是在实际项目开发中一定要注意这两个复用问题。

二、实验程序编写

1.修改设备树及相关文件

  如上文所述,使用 platform 框架开发驱动,需要在 pinctrl 节点以及设备树中的定义相关设备信息,以供后续的驱动开发中使用。

2.驱动程序

#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     "dtsplatled" /* 设备名字 */
#define LEDOFF          0
#define LEDON           1

/* leddev 设备结构体 */
struct leddev_dev
{
    dev_t devid;              /* 设备号 */
    struct cdev cdev;         /* cdev */
    struct class *class;      /* 类 */
    struct device *device;    /* 设备 */
    struct device_node *node; /* LED 设备节点 */
    int gpio_led;             /* LED 灯 GPIO 标号 */
};

struct leddev_dev leddev; /* led 设备 */

void led_switch(u8 sta)
{
    if (sta == LEDON)
    {
        gpio_set_value(leddev.gpio_led, 0);
    }
    else if (sta == LEDOFF)
    {
        gpio_set_value(leddev.gpio_led, 1);
    }
}

static int led_gpio_init(struct device_node *nd)
{
    int ret;

    /* 从设备树中获取 GPIO */
    leddev.gpio_led = of_get_named_gpio(nd, "led-gpio", 0);
    if (!gpio_is_valid(leddev.gpio_led))
    {
        printk(KERN_ERR "leddev: Failed to get led-gpio\n");
        return -EINVAL;
    }

    /* 申请使用 GPIO */
    ret = gpio_request(leddev.gpio_led, "LED0");
    if (ret)
    {
        printk(KERN_ERR "led: Failed to request led-gpio\n");
        return ret;
    }

    /* 将 GPIO 设置为输出模式并设置 GPIO 初始电平状态 */
    gpio_direction_output(leddev.gpio_led, 1);

    return 0;
}

/*打开设备,open函数*/
static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

/*向设备写数据,write函数*/
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);
    }
    else if (ledstat == LEDOFF)
    {
        led_switch(LEDOFF);
    }
    return 0;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
};

/*probe函数,当设备与驱动匹配成功后就会执行此函数*/
static int led_probe(struct platform_device *pdev)
{
    int ret;

    printk("led driver and device was matched!\r\n");

    /* 初始化 LED */
    ret = led_gpio_init(pdev->dev.of_node);
    if (ret < 0)
        return ret;

    /* 1、设置设备号 */
    ret = alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
    if (ret < 0)
    {
        pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
        goto free_gpio;
    }

    /* 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);
free_gpio:
    gpio_free(leddev.gpio_led);
}

/*remove函数*/
static int led_remove(struct platform_device *dev)
{
    gpio_set_value(leddev.gpio_led, 1); /* 卸载驱动的时候关闭 LED */
    gpio_free(leddev.gpio_led);         /* 注销 GPIO */
    cdev_del(&leddev.cdev);             /* 删除 cdev */
    unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    device_destroy(leddev.class, leddev.devid); /* 注销设备 */
    class_destroy(leddev.class);                /* 注销类 */
    return 0;
}

/*匹配列表*/
static const struct of_device_id led_of_match[] = {
    {.compatible = "amonter,led"},
    {/* Sentinel */}
};

MODULE_DEVICE_TABLE(of, led_of_match);

/* platform 驱动结构体 */
static struct platform_driver led_driver = {
    .driver = {
        .name = "stm32mp1-led",         /* 驱动名字,用于和设备匹配 */
        .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("amonter");
MODULE_INFO(intree, "Y");

①自定义函数 led_gpio_init,该函数的参数是 struct device_node 类型的指针,也就是 led 对应的设备节点,当调用函数的时候传递进来
②platform 下的 probe 函数:led_probe,当设备树中的设备节点与驱动之间匹配成功,会先去初始化 pinctrl 里面配置的 IO,也就是根据上文设备树中的属性进行配置,然后再执行 probe 函数。在此程序段中调用 led_gpio_init 函数时,将 pdev->dev.of_node 作为参数传递到函数中,platform_device 结构体中内置了一个 device 结构体类型的成员变量 dev。 在 device 结构体中定义了一个 device_node 类型的指针变量 of_node,使用设备树的情况下,当匹配成功之后,of_node 会指向设备树中定义的节点,所以在这里我们不需要通过调用 of_find_node_by_path(“/gpioled”)函数得到 led 的节点。原来在驱动加载函数里面做的工作现在全部放到 probe 函数里面完成。
③platform 下的 remove 函数:led_remove,当 platform 驱动模块被卸载时此函数就会执行。在此函数里面释放内存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。
④of_device_id 匹配表,描述了此驱动都和什么样的设备匹配,其中添加了一条值为"amonter,led"的 compatible 属性值,当设备树中某个设备节点的 compatible 属性值也为“amonter,led”的时候就会与此驱动匹配。
⑤platform_driver 驱动结构体变量 led_driver,在此函数段中设置这个 platform 驱动的名字为“stm32mp1-led”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“stm32mp1-led”的文件。后续程序段绑定 platform 驱动的 of_match_table 表。
⑥platform 驱动模块入口函数,在此函数里面通过 platform_driver_register 向 Linux 内核注册一个 platform 驱动 led_driver
⑦platform 驱动驱动模块出口函数,在此函数里面通过 platform_driver_unregister 从 Linux 内核卸载一个 platform 驱动 led_driver。

三、程序编译与测试

  将Makefile中的目标文件修改成本节的源程序目标文件即可。测试程序App与上一节使用的相同。编译完成后将对应的模块文件与可执行文件拷贝到nfs相应的目录下即可启动开发板进行测试。
在这里插入图片描述
在/sys/bus/platform/drivers/目录下存在名为“stm32mp1-led”这个驱动文件。同时内核也提示驱动与设备匹配成功。
在这里插入图片描述
在/sys/bus/platform/devices/目录下也存在gpioled设备文件。
然后即可使用测试程序对驱动进行测试,同样也可以使用rmmod命令卸载驱动程序。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值