參考:https://wiki.sipeed.com/soft/Lichee/zh/Zero-Doc/Drive/GPIO_mmap.html
上一篇:荔枝派zero驅(qū)動(dòng)開發(fā)03:設(shè)備樹基礎(chǔ)
下一篇:荔枝派zero驅(qū)動(dòng)開發(fā)05:GPIO操作(使用GPIO子系統(tǒng))
- 關(guān)鍵詞:ioremap/iounmap,copy_from_user/copy_to_user,readl/writel
設(shè)備樹修改:
本文不涉及設(shè)備樹操作,但由于默認(rèn)設(shè)備樹配置了LED,因此先在設(shè)備樹中禁用默認(rèn)的LED配置,重新編譯設(shè)備樹后,使用新的設(shè)備樹啟動(dòng)
關(guān)鍵代碼:
#define V3S_GPIO_BASE 0x01C20800
// 模式寄存器,4bit,最高位保留,000輸入,001為輸出
#define V3S_GPIOG_MODE (V3S_GPIO_BASE + 0xD8)
// 每個(gè)腳的輸入輸出數(shù)據(jù),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); // 引腳初始化為低
根據(jù)v3s寄存器表,GPIO的物理地址基址為0x01C20800,GPIOG的模式寄存器地址為(0x01C20800+0xD8)
在裸機(jī)開發(fā)中,可以直接讀寫*(0x01C20800+0xD8)來操作GPIO寄存器;但在linux中,內(nèi)核不能直接訪問物理地址,必須映射為虛擬地址后才可以訪問,映射和取消映射的函數(shù)為ioremap()和iounmap()。
writel() 向內(nèi)存映射的物理地址上寫數(shù)據(jù),wirtel() 寫入 32 位數(shù)據(jù) (4字節(jié));readl同理,讀取32位數(shù)據(jù) (4字節(jié));類似的操作函數(shù)有writeb/writew/writel,分別對應(yīng)8位、16位、32位操作
位操作比較清晰,清除指定的位,或在指定位寫入數(shù)據(jù);v3s的模式寄存器每個(gè)腳占用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) //默認(rèn)輸入為0x30即字符'0'
{
val=readl(GPIOG_DATA);
val &= ~(1 << PIN_N); //低電平點(diǎ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;
}
用戶空間內(nèi)存不能直接訪問內(nèi)核空間的內(nèi)存,需要借助函數(shù) copy_from_user 將用戶空間的數(shù)據(jù)復(fù)制到內(nèi)核空間;LED控制較為簡單,只需一個(gè)字符即可控制LED,這里復(fù)制1個(gè)字符即可,應(yīng)用層也只需一個(gè)字符控制LED
測試:
其他參考之前的字符設(shè)備模板寫即可,修改Makefile并編譯,將生成的ledchar.ko拷貝到開發(fā)板,注意前述的設(shè)備樹修改
使用echo測試
使用應(yīng)用層APP測試
這里也寫了一個(gè)簡單的測試app,源碼附在文后,調(diào)用本條命令進(jìn)行編譯,編譯出目標(biāo)文件拷貝到開發(fā)板
/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -o ledcharApp ledcharApp.c
APP運(yùn)行測試:
一點(diǎn)心得:
本篇是最接近裸機(jī)的開發(fā)方式了,簡單直接、深入底層,不涉及Linux的程序框架與造好的輪子;實(shí)際開發(fā)中幾乎不會使用這種開發(fā)方式,但有助于理解驅(qū)動(dòng)框架和硬件的關(guān)系,可以有一個(gè)直觀感受。
源碼
ledcharApp.c文章來源:http://www.zghlxwxcb.cn/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文章來源地址http://www.zghlxwxcb.cn/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個(gè)引腳,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)
// 每個(gè)腳的輸入輸出數(shù)據(jù),1bit
#define V3S_GPIOG_DATA (V3S_GPIO_BASE + 0xE8)
// 驅(qū)動(dòng)能力,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)
// 中斷狀態(tài),1bit,1發(fā)生中斷,寫1清除
#define V3S_GPIOG_INT_STA (V3S_GPIO_BASE + 0x254)
// 中斷時(shí)鐘及分頻配置
#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); //低電平點(diǎ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) // 定義了設(shè)備號,靜態(tài)設(shè)備號
{
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 // 沒有定義設(shè)備號,動(dòng)態(tài)申請?jiān)O(shè)備號
{
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后的標(biāo)簽沒有return操作,將順序執(zhí)行多個(gè)label直至return,這里反向?qū)?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");
到了這里,關(guān)于荔枝派zero驅(qū)動(dòng)開發(fā)04:GPIO操作(寄存器方式)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!