一、GPIO寄存器
1、对GPIO进行控制有以下步骤
①:是能GPIO的时钟(默认开启,不用配置);
②: 设置引脚复用为GPIO(复位默认配置GPIO,不用配置);
③:设置引脚属性(上下拉、速率、驱动能力,默认不用配置);
④:控制GPIO引脚为输出,并且输出高低电平。
2、GPIO功能引脚及寄存器
rk3566 有5个gpio控制器 官方称为bank 每个控制器下 控制32个引脚 32个引脚 被分为4组(A B C D)
A B C D
0-7 8-15 16-23 24-31
基地址 PMU_GRF 0xFDC20000 SYS_GRF 0xFDC60000
偏移地址 PMU_GRF_GPIO3B_IOMUX_H 0x004C
复用功能寄存器: 基地址 + 偏移地址 = 0xFDC60000 + 0x004C = 0xFDC6004C
0xFDC6004C 0- 2位写0(gpio功能) 16-18 写1(写使能) 对寄存器进行操作时一定要先将值读出来 将此值或上我们要写得值 对gpio3_b4引脚初始化复用功能位gpio 0x00004440 | 0x70000
读四个字节的数据 root@linaro-alip:/# io -r -4 0xFDC6004C fdc6004c: 00004440
写四个字节的数据 root@linaro-alip:/# io -w -4 0xFDC6004C 0x00074440
gpio控制器基地址 GPIO3 0xFE760000 方向寄存器偏移地址
GPIO_SWPORT_DDR_L 0x0008
GPIO_SWPORT_DDR_H 0x000C
A B C D 0-7 8-15 16-23 24-31
b0 b1 b2 b3 b4 b5 b6 b7
8 /9 /10 /11/12 /13 /14 /15
0xFE760000 + 0x0008 = 0xFE760008 12位写1(设置为输出) 28位写1(写使能) 0x00000000 |= 0x10001000
读四个字节的数据 root@linaro-alip:/# io -r -4 0xFE760008 fe760008: 00000000
写四个字节的数据 root@linaro-alip:/# io -w -4 0xFE760008 0x10001000
数据寄存器 GPIO_SWPORT_DR_L 0x0000 0xFE760000 + 0x0000 = 0xFE760000 12位写1(设置输出高电平) 28位写1(写使能) 0x00000000 |= 0x10001000
读四个字节的数据 root@linaro-alip:/# io -r -4 0xFE760000 fe760000: 00000000
写四个字节的数据 root@linaro-alip:/# io -w -4 0xFE760000 0x10001000
寄存器地址 都是物理地址 驱动在内核层 运行在3-4g的虚拟内存中 是不可以直接操作物理地址的 我们要将物理地址映射到虚拟内存中 才可以进行操作
void * ioremap(unsigned long offset, unsigned long size)
功能:将物理内存映射到虚拟内存中
参数: offset 偏移地址 size 大小 字节
返回值:成返回虚拟地址 失败返回NULL
void iounmap(void __iomem *addr);
功能:注销映射
参数:虚拟地址
返回值:无
3、led设备驱动模块(ledchardev.c)
/*
** 复用型引脚分为5组(GPIO0~4),每组里面都有32个复用型引脚,而且又分为4个小组(A、B、C、D),每个小组8个引脚(0~7)
** GPIO3_4B
** 在GPIO3大组,第二的B小组,第4个引脚,
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/device.h>
#include <linux/io.h>
//设备名称
#define DEV_NAME "ledchardev"
#define DEV_CNT 1
//复用功能寄存器
#define PMU_GRF_GPIO3B_IOMUX_H 0xFDC6004C
//GPIO方向控制寄存器
#define GPIO_SWPORT_DDR_L 0xFE760008
//GPIO数据寄存器
#define GPIO_SWPORT_DR_L 0xFE760000
#define LED_ON 1
#define LED_OFF 0
char kbuf[128] = {0};
//定义字符设备的设备编号
static dev_t dev_id;
//定义一个设备类
struct class *led_chrdev_class;
//定义新的一个结构体 struct chr_dev
struct led_chrdev{
struct cdev dev;
unsigned int __iomem *va_iomux;
unsigned int __iomem *va_ddr;
unsigned int __iomem *va_dr;
unsigned int led_pin;
};
static struct led_chrdev led_cdev[DEV_CNT] = {
{.led_pin = 12 }, //GPIO3_B4,就是8+4=12,对应DR_L, A和B属于低,C和D属于高。
};
void led_switch(unsigned char state)
{
unsigned int val;
if(state == LED_ON){
val = ioread32(led_cdev->va_dr);
val |= ((unsigned int)0x01 << (led_cdev->led_pin+16));
val |= ((unsigned int)0x01 << (led_cdev->led_pin)); //输出高电平
iowrite32(val, led_cdev->va_dr);
printk(KERN_ALERT "[ KERN_ALERT ] led turn on dr_val=%#lX.\n", *(led_cdev->va_dr));
}
else if(state == LED_OFF){
val = ioread32(led_cdev->va_dr);
val |= ((unsigned int)0x01 << (led_cdev->led_pin+16));
val &= ~((unsigned int)0x01 << (led_cdev->led_pin)); //输出低电平
iowrite32(val, led_cdev->va_dr);
printk(KERN_ALERT "[ KERN_ALERT ] led turn off dr_val=%#lX.\n", *(led_cdev->va_dr));
}
}
int led_open(struct inode *inode, struct file *file)
{
unsigned int val = 0;
//通过 led_chrdev 结构变量中 dev 成员的地址找到这个结构体变量的首地址
struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev, dev);
//把文件的私有数据 private_data 指向设备结构体 led_cdev
file->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);
printk(KERN_ALERT "[ KERN_ALERT ] ledchardev open ...\n");
//设置IO复用(复位就默认设置GPIO复用功能,可以不配置iomux寄存器)
val = ioread32(led_cdev->va_iomux);
//val |= 0x70000;
val |= ((unsigned int)0x07 << (0+16));
val &= ~((unsigned int)0x01 << (0));
iowrite32(val, led_cdev->va_iomux);
printk(KERN_ALERT "[ KERN_ALERT ] va_iomux_val=%#lX.\n", *(led_cdev->va_iomux));
//设置输出模式
val = ioread32(led_cdev->va_ddr);
val = ((unsigned int)0x01 << (led_cdev->led_pin+16));
val |= ((unsigned int)0x01 << (led_cdev->led_pin));
iowrite32(val, led_cdev->va_ddr);
printk(KERN_ALERT "[ KERN_ALERT ] va_ddr_val=%#lX.\n", *(led_cdev->va_ddr));
//输出低电平
val = ioread32(led_cdev->va_dr);
val |= ((unsigned int)0x01 << (led_cdev->led_pin+16));
val &= ~((unsigned int)0x01 << (led_cdev->led_pin));
iowrite32(val, led_cdev->va_dr);
printk(KERN_ALERT "[ KERN_ALERT ] va_dr_val=%#lX.\n", *(led_cdev->va_dr));
return 0;
}
ssize_t led_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{
printk(KERN_ALERT "[ KERN_ALERT ] ledchardev read!\n");
return 0;
}
ssize_t led_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
unsigned char ret;
struct led_chrdev *led_cdev = (struct led_chrdev *)file->private_data;
if(size > sizeof(kbuf)){
size = sizeof(kbuf);
}
if(copy_from_user(kbuf, ubuf, size)){
printk(KERN_ALERT "[ KERN_ALERT ] copy data form user fail!\n");
return -EIO;
}
ret = kbuf[0];
if(ret == LED_ON){
led_switch(LED_ON);
}
else if(ret == LED_OFF){
led_switch(LED_OFF);
}
printk(KERN_ALERT "[ KERN_ALERT ] ledchardev write!\n");
return size;
}
int led_close(struct inode *inode, struct file *file)
{
printk(KERN_ALERT "[ KERN_ALERT ] ledchardev close!\n");
return 0;
}
struct file_operations led_chrdev_fops= {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_close
};
static int __init ledcdev_init(void)
{
int i = 0;
dev_t cur_dev;
unsigned int val = 0;
int ret = 0;
//物理地址映射虚拟地址,32位==>A0~A7 B0~B7 C0~C7 D0~D7,其中A B对应低16位,C D对应高16位
led_cdev[0].va_iomux = ioremap(PMU_GRF_GPIO3B_IOMUX_H, 4);
led_cdev[0].va_ddr = ioremap(GPIO_SWPORT_DDR_L, 4);
led_cdev[0].va_dr = ioremap(GPIO_SWPORT_DR_L, 4);
//动态分配主设备号,从设备号为0,可通过cat /proc/devices查看分配my_chrdev的主设备号
ret = alloc_chrdev_region(&dev_id, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk(KERN_ALERT "[ KERN_ALERT ] fail to alloc dev major.\n");
return ret;
}
//创建一个设备类
led_chrdev_class = class_create(THIS_MODULE, "ledchardev");
//关联结构体
for(i=0; i<DEV_CNT; i++){
cdev_init(&led_cdev[i].dev, &led_chrdev_fops);
led_cdev[i].dev.owner = THIS_MODULE;
cur_dev = MKDEV(MAJOR(dev_id), MINOR(dev_id)+i);
cdev_add(&led_cdev[i].dev, cur_dev, 1);
device_create(led_chrdev_class, NULL, cur_dev, NULL, DEV_NAME "%d", i);
}
printk(KERN_ALERT "[ KERN_ALERT ] ledchardev init ...\n");
return 0;
}
static void __exit ledcdev_exit(void)
{
int i = 0;
dev_t cur_dev;
printk(KERN_ALERT "[ KERN_ALERT ] ledchardev exit ...\n");
for(i=0; i<DEV_CNT; i++){
iounmap(led_cdev[i].va_dr);
iounmap(led_cdev[i].va_ddr);
iounmap(led_cdev[i].va_iomux);
}
for(i=0; i<DEV_CNT; i++){
cur_dev = MKDEV(MAJOR(dev_id), MINOR(dev_id)+i);
device_destroy(led_chrdev_class, cur_dev);
cdev_del(&led_cdev[i].dev);
}
unregister_chrdev_region(dev_id, DEV_CNT);
class_destroy(led_chrdev_class);
}
module_init(ledcdev_init);
module_exit(ledcdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zbl");
4、应用程序(ledchardevapp.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define LED_ON 1
#define LED_OFF 0
int main(int argc, char *argv[])
{
int fd;
int ret;
unsigned char databuf[1] = {0};
if(argc != 2){
printf("Error Usage!\n");
return -1;
}
fd = open("/dev/ledchardev0", O_RDWR);
if(fd == -1)
{
printf("open %s failed.\n", "/dev/ledchardev0");
return -1;
}
databuf[0] = atoi(argv[1]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0){
printf("LED control failed.\n");
close(fd);
return -1;
}
ret = close(fd);
if(ret < 0){
printf("close %s failed.\n", "/dev/ledchardev0");
return -1;
}
return 0;
}
5、交叉编译makefile
PWD ?= $(shell pwd)
KERNELDIR := /home/zbl/tspi-rk3566/sdk/linux/kernel
CROSS_COMPILE ?= /home/zbl/tspi-rk3566/sdk/linux/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
CC := $(CROSS_COMPILE)gcc
obj-m += ledchardev.o
module:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 modules
@# -C 表示从当前目录切换到内核源码目录下,借助内核源码makefile进行make编译
@# M=$(PWD) 表示只编译当前目录下的驱动
@# ARCH=arm64 指定编译架构
$(CC) ledchardevapp.c -o app
@# 交叉编译应用程序
.PHONE:clean
clean:
make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 clean
rm app
6、整体执行流程
1、编写内核驱动ledchardev.c
2、编写应用程序ledchardevapp.c
3、编写makefile,添加交叉编译工具及编译应用执行文件
4、.ko及app应用执行文件导入开发板
5、修改执行权限 chmod 777 app ledchardev.ko
6、加载内核启动 sudo insmod ledchardev.ko
root@localhost:/home/lckfb/zbl-kernel-modules/05# sudo insmod ledchardev.ko
[ 4294.260486] ledchardev reg successed.
[ 4294.263374] led reg init ok.
7、查看驱动lsmod
lckfb@linux:~/zbl-kernel-modules$ lsmod
Module Size Used by
ledchardev 16384 0
bcmdhd 1048576 0
8、查看驱动主设备号 cat /porc/devices
226 drm
236 ledchardev
237 hidraw
238 rpmb
239 ttyGS
9、查看节点是否加载成功 ls -l /dev/ledchardev0
lckfb@linux:~/zbl-kernel-modules$ ll /dev/ledchardev0
crw-r--r-- 1 root root 236, 0 Jun 4 13:59 /dev/ledchardev0
10、执行应用程序GPIO输出高电平,点亮LED ./app 1
root@localhost:/home/lckfb/zbl-kernel-modules/05# ./app 1
11、执行应用程序GPIO输出低电平,熄灭LED ./app 0
root@localhost:/home/lckfb/zbl-kernel-modules/05# ./app 1
12、卸载内核驱动(自动删除设备节点文件) sudo rmmod ledchardev.ko
root@localhost:/home/lckfb/zbl-kernel-modules# sudo rmmod ledchardev.ko
[ 2750.540287] [ KERN_ALERT ] ledchardev exit ...
标签:va,12,led,val,--,dev,ALERT,cdev,LED From: https://www.cnblogs.com/zblblog/p/18249815