Posts Linux LED驱动--寄存器版
Post
Cancel

Linux LED驱动--寄存器版

今天啥都不想做,想想那就来记录一下吧,话说有九个月没写了……

从STM32F429换到了MYS-6ULX-IOT(imx.6ull, ARM Cortex A7),中文英文的书都看了好几本,写了一些驱动,突然想写点教程,那么,先从点灯开始写吧……

在这之前,我已经看了《Linux Device Drivers Development Develop customized drivers for embedded Linux》七章有多了,然后实践是跟着这本 《Linux Driver Development for Embedded Processors Second Edition》,对我自己来说,如果忘了,拿出书和练习写的代码看一下就ok了,今天闲着没事写一篇,毕竟承诺了要把这板子(老师给我报销了200块..)交给光电协会,写几篇教程帖子给师弟师妹也好……

觉得不错的书:(可以自己买或找,也可以问我要电子版,这些是有版权的书,就不提供下载啦~)
《Linux Device Drivers Development Develop customized drivers for embedded Linux》:……作者说为了节省排版,不知道他当时想的什么玩意……反向思考他可能觉得这样读者看的更细吧,于是压缩了代码……不过内容可以的。
《Linux Driver Development for Embedded Processors Second Edition》:真香!我打印了出来抱着看!
《Mastering Embedded Linux Programming, 2e》:书如其名。优、缺点都是很全,也是非常好的书。

直入正题。

原理图:MYS-6ULX-IOT.pdf
datasheet:IMX6ULLRM.pdf

打开原理图,找到我们的两个LED,它们分别接到了SNVS_TAMPER1/2: iot-leds
可以看到,输出低电平点亮,我们需要把这两个引脚设为推挽输出、上拉模式即pull-up output,以确保能够正常输出高电平。

接下来,就是配置GPIO了,首先找到引脚复用控制器寄存器,把这两引脚配置成GPIO模式,这里的是SNVS。 找到imx6ull-pinfunc-snvs.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef __DTS_IMX6ULL_PINFUNC_SNVS_H
#define __DTS_IMX6ULL_PINFUNC_SNVS_H
/*
* The pin function ID is a tuple of
* <mux_reg conf_reg input_reg mux_mode input_val>
*/
#define MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10                  0x0000 0x0044 0x0000 0x5 0x0
#define MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11                  0x0004 0x0048 0x0000 0x5 0x0
#define MX6ULL_PAD_SNVS_TAMPER0__GPIO5_IO00                0x0008 0x004C 0x0000 0x5 0x0
#define MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01                0x000C 0x0050 0x0000 0x5 0x0
...

右边的数字意义如注释,从左到右分别为

  • IOMUX register offset
  • Pad configuration register offset
  • Select input daisy chain register offset
  • IOMUX configuration setting
  • Select input daisy chain setting

这里的offset(偏移量)是相对IOMUXC_SNVS的首地址而言的,在datasheet里可以看到它的map:
snvs
前两个offset请看datasheet;
第三个一般为0;
第四个0x11配置成GPIO模式,详见数据手册;
第五个跟第三个一样0,现在不需要关注。

看了datasheet会发现,还有个PAD_CTL寄存器没设置,这个寄存器是用来配置GPIO的:

arch/arm/boot/dts/里找到mys-imx6ull-14x14-evk.dts的&iomuxc_snvs {},在里面的imx6ul-evk {}里加上如下:

1
2
3
4
5
6
7
8
...
pinctrl_gpio_leds: ledgrp {
        fsl,pins = <
            MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x1b0b0
            MX6ULL_PAD_SNVS_TAMPER2__GPIO5_IO02 0x1b0b0
        >;
    };
...

后面的数值便是PAD_CTL寄存器的设置值,0x1b0b0代表了100K上拉等的设置,具体见数据手册。(如果要把该dts推送到linux,需要到 Documentation/devicetree/bindings/pinctrl 里写好文档)

