例行的点灯来喽。
之前是寄存器读写,现在要学习通过设备树点灯。
dtsled.c
寄存器写在reg
把用到的寄存器写在设备树的led节点的reg属性。
其实还是对寄存器的读写。 (不推荐)
头文件
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of.h>
#define DTSLED_NAME "dtsled" /* class_create */
#define DTSLED_COUNT 1 // 申请设备号的数量
#define LEDOFF 0 /* led off */
#define LEDON 1 /* led on */
/* 物理地址 */
#define GPIO_C_DR_BASE (0xE0003200) // data
#define GPIO_C_DDR_BASE (0xE0003204) // direction
#define GPIO_C_INTEN_BASE (0xE0003230) // interrup enable
/* 地址映射后的虚拟指针 */
static void __iomem *FMQL_GPIO_C_DR;
static void __iomem *FMQL_GPIO_C_DDR;
static void __iomem *FMQL_GPIO_C_INTEN;
设备结构体★
/* dtsled设备结构体 */
struct dtsled_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点*/
};
static struct dtsled_dev dtsled; /* led device */
操作函数
static int dtsled_open(struct inode * inode, struct file * filp){
filp -> private_data = &dtsled; /* 设置私有数据 */
return 0;
}
static ssize_t dtsled_read(struct file * filp, char __user * buf,
size_t cnt, loff_t *offt){
return 0;
}
static int dtsled_release(struct inode * inode, struct file * filp){
return 0;
}
static ssize_t dtsled_write(struct file * filp, const char __user * buf,
size_t cnt, loff_t *offt){
int ret;
int val;
char kern_databuf[1];
ret = copy_from_user(kern_databuf, buf, cnt); /* 得到应用层传递过来的数据 */
if(ret < 0){
printk(KERN_ERR "kernel write failed!\r\n");
return -EFAULT;
}
val = readl(FMQL_GPIO_C_DR);
if(kern_databuf[0] == 1){
val |= (1 << 5); // led on
} else if(kern_databuf[0] == 0){
val &= ~(1 << 5); // led off
}
writel(val, FMQL_GPIO_C_DR);
return 0;
}
/* dtsled 设备操作函数 */
static struct file_operations dtsled_ops = {
.owner = THIS_MODULE,
.read = dtsled_read,
.write = dtsled_write,
.open = dtsled_open,
.release = dtsled_release,
};
地址映射
/* 地址映射 */
// of_iomap(dtsled.nd, num) // num为设备树定义的reg属性
// 因为我的设备树文件里,led节点下没有reg属性,所以就按之前的方式写
static inline void led_ioremap(void){
FMQL_GPIO_C_DR = ioremap(GPIO_C_DR_BASE, 4);
FMQL_GPIO_C_DDR = ioremap(GPIO_C_DDR_BASE, 4);
FMQL_GPIO_C_INTEN = ioremap(GPIO_C_INTEN_BASE, 4);
}
/* 取消地址映射 */
static inline void led_iounmap(void)
{
iounmap(FMQL_GPIO_C_DR);
iounmap(FMQL_GPIO_C_DDR);
iounmap(FMQL_GPIO_C_INTEN);
}
驱动模块注册/卸载
/* 驱动模块注册和卸载 */
static int __init dtsled_init(void)
{
const char *str;
u32 val = 0;
int ret = 0;
printk(KERN_EMERG "dtsled init\r\n");
/* 获取led设备节点 */
dtsled.nd = of_find_node_by_path("/leds");
if (NULL == dtsled.nd) {
printk(KERN_ERR "dtsled: can't find node\n");
return -ENOENT;
}
/* 获取status属性 */
ret = of_property_read_string(dtsled.nd, "status", &str);
if (!str || strcmp(str, "okay")) {
printk(KERN_ERR "dtsled: status is not okay\n");
return -ENOENT;
}
/* 读取compatible属性并进行匹配 */
//str = of_get_property(dtsled.nd, "compatible", NULL);
ret = of_property_read_string(dtsled.nd, "compatible", &str);
if (!str || strcmp(str, "gpio-leds")) {
printk(KERN_ERR "dtsled: compatible is not gpio-leds\n");
return -ENOENT;
}
printk(KERN_ERR "dtsled: successful\r\n");
/* 寄存器地址映射 */
led_ioremap();
/* 初始化 */
val = readl(FMQL_GPIO_C_DDR);
val |= (1 << 5); // 1: output
writel(val, FMQL_GPIO_C_DDR);
val = readl(FMQL_GPIO_C_DR);
ret = of_property_read_string(dtsled.nd, "default-state", &str);
if(!ret){
if(!strcmp(str, "on"))
val |= (1 << 5); // 1: led on
else val &= ~(1 << 5); // 0: led off
} else val &= ~(1 << 5);
writel(val, FMQL_GPIO_C_DR);
/* 注册字符设备驱动 */ // 设备号,cdev,class,devie
#if 1 // 设备号
if(dtsled.major){
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DTSLED_COUNT, DTSLED_NAME);
if(ret)
goto out1;
} else {
ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_COUNT, DTSLED_NAME);
if(ret)
goto out1;
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
}
printk(KERN_EMERG "dtsled major = %d, minor = %d\r\n", dtsled.major, dtsled.minor);
#endif
#if 1 // cdev
cdev_init(&dtsled.cdev, &dtsled_ops);
dtsled.cdev.owner = THIS_MODULE;
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_COUNT);
if(ret)
goto out2;
#endif
#if 1 // class
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if(IS_ERR(dtsled.class)) {
ret = PTR_ERR(dtsled.class);
goto out3;
}
#endif
#if 1 // device
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if(IS_ERR(dtsled.device)) {
ret = PTR_ERR(dtsled.device);
goto out4;
}
#endif
return 0;
#if 1 // goto
out4:
class_destroy(dtsled.class);
out3:
cdev_del(&dtsled.cdev);
out2:
unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
out1:
led_iounmap();
return ret;
#endif
}
static void __exit dtsled_exit(void)
{
u32 val = 0;
printk(KERN_EMERG "dtsled exit\r\n");
/* 灭灯 */
val = readl(FMQL_GPIO_C_DR);
val &= ~(1 << 5); // 0: led off
writel(val, FMQL_GPIO_C_DR);
/* 注销字符驱动设备 */ // device, class, cdev, 设备号
device_destroy(dtsled.class, dtsled.devid);
class_destroy(dtsled.class);
cdev_del(&dtsled.cdev);
unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
/* 取消地址映射 */
led_iounmap();
}
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_AUTHOR("Skylar");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FMQL DTS LED dev");
测试程序APP
沿用之前的ledAPP.c
gpioled.c
不用寄存器读写,采用设备树的方式。
gpio子系统
#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1
fmql中对gpio的节点描述为:(在amba@0父节点下)
gpio0,也为porta,是MIO[31:0](porta定义了snps,nr-gpios 为引脚数量)
同理,gpio1 = portb = MIO[53:32]
gpio2 = portc = EMIO[85:54]
gpio3 = portd = EMIO[117:86]
我用的是引脚59,即portc的MIO[5] :
gpio API函数
(来自正点原子pdf)
gpio of函数
pinctrl子系统
代码
在dtsled.c的基础上进行修改。
设备树不需要reg属性了,因为不直接对寄存器进行操作。
led-gpio属性是为了获取gpio编号
#include
记得添加:(不然没有of_gpio相关的函数)
#include <linux/of_gpio.h>
设备结构体
/* dtsled设备结构体 */
struct gpioled_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* LED所使用的GPIO编号 */
};
和dtsled.c相比,多了最后的led_gpio。
驱动模块注册
__init函数多的步骤:
读取设备节点,以及节点属性(status,compatible)后,
多了读取led-gpio属性。然后向gpio子系统申请使用gpio、管脚设置为output。
/* 4.获取设备树中的led-gpio属性,得到LED所使用的GPIO编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(!gpio_is_valid(gpioled.led_gpio)) {
printk(KERN_ERR "gpioled: Failed to get led-gpio\n");
return -EINVAL;
}
printk(KERN_INFO "gpioled: led-gpio num = %d\r\n", gpioled.led_gpio);
/* 5.向gpio子系统申请使用GPIO */
ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
if (ret) {
printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
return ret;
}
/* 6.将led gpio管脚设置为输出模式 */
gpio_direction_output(gpioled.led_gpio, 0);
后面的内容就一样了:初始化led,注册字符设备驱动(设备号,cdev,class,device)
__exit函数多了一步,就是最后的释放gpio
static void __exit led_exit(void)
{
/* 注销设备 */
device_destroy(gpioled.class, gpioled.devid);
/* 注销类 */
class_destroy(gpioled.class);
/* 删除cdev */
cdev_del(&gpioled.cdev);
/* 注销设备号 */
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
/* 释放GPIO */
gpio_free(gpioled.led_gpio);
}
运行
dtsled.c
(绝对路径)设备节点写错了 。
其实没错,我写的是“/leds/gpio-led3”。但是就是不行。
后来改成“/leds”就可以了。
(因为我leds下面只有一个gpio-led3子节点?所以二者等效?)
- “/”是根节点
- “/leds”表示根节点下,有名为“leds”的节点
- 在我自己写的设备树文件中,leds作为父节点,有一个子节点gpio-led3
- 查看正点原子linux开发pdf:
gpioled.c
修改了led的设备树,不用led3父节点下又有gpio-led节点了。现在根节点下,只有led3 :
led3 {
compatible = "fmql,led";
status = "okay";
label = "led3";
led-gpio = <&portc 5 GPIO_ACTIVE_HIGH>; // 59
//linux,default-trigger = "timer";//or heartbeat
default-state = "off";
};
运行测试:ok
获取设备节点的属性时,一定要记得设备节点以及属性的名称与设备树中写的一致,如status,compatible,led-gpio等
比如我这里寻找的是根节点下的名为”led3“的设备节点,但是设备树中写的是”leds“,就会failed。
再比如,我这里找的是节点的”led-gpio“属性,如果设备树中写的是”gpios“,则也会failed。
(以上均为本人调试过程中出现的问题)一定要照着设备树写!!!
其他 - vscode
修改vscode设置(因为tab键是4个空格而不是一个tab字符:
vscode怎么修改tab缩进 • Worktile社区
设置了但是不管用。。。
原来是修改这里就行:
vscode页面的右下角,”制表符长度:4“,这里原来是”空格“,点击”空格“, 修改为制表符缩进即可: