Linux common clock framework(2)_clock provider

1. 前言

本文接上篇文章,从clock driver的角度,分析怎么借助common clock framework管理系统的时钟资源。换句话说,就是怎么编写一个clock driver。

由于kernel称clock driver为clock provider(相应的,clock的使用者为clock consumer),因此本文遵循这个规则,统一以clock provider命名。

2. clock有关的DTS

我们在“Linux common clock framework(1)_概述”中讲述clock consumer怎么使用clock时,提到过clock consumer怎么在DTS中指定所使用的clock。这里再做进一步说明。

2.1 clock provider的DTS

我们知道,DTS(Device Tree Source)是用来描述设备信息的,那系统的clock资源,是什么设备呢?换句话,用什么设备表示呢?这决定了clock provider的DTS怎么写。

通常有两种方式:

方式1,将系统所有的clock,抽象为一个虚拟的设备,用一个DTS node表示。这个虚拟的设备称作clock controller,参考如下例子:

   1: /* arch/arm/boot/dts/exynos4210.dtsi */

   2: clock: clock-controller@0x10030000 {

   3:         compatible = "samsung,exynos4210-clock";

   4:         reg = <0x10030000 0x20000>;

   5:         #clock-cells = <1>;

   6: };
    clock,该clock设备的名称,clock consumer可以根据该名称引用clock;
    
    #clock-cells,该clock的cells,1表示该clock有多个输出,clock consumer需要通过ID值指定所要使用的clock(很好理解,系统那么多clock,被抽象为1个设备,因而需要额外的ID标识)。

方式2,每一个可输出clock的器件,如“Linux common clock framework(1)_概述”所提及的Oscillator、PLL、Mux等等,都是一个设备,用一个DTS node表示。每一个器件,即是clock provider,也是clock consumer(根节点除外,如OSC),因为它需要接受clock输入,经过处理后,输出clock。参考如下例子(如果能拿到对应的datasheet会更容易理解):

   1: /* arch/arm/boot/dts/sun4i-a10.dtsi */

   2: clocks {

   3:     #address-cells = <1>;

   4:     #size-cells = <1>;

   5:     ranges;

   6:  

   7:     /*

   8:      * This is a dummy clock, to be used as placeholder on

   9:      * other mux clocks when a specific parent clock is not

  10:      * yet implemented. It should be dropped when the driver

  11:      * is complete.

  12:      */

  13:     dummy: dummy {

  14:         #clock-cells = <0>;

  15:         compatible = "fixed-clock";

  16:         clock-frequency = <0>;

  17:     };

  18:  

  19:     osc24M: osc24M@01c20050 {

  20:         #clock-cells = <0>;

  21:         compatible = "allwinner,sun4i-osc-clk";

  22:         reg = <0x01c20050 0x4>;

  23:         clock-frequency = <24000000>;

  24:     };

  25:  

  26:     osc32k: osc32k {

  27:         #clock-cells = <0>;

  28:         compatible = "fixed-clock";

  29:         clock-frequency = <32768>;

  30:     };

  31:  

  32:     pll1: pll1@01c20000 {

  33:         #clock-cells = <0>;

  34:         compatible = "allwinner,sun4i-pll1-clk";

  35:         reg = <0x01c20000 0x4>;

  36:         clocks = <&osc24M>;

  37:     };

  38:  

  39:     /* dummy is 200M */

  40:     cpu: cpu@01c20054 {

  41:         #clock-cells = <0>;

  42:         compatible = "allwinner,sun4i-cpu-clk";

  43:         reg = <0x01c20054 0x4>;

  44:         clocks = <&osc32k>, <&osc24M>, <&pll1>, <&dummy>;

  45:     };

  46:  

  47:     axi: axi@01c20054 {

  48:         #clock-cells = <0>;

  49:         compatible = "allwinner,sun4i-axi-clk";

  50:         reg = <0x01c20054 0x4>;

  51:         clocks = <&cpu>;

  52:     };

  53:  

  54:     axi_gates: axi_gates@01c2005c {

  55:         #clock-cells = <1>;

  56:         compatible = "allwinner,sun4i-axi-gates-clk";

  57:         reg = <0x01c2005c 0x4>;

  58:         clocks = <&axi>;

  59:         clock-output-names = "axi_dram";

  60:     };

  61:  

  62:     ahb: ahb@01c20054 {

  63:         #clock-cells = <0>;

  64:         compatible = "allwinner,sun4i-ahb-clk";

  65:         reg = <0x01c20054 0x4>;

  66:         clocks = <&axi>;

  67:     };

  68:  

  69:     ahb_gates: ahb_gates@01c20060 {

  70:         #clock-cells = <1>;

  71:         compatible = "allwinner,sun4i-ahb-gates-clk";

  72:         reg = <0x01c20060 0x8>;

  73:         clocks = <&ahb>;

  74:         clock-output-names = "ahb_usb0", "ahb_ehci0",

  75:             "ahb_ohci0", "ahb_ehci1", "ahb_ohci1", "ahb_ss",

  76:             "ahb_dma", "ahb_bist", "ahb_mmc0", "ahb_mmc1",

  77:             "ahb_mmc2", "ahb_mmc3", "ahb_ms", "ahb_nand",

  78:             "ahb_sdram", "ahb_ace",    "ahb_emac", "ahb_ts",

  79:             "ahb_spi0", "ahb_spi1", "ahb_spi2", "ahb_spi3",

  80:             "ahb_pata", "ahb_sata", "ahb_gps", "ahb_ve",

  81:             "ahb_tvd", "ahb_tve0", "ahb_tve1", "ahb_lcd0",

  82:             "ahb_lcd1", "ahb_csi0", "ahb_csi1", "ahb_hdmi",

  83:             "ahb_de_be0", "ahb_de_be1", "ahb_de_fe0",

  84:             "ahb_de_fe1", "ahb_mp", "ahb_mali400";

  85:     };

  86:  

  87:     apb0: apb0@01c20054 {

  88:         #clock-cells = <0>;

  89:         compatible = "allwinner,sun4i-apb0-clk";

  90:         reg = <0x01c20054 0x4>;

  91:         clocks = <&ahb>;

  92:     };

  93:  

  94:     apb0_gates: apb0_gates@01c20068 {

  95:         #clock-cells = <1>;

  96:         compatible = "allwinner,sun4i-apb0-gates-clk";

  97:         reg = <0x01c20068 0x4>;

  98:         clocks = <&apb0>;

  99:         clock-output-names = "apb0_codec", "apb0_spdif",

 100:             "apb0_ac97", "apb0_iis", "apb0_pio", "apb0_ir0",

 101:             "apb0_ir1", "apb0_keypad";

 102:     };

 103:  

 104:     /* dummy is pll62 */

 105:     apb1_mux: apb1_mux@01c20058 {

 106:         #clock-cells = <0>;

 107:         compatible = "allwinner,sun4i-apb1-mux-clk";

 108:         reg = <0x01c20058 0x4>;

 109:         clocks = <&osc24M>, <&dummy>, <&osc32k>;

 110:     };

 111:  

 112:     apb1: apb1@01c20058 {

 113:         #clock-cells = <0>;

 114:         compatible = "allwinner,sun4i-apb1-clk";

 115:         reg = <0x01c20058 0x4>;

 116:         clocks = <&apb1_mux>;

 117:     };

 118:  

 119:     apb1_gates: apb1_gates@01c2006c {

 120:         #clock-cells = <1>;

 121:         compatible = "allwinner,sun4i-apb1-gates-clk";

 122:         reg = <0x01c2006c 0x4>;

 123:         clocks = <&apb1>;

 124:         clock-output-names = "apb1_i2c0", "apb1_i2c1",

 125:             "apb1_i2c2", "apb1_can", "apb1_scr",

 126:             "apb1_ps20", "apb1_ps21", "apb1_uart0",

 127:             "apb1_uart1", "apb1_uart2", "apb1_uart3",

 128:             "apb1_uart4", "apb1_uart5", "apb1_uart6",

 129:             "apb1_uart7";

 130:     };

 131: };


    osc24M和osc32k是两个root clock,因此只做clock provider功能。它们的cells均为0,因为直接使用名字即可引用。另外,增加了“clock-frequency”自定义关键字,这样在板子使用的OSC频率改变时,如变为12M,不需要重新编译代码,只需更改DTS的频率即可(这不正是Device Tree的核心思想吗!)。话说回来了,osc24M的命名不是很好,如果频率改变,名称也得改吧,clock consumer的引用也得改吧;

    pll1即是clock provider(cell为0,直接用名字引用),也是clock consumer(clocks关键字,指定输入clock为“osc24M”);

    再看一个复杂一点的,ahb_gates,它是clock provider(cell为1),通过clock-output-names关键字,描述所有的输出时钟。同时它也是clock consumer(由clocks关键字可知输入clock为“ahb”)。需要注意的是,clock-output-names关键字只为了方便clock provider编程方便(后面会讲),clock consumer不能使用(或者可理解为不可见);

