前言
今天是儿童节,挣个奖牌给小孩玩玩。
在 linux 驱动大家庭中,LED 驱动算是个儿童,今天就写写他吧。正好之前写过他的婴儿时期《i.MX6ULL 裸机点亮 LED》,记得那时候他还穿着开裆裤呢,裸鸡嘛。
ioremap()
裸机程序也好、linux 驱动程序也好,最终都是要操作真实设备的,那就要操作物理地址(设备寄存器)。之前写裸机程序,由于没有开启 MMU,CPU 操作的地址就是物理地址。现在写 linux 驱动程序不一样了,内核(包括驱动)操作的都是虚拟地址而无法直接操作物理地址。要想操作物理地址,就要上一个大杀器:ioremap(),可以参考我之前的一篇文章《ioremap()》,这里就不多介绍了。
代码
led.c
#include <asm/io.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 字符设备名称: cat /proc/devices 显示的字符设备名称 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 寄存器物理地址 */
#define CCM_CCR_BASE 0x020C4000 // Clock Controller Module(CCM)
#define CCM_CCGR1 (CCM_CCR_BASE + 0x6C)
#define GPIO1_BASE 0x0209C000
#define GPIO1_DR (GPIO1_BASE + 0x0)
#define GPIO1_GDIR (GPIO1_BASE + 0x4)
#define SW_MUX_CTL_BASE 0x020E0000 // software mux control registers
#define SW_MUX_CTL_PAD_GPIO1_IO03 (SW_MUX_CTL_BASE + 0x68)
#define SW_PAD_CTL_PAD_GPIO1_IO03 (SW_MUX_CTL_BASE + 0x2F4)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *V_CCM_CCGR1;
static void __iomem *V_SW_MUX_GPIO1_IO03;
static void __iomem *V_SW_PAD_GPIO1_IO03;
static void __iomem *V_GPIO1_DR;
static void __iomem *V_GPIO1_GDIR;
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
void led_switch(u8 sta)
{
u32 val;
if (sta == LEDON) {
val = readl(V_GPIO1_DR);
val &= ~(1 << 3);
writel(val, V_GPIO1_DR);
} else if (sta == LEDOFF) {
val = readl(V_GPIO1_DR);
val |= (1 << 3);
writel(val, V_GPIO1_DR);
}
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if (retvalue < 0) {
printk("kernel write failed!\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if (ledstat == LEDON) {
led_switch(LEDON); /* 打开LED灯 */
} else if (ledstat == LEDOFF) {
led_switch(LEDOFF); /* 关闭LED灯 */
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0;
/* 1. 寄存器地址映射 */
V_CCM_CCGR1 = ioremap(CCM_CCGR1, 4);
V_SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_CTL_PAD_GPIO1_IO03, 4);
V_SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_CTL_PAD_GPIO1_IO03, 4);
V_GPIO1_DR = ioremap(GPIO1_BASE, 4);
V_GPIO1_GDIR = ioremap(GPIO1_GDIR, 4);
/* 2. 使能GPIO1时钟 */
val = readl(V_CCM_CCGR1);
val |= (3 << 26);
writel(val, V_CCM_CCGR1);
/* 3. 设置GPIO1_IO03的复用功能,将其复用为 GPIO1_IO03,最后设置IO属性 */
writel(5, V_SW_MUX_GPIO1_IO03);
/* 4. 设置GPIO1_IO03为输出功能 */
val = readl(V_GPIO1_GDIR);
val |= (1 << 3);
writel(val, V_GPIO1_GDIR);
/* 5. 配置引脚属性,驱动能力、速度、上下拉 */
writel(0x10B0, V_SW_PAD_GPIO1_IO03);
/* 6. 默认关闭 LED */
val = readl(V_GPIO1_DR);
val |= (1 << 3);
writel(val, V_GPIO1_DR);
/* 7. 注册字符设备驱动 */
retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if (retvalue < 0) {
printk("register chrdev failed!\n");
return -EIO;
}
return 0;
}
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(V_CCM_CCGR1);
iounmap(V_SW_MUX_GPIO1_IO03);
iounmap(V_SW_PAD_GPIO1_IO03);
iounmap(V_GPIO1_DR);
iounmap(V_GPIO1_GDIR);
/* 注销字符设备驱动 */
unregister_chrdev(LED_MAJOR, LED_NAME);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liyongjun");
led_app.c
#include "fcntl.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/stat.h"
#include "sys/types.h"
#include "unistd.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if (argc != 3) {
printf("Usage: %s devfile 0/1\n", argv[0]);
return -1;
}
filename = argv[1];
/* 打开设备文件驱动 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("file %s open failed!\n", argv[1]);
return -1;
}
/* 要执行的操作:打开或关闭 */
databuf[0] = atoi(argv[2]);
/* 向 /dev/led 文件写入数据 */
retvalue = write(fd, databuf, sizeof(databuf));
if (retvalue < 0) {
printf("LED Control Failed!\n");
close(fd);
return -1;
}
/* 关闭文件 */
retvalue = close(fd);
if (retvalue < 0) {
printf("file %s close failed!\n", argv[1]);
return -1;
}
return 0;
}
Makefile
KERNELDIR := ../../../linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/
CURRENT_PATH := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
rm led_app
CROSS_COMPILE = ../../../tool/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
app:
$(CROSS_COMPILE) led_app.c -o led_app
install:
cp led.ko led_app ../../../rootfs/home/root/
验证
step1:安装驱动程序
insmod led.ko
# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
200 led
207 ttymxc
step2:创建字符设备文件
# mknod /dev/led c 200 0
#
# ls -lh /dev/led
crw-r--r-- 1 root root 200, 0 May 31 14:06 /dev/led
step3:执行测试程序
# ./led_app /dev/led 1
# ./led_app /dev/led 0
看到 led 亮、灭