GPIO驅動除錯
阿新 • • 發佈:2019-01-02
最近在除錯安霸SDK,這個SDK中並沒有提供直接操作GPIO口的驅動設定,在使用GPIO 的時候一般是通過echo命令的匯出gpio口,設定其屬性,具體方法我在另一文章中寫過,並且網上也有很多的例子可以檢視就不多寫了。今天主要是對於晶片的GPIO的datasheet和驅動程式進行分析,在Linux系統中核心已經提供了很多介面去操作GPIO
核心中gpio的使用
1 測試gpio埠是否合法 int gpio_is_valid(int number);
2 申請某個gpio埠當然在申請之前需要顯示的配置該gpio埠的pinmux
int gpio_request(unsigned gpio, const char *label)
3 標記gpio的使用方向包括輸入還是輸出
/*成功返回零失敗返回負的錯誤值*/
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
4 獲得gpio引腳的值和設定gpio引腳的值(對於輸出)
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);
5 gpio 當作中斷口使用
int gpio_to_irq(unsigned gpio);
返回的值即中斷編號可以傳給request_irq()和free_irq()
核心通過呼叫該函式將gpio埠轉換為中斷,在使用者空間也有類似方法
6 匯出gpio埠到使用者空間
int gpio_export(unsigned gpio, bool direction_may_change);
核心可以對已經被gpio_request()申請的gpio埠的匯出進行明確的管理,
引數direction_may_change 表示使用者程式是否允許修改gpio的方向,假如可以
則引數direction_may_change為真
/* 撤銷GPIO的匯出 */
void gpio_unexport();
核心實現的GPIO介面,在使用者空間得到了很好的體現,即使用echo的命令操作。具體的程式碼,在kernel/linux-3.10/drivers/gpio中實現,這是整個gpio子系統。話說這次寫驅動並沒有使用過上述介面,基本都是自己根據datasheet實現的介面。GPIO的驅動是個非常簡單的驅動,只要是學會如何去閱讀datasheet,然後去操作GPIO的暫存器就可以。
接下看看datasheet的,如下截圖
這是GPIO的暫存器的說明,限於篇幅問題,就只截上述圖,安霸晶片的GPIO的地址是不連續的,總共分為四組,定義如下
#define GPIO0_StartAddr 0xE8009000
#define GPIO1_StartAddr 0xE800A000
#define GPIO2_StartAddr 0xE800E000
#define GPIO3_StartAddr 0xE8010000
總共114個引腳,分為四組,每組32個引腳,每個引腳對應12個暫存器,換言之,每個暫存器對應32個引腳。確定基地址,每個bit位對應一個引腳。
關於暫存器的介紹:
如上術分別對應每個暫存器。只要把這些引腳和暫存器等之間的對應關係搞清楚,gpio的驅動實現就簡單多了。
好了,datasheet很多東西自己看的理解的可能有些清楚,但在敘述過程中可能有些繁瑣或是限於本人語文老師的原因,寫的有點亂,多多理解。
接下來直接附上GPIO驅動程式碼:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include<linux/device.h>
#define GPIO0_StartAddr 0xE8009000
#define GPIO1_StartAddr 0xE800A000
#define GPIO2_StartAddr 0xE800E000
#define GPIO3_StartAddr 0xE8010000
#define GPIO_MAPSIZE 0x2C
#define GPIO_OUT 1
#define GPIO_IN 0
#define GPIO_HIGH 1
#define GPIO_LOW 0
#define REG(Addr) (*(volatile unsigned int*) (Addr))
#define DEVICE_NAME "AM_GPIO" //定義裝置名
//定義操作
#define SET_OUTPUT_LOW 0 //設定io口為輸出低電平模式
#define SET_OUTPUT_HIGH 1 //設定io口為輸出高電平模式
#define SET_INPUT 2 //設定io口為輸入模式
#define GET_VALUE 3 //獲取io口的狀態
#define SET_IRQ_LEVEL_LOW 4 //設定io口為低電平觸發中斷模式
#define SET_IRQ_LEVEL_HIGH 5 //設定io口為高電平觸發中斷模式
#define SET_IRQ_EDGES_FAILL 6 //設定io口為下降沿觸發模式
#define SET_IRQ_EDGES_RISI 7 //設定io口為上升沿觸發模式
//定義裝置結構體
static struct class *cdev_class;
struct cdev cdev;
//定義裝置號引數
dev_t dev = 0;
//open函式
static int gpio_open(struct inode *inode, struct file *filp)
{
return 0;
}
//release函式
int gpio_release(struct inode *inode, struct file *filp)
{
printk("gpio device released\n");
return 0;
}
//ioctl函式
static void s2l_gpio_setpin(void *Regaddr, unsigned int Addr_offset,
unsigned int MaskBit, unsigned int to)
{
unsigned int tmp=0;
tmp = REG((unsigned int)Regaddr +Addr_offset);
tmp &= ~(1<<MaskBit);
tmp |= (!!to<<MaskBit);
REG((unsigned int)Regaddr +Addr_offset) = tmp;
}
unsigned int s2l_gpio_getpin(void *Regaddr, unsigned int Addr_offset, unsigned int MaskBit)
{
unsigned int tmp=0;
tmp=REG((unsigned int)Regaddr +Addr_offset);
tmp &= (1<<MaskBit);
return tmp?1:0;
}
static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long gpio_num)
{
int ret;
unsigned int MaskBit;
unsigned int group_num;
void * GpioBase = NULL;
group_num = gpio_num/32;
MaskBit = gpio_num%32;
switch (group_num)
{
case 0:
GpioBase = ioremap(GPIO0_StartAddr,GPIO_MAPSIZE);
break;
case 1:
GpioBase = ioremap(GPIO1_StartAddr,GPIO_MAPSIZE);
break;
case 2:
GpioBase = ioremap(GPIO2_StartAddr,GPIO_MAPSIZE);
break;
case 3:
GpioBase = ioremap(GPIO3_StartAddr,GPIO_MAPSIZE);
break;
default:
break;
}
switch (cmd)
{
case SET_OUTPUT_LOW://0
{
s2l_gpio_setpin(GpioBase, 0x04, MaskBit , GPIO_OUT); //output
s2l_gpio_setpin(GpioBase, 0x00, MaskBit , GPIO_LOW);//low
break;
}
case SET_OUTPUT_HIGH:
{
s2l_gpio_setpin(GpioBase, 0x04, MaskBit , GPIO_OUT); //output
s2l_gpio_setpin(GpioBase, 0x00, MaskBit , GPIO_HIGH);//high
break;
}
case SET_INPUT:
{
s2l_gpio_setpin(GpioBase, 0x04, MaskBit , GPIO_IN); //output
break;
}
case GET_VALUE:
{
ret = s2l_gpio_getpin(GpioBase,0x00,MaskBit);
return ret;
printk("ret = %d\n",ret);
break;
}
case SET_IRQ_LEVEL_LOW:
{
s2l_gpio_setpin(GpioBase,0x08,MaskBit,GPIO_LOW);//設定中斷為電平觸發模式
s2l_gpio_setpin(GpioBase,0x10,MaskBit,GPIO_LOW);//設定低電平觸發
break;
}
case SET_IRQ_LEVEL_HIGH:
{
s2l_gpio_setpin(GpioBase,0x08,MaskBit,GPIO_LOW);//設定中斷為電平觸發模式
s2l_gpio_setpin(GpioBase,0x10,MaskBit,GPIO_HIGH);//設定高電平觸發
break;
}
case SET_IRQ_EDGES_FAILL:
{
s2l_gpio_setpin(GpioBase,0x08,MaskBit,GPIO_HIGH);//設定中斷為邊沿觸發模式
s2l_gpio_setpin(GpioBase,0x10,MaskBit,GPIO_LOW);//設定下降沿觸發
break;
}
case SET_IRQ_EDGES_RISI:
{
s2l_gpio_setpin(GpioBase,0x08,MaskBit,GPIO_HIGH);//設定中斷為邊沿觸發模式
s2l_gpio_setpin(GpioBase,0x10,MaskBit,GPIO_LOW);//設定上升沿觸發
break;
}
default:
break;
}
return 0;
}
//file_operations
static struct file_operations gpio_fops =
{
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_release,
.unlocked_ioctl = gpio_ioctl,
};
//初始化並註冊cdev
static int gpio_init(void)
{
int result;
int err;
result = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
if (result)
{
printk("globalvar register failure\n");
unregister_chrdev_region(dev,1);
return result;
}
else
{
printk(" register success\n");
}
cdev_init(&cdev, &gpio_fops);
err = cdev_add(&cdev, dev, 1);
if (err < 0)
{
printk("add register error\n");
unregister_chrdev_region(dev, 1);
return err;
}
cdev_class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(cdev_class))
{
printk("ERR:cannot create a cdev_class\n");
unregister_chrdev_region(dev, 1);
return -1;
}
device_create(cdev_class,NULL, dev, 0, DEVICE_NAME);
return result;
}
//exit函式
static void gpio_exit(void)
{
device_destroy(cdev_class, dev);
class_destroy(cdev_class);
unregister_chrdev_region(dev,1);
printk("exit success\n");
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_AUTHOR("wangmutian");
MODULE_DESCRIPTION("GPIO Driver"); // 一些描述資訊
MODULE_LICENSE("GPL");
所有的程式碼不到三百行,最主要的就是實現了一個字元裝置驅動的結構體
static struct file_operations gpio_fops =
{
.owner = THIS_MODULE,
.open = gpio_open,
.release = gpio_release,
.unlocked_ioctl = gpio_ioctl,
};
特別說明的一點就是 在Linux核心2.6版本之後的,ioctl全都變成了unlocked_ioctl ,我現在使用的系統核心是3.10的,原先在上家公司做pos機,主要是使用2.6核心,一直沒在意過這個事,這次這個問題導致我在編譯過程,始終出現錯誤,然後才發現是這個關鍵字導致的問題,希望這個對大家有所幫助。
還有一個可以學習的地方就是這兩個函式,用來操作GPIO的暫存器
static void s2l_gpio_setpin(void *Regaddr, unsigned int Addr_offset,
unsigned int MaskBit, unsigned int to)
{
unsigned int tmp=0;
tmp = REG((unsigned int)Regaddr +Addr_offset);
tmp &= ~(1<<MaskBit);
tmp |= (!!to<<MaskBit);
REG((unsigned int)Regaddr +Addr_offset) = tmp;
}
unsigned int s2l_gpio_getpin(void *Regaddr, unsigned int Addr_offset, unsigned int MaskBit)
{
unsigned int tmp=0;
tmp=REG((unsigned int)Regaddr +Addr_offset);
tmp &= (1<<MaskBit);
return tmp?1:0;
}
其他都是一下Linux驅動提供的標準介面,在其他地方都可以找個解釋。