1、綜述
基于硬件工程師的提出的一個測試需求:每隔5秒鐘拉高PA的使能腳,間隔5秒鐘再拉低PA使能腳(這里的PA 指的是power amplifier->即功率放大器的意思)
換句話說,就是播放音樂時,每隔5秒鐘有聲音,5秒鐘沒聲音。
學習本文你會掌握以下知識點:
1.linux中定時器的概念和使用
2.linux工作隊列的概念和使用
3.如何使能PA腳
4.定時器和工作隊列在linux中的實際運用
5.需求實現(xiàn)
2、基本知識
一、linux中定時器的概念和使用
Linux內核中,如果想要周期性的做一件事情,或者在某個特定的時間點去做一件事,比如每過5秒讓閃光燈亮一下等,應該怎么辦呢?
Linux給我們提供了timer_list (內核定時器)來實現(xiàn)相應的功能。
timer_list結構體:(路徑: kernel-3.18/include/linux/timer.h)

包含的主要成員:
a. data:傳遞到超時處理函數(shù)的參數(shù),主要在多個定時器同時使用時,區(qū)別是哪個timer超時。
b. expires:定時器超時的時間,以linux的jiffies來衡量。
c. void (*function)(unsigned long):定時器超時處理函數(shù)。
1.相關API函數(shù)
a. init_timer(struct timer_list*):定時器初始化函數(shù);
b. add_timer(struct timer_list*):往系統(tǒng)添加定時器;
c. mod_timer(struct timer_list *, unsigned long jiffier_timerout):
修改定時器的超時時間為jiffies_timerout;
(Linux系統(tǒng)中的jiffies類似于Windows里面的TickCount,
它是定義在內核里面的一個全局變量,
只是它的單位并不是秒或是毫秒。
通常是250個jiffies為一秒,在內核里面可以直接使用宏定義:HZ
)
d. timer_pending(struct timer_list *):定時器狀態(tài)查詢,
如果在系統(tǒng)的定時器列表中則返回1,否則返回0;
e. del_timer(struct timer_list*):刪除定時器。
2.相關API函數(shù)源碼解析
a)init_timer函數(shù)
a)init_timer函數(shù)(路徑: kernel-3.18/include/linux/timer.h)
#define init_timer(timer) \
__init_timer((timer), 0)
#define __init_timer(_timer, _flags)\
init_timer_key((_timer), (_flags), NULL, NULL)
可以看出 實際上是調用init_timer_key()函數(shù)去初始化
(路徑: kernel-3.18/kernel/time/timer.c)
void init_timer_key(struct timer_list *timer, unsigned int flags,
const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, flags, name, key);
}
- init_timer_key -初始化一個計時器
- @timer:要初始化的計時器。
- @flags:定時器的旗幟
- @name:計時器名稱
- @key:用于跟蹤計時器 同步鎖的依賴關系
*注意: 必須先調用init_timer_key()進行初始化,然后才能調用其他跟定時器相關的方法,例如add_timer,mod_timer等
b)add_timer函數(shù)
b)add_timer函數(shù)(路徑: kernel-3.18/kernel/time/timer.c)
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));//打印相關log
mod_timer(timer, timer->expires);//調用mod_timer設置時間
}
- add_timer -啟動一個計時器,或者說激活一個定時器。
- @timer:要添加的定時器
分析:add_timer用于往系統(tǒng)中添加一個定時器,參數(shù)timer為要添加的定時器(timer_list)
,當系統(tǒng)時間經過timer->expires這么多時間,就會去調用timer->function回調方法去完成相應的任務。
注意:必須先初始化timer->expires,timer->function,timer->data這三個成員變量,才能調用add_timer()這個方法
c)mod_timer函數(shù)
c)mod_timer函數(shù)(路徑: kernel-3.18/kernel/time/timer.c)
int mod_timer(struct timer_list *timer, unsigned long expires)
{
expires = apply_slack(timer, expires);
if (timer_pending(timer) && timer->expires == expires)
return 1;
return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}
- mod_timer -修改一個timer(定時器)的超時時間
- @timer:要修改的計時器。
- @expires:新的超時,單位jiffies
分析:mod_timer()是更新活動計時器過期字段(即expires參數(shù))的一種更有效的方法(如果計時器沒有被激活,mod_timer會先激活計時器,然后在重新設定超時時間)
實際上 調用mod_timer(timer, expires)相當于
del_timer(timer); timer->expires = expires; add_timer(timer);
注意:如果有多個未序列化的并發(fā)用戶使用相同的計時器,則mod_timer()是修改超時的唯一安全方法,因為add_timer()不能修改已經運行的計時器。
該函數(shù)返回是否已經修改了一個待定定時器
如果調用mod_timer去修改一個定時器,
如果當前定時器處于非激活狀態(tài),則該函數(shù)返回0,
如果當前定時器處于激活狀態(tài),則該函數(shù)返回1
Ps:調用了add_timer(),就表示該定時器處于激活狀態(tài)
d)add_timer函數(shù)
d)add_timer函數(shù) (路徑: kernel-3.18/include/linux/timer.h)
static inline int timer_pending(const struct timer_list * timer)
{
return timer->entry.next != NULL;
}
- timer_pending——是否有一個計時器正在等待?
- @timer:給定的定時器
timer_pending會告訴給定的計時器是否正在等待
返回值:如果計時器掛起,則為1;如果不是,則為0
如果timer->entry.next為NULL,表示計時器沒有掛起,返回0
如果timer->entry.next不等于NULL,表示計時器掛起,返回1
e)del_timer函數(shù)
e)del_timer函數(shù) (路徑: kernel-3.18/kernel/time/timer.c)
int del_timer(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = 0;
debug_assert_init(timer);
timer_stats_timer_clear_start_info(timer);
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
ret = detach_if_pending(timer, base, true);
spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
- del_timer -刪除(停用)計時器。
*@timer:被停用的計時器
分析:del_timer()禁用計時器——這對激活的和非激活的計時器都有效。
函數(shù)返回是否已經禁用了一個待定定時器。
(即。一個非激活計時器的del_timer()返回0,激活計時器返回1)
3.使用定時器的一般流程為:
- 1)定義timer_list,、初始化timer_list、編寫function;
- 2)為timer的expires、data、function賦值;
- 3)調用add_timer將timer往系統(tǒng)添加定時器;(或者直接調用mod_timer方法)
- 4)在定時器到期時,function被運行;
- 5)在程序中涉及timer控制的地方適當?shù)卣{用del_timer、mod_timer刪除timer或改動timer的expires。
實例:
static struct timer_list timer;//定義計時器
/*回調函數(shù)*/
static void miki_test_callback(unsigned long a)
{
//這里添加相應的邏輯,比如每隔5秒讓閃關燈亮一次等
}
//初始化相關參數(shù)
static void miki_init(void)
{
init_timer(&timer);//先初始化timer
test_timer.expires = jiffies + (20 * HZ);//設置超時 20*HZ 表示20秒
test_timer.function = &miki_test_callback;//設置回調函數(shù)
test_timer.data = ((unsigned long)0);//設置data參數(shù),一般傳入0即可
add_timer(&test_timer);//把定時器添加到系統(tǒng)中,激活定時器
}
/*主函數(shù)*/
void mian()
{
miki_init();
//如果需要修改定時器的時間,則調用mod_timer
mod_timer(&test_timer, jiffies + (10 * HZ));
}
二、linux中工作隊列(workqueue)的概念和使用
1.什么是workqueue(工作隊列)
Linux中的Workqueue機制就是為了簡化內核線程的創(chuàng)建。通過調用workqueue的接口就能創(chuàng)建內核線程。并且可以根據當前系統(tǒng)CPU的個數(shù)創(chuàng)建線程的數(shù)量,使得線程處理的事務能夠并行化。workqueue是內核中實現(xiàn)簡單而有效的機制,他顯然簡化了內核daemon的創(chuàng)建,方便了用戶的編程.
工作隊列(workqueue)是另外一種將工作推后執(zhí)行的形式.工作隊列可以把工作推后,交由一個內核線程去執(zhí)行,也就是說,這個下半部分可以在進程上下文中執(zhí)行。最重要的就是工作隊列允許被重新調度甚至是睡眠
2.相關數(shù)據結構
Linux中的Workqueue機制就是為了簡化內核線程的創(chuàng)建。通過調用workqueue的接口就能創(chuàng)建內
我們把推后執(zhí)行的任務叫做工作(work),描述它的數(shù)據結構為work_struct
路徑: kernel-3.18/include/linux/workqueue.h

這些工作以隊列結構組織成工作隊列(workqueue),其數(shù)據結構為workqueue_struct:
路徑: kernel-3.18/include/linux/workqueue.h


3.相關API(接口)函數(shù):
路徑:kernel-3.18/kernel/workqueue.c
路徑:kernel-3.18/include/linux/workqueue.h
1) create_workqueue(name)
用于創(chuàng)建一個workqueue隊列,為系統(tǒng)中的每個CPU都創(chuàng)建一個內核線程。
輸入參數(shù):@name:workqueue的名稱
2) create_singlethread_workqueue(name)
用于創(chuàng)建workqueue,只創(chuàng)建一個內核線程。輸入參數(shù):
輸入參數(shù):@name:workqueue名稱
3)destroy_workqueue(struct workqueue_struct *wq)
釋放workqueue隊列。輸入參數(shù):
輸入參數(shù):@ workqueue_struct:需要釋放的workqueue隊列指針
4) schedule_work(struct work_struct *work);
調度執(zhí)行一個具體的任務
輸入參數(shù):
@ work_struct:具體任務對象指針
5) schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
延遲一定時間去執(zhí)行一個具體的任務,功能與schedule_work類似,多了一個延遲時間,
輸入參數(shù):
@work_struct:具體任務對象指針
@delay:延遲時間
6)queue_work(struct workqueue_struct *wq, struct work_struct *work)
調度執(zhí)行一個指定workqueue中的任務。
輸入參數(shù):
@ workqueue_struct:指定的workqueue指針
@work_struct:具體任務對象指針
7)queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
延遲調度執(zhí)行一個指定workqueue中的任務,
功能與queue_work類似,輸入參數(shù)多了一個delay。
4使用工作隊列的一般流程為:
- 1)定聲明工作處理函數(shù)function、指向工作隊列的指針和工作結構體變量work_struct;
- 2)調用create_singlethread_workqueue或者create_workqueue創(chuàng)建自己的工作隊列;
- 3)將工作添加入自己創(chuàng)建的工作隊列等待執(zhí)行->queue_work
- 4)刪除自己的工作隊列;
實例:
static struct workqueue_struct *miki_test_wq;//聲明工作隊列
static struct work_struct miki_test_work;;//聲明工作
/*工作處理函數(shù)*/
static void miki_test_work_callback ()
{
//這里添加相應的邏輯,比如每隔5秒讓閃關燈亮一次等
}
/*主函數(shù)*/
void mian()
{
//創(chuàng)建自己的工作隊列
miki_test_wq = create_singlethread_workqueue("miki_test");
//初始化工作,實際上是讓工作work綁定工作處理函數(shù)miki_test_work_callback
INIT_WORK(&miki_test_work, miki_test_work_callback);
//調度執(zhí)行一個指定(miki_test_wq)中的任務miki_test_work
queue_work(miki_test_wq, &miki_test_work);
}
三.如何使能PA腳
在mt_soc_codec_mt63xx.c中Ext_Speaker_Amp_Change函數(shù)中進行外部PA的gpio控制就可以。
路徑:
kernel-3.18/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c