也许您会问,这些DTS描述,怎么使用?怎么和代码关联起来?先不着急,我们慢慢看。

2.2 clock consumer的DTS

在2.1中的方法二,我们已经看到clock consumer的DTS了,因为很多clock provider也是clock consumer。这里再举几个例子,做进一步说明。

例子1(对应2.1中的方式1,来自同一个DTS文件):

   1: /* arch/arm/boot/dts/exynos4210.dtsi */

   2: mct@10050000 {

   3:         compatible = "samsung,exynos4210-mct";

   4:         ...

   5:         clocks = <&clock 3>, <&clock 344>;

   6:         clock-names = "fin_pll", "mct";

   7:         ...

   8: };

    clocks,指明该设备的clock列表,clk_get时,会以它为关键字,去device_node中搜索,以得到对应的struct clk指针;

    clocks需要指明的信息,由clock provider的“#clock-cells”规定:为0时,只需要提供一个clock provider name(称作phandle);为1时,表示phandle有多个输出,则需要额外提供一个ID,指明具体需要使用那个输出。这个例子直接用立即数表示,更好的做法是,将系统所有clock的ID,定义在一个头文件中,而DTS可以包含这个头文件,如“clocks = <&clock CLK_SPI0>”;

    clock-names,为clocks指定的那些clock分配一些易于使用的名字,driver可以直接以名字为参数,get clock的句柄(具体可参考“Linux common clock framework(1)_概述”中clk_get相关的接口描述)。

例子2,如果clock provider的“#clock-cells”为0,可直接引用该clock provider的名字,具体可参考2.1中的方式2。

