开发板:正点原子阿尔法
本文示例会结合前面知识,pinctrl与gpio,设备树,工作队列,定时器,sysfs,platform驱动做一个简单总结,会有一些不合理的地方,实现1s翻转一次led的状态
led.h
#ifndef LED_DRIVER_REG_H
#define LED_DRIVER_REG_H
#include /*设备号所在头文件*/
#include /*内核模块声明的相关函数*/
#include /*module_init和module_exit*/
#include /*内核的各种函数*/
#include /*readl函数*/
#include /*cdev*/
#include /*class & device*/
#include
#include /*copy_from_user*/
#include /*gpio fileoperation*/
#include
#include
#include
#include
#define DEVICE_NAME "led_driver"
#define DEVICE_CNT 1
#define LED_ON 1
#define LED_OFF 0
#define HIGH_LEVEL 1
#define LOW_LEVEL 0
struct led_device
{
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
int led_gpio;
struct device_node* dev_node;
struct timer_list timer; /*定时器*/
struct work_struct led_work; /*工作队列*/
};
struct led_device led_device;
#endif
led.c
#include "led.h"
static void led_switch(int led_status)
{
switch(led_status)
{
case LED_ON:
gpio_set_value(led_device.led_gpio,LOW_LEVEL);
break;
case LED_OFF:
gpio_set_value(led_device.led_gpio,HIGH_LEVEL);
break;
default:
gpio_set_value(led_device.led_gpio,HIGH_LEVEL);
break;
}
}
//echo
static ssize_t led_enable_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t len)
{
bool iLedEnable;
u32 ret;
ret = strtobool(buf, &iLedEnable);
if(ret < 0)
{
printk("strtobool failed\n");
return ret;
}
printk("led led_enable_store is=%d\n",iLedEnable);
led_switch(!!iLedEnable);
return len;
}
//声明led_enable文件节点
static DEVICE_ATTR(led_enable, S_IWUSR, NULL,led_enable_store);
static struct attribute *atk_imx6ul_led_sysfs_attrs[] = {
&dev_attr_led_enable.attr,
NULL,
};
static const struct attribute_group dev_attr_grp = {
.attrs = atk_imx6ul_led_sysfs_attrs,
NULL,
};
static int imx6ull_led_open(struct inode *inode, struct file *file)
{
file->private_data = &led_device;
return 0;
}
static int imx6ull_led_close(struct inode *inode, struct file *file)
{
file->private_data = &led_device;
return 0;
}
static ssize_t imx6ull_led_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
return 0;
}
static ssize_t imx6ull_led_write(struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
u32 ret;
unsigned char cALedbuf[1];
unsigned char iLedSta;
ret = copy_from_user(cALedbuf,buf,cnt);
if(ret < 0)
{
printk("copy from user failed\n");
return -EINVAL;
}
iLedSta = cALedbuf[0];
printk("++klz write led ,led status %d\n",iLedSta);
led_switch(!!iLedSta);
return 0;
}
static struct file_operations led_device_fops = {
.owner = THIS_MODULE,
.read = imx6ull_led_read,
.write = imx6ull_led_write,
.open = imx6ull_led_open,
.release = imx6ull_led_close,
};
static int led_parse_dt(void)
{
int ret;
/*1.获取设备树中compatible属性的字符串值klz-led*/
led_device.dev_node = of_find_compatible_node(NULL,NULL,"klz-led");
if(led_device.dev_node == NULL)
{
printk("led device node find failed\n");
return -1;
}
/*2.获取gpio编号,将节点中的“led-gpio”属性值转换为对应的 LED 编号。*/
led_device.led_gpio = of_get_named_gpio(led_device.dev_node, "led-gpio", 0);
if(led_device.led_gpio < 0)
{
printk("failed to get gpio\n");
return -1;
}
/*3.申请gpio管脚*/
ret = gpio_request(led_device.led_gpio, "klz-led");
if(ret != 0)
{
printk("gpio request failed\n");
return -1;
}
/*4.设置gpio为输出且输出高电平,熄灭*/
ret = gpio_direction_output(led_device.led_gpio,HIGH_LEVEL);
if(ret != 0)
{
printk("gpio direction output failed\n");
return -1;
}
return 0;
}
static void led_exchange_work(struct work_struct *work)
{
struct led_device *data = container_of(work, struct led_device, led_work);
gpio_set_value((*data).led_gpio,HIGH_LEVEL);
msleep(1000);
gpio_set_value((*data).led_gpio,LOW_LEVEL);
}
//2s调度一次工作队列
static void led_exchange_timer(unsigned long handle)
{
schedule_work(&led_device.led_work);
mod_timer(&led_device.timer, jiffies +msecs_to_jiffies(2000));
}
static int led_driver_probe(struct platform_device *pdev)
{
u32 ret;
printk("klz");
printk("led driver and device was matched!\r\n");
/*1.设备树解析*/
ret = led_parse_dt();
if (ret < 0) {
printk("led parse error");
return ret;
}
/*2.字符设备驱动框架那一套*/
/*2.1 之前定义了主设备号*/
if(led_device.major)
{
/*选择次设备号*/
led_device.devid = MKDEV(led_device.major,0);
/*注册设备号*/
ret = register_chrdev_region(led_device.devid, DEVICE_CNT, DEVICE_NAME);
if(ret < 0)
{
printk("register_chrdev_region failed\n");
return ret;
}
}else
{
/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
alloc_chrdev_region(&led_device.devid, 0, DEVICE_CNT, DEVICE_NAME); /* 申请设备号 */
led_device.major = MAJOR(led_device.devid); /* 获取分配号的主设备号 */
led_device.minor = MINOR(led_device.devid); /* 获取分配号的次设备号 */
}
led_device.cdev.owner = THIS_MODULE;
cdev_init(&led_device.cdev,&led_device_fops);
/*自动创建设备结点,在/dev目录下体现*/
ret = cdev_add(&led_device.cdev,led_device.devid,DEVICE_CNT);
if(ret < 0)
{
printk("cdev_add device failed\n");
goto fail_cdev_add;
}
led_device.class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(led_device.class))
{
printk("class creat failed\n");
goto fail_class_create;
}
/*生成dev/DEVICE_NAME文件*/
led_device.device = device_create(led_device.class,NULL,led_device.devid,NULL,DEVICE_NAME);
if(IS_ERR(led_device.device))
{
printk("device class failed\n");
goto fail_device_create;
}
/*创建led_enable结点,直接通过系统调用来操作驱动*/
ret = sysfs_create_group(&led_device.device->kobj,&dev_attr_grp);
if(ret)
{
printk("failed to create sys files\n");
goto fail_sys_create;
}
//struct led_device *device = dev_get_drvdata(dev);
/*初始化定时器,工作队列*/
INIT_WORK(&led_device.led_work,led_exchange_work);
setup_timer(&led_device.timer,led_exchange_timer,(unsigned long)&led_device);//最后一个值可以传指针
/*激活定时器,add_timer不激活定时器*/
mod_timer(&led_device.timer, jiffies +msecs_to_jiffies(0));
dev_set_drvdata(&pdev->dev, &led_device);
printk("%s:probe success\n",__func__);
return 0;
fail_cdev_add:
unregister_chrdev_region(led_device.devid,DEVICE_CNT);
return -1;
fail_class_create:
cdev_del(&led_device.cdev);
unregister_chrdev_region(led_device.devid,DEVICE_CNT);
return -1;
fail_device_create:
cdev_del(&led_device.cdev);
unregister_chrdev_region(led_device.devid,DEVICE_CNT);
class_destroy(led_device.class);
return -1;
fail_sys_create:
sysfs_remove_group(&led_device.device->kobj,&dev_attr_grp);
cdev_del(&led_device.cdev);
unregister_chrdev_region(led_device.devid,DEVICE_CNT);
class_destroy(led_device.class);
return -1;
}
static int led_driver_remove(struct platform_device *pdev)
{
gpio_set_value(led_device.led_gpio,1);
//依赖device,先删除
sysfs_remove_group(&led_device.device->kobj, &dev_attr_grp);
cdev_del(&led_device.cdev);
unregister_chrdev_region(led_device.devid,DEVICE_CNT);
/*依赖于class所以先删除*/
device_destroy(led_device.class, led_device.devid);
class_destroy(led_device.class);
//删除定时器
del_timer_sync(&led_device.timer);
//取消工作队列
cancel_work_sync(&led_device.led_work);
gpio_free(led_device.led_gpio);
return 0;
}
/* 匹配列表,led_of_match中的compatible与设备树中的
* compatible匹配,匹配成功则跑probe函数
*/
static const struct of_device_id led_of_match[] = {
{ .compatible = "klz-led" },
{ /* Sentinel */ }
};
/*
* platform 平台驱动结构体
*/
static struct platform_driver led_driver = {
.driver = {
.name = "klz-led",
.of_match_table = led_of_match,
},
.probe = led_driver_probe,
.remove = led_driver_remove,
};
module_platform_driver(led_driver);
MODULE_AUTHOR("klz <[email protected]>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("led driver of atk imx6ull");
Makefile:
KERNELDIR := /home/klz/linux/linux-4.1.15
CURRENT_PATH := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
copy:
sudo cp *.ko /home/klz/linux/nfs/rootfs/lib/modules/4.1.15
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
执行make && make copy拷贝到根文件系统中,
在根结点设备树添加,注意屏蔽其他用到一样结点
/*klz 2021.1.5*/
klzled{
#address-cells = <1>;
#size-cells = <1>;
compatible = "klz-led";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
};
pinctrl下,注意屏蔽其他用到一样结点
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
>;
};
在Linux源码目录下执行
make dtbs
拷贝到根文件系统
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ../tftpboot/
将编译出来 led.ko 拷贝到 rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入如下命令加载 led.ko 这个驱动模块
depmod //第一次加载驱动的时候需要运行此命令
modprobe led.ko //加载驱动模块
或者
insmod led.ko
驱动模块加载完成以后到/sys/bus/platform/drivers/目录下查看驱动是否存在,我们在led.c 中设置 led_driver (platform_driver 类型)的 name 字段为“ klz-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“klz-led”这个文件
同理,在/sys/bus/platform/devices/目录下也存在 led 的设备文件,也就是设备树中 klzled 这
个节点
驱动和模块都存在,当驱动和设备匹配成功以后就会输出如图
如果要卸载驱动的话输入如下命令即可:
rmmod led.ko
1.使用sysfs_create_files会有警告,所以换成sysfs_create_group
2.sysfs_remove_group卸载在device卸载后面,不然会报空指针