因此,
使能PA : Ext_Speaker_Amp_Change(true)
關閉PA : Ext_Speaker_Amp_Change(false)
提示:關于mt_soc_codec_63xx.c文件的路徑
【7.0】
kernel-3.18/sound/soc/mediatek/mt_soc_audio_v3/mt_soc_codec_63xx.c

【8.0】
kernel-3.18/sound/soc/mediatek/mt6735/mt_soc_codec_63xx.c
可以通過編譯生成的out目錄來查看系統(tǒng)編譯了哪些文件

四.定時器和工作隊列在linux中的實際運用
kernel-3.18/drivers/misc/mediatek/accdet/mt6580/accdet.c
在耳機驅動中,可以看到定時器和工作隊列的使用



分析:設置了一個定時器micbias_timer,設置時間為6秒,最后調用mod_timer去激活定時器,6秒后會自動調用disable_micbias函數(shù)
創(chuàng)建了一個名稱為accdet工作隊列,
為accdet_work設置回調方法accdet_work_callback
我們知道工作是要調用queue_work()這個方法把工作提交到工作隊列中,才會回調
accdet_work_callback方法去完成相應的任務,那么在哪里調用了該方法呢?

小結:定時器micbias_timer每隔6秒鐘就會去調用queue_work方法,告訴系統(tǒng),你要去調用accdet_work_callback方法去完成相應的任務
accdet_work_callback函數(shù)->主要用于檢測并且設置耳機的狀態(tài)

