荔枝派zero驱动开发04:GPIO操作(寄存器方式)

参考: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

#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

#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");
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值