前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你需要知道的Linux 系统下外设时钟管理

你需要知道的Linux 系统下外设时钟管理

作者头像
用户6543014
发布2019-12-09 15:30:22
1.1K0
发布2019-12-09 15:30:22
举报
文章被收录于专栏:CU技术社区CU技术社区

嵌入式系统一般要求低功耗,出于这个原因,一般只把需要使用到的外设时钟源打开,其他不需要使用到的模块,则默认关闭它们。

LCD 模块,上电时候默认情况是关闭的,所以,要想使用 LCD 模块,配置它寄存器必须先开启它时钟。

如何知道,哪个模块时钟源是打开的?哪些模块时钟源是关闭的?不同的芯片时钟设置一定不相同的,所以实现代码是编写在和具体芯片相关的文件中:

代码语言:javascript
复制
Clock-exynos4.c (arch\arm\mach-exynos)

内核使用 struct clk 结构描述一个外设模块的时钟信息:

代码语言:javascript
复制
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
};

一个已经移植好,可以运行的内核,它的外设时钟都已经在系统初期已经完成注册,实现文件就在

代码语言:javascript
复制
Clock-exynos4.c  arch\arm\Mach-exynos

关于 LCD 控制器(fimd0)模块的时钟定义:

把 exynos4_clk_fimd0 结构放入数组中:

代码语言:javascript
复制
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();
}

分析:

代码语言:javascript
复制
s3c24xx_register_clocks(exynos4_clk_cdev, ARRAY_SIZE(exynos4_clk_cdev));

是注册了 fimd0 模块的时钟信息

代码语言:javascript
复制
//fyyy:注册后禁止它,为了降低功耗
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clk_cdev); ptr++) {
s3c_disable_clocks(exynos4_clk_cdev[ptr], 1);//这里有禁止 lcd 相关的时钟 fimd0
}

要使用这个模块,必须先开这个模块的时钟。

代码语言:javascript
复制
clkdev_add_table(exynos4_clk_lookup, ARRAY_SIZE(exynos4_clk_lookup));

这一行是把可以通过设备名查找到的 clk 结构加到可查询的链表上。内核 struct clk_lookup 结构来表示一个可以被查找到的时钟结构。

代码语言:javascript
复制
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

定义如下:

代码语言:javascript
复制
#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 。时钟获得结构获取函数:

代码语言:javascript
复制
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: 获得时钟成功

示例:

代码语言:javascript
复制
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); //获得成功后可以使能模块时钟了

时钟使能函数:

代码语言:javascript
复制
int clk_enable(struct clk *clk);

功能: 在获得 clk 结构后,就可以调用 clk_enable 函数来使能模块的时钟

返回: 0:成员;负数:失败 时钟禁止函数:

代码语言:javascript
复制
void clk_disable(struct clk *clk);

功能:当不需要使用一个模块时候,要降低功耗,可以关闭它。获得模块的运行时钟频率:

代码语言:javascript
复制
unsigned long clk_get_rate(struct clk *clk);

功能: 根据结构获得模块的运行频率

返回:模块的运行频率,单位是 HZ 减少时钟引用计数,如果你使用

代码语言:javascript
复制
void clk_put(struct clk *clk);

当使用了 clk_get, clk_enable 后,如果不想使用模块了,则需要 clk_put 引用计数。设置模块的运行时钟:

代码语言:javascript
复制
int clk_set_rate(struct clk *clk, unsigned long rate);

参数: rate 要设置的目标运行频率

返回: 0:成员;负数:失败

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SACC开源架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档