I.MX6U嵌入式Linux Platform设备驱动开发(2)自带LED和杂项驱动
1、自带LED
要使用 Linux 内核自带的 LED 灯驱动首先得先配置 Linux 内核,使能自带的 LED 灯驱
动,进入Linux源码
/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
make menuconfig
内核自带的驱动,都是通过图形化界面配置,选择使能或者不使能。使能驱动以后在.config里面就会存在:CONFIG_LEDS_GPIO=y
在linux内核源码里面一般驱动文件夹下Makefile会使用CONFIG_XXX来决定要编译哪个文件。
LED 灯驱动文件为/drivers/leds/leds-gpio.c
也可以通过leds-gpio.c来查找配置的地方。打开配置页面,输入/,可以进入查找页面,输入CONFIG_XXX开头的,可以找到,有时候可以找到很多条,需要哪一条就输入那条前面的数字,即可进入使用说明。
1.1、自带驱动分析
LED 驱动会采用 module_platform_driver 函数向 Linux 内核注册platform 驱动,module_platform_driver 定义在 include/linux/platform_device.h 文件中,为一个宏。
module_platform_driver 依赖 module_driver,module_driver 也是一个宏,定义在include/linux/device.h 文件中。
将:
module_platform_driver(gpio_led_driver)
展开以后就是:
static int __init gpio_led_driver_init(void)
{
return platform_driver_register (&(gpio_led_driver));
}
module_init(gpio_led_driver_init);
static void __exit gpio_led_driver_exit(void)
{
platform_driver_unregister (&(gpio_led_driver) );
}
module_exit(gpio_led_driver_exit);
因此 module_platform_driver 函数的功能就是完成 platform 驱动的注册和删除。
当驱动和设备匹配以后 gpio_led_probe 函数就会执行,此函数主要是从设备树中获取 LED
灯的 GPIO 信息;
如果使用设备树的话,使用 gpio_leds_create 函数从设备树中提取设备信息,获取到的 LED 灯 GPIO 信息保存在返回值中。
1.2、内核自带LED驱动使用
(1)首先将驱动编译进内核里面;
(2)根据绑定文档在设备树里面添加对应的设备节点信息;
如果无设备树,那么就要使用platform_device_register向总线注册设备;
如果有设备树,那么直接修改设备树,添加指定的节点。
绑定文档所在位置:源码->Documentation->leds,参照文档来添加设备树节点。
在Ubuntu中,打开内核源码找到,imx6ul-alientek-emmc.dts,找到根节点,在根节点的最后面添加子节点,
dtsleds {
compatible = "gpio-led";//一定要和驱动对应起来,找到gpio的匹配表,属性值为gpio-led
led0 {
lable = "red";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpioled>;
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
default-state = "on";
};
};
注意:别忘了检查gpio1 IO03有没有被其他节点占用。直接搜索 gpio1 3
make dtbs
cp arch/arm/boot/zImage /home/yang/linux/tftpboot/ -f
cp arch/arm/boot/dts/imx6uii-alientek-emmc.dtb /home/yang/linux/tftpboot/ -f
使用新的内核和设备树启动开发板。
还可以修改设备树子节点,灯默认打开,并作为心跳灯
dtsleds {
compatible = "gpio-led";//一定要和驱动对应起来,找到gpio的匹配表,属性值为gpio-led
led0 {
lable = "red";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpioled>;
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
default-state = "on";
linux,default-trigger = "heartbeat";
};
};
此时,开发板作为心跳灯在闪烁,也可以从控制台关闭心跳灯,打开或者关闭灯。
进入/sys/bus/platform/devices/dtsleds 这个目录。这个目录中有一个leds文件夹,进入到leds目录中,有一个red子目录,这个子目录的名字就是我们在设备树中第 5 行设置的 label 属性值。
/sys/devices/platform/dtsleds/leds # cd red/
/sys/devices/platform/dtsleds/leds/red/ # ls
/sys/devices/platform/dtsleds/leds/red/ # cat trigger
/sys/devices/platform/dtsleds/leds/red/ # echo none > trigger
/sys/devices/platform/dtsleds/leds/red/ # echo heartbeat > trigger
/sys/devices/platform/dtsleds/leds/red/ # ech0 1 > brightness
也可以参照用户体验的那个文档来操作。
2、MISC驱动
MISC 驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。MISC 驱动其实就是最简单的字符设备驱动,通常嵌套在 platform 总线驱动中。
2.1、MISC设备驱动简介
所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。
MISC 设备会自动创建 cdev,不需要像我们以前那样手动创建。
需要向 Linux 注册一个 miscdevice 设备,miscdevice是一个结构体,定义在文件 include/linux/miscdevice.h 中。
在miscdevice这个结构体中,定义一个 MISC 设备,以后我们需要设置 minor、name 和 fops 这三个成员变量。
(1)minor 表示子设备号,MISC 设备的主设备号为 10,这个是固定的,需要用户指定子设备号,Linux 系统已经预定义了一些 MISC 设备的子设备号,这些预定义的子设备号定义在
include/linux/miscdevice.h 文件中,我们在使用的时候可以从这些预定义的子设备号中挑选一个,当然也可以自己定义,只要这个子设备号在***当前系统中***没有被其他设备使用接口。
(2)name 就是此 MISC 设备名字,当此设备注册成功以后就会在/dev 目录下生成一个名为 name的设备文件。
(3)fops 就是字符设备的操作集合,MISC 设备驱动最终是需要使用用户提供的 fops操作集合。
当设置好 miscdevice 以后就需要使用 misc_register 函数向系统中注册一个 MISC 设备。们卸载设备驱动模块的时候需要调用 misc_deregister 函数来注销掉 MISC 设备。
2.2、实验程序编写
本章实验我们采用 platform 加 misc 的方式编写 beep 驱动,这也是实际的 Linux 驱动中很常用的方法。采用 platform 来实现总线、设备和驱动,misc 主要负责完成字符设备的创建。
cd /liunx/IMX6ULL/Linux_Driver/
mkdir 19_miscbeep
cp 18_dtsplatform/ * 19_miscbeep/ -rf
cp 18_dtsplatform/.vscode 19_miscbeep/ -rf
cd 19_miscbeep/
ls
rm platledAPP
rm dtsplatform.code-workspace
mv leddriver.c miscbeep.c
2.2.1、beep设备树
需要在 imx6ull-alientek-emmc.dts 文件中创建蜂鸣器设备节点,这里我们直接使用 前面创建的 beep 这个设备节点即可。
beep{
compatible = "alientek,beep";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
2.2.2、驱动
将Makefile中的.o文件名字修改一下:
打开miscbeep.c文件,只保留头文件,将剩余的删除。
编写驱动入口、驱动出口函数
/* 驱动入口函数 */
static int __init miscbeep_init(void)
{
return 0;
}
/* 驱动出口函数 */
static void __exit miscbeep_exit(void)
{
}
module_init(miscbeep_init);
module_exit(miscbeep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");
编译一下,查看有没有出错。
在入口里面注册platform函数,函数的参数是结构体,因此要定义一个platform结构体。
/* platform */
static struct platform_driver miscbeep_driver = {
};
/* 驱动入口函数 */
static int __init miscbeep_init(void)
{
return platform_driver_register(&miscbeep_driver);
}
/* 驱动出口函数 */
static void __exit miscbeep_exit(void)
{
platform_driver_unregister(&miscbeep_driver);
}
下面完善platform结构体,里面的成员变量driver中的name的名字要跟设备树里面的是一样的,alientenk_beep;接下来是设备树匹配表,完成设备树匹配表.platform中的.compatible要和设备树里面的进行匹配。
/* platform匹配表 */
static const struct of_device_id beep_of_match[] = {
{.compatible = "alientek,beep"},
{/* Sentinel */},
};
/* platform */
static struct platform_driver miscbeep_driver = {
.driver = {
.name = "imx6ul-beep",
.of_match_table = beep_of_match,/* 设备树匹配表 */
},
.probe = miscbeep_probe,
.remove = miscbeep_remove,
};
下面实现miscbeep_probe和miscbeep_remove函数。
/* probe函数 */
static int miscbeep_probe(struct platform_device *dev)
{
return 0;
}
/* remove函数 */
static int miscbeep_remove(struct platform_device *dev)
{
return 0;
}
编译一下:
在probe函数中需要的工作:
(1)初始化蜂鸣器IO
(2)misc驱动注册
先定义一个miscbeep设备结构体
struct miscbeep_dev {
struct device_node *nd;/* 设备节点 */
int beep_gpio;/* beep gpio */
};
struct miscbeep_dev miscbeep;
因此在probe函数中,找到node,初始化IO,gpio属性名字是:beep-gpio,索引是0,只有一个;
/* probe函数 */
static int miscbeep_probe(struct platform_device *dev)
{
int ret = 0;
//初始化蜂鸣器IO
miscbeep.nd = dev->dev.of_node;
miscbeep.beep_gpio = of_get_named_gpio(miscbeep.nd, "beep-gpio", 0);
if(miscbeep.beep_gpio < 0){
ret = -EINVAL;
goto fail_findgpio;
}
ret = gpio_request(miscbeep.beep_gpio, "beep-gpio");
if(ret){
printk("can't request %d gpio!\r\n",miscbeep.beep_gpio);
ret = -EINVAL;
goto fail_findgpio;
}
ret = gpio_direction_output(miscbeep.beep_gpio, 1);//输出,默认高电平
if(ret < 0){
goto fail_setoutput;
}
//misc驱动注册
return 0;
fail_setoutput://失败的时候释放掉gpio
gpio_free(miscbeep.beep_gpio);
fail_findgpio:
return ret;
}
/* remove函数 */
static int miscbeep_remove(struct platform_device *dev)
{
gpio_set_value(miscbeep.beep_gpio,1);//拉高,关闭BEEP
gpio_free(miscbeep.beep_gpio);//释放gpio
return 0;
}
编译测试一下:
make
sudo cp miscbeep.ko /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f
开发板加载驱动,卸载驱动:
编写misc驱动注册部分的代码:使用misc_register(),注册的内容是:struct miscdevice。将结构体放到上面:
#define MISCBEEP_NAME "miscbeep"
#define MISCBEEP_MINOR 144
/* miscdevice 结构体 */
static struct miscdevice beep_miscdev = {
.minor = MISCBEEP_MINOR,
.name = MISCBEEP_NAME,
.fops = &miscbeep_fops,
};
下面完成字符设备操作集:
static int miscbeep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &miscbeep;
return 0;
}
static int miscbeep_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssiez_t miscbeep_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
}
/* 字符设备操作集 */
struct file_operations miscbeep_fops = {
.owner = THIS_MODULE,
.open = miscbeep_open,
.release = miscbeep_release,
.write = miscbeep_write,
};
构建好了,就可以misc驱动注册了。
//misc驱动注册
ret = misc_register(&beep_miscdev);
if(ret < 0){
goto fail_setoutput;
}
当卸载的时候,把MISC卸载掉,
/* remove函数 */
static int miscbeep_remove(struct platform_device *dev)
{
......
misc_deregister(&beep_miscdev);
return 0;
}
编译报错:缺少头文件
#define <linux/miscdevice.h>
至此,驱动框架已经生成,加载测试:
make
sudo cp miscbeep.ko /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f
开发板测试一下:
/lib/modules/4.1.15 # modprobe miscbeep.ko
/lib/modules/4.1.15 # ls /dev/miscbeep -l
接下来实现write函数。
#define BEEP_OFF 0
#define BEEP_ON 1
static ssiez_t miscbeep_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret;
unsigned char databuf[1];
struct miscbeep_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, count);
if(ret < 0){
return -EINVAL;
}
if(databuf[0] == BEEPON){
gpio_set_value(dev->beep_gpio, 0);/* 打开蜂鸣器 */
} else if(databuf[0] == BEEPOFF){
gpio_set_value(dev->beep_gpio, 0);/* 关闭蜂鸣器 */
}
}
2.2.3、应用
将上一节的应用程序测试APP复制过来
mv platledAPP.c beepAPP.c
arm-linux-gnueabihf-gcc beepAPP.c -o beepAPP
sudo cp miscbeep.ko beepAPP /home/yang/nfs/rootfs/lib/modules/4.1.15/ -f
开发板上电:
/lib/modules/4.1.15 # lsmod
/lib/modules/4.1.15 # rmmod miscbeep.ko
/lib/modules/4.1.15 # modprobe miscbeep.ko
/lib/modules/4.1.15 # ls
/lib/modules/4.1.15 # ./beepAPP /dev/miscbeep 0
/lib/modules/4.1.15 # ./beepAPP /dev/miscbeep 1