fmql之字符驱动设备(2)-设备树

例行的点灯来喽。

之前是寄存器读写,现在要学习通过设备树点灯。

Linux GPIO子系统相关知识:

linux内核驱动-gpio子系统 - fuzidage - 博客园

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“,这里原来是”空格“,点击”空格“, 修改为制表符缩进即可:

但是,每次打开一个新的文件(.c等),都要重新配置制表符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值