你需要知道的Linux 系統下外設時鐘管理

  • 2019 年 12 月 9 日
  • 筆記

嵌入式系統一般要求低功耗,出於這個原因,一般只把需要使用到的外設時鐘源打開,其他不需要使用到的模塊,則默認關閉它們。

LCD 模塊,上電時候默認情況是關閉的,所以,要想使用 LCD 模塊,配置它寄存器必須先開啟它時鐘。

如何知道,哪個模塊時鐘源是打開的?哪些模塊時鐘源是關閉的?不同的芯片時鐘設置一定不相同的,所以實現代碼是編寫在和具體芯片相關的文件中:

Clock-exynos4.c (archarmmach-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  archarmMach-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.5includeLinux  //它是用來查找 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.5includeLinux 。時鐘獲得結構獲取函數:

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_clockn");  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:成員;負數:失敗