参考:https://wiki.sipeed.com/soft/Lichee/zh/Zero-Doc/Drive/GPIO_mmap.html
上一篇:荔枝派zero驱动开发03:设备树基础
下一篇:荔枝派zero驱动开发05:GPIO操作(使用GPIO子系统)
- 关键词:ioremap/iounmap,copy_from_user/copy_to_user,readl/writel
设备树修改:
本文不涉及设备树操作,但由于默认设备树配置了LED,因此先在设备树中禁用默认的LED配置,重新编译设备树后,使用新的设备树启动
关键代码:
#define V3S_GPIO_BASE 0x01C20800
// 模式寄存器,4bit,最高位保留,000输入,001为输出
#define V3S_GPIOG_MODE (V3S_GPIO_BASE + 0xD8)
// 每个脚的输入输出数据,1bit
#define V3S_GPIOG_DATA (V3S_GPIO_BASE + 0xE8)
static void __iomem *GPIOG_MODE;
static void __iomem *GPIOG_DATA;
GPIOG_MODE = ioremap(V3S_GPIOG_MODE, 4);
GPIOG_DATA = ioremap(V3S_GPIOG_DATA, 4);
uint32_t val=0;
val=readl(GPIOG_MODE);
val &= ~(7 << (PIN_N * 4)); // 清除配置
val |= (1 << (PIN_N * 4)); // 配置为输出
writel(val, GPIOG_MODE);
val=readl(GPIOG_DATA);
val &= ~(1 << PIN_N);
writel(val, GPIOG_DATA); // 引脚初始化为低
根据v3s寄存器表,GPIO的物理地址基址为0x01C20800,GPIOG的模式寄存器地址为(0x01C20800+0xD8)
在裸机开发中,可以直接读写*(0x01C20800+0xD8)来操作GPIO寄存器;但在linux中,内核不能直接访问物理地址,必须映射为虚拟地址后才可以访问,映射和取消映射的函数为ioremap()和iounmap()。
writel() 向内存映射的物理地址上写数据,wirtel() 写入 32 位数据 (4字节);readl同理,读取32位数据 (4字节);类似的操作函数有writeb/writew/writel,分别对应8位、16位、32位操作
位操作比较清晰,清除指定的位,或在指定位写入数据;v3s的模式寄存器每个脚占用4bit,0b000输入,0b001输出,要配置为输出直接向指定的4bit写入0b001即可
static ssize_t led_gpio_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
unsigned char val;
copy_from_user(&val, user_buf, 1);
printk("led_gpio_write: 0x%02x\n", val);
if (val=='1' || val == 1) //默认输入为0x30即字符'0'
{
val=readl(GPIOG_DATA);
val &= ~(1 << PIN_N); //低电平点亮
writel(val, GPIOG_DATA);
}
else if (val=='0' || val == 0)
{
val=readl(GPIOG_DATA);
val|= (1 << PIN_N); //高电平熄灭
writel(val, GPIOG_DATA);
}
return 1;
}
用户空间内存不能直接访问内核空间的内存,需要借助函数 copy_from_user 将用户空间的数据复制到内核空间;LED控制较为简单,只需一个字符即可控制LED,这里复制1个字符即可,应用层也只需一个字符控制LED
测试:
其他参考之前的字符设备模板写即可,修改Makefile并编译,将生成的ledchar.ko拷贝到开发板,注意前述的设备树修改
使用echo测试
使用应用层APP测试
这里也写了一个简单的测试app,源码附在文后,调用本条命令进行编译,编译出目标文件拷贝到开发板
/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -o ledcharApp ledcharApp.c
APP运行测试:
一点心得:
本篇是最接近裸机的开发方式了,简单直接、深入底层,不涉及Linux的程序框架与造好的轮子;实际开发中几乎不会使用这种开发方式,但有助于理解驱动框架和硬件的关系,可以有一个直观感受。
源码
ledcharApp.c文章来源:https://www.toymoban.com/news/detail-808037.html
#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"
#include "string.h"
#include "sys/types.h"
#include "sys/stat.h"
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
char val = 0;
if (argc != 3)
{
printf("Usage !=3\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("cannot open file:%s\r\n", filename);
return -1;
}
val = atoi(argv[2]);
ret = write(fd, &val, sizeof(val));
if (ret < 0)
printf("cannot write file:%s\r\n", filename);
else
printf("app:set led:%d\r\n", val);
ret = close(fd);
if (ret < 0)
{
printf("cannot close file:%s\r\n", filename);
return -1;
}
return 0;
}
ledchar.c文章来源地址https://www.toymoban.com/news/detail-808037.html
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/printk.h>
#include <linux/uaccess.h>
struct ledchar_dev
{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
};
struct ledchar_dev ledchar = {
.major = 0,
};
#define PIN_N 0 // 第0个引脚,PG0,绿色
#define DEV_NAME "led"
#define LED_ON 0 // 上拉,低电平亮
#define LED_OFF 1
#define V3S_GPIO_BASE 0x01C20800
// 模式寄存器,4bit,最高位保留,000输入,001为输出
#define V3S_GPIOG_MODE (V3S_GPIO_BASE + 0xD8)
// 每个脚的输入输出数据,1bit
#define V3S_GPIOG_DATA (V3S_GPIO_BASE + 0xE8)
// 驱动能力,2bit,0-3逐级递增
#define V3S_GPIOG_DRIVING (V3S_GPIO_BASE + 0xEC)
// 上拉下拉,2bit,0浮空,1上拉,2下拉
#define V3S_GPIOG_PULL (V3S_GPIO_BASE + 0xF4)
// 中断配置,4bit,0上升,1下降,2高电平,3低电平,4双边沿
#define V3S_GPIOG_INTCFG (V3S_GPIO_BASE + 0x240)
// 中断使能,1bit,1使能
#define V3S_GPIOG_INT_CTRL (V3S_GPIO_BASE + 0x250)
// 中断状态,1bit,1发生中断,写1清除
#define V3S_GPIOG_INT_STA (V3S_GPIO_BASE + 0x254)
// 中断时钟及分频配置
#define V3S_GPIOG_INT_DEB (V3S_GPIO_BASE + 0x258)
static void __iomem *GPIOG_MODE;
static void __iomem *GPIOG_DATA;
static void __iomem *GPIOG_DRIVING;
static void __iomem *GPIOG_PULL;
static int led_gpio_open(struct inode *inode, struct file *file)
{
return 0;
}
static int led_gpio_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static int led_gpio_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t led_gpio_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
unsigned char val;
copy_from_user(&val, user_buf, 1);
printk("led_gpio_write: 0x%02x\n", val);
if (val=='1' || val == 1)
{
val=readl(GPIOG_DATA);
val &= ~(1 << PIN_N); //低电平点亮
writel(val, GPIOG_DATA);
}
else if (val=='0' || val == 0)
{
val=readl(GPIOG_DATA);
val|= (1 << PIN_N); //高电平熄灭
writel(val, GPIOG_DATA);
}
return 1;
}
static const struct file_operations ledchar_fops = {
.open = led_gpio_open,
.read = led_gpio_read,
.release = led_gpio_release,
.write = led_gpio_write,
};
static int __init led_driver_init(void)
{
int ret;
GPIOG_MODE = ioremap(V3S_GPIOG_MODE, 4);
GPIOG_DATA = ioremap(V3S_GPIOG_DATA, 4);
GPIOG_DRIVING = ioremap(V3S_GPIOG_DRIVING, 4);
GPIOG_PULL = ioremap(V3S_GPIOG_PULL, 4);
uint32_t val=0;
val=readl(GPIOG_MODE);
val &= ~(7 << (PIN_N * 4)); // 清除配置
val |= (1 << (PIN_N * 4)); // 配置为输出
writel(val, GPIOG_MODE);
val=readl(GPIOG_DATA);
val &= ~(1 << PIN_N);
writel(val, GPIOG_DATA); // 引脚初始化为低
if (ledchar.major) // 定义了设备号,静态设备号
{
ledchar.devid = MKDEV(ledchar.major, 0);
ret = register_chrdev_region(ledchar.major, 1, DEV_NAME);
if (ret < 0)
{
pr_err("cannot register %s char driver.ret:%d\r\n", DEV_NAME, ret);
goto exit;
}
}
else // 没有定义设备号,动态申请设备号
{
ret = alloc_chrdev_region(&ledchar.devid, 0, 1, DEV_NAME);
if (ret < 0)
{
pr_err("cannot alloc_chrdev_region,ret:%d\r\n", ret);
goto exit;
}
ledchar.major = MAJOR(ledchar.devid);
ledchar.minor = MINOR(ledchar.devid);
}
printk("led major=%d,minor=%d\r\n", ledchar.major, ledchar.minor);
ledchar.cdev.owner = THIS_MODULE;
cdev_init(&ledchar.cdev, &ledchar_fops);
ret = cdev_add(&ledchar.cdev, ledchar.devid, 1);
if (ret < 0)
goto del_unregister;
ledchar.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(ledchar.class))
goto del_cdev;
ledchar.device = device_create(ledchar.class, NULL, ledchar.devid, NULL, DEV_NAME);
if (IS_ERR(ledchar.device))
goto destroy_class;
return 0;
// 注意 goto后的标签没有return操作,将顺序执行多个label直至return,这里反向写
destroy_class:
class_destroy(ledchar.class);
del_cdev:
cdev_del(&ledchar.cdev);
del_unregister:
unregister_chrdev_region(ledchar.devid, 1);
exit:
printk("chardev_init failed\r\n");
return -EIO;
}
static void __exit led_driver_exit(void)
{
iounmap(GPIOG_MODE);
iounmap(GPIOG_DATA);
iounmap(GPIOG_DRIVING);
iounmap(GPIOG_PULL);
cdev_del(&ledchar.cdev);
unregister_chrdev_region(ledchar.devid, 1);
device_destroy(ledchar.class, ledchar.devid);
class_destroy(ledchar.class);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("USER");
MODULE_INFO(intree, "Y");
到了这里,关于荔枝派zero驱动开发04:GPIO操作(寄存器方式)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!