例子3,2.1中方式2有一个clock provider的名字为apb0_gates,它的“#clock-cells”为1,并通过clock-output-names指定了所有的输出clock,那么,clock consumer怎么引用呢?如下(2和.1中的方式2,来自同一个DTS文件):

   1: /* arch/arm/boot/dts/sun4i-a10.dtsi */

   2: soc@01c20000 {

   3:         compatible = "simple-bus";

   4:         ...

   5:  

   6:         pio: pinctrl@01c20800 {

   7:                 compatible = "allwinner,sun4i-a10-pinctrl";

   8:                 reg = <0x01c20800 0x400>;

   9:                 clocks = <&apb0_gates 5>;

  10:                 ...

  11:         }

  12: }
和例子1一样,指定phandle为“aph0_gates”,ID5

2.3 DTS相关的讨论和总结

我们在上面提到了clock provider的两种DTS定义方式,哪一种好呢?

从规范化、条理性的角度,毫无疑问方式2是好的,它真正理解了Device Tree的精髓,并细致的执行。且可以利用很多clock framework的标准实现(后面会讲)。

而方式1的优点是,DTS容易写,相应的clock driver也较为直观,只是注册一个一个clock provider即可,没有什么逻辑可言。换句话说,方式1比较懒。

后面的API描述,蜗蜗会着重从方式2的角度,因为这样才能体会到软件设计中的美学。

注1:上面例子中用到了两个公司的代码,方式1是三星的,方式2是全志的。说实话,全志的代码写的真漂亮,一个默默无闻的白牌公司,比三星这种国际大公司强多了。从这里,我们可以看到中国科技业的未来,还是很乐观的。

3. clock provider有关的API汇整

clock provider的API位于include/linux/clk-provider.h。

3.1 struct clk_hw

由“Linux common clock framework(1)_概述”可知,clock framework使用struct clk结构抽象clock,但该结构对clock consumer是透明的(不需要知道它的内部细节)。同样,struct clk对clock provider也是透明的。framework提供了struct clk_hw结构,从clock provider的角度,描述clock,该结构的定义如下:

/**
 * struct clk_hw - handle for traversing from a struct clk to its corresponding
 * hardware-specific structure.  struct clk_hw should be declared within struct
 * clk_foo and then referenced by the struct clk instance that uses struct
 * clk_foo's clk_ops
 *
 * @core: pointer to the struct clk_core instance that points back to this
 * struct clk_hw instance
 *
 * @clk: pointer to the per-user struct clk instance that can be used to call
 * into the clk API
 *
 * @init: pointer to struct clk_init_data that contains the init data shared
 * with the common clock framework. This pointer will be set to NULL once
 * a clk_register() variant is called on this clk_hw pointer.
 */
struct clk_hw {
	struct clk_core *core;
	struct clk *clk;
	const struct clk_init_data *init;
};
- `core`:指向指向当前`struct clk_hw`实例的`struct clk_core`实例的指针。`struct clk_core`用于将`struct clk_hw`与其它相关的结构联系起来。
- `clk`:指向每个用户使用的`struct clk`实例的指针。通过该指针可以调用`clk` API来操作时钟。
- `init`:指针,指向包含与通用时钟框架共享的初始化数据的`struct clk_init_data`结构。一旦在`clk_hw`指针上调用`clk_register()`变体之一,该指针将被设置为NULL。

init,描述该clock的静态数据,clock provider负责把系统中每个clock的静态数据准备好,然后交给clock framework的核心逻辑,剩下的事情,clock provider就不用操心了。这个过程,就是clock driver的编写过程,简单吧?该静态数据的数据结构如下。

通过使用`struct clk_hw`,可以将硬件特定的操作与通用的时钟框架进行分离,提供了一种方便的方式来访问和操作与时钟相关的硬件。
- `name`:时钟的名称。
- `ops`:该时钟支持的操作。
- `parent_names`:所有可能父时钟的字符串名称数组。
- `parent_data`:所有可能父时钟的父数据数组(当某些父时钟是时钟控制器之外的时候)。
- `parent_hws`:所有可能父时钟的指针数组(当所有父时钟都在时钟控制器内部时)。
- `num_parents`:可能父时钟的数量。
- `flags`:框架层面的提示和特性。

