1 硬件结构
我使用的是风火轮出品的树莓派A20板子,同样也买了一个DVK521扩展的底板,可以看到有8个LED灯,关于GPIO,点亮LED是最好的驱动编写例子。
先看DVK521板子上的LED灯硬件原理:
LED灯原理比较简单,安装该图来看,需要将PE4~PE11置高即可,从这里看来,A20的IO口输出电流能力还是比较强。
2 驱动编写
2.1 GPIO寄存器的相关知识
按照以前S3C2440,S3C6410等板子,找到寄存器很简单,只需要翻遍手册,然后使用内核提供的ioremap将硬件地址映射到内核的虚拟地址上,即可达到通过控制虚拟地址达到控制硬件地址的效果。
但是,在A20上,控制方法已经改成了script_parser_fetch()等等API操作方法,这表示描述方法有所不同,但是具体思路仍然相同。要注意的是,也可以使用以前的控制方法。
在《树莓派3主控芯片手册V1.0(A20datasheet).pdf》(点击链接)文档的P19页上,
A20将通用的GPIO当成了PIO,我截取出PIO的地址范围,如下图所示:
再跳到P240,找到1.19 Port Controller这一章,我们只需要关注的是Port E(PE):12 input/output port。
在这一章里,说明了一些知识点:
1.上面列的GPIO引脚中,除了Port S做位DRAM来用,其他的没有复用的功能的,都可以用作普通IO口。
2.A20上有32个外部PIO中断。
由于是初次使用该款芯片,而GPIO是最基本的功能,所以,还是继续贴出P247上的关于GPIO的配置寄存器内容,方便后面的查找:
2.2 配置寄存器
我在MarsBoard-A20-Linux-SDK-V1.2源码目录下,找到了sun4i-keypad.c文件,在sw_keypad_init()中找到了API函数script_parser_fetch(),根据这个函数,我在gpio-sunxi.c中找到了操作gpio的许多API函数,例如sunxi_gpio_to_irq(),sunxi_gpio_set()等等。
最终,将所有的点,可以挂接到一个结构体上:struct gpio_chip。
到这里,对于GPIO的内容,就到头了,分析再多,那就变成了一篇驱动程序分析的文章了。
在gpio-sunxi.c的sunxi_gpio_probe()函数里,通过注释,知道该函数中有在找script.bin文件。
2.2.1 找到fex2bin工具,sys_config.fex,script.bin的目录
先找到fex2bin工具,sys_config.fex,script.bin在哪个目录下:
wityuan@ubuntu:~/Downloads/MarsBoard-A20-Linux-SDK-V1.2$ ls tools/sunxi-tools/
adb-devprobe.sh fel-copy.c jtag-loop.c nand-part-main.c script_fex.c
bin fel-gpio jtag-loop.lds nand-part-main.o script_fex.h
bin2fex fel-pio.c jtag-loop.S phoenix_info.c script.h
boot_head.lds fel-pio.lds Makefile pio script+lcd.bin
boot_head.S fel-sdboot.c nand-common.h pio.c script_lcd.fex
bootinfo fel-sdboot.lds nand-part README script_uboot.c
bootinfo.c fex2bin nand-part-a10.h script.bin script_uboot.h
common.h fexc nand-part-a10.o script_bin.c sys_config.fex
COPYING fexc.c nand-part-a20.h script_bin.h usb-boot
fel fexc.h nand-part-a20.o script.c
fel.c include nand-part.c script.fex
wityuan@ubuntu:~/Downloads/MarsBoard-A20-Linux-SDK-V1.2$
2.2.2 找到开发板上的script.bin文件位置
在开发板上使用如下命令可以找到script.bin文件:
root@marsboard:~# ls /mnt/
root@marsboard:~# mount /dev/nanda /mnt/
root@marsboard:~# ls /mnt/
boot.axf drv_de.drv font32.sft os_show sprite.axf
boot.ini drv_hdmi.drv linux prvt.axf uEnv.txt
boot_signature.axf font24.sft magic.bin script.bin uImage
root@marsboard:~#
2.2.3 修改sys_config.fex文件
从sys_config.fex文件中,可以看到,里面描述的都是io口的功能。
我把里面最基本的功能描述,摘录如下:
;A20 PAD application
;-------------------------------------------------------------------------------
; 说明:
; 1. 脚本中的字符串区分大小写,用户可以修改"="后面的数值,但是不要修改前面的字符串
; 2. 新增主键和子键的名称必须控制在32个字符以内,不包括32个
; 3. 所以的注释以“;”开始,单独占据一行
; 4. 注释不可和配置项同行,例如:主键和子健后面不能添加任何形式的注释
;
; gpio的描述形式:Port:端口+组内序号<功能分配><内部电阻状态><驱动能力><输出电平状态>
; 例如:port:PA0<0><default><default><default>
;-------------------------------------------------------------------------------
现在在硬件上,有PE4~PE11这些管脚需要设置。
- 用什么参数怎么去设置一个IO口?
要去寻求这个问题的答案,内核肯定会有相应的依据,我在script.h中找到了结构体:
struct sunxi_property_gpio_value。
摘录如下:
struct sunxi_property_gpio_value {
u32 port; /*表明是哪个端口,如Port A,Port B等等*/
u32 port_num; /*表明是哪个端口号,例如PE14的14*/
s32 mul_sel; /*表明的是功能分配,例如复用功能还是普通IO口*/
s32 pull; /*表明的是内部上拉状态*/
s32 drv_level; /*表明的是驱动能力*/
s32 data; /*表明的是输出电平状态*/
};
每一项含义清楚了之后,我们就可以一个个的去设置了,那么,还是要回到手册:《树莓派3主控芯片手册V1.0(A20datasheet).pdf》上的P249。
根据手册上的说明:
mul_sel:
0--输入
1--输出
2~7--复用功能
pull:
0--上拉/下拉禁止
1--上拉使能
2--下拉使能
drv_level:
0--level 0
1--level 1
2--level 2
3--level 3
data:
0--输出电平为0
1--输出电平为1
可以在这个网站上找到详细描述:链接
现在,可以在sys_config.fex文件中添加PE4~PE11的信息:
[led_test_para]
led_test_enable = 1
led1 = port:PE4<1><1><default><default>
led2 = port:PE5<1><1><default><default>
led3 = port:PE6<1><1><default><default>
led4 = port:PE7<1><1><default><default>
led5 = port:PE8<1><1><default><default>
led6 = port:PE9<1><1><default><default>
led7 = port:PE10<1><1><default><default>
led8 = port:PE11<1><1><default><default>
修改完成后,需要将该文件更新为script.bin,操作方法如下:
先看怎么用fex2bin这个工具:
wityuan@ubuntu:~/Downloads/MarsBoard-A20-Linux-SDK-V1.2/tools/sunxi-tools$ ./fex2bin -v
./fex2bin: from fex:<stdin> to bin:<stdout>
之后,我们先备份源文件,然后再生成自己的script.bin文件。
wityuan@ubuntu:~/Downloads/MarsBoard-A20-Linux-SDK-V1.2/tools/sunxi-tools$ cp script.bin script_bak.bin
wityuan@ubuntu:~/Downloads/MarsBoard-A20-Linux-SDK-V1.2/tools/sunxi-tools$ ./fex2bin sys_config.fex script.bin
最后,将A20开发板上的script.bin替换掉。
2.2.4 获取script.bin的参数
找到\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\leds\leds-sunxi.c文件,这是一个很好的控制led灯的例子程序,可以参考它来做下面的工作。
我直接粘贴出来整个简单的gpio.c程序文件:
#include "linux/init.h"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <plat/sys_config.h>
script_gpio_set_t info;
unsigned gpio_handler;
static int __init led_init(void)
{
int err = 0;
int led_test_enabled = 0;
int ret = 0;
err = script_parser_fetch("led_test_para", "led_test_enable", &led_test_enabled,
sizeof(led_test_enabled)/sizeof(int));
if(!err){
printk("---script.bin led get ok,value:%d----\n",led_test_enabled);
}
else
{
printk("---script.bin led get false----\n");
return -1;
}
err = script_parser_fetch("led_test_para", "led1",
(int *)&info,
sizeof(script_gpio_set_t));
if (err) {
printk("----script.bin get io error----\r\n");
return -1;
}
/* reserve gpio for led */
gpio_handler = gpio_request_ex("led_test_para", "led1");
if (!gpio_handler) {
printk("----script.bin can't requst handler----\r\n");
return -1;
}
/*set pin as output*/
ret = gpio_set_one_pin_io_status(gpio_handler, 1,
"led1");
if (!ret)
/*set value level 1*/
ret = gpio_write_one_pin_value(gpio_handler,
1, "led1");
return 0;
}
static void __exit led_exit(void)
{
if (gpio_handler)
gpio_release(gpio_handler, 1);
printk("---dirver exit---\r\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_DESCRIPTION("Driver for empty");
MODULE_AUTHOR("wit_yuan");
MODULE_LICENSE("GPL");
Makefile文件如下:
ifeq ($(KERNELRELEASE),)
KERNEL_DIR=/home/wityuan/Downloads/MarsBoard-A20-Linux-SDK-V1.2/linux-sunxi
PWD=$(shell pwd)
modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc -o gpio gpio.c
modules_install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -rf *.ko *.o .tmp_versions *.mod.c modules.order Module.symvers .*.cmd
else
obj-m:=gpio.o
endif
然后将gpio.ko文件下载到开发板上,再开发板上执行:
root@marsboard:~# insmod gpio.ko
---script.bin led get ok,value:1----
root@marsboard:~# rmmod gpio
---dirver exit---
root@marsboard:~#
可以看到insmod程序的时候,L1灯亮起,而rmmod程序的时候,L1灯灭。
3 进阶
3.1 添加应用层代码
上面的驱动程序,只是在内核驱动程序层面控制硬件。现在我们需要通过应用层程序来控制底层的硬件。
直接上代码吧。
gpio.c代码如下:
#include "linux/init.h"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <plat/sys_config.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
static script_gpio_set_t info;
static unsigned gpio_handler;
static struct class *leds_class;
static struct device *leds_device;
static unsigned int leds_major;
static int led_open(struct inode *inode, struct file *filp);
static ssize_t led_write (struct file *filp, const char __user *buf, size_t len, loff_t *off);
static int led_close(struct inode *inode, struct file *filp);
struct file_operations led_operations = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_close,
};
static int led_open(struct inode *inode, struct file *filp)
{
int err = 0;
int led_test_enabled = 0;
int ret = 0;
err = script_parser_fetch("led_test_para", "led_test_enable", &led_test_enabled,
sizeof(led_test_enabled)/sizeof(int));
if(!err){
printk("---script.bin led get ok,value:%d----\n",led_test_enabled);
}
else
{
printk("---script.bin led get false----\n");
return -1;
}
err = script_parser_fetch("led_test_para", "led1",
(int *)&info,
sizeof(script_gpio_set_t));
if (err) {
printk("----script.bin get io error----\r\n");
return -1;
}
/* reserve gpio for led */
gpio_handler = gpio_request_ex("led_test_para", "led1");
if (!gpio_handler) {
printk("----script.bin can't requst handler----\r\n");
return -1;
}
return 0;
}
static ssize_t led_write (struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
int val;
gpio_set_one_pin_io_status(gpio_handler, 1,
"led1");
copy_from_user(&val,buf,4);
printk("----led val:%d----\r\n",val);
switch(val){
case 0:
gpio_write_one_pin_value(gpio_handler,
0, "led1");
break;
case 1:
gpio_write_one_pin_value(gpio_handler,
1, "led1");
break;
default:
break;
}
return 0;
}
static int led_close(struct inode *inode, struct file *filp)
{
printk("----led close----\r\n");
return 0;
}
static int __init led_init(void)
{
leds_major = register_chrdev(0, "led_chrdev", &led_operations);
leds_class = class_create(THIS_MODULE, "leds_class");
if(!leds_class){
unregister_chrdev(leds_major, "led_chrdev");
printk("----leds_chrdev error----\r\n");
return -1;
}
leds_device = device_create(leds_class, NULL, MKDEV(leds_major,0),
NULL, "leds_device");
if(!leds_device){
class_destroy(leds_class);
unregister_chrdev(leds_major, "led_chrdev");
printk("----leds_device error----\r\n");
return -1;
}
printk("----leds init ok----\r\n");
return 0;
}
static void __exit led_exit(void)
{
if (gpio_handler)
gpio_release(gpio_handler, 1);
device_destroy(leds_class, MKDEV(leds_major, 0));
class_destroy(leds_class);
unregister_chrdev(leds_major, "led_chrdev");
printk("---driver exit---\r\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_DESCRIPTION("Driver for empty");
MODULE_AUTHOR("wit_yuan");
MODULE_LICENSE("GPL");
led_test.c代码如下:
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd;
int val;
fd = open("/dev/leds_device",O_RDWR);
if(fd < 0){
printf("---open file error----\r\n");
return -1;
}
if(argc != 2){
printf("usage %s on|off\r\n",argv[0]);
return -1;
}
if(strncmp(argv[1],"on",2) == 0){
val = 1;
write(fd,&val,1);
}
else
{
val = 0;
write(fd,&val,1);
}
return 0;
}
Makefile代码如下:
ifeq ($(KERNELRELEASE),)
KERNEL_DIR=/home/wityuan/Downloads/MarsBoard-A20-Linux-SDK-V1.2/linux-sunxi
PWD=$(shell pwd)
modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc -o gpio gpio.c
modules_install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -rf *.ko *.o .tmp_versions *.mod.c modules.order Module.symvers .*.cmd
else
obj-m:=gpio.o
endif
测试:
root@marsboard:~# ./led_test on
---script.bin led get ok,value:1----
----led val:1----
---led close----
root@marsboard:~# ./led_test off
---script.bin led get ok,value:1----
----led val:0----
----led close----
3.2 多LED灯控制
夜深了,睡觉,待续吧。。。
3.3 使用cdev注册设备
3.4 另外一种控制方式:
sys_config.fex下的GPIO内容信息如下:
如果需要控制gpio_pin_2,则:
root@marsboard:~# echo 2 > /sys/class/gpio/export
root@marsboard:~#
root@marsboard:~# ls /sys/class/gpio/
export gpio1_ph9 gpio2_pi15 gpiochip1 unexport
root@marsboard:~# echo out > /sys/class/gpio/gpio2_pi15/direction
root@marsboard:~# echo 1 > /sys/class/gpio/gpio2_pi15/value
root@marsboard:~# echo 0 > /sys/class/gpio/gpio2_pi15/value
通过这种方式,就可以控制LED灯的亮灭。
4 树莓派A20的键盘复位驱动程序
该复位程序需要做到:复位引脚拉低至少20ms,再等400ms左右将引脚拉为高电平才可行。
4.1 添加配置信息
在MarsBoard-A20-Linux-SDK-V1.1/tools/sunxi-tools下找到sys_config.fex文件,然后添加内容:
[gpio_whb_para]
gpio_whb_used = 1
gpio_whb_num = 1
gpio_whb_pin_1 = port:PH6<1><default><default><1>
gpio_whb_name_1 = "keypad_reset"
如下图所示:
接着:
$ ./fex2bin sys_config.fex script.bin
在A20上:
$ mount /dev/nanda /mnt
然后将script.bin文件上传到A20的/mnt目录中。
4.2 修改Kconfig和Makefile内容
在linux-sunxi/drivers/misc/目录下,修改Kconfig和Makefile内容。
先修改Kconfig文件内容:
config GPOI_SUNXI_WHB
tristate "GPIO Support for sunxi platform (whb add)"
depends on (ARCH_SUN4I || ARCH_SUN5I || ARCH_SUN7I)
help
This option enables support for gpio connected
lines on the Allwinner SOCs (sun4i/sun5i).
The gpios must be defined in [gpio_whb_para] section of sysconfig.fex
然后修改Makefile文件内容:
obj-$(CONFIG_GPOI_SUNXI_WHB) += gpio_sunxi_whb.o
接着:
$ make menuconfig
可以看到如下图所示内容:
验证是否已经在内核中是选中状态,可在linux-sunxi根目录下查看.config文件,如下图所示:
4.3 编写驱动程序
命名驱动程序名称为:gpio_sunxi_whb.c,最终需要放到目录:linux-sunxi/drivers/misc/下。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <plat/sys_config.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define GPIO_WHB_DEBUG
#ifdef GPIO_WHB_DEBUG
#define gpio_whb_debug(fmt, ...) printk(KERN_INFO "[GPIO_WHB][BUG][%d]"fmt, __LINE__, ##__VA_ARGS__)
#else
#define gpio_whb_debug(fmt, ...)
#endif /* KEYBOARD_I2C7290_DEBUG */
#define gpio_whb_error(fmt, ...) printk(KERN_INFO "[GPIO_WHB][ERR]"fmt"\n", ##__VA_ARGS__)
#define DRV_NAME "gpio_whb"
#define GPIO_WHB_IOCTL_GET 0
#define GPIO_WHB_IOCTL_SET 1
struct sunxi_gpio_whb_data {
unsigned gpio_handler;
script_gpio_set_t info;
char pin_name[16];
char gpio_name[64];
};
struct gpio_whb_ioctl_parameter {
int gpio;
int val;
};
static int gpio_whb_num = 0;
static struct sunxi_gpio_whb_data *psunxi_gpios_whb = NULL;
static struct miscdevice gpio_whb_misc = {0};
/* Get gpio pin value */
static int sunxi_gpio_get_value(unsigned gpio)
{
int ret;
user_gpio_set_t gpio_info[1];
if (gpio >= gpio_whb_num)
return -1;
ret = gpio_get_one_pin_status(psunxi_gpios_whb[gpio].gpio_handler,
gpio_info, psunxi_gpios_whb[gpio].pin_name, 1);
return gpio_info->data;
}
/* Set pin value (output mode) */
static void sunxi_gpio_set_value(unsigned gpio, int value)
{
int ret ;
if (gpio >= gpio_whb_num)
return;
gpio_whb_debug("SET:pin_name:%s, value:%d", psunxi_gpios_whb[gpio].pin_name, value);
ret = gpio_write_one_pin_value(psunxi_gpios_whb[gpio].gpio_handler,
value, psunxi_gpios_whb[gpio].pin_name);
return;
}
/* Set pin direction -> out (mul_sel = 1), pin_data -> value */
static int sunxi_direction_output(unsigned gpio, int value)
{
int ret;
if (gpio >= gpio_whb_num)
return -1;
ret = gpio_set_one_pin_io_status(psunxi_gpios_whb[gpio].gpio_handler, 1,
psunxi_gpios_whb[gpio].pin_name);
if (!ret)
ret = gpio_write_one_pin_value(psunxi_gpios_whb[gpio].gpio_handler,
value, psunxi_gpios_whb[gpio].pin_name);
return ret;
}
/* Check if gpio num requested and valid */
static int sunxi_gpio_is_valid(unsigned gpio)
{
if (gpio >= gpio_whb_num)
return -1;
if (psunxi_gpios_whb[gpio].gpio_handler)
return 0;
return -1;
}
static int gpio_whb_open(struct inode *inode, struct file *file)
{
int i = 0;
//所有IO设置为输出并置1
/*
for (i = 0; i < gpio_whb_num; ++i) {
sunxi_direction_output(i, 1);
}
*/
for (i = 0; i < gpio_whb_num; ++i) {
gpio_whb_debug("Open: Set GPIO %d is 1\n", i);
sunxi_gpio_set_value(i, 1);
}
return 0;
}
static int gpio_whb_close(struct inode *inode, struct file *file)
{
int i = 0;
//所有IO置1(因为键盘复位时是低电平,所以关闭时需要置1)
for (i = 0; i < gpio_whb_num; ++i) {
gpio_whb_debug("Close: Set GPIO %d is 1\n", i);
sunxi_gpio_set_value(i, 1);
}
return 0;
}
/*
static ssize_t gpio_whb_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
return 0;
}
*/
static long gpio_whb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct gpio_whb_ioctl_parameter para = {0};
copy_from_user(¶, (void *)arg, sizeof(struct gpio_whb_ioctl_parameter));
gpio_whb_debug("Cmd: %d, GPIO: %d\n", cmd, para.gpio);
if (sunxi_gpio_is_valid(para.gpio) != 0) {
gpio_whb_error("gpio invalid");
return -1;
}
switch (cmd) {
case GPIO_WHB_IOCTL_GET:
para.val = sunxi_gpio_get_value(para.gpio);
copy_to_user((void *)arg, ¶, sizeof(struct gpio_whb_ioctl_parameter));
break;
case GPIO_WHB_IOCTL_SET:
sunxi_gpio_set_value(para.gpio, para.val);
break;
default:
return -1;
}
return 0;
}
static const struct file_operations gpio_whb_fops = {
.owner = THIS_MODULE,
.open = gpio_whb_open,
.release = gpio_whb_close,
.unlocked_ioctl = gpio_whb_ioctl,
// .read = gpio_whb_read,
// .write = gpio_whb_write,
};
static int __init sunxi_gpio_whb_init(void)
{
int i, err;
int gpio_whb_used = 0;
struct sunxi_gpio_whb_data *gpio_whb_i;
char key[20];
struct miscdevice *misc = NULL;
/* parse script.bin for [leds_para] section
leds_used/leds_num/leds_pin_x/leds_name_x */
gpio_whb_debug("gpio_whb driver init\n");
err = script_parser_fetch("gpio_whb_para", "gpio_whb_used", &gpio_whb_used, sizeof(gpio_whb_used)/sizeof(int));
if (err) {
/* Not error - just info */
gpio_whb_error("sunxi gpio_whb can't find script data '[gpio_whb_para]' 'gpio_whb_used'\n");
return err;
}
if (!gpio_whb_used) {
gpio_whb_error("gpio_whb_used is false. Skip leds initialization\n");
err = 0;
return err;
}
err = script_parser_fetch("gpio_whb_para", "gpio_whb_num", &gpio_whb_num, sizeof(gpio_whb_num)/sizeof(int));
if (err) {
gpio_whb_error("script_parser_fetch '[gpio_whb_para]' 'gpio_whb_num' error\n");
return err;
}
if (!gpio_whb_num) {
gpio_whb_error("gpio_whb_num is none. Skip leds initialization\n");
err = 0;
return err;
}
gpio_whb_debug("gpio_whb_num = %d", gpio_whb_num);
/* allocate memory for leds gpio data and platform device data */
psunxi_gpios_whb = kzalloc(sizeof(struct sunxi_gpio_whb_data) * gpio_whb_num, GFP_KERNEL);
if (!psunxi_gpios_whb) {
gpio_whb_error("%s kzalloc failed\n", __func__);
err = -ENOMEM;
goto exit;
}
gpio_whb_i = psunxi_gpios_whb;
/* parse leds gpio/name script data */
for (i = 0; i < gpio_whb_num; i++) {
/* make next script entry name */
sprintf(gpio_whb_i->pin_name, "gpio_whb_pin_%d", i+1);
/* fetch next led name */
sprintf(key, "gpio_whb_name_%d", i + 1);
err = script_parser_fetch("gpio_whb_para", key,
(int *)gpio_whb_i->gpio_name,
sizeof(gpio_whb_i->gpio_name)/sizeof(int));
if (err) {
gpio_whb_error("script_parser_fetch '[gpio_whb_para]' '%s' error\n", key);
goto exit;
}
/* fetch next led gpio information */
sprintf(key, "gpio_whb_pin_%d", i + 1);
err = script_parser_fetch("gpio_whb_para", key,
(int *)&gpio_whb_i->info,
sizeof(script_gpio_set_t));
if (err) {
gpio_whb_error("script_parser_fetch '[gpio_whb_para]' '%s' error\n", key);
break;
}
gpio_whb_debug("gpio_name:%s, port:%d,port_num:%d\n", gpio_whb_i->info.gpio_name, gpio_whb_i->info.port, gpio_whb_i->info.port_num);
/* reserve gpio for led */
gpio_whb_i->gpio_handler = gpio_request_ex("gpio_whb_para", key);
if (!gpio_whb_i->gpio_handler) {
gpio_whb_error("can't request '[gpio_whb_para]' '%s', already used ?", key);
break;
}
gpio_whb_i++;
}
misc = &gpio_whb_misc;
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = DRV_NAME;
misc->fops = &gpio_whb_fops;
err = misc_register(misc);
if (err) {
gpio_whb_error("Unable to register a misc device\n");
goto exit;
}
gpio_whb_debug("Register a misc device Ok\n");
return 0;
exit:
if (err != -ENOMEM) {
for (i = 0; i < gpio_whb_num; i++) {
if (psunxi_gpios_whb[i].gpio_handler)
gpio_release(psunxi_gpios_whb[i].gpio_handler, 1);
}
kfree(psunxi_gpios_whb);
return err;
}
return err;
}
static void __exit sunxi_gpio_whb_exit(void)
{
int i = 0;
struct miscdevice *misc = NULL;
misc = &gpio_whb_misc;
misc_deregister(misc);
for (i = 0; i < gpio_whb_num; i++) {
if (psunxi_gpios_whb[i].gpio_handler)
gpio_release(psunxi_gpios_whb[i].gpio_handler, 1);
}
kfree(psunxi_gpios_whb);
return;
}
module_init(sunxi_gpio_whb_init);
module_exit(sunxi_gpio_whb_exit);
MODULE_ALIAS("platform:gpio_whb-sunxi");
MODULE_DESCRIPTION("sunxi gpio(whb) driver");
MODULE_AUTHOR("wang hb <xxx@xxx.com>");
MODULE_LICENSE("GPL");
4.4 验证驱动加载是否成功
在树莓派A20下,使用如下命令:
root@marsboard:~# ll /dev/gpio_whb
crw------- 1 root root 10, 59 Jan 1 00:46 /dev/gpio_whb
root@marsboard:~#
这说明驱动确实加载进去了。
5 SPI使用GPIO切换选择
将代码命名为spi-gpio.c,如果在编入内核,可以存放在如上所示,也就是linux-sunxi/drivers/misc/spi-gpio.c,如下所示:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <plat/sys_config.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define GPIO_DEBUG
#ifdef GPIO_DEBUG
#define gpio_debug(fmt, ...) printk(KERN_INFO "[%s][BUG][%d]"fmt, __FILE__,__LINE__, ##__VA_ARGS__)
#else
#define gpio_debug(fmt, ...)
#endif
#define gpio_error(fmt, ...) printk(KERN_INFO "[%s][ERR]"fmt"\n", __FILE__,##__VA_ARGS__)
#define DRV_NAME "A20_GPIO"
#define GPIO_IOCTL_GET 0
#define GPIO_IOCTL_SET 1
struct sunxi_gpio_data {
unsigned gpio_handler;
script_gpio_set_t info;
char pin_name[16];
char gpio_name[64];
};
struct gpio_ioctl_parameter {
int gpio;
int val;
};
static int gpio_num = 0;
static struct sunxi_gpio_data *psunxi_gpio = NULL;
static struct miscdevice gpio_misc = {0};
/* Set pin value (output mode) */
static void sunxi_gpio_set_value(unsigned gpio, int value)
{
int ret ;
if (gpio >= gpio_num)
return;
gpio_debug("SET:pin_name:%s, value:%d", psunxi_gpio[gpio].pin_name, value);
ret = gpio_write_one_pin_value(psunxi_gpio[gpio].gpio_handler,
value, psunxi_gpio[gpio].pin_name);
return;
}
/* Set pin direction -> out (mul_sel = 1), pin_data -> value */
static int sunxi_direction_output(unsigned gpio, int value)
{
int ret;
if (gpio >= gpio_num)
return -1;
ret = gpio_set_one_pin_io_status(psunxi_gpio[gpio].gpio_handler, 1,
psunxi_gpio[gpio].pin_name);
if (!ret)
ret = gpio_write_one_pin_value(psunxi_gpio[gpio].gpio_handler,
value, psunxi_gpio[gpio].pin_name);
return ret;
}
static int gpio_open(struct inode *inode, struct file *file)
{
int i = 0;
for (i = 0; i < gpio_num; ++i) {
gpio_debug("Open: Set GPIO %d is 1\n", i);
sunxi_gpio_set_value(i, 1);
}
return 0;
}
static ssize_t gpio_write (struct file *filep, const char __user *buf, size_t len, loff_t *off)
{
unsigned char value;
int ret;
if(len != 1){
printk("---len error-------\r\n");
return -1;
}
ret = copy_from_user(&value,buf,len);
switch(value){
case 0:
sunxi_direction_output(0,0);
sunxi_direction_output(1,0);
sunxi_direction_output(2,0);
break;
case 1:
sunxi_direction_output(0,1);
sunxi_direction_output(1,0);
sunxi_direction_output(2,0);
break;
case 2:
sunxi_direction_output(0,0);
sunxi_direction_output(1,1);
sunxi_direction_output(2,0);
break;
case 3:
sunxi_direction_output(0,1);
sunxi_direction_output(1,1);
sunxi_direction_output(2,0);
break;
case 4:
sunxi_direction_output(0,0);
sunxi_direction_output(1,0);
sunxi_direction_output(2,1);
break;
case 5:
sunxi_direction_output(0,1);
sunxi_direction_output(1,0);
sunxi_direction_output(2,1);
break;
case 6:
sunxi_direction_output(0,0);
sunxi_direction_output(1,1);
sunxi_direction_output(2,1);
break;
case 7:
sunxi_direction_output(0,1);
sunxi_direction_output(1,1);
sunxi_direction_output(2,1);
break;
default:
printk("%s,value error\r\n",__FILE__);
break;
}
return len;
}
static int gpio_close(struct inode *inode, struct file *file)
{
int i = 0;
for (i = 0; i < gpio_num; ++i) {
gpio_debug("Close: Set GPIO %d is 1\n", i);
sunxi_gpio_set_value(i, 1);
}
return 0;
}
static const struct file_operations A20_gpio_fops = {
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_close,
.write = gpio_write,
};
static int __init sunxi_gpio_init(void)
{
int i, err;
int gpio_used = 0;
struct sunxi_gpio_data *gpio_i;
struct miscdevice *misc = NULL;
gpio_debug("gpio driver init\n");
err = script_parser_fetch("A20_SPI_GPIO_para", "gpio_used", &gpio_used, sizeof(gpio_used)/sizeof(int));
if (err) {
/* Not error - just info */
gpio_error("sunxi gpio can't find script data '[gpio_para]' 'gpio_used'\n");
return err;
}
if (!gpio_used) {
gpio_error("gpio_used is false. Skip leds initialization\n");
err = 0;
return err;
}
err = script_parser_fetch("A20_SPI_GPIO_para", "gpio_num", &gpio_num, sizeof(gpio_num)/sizeof(int));
if (err) {
gpio_error("script_parser_fetch '[gpio_whb_para]' 'gpio_whb_num' error\n");
return err;
}
if (3!=gpio_num) {
gpio_error("gpio_num is not 3. Skip gpio initialization\n");
err = 0;
return err;
}
gpio_debug("gpio_num = %d", gpio_num);
/* allocate memory for leds gpio data and platform device data */
psunxi_gpio = kzalloc(sizeof(struct sunxi_gpio_data) * gpio_num, GFP_KERNEL);
if (!psunxi_gpio) {
gpio_error("%s kzalloc failed\n", __func__);
err = -ENOMEM;
goto exit;
}
gpio_i = psunxi_gpio;
/* parse leds gpio/name script data */
for (i = 0; i < gpio_num; i++) {
/* make next script entry name */
sprintf(gpio_i->pin_name, "A20_GPIO%d", i+1);
err = script_parser_fetch("A20_SPI_GPIO_para", gpio_i->pin_name,
(int *)gpio_i->gpio_name,
sizeof(gpio_i->gpio_name)/sizeof(int));
if (err) {
gpio_error("script_parser_fetch '[A20_SPI_GPIO_para]' '%s' error\n", gpio_i->pin_name);
goto exit;
}
gpio_debug("gpio_name:%s, port:%d,port_num:%d\n", gpio_i->info.gpio_name, gpio_i->info.port, gpio_i->info.port_num);
/* reserve gpio for led */
gpio_i->gpio_handler = gpio_request_ex("A20_SPI_GPIO_para", gpio_i->pin_name);
if (!gpio_i->gpio_handler) {
gpio_error("can't request '[gpio_whb_para]' '%s', already used", gpio_i->pin_name);
break;
}
gpio_i++;
}
for (i = 0; i < gpio_num; ++i) {
sunxi_gpio_set_value(i, 1);
}
misc = &gpio_misc;
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = DRV_NAME;
misc->fops = &A20_gpio_fops;
err = misc_register(misc);
if (err) {
gpio_error("Unable to register a misc device\n");
goto exit;
}
gpio_debug("Register a misc device Ok\n");
return 0;
exit:
if (err != -ENOMEM) {
for (i = 0; i < gpio_num; i++) {
if (psunxi_gpio[i].gpio_handler)
gpio_release(psunxi_gpio[i].gpio_handler, 1);
}
kfree(psunxi_gpio);
return err;
}
return err;
}
static void __exit sunxi_gpio_exit(void)
{
int i = 0;
struct miscdevice *misc = NULL;
misc = &gpio_misc;
misc_deregister(misc);
for (i = 0; i < gpio_num; i++) {
if (psunxi_gpio[i].gpio_handler)
gpio_release(psunxi_gpio[i].gpio_handler, 1);
}
kfree(psunxi_gpio);
return;
}
module_init(sunxi_gpio_init);
module_exit(sunxi_gpio_exit);
MODULE_ALIAS("platform:spi_fpga_dsp_gpio_wityuan-sunxi");
MODULE_DESCRIPTION("sunxi spi_fpga_gpio(wityuan) driver");
MODULE_AUTHOR("wityuan <xxx@xxx.com>");
MODULE_LICENSE("GPL");
Makefile文件为:
ifeq ($(KERNELRELEASE),)
KERNEL_DIR=/home/wityuan/Downloads/MarsBoard-A20-Linux-SDK-V1.2/linux-sunxi
PWD=$(shell pwd)
modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc -o spi-gpio spi-gpio.c
modules_install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
rm -rf *.ko *.o .tmp_versions *.mod.c modules.order Module.symvers .*.cmd
else
obj-m:=spi-gpio.o
endif
测试程序命名为spi-gpio_test.c,内容如下:
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int fd;
int val;
unsigned char write_val;
fd = open("/dev/A20_GPIO",O_RDWR);
if(fd < 0){
printf("---open file error----\r\n");
return -1;
}
if(argc != 2){
printf("usage %s (0|1|2|3|4|5|6|7|)\r\n",argv[0]);
return -1;
}
write_val = (unsigned char)atoi(argv[1]);
if((write_val>7) | (write_val<0)){
printf("---value is valid---\r\n");
return -1;
}
write(fd,&write_val,1);
while(1);
return 0;
}
即可通过如下方式进行测试:
$ ./spi-gpio_test 0
$ ./spi-gpio_test 1
$ ./spi-gpio_test 2
$ ./spi-gpio_test 3
$ ./spi-gpio_test 4
$ ./spi-gpio_test 5
$ ./spi-gpio_test 6
$ ./spi-gpio_test 7
最终,我们往内核添加时,要将spi-gpio.c放到linux-sunxi/drivers/misc下。
并且在Kconfig和Makefile中添加内容:
Kconfig:
config SPI_GPIO_YUAN
tristate "(wit_yuan add)SPI GPIO SELECT Support for sunxi platform"
depends on (ARCH_SUN4I || ARCH_SUN5I || ARCH_SUN7I)
help
This option enables support for gpio connected
lines on the Allwinner SOCs (sun4i/sun5i).
The gpios must be defined in [A20_SPI_GPIO_para] section of sysconfig.fex
Makefile:
obj-$(CONFIG_SPI_GPIO_YUAN) += spi-gpio.o
sys_config.fex配置为:
[A20_SPI_GPIO_para]
gpio_used = 1
gpio_num = 3
A20_GPIO1 = port:PH0<1><1><default><default>
A20_GPIO2 = port:PH1<1><1><default><default>
A20_GPIO3 = port:PH1<1><1><default><default>
最终,查找是否有设备:
$ ls /dev/A20_GPIO
6 AD,RCA,MIC等使用IO脉冲切换
[AD_Switch_para]
AD_Switch_used = 1
gpio_num = 2
sw1 = port:PH0<1><1><default><default>
sw2 = port:PH1<1><1><default><default>
接着,在linux-sunxi/drivers/misc/目录下,修改Kconfig和Makefile内容。
先修改Kconfig文件内容:
config A20_sw
tristate "A20_sw for sunxi platform (wityuan add)"
depends on (ARCH_SUN4I || ARCH_SUN5I || ARCH_SUN7I)
help
This option enables support for gpio connected
lines on the Allwinner SOCs (sun4i/sun5i).
The gpios must be defined in section of sysconfig.fex
然后修改Makefile文件内容:
obj-$(CONFIG_A20_sw) += A20_sw.o
接着:
$ make menuconfig
找到该选项并且选中:
最后,我们需要将驱动程序,名称为:gpio_sunxi_whb.c,放到目录:linux-sunxi/drivers/misc/下。
源码如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <plat/sys_config.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define GPIO_DEBUG
#ifdef GPIO_DEBUG
#define gpio_debug(fmt, ...) printk(KERN_INFO "[%s][BUG][%d]"fmt, __FILE__,__LINE__, ##__VA_ARGS__)
#else
#define gpio_debug(fmt, ...)
#endif
#define gpio_error(fmt, ...) printk(KERN_INFO "[%s][ERR]"fmt"\n", __FILE__,##__VA_ARGS__)
#define DRV_NAME "A20_SW"
struct sunxi_gpio_data {
unsigned gpio_handler;
script_gpio_set_t info;
char pin_name[16];
char gpio_name[64];
};
typedef struct{
int id;
int value;
}SW_T;
static int gpio_num = 0;
static struct sunxi_gpio_data *psunxi_gpio = NULL;
static struct miscdevice gpio_misc = {0};
/* Set pin value (output mode) */
static void sunxi_gpio_set_value(unsigned gpio, int value)
{
int ret ;
if (gpio >= gpio_num)
return;
gpio_debug("SET:pin_name:%s, value:%d", psunxi_gpio[gpio].pin_name, value);
ret = gpio_write_one_pin_value(psunxi_gpio[gpio].gpio_handler,
value, psunxi_gpio[gpio].pin_name);
return;
}
static int gpio_open(struct inode *inode, struct file *file)
{
int i = 0;
for (i = 0; i < gpio_num; ++i) {
gpio_debug("Open: Set GPIO %d is 1\n", i);
sunxi_gpio_set_value(i, 0);
}
return 0;
}
static ssize_t gpio_write (struct file *filep, const char __user *buf, size_t len, loff_t *off)
{
SW_T sw_t;
int ret;
if(len != (sizeof(sw_t))){
printk("---len error-------\r\n");
return -1;
}
ret = copy_from_user(&sw_t,buf,len);
printk("---sw_t.id:%d, sw_t.value:%d-------\r\n",sw_t.id,sw_t.value);
if(sw_t.id == 0){
sunxi_gpio_set_value(0, sw_t.value);
sunxi_gpio_set_value(1, 0);
}
else {
sunxi_gpio_set_value(0, 0);
sunxi_gpio_set_value(1, sw_t.value);
}
return len;
}
static int gpio_close(struct inode *inode, struct file *file)
{
#if 0
int i = 0;
for (i = 0; i < gpio_num; ++i) {
gpio_debug("Close: Set GPIO %d is 1\n", i);
sunxi_gpio_set_value(i, 0);
}
#endif
return 0;
}
static const struct file_operations A20_gpio_fops = {
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_close,
.write = gpio_write,
};
static int __init sunxi_gpio_init(void)
{
int i, err;
int gpio_used = 0;
struct sunxi_gpio_data *gpio_i;
struct miscdevice *misc = NULL;
gpio_debug("gpio driver init\n");
err = script_parser_fetch("AD_Switch_para", "AD_Switch_used", &gpio_used, sizeof(gpio_used)/sizeof(int));
if (err) {
/* Not error - just info */
gpio_error("sunxi gpio can't find script data '[gpio_para]' 'gpio_used'\n");
return err;
}
if (!gpio_used) {
gpio_error("gpio_used is false. Skip leds initialization\n");
err = 0;
return err;
}
err = script_parser_fetch("AD_Switch_para", "gpio_num", &gpio_num, sizeof(gpio_num)/sizeof(int));
if (err) {
gpio_error("script_parser_fetch '[AD_Switch_para]' error\n");
return err;
}
if (2!=gpio_num) {
gpio_error("gpio_num is not 2. Skip gpio initialization\n");
err = 0;
return err;
}
gpio_debug("gpio_num = %d", gpio_num);
/* allocate memory for leds gpio data and platform device data */
psunxi_gpio = kzalloc(sizeof(struct sunxi_gpio_data) * gpio_num, GFP_KERNEL);
if (!psunxi_gpio) {
gpio_error("%s kzalloc failed\n", __func__);
err = -ENOMEM;
goto exit;
}
gpio_i = psunxi_gpio;
/* parse leds gpio/name script data */
for (i = 0; i < gpio_num; i++) {
/* make next script entry name */
sprintf(gpio_i->pin_name, "sw%d", i+1);
err = script_parser_fetch("AD_Switch_para", gpio_i->pin_name,
(int *)gpio_i->gpio_name,
sizeof(gpio_i->gpio_name)/sizeof(int));
if (err) {
gpio_error("script_parser_fetch '[AD_Switch_para]' '%s' error\n", gpio_i->pin_name);
goto exit;
}
gpio_debug("gpio_name:%s, port:%d,port_num:%d\n", gpio_i->info.gpio_name, gpio_i->info.port, gpio_i->info.port_num);
/* reserve gpio for led */
gpio_i->gpio_handler = gpio_request_ex("AD_Switch_para", gpio_i->pin_name);
if (!gpio_i->gpio_handler) {
gpio_error("can't request '[AD_Switch_para]' '%s', already used", gpio_i->pin_name);
break;
}
gpio_i++;
}
for (i = 0; i < gpio_num; ++i) {
sunxi_gpio_set_value(i, 0);
}
misc = &gpio_misc;
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = DRV_NAME;
misc->fops = &A20_gpio_fops;
err = misc_register(misc);
if (err) {
gpio_error("Unable to register a misc device\n");
goto exit;
}
gpio_debug("Register a misc device Ok\n");
return 0;
exit:
if (err != -ENOMEM) {
for (i = 0; i < gpio_num; i++) {
if (psunxi_gpio[i].gpio_handler)
gpio_release(psunxi_gpio[i].gpio_handler, 1);
}
kfree(psunxi_gpio);
return err;
}
return err;
}
static void __exit sunxi_gpio_exit(void)
{
int i = 0;
struct miscdevice *misc = NULL;
misc = &gpio_misc;
misc_deregister(misc);
for (i = 0; i < gpio_num; i++) {
if (psunxi_gpio[i].gpio_handler)
gpio_release(psunxi_gpio[i].gpio_handler, 1);
}
kfree(psunxi_gpio);
return;
}
module_init(sunxi_gpio_init);
module_exit(sunxi_gpio_exit);
MODULE_ALIAS("platform:sw0,sw1 select");
MODULE_DESCRIPTION("sw0,sw1(wityuan) driver");
MODULE_AUTHOR("wityuan <xxx@xxx.com>");
MODULE_LICENSE("GPL");
测试程序sw_test.c为:
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
typedef struct{
int id;
int value;
}SW_T;
int main(int argc,char *argv[])
{
int fd;
int val;
SW_T sw_t;
fd = open("/dev/A20_SW",O_RDWR);
if(fd < 0){
printf("---open file error----\r\n");
return -1;
}
if(argc != 2){
printf("usage %s on|off\r\n",argv[0]);
return -1;
}
val = atoi(argv[1]);
sw_t.id = val;
sw_t.value = 1;
write(fd,&sw_t,sizeof(SW_T));
//sleep(1);
usleep(200000);
sw_t.id = val;
sw_t.value = 0;
write(fd,&sw_t,sizeof(SW_T));
usleep(200000);
while(1);
return 0;
}
7 参考文件
1.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\leds\leds-sunxi.c
2.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\gpio\gpio-sunxi.c