IMX6ULL 移植

有了硬件实物,我们的嵌入式 Linux 学习就更加具体了。

1 LED 驱动程序

参考前面 Linux篇-chrdev 的模板,我们尝试在开发板上进一步开发。

本节涉及的内容有 pinctrlgpio 子系统。这两个子系统是驱动分层思想下的产物。

pinctrl 子系统:

  • 获取设备树中 pin 的信息
  • 设置 pin 的复用
  • 设置 pin 的电气特性

IMX6ULL 当中: pinctrl 是 Linux 软件框架名,iomuxc 才是这颗芯片上真实存在的硬件控制器名。

gpio 子系统:

如果将一个 pinctrl 子系统的 PIN 复用为 GPIO 的话,那么就需要使用 gpio 子系统了。通过 gpio 子系统,我们可以很方便地在设备树当中添加 gpio 的相关信息。

1.1 pinctrl 加入到设备树的模板

以 IMX6ULL 为例。

iomuxc/imx6ul-evk 下加入新的节点,这里以驱动一个 LED 为例。

1
2
3
4
5
6
7
8
9
&iomuxc {
imx6ul-evk { /* 该节点下进行 */
pinctrl_test: testgrp { /* 自定义 */
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 /* pin 的配置信息 */
>;
};
};
};

1.2 gpio 加入到设备树的模板

pinctrl 子系统是通过 fsl,pins 属性来获取 PIN 的配置信息的。

我们需要在根节点下创建对应设备的子节点。

1
2
3
4
5
6
7
8
9
/ {
test {
compatible = "atk,test";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>; /* 添加 pinctrl-0 节点 */
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; /* 添加 gpio 的属性信息 */
status = "okay";
};
};

1.3 gpio 子系统相关 OF 函数

of_gpio_named_countof_gpio_countof_get_named_gpio

1.3 实例

通过查看原理图,我们得知了 LED0 连接着 GPIO1_3

alt text

我们的设备树代码如下:

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
// linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/boot/dts/imx6ull-14x14-evk-emmc.dts
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/

#include "imx6ull-14x14-evk.dts"

/ {
alphaled {
compatible = "atk,alphaled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_alphaled>;
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
};

/* 此处的原因是 tsc 节点复用了 GPIO1_3,但是我们需要使用,于是暂时禁止掉 */
&tsc {
status = "disabled";
};

&iomuxc {
imx6ul-evk {
pinctrl_alphaled: alphaledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 /* 这里的 0xb0 后续会解释,主要是根据手册配置的电气特性 */
>;
};
};
};

&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
};

驱动层代码。

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
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define TESTDEV_NAME "test"

struct test_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct gpio_desc *gpiod;
};

static int test_open(struct inode *inode, struct file *file)
{
struct test_dev *led;

led = container_of(inode->i_cdev, struct test_dev, cdev);
file->private_data = led;

return 0;
}

static ssize_t test_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct test_dev *led = file->private_data;
char kbuf[16];
size_t len;
int ret, value;

if (!count)
return 0;

len = min(count, sizeof(kbuf) - 1);
if (copy_from_user(kbuf, buf, len))
return -EFAULT;

kbuf[len] = '\0';

ret = kstrtoint(kbuf, 0, &value);
if (ret)
return ret;

gpiod_set_value_cansleep(led->gpiod, !!value);

return count;
}

static const struct file_operations test_fops = {
.owner = THIS_MODULE,
.open = test_open,
.write = test_write,
};

static int test_probe(struct platform_device *pdev)
{
struct test_dev *led;
int ret;

led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;

led->gpiod = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_HIGH);
if (IS_ERR(led->gpiod)) {
dev_err(&pdev->dev, "failed to get led gpio\n");
return PTR_ERR(led->gpiod);
}

ret = alloc_chrdev_region(&led->devid, 0, 1, TESTDEV_NAME);
if (ret)
return ret;

cdev_init(&led->cdev, &test_fops);
led->cdev.owner = THIS_MODULE;

ret = cdev_add(&led->cdev, led->devid, 1);
if (ret)
goto err_unregister_chrdev;

led->class = class_create(THIS_MODULE, TESTDEV_NAME);
if (IS_ERR(led->class)) {
ret = PTR_ERR(led->class);
goto err_cdev_del;
}

led->device = device_create(led->class, NULL, led->devid, NULL,
TESTDEV_NAME);
if (IS_ERR(led->device)) {
ret = PTR_ERR(led->device);
goto err_class_destroy;
}

platform_set_drvdata(pdev, led);
dev_info(&pdev->dev, "test probe ok\n");

return 0;

err_class_destroy:
class_destroy(led->class);
err_cdev_del:
cdev_del(&led->cdev);
err_unregister_chrdev:
unregister_chrdev_region(led->devid, 1);
return ret;
}

static int test_remove(struct platform_device *pdev)
{
struct test_dev *led = platform_get_drvdata(pdev);

device_destroy(led->class, led->devid);
class_destroy(led->class);
cdev_del(&led->cdev);
unregister_chrdev_region(led->devid, 1);

return 0;
}

static const struct of_device_id test_of_match[] = {
{ .compatible = "atk,test" },
{ }
};
MODULE_DEVICE_TABLE(of, test_of_match);

static struct platform_driver test_driver = {
.probe = test_probe,
.remove = test_remove,
.driver = {
.name = TESTDEV_NAME,
.of_match_table = test_of_match,
},
};

module_platform_driver(test_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jvle");
MODULE_DESCRIPTION("GPIO descriptor based test driver");

应用层。

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
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s <0|1> [device]\n", prog);
fprintf(stderr, "Example: %s 1 /dev/test\n", prog);
}

int main(int argc, char *argv[])
{
const char *dev = "/dev/test";
const char *value;
int fd;
ssize_t ret;

if (argc < 2 || argc > 3) {
usage(argv[0]);
return 1;
}

value = argv[1];
if (strcmp(value, "0") && strcmp(value, "1")) {
usage(argv[0]);
return 1;
}

if (argc == 3)
dev = argv[2];

fd = open(dev, O_WRONLY);
if (fd < 0) {
fprintf(stderr, "open %s failed: %s\n", dev, strerror(errno));
return 1;
}

ret = write(fd, value, strlen(value));
if (ret < 0) {
fprintf(stderr, "write failed: %s\n", strerror(errno));
close(fd);
return 1;
}

close(fd);
return 0;
}

Makefile。

1
2
3
4
5
6
7
8
9
10
11
12
13
KDIR ?= ../linux-imx-rel_imx_4.1.15_2.1.0_ga
ARCH ?= arm
CROSS_COMPILE ?= ../gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-

obj-m += test.o

all:
$(MAKE) -C $(KDIR) M=$(CURDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
$(CROSS_COMPILE)gcc -Wall -static -O2 -o test_app test_app.c

clean:
$(MAKE) -C $(KDIR) M=$(CURDIR) clean
rm -f test_app

由于修改了设备树,我们在这里需要重新编译一下。

1
make ARCH=arm CROSS_COMPILE=../gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j$(nproc) imx6ull-14x14-evk-emmc.dtb

之后将编译完成的 dtb.ko 文件拷贝到系统当中,重新烧录。

然后加载内核模块。

测试结果如下:

先安装模块。

1
2
[imx6ull:/]# insmod /lib/modules/4.1.15/extra/test.ko 
test alphaled: test probe ok

之后尝试开关灯。

1
2
3
[imx6ull:/]# test_app 0
[imx6ull:/]# test_app 1
[imx6ull:/]# test_app 0

开灯。

alt text

关灯。

alt text