`struct clk_init_data`用于初始化时钟的共通数据,该数据与具体的时钟提供程序和通用时钟框架之间的交互有关。通过使用这个结构体,可以将与时钟相关的初始化数据与具体的时钟实现分离,提供了一种通用的方式来处理时钟的初始化和设置。该结构体中的字段可以根据具体的时钟需求进行配置,以满足不同的应用场景和需求。
/**
 * struct clk_ops -  Callback operations for hardware clocks; these are to
 * be provided by the clock implementation, and will be called by drivers
 * through the clk_* api.
 *
 * @prepare:	Prepare the clock for enabling. This must not return until
 *		the clock is fully prepared, and it's safe to call clk_enable.
 *		This callback is intended to allow clock implementations to
 *		do any initialisation that may sleep. Called with
 *		prepare_lock held.
 *
 * @unprepare:	Release the clock from its prepared state. This will typically
 *		undo any work done in the @prepare callback. Called with
 *		prepare_lock held.
 *
 * @is_prepared: Queries the hardware to determine if the clock is prepared.
 *		This function is allowed to sleep. Optional, if this op is not
 *		set then the prepare count will be used.
 *
 * @unprepare_unused: Unprepare the clock atomically.  Only called from
 *		clk_disable_unused for prepare clocks with special needs.
 *		Called with prepare mutex held. This function may sleep.
 *
 * @enable:	Enable the clock atomically. This must not return until the
 *		clock is generating a valid clock signal, usable by consumer
 *		devices. Called with enable_lock held. This function must not
 *		sleep.
 *
 * @disable:	Disable the clock atomically. Called with enable_lock held.
 *		This function must not sleep.
 *
 * @is_enabled:	Queries the hardware to determine if the clock is enabled.
 *		This function must not sleep. Optional, if this op is not
 *		set then the enable count will be used.
 *
 * @disable_unused: Disable the clock atomically.  Only called from
 *		clk_disable_unused for gate clocks with special needs.
 *		Called with enable_lock held.  This function must not
 *		sleep.
 *
 * @save_context: Save the context of the clock in prepration for poweroff.
 *
 * @restore_context: Restore the context of the clock after a restoration
 *		of power.
 *
 * @recalc_rate	Recalculate the rate of this clock, by querying hardware. The
 *		parent rate is an input parameter.  It is up to the caller to
 *		ensure that the prepare_mutex is held across this call. If the
 *		driver cannot figure out a rate for this clock, it must return
 *		0. Returns the calculated rate. Optional, but recommended - if
 *		this op is not set then clock rate will be initialized to 0.
 *
 * @round_rate:	Given a target rate as input, returns the closest rate actually
 *		supported by the clock. The parent rate is an input/output
 *		parameter.
 *
 * @determine_rate: Given a target rate as input, returns the closest rate
 *		actually supported by the clock, and optionally the parent clock
 *		that should be used to provide the clock rate.
 *
 * @set_parent:	Change the input source of this clock; for clocks with multiple
 *		possible parents specify a new parent by passing in the index
 *		as a u8 corresponding to the parent in either the .parent_names
 *		or .parents arrays.  This function in affect translates an
 *		array index into the value programmed into the hardware.
 *		Returns 0 on success, -EERROR otherwise.
 *
 * @get_parent:	Queries the hardware to determine the parent of a clock.  The
 *		return value is a u8 which specifies the index corresponding to
 *		the parent clock.  This index can be applied to either the
 *		.parent_names or .parents arrays.  In short, this function
 *		translates the parent value read from hardware into an array
 *		index.  Currently only called when the clock is initialized by
 *		__clk_init.  This callback is mandatory for clocks with
 *		multiple parents.  It is optional (and unnecessary) for clocks
 *		with 0 or 1 parents.
 *
 * @set_rate:	Change the rate of this clock. The requested rate is specified
 *		by the second argument, which should typically be the return
 *		of .round_rate call.  The third argument gives the parent rate
 *		which is likely helpful for most .set_rate implementation.
 *		Returns 0 on success, -EERROR otherwise.
 *
 * @set_rate_and_parent: Change the rate and the parent of this clock. The
 *		requested rate is specified by the second argument, which
 *		should typically be the return of .round_rate call.  The
 *		third argument gives the parent rate which is likely helpful
 *		for most .set_rate_and_parent implementation. The fourth
 *		argument gives the parent index. This callback is optional (and
 *		unnecessary) for clocks with 0 or 1 parents as well as
 *		for clocks that can tolerate switching the rate and the parent
 *		separately via calls to .set_parent and .set_rate.
 *		Returns 0 on success, -EERROR otherwise.
 *
 * @recalc_accuracy: Recalculate the accuracy of this clock. The clock accuracy
 *		is expressed in ppb (parts per billion). The parent accuracy is
 *		an input parameter.
 *		Returns the calculated accuracy.  Optional - if	this op is not
 *		set then clock accuracy will be initialized to parent accuracy
 *		or 0 (perfect clock) if clock has no parent.
 *
 * @get_phase:	Queries the hardware to get the current phase of a clock.
 *		Returned values are 0-359 degrees on success, negative
 *		error codes on failure.
 *
 * @set_phase:	Shift the phase this clock signal in degrees specified
 *		by the second argument. Valid values for degrees are
 *		0-359. Return 0 on success, otherwise -EERROR.
 *
 * @get_duty_cycle: Queries the hardware to get the current duty cycle ratio
 *              of a clock. Returned values denominator cannot be 0 and must be
 *              superior or equal to the numerator.
 *
 * @set_duty_cycle: Apply the duty cycle ratio to this clock signal specified by
 *              the numerator (2nd argurment) and denominator (3rd  argument).
 *              Argument must be a valid ratio (denominator > 0
 *              and >= numerator) Return 0 on success, otherwise -EERROR.
 *
 * @init:	Perform platform-specific initialization magic.
 *		This is not used by any of the basic clock types.
 *		This callback exist for HW which needs to perform some
 *		initialisation magic for CCF to get an accurate view of the
 *		clock. It may also be used dynamic resource allocation is
 *		required. It shall not used to deal with clock parameters,
 *		such as rate or parents.
 *		Returns 0 on success, -EERROR otherwise.
 *
 * @terminate:  Free any resource allocated by init.
 *
 * @debug_init:	Set up type-specific debugfs entries for this clock.  This
 *		is called once, after the debugfs directory entry for this
 *		clock has been created.  The dentry pointer representing that
 *		directory is provided as an argument.  Called with
 *		prepare_lock held.  Returns 0 on success, -EERROR otherwise.
 *
 * @pre_rate_change: Optional callback for a clock to fulfill its rate
 *		change requirements before any rate change has occurred in
 *		its clock tree. Returns 0 on success, -EERROR otherwise.
 *
 * @post_rate_change: Optional callback for a clock to clean up any
 *		requirements that were needed while the clock and its tree
 *		was changing states. Returns 0 on success, -EERROR otherwise.
 *
 * The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow
 * implementations to split any work between atomic (enable) and sleepable
 * (prepare) contexts.  If enabling a clock requires code that might sleep,
 * this must be done in clk_prepare.  Clock enable code that will never be
 * called in a sleepable context may be implemented in clk_enable.
 *
 * Typically, drivers will call clk_prepare when a clock may be needed later
 * (eg. when a device is opened), and clk_enable when the clock is actually
 * required (eg. from an interrupt). Note that clk_prepare MUST have been
 * called before clk_enable.
 */
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);
	int		(*save_context)(struct clk_hw *hw);
	void		(*restore_context)(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);
	int		(*get_duty_cycle)(struct clk_hw *hw,
					  struct clk_duty *duty);
	int		(*set_duty_cycle)(struct clk_hw *hw,
					  struct clk_duty *duty);
	int		(*init)(struct clk_hw *hw);
	void		(*terminate)(struct clk_hw *hw);
	void		(*debug_init)(struct clk_hw *hw, struct dentry *dentry);
	int		(*pre_rate_change)(struct clk_hw *hw,
					   unsigned long rate,
					   unsigned long new_rate);
	int		(*post_rate_change)(struct clk_hw *hw,
					    unsigned long old_rate,
					    unsigned long rate);
};

这段代码描述了一个名为struct clk_ops的数据结构,其目的是提供由驱动程序调用的硬件时钟实现的回调操作。驱动程序可以通过调用clk_* API来调用这些回调函数。struct clk_ops具有多个字段,包括prepareunprepareis_preparedenabledisableis_enabledsave_contextrestore_contextrecalc_rate等回调函数,它们的功能分别是准备使能时钟、释放时钟的准备状态、确定时钟是否准备好、使能时钟、禁用时钟、确定时钟是否已启用、保存和恢复时钟上下文以及重新计算时钟频率。这些回调函数可以解耦具体的时钟实现并提供一个通用的方式来处理时钟的初始化和设置。

/*
 * flags used across common struct clk.  these flags should only affect the
 * top-level framework.  custom flags for dealing with hardware specifics
 * belong in struct clk_foo
 *
 * Please update clk_flags[] in drivers/clk/clk.c when making changes here!
 */
#define CLK_SET_RATE_GATE	BIT(0) /* must be gated across rate change */
#define CLK_SET_PARENT_GATE	BIT(1) /* must be gated across re-parent */
#define CLK_SET_RATE_PARENT	BIT(2) /* propagate rate change up one level */
#define CLK_IGNORE_UNUSED	BIT(3) /* do not gate even if unused */
				/* unused */
				/* unused */
#define CLK_GET_RATE_NOCACHE	BIT(6) /* do not use the cached clk rate */
#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */
#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */
#define CLK_RECALC_NEW_RATES	BIT(9) /* recalc rates after notifications */
#define CLK_SET_RATE_UNGATE	BIT(10) /* clock needs to run to set rate */
#define CLK_IS_CRITICAL		BIT(11) /* do not gate, ever */
/* parents need enable during gate/ungate, set rate and re-parent */
#define CLK_OPS_PARENT_ENABLE	BIT(12)
/* duty cycle call may be forwarded to the parent clock */
#define CLK_DUTY_CYCLE_PARENT	BIT(13)
#define CLK_DONT_HOLD_STATE	BIT(14) /* Don't hold state */

这段代码定义了一些标志(flags),它们被用于控制通用的struct clk结构体的行为。这些标志只应该影响顶层框架。用于处理硬件具体细节的自定义标志应该属于struct clk_foo

这些标志包括:

  • CLK_SET_RATE_GATE:在频率更改期间必须受到门控限制。
  • CLK_SET_PARENT_GATE:在重新确定父时钟期间必须受到门控限制。
  • CLK_SET_RATE_PARENT:向上传递频率更改,一个级别一级。
  • CLK_IGNORE_UNUSED:即使未使用,也不进行门控。
  • CLK_GET_RATE_NOCACHE:不使用缓存的时钟频率。
  • CLK_SET_RATE_NO_REPARENT:频率更改时不重新确定父时钟。
  • CLK_GET_ACCURACY_NOCACHE:不使用缓存的时钟精度。
  • CLK_RECALC_NEW_RATES:在通知之后重新计算频率。
  • CLK_SET_RATE_UNGATE:时钟需要运行才能设置频率。
  • CLK_IS_CRITICAL:永远不进行门控。
  • CLK_OPS_PARENT_ENABLE:在门控/不门控、设置频率和重新确定父时需要使能父时钟。
  • CLK_DUTY_CYCLE_PARENT:占空比调用可能被转发到父时钟。
  • CLK_DONT_HOLD_STATE:不保持状态。

3.2 clock tree建立相关的API

3.2.1 clk_register

系统中,每一个clock都有一个struct clk_hw变量描述,clock provider需要使用register相关的接口,将这些clock注册到kernel,clock framework的核心代码会把它们转换为struct clk变量,并以tree的形式组织起来。这些接口的原型如下:

   1: /**

   2:  * clk_register - allocate a new clock, register it and return an opaque cookie

   3:  * @dev: device that is registering this clock

   4:  * @hw: link to hardware-specific clock data

   5:  *

   6:  * clk_register is the primary interface for populating the clock tree with new

   7:  * clock nodes.  It returns a pointer to the newly allocated struct clk which

   8:  * cannot be dereferenced by driver code but may be used in conjuction with the

   9:  * rest of the clock API.  In the event of an error clk_register will return an

  10:  * error code; drivers must test for an error code after calling clk_register.

  11:  */

  12: struct clk *clk_register(struct device *dev, struct clk_hw *hw);

  13: struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw);

  14:  

  15: void clk_unregister(struct clk *clk);

  16: void devm_clk_unregister(struct device *dev, struct clk *clk);

这些API比较简单(复杂的是怎么填充struct clk_hw变量),register接口接受一个填充好的struct clk_hw指针,将它转换为sruct clk结构,并根据parent的名字,添加到clock tree中。

不过,clock framework所做的远比这周到,它基于clk_register,又封装了其它接口,使clock provider在注册clock时,连struct clk_hw都不需要关心,而是直接使用类似人类语言的方式,下面继续。

3.2.2 clock分类及register

根据clock的特点,clock framework将clock分为fixed rate、gate、devider、mux、fixed factor、composite六类,每一类clock都有相似的功能、相似的控制方式,因而可以使用相同的逻辑s,统一处理,这充分体现了面向对象的思想。

1)fixed rate clock

这一类clock具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类clock。

可以直接通过DTS配置的方式支持,clock framework core能直接从DTS中解出clock信息,并自动注册到kernel,不需要任何driver支持。

clock framework使用struct clk_fixed_rate结构抽象这一类clock,另外提供了一个接口,可以直接注册fixed rate clock,如下:

/**
 * struct clk_fixed_rate - fixed-rate clock
 * @hw:		handle between common and hardware-specific interfaces
 * @fixed_rate:	constant frequency of clock
 * @fixed_accuracy: constant accuracy of clock in ppb (parts per billion)
 * @flags:	hardware specific flags
 *
 * Flags:
 * * CLK_FIXED_RATE_PARENT_ACCURACY - Use the accuracy of the parent clk
 *                                    instead of what's set in @fixed_accuracy.
 */
struct clk_fixed_rate {
	struct		clk_hw hw;
	unsigned long	fixed_rate;
	unsigned long	fixed_accuracy;
	unsigned long	flags;
};

#define CLK_FIXED_RATE_PARENT_ACCURACY	BIT(0)

extern const struct clk_ops clk_fixed_rate_ops;
struct clk_hw *__clk_hw_register_fixed_rate(struct device *dev,
		struct device_node *np, const char *name,
		const char *parent_name, const struct clk_hw *parent_hw,
		const struct clk_parent_data *parent_data, unsigned long flags,
		unsigned long fixed_rate, unsigned long fixed_accuracy,
		unsigned long clk_fixed_flags, bool devm);
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
		const char *parent_name, unsigned long flags,
		unsigned long fixed_rate);
clock provider一般不需要直接使用struct clk_fixed_rate结构,因为clk_register_fixed_rate接口是非常方便的。

clk_register_fixed_rate接口以clock name、parent name、fixed_rate为参数,创建一个具有固定频率的clock,该clock的clk_ops也是clock framework提供的,不需要provider关心;

如果使用DTS的话,clk_register_fixed_rate都不需要,直接在DTS中配置即可,后面会说明。

2)gate clock

这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:

    需要提供的参数包括:

    name,clock的名称;

    parent_name,parent clock的名称,没有的话可留空;

    flags,可参考3.1中的说明;

    reg,控制该clock开关的寄存器地址(虚拟地址);

    bit_idx,控制clock开关的bit位(是1开,还是0开,可通过下面gate特有的flag指定);

    clk_gate_flags,gate clock特有的flag,当前只有一种:CLK_GATE_SET_TO_DISABLE,clock开关控制的方式,如果置位,表示写1关闭clock,反之亦然;

    lock,如果clock开关时需要互斥,可提供一个spinlock。

3)divider clock

这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面两个接口注册:

/**
 * clk_register_divider - register a divider clock with the clock framework
 * @dev: device registering this clock
 * @name: name of this clock
 * @parent_name: name of clock's parent
 * @flags: framework-specific flags
 * @reg: register address to adjust divider
 * @shift: number of bits to shift the bitfield
 * @width: width of the bitfield
 * @clk_divider_flags: divider-specific flags for this clock
 * @lock: shared register lock for this clock
 */
#define clk_register_divider(dev, name, parent_name, flags, reg, shift, width, \
			     clk_divider_flags, lock)			       \
	clk_register_divider_table((dev), (name), (parent_name), (flags),      \
				   (reg), (shift), (width),		       \
				   (clk_divider_flags), NULL, (lock))
- `dev`:注册此时钟的设备。
- `name`:该时钟的名称。
- `parent_name`:时钟的父时钟名称。
- `flags`:特定于框架的标志。
- `reg`:调整分频器的寄存器地址。
- `shift`:需要移位的位数。
- `width`:位域的宽度。
- `clk_divider_flags`:该时钟的分频器特定标志。
- `lock`:用于此时钟的共享寄存器锁。

struct clk *clk_register_divider_table(struct device *dev, const char *name,
		const char *parent_name, unsigned long flags,
		void __iomem *reg, u8 shift, u8 width,
		u8 clk_divider_flags, const struct clk_div_table *table,
		spinlock_t *lock);


    该接口用于注册分频比不规则的clock,和上面接口比较,差别在于divider值和寄存器值得对应关系由一个table决定,该table的原型为:

    struct clk_div_table {
            unsigned int    val;
            unsigned int    div;
    };

    其中val表示寄存器值,div表示分频值,它们的关系也可以通过clk_divider_flags改变。

