之前编辑了一个platform设备,我们需要写一个设备程序,之前使用设备树发现设备程序中的内容我们都可以用设备树来实现,因此我们可以通过platform总线驱动设备树中的硬件设备,这样我们就可以只写一个驱动程序就可以了。
使用platform总线和设备树中的节点之间有一个pinctrl节点来关联,这里需要改动的文件有两个:stm32mp15-pinctrl.dtsi和stm32mp157a-dk1.dts两个。
stm32mp15-pinctrl.dtsi中添加pinctrl节点:
led_pins_xhy:gpioled-0 {
pins {
pinmux= <STM32_PINMUX('A', 14, GPIO)>; //设置 PA14 复用为 GPIO 功能
drive-push-pull; //推挽输出
bias-pull-up; //内部上拉
output-high; //输出高电平
slew-rate =<0>; //速度为0 档
};
};
stm32mp157a-dk1.dts中xhy_led加入 pinctrl-names和pinctrl-0节点信息。
xhy_led {
compatible= "xhy,led";
status= "okay";
led-gpio= <&gpioa 14 GPIO_ACTIVE_LOW>;
pinctrl-names= "default";
pinctrl-0= <&led_pins_xhy>;
};
然后重新编译.dtb文件:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs
然后将dts/stm32mp157a-dk1.dtb复制到tftp文件夹中:
cp arch/arm/boot/dts/stm32mp157a-dk1.dtb /home/helloxhy/tftp/ -rf
驱动编写
这只需要编写一个led.c文件就可以了,由于这次即使用了platform总线有使用了设备树,属于之前几种编写方式的集合,所以我们从新编写led.c文件:
首先写入头文件:
#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>
然后定义设备结构体:
struct leddev_dev{
/* 用于注册设备 */
dev_tdevid;
structcdev cdev;
structclass *class;
structdevice *device;
/* 用于从设备树获取信息 */
structdevice_node *node;
intgpio_led;
};
struct leddev_dev leddev;
之后定义驱动结构体,由于使用了platform总线所以release由设备驱动定义:
static struct file_operations led_fops = {
.owner= THIS_MODULE,
.open= led_open,
.read =led_read,
.write= led_write,
};
其中.open和.read为空函数,原因是.read没有调用,这里只写没有读IO口,.open没有需要执行的信息。
所以只需要写.write程序就可以了,其中只调用了两个函数
copy_from_user(databuf, buf, cnt)获取需要执行的信息
gpio_set_value(leddev.gpio_led, 0)对IO口进行操作
:
static ssize_t led_write(struct file *filp,const char __user *buf, size_t cnt, loff_t *offt)
{
intretvalue;
unsignedchar databuf[1];
unsignedchar ledstat;
retvalue= copy_from_user(databuf, buf, cnt);
if(retvalue< 0) {
printk("kernelwrite failed!\r\n");
return-EFAULT;
}
ledstat= databuf[0];
if(ledstat == LEDON) {
lgpio_set_value(leddev.gpio_led,0);
}else if (ledstat == LEDOFF) {
gpio_set_value(leddev.gpio_led,1);
}
return0;
}
接下来需要对platform总线进行操作,驱动的入口和出口函数分别是对platform模块的加载和卸载:
static int __init leddriver_init(void)
{
returnplatform_driver_register(&led_driver); //加载led_driver
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);//卸载led_driver
}
由此可见需要定义platform模块,名称为led_driver,其中需要定义的由四个:
1设备树的名称,便于关联设备树。2、驱动的名称。
3、flatform驱动初始化。4、flatform驱动清除:
static struct platform_driver led_driver ={
.driver = {
.name = "xhy-led", // 2、驱动的名称。
.of_match_table = led_of_match, // 1设备树的名称,便于关联设备树
},
.probe = led_probe, //3、flatform驱动初始化
.remove = led_remove, //4、flatform驱动清除
};
接下来需要定义led_of_match其中.compatible是设备树中定义的内容:
static const struct of_device_idled_of_match[] = {
{.compatible = " xhy,led " },
{ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
最后需要编写flatform驱动初始化和flatform驱动清除两个函数:
flatform驱动初始化包括gpio的初始化和设备注册。
gpio初始化使用函数:of_get_named_gpio、gpio_request和gpio_direction_output,分别为从设备树中获取gpio,从flatform总线申请gpio,以及配置。
leddev.gpio_led = of_get_named_gpio(nd,"led-gpio", 0);
//"led-gpio"在设备树中的内容为led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;
ret = gpio_request(leddev.gpio_led,"LED0");
gpio_direction_output(leddev.gpio_led,1);
接下来是设备注册,直接使用新字符设备中的设备注册:
/* 1、设置设备号 */
ret =alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
if(ret< 0) {
pr_err("%sCouldn't alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
gotofree_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)
gotodel_unregister;
/*4、创建类 */
leddev.class= class_create(THIS_MODULE, LEDDEV_NAME);
if(IS_ERR(leddev.class)) {
gotodel_cdev;
}
/*5、创建设备 */
leddev.device= device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
if(IS_ERR(leddev.device)) {
gotodestroy_class;
}
最后是platform驱动的remove函数,主要是led关闭和设备的注销:
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);/* 注销类 */
编译测试:
Ubuntu编译:
make
arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp
cp led.ko ledApp /home/helloxhy/nfs/lib/modules/5.4.56/ -rf
板子上测试:
depmod
modprobe led
./ledApp /dev/newled 0
./ledApp /dev/newled 1
测试的过程中遇见了几个BUG,分享出来:
1、 err -2
如图发现gpio相关的很多函数查找不到,原因是没有进行申明,在程序接为需要申明下:
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xhy");
MODULE_INFO(intree, "Y");
2、 err -22
无法加载
执行modprobe led的时候没有打印信息,之后对dev进行查询发现没有加载驱动,执行rmmod led打印leddriver_exit,然后再执行modprobe led报错err -22。
查询资料发现其实Linux内核时调用了pinctrl的gpio接口的,打开/home/helloxhy/mylinux/drivers/pinctrl/stm32/pinctrl-stm32.c文件,搜索gpio_set发现由13处,其中第870行代码为
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,
};,
这是个结构体,把其中的true改为false,然后重新编译内核并使用新的内核重启:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- uImage dtbs LOADADDR=0XC -j16
cp arch/arm/boot/uImage /home/helloxhy/tftp/ -rf
然后重新执行: