【深入Linux内核驱动】通用时钟框架驱动 Common Clk Framework(CCF)【转】

时钟驱动

本文摘录于《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,仅自己学习记录,详细阅读请看原书.

在一个 SoC 中,晶振、 PLL 、驱动和门等会形成一个时钟树形结构,在 Linux 2.6 中,也存有clk_get_rate ()clk_set_rate ()clk_get_parent () clk_set_parent ()等通用 API ,但是这些 API 由每个 SoC 单独实现,而且各个 SoC 供应商在实现方面的差异很大,于是内核增加了一个新的通用时钟框架以解决这个碎片化问题。之所以称为通用时钟,是因为这个通用主要体现在:

  1. 统一的 clk 结构体,统一的定义于clk.h中的 clk API ,这些 API 会调用统一的 clk_ops 中的回调函数;

这个统一的clk 结构体的定义如代码清单所示:

clk 结构体
struct clk {
 const char *name;
 const struct clk_ops *ops;
 struct clk_hw *hw;
 char **parent_names;
 struct clk **parents;
 struct clk *parent;
 struct hlist_head children;
 struct hlist_node child_node;
...
};

// clk_ops 定义是关于时钟使能、禁止、计算频率等的操作集
clk_ops 结构体
struct clk_ops {
	int		(*prepare)(struct clk_hw *hw);
	void		(*unprepare)(struct clk_hw *hw);
	int		(*is_prepared)(struct clk_hw *hw);
	void		(*unprepare_unused)(struct clk_hw *hw);
	int		(*enable)(struct clk_hw *hw);
	void		(*disable)(struct clk_hw *hw);
	int		(*is_enabled)(struct clk_hw *hw);
	void		(*disable_unused)(struct clk_hw *hw);
	unsigned long	(*recalc_rate)(struct clk_hw *hw,
					unsigned long parent_rate);
	long		(*round_rate)(struct clk_hw *hw, unsigned long rate,
					unsigned long *parent_rate);
	int		(*determine_rate)(struct clk_hw *hw,
					  struct clk_rate_request *req);
	int		(*set_parent)(struct clk_hw *hw, u8 index);
	u8		(*get_parent)(struct clk_hw *hw);
	int		(*set_rate)(struct clk_hw *hw, unsigned long rate,
				    unsigned long parent_rate);
	int		(*set_rate_and_parent)(struct clk_hw *hw,
				    unsigned long rate,
				    unsigned long parent_rate, u8 index);
	unsigned long	(*recalc_accuracy)(struct clk_hw *hw,
					   unsigned long parent_accuracy);
	int		(*get_phase)(struct clk_hw *hw);
	int		(*set_phase)(struct clk_hw *hw, int degrees);
	void		(*init)(struct clk_hw *hw);
	int		(*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};

  1. 对具体的 SoC 如何去实现针对自己 SoC 的 clk 驱动,如何提供硬件特定的回调函数的方法也进行了统一。

在代码清单中这个通用的 clk 结构体中,第 4 行的 clk_hw 是联系 clk_ops 中回调函数和具体硬件细节的纽带, clk_hw 中只包含通用时钟结构体的指针以及具体硬件的init数据,如代码清单所示:

clk_hw 结构体
struct clk_hw {
	struct clk_core *core;
	struct clk *clk;
	const struct clk_init_data *init;
};

其中的 clk_init_data 包含了具体时钟的名称、可能的父级时钟的名称列表 parent_names 、可能的父级时钟数量num_parents 等,实际上这些名称的匹配对建立时钟间的父子关系功不可没,如代码清单所示:

clk_init_data 结构体
struct clk_init_data {
	const char		*name;
	const struct clk_ops	*ops;
	const char		* const *parent_names;
	u8			num_parents;
	unsigned long		flags;
};

从 clk 核心层到具体芯片 clk 驱动的调用顺序为:

clk_enable ( clk );  -----> clk->ops->enable ( clk->hw );

通用的 clk API (如 clk_enable )在调用底层 clk 结构体的 clk_ops 成员函数(如 clk->ops->enable )时,会将 clk->hw 传递过去。一般在具体的驱动中会定义针对特定 clk (如 foo )的结构体,该结构体中包含 clk_hw 成员以及硬件私有数据:

struct clk_foo {
struct clk_hw hw;
... hardware specific data goes here ...
};

并定义 to_clk_foo ()宏,以便通过 clk_hw 获取 clk_foo :

#define to_clk_foo(_hw) container_of(_hw, struct clk_foo, hw)

在针对 clk_fooclk_ops 的回调函数中,我们便可以通过 clk_hwto_clk_foo 最终获得硬件私有数据,并访问硬件读写寄存器以改变时钟的状态:

struct clk_ops clk_foo_ops {
	.enable = &clk_foo_enable;
	.disable = &clk_foo_disable;
};
int clk_foo_enable(struct clk_hw *hw)
{
	struct clk_foo *foo;
	foo = to_clk_foo(hw);
/*
访问硬件读写寄存器以改变时钟的状态
*/
	...
	return 0;
};

在具体的 clk 驱动中,需要通过 clk_register ()以及它的变体注册硬件上所有的 clk ,通过 clk_register_clkdev ()注册 clk 的一个lookup (这样可以通过 con_id 或者 dev_id 字符串寻找到这个 clk ),这两个函数的原型为:

struct clk *clk_register(struct device *dev, struct clk_hw *hw);
int clk_register_clkdev(struct clk *clk, const char *con_id, const char *dev_fmt, ...);

另外,针对不同的 clk 类型(如固定频率的 clk 、 clk 门、 clk 驱动等), clk 子系统又提供了几个快捷函数以完成clk_register ()的过程:

struct clk *clk_register_fixed_rate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate);
struct clk *clk_register_gate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, void __iomem *reg, u8 bit_idx, u8 clk_gate_flags, spinlock_t *lock);
struct clk *clk_register_divider(struct device *dev, const char *name, const char *parent_name, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_divider_flags, spinlock_t *lock);

