1、概述
通過風扇FG腳獲取風扇轉速。
2、分析

根據風扇規格書可知風扇風速=60/(2*脈沖周期),周期T=1/頻率。那么我們需要獲取FG腳上的脈沖頻率,即可獲取風扇風速。
3、解決方法
利用邊沿觸發中斷利用定時器獲取1s進入中斷的次數即可獲取脈沖頻率。
(1)注冊檢測腳
gpio-pwms {
compatible = "gpio-pwms";
pinctrl-names = "default";
pwm1 {
label = "pwm1";
gpios = <&pio 0 6 GPIO_ACTIVE_HIGH>;
gpios-fg = <&pio 0 17 GPIO_ACTIVE_HIGH>;
};
pwm2 {
label = "pwm2";
gpios = <&pio 6 9 GPIO_ACTIVE_HIGH>;
gpios-fg = <&pio 0 3 GPIO_ACTIVE_HIGH>;
};
pwm3{
label = "pwm3";
gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>;
gpios-fg = <&pio 0 21 GPIO_ACTIVE_HIGH>;
};
pwm4{
label = "pwm4";
gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>;
gpios-fg = <&pio 0 20 GPIO_ACTIVE_HIGH>;
};
};
(2)編寫驅動
-
解析dts文件,獲取fg腳
for_each_child_of_node(node, fg)
{
enum of_gpio_flags flagsfg;
if (!of_find_property(fg, "gpios-fg", NULL))
{
pdata->npwms--;
printk( "Fail to find gpios-fg\n");
continue;
}
pwm = &pdata->pwms[i++];
pwm->gpio_fg = of_get_named_gpio_flags(fg,"gpios-fg", 0, &flagsfg);
printk("pwm->gpio-fg=%d,flags=%d",pwm->gpio_fg,flagsfg);
if (pwm->gpio_fg < 0)
{
error = pwm->gpio_fg;
if (error != -ENOENT)
{
if (error != -EPROBE_DEFER)
dev_err(dev,
"Failed to get gpio-fg flags, error: %d\n",
error);
return ERR_PTR(error);
}
}
}
-
申請中斷
switch(gpiofg)
{
case 17:
error= devm_gpio_request(dev, gpiofg,"fan1_FG"); break;
case 3:
error= devm_gpio_request(dev, gpiofg,"fan2_FG"); break;
case 21:
error= devm_gpio_request(dev, gpiofg,"fan3_FG"); break;
case 20:
error= devm_gpio_request(dev, gpiofg,"fan4_FG"); break;
default:
break;
}
if (error){
printk( "unable to request gpio %u, err=%d\n",
gpiofg, error);
}
gpwm->irq_fg= gpio_to_irq(gpiofg); //獲取一個gpio對應的中斷號
if (gpwm->irq_fg < 0)
{
printk("return irq number error!");
}
switch(gpiofg)
{
case 17:
pin1FGirq = gpwm->irq_fg;
INIT_WORK(&gpwm->gpiofg_work, fan1_speed); //初始化工作隊列
irq_set_irq_type(gpwm->irq_fg, IRQ_TYPE_EDGE_FALLING); //設置觸發類型
error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler,
IRQF_SHARED,"fan1_FG", gpwm); //申請中斷設置中斷類型為 共享中斷
break;
case 3:
pin2FGirq = gpwm->irq_fg;
INIT_WORK(&gpwm->gpiofg_work, fan2_speed);
error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler,
IRQF_SHARED,"fan2_FG", gpwm);
break;
case 21:
pin3FGirq = gpwm->irq_fg;
INIT_WORK(&gpwm->gpiofg_work, fan3_speed);
error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler,
IRQF_SHARED,"fan3_FG", gpwm);
break;
case 20:
pin4FGirq = gpwm->irq_fg;
INIT_WORK(&gpwm->gpiofg_work, fan4_speed);
error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler,
IRQF_SHARED,"fan4_FG", gpwm);
break;
default:
break;
}
if (error) {
printk( "failed to request irq, err=%d\n", error);
}
disable_irq(gpwm->irq_fg); //默認關閉中斷
}
-
中斷服務程序
static irqreturn_t get_fan_speed_irq_handler(int irq, void *dev_id)
{
struct pwm_chip *gpiofg_data = dev_id;
schedule_work(&gpiofg_data->gpiofg_work); //schedule_work(work)來通知內核線程,然后中斷結束后,再去繼續執行work對應的func函數
return IRQ_HANDLED;
}
注意:
//中斷服務程序的返回值必須為IRQ_HANDLED
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device or was not handled
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
中斷服務程序有三個返回值,三個值代表不同意思,如果返回值為IR_NONE,系統會認為這個中斷沒有被處理(但是中斷程序執行了),當 未處理中斷次數超過100000次時,系統會disable掉這個中斷。系統會認為中斷卡死了,這是共享中斷的特性,會根據中斷服務程序的返回值判斷中斷程序是否被處理。
當一個中斷號上有多個中斷共享的時候,該中斷來的時候,內核會依次調用共享該中斷號的各個中斷處理函數,如果中斷處理函數檢測到該中斷不是自己的中斷時就會返回IRQ_NONE,這時內核就會調用下一個中斷處理函數,而這些中斷處理函數中必須至少有一個返回IRQ_HANDLED告知內核該中斷是自己的中斷,已經正常處理,若內核依次調用完所有該中斷號的中斷處理函數仍未得到IRQ_HANDLED的返回值,內核就會報告上述錯誤,並在該中斷出現一定次數后關閉該中斷。即只有中斷處理函數返回 IRQ_HANDLED ,這個中斷才是被正確完成的。
中斷卡死的處理過程:
//Linux-4.14.25/kernel/irq/spurious.c
irq = irq_desc_get_irq(desc);
if (unlikely(try_misrouted_irq(irq, desc, action_ret))) {
int ok = misrouted_irq(irq);
if (action_ret == IRQ_NONE)
desc->irqs_unhandled -= ok;
}
desc->irq_count++;
if (likely(desc->irq_count < 100000))
return;
desc->irq_count = 0;
if (unlikely(desc->irqs_unhandled > 99900)) {
/*
* The interrupt is stuck
*/
__report_bad_irq(desc, action_ret);
/*
* Now kill the IRQ
*/
printk(KERN_EMERG "Disabling IRQ #%d\n", irq);
desc->istate |= IRQS_SPURIOUS_DISABLED;
desc->depth++;
irq_disable(desc);
mod_timer(&poll_spurious_irq_timer,
jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
}
desc->irqs_unhandled = 0;
}
查看中斷信息:


-
工作隊列的任務
static void fan1_speed(struct work_struct *ws)
{
pinFG1_frequency++;
}
static void fan2_speed(struct work_struct *ws)
{
pinFG2_frequency++;
}
static void fan3_speed(struct work_struct *ws)
{
pinFG3_frequency++;
}
static void fan4_speed(struct work_struct *ws)
{
pinFG4_frequency++;
}
工作隊列的介紹
在中斷處理中,經常用到工作隊列,這樣便能縮短中斷處理時的時間
//工作隊列初始化函數
INIT_WORK(work, func);
中斷中通過調用schedule_work(work)來通知內核線程,然后中斷結束后,再去繼續執行work對應的func函數
示例
當中斷來了,立馬調用schedule_work(work),然后退出.
中斷結束后,內核便會調用_work對應的func函數,最后才來讀取按鍵值,上報按鍵值,這樣就大大縮短了中斷處理時間
-
定時器初始化
static void fan1_init_timer(void)
{
fan1timer.expires = jiffies+100;//設定 超時時間,100代表1秒?
timer_setup(&fan1timer, fan1_timer, 0);
add_timer(&fan1timer); //添加定時器,定時器開始生效
enable_irq(pin1FGirq);
}
static void fan2_init_timer(void)
{
fan2timer.expires = jiffies+100;//設定 超時時間,100代表1秒
timer_setup(&fan2timer, fan2_timer, 0); //准備timer,並設置超時時執行的函數。
add_timer(&fan2timer); //添加定時器,定時器開始生效
enable_irq(pin2FGirq);
}
static void fan3_init_timer(void)
{
fan3timer.expires = jiffies+100;//設定 超時時間,100代表1秒
timer_setup(&fan3timer, fan3_timer, 0);
add_timer(&fan3timer); //添加定時器,定時器開始生效
enable_irq(pin3FGirq);
}
static void fan4_init_timer(void)
{
fan4timer.expires = jiffies+100;//設定 超時時間,100代表1秒
timer_setup(&fan4timer, fan4_timer, 0);
add_timer(&fan4timer); //添加定時器,定時器開始生效
enable_irq(pin4FGirq);
}
-
定時器超時處理函數
static void fan1_timer(struct timer_list *t)
{
pinFG_frequency[0] = pinFG1_frequency;
pinFG1_frequency = 0;
mod_timer(&fan1timer,jiffies+100); // 修改定時器的expire
}
static void fan2_timer(struct timer_list *t)
{
pinFG_frequency[1] = pinFG2_frequency;
pinFG2_frequency = 0;
mod_timer(&fan2timer,jiffies+100);
}
static void fan3_timer(struct timer_list *t)
{
pinFG_frequency[2] = pinFG3_frequency;
pinFG3_frequency = 0;
mod_timer(&fan3timer,jiffies+100);
}
static void fan4_timer(struct timer_list *t)
{
pinFG_frequency[3] = pinFG4_frequency;
pinFG4_frequency = 0;
mod_timer(&fan4timer,jiffies+100);
}
-
read函數(應用層read會調用到這個函數)
ssize_t pwm_drv_read (struct file *filp, char __user *userbuf, size_t count, loff_t *fpos)
{
int ret=0, i = 0,j=0;
unsigned char tmp[8] ={0};
//應用層從內核讀取數據時,只能一個字節一個字節讀,所以將頻率short型數據要分成兩個單字節數據讀。
while(i<8)
{
tmp[i] = pinFG_frequency[j]>>8 ;
tmp[i+1] = pinFG_frequency[j];
i+=2;
j++;
}
ret= copy_to_user(userbuf, tmp, sizeof(tmp)/sizeof(tmp[0]));
if(ret==1)
{
printk("copy data error!\n");
ret = -1;
}
return ret;
(3)應用層獲取數據
void fan_get_rotating_speed(uint16_t *arg,uint8_t len)
{
int fd=-1,ret=-1,i=0,j=0;
uint8_t recv_buff[8]={0};
uint16_t pinFG_Freqency[4]={0};
printf("fan_get_rotating_speed\n");
fd = open(dev_fan[0].description,O_RDWR );
if(fd < 0)
{
printf("failed to open pwm0 failed!\n");
}
//讀取數據
ret = read(fd,recv_buff,len*2);
if(ret<0)
{
printf("get fan rotating speed error!");
}
//將8個字節的數據合成4個short型數據
while(i<8)
{
pinFG_Freqency[j] = (unsigned short)recv_buff[i]<<8|recv_buff[i+1];
i+=2;
j++;
}
//計算轉速
for(i=0;i<len;i++)
{
arg[i]=(uint16_t)((60*pinFG_Freqency[i])/2);
}
close(fd);
}
driver-ipollo.c中去調用
else if (strcasecmp(option, "getallstats") == 0) {
char tmp_str[64] = { 0 };
uint16_t fan_speed[4]={0};
fan_get_rotating_speed(fan_speed,sizeof(fan_speed)/sizeof(fan_speed[0]));
sprintf(tmp_str, "\"fanspeed[0:%d]:[1:%d][2:%d][3:%d]\"",fan_speed[0],fan_speed[1],fan_speed[2],fan_speed[3]);
strcat(replybuf, tmp_str);
可通過命令去獲取風速:
echo -n "ascset|0,getallstats" | nc 192.168.1.100 4028 && echo