4)mux clock

这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面两个接口注册:

#define clk_register_mux(dev, name, parent_names, num_parents, flags, reg,    \
			 shift, width, clk_mux_flags, lock)		      \
	clk_register_mux_table((dev), (name), (parent_names), (num_parents),  \
			       (flags), (reg), (shift), BIT((width)) - 1,     \
			       (clk_mux_flags), NULL, (lock))


    该接口可注册mux控制比较规则的clock(类似divider clock):

    parent_names,一个字符串数组,用于描述所有可能的parent clock;

    num_parents,parent clock的个数;

    reg、shift、width,选择parent的寄存器、偏移、宽度,默认情况下,寄存器值为0时,对应第一个parent,依此类推。如有例外,可通过下面的flags,以及另外一个接口实现;

    clk_mux_flags,mux clock特有的flag:

            CLK_MUX_INDEX_ONE,寄存器值不是从0开始,而是从1开始;
            CLK_MUX_INDEX_BIT,寄存器值为2的幂。

   1: struct clk *clk_register_mux_table(struct device *dev, const char *name,

   2:                 const char **parent_names, u8 num_parents, unsigned long flags,

   3:                 void __iomem *reg, u8 shift, u32 mask,

   4:                 u8 clk_mux_flags, u32 *table, spinlock_t *lock);

5)fixed factor clock

这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调。

可通过下面接口注册:

   1: struct clk *clk_register_fixed_factor(struct device *dev, const char *name,

   2:                 const char *parent_name, unsigned long flags,

   3:                 unsigned int mult, unsigned int div);

另外,这一类接口和fixed rateclock类似,不需要提供driver,只需要配置dts即可。

6)composite clock

顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:

struct clk *clk_register_composite(struct device *dev, const char *name,
		const char * const *parent_names, int num_parents,
		struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
		struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
		struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
		unsigned long flags);
看着有点复杂,但理解了上面1~5类clock,这里就只剩下苦力了,耐心一点,就可以了。

3.2.3 DTS相关的API

再回到第2章DTS相关的介绍,clock driver使用一个DTS node描述一个clock provider,而clock consumer则会使用类似“clocks = <&clock 32>, <&clock 45>;”的形式引用,clock framework会自行把这些抽象的数字转换成实际的struct clk结构,怎么做的呢?肯定离不开clock provider的帮助。

3.2.1和3.2.2小节所描述的regitser接口,负责把clocks抽象为一个一个的struct clock,与此同时,clock provider需要把这些struct clk结构保存起来,并调用clock framework的接口,将这些对应信息告知framework的OF模块,这样才可以帮助将clock consumer的DTS描述转换为struct clk结构。该接口如下:

   1: int of_clk_add_provider(struct device_node *np,

   2:                         struct clk *(*clk_src_get)(struct of_phandle_args *args,

   3:                                                    void *data),

   4:                         void *data);

对于常用的one cell clock provider(第2章的例子),clock framework core提供一个默认的会调用函数,如下:

   1: struct clk_onecell_data {

   2:         struct clk **clks;

   3:         unsigned int clk_num;

   4: };

   5: struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data);

其中data指针为struct clk_onecell_data结构,该结构提供了clk指针和clk_num的对应,clock provider在regitser clocks时,同时维护一个clk和num对应的数组,并调用of_clk_add_provider接口告知clock framework core即可。

4. 使用clock framework编写clock驱动的步骤

编写clock driver的步骤大概如下:

1)分析硬件的clock tree,按照上面所描述的分类,讲这些clock分类。

2)将clock tree在DTS中描述出来,需要注意以下几2点:

    a)对于fixed rate clocks,.compatible固定填充"fixed-clock",并提供"clock-frequency"和"clock-output-names"关键字。之后不需要再driver中做任何处理,clock framework core会帮我们搞定一切。

    b)同样,对于fixed factor clock,.compatible为"fixed-factor-clock",并提供"clock-div"、"clock-mult"和"clock-output-names"关键字。clock framework core会帮我们搞定一切。

    切记,尽量利用kernel已有资源,不要多写一行代码,简洁的就是美的!

3)对于不能由clock framework core处理的clock,需要在driver中使用struct of_device_id进行匹配,并在初始化时,调用OF模块,查找所有的DTS匹配项,并执行合适的regitser接口,注册clock。

4)注册clock的同时,将返回的struct clk指针,保存在一个数组中,并调用of_clk_add_provider接口,告知clock framework core。

5)最后,也是最重要的一点,多看kernel源代码,多模仿,多抄几遍,什么都熟悉了!