然后,在mys-imx6ull-14x14-evk.dts加上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ {
    ...
    led0 {
        compatible = "leds";
        label = "led0";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_gpio_leds>;
    };

    led1 {
        compatible = "leds";
        label = "led1";
    };
    ...

后面就是写程序了: 这是个platform device driver,不详述了,自己看书 这个第一版的led驱动是独立的,不用已有的子系统,把它归到misc去,思路:

  • 弄好misc驱动程序框架,整理思路填充程序,哪里注册驱动,哪里注销驱动回收资源,预定义各数据块,需要再加;
  • 前面的寄存器pinctrl子系统会设置,剩下的就是设置GPIO寄存器,只需要设置DATA & GPIO direction两个寄存器了。所以剩下就是查datasheet找到地址,填充程序获取寄存器的virtual address,获取了总不能人工记这地址,当然把它挂到对应的led_dev,随led_dev数据块一起存在/被回收;
  • 剩下就是向寄存器写数据了

贴上程序led_v1_misc.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <linux/module.h>
#include <linux/fs.h> /* struct file_operations */
#include <linux/platform_device.h> /* platform_driver_register(), platform_set_drvdata() */
#include <linux/io.h>         /* devm_ioremap(), iowrite32() */
#include <linux/of.h>         /* of_property_read_string() */
#include <linux/uaccess.h>    /* copy_from_user(), copy_to_user() */
#include <linux/miscdevice.h> /* misc_register() */

// private led device structure
struct led_dev
{
    struct miscdevice led_misc_device;
    u32 led_mask;
    const char *led_name;
    char led_value[8];
};

#define GPDR 0x020AC000    /* GPIO5 DATA register: DR */
#define GPDIR 0x020AC004    /* GPIO5 GPIO direction register: GDIR */
#define LED0_MASK 1 << 1    /* MASK DATA bit, GPIO_LOW to light leds */
#define LED1_MASK 1 << 2
#define ALL_MASK (1 << 1 | 1 << 2)

static void __iomem *GPDR_V;    /* get virtual address */
static void __iomem *GPDIR_V;

static int led_write(struct file *file, const char __user *buff,
                                    size_t count, loff_t *ppos)
{
    u32 read, write;
    struct led_dev *led;
    pr_info("Entering led_write()...\n");

    /* The private_data is led_misc_device structure in the private led_dev structure. */
    led = container_of(file->private_data, struct led_dev, led_misc_device);

    if(copy_from_user(led->led_value, buff, count)) {
        pr_info("Bad copied value!\n");
        return -EFAULT;
    }
    led->led_value[count-1] = '\0';
    pr_info("buff from user space: %s\n", led->led_value);
    
    read = ioread32(GPDR_V);
    if(!strcmp(led->led_value,"1") || !strcmp(led->led_value, "on"))
        iowrite32(read & ~(led->led_mask), GPDR_V);
    else if (!strcmp(led->led_value, "0") || !strcmp(led->led_value, "off"))
        iowrite32(read | (led->led_mask), GPDR_V);
    else {
        pr_info("Bad value!\n");
        return -EINVAL;
    }
    
    pr_info("leaving led_write()...\n");
    return count;
}

static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,
};

static int led_probe(struct platform_device *pdev)
{
    pr_info("led_probe entered.\n");

    struct led_dev *led;
    pr_info("%s\n", pdev->name);
    GPDR_V = devm_ioremap(&pdev->dev, GPDR, sizeof(u32));
    GPDIR_V = devm_ioremap(&pdev->dev, GPDIR, sizeof(u32));
    pr_info("0x%08lx\n", GPDR_V);
    pr_info("0x%08lx\n", GPDIR_V);

    iowrite32(ALL_MASK, GPDR_V);
    iowrite32(ALL_MASK, GPDIR_V);

    led = devm_kzalloc(&pdev->dev, sizeof(struct led_dev), GFP_KERNEL);

    of_property_read_string(pdev->dev.of_node, "label", &led->led_name);

    led->led_misc_device.minor = MISC_DYNAMIC_MINOR;
    led->led_misc_device.name = led->led_name;
    led->led_misc_device.fops = &led_fops;

    if (strcmp(led->led_name,"led0") == 0)
        led->led_mask = LED0_MASK;
    else if (strcmp(led->led_name, "led1") == 0)
        led->led_mask = LED1_MASK;
    else {
        pr_info("Bad device tree value...\n");
        return -EINVAL;
    }

    int err = misc_register(&led->led_misc_device);
    if (err)
        return err;
    
    platform_set_drvdata(pdev, led);
    pr_info("led_probe exit!\n");
    return 0;
}


static int led_remove(struct platform_device *pdev)
{
    struct led_dev *led = platform_get_drvdata(pdev);
    iowrite32(ALL_MASK, GPDR_V);
    misc_deregister(&led->led_misc_device);
    pr_info("led_removed!\n");
    return 0;
}

static const struct of_device_id my_of_ids[] = {
    { .compatible = "leds" },
    {},
};
MODULE_DEVICE_TABLE(of, my_of_ids);

static struct platform_driver led_platform_driver = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "leds",
        .of_match_table = my_of_ids,
        .owner = THIS_MODULE,
    }
};

module_platform_driver(led_platform_driver);
// static int led_init(void)
// {
//     pr_info("led initing...\n");
//     int err = platform_driver_register(&led_platform_driver);
//     if (err) {
//         pr_err("platform value returned %d\n", err);
//         return err;
//     }
//     pr_info("led init done!\n");
//     return 0;
// }
// static void led_exit(void)
// {
//     iowrite32(ALL_MASK, GPDR_V);
//     platform_driver_unregister(&led_platform_driver);
//     pr_info("led driver exit! \n");
// }
// module_init(led_init);
// module_exit(led_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("whqee <whqee@foxmail.com>");
MODULE_DESCRIPTION("A simple miscellaneous led driver module.");

没什么好说的,琢磨源码就行。运行结果:

end

This post is licensed under CC BY 4.0 by the author.