0.重点
1.在.dts文件中创建相应地设备节点
2.编写驱动程序,获取设备树中的相关属性值
3.使用获取到的有关属性值来初始化LED所使用的GPIO
一.修改设备树文件(.dts文件)
1.在根节点"/"下创建一个名为"alphaled"的子节点,输入以下内容
alphaled
{
#address-cells = <1>; //表示reg属性中,起始地址占用一个字长
#size-cells = <1>; //表示reg属性中,地址长度占用一个字长
compatible = "atkalpha-led"; //设置节点兼容性位"atkalpha-led"
status = "okay";
/* reg属性中设置了驱动程序需要使用到的寄存器的物理地址 */
reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
0X0209C000 0X04 /* GPIO1_DR_BASE */
0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
};
2.在内核源码根目录下重新编译设备树文件
~/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek$ make dtbs
3.使用新的imx6ull-14x14-evk.dtb来启动Linux内核,这里是将设备树放到了tftp中使用网络启动。
注意:启动后在/proc/device-tree/目录中查看是否有alphaled这个节点。
二.准备(包含头文件等)
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DTSLED_CNT 1 /* 设备号个数 */
#define DTSLED_NAME "dtsled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* 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; /* 设备节点 */
};
struct dtsled_dev dtsled; /* led 设备 */
三.LED控制函数、open、read、write、release、绑定设备操作函数
①.LED控制函数
/*
* @description : LED 打开/关闭
* @param - sta : LEDON(0) 打开 LED, LEDOFF(1) 关闭 LED
* @return : 无
*/
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LED_ON)
{
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}
else if(sta == LED_OFF)
{
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
②.open函数
/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param – filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &dtsled; /* 设置私有数据 */
return 0;
}
③.read函数
/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
return 0;
}
④.write函数
/*
* @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!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LED_ON)
{
led_switch(LEDON); /* 打开 LED 灯 */
}
else if(ledstat == LED_OFF)
{
led_switch(LEDOFF); /* 关闭 LED 灯 */
}
return 0;
}
⑤.release函数
/*
* @description : 关闭/释放设备
* @param – filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
⑥.绑定设备操作函数
/* 设备操作函数 */
static struct file_operations dtsled_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
四.驱动入口函数和驱动出口函数
1.驱动入口函数
(1).获取设备数中的属性数据
①.获取设备节点
/* 获取设备数中的属性数据 */
/* 1.获取设备结点:alphaled */
dtsled.nd = of_find_node_by_path("/alphaled");
if(dtsled.nd == NULL)
{
printk("alphaled node can not found!\r\n");
return -EINVAL;
}
else
{
printk("alphaled node has been found!\r\n");
}
②.获取compatible属性内容
/* 2.获取compatible属性内容 */
proper = of_find_property(dtsled.nd,"compatible",NULL);
if(proper == NULL)
{
printk("compatible property find failed!\r\n");
}
else
{
printk("compatible = %s\r\n",(char *)proper->value);
}
③.获取status属性内容
/* 获取status属性内容 */
ret = of_property_read_string(dtsled.nd,"status",&str);
if(0 > ret)
{
printk("status read failed!\r\n");
}
else
{
printk("status = %s\r\n",str);
}
④.获取reg属性内容
/* 4、获取reg属性内容 */
ret = of_property_read_u32_array(dtsled.nd,"reg",regdata,10);
if(0 > ret)
{
printk("reg property read failed!\r\n");
}
else
{
uint8_t i = 0;
printk("reg data : \r\n");
for(i = 0; i< 10; i++)
printk("%#X",regdata[i]);
printk("\r\n");
}
(2).初始化LED
①.寄存器地址映射
/* 二、初始化LED */
#if 0
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0],regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2],regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4],regdata[5]);
GPIO1_DR = ioremap(regdata[6],regdata[7]);
GPIO1_GDIR = ioremap(regdata[8],regdata[9]);
#else
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd,0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd,1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd,2);
GPIO1_DR = of_iomap(dtsled.nd,3);
GPIO1_GDIR = of_iomap(dtsled.4);
#endif
②.使能GPIO1时钟
/* 2.使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); //清除以前的值
val |= (3 << 26); //设置新值
writel(val,IMX6U_CCM_CCGR1);
③.设置GPIO1_IO03的复用功能,将其复用为GPIO_IO03,最后设置IO属性
/* 3.设置GPIO1_IO03的复用功能,将其复用为GPIO1_IO03,最后设置IO属性 */
writel(5,SW_MUX_GPIO1_IO03);
//寄存器SW_PAD_GPIO1_IO03设置IO属性
writel(0x10b0,SW_PAD_GPIO1_IO03);
④.设置GPIO1_IO03为输出功能
/* 4.设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); //清除以前的设置
val |= (1 << 3); //设置为输出
writel(val,GPIO1_GDIR);
⑤.初始化设置为默认关闭LED
/* 5.初始化为关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val,GPIO1_DR);
(3).注册字符设备驱动
①.创建设备号
/* 三、注册字符设备驱动 */
/* 1.创建设备号 */
if(dtsled.major) //若定义了设备号
{
dtsled.devid = MKDEV(dtsled.major,0);
register_chrdev_region(dtsled.devid,DTSLED_CNT,DTSLED_NAME);
}
else //没有定义设备号
{
//申请设备号
alloc_chrdev_region(&dtsled.devid,0,DTSLED_CNT,DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid); //获取主设备号
dtsled.minor = MINOR(dtsled.devid); //获取此设备号
}
printk("dtsled major = %d, minor = %d\r\n",dtsled.major,dtsled.minor);
②.初始化cdev
/* 2.初始化cdev */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev,&dtsled_fops);
③.添加一个cdev
/* 3.添加一个cdev */
cdev_add(&dtsled.cdev,dtsled.devid,DTSLED_CNT);
④.创建类
/* 4.创建类 */
dtsled.class = class_create(THIS_MODULE,DTSLED_NAME);
if(IS_ERR(dtsled.class))
{
return PTR_ERR(dtsled.class);
}
⑤.创建设备
/* 5.创建设备 */
dtsled.device = device_create(dtsled.class,NULL,dtsled.devid,NULL,DTSLED_NAME);
if(IS_ERR(dtsled.device))
{
return PTR_ERR(dtsled.device);
}
return 0;
} //led_init函数结束
2.驱动出口函数
static void __exit led_exit(void)
{
/* 1.取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 2.注销字符设备驱动 */
cdev_del(&dtsled.cdev); //删除cdev
unregister_chrdev_region(dtsled.devid,DTSLED_CNT); //注销设备号
device_destroy(dtsled.class,dtsled.devid);
class_destroy(dtsled.class);
}
五.绑定驱动入口函数与出口函数
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kaneki");
六.编译驱动程序,并加载驱动
1.编写Makefile
#包含Linux内核源码的路径
KERNELDIR := /home/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH :=$(shell pwd)
obj-m := dtsled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
2.编译
make -j32
3.加载驱动
depmod //第一次加载驱动的时候需要运行此命令
modprobe dtsled.ko //加载驱动