<think>好的,我现在要处理用户关于Linux内核4.19.35版本中IMX6ULL SOC的CCM模块的问题。用户需要了解CCM子系统的相关内容,包括子系统、驱动程序、设备树配置等,并希望获得一个教程式的回答。我需要先理解用户的具体需求,然后根据提供的引用内容整理相关信息。 首先,用户提到的IMX6ULL是NXP的一款处理器,CCM(Clock Control Module)是时钟控制模块,负责管理和分配各个外设的时钟。在内核中,CCM的配置通常涉及设备树、驱动程序和相关的子系统。用户引用了三个参考资料,其中引用1提到了设备树文件的路径和作用,引用2提到了启动内核时卡住的问题,引用3详细描述了CCM的时钟设置,包括分频系数、PLL配置等。 接下来,我需要结合这些引用内容,梳理出CCM模块在设备树中的配置方法、驱动程序的实现以及子系统的结构。根据引用3,CCM的时钟设置包括设置分频寄存器、选择PLL源等,这可能涉及到设备树中的时钟节点配置。引用1指出设备树文件的位置在`ebf-buster-linux/arch/arm/boot/dts/imx6ull-mmc-npi.dts`,用户可能需要查看这个文件中的相关节点,比如`clks`节点。 此外,用户提供的引用2中提到了启动时卡在Starting kernel的问题,这可能与设备树配置错误有关,特别是时钟配置不正确导致内核无法正确初始化。因此,在教程中需要强调设备树配置的正确性,以及如何调试相关问题。 接下来,我需要整理CCM子系统的结构。Linux内核中的时钟子系统通常由Common Clock Framework(CCF)来管理,CCM驱动应基于此框架实现。驱动程序部分可能涉及到注册时钟提供者(clock provider),定义各个时钟的初始化数据,并在设备树中指定时钟源和父时钟。 关于设备树配置,用户需要知道如何在设备树中定义时钟控制器节点,如何为各外设分配时钟,以及如何引用这些时钟。例如,在设备树中,CCM节点可能包含多个时钟定义,每个外设的节点会通过`clocks`属性引用相应的时钟。 另外,引用3中提到PLL2、PLL3和PLL7的固定频率,以及如何设置分频系数,这些信息需要体现在设备树或驱动程序的初始化代码中。可能需要在设备树中配置某些时钟的父节点和频率参数,或者在驱动程序中设置寄存器值。 最后,用户可能需要一个步骤指南,指导如何在内核中配置CCM,包括设备树的修改、驱动的加载以及调试方法。同时,需要提醒用户注意常见问题,比如时钟配置错误导致的启动失败,以及如何通过读取寄存器或调试信息来排查问题。 总结来说,回答应涵盖以下几个部分: 1. CCM子系统在内核中的结构,特别是与Common Clock Framework的关系。 2. 设备树中CCM相关节点的配置方法,包括时钟控制器节点和各外设的时钟引用。 3. 驱动程序如何与设备树交互,初始化时钟设置。 4. 常见问题及解决方法,如启动失败时的调试技巧。 需要确保引用提供的资料,特别是设备树路径、时钟设置细节以及启动问题的分析。同时,生成的相关问题应围绕CCM配置、设备树调试、时钟问题排查等方面。</think>### Linux Kernel 4.19.35 IMX6ULL CCM模块配置教程 #### 1. CCM子系统概述 CCM(Clock Control Module)是IMX6ULL的时钟控制模块,负责管理CPU、外设和总线时钟的分配与分频。在Linux内核中,其驱动基于**Common Clock Framework (CCF)** 实现,通过设备树描述硬件资源[^1]。关键功能包括: - PLL配置(如PLL2固定为528MHz,PLL3/PLL7固定为480MHz)[^3] - 分频系数设置(如`DIV_SELECT`和`cacrr`寄存器操作) - 时钟源切换(如`ARM_PLL`与`ARM_ROOT`的关联) #### 2. 设备树配置 设备树文件路径为: `ebf-buster-linux/arch/arm/boot/dts/imx6ull-mmc-npi.dts`[^1] ##### 2.1 时钟控制器定义 ```dts clocks { #address-cells = <1>; #size-cells = <0>; osc24m: osc24m { compatible = "fixed-clock"; clock-frequency = <24000000>; #clock-cells = <0>; }; // 其他PLL和分频器定义... }; &ccm { assigned-clocks = <&clks IMX6UL_CLK_PLL3_PFD1>; assigned-clock-rates = <454736000>; // 分频设置示例 clocks = <&osc24m>, <&clks IMX6UL_CLK_OSC>; clock-names = "osc", "ipp_di0"; }; ``` 关键参数: - `assigned-clocks`指定需动态配置的时钟 - `assigned-clock-rates`设置目标频率 - 通过`clock-names`标识时钟源层级关系 #### 3. 驱动程序分析 驱动文件位于: `drivers/clk/imx/clk-imx6ul.c` ##### 3.1 时钟注册流程 ```c static void __init imx6ul_clocks_init(struct device_node *ccm_node) { // 初始化基础时钟源 clks[IMX6UL_CLK_DUMMY] = imx_clk_fixed("dummy", 0); clks[IMX6UL_CLK_CKIL] = of_clk_get_by_name(ccm_node, "ckil"); // 配置PLL2分频 clks[IMX6UL_CLK_PLL2] = imx_clk_pllv3(...); clks[IMX6UL_CLK_PLL2_BUS] = imx_clk_gate("pll2_bus", "pll2", ccm_base + CCM_CCDR, 16); // 注册到CCF框架 for (i = 0; i < ARRAY_SIZE(clks); i++) clk_register_clkdev(clks[i], NULL, __clk_get_name(clks[i])); } ``` #### 4. 调试与常见问题 **问题现象**:启动卡在`Starting kernel...`[^2] **排查步骤**: 1. 检查设备树中CCM节点是否与硬件匹配 2. 使用`clk_summary`查看时钟分配: ```bash cat /sys/kernel/debug/clk/clk_summary ``` 3. 验证PLL配置寄存器值是否符合预期(参考引用3的分频逻辑) #### 5. 关键寄存器操作 根据引用3的配置流程: 1. 设置`CCM_CACRR`寄存器分频系数 2. 通过`CCM_CCSR`切换ARM内核时钟源 3. 检查`CCM_CDHIPR`寄存器状态位(如bit31是否置1) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值