1.时钟树
2.需要控制的LED的GPIO引脚
GPIO8_A1,找到对应的时钟控制描述
通过Adress maping 找到这个寄存器的地址:0xFF760000 + 0x0198
时钟使能:CRU_CLKGATE14_CON (0xFF760000 + 0x0198);
IO复用: GRF_GPIO8A_IOMUX (0xFF770000 + 0x0080);
IO方向: GPIO8_SWPORTA_DDR (0xFF7F0000 + 0x0004);
IO数据: GPIO8_SWPORTA_DR (0xFF7F0000 + 0x0000,);
在rk3288_firefly.dts添加设备节点
myled-node {
xxx_led = "led_for_test";
#address-cells = <1>;
#size-cells = <1>;
compatible = "rk3288_testled";
status = "okay";
reg = < 0xFF760198 4 /* 时钟使能*/
0xFF770080 4 /* IO 复用*/
0xFF7F0004 4 /* 设置方向*/
0xFF7F0000 4>; /* 数据设置*/
};
然后在内核根目录下 make dtbs,然后更换这个文件: /boot/rk3288-frefly.dtb
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/mach/map.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/amba/bus.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#define DTSLED_CNT 1 /* 设备号个数 */
#define DTSLED_NAME "rk3288_dtsled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 设备结构体 */
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 rk3288_dtsled; /* led设备 */
/* 映射后的寄存器虚拟地址指针 */
static void __iomem * CRU_CLKGATE14_CON;
static void __iomem * GRF_GPIO8A_IOMUX ;
static void __iomem * GPIO8A_SWPORTA_DDR;
static void __iomem * GPIO8A_SWPORTA_DR ;
void led_switch(u8 sta)
{
u32 val =0;
if(sta == LEDON)
{
val = readl(GPIO8A_SWPORTA_DR);
val &= ~(1<<1);
writel(val,GPIO8A_SWPORTA_DR); //GPIO8_A1输出低电平
}
else if(sta == LEDOFF)
{
val = readl(GPIO8A_SWPORTA_DR);
val |= (1<<1);
writel(val,GPIO8A_SWPORTA_DR); //GPIO8_A1输出高电平
}
}
/*
* @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 = &rk3288_dtsled; //设置私有数据
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt, loff_t *off_t)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *off_t)
{
int ret;
unsigned char databuf[1];
unsigned char ledstat;
ret = copy_from_user(databuf,buf,cnt);
if(ret < 0)
{
printk("kernel write failed \n");
return -1;
}
ledstat = databuf[0];
if(ledstat == LEDON)
{
led_switch(LEDON);
}
else if(ledstat == LEDOFF)
{
led_switch(LEDOFF);
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
printk("led release \n");
return 0;
}
static struct file_operations rk3288_dtsled_fops ={
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* 驱动的入口函数
*/
static int __init led_init(void)
{
int ret;
const char *str;
unsigned int regdata[14];
struct property *proper;
unsigned int val =0;
/* 获取设备树中的属性数据 */
//1.获取设备节点
rk3288_dtsled.nd = of_find_node_by_path("/myled-node");
if(rk3288_dtsled.nd == NULL)
{
printk("myled-node can not find \n");
}
else
{
printk("myled-node has been found \n");
}
//2.获取属性内容
proper = of_find_property(rk3288_dtsled.nd,"compatible",NULL);
if(proper == NULL)
{
printk("compatible property find failed \n");
}
else
{
printk("compatible = %s \n",(char *)proper->value);
}
/* 3、获取 status 属性内容 */
ret = of_property_read_string(rk3288_dtsled.nd, "status", &str);
if(ret < 0){
printk("status read failed!\r\n");
} else {
printk("status = %s\r\n",str);
}
/* 4、获取 reg 属性内容 */
ret = of_property_read_u32_array(rk3288_dtsled.nd, "reg", regdata, 8);
if(ret < 0) {
printk("reg property read failed!\r\n");
} else {
u8 i = 0;
printk("reg data:\r\n");
for(i = 0; i < 8; i++)
printk("%#X ", regdata[i]);
printk("\r\n");
}
{
struct platform_device *op = of_find_device_by_node(rk3288_dtsled.nd);
printk("num_resources: %d \n", op->num_resources);
}
#if 1
/* 1、寄存器地址映射 */
CRU_CLKGATE14_CON = ioremap(regdata[0], regdata[1]);
GRF_GPIO8A_IOMUX = ioremap(regdata[2], regdata[3]);
GPIO8A_SWPORTA_DDR = ioremap(regdata[4], regdata[5]);
GPIO8A_SWPORTA_DR = ioremap(regdata[6], regdata[7]);
#else
/* 初始化LED */
CRU_CLKGATE14_CON = of_iomap(rk3288_dtsled.nd, 0);
//GRF_GPIO8A_IOMUX = of_iomap(rk3288_dtsled.nd, 1);
//GPIO8A_SWPORTA_DDR = of_iomap(rk3288_dtsled.nd, 2);
//GPIO8A_SWPORTA_DR = of_iomap(rk3288_dtsled.nd, 3);
#endif
#if 1
//使能GPIO8的时钟
val = readl(CRU_CLKGATE14_CON);
val &= ~(0<<8);
val |= (1<<(8+16));
writel(val,CRU_CLKGATE14_CON);
//设置GPIO8_A1的复用功能
val = readl(GRF_GPIO8A_IOMUX);
val &= ~(0<<2);
val |= (3<<(2+16));
writel(val,GRF_GPIO8A_IOMUX);
//设置GPIO8_A1的方向输出
val = readl(GPIO8A_SWPORTA_DDR);
val |= 1<<1;
writel(val,GPIO8A_SWPORTA_DDR);
//默认关闭led
val = readl(GPIO8A_SWPORTA_DR);
val |= (1<<1);
writel(val,GPIO8A_SWPORTA_DR);
#endif
/* 注册字符设备驱动 */
//1.创建设备号
if(rk3288_dtsled.major){
rk3288_dtsled.devid = MKDEV(rk3288_dtsled.major,0);
register_chrdev_region(rk3288_dtsled.devid,DTSLED_CNT,DTSLED_NAME);
}else{
alloc_chrdev_region(&rk3288_dtsled.devid,0, DTSLED_CNT,
DTSLED_NAME); //申请设备号
rk3288_dtsled.major = MAJOR(rk3288_dtsled.devid);
rk3288_dtsled.minor = MINOR(rk3288_dtsled.devid);
}
printk("rk3288_dtsled major=%d,minor=%d \n",rk3288_dtsled.major,rk3288_dtsled.minor);
//初始化cdev
rk3288_dtsled.cdev.owner = THIS_MODULE;
cdev_init(&rk3288_dtsled.cdev,&rk3288_dtsled_fops);
//添加一个cdev
cdev_add(&rk3288_dtsled.cdev,rk3288_dtsled.devid,DTSLED_CNT);
//创建类
rk3288_dtsled.class = class_create(THIS_MODULE,DTSLED_NAME);
if(IS_ERR(rk3288_dtsled.class))
return PTR_ERR(rk3288_dtsled.class);
//创建设备
rk3288_dtsled.device = device_create(rk3288_dtsled.class,NULL,rk3288_dtsled.devid,
NULL,DTSLED_NAME);
if(IS_ERR(rk3288_dtsled.device)){
return PTR_ERR(rk3288_dtsled.device);
}
return 0;
}
static void __exit led_exit(void)
{
iounmap(CRU_CLKGATE14_CON);
iounmap(GRF_GPIO8A_IOMUX);
iounmap(GPIO8A_SWPORTA_DDR);
iounmap(GPIO8A_SWPORTA_DR);
//注销字符设备驱动
cdev_del(&rk3288_dtsled.cdev); //删除cdev
unregister_chrdev_region(rk3288_dtsled.devid,DTSLED_CNT); //注销设备号
device_destroy(rk3288_dtsled.class,rk3288_dtsled.devid);
class_destroy(rk3288_dtsled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
//这里of_iomap会报错:先留个坑,后面解决·······································
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDON 1
#define LEDOFF 0
int main(int argc, char *argv[])
{
int fd,retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3)
{
printf("error usage \n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0)
{
printf("file %s open failed \n", filename);
}
databuf[0] = atoi(argv[2]);
retvalue = write(fd,databuf,sizeof(databuf));
if(retvalue < 0)
{
printf("led write failed \n");
close(fd);
return -1;
}
retvalue = close(fd);
printf("test nor \n");
}
Makefile文件
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/mysamba/RK_3288_SDK/linux-4.4
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ledtest ledtest.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ledtest
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += rk3288_led_driver.o