编写字符驱动程序,测试下GPIO口的输出。
一、硬件部分
测试使用端口GPIO0_A4,开发板端口电路图如下:
二、硬件驱动分析
开发上LED端口对应的是GPIO3_D4端口,驱动程序也是配置这个端口测试的,下里面来分析下使用GPIO0_A4端口怎么配置。
开发板主芯片资料有些地方没有看太明白,根据芯片手册和开发板教程来理解测试。
配置端口需要配置以下方面:
PMUGRF_GPIO0A_IOMUX_H和PMUGRF_GPIO0A_DS_H寄存器来配置GPIO0_A4~GPIO0_A7端口的复用功能和驱动能力,下面是的偏移地址。
2.1、端口引脚复用配置
寄存器PMUGRF_GPIO0A_IOMUX_H的配置gpio0_A4选择GPIO功能,write_enable相应的为置1后,可以写入寄存器。
2.2、端口驱动能力配置
寄存器MUGRF_GPIO0A_DS_H的配置gpio0_A4_ds选择端口的驱动能力,这部分手册上也没有找到介绍,这里选择默认0。
write_enable相应的为置1后,可以写入寄存器。
2.3、GPIO输入输出设置
设置寄存器GPIO0_SWPORT_DDR_L
2.4、GPIO数据寄存器
端口的输入输出电平在寄存器GPIO0_SWPORT_DR_L中设置或读取
关于GPIO方向和输入\输出的电平设置可以参考开发板驱动手册中有管的讲解。
三、程序
3.1、驱动程序dev_led_n.c
//dev_led.c
#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/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <asm/io.h>
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_NAME "dev_led_n" //设备名字
struct chr_dev_led
{
dev_t devid;
struct cdev cdev; //定义一个 cdev 结构体
struct class *class; //定义类
struct device *device; //设备
int major; //主设备号
int minor; //次设备号
};
struct chr_dev_led new_chr_dev_led; //设备
//#define DEV_LED_MAJOR 200 /* 主设备号 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
#define GRF_BASE (0XFE000000)
#define GRF_GPIO3D_IOMUX_H (GRF_BASE + 0X1006C)
#define GRF_GPIO3D_DS_H (GRF_BASE + 0X100EC)
#define GPIO3_BASE (0XFF640000)
#define GPIO3_SWPORT_DR_H (GPIO3_BASE + 0X0004)
#define GPIO3_SWPORT_DDR_H (GPIO3_BASE + 0X000C)
#define PMUGRF_BASE (0XFE020000)
#define PMUGRF_GPIO0A_IOMUX_L (PMUGRF_BASE + 0X0000)
#define PMUGRF_GPIO0A_IOMUX_H (PMUGRF_BASE + 0X0004)
#define PMUGRF_GPIO0A_DS_L (PMUGRF_BASE + 0X0020)
#define PMUGRF_GPIO0A_DS_H (PMUGRF_BASE + 0X0024)
#define GPIO0_BASE (0XFF460000)
#define GPIO0_SWPORT_DR_L (GPIO0_BASE + 0X0000)
#define GPIO0_SWPORT_DR_H (GPIO0_BASE + 0X0004)
#define GPIO0_SWPORT_DDR_L (GPIO0_BASE + 0X0008)
#define GPIO0_SWPORT_DDR_H (GPIO0_BASE + 0X000C)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *GRF_GPIO3D_IOMUX_H_PI;
static void __iomem *GRF_GPIO3D_DS_H_PI;
static void __iomem *GPIO3_SWPORT_DR_H_PI;
static void __iomem *GPIO3_SWPORT_DDR_H_PI;
static void __iomem *PMUGRF_GPIO0A_IOMUX_L_PI;
static void __iomem *PMUGRF_GPIO0A_IOMUX_H_PI;
static void __iomem *PMUGRF_GPIO0A_DS_L_PI;
static void __iomem *PMUGRF_GPIO0A_DS_H_PI;
static void __iomem *GPIO0_SWPORT_DR_L_PI;
static void __iomem *GPIO0_SWPORT_DR_H_PI;
static void __iomem *GPIO0_SWPORT_DDR_L_PI;
static void __iomem *GPIO0_SWPORT_DDR_H_PI;
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO3_SWPORT_DR_H_PI);
val &= ~(0X1 << 12); /* bit12 清零*/
val |= ((0X1 << 28) | (0X1 << 12)); /* bit28 置1,允许写bit12,
bit12,低电平 */
writel(val, GPIO3_SWPORT_DR_H_PI);
//--------------------------------------------
val = readl(GPIO0_SWPORT_DR_L_PI);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, GPIO0_SWPORT_DR_L_PI);
}else if(sta == LEDOFF) {
val = readl(GPIO3_SWPORT_DR_H_PI);
val &= ~(0X1 << 12); /* bit12 清零*/
val |= ((0X1 << 28) | (0X0 << 12)); /* bit28 置1,允许写bit12,
bit12,低电平 */
writel(val, GPIO3_SWPORT_DR_H_PI);
//--------------------------------------------
val = readl(GPIO0_SWPORT_DR_L_PI);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X0 << 4));
writel(val, GPIO0_SWPORT_DR_L_PI);
}
}
/*
* @description : 物理地址映射
* @return : 无
*/
void led_remap(void)
{
GRF_GPIO3D_IOMUX_H_PI = ioremap(GRF_GPIO3D_IOMUX_H, 4);
GRF_GPIO3D_DS_H_PI = ioremap(GRF_GPIO3D_DS_H, 4);
GPIO3_SWPORT_DR_H_PI = ioremap(GPIO3_SWPORT_DR_H, 4);
GPIO3_SWPORT_DDR_H_PI = ioremap(GPIO3_SWPORT_DDR_H, 4);
//GPIO0_A
PMUGRF_GPIO0A_IOMUX_H_PI = ioremap(PMUGRF_GPIO0A_IOMUX_H, 4);
PMUGRF_GPIO0A_IOMUX_L_PI = ioremap(PMUGRF_GPIO0A_IOMUX_L, 4);
PMUGRF_GPIO0A_DS_L_PI = ioremap(PMUGRF_GPIO0A_DS_L, 4);
PMUGRF_GPIO0A_DS_H_PI = ioremap(PMUGRF_GPIO0A_DS_H, 4);
GPIO0_SWPORT_DR_L_PI = ioremap(GPIO0_SWPORT_DR_L, 4);
GPIO0_SWPORT_DR_H_PI = ioremap(GPIO0_SWPORT_DR_H, 4);
GPIO0_SWPORT_DDR_L_PI = ioremap(GPIO0_SWPORT_DDR_L, 4);
GPIO0_SWPORT_DDR_H_PI = ioremap(GPIO0_SWPORT_DDR_H, 4);
}
/*
* @description : 取消映射
* @return : 无
*/
void led_unmap(void)
{
/* 取消映射 */
iounmap(GRF_GPIO3D_IOMUX_H_PI);
iounmap(GRF_GPIO3D_DS_H_PI);
iounmap(GPIO3_SWPORT_DR_H_PI);
iounmap(GPIO3_SWPORT_DDR_H_PI);
iounmap(PMUGRF_GPIO0A_IOMUX_H_PI);
iounmap(PMUGRF_GPIO0A_IOMUX_L_PI);
iounmap(PMUGRF_GPIO0A_DS_H_PI);
iounmap(PMUGRF_GPIO0A_DS_L_PI);
iounmap(GPIO0_SWPORT_DR_L_PI);
iounmap(GPIO0_SWPORT_DR_H_PI);
iounmap(GPIO0_SWPORT_DDR_L_PI);
iounmap(GPIO0_SWPORT_DDR_H_PI);
}
static int dev_led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &new_chr_dev_led; //设置私有数据
return 0;
}
static ssize_t dev_led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t dev_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 == LEDON) {
led_switch(LEDON); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
led_switch(LEDOFF); /* 关闭LED灯 */
}
return 0;
}
static int dev_led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* 设备操作函数结构体
*/
static struct file_operations dev_led_fops = {
.owner = THIS_MODULE,
.open = dev_led_open,
.read = dev_led_read,
.write = dev_led_write,
.release = dev_led_release,
};
static int __init dev_led_init(void)
{
int retvalue = 0;
u32 val = 0;
/* 初始化LED */
/* 1、寄存器地址映射 */
led_remap();
/* 2、设置GPIO3_D4为GPIO功能。*/
val = readl(GRF_GPIO3D_IOMUX_H_PI);
val &= ~(0X7 << 0); /* bit2:0,清零 */
val |= ((0X7 << 16) | (0X0 << 0)); /* bit18:16 置1,允许写bit2:0,
bit2:0:0,用作GPIO3_D4 */
writel(val, GRF_GPIO3D_IOMUX_H_PI);
/* 3、设置GPIO3_D4驱动能力为level0 */
val = readl(GRF_GPIO3D_DS_H_PI);
val &= ~(0XF << 0); /* bit3:0清零*/
val |= ((0XF << 16) | (0X0 << 0)); /* bit19:16 置1,允许写bit3:0,
bit2:0:0,用作GPIO3_D4 */
writel(val, GRF_GPIO3D_DS_H_PI);
/* 4、设置GPIO3_D4为输出 */
val = readl(GPIO3_SWPORT_DDR_H_PI);
val &= ~(0X1 << 12); /* bit12 清零*/
val |= ((0X1 << 28) | (0X1 << 12)); /* bit28 置1,允许写bit12,
bit12,高电平 */
writel(val, GPIO3_SWPORT_DDR_H_PI);
/* 5、设置GPIO3_D4为低电平,关闭LED灯。*/
val = readl(GPIO3_SWPORT_DR_H_PI);
val &= ~(0X1 << 12); /* bit12 清零*/
val |= ((0X1 << 28) | (0X1 << 12)); /* bit28 置1,允许写bit12,
bit12,低电平 */
writel(val, GPIO3_SWPORT_DR_H_PI);
//-----------------------------------------------------------------------
//GPIO0_A--GPIO设置为GPIO功能
val = readl(PMUGRF_GPIO0A_IOMUX_H_PI);
val &= ~(0X7 << 0); /* bit2:0,清零 */
val |= ((0X7 << 16) | (0X0 << 0)); /* bit18:16 置1,允许写bit2:0,
bit2:0:0,用作GPIO0_A0 */
writel(val, PMUGRF_GPIO0A_IOMUX_H_PI);
//val = readl(PMUGRF_GPIO0A_IOMUX_L_PI);
//printk("PMUGRF_GPIO0A_IOMUX_L_PI=0x%x\r\n",val);
//设置GPIO0_A0驱动能力为level0
val = readl(PMUGRF_GPIO0A_DS_H_PI);
val &= ~(0XF << 0); /* bit3:0清零*/
val |= ((0XF << 16) | (0X0 << 0));
writel(val, PMUGRF_GPIO0A_DS_H_PI);
//设置GPIO0_A0为输出
val = readl(GPIO0_SWPORT_DDR_L_PI);
val &= ~(0X1 << 4); /* bit0 清零*/
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, GPIO0_SWPORT_DDR_L_PI);
//val = readl(GPIO0_SWPORT_DDR_L_PI);
//printk("GPIO0_SWPORT_DDR_L_PI=0x%x\r\n",val);
//设置GPIO0_A0为低电平
val = readl(GPIO0_SWPORT_DR_L_PI);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, GPIO0_SWPORT_DR_L_PI);
//printk("GPIO0_SWPORT_DR_L_PI=0x%x\r\n",val);
//-----------------------------------------------------------------------
//注册字符设备驱动
//创建设备号
if (new_chr_dev_led.major)
{
new_chr_dev_led.devid = MKDEV(new_chr_dev_led.major, 0);
retvalue = register_chrdev_region(new_chr_dev_led.devid, DEVICE_NUMBER, DEVICE_NAME);
if(retvalue < 0)
{
//printk("register chrdev failed!\r\n");
pr_err("cannot register %s char driver [ret=%d]\n",DEVICE_NAME, DEVICE_NUMBER);
goto fail_map;
}
}
else
{
retvalue = alloc_chrdev_region(&new_chr_dev_led.devid, 0, DEVICE_NUMBER, DEVICE_NAME);
if(retvalue < 0)
{
//printk("register chrdev failed!\r\n");
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DEVICE_NAME, retvalue);
goto fail_map;
}
new_chr_dev_led.major = MAJOR(new_chr_dev_led.devid);
new_chr_dev_led.minor = MINOR(new_chr_dev_led.devid);
}
printk(" major=%d,minor=%d\r\n",new_chr_dev_led.major,new_chr_dev_led.minor);
//初始化cdev
new_chr_dev_led.cdev.owner = THIS_MODULE;
cdev_init(&new_chr_dev_led.cdev, &dev_led_fops);
//添加cdev
retvalue = cdev_add(&new_chr_dev_led.cdev, new_chr_dev_led.devid, DEVICE_NUMBER);
if(retvalue < 0)
{
goto del_unregister;
}
//创建类
new_chr_dev_led.class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(new_chr_dev_led.class))
{
goto del_cdev;
}
//创建设备
new_chr_dev_led.device = device_create(new_chr_dev_led.class, NULL,new_chr_dev_led.devid, NULL, DEVICE_NAME);
if (IS_ERR(new_chr_dev_led.device))
{
goto destroy_class;
}
return 0;
destroy_class:
class_destroy(new_chr_dev_led.class);
del_cdev:
cdev_del(&new_chr_dev_led.cdev);
del_unregister:
unregister_chrdev_region(new_chr_dev_led.devid, DEVICE_NUMBER);
fail_map:
led_unmap();
return -EIO;
}
static void __exit dev_led_exit(void)
{
u32 val=0;
//--------------------------------------------------------------------------
val = readl(GPIO3_SWPORT_DR_H_PI);
val &= ~(0X1 << 12); /* bit12 清零*/
val |= ((0X1 << 28) | (0X0 << 12)); /* bit28 置1,允许写bit12,
bit12,低电平 */
writel(val, GPIO3_SWPORT_DR_H_PI);
val = readl(GPIO0_SWPORT_DR_L_PI);
val &= ~(0X1 << 4); /* bit12 清零*/
val |= ((0X1 << 20) | (0X0 << 4)); /* bit28 置1,允许写bit12,
bit12,低电平 */
writel(val, GPIO0_SWPORT_DR_L_PI);
//--------------------------------------------------------------------------
//取消映射
led_unmap();
//注销字符设备驱动
cdev_del(&new_chr_dev_led.cdev);//删除cdev
unregister_chrdev_region(new_chr_dev_led.devid, DEVICE_NUMBER); //注销设备号
device_destroy(new_chr_dev_led.class, new_chr_dev_led.devid);
class_destroy(new_chr_dev_led.class);
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(dev_led_init);
module_exit(dev_led_exit);
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");
3.2、应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char ledbuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
ledbuf[0]=atoi(argv[2]);
retvalue = write(fd, ledbuf, sizeof(ledbuf));
if(retvalue < 0){
printf("write file led failed!\r\n");
close(fd);
return -1;
}
retvalue = close(fd);
if(retvalue < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
四、运行
4.1、复制文件到开发板
root@ubuntu:/opt/atk-rv1126_app/led_test_n# scp dev_led_n.ko root@192.168.1.117:/lib/modules/4.19.111/
root@ubuntu:/opt/atk-rv1126_app/led_test_n# scp dev_led_app_n root@192.168.1.117:/lib/modules/4.19.111/
4.2、加载模块
[root@ATK-DLRV1126:/lib/modules/4.19.111]# modprobe dev_led_n
加载的的模块
自动创建的设备号
4.3、控制GPIO口输出
[root@ATK-DLRV1126:/lib/modules/4.19.111]# ./dev_led_app_n /dev/dev_led_n 0
[root@ATK-DLRV1126:/lib/modules/4.19.111]# ./dev_led_app_n /dev/dev_led_n 1
使用示波器查看GPIO0_A4引脚输出电平信号。