前言
前面是如何操作GPIO
進行輸出,這里我重新實現了一個gpio
的驅動,可以獲取外部信號的輸入。gpio-demo.c中已經包括檢測一個gpio
的信號,並且包含了中斷和輪詢兩種方式,可以通過設備樹里的mode
屬性進行選擇。
設備樹
本文檢測的輸入引腳是GPIO3_D0
,具體的設備樹如下所示;
gpio-demo {
compatible = "gpio-demo";
input-gpio = <&gpio3 RK_PD0 GPIO_ACTIVE_LOW>;
mode = <1>; // 0:poll 1:interrupt
poll_time = <1000>; //ms
};
compatible
:設備兼容屬性為gpio-demo
,與后面的驅動代碼中的
gpio_demo_of_match[] = { { .compatible = "gpio-demo"}, {}, }
需要相同;input-gpio
:這個屬性值通過of_get_named_gpio
來獲取;mode
:用於判斷當前的工作模式是輪詢還是中斷;poll_time
:輪詢模式下的周期,間隔多少毫秒會讀取一次gpio
的狀態;
對於設備樹的解析,單獨封裝了一個接口;
static int gpio_parse_data(struct gpio_demo_device *di){
int ret;
struct gpio_platform_data *pdata;
struct device *dev = di->dev;
struct device_node *np = di->dev->of_node;
pdata = devm_kzalloc(di->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
return -ENOMEM;
}
di->pdata = pdata;
// set default value for platform data
pdata->mode = DEFAULT_MODE;
pdata->poll_ms = DEFAULT_POLL_TIME * 1000;
dev_info(dev,"parse platform data\n");
ret = of_property_read_u32(np, "mode", &pdata->mode);
if (ret < 0) {
dev_err(dev, "can't get mode property\n");
}
ret = of_property_read_u32(np, "poll_time", &pdata->poll_ms);
if (ret < 0) {
dev_err(dev, "can't get poll_ms property\n");
}
pdata->gpio_index = of_get_named_gpio(np,"input-gpio", 0);
if (pdata->gpio_index < 0) {
dev_err(dev, "can't get input gpio\n");
}
// debug parse device tree data
dev_info(dev, "Success:mode is %d\n", pdata->mode);
dev_info(dev, "Success:gpio index is %d\n", pdata->gpio_index);
return 0;
}
兩個結構體
gpio_platform_data
gpio_platform_data
主要是對設備樹中眾多屬性的封裝;
struct gpio_platform_data {
int mode;
int count;
int gpio_index;
struct mutex mtx;
int poll_ms;
};
gpio_demo_device
gpio_demo_device
是與設備驅動中相關資源的封裝,包括工作隊列等等;
struct gpio_demo_device {
struct platform_device *pdev;
struct device *dev;
struct gpio_platform_data *pdata;
struct workqueue_struct *gpio_monitor_wq;
struct delayed_work gpio_delay_work ;
int gpio_irq;
};
兩種方式
在驅動的probe
函數中,先通過gpio_parse_data
解析設備樹文件,從而獲取mode
屬性的值:
0
:gpio_demo_init_poll
初始化進入輪詢工作模式;1
:gpio_demo_init_interrupt
初始化進入中斷工作模式;
static int gpio_demo_probe(struct platform_device *pdev){
...
ret = gpio_parse_data(priv);
if (ret){
dev_err(dev,"parse data failed\n");
}
...
if (priv->pdata->mode == 0){
gpio_demo_init_poll(priv); //輪詢
} else {
gpio_demo_init_interrupt(priv);//中斷
}
}
輪詢
在輪詢工作模式下,已經通過gpio_demo_init_poll
對工作隊列進行初始化,之后,后啟動運行gpio_demo_work
任務,並在規定的調度時間內,重復檢測運行這個任務。
通過gpio_get_value(gpio_index)
讀取GPIO3_D0
上的電平狀態,如果需要對邊沿信號進行處理還需要做改動,本文只能對電平信號進行處理。
static void gpio_demo_work(struct work_struct *work) {
struct gpio_demo_device *di = container_of(work,
struct gpio_demo_device,
gpio_delay_work.work);
struct gpio_platform_data *padta = di->pdata;
int gpio_index,value;
//獲取gpio索引號
gpio_index = padta->gpio_index;
if (!gpio_is_valid(gpio_index) ) {
dev_err(di->dev, "gpio is not valid\n");
goto end;
}
if ( (value = gpio_get_value(gpio_index) ) == 0) {
dev_info(di->dev,"get value is %d\n",value);
}else{
dev_info(di->dev,"get value is %d\n",value);
}
end:
queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
msecs_to_jiffies(di->pdata->poll_ms));
}
外部中斷
中斷的申請和初始化在gpio_demo_init_interrupt
函數中已經實現,如下所示;
通過gpio_to_irq
接口獲取相應GPIO
上的軟件中斷號,然后通過devm_request_irq
申請中斷;
static int gpio_demo_init_interrupt(struct gpio_demo_device *di) {
...
// 獲取gpio上的中斷號
irq = gpio_to_irq(gpio_index);
...
//申請中斷
ret = devm_request_irq(di->dev, irq, gpio_demo_isr,
IRQF_TRIGGER_FALLING, //下降沿
"gpio-demo-isr", //中斷名稱
di);
...
}
其中,每次外部發送一個下降沿信號,就會觸發中斷並進入gpio_demo_isr
這個中斷服務程序;下面來看一下這個gpio_demo_isr
,在這里可以做一些我們想做的事情;
static irqreturn_t gpio_demo_isr(int irq, void *dev_id)
{
struct gpio_demo_device *di = (struct gpio_demo_device *)dev_id;
struct gpio_platform_data *pdata = di->pdata;
BUG_ON(irq != gpio_to_irq(pdata->gpio_index));
//TODO
dev_info(di->dev, "%s\n", __func__);
return IRQ_HANDLED;
}
最終,我只在中斷服務程序中打印了一下串口信息,方便驗證。
總結
通過這次學習和總結,總體了解了以下幾點;
- 通過
delayed_work
對GPIO
進行輪詢操作,后面會再深入學習一下; - 學習了對於
GPIO
上的中斷申請,目前對於中斷還是剛好夠用的階段,中斷的篇幅較長,可以對其原理做一下學習,還有內核中中斷的機制; - 學習了內核中讀取設備樹的幾個接口;
- 學習了platform設備驅動模型的框架;
附錄
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
//API for libgpio
#include <linux/gpio.h>
//API for malloc
#include <linux/slab.h>
//API for device tree
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
//API for thread
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/mutex.h>
//API for delaywork
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#define TIMER_MS_COUNTS 1000
// default value of dts
#define DEFAULT_POLL_TIME 5
#define DEFAULT_MODE 1
struct gpio_platform_data {
int mode;
int count;
int gpio_index;
struct mutex mtx;
int poll_ms;
};
struct gpio_demo_device {
struct platform_device *pdev;
struct device *dev;
struct gpio_platform_data *pdata;
struct workqueue_struct *gpio_monitor_wq;
struct delayed_work gpio_delay_work ;
int gpio_irq;
};
static int gpio_parse_data(struct gpio_demo_device *di){
int ret;
struct gpio_platform_data *pdata;
struct device *dev = di->dev;
struct device_node *np = di->dev->of_node;
pdata = devm_kzalloc(di->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
return -ENOMEM;
}
di->pdata = pdata;
// set default value for platform data
pdata->mode = DEFAULT_MODE;
pdata->poll_ms = DEFAULT_POLL_TIME * 1000;
dev_info(dev,"parse platform data\n");
ret = of_property_read_u32(np, "mode", &pdata->mode);
if (ret < 0) {
dev_err(dev, "can't get mode property\n");
}
ret = of_property_read_u32(np, "poll_time", &pdata->poll_ms);
if (ret < 0) {
dev_err(dev, "can't get poll_ms property\n");
}
pdata->gpio_index = of_get_named_gpio(np,"input-gpio", 0);
if (pdata->gpio_index < 0) {
dev_err(dev, "can't get input gpio\n");
}
// debug parse device tree data
dev_info(dev, "Success:mode is %d\n", pdata->mode);
dev_info(dev, "Success:gpio index is %d\n", pdata->gpio_index);
return 0;
}
static void gpio_demo_work(struct work_struct *work) {
struct gpio_demo_device *di = container_of(work,
struct gpio_demo_device,
gpio_delay_work.work);
struct gpio_platform_data *padta = di->pdata;
int gpio_index,value;
gpio_index = padta->gpio_index;
if (!gpio_is_valid(gpio_index) ) {
dev_err(di->dev, "gpio is not valid\n");
goto end;
}
if ( (value = gpio_get_value(gpio_index) ) == 0) {
dev_info(di->dev,"get value is %d\n",value);
}else{
dev_info(di->dev,"get value is %d\n",value);
}
end:
queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
msecs_to_jiffies(di->pdata->poll_ms));
}
static int gpio_demo_init_poll(struct gpio_demo_device *di) {
dev_info(di->dev,"%s\n", __func__);
di->gpio_monitor_wq = alloc_ordered_workqueue("%s",
WQ_MEM_RECLAIM | WQ_FREEZABLE, "gpio-demo-wq");
INIT_DELAYED_WORK(&di->gpio_delay_work, gpio_demo_work);
queue_delayed_work(di->gpio_monitor_wq, &di->gpio_delay_work,
msecs_to_jiffies(TIMER_MS_COUNTS * 5));
return 0;
}
static irqreturn_t gpio_demo_isr(int irq, void *dev_id)
{
struct gpio_demo_device *di = (struct gpio_demo_device *)dev_id;
struct gpio_platform_data *pdata = di->pdata;
BUG_ON(irq != gpio_to_irq(pdata->gpio_index));
dev_info(di->dev, "%s\n", __func__);
//printk("%s\n",__func__);
return IRQ_HANDLED;
}
static int gpio_demo_init_interrupt(struct gpio_demo_device *di) {
int irq, ret;
int gpio_index = di->pdata->gpio_index;
dev_info(di->dev,"%s\n", __func__);
if (!gpio_is_valid(gpio_index)){
return -1;
}
irq = gpio_to_irq(gpio_index);
if (irq < 0) {
dev_err(di->dev, "Unable to get irq number for GPIO %d, error %d\n",
gpio_index, irq);
gpio_free(gpio_index);
return -1;
}
ret = devm_request_irq(di->dev, irq, gpio_demo_isr,
IRQF_TRIGGER_FALLING,
"gpio-demo-isr",
di);
if (ret) {
dev_err(di->dev, "Unable to claim irq %d; error %d\n",
irq, ret);
gpio_free(gpio_index);
return -1;
}
return 0;
}
static int gpio_demo_probe(struct platform_device *pdev){
int ret;
struct gpio_demo_device *priv;
struct device *dev = &pdev->dev;
priv = devm_kzalloc(dev, sizeof(*priv) , GFP_KERNEL);
if (!priv) {
return -ENOMEM;
}
priv->dev = dev; //important
ret = gpio_parse_data(priv);
if (ret){
dev_err(dev,"parse data failed\n");
}
platform_set_drvdata(pdev,priv);
if (priv->pdata->mode == 0){
gpio_demo_init_poll(priv);
} else {
gpio_demo_init_interrupt(priv);
}
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id gpio_demo_of_match[] = {
{ .compatible = "gpio-demo"},
{},
}
MODULE_DEVICE_TABLE(of,gpio_demo_of_match);
#else
static struct of_device_id gpio_demo_of_match[] = {
{ },
}
#endif
static struct platform_driver gpio_demo_driver = {
.probe = gpio_demo_probe,
.driver = {
.name = "gpio-demo-device",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_demo_of_match),
}
};
static int __init gpio_demo_init(void){
return platform_driver_register(&gpio_demo_driver);
}
static void __exit gpio_demo_exit(void){
platform_driver_unregister(&gpio_demo_driver);
}
late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gpio demo Driver");
MODULE_ALIAS("platform:gpio-demo");