====> Step 1: <====
修改maria-am335x-common.dts文件,主要是有关led的pinmux内容,去掉默认的am335x-bone四个灯,添加核心板上的LED指示灯,即GPIO3_8。
diff a/arch/arm/boot/dts/maria-am335x-common.dts b/arch/arm/boot/dts/maria-am335x-common.dts
--- a/arch/arm/boot/dts/maria-am335x-common.dts
+++ a/arch/arm/boot/dts/maria-am335x-common.dts
@@ -27,27 +27,5 @@
- led@2 {
- label = "beaglebone:green:heartbeat";
- gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
- linux,default-trigger = "heartbeat";
- default-state = "off";
- };
-
- led@3 {
- label = "beaglebone:green:mmc0";
- gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>;
- linux,default-trigger = "mmc0";
- default-state = "off";
- };
-
- led@4 {
- label = "beaglebone:green:usr2";
- gpios = <&gpio1 23 GPIO_ACTIVE_HIGH>;
- linux,default-trigger = "cpu0";
- default-state = "off";
- };
-
- led@5 {
- label = "beaglebone:green:usr3";
- gpios = <&gpio1 24 GPIO_ACTIVE_HIGH>;
- linux,default-trigger = "mmc1";
- default-state = "off";
- };
+ led@1 {
+ label = "maria-am335x:core";
+ gpios = <&gpio3 8 GPIO_ACTIVE_HIGH>;
+ default-state = "on";
+ };
====> Step 2: <====
修改maria-am335x-common.dts文件,配置GPIO3_8的pinmux,也就是操作Control Module Register。
注意下文的那个0x1E8怎么来的?为什么不是“3*32+8”?
设置引脚的pinmux功能,在AM335x中被称为Control Module,它的起始地址为0x44E10000。
从0x44E10000开始偏移0x800的地址,是各种各样的conf寄存器:
conf_<module>_<pin> Register (offset = 800h–A34h)
9E8h conf_emu1
因此为0x1E8。
diff a/arch/arm/boot/dts/maria-am335x-common.dts b/arch/arm/boot/dts/maria-am335x-common.dts
--- a/arch/arm/boot/dts/maria-am335x-common.dts
+++ a/arch/arm/boot/dts/maria-am335x-common.dts
@@ -46,8 +46,5 @@
- user_leds_s0: user_leds_s0 {
- pinctrl-single,pins = <
- 0x54 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a5.gpio1_21 */
- 0x58 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a6.gpio1_22 */
- 0x5c (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a7.gpio1_23 */
- 0x60 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a8.gpio1_24 */
- >;
- };
+ core_led: core_led {
+ pinctrl-single,pins = <
+ 0x1E8 (PIN_OUTPUT | MUX_MODE7) /* emu1.gpio3_8 */
+ >;
+ };
====> Step 3: <====
修改maria-am335x-common.dts文件,配置pinctrl-0。
完成这一步了后就可以重新编译dtb文件了,使用这个dtb和原来的内核,就会发现核心板上的LED灯在启动系统后被点亮。U-Boot也会点亮这个LED灯,但是在解压内核时,它又会熄灭,直到内核重新根据dtb设置它的初始状态。
diff a/arch/arm/boot/dts/maria-am335x-common.dts b/arch/arm/boot/dts/maria-am335x-common.dts
--- a/arch/arm/boot/dts/maria-am335x-common.dts
+++ a/arch/arm/boot/dts/maria-am335x-common.dts
@@ -23,1 +23,1 @@
- pinctrl-0 = <&user_leds_s0>;
+ pinctrl-0 = <&core_led>;
====> Step 4: <====
先来一个简单的module文件练习一下,此处当然是无敌的hello module了,这个模块的作用是——nothing。它仅仅演示了一个模块被创建和加载的过程。需要修改的有三个文件:
driver/char/maria-hello.c
driver/char/Kconfig
driver/char/Makefile
================
diff b/driver/char/maria-hello.c
+++ b/driver/char/maria-hello.c
@@ +0,19 @@
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+
+ static int __init hello_init(void)
+ {
+ printk("Hello! This is a Module Test.\n");
+ return 0;
+ }
+
+ static void __exit hello_exit(void)
+ {
+ printk("Goodbye.\n");
+ }
+
+ module_init(hello_init);
+ module_exit(hello_exit);
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Marianna Zhu <mariabrook@sina.com>");
模块的.c文件由头文件、模块装载时调用的函数、模块移除时被调用的函数,以及特殊宏(许可证声明等)组成。其实就算不声明许可证也可以编译成功,虽然内核会抱怨,但也仅仅是抱怨。ps:作为开源软件爱好者,还是强烈建议加上它。
================
diff a/driver/char/Kconfig b/driver/char/Kconfig
--- a/driver/char/maria-hello.c
+++ b/driver/char/maria-hello.c
@@ -7,0 +7,6 @@
+ config MARIA_HELLO
+ tristate "maria_hello module"
+ default m
+ help
+ This is a Module Test.
+
这是Kconfig文件,在make menuconfig时会展示其中的选项。
里面的“tristate”关键字,意思是此模块可以被编译成includes、excludes,以及modularizes,如果是“bool”关键字,则只有includes和excludes两种状态。只有modularizes才会编译出.ko文件。
================
diff a/driver/char/Makefile b/driver/char/Makefile
--- a/driver/char/Makefile
+++ b/driver/char/Makefile
@@ -5,0 +5,1 @@
+ obj-$(CONFIG_MARIA_HELLO) += maria-hello.o
Makefile告诉编译器Kconfig所展示的选项采用的是哪个.o文件。它们的名字没有必然相同,相同是为了便于人眼阅读。
================
修改完这三个文件之后,重新编译并把dtb和uImage放置在tftp服务器目录下,把maria.ko文件放置在SD卡的boot分区(从tftp加载内核,但是内核加载之后暂时无法再通过tftp加载maria.ko文件,因此此时Ethernet还没有在内核中被正确的驱动,所以这里采用了比较麻烦的方法):
make ARCH=arm CROSS_COMPILE=/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8 menuconfig
make ARCH=arm CROSS_COMPILE=/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8 maria-am335x.dtb
make ARCH=arm CROSS_COMPILE=/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8 LOADADDR=0x80008000 uImage
make ARCH=arm CROSS_COMPILE=/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8 modules
rm /tftp_root/maria-am335x.dtb /tftp_root/uImage
cp arch/arm/boot/dts/maria-am335x.dtb arch/arm/boot/uImage /tftp_root/
cp drivers/char/maria-led.ko /run/media/maria/boot/
重新启动内核,等待命令提示符出现。由于现在这块电路板还没有调试Kernel中的Ethernet,手上没有USB能够识别的U盘,而SD卡中的文件系统又不能在电脑上读写,因此将maria-hello.ko文件放置在SD卡的BOOT分区,再使用mount命令加载它。
root@ok335x:/media# mount /dev/mmcblk0p1 /media/maria/
root@ok335x:/media# cd /media/maria/
root@ok335x:/media/maria# insmod maria-hello.ko
[ 75.220453] Hello! This is a Module Test.
root@ok335x:/media/maria# lsmod
Module Size Used by
maria_hello 726 0
root@ok335x:/media/maria# rmmod maria-hello.ko
[ 115.171509] Goodbye.
====> Step 5: <====
在了解hello模块的基础上,构造GPIO的模块文件。此处需要修改的也是三个:
driver/char/maria-led.c
driver/char/Kconfig
driver/char/Makefile
================
diff b/driver/char/maria-led.c
+++ b/driver/char/maria-led.c
@@ +0,91 @@
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/miscdevice.h>
+ #include <linux/types.h>
+ #include <linux/ioctl.h>
+ #include <linux/cdev.h>
+ #include <linux/delay.h>
+ #include <linux/gpio.h>
+ #include <linux/errno.h>
+ #include <linux/fs.h>
+ #include <linux/init.h>
+
+ #define CORE_LED (3 * 32 + 8)
+ #define DEVICE "led_core"
+ #define MAGIC_NUM 0xDB
+ #define MAGIC_SET_LOW _IO(MAGIC_NUM, 0)
+ #define MAGIC_SET_HIGH _IO(MAGIC_NUM, 1)
+
+ int led_open(struct inode *inode, struct file *filp)
+ {
+ int ret;
+ ret = gpio_request_one(CORE_LED,
+ (GPIOF_DIR_OUT | GPIOF_OUT_INIT_HIGH), "core_led");
+ if (ret) {
+ printk("Error: cannot request gpio CORE_LED.\n");
+ printk("Error ret = %d but still can be manipulated.\n", ret);
+ }
+ gpio_set_value(CORE_LED, 1);
+ return 0;
+ }
+
+ int led_release(struct inode *inode, struct file *filp)
+ {
+ gpio_free(CORE_LED);
+ printk("GPIO LED dev release.\n");
+ return 0;
+ }
+
+ long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+ {
+ switch (cmd) {
+ case MAGIC_SET_LOW:
+ printk("GPIO LED set low.\n");
+ gpio_set_value(CORE_LED, 0);
+ break;
+ case MAGIC_SET_HIGH:
+ printk("GPIO LED set high.\n");
+ gpio_set_value(CORE_LED, 1);
+ break;
+ default:
+ printk("Error: unvalid cmd.\n");
+ break;
+ }
+ return 0;
+ }
+
+ struct file_operations led_fops = {
+ .owner = THIS_MODULE,
+ .open = led_open,
+ .release = led_release,
+ .unlocked_ioctl = led_ioctl,
+ };
+
+ struct miscdevice led_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = DEVICE,
+ .fops = &led_fops,
+ };
+
+ static int __init led_init(void)
+ {
+ int ret;
+ ret = misc_register(&led_dev);
+ if (ret) {
+ printk("Error: cannot register misc.\n");
+ return ret;
+ }
+ printk("misc_register %s\n", DEVICE);
+ return 0;
+ }
+
+ static void __exit led_exit(void)
+ {
+ misc_deregister(&led_dev);
+ printk("misc_deregister %s\n", DEVICE);
+ }
+
+ module_init(led_init);
+ module_exit(led_exit);
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("Marianna Zhu <mariabrook@sina.com>");
此处采用了misc方式来注册设备,实际上典型的设备注册并不是使用这种方法,建议参考《深入理解Linux设备驱动程序》的第三章。misc是为简易字符型设备而设计的驱动策略,这里可以使用它,因为GPIO本身就是简单到不能再简单的字符型设备。
file方法最常见的是read和write,但是由于GPIO的操作并不像是数据的读写(当然也可以使用数据读写的方式),而像是根据某个命令执行操作,因此这里使用ioctl方法。
还有要注意的地方是,gpio_request放置在了open函数里,而不是在module_init中,这是由于模块一般跟随系统初始化时装载,运行整个周期而很少被卸载,因此如果GPIO在module_init时request,那么当其它模块要使用这个GPIO时就非常不方便,因此最好设置为open时request。
================
diff a/driver/char/Kconfig b/driver/char/Kconfig
--- a/driver/char/maria-hello.c
+++ b/driver/char/maria-hello.c
@@ -13,0 +13,6 @
+ config MARIA_LED
+ tristate "maria_led module"
+ default m
+ help
+ This is a Module for LED Driving.
================
diff a/driver/char/Makefile b/driver/char/Makefile
--- a/driver/char/Makefile
+++ b/driver/char/Makefile
@@ -5,0 +5,1 @@
+ obj-$(CONFIG_MARIA_LED) += maria-led.o
================
此处只需要编译模块即可,无需再编译uImage和maria-am335x.dtb。编译完成之后将其放置进SD卡的boot分区,并mount它,使用insmod命令装载模块。
make ARCH=arm CROSS_COMPILE=/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8 menuconfig
make ARCH=arm CROSS_COMPILE=/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8 modules
root@ok335x:/media# mount /dev/mmcblk0p1 /media/maria/
root@ok335x:/media# cd /media/maria/
root@ok335x:/media/maria# insmod maria-led.ko
[ 135.045881] misc_register led_core
====> Step 6: <====
编写led的应用程序。这个应用程序实现的功能非常简单,就是让核心板上的led灯间隔1s一亮一灭。Linux所有设备都是文件,因此程序需要执行的操作是,打开文件,并使用ioctl方法操作文件。
================
diff b/../maria-led.c
+++ b/../maria-led.c
@@ +0,32 @@
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ #include <errno.h>
+ #include <string.h>
+ #include <asm-generic/ioctl.h>
+
+ #define DEVICE "/dev/led_core"
+ #define MAGIC_NUM 0xDB
+ #define MAGIC_SET_LOW _IO(MAGIC_NUM, 0)
+ #define MAGIC_SET_HIGH _IO(MAGIC_NUM, 1)
+
+ int main(int argc, char *argv[])
+ {
+ int fd;
+
+ printf("Start led test.\n");
+
+ fd = open(DEVICE, O_RDWR);
+ if (fd < 2) {
+ printf("Cannot open device %s\n", DEVICE);
+ return -EFAULT;
+ }
+ while (1) {
+ ioctl(fd, MAGIC_SET_LOW, 0);
+ sleep(1);
+ ioctl(fd, MAGIC_SET_HIGH, 0);
+ sleep(1);
+ }
+ return 0;
+ }
编写完成之后,编译:
/opt/gcc-linaro-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -static led_test.c -o led_test
注意这里要使用静态编译,而不是动态编译,否则执行时会提示:No such file or directory.这可能是因为目前使用的文件系统和内核并不配套,anyway,这个问题等制作新的文件系统时再解决,目前先采用给gcc添加-static参数的方法。
在目标板上执行:
./led_test
就会发现核心板上的GPIO一亮一灭,这说明GPIO被成功的驱动。
另外,在首次使用这个模块gpio_request时会失败,返回错误值ret=-16,即-EBUSY,原因是这个GPIO已经在被使用了,答案就藏在driver/leds/leds-gpio.c中,它是mainline自带的led驱动。
(常见的错误返回值定义在include/uapi/asm-generic/errno-base.h中)
如何使用mainline中自带的led驱动呢?如何把它和dts文件中的compatible参数联系起来呢?这就是下一节的内容了。
文章评论(0条评论)
登录后参与讨论