你需要知道的Linux 系统下外设时钟管理
嵌入式系統一般要求低功耗,出于這個原因,一般只把需要使用到的外設時鐘源打開,其他不需要使用到的模塊,則默認關閉它們。
LCD 模塊,上電時候默認情況是關閉的,所以,要想使用 LCD 模塊,配置它寄存器必須先開啟它時鐘。
如何知道,哪個模塊時鐘源是打開的?哪些模塊時鐘源是關閉的?不同的芯片時鐘設置一定不相同的,所以實現代碼是編寫在和具體芯片相關的文件中:
Clock-exynos4.c (arch\arm\mach-exynos)內核使用 struct clk 結構描述一個外設模塊的時鐘信息:
struct clk {struct list_head list;
struct module *owner;
struct clk *parent;
const char *name;
const char *devname;//設備名,用來查找。
int id;
int usage;
unsigned long rate;
unsigned long ctrlbit;
struct clk_ops *ops;
int (*enable)(struct clk *, int enable);//指向模塊時鐘使能/禁止時鐘的函數
struct clk_lookup lookup;
#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS)
struct dentry *dent; /* For visible tree hierarchy */
#endif
};
一個已經移植好,可以運行的內核,它的外設時鐘都已經在系統初期已經完成注冊,實現文件就在
Clock-exynos4.c arch\arm\Mach-exynos關于 LCD 控制器(fimd0)模塊的時鐘定義:
把 exynos4_clk_fimd0 結構放入數組中:
void __init exynos4_register_clocks(void){
int ptr;
s3c24xx_register_clocks(exynos4_clks, ARRAY_SIZE(exynos4_clks));
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_sysclks); ptr++)
s3c_register_clksrc(exynos4_sysclks[ptr], 1);
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_sclk_tv); ptr++)
s3c_register_clksrc(exynos4_sclk_tv[ptr], 1);
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clksrc_cdev); ptr++)
s3c_register_clksrc(exynos4_clksrc_cdev[ptr], 1);
//注冊時鐘源,其中 sclk_fimd0 就是在這里注冊的 ,在 exynos4_clksrcs 數組中定義
s3c_register_clksrc(exynos4_clksrcs, ARRAY_SIZE(exynos4_clksrcs));
//默認打開時鐘的模塊
s3c_register_clocks(exynos4_init_clocks_on, ARRAY_SIZE(exynos4_init_clocks_on));
s3c_register_clocks(exynos4_init_audss_clocks, ARRAY_SIZE(exynos4_init_audss_clocks));
s3c_disable_clocks(exynos4_init_audss_clocks, ARRAY_SIZE(exynos4_init_audss_clocks));
s3c24xx_register_clocks(exynos4_gate_clocks, ARRAY_SIZE(exynos4_gate_clocks));
//fyyy:注冊設備時鐘,其中 LCD 時鐘就在這里注冊,可以通過 clk_get 獲得
s3c24xx_register_clocks(exynos4_clk_cdev, ARRAY_SIZE(exynos4_clk_cdev));
//fyyy:注冊后禁止它,為了降低功耗
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clk_cdev); ptr++)
s3c_disable_clocks(exynos4_clk_cdev[ptr], 1);//這里有禁止 lcd 相關的時鐘 fimd0
s3c_register_clocks(exynos4_init_clocks_off, ARRAY_SIZE(exynos4_init_clocks_off));
//默認關閉時鐘的模塊
s3c_disable_clocks(exynos4_init_clocks_off, ARRAY_SIZE(exynos4_init_clocks_off));
//可以查找的時鐘 ,可以通過 clk_get 獲得
Clkdev_add_table(exynos4_clk_lookup, ARRAY_SIZE(exynos4_clk_lookup));
register_syscore_ops(&exynos4_clock_syscore_ops);
s3c24xx_register_clock(&dummy_apb_pclk);
s3c_pwmclk_init();
}
分析:
s3c24xx_register_clocks(exynos4_clk_cdev, ARRAY_SIZE(exynos4_clk_cdev));是注冊了 fimd0 模塊的時鐘信息
//fyyy:注冊后禁止它,為了降低功耗for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clk_cdev); ptr++) {
s3c_disable_clocks(exynos4_clk_cdev[ptr], 1);//這里有禁止 lcd 相關的時鐘 fimd0
}
要使用這個模塊,必須先開這個模塊的時鐘。
clkdev_add_table(exynos4_clk_lookup, ARRAY_SIZE(exynos4_clk_lookup));這一行是把可以通過設備名查找到的 clk 結構加到可查詢的鏈表上。內核 struct clk_lookup 結構來表示一個可以被查找到的時鐘結構。
Clkdev.h linux-3.5\include\Linux//它是用來查找 struct clk 結構的。
//有了它,就可以通過設備名或時鐘源的名字來找到相應的 struct clk 結構。
struct clk_lookup {
struct list_head node;
const char *dev_id; //設備名,提供對外搜索的名字,匹配使用的
const char *con_id; //總線名,也可以用來搜索,匹配使用
struct clk *clk; //指向模塊時鐘信息結構
};
實際的匹配過程是會比較 dev_id 和 con_id 兩個成員的,如果匹配上,則返回 clk 結構。
內核提供一個輔助填充宏:CLKDEV_INIT
定義如下:
#define CLKDEV_INIT(d, n, c) \{ \
.dev_id = d, \
.con_id = n, \
.clk = c, \
}
//可以被查找操作的模塊時鐘
//它是用來查找 struct clk 結構的。
//有了它,就可以通過設備名或時鐘源的名字來找到相應的 struct clk 結構。
static struct clk_lookup exynos4_clk_lookup[] = {
……
//通過設備名或時鐘源名查找到 exynos4_clk_fimd0 結構
CLKDEV_INIT("exynos4-fb.0", "lcd", &exynos4_clk_fimd0),
……
};
struct device dev;
struct clk * clk_bus;
dev. init_name = "exynos4-fb.0";
clk_bus = clk_get(&dev, "lcd" );
如何找到模塊的時鐘結構?內核提供了操作時鐘相關的 API 函數,這些 API 接口函數是通用的,聲明在 Clk.h linux-3.5\include\Linux 。時鐘獲得結構獲取函數:
struct clk *clk_get(struct device *dev, const char *id);功能:通過 dev. init_name 和參數 id 進行在 struct clk_lookup 注冊到內核的時鐘結構鏈表查找。參數 dev. init_name 和 clk_lookup 結構中的 dev_id 成員比較 參數 id 和 clk_lookup 結構中的 con_id 比較 如果兩個成員都相同就返回 clk_lookup 結構中的中 clk 指針。
返回值:IS_ERR(clk_get 返回值)?
非 0: 獲得失敗,這時候應該返回 –ENODEV 錯誤碼 IS_ERR(clk_get 返回值)?
0: 獲得時鐘成功
示例:
s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");if (IS_ERR(s3c_ac97.ac97_clk)) {
dev_err(&pdev->dev, "ac97 failed to get ac97_clock\n");
ret = -ENODEV;
goto err2;
}
clk_enable(s3c_ac97.ac97_clk); //獲得成功后可以使能模塊時鐘了
時鐘使能函數:
int clk_enable(struct clk *clk);功能: 在獲得 clk 結構后,就可以調用 clk_enable 函數來使能模塊的時鐘
返回: 0:成員;負數:失敗 時鐘禁止函數:
void clk_disable(struct clk *clk);功能:當不需要使用一個模塊時候,要降低功耗,可以關閉它。獲得模塊的運行時鐘頻率:
unsigned long clk_get_rate(struct clk *clk);功能: 根據結構獲得模塊的運行頻率?
返回:模塊的運行頻率,單位是 HZ 減少時鐘引用計數,如果你使用
void clk_put(struct clk *clk);當使用了 clk_get, clk_enable 后,如果不想使用模塊了,則需要 clk_put 引用計數。設置模塊的運行時鐘:
int clk_set_rate(struct clk *clk, unsigned long rate);參數: rate 要設置的目標運行頻率
返回: 0:成員;負數:失敗
—————END—————
掃碼或長按關注
回復「?加群?」進入技術群聊
總結
以上是生活随笔為你收集整理的你需要知道的Linux 系统下外设时钟管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Arm技术与市场
- 下一篇: 全球与中国ARM开发套件市场现状及未来发