五、 需求實現(xiàn)
依葫蘆畫瓢,我們可以模仿在耳機驅動中,Linux的使用定時器和工作隊列的方式去完成這個需求。
步驟一:聲明變量
static struct timer_list test_timer;//定義定時器
static struct workqueue_struct *miki_test_wq;//定義工作
static struct work_struct miki_test_work;//定義工作隊列
步驟二:編寫回調方法
//工作隊列的回調方法
static void miki_test_work_callback(struct work_struct *work)
{
Ext_Speaker_Amp_Change(true);//打開PA
msleep(5 * 1000);//休眠5秒
Ext_Speaker_Amp_Change(false);//關閉PA
mod_timer(&test_timer, jiffies + (5* HZ));//重新激活定時器
}
//定時器的回調方法
static void miki_test_callback(unsigned long a)
{
queue_work(miki_test_wq, &miki_test_work);
}
步驟三:初始化定時器和工作隊列
static void miki_init (void)
{
miki_test_wq = create_singlethread_workqueue("miki_test");
INIT_WORK(&miki_test_work, miki_test_work_callback);
init_timer(&test_timer);
test_timer.expires = jiffies + (5 * HZ);//時間設置為5秒
test_timer.function = &miki_test_callback;//設置定時器回調方法
test_timer.data = ((unsigned long)0);
mod_timer(&test_timer, test_timer.expires);//激活定時器
}
步驟四:在模塊入口函數(shù)中調用miki_init()方法
static int __init mtk_mt6331_codec_init(void)->模塊入口函數(shù)
{
//省略部分源碼
miki_init();
return platform_driver_register(&mtk_codec_6331_driver);
}

到此,本文就結束了,希望有所收獲,lol開啟,嘻嘻(#^.^#)。
Stay hungry,Stay foolish!
荊軻刺秦王