drivers/clk/clk-prima2.c 为例,与该驱动对应的芯片 SiRFprimaII 的外围接了一个 26MHz 的晶振和一个 32.768kHz 的RTC 晶振,在 26MHz 晶振的后面又有 3 个 PLL ,当然 PLL 后面又接了更多的 clk 节点,则它的相关驱动代码如清单:

clk 驱动案例
static unsigned long pll_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
	unsigned long fin = parent_rate;
	struct clk_pll *clk = to_pllclk(hw);
 	...
}

static long pll_clk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate)
{
 	...
}

static int pll_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
{
	...
}

static struct clk_ops std_pll_ops = {
	.recalc_rate = pll_clk_recalc_rate,
	.round_rate = pll_clk_round_rate,
	.set_rate = pll_clk_set_rate,25};

static const char *pll_clk_parents[] = {
	"osc",
};

static struct clk_init_data clk_pll1_init = {
	.name = "pll1",
	.ops = &std_pll_ops,
	.parent_names = pll_clk_parents,
	.num_parents = ARRAY_SIZE(pll_clk_parents),
};

static struct clk_init_data clk_pll2_init = {
	.name = "pll2",
	.ops = &std_pll_ops,
	.parent_names = pll_clk_parents,
	.num_parents = ARRAY_SIZE(pll_clk_parents),
};

static struct clk_init_data clk_pll3_init = {
	.name = "pll3",
	.ops = &std_pll_ops,
	.parent_names = pll_clk_parents,
	.num_parents = ARRAY_SIZE(pll_clk_parents),
};

static struct clk_pll clk_pll1 = {
	.regofs = SIRFSOC_CLKC_PLL1_CFG0,
	.hw = {
		.init = &clk_pll1_init,
	},

};
static struct clk_pll clk_pll2 = {
	.regofs = SIRFSOC_CLKC_PLL2_CFG0,
	.hw = {
		.init = &clk_pll2_init,
	},
};

static struct clk_pll clk_pll3 = {
	.regofs = SIRFSOC_CLKC_PLL3_CFG0,
	.hw = {
		.init = &clk_pll3_init,
	},
};
void __init sirfsoc_of_clk_init(void)
{
 	...
/* These are always available (RTC and 26MHz OSC)*/
clk = clk_register_fixed_rate(NULL, "rtc", NULL,

CLK_IS_ROOT, 32768);
BUG_ON(!clk);
clk = clk_register_fixed_rate(NULL, "osc", NULL,

CLK_IS_ROOT, 26000000);
BUG_ON(!clk);

clk = clk_register(NULL, &clk_pll1.hw);
BUG_ON(!clk);
clk = clk_register(NULL, &clk_pll2.hw);
BUG_ON(!clk);
clk = clk_register(NULL, &clk_pll3.hw);
BUG_ON(!clk);
 ...
}

另外,目前内核更加倡导的方法是通过设备树来描述电路板上的时钟树,以及时钟和设备之间的绑定关系。通常我们需要在 clk 控制器的节点中定义 #clock-cells 属性,并且在 clk 驱动中通过of_clk_add_provider ()注册时钟控制器为一个时钟树的提供者( Provider ),并建立系统中各个时钟和索引的映射表,如:

Clock ID

rtc  0
osc  1
pll1  2
pll2  3
pll3  4
mem  5
sys   6 
security  7
dsp  8
gps  9
mf  10

在每个具体的设备中,对应的 .dts 节点上的 clocks=<&clks index> 属性指向其引用的 clk 控制器节点以及使用的时钟的索引,如:

gps@a8010000 {
compatible = "sirf,prima2-gps";
reg = <0xa8010000 0x10000>;
interrupts = <7>;
clocks = <&clks 9>;
};

要特别强调的是,在具体的设备驱动中,一定要通过通用 clk API 来操作所有的时钟,而不要直接通过读写 clk 控制器的寄存器来进行,这些 API 包括:

struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
int clk_enable(struct clk *clk);
int clk_prepare(struct clk *clk);
void clk_unprepare(struct clk *clk);
void clk_disable(struct clk *clk);
static inline int clk_prepare_enable(struct clk *clk);
static inline void clk_disable_unprepare(struct clk *clk);
unsigned long clk_get_rate(struct clk *clk);
int clk_set_rate(struct clk *clk, unsigned long rate);
struct clk *clk_get_parent(struct clk *clk);
int clk_set_parent(struct clk *clk, struct clk *parent);

值得一提的是,名称中含有 prepare unprepare 字符串的 API 是内核后来才加入的,过去只有 clk_enable ()clk_disable ()。只有 clk_enable ()clk_disable ()带来的问题是,有时候,某些硬件使能 / 禁止时钟可能会引起睡眠以使得使能 / 禁止不能在原子上下文进行。加上 prepare 后,把过去的 clk_enable ()分解成不可在原子上下文调用的 clk_prepare ()(该函数可能睡眠)和可以在原子上下文调用的 clk_enable ()。而clk_prepare_enable ()则同时完成准备和使能的工作,当然也只能在可能睡眠的上下文调用该 API。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值