一、CCF子系统概述
计算机硬件通过许多时钟设备提供时钟。从进入系统内部的 cpu core 时钟开始,使用数十种时钟,如 timer、i2c、uart 等。每个 ARM SoC都通过多个时钟设备驱动程序设置时钟,硬件千差万别。
linux内核采用了一个时钟子系统来解决这个问题。在linux 3.4之后的版本,linux内核时钟子系统又叫通用时钟框架子系统,简称CCF子系统(common clock framework)。
CCF子系统是用来管理系统clock资源的子系统,根据职能,可以分为三个部分:
- 向其它驱动提供操作clock的通用API,屏蔽其中的硬件特性;
- 实现clock控制的通用逻辑,这部分和硬件无关,向上提供统一的操作接口,向下提供底层platform操作的接口;
- 将和硬件相关的clock控制逻辑封装成操作函数集,交由底层的platform开发者实现;比如选择哪个时钟源,设置输出的频率等;
1.1 时钟种类说明
在s3c2410时钟章节中我们曾经介绍了其复杂的时钟结构,下图是一个简单的clock结构图:
如上图所示,时钟大致可以分为如下几种:
- 提供基础时钟源的晶振(有源晶振、无源晶振);
- 用于倍频的PLL(锁相环,Phase Locked Loop);
- 用于分频的divider
- 用于多路选择的mux;
- 用于clock enable控制的与门;
- 各个时钟模块的组合;
虽然每一个SOC的时钟控制器的寄存器地址空间和具体结构略有差异,但是时钟控制器的组成目前来说,基本都是这个样子。
在CCF子系统的抽象中,将这六种不同类型的时钟均抽象出来:
- 定义不同的结构体,比如struck clk_gate、struct clk_mux等,他们都是对struct clk_hw的封装;如果你学过OOP的话,可以把 clk_hw看到是时钟基类,而把clk_gate、clk_mux看做具体的子类;
- 提供了单独的时钟注册函数,比如clk_register_gate、clk_register_mux等,也就是对clk_register函数的封装体;
- 针对硬件时钟的操作接口,也抽象了对应的结构体struct clk_ops,包含时钟的使能接口、时钟频率的修改接口等等;
在针对上述所说的不同种类的时钟,其并不需要实现所有struct clk_ops中定义的接口,如下图所示:
- 针对时钟使能的与门电路而言,仅需要实现enable、disable、is_enable接口即可;
- 针对多路时钟选择的mux而言,则需要实现父时钟的设置及获取的接口set_parent、get_parent等;
- 对于倍频、分频而言,则需要实现时钟频率相关的接口set_rate、recalc_rate等;
1.2 CCF子系统框图
下图是CCF子系统的架构图,其对各设备驱动提供统一时钟操作的接口,实现为:
- 根据硬件设备名获取其对应的输入时钟:
- 配置时钟(使能时钟、时钟频率配置);
在CCF内部,针对每一个时钟,抽象基类结构为struct clk_hw,该结构体中包含每一个硬件时钟的操作接口struct clk_ops,当时钟驱动程序完成时钟操作接口的定义,并调用clk_register完成注册后,则CCF即可借助clk_hw的clk_ops完成对时钟的参数配置等操作。
在linux内核中称clock driver为clock provider,相应的clock的使用者一般也是我们设备驱动程序称为clock consumer。
二、CCF子系统基本数据结构
CCF子系统抽象了数据结构struck clk、struct clk_hw、struct clk_ops:
- clk_hw是对一个时钟的抽象基类(具体的子类,根据时钟种类的不同定义不同,比如clk_gate、clk_mux,这里就不一一介绍了);
- clk_ops是时钟的操作接口的抽象;
- struct clk:对于驱动开发来说,struct clk只是访问时钟的一个句柄,有了它,驱动开发就可以对时钟进行配置;其中sruct clk_hw结构包含了struck clk成员;
2.1 struct clk(drivers/clk/clk.c)
一个系统的时钟树结构是固定的,因此时钟的数目和用途也是固定的,我们以上面我们的案例图为例,假设其为一个完整的时钟系统,它的时钟包括:osc_clk、pll1_clk、pll2_clk、pll3_clk、hw1_clk、hw2_clk、hw3_clk。
我们完全可以通过名字,抽象这7个clock,进行开/关、rate调整等操作。但这样做有一个缺点:不能很好的处理时钟之间的级联关系,如hw2_clk和hw3_clk都关闭后,pll2_clk才能关闭。因此就引入struct clk结构,以链表的形式维护这种关系。
系统的struct clk是在由clock driver在系统启动时初始化完毕的,我们需要访问某个时钟时,只需要获取它对应的struct clk结构即可,怎么获取呢?通过名字索引就可以。
由于设备(由struct device表示)对应的clock(由struct clk表示)也是固定的啊,可不可以找到设备就能找到clock?可以,不过需要借助device tree。
struct clk定义在drivers/clk/clk.c:
struct clk { struct clk_core *core; struct device *dev; // 关联的设备 const char *dev_id; // 来自device->name const char *con_id; // 来自时钟别名 unsigned long min_rate; unsigned long max_rate; unsigned int exclusive_count; struct hlist_node clks_node; // 哈希链表数据节点 };
2.2 struck clk_core(drivers/clk/clk.c)
struct clk_core { const char *name; const struct clk_ops *ops; struct clk_hw *hw; struct module *owner; struct device *dev; struct device_node *of_node; struct clk_core *parent; struct clk_parent_map *parents; u8 num_parents; u8 new_parent_index; unsigned long rate; unsigned long req_rate; unsigned long new_rate; struct clk_core *new_parent; struct clk_core *new_child; unsigned long flags; bool orphan; bool rpm_enabled; unsigned int enable_count; unsigned int prepare_count; unsigned int protect_count; unsigned long min_rate; unsigned long max_rate; unsigned long accuracy; int phase; struct clk_duty duty; struct hlist_head children; struct hlist_node child_node; struct hlist_head clks; // 哈希双向链表头节点 unsigned int notifier_count; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct hlist_node debug_node; #endif struct kref ref; };
2.3 struct clk_ops(include/linux/clk-provider.h)
struct clk_ops定义在include/linux/clk-provider.h文件中:
/** * 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. * 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 not used by any of the basic clock types. * Please consider other ways of solving initialization problems * before using this callback, as its use is discouraged. * * @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. * * * 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); void (*init)(struct clk_hw *hw); void (*debug_init)(struct clk_hw *hw, struct dentry *dentry); };
2.4 struct clk_hw(include/linux/clk-provider.h)
/** * 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. */ struct clk_hw { struct clk_core *core; struct clk *clk; // 由CCF维护,并且提供给用户使用 const struct clk_init_data *init; // 描述该clk的静态属性 };
2.5 struck clk_init_data(include/linux/clk-provider.h)
/** * struct clk_init_data - holds init data that's common to all clocks and is * shared between the clock provider and the common clock framework. * * @name: clock name * @ops: operations this clock supports * @parent_names: array of string names for all possible parents * @parent_data: array of parent data for all possible parents (when some * parents are external to the clk controller) * @parent_hws: array of pointers to all possible parents (when all parents * are internal to the clk controller) * @num_parents: number of possible parents * @flags: framework-level hints and quirks */ struct clk_init_data { const char *name; // 时钟的名字 const struct clk_ops *ops; // 时钟的操作函数 /* Only one of the following three should be assigned */ const char * const *parent_names; // 时钟的父时钟名字列表 const struct clk_parent_data *parent_data; // 时钟的父时钟数据列表 const struct clk_hw **parent_hws; // 时钟的父时钟列表 u8 num_parents; // 父时钟的个数 unsigned long flags; };
2.6 数据结构之间的关系
为了更加清晰的了解CCF子系统各个数据结构作用和之间的关系,我们绘制其关系图,如下:
不知道你没有发现CCF子系统的数据结构中没有使用设备驱动模型,没包含struct device类型的变量。这应该是clk provider主要为系统提供时钟,而一些系统clk的初始化及使能会早于设备驱动模型的初始化,因此没有使用设备驱动模型,但是还是使用了引用计数功能的。
三、CCF时钟操作API
3.1 获取clock
驱动开发人员在进行操作设备的时钟之前,首先需要获取和该时钟关联的struct clk指针,获取的接口如下:
struct clk *clk_get(struct device *dev, const char *id); struct clk *devm_clk_get(struct device *dev, const char *id); void clk_put(struct clk *clk); void devm_clk_put(struct device *dev, struct clk *clk); struct clk *clk_get_sys(const char *dev_id, const char *con_id); struct clk *of_clk_get(struct device_node *np, int index); struct clk *of_clk_get_by_name(struct device_node *np, const char *name); struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);
其中clk_get,根据设备指针或者时钟别名参数查找时钟;dev和id任意一个可以为空:
- 如果id为空,则必须有device tree的支持才能获得device对应的clk;
- 时钟别名是在时钟初始化的时候调用samsung_clk_register_alias注册的,每个时钟别名对应一个clock lookup;
devm_clk_get,和clk_get一样,只是使用了device resource management,可以自动释放;
clk_put、devm_clk_put,get的反向操作,一般和对应的get API成对调用;
clk_get_sys,类似clk_get,不过使用device的name替代device结构;
of_clk_get、of_clk_get_by_name、of_clk_get_from_provider,device tree相关的接口,直接从相应的DTS node中,以index、name等为索引,获取clk;
3.2 clock配置
int clk_prepare(struct clk *clk) void clk_unprepare(struct clk *clk) static inline int clk_enable(struct clk *clk) static inline void clk_disable(struct clk *clk) static inline unsigned long clk_get_rate(struct clk *clk) static inline int clk_set_rate(struct clk *clk, unsigned long rate) static inline long clk_round_rate(struct clk *clk, unsigned long rate) static inline int clk_set_parent(struct clk *clk, struct clk *parent) static inline struct clk *clk_get_parent(struct clk *clk) static inline int clk_prepare_enable(struct clk *clk) static inline void clk_disable_unprepare(struct clk *clk)
clk_enable/clk_disable,使能/禁止clock;不会睡眠
clk_prepare/clk_unprepare,使能clock前的准备工作/禁止clock后的善后工作;可能会睡眠
clk_get_rate/clk_set_rate/clk_round_rate,clock频率的获取和设置,其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误。如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值;
clk_set_parent/clk_get_parent设置/获取clock的parent clock;
clk_prepare_enable,将clk_prepare和clk_enable组合起来,一起调用;
clk_disable_unprepare,将clk_disable和clk_unprepare组合起来,一起调用;
prepare/unprepare,enable/disable的说明:
- 这两套API的本质,是把clock的使能/停止分为atomic和non-atomic两个阶段,以方便实现和调用。因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:
- 一是告诉底层的clock driver,请把可能引起睡眠的操作,放到prepare/unprepare中实现,一定不能放到enable/disable中;
- 二是提醒上层使用clock的driver,调用prepare/unprepare接口时可能会睡眠哦,千万不能在atomic上下文(例如中断处理中)调用,而调用enable/disable接口则可放心;
- 另外,clock的开关为什么需要睡眠呢?这里举个例子,例如enable PLL clk,在启动PLL后,需要等待它稳定。而PLL的稳定时间是很长的,这段时间要把CPU交出(进程睡眠),不然就会浪费CPU;
最后,为什么会有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果调用者能确保是在non-atomic上下文中调用,就可以顺序调用prepare/enable、disable/unprepared,为了简单,framework就帮忙封装了这两个接口。
3.3 clock注册接口
struct clk *clk_register(struct device *dev, struct clk_hw *hw); struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw); void clk_unregister(struct clk *clk); void devm_clk_unregister(struct device *dev, struct clk *clk);
通过clk_register接口可以将时钟struck clk_hw注册到内核,内核代码会将他们 转换为struct clk,并以树的形式组织在一起。
根据时钟种类的不同,CCF子系统将时钟分为fixed rate、gate、devider、mux、fixed factor、composite clock,每一类时钟都有相似的功能、相似的控制方式,因而可以使用相同的逻辑,统一处理,这充分体现了面向对象的思想。
3.3.1 fixed rate clock
这一类时钟具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类时钟、
在include/linux/clk-provider.h文件中定义struct clk_fixed_rate结构抽象这一类clock,另外提供了一些接口,可以直接注册fixed rate clock,如下:
/* * DOC: Basic clock implementations common to many platforms * * Each basic clock hardware type is comprised of a structure describing the * clock hardware, implementations of the relevant callbacks in struct clk_ops, * unique flags for that hardware type, a registration function and an * alternative macro for static initialization */ /** * struct clk_fixed_rate - fixed-rate clock * @hw: handle between common and hardware-specific interfaces * @fixed_rate: constant frequency of clock */ struct clk_fixed_rate { struct clk_hw hw; unsigned long fixed_rate; unsigned long fixed_accuracy; }; #define to_clk_fixed_rate(_hw) container_of(_hw, struct clk_fixed_rate, hw) extern const struct clk_ops clk_fixed_rate_ops; 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_hw *clk_hw_register_fixed_rate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate); struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate, unsigned long fixed_accuracy); void clk_unregister_fixed_rate(struct clk *clk); struct clk_hw *clk_hw_register_fixed_rate_with_accuracy(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate, unsigned long fixed_accuracy); void clk_hw_unregister_fixed_rate(struct clk_hw *hw); void of_fixed_clk_setup(struct device_node *np);
3.3.2 gate clock
这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:
/** * struct clk_gate - gating clock * * @hw: handle between common and hardware-specific interfaces * @reg: register controlling gate * @bit_idx: single bit controlling gate * @flags: hardware-specific flags * @lock: register lock * * Clock which can gate its output. Implements .enable & .disable * * Flags: * CLK_GATE_SET_TO_DISABLE - by default this clock sets the bit at bit_idx to * enable the clock. Setting this flag does the opposite: setting the bit * disable the clock and clearing it enables the clock * CLK_GATE_HIWORD_MASK - The gate settings are only in lower 16-bit * of this register, and mask of gate bits are in higher 16-bit of this * register. While setting the gate bits, higher 16-bit should also be * updated to indicate changing gate bits. * CLK_GATE_BIG_ENDIAN - by default little endian register accesses are used for * the gate register. Setting this flag makes the register accesses big * endian. */ struct clk_gate { struct clk_hw hw; void __iomem *reg; // 控制该clock开关的寄存器地址(虚拟地址) u8 bit_idx; // 控制clock开关的bit位 u8 flags; spinlock_t *lock; //如果clock开关是独享的,可以使用自旋锁 }; #define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw) #define CLK_GATE_SET_TO_DISABLE BIT(0) #define CLK_GATE_HIWORD_MASK BIT(1) #define CLK_GATE_BIG_ENDIAN BIT(2) extern const struct clk_ops clk_gate_ops; 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_hw *clk_hw_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); void clk_unregister_gate(struct clk *clk); void clk_hw_unregister_gate(struct clk_hw *hw); int clk_gate_is_enabled(struct clk_hw *hw);
3.3.3 driver clock
这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面接口注册:
/** * struct clk_divider - adjustable divider clock * * @hw: handle between common and hardware-specific interfaces * @reg: register containing the divider * @shift: shift to the divider bit field * @width: width of the divider bit field * @table: array of value/divider pairs, last entry should have div = 0 * @lock: register lock * * Clock with an adjustable divider affecting its output frequency. Implements * .recalc_rate, .set_rate and .round_rate * * Flags: * CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the * register plus one. If CLK_DIVIDER_ONE_BASED is set then the divider is * the raw value read from the register, with the value of zero considered * invalid, unless CLK_DIVIDER_ALLOW_ZERO is set. * CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from * the hardware register * CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors. For dividers which have * CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor. * Some hardware implementations gracefully handle this case and allow a * zero divisor by not modifying their input clock * (divide by one / bypass). * CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit * of this register, and mask of divider bits are in higher 16-bit of this * register. While setting the divider bits, higher 16-bit should also be * updated to indicate changing divider bits. * CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded * to the closest integer instead of the up one. * CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should * not be changed by the clock framework. * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED * except when the value read from the register is zero, the divisor is * 2^width of the field. * CLK_DIVIDER_BIG_ENDIAN - By default little endian register accesses are used * for the divider register. Setting this flag makes the register accesses * big endian. */ struct clk_divider { struct clk_hw hw; void __iomem *reg; // 控制clock分频比的寄存器地址 u8 shift; // 控制分频比的bit在寄存器的便宜 u8 width; // 控制分频比的bit个数 u8 flags; const struct clk_div_table *table; spinlock_t *lock; }; #define clk_div_mask(width) ((1 << (width)) - 1) #define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw) #define CLK_DIVIDER_ONE_BASED BIT(0) #define CLK_DIVIDER_POWER_OF_TWO BIT(1) #define CLK_DIVIDER_ALLOW_ZERO BIT(2) #define CLK_DIVIDER_HIWORD_MASK BIT(3) #define CLK_DIVIDER_ROUND_CLOSEST BIT(4) #define CLK_DIVIDER_READ_ONLY BIT(5) #define CLK_DIVIDER_MAX_AT_ZERO BIT(6) #define CLK_DIVIDER_BIG_ENDIAN BIT(7) extern const struct clk_ops clk_divider_ops; extern const struct clk_ops clk_divider_ro_ops; unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate, unsigned int val, const struct clk_div_table *table, unsigned long flags, unsigned long width); long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent, unsigned long rate, unsigned long *prate, const struct clk_div_table *table, u8 width, unsigned long flags); long divider_ro_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent, unsigned long rate, unsigned long *prate, const struct clk_div_table *table, u8 width, unsigned long flags, unsigned int val); int divider_get_val(unsigned long rate, unsigned long parent_rate, const struct clk_div_table *table, u8 width, unsigned long flags); 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); struct clk_hw *clk_hw_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); 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); struct clk_hw *clk_hw_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); void clk_unregister_divider(struct clk *clk); void clk_hw_unregister_divider(struct clk_hw *hw);
其中struct clk_div_table:
struct clk_div_table { unsigned int val; // 寄存器值 unsigned int div; // 分频值 };
3.3.4 mux lock
这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面接口注册:
/** * struct clk_mux - multiplexer clock * * @hw: handle between common and hardware-specific interfaces * @reg: register controlling multiplexer * @table: array of register values corresponding to the parent index * @shift: shift to multiplexer bit field * @mask: mask of mutliplexer bit field * @flags: hardware-specific flags * @lock: register lock * * Clock with multiple selectable parents. Implements .get_parent, .set_parent * and .recalc_rate * * Flags: * CLK_MUX_INDEX_ONE - register index starts at 1, not 0 * CLK_MUX_INDEX_BIT - register index is a single bit (power of two) * CLK_MUX_HIWORD_MASK - The mux settings are only in lower 16-bit of this * register, and mask of mux bits are in higher 16-bit of this register. * While setting the mux bits, higher 16-bit should also be updated to * indicate changing mux bits. * CLK_MUX_READ_ONLY - The mux registers can't be written, only read in the * .get_parent clk_op. * CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired * frequency. * CLK_MUX_BIG_ENDIAN - By default little endian register accesses are used for * the mux register. Setting this flag makes the register accesses big * endian. */ struct clk_mux { struct clk_hw hw; void __iomem *reg; u32 *table; u32 mask; u8 shift; u8 flags; spinlock_t *lock; }; #define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) #define CLK_MUX_INDEX_ONE BIT(0) #define CLK_MUX_INDEX_BIT BIT(1) #define CLK_MUX_HIWORD_MASK BIT(2) #define CLK_MUX_READ_ONLY BIT(3) /* mux can't be changed */ #define CLK_MUX_ROUND_CLOSEST BIT(4) #define CLK_MUX_BIG_ENDIAN BIT(5) extern const struct clk_ops clk_mux_ops; extern const struct clk_ops clk_mux_ro_ops; struct clk *clk_register_mux(struct device *dev, const char *name, const char * const *parent_names, u8 num_parents, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_mux_flags, spinlock_t *lock); struct clk_hw *clk_hw_register_mux(struct device *dev, const char *name, const char * const *parent_names, u8 num_parents, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_mux_flags, spinlock_t *lock); struct clk *clk_register_mux_table(struct device *dev, const char *name, const char * const *parent_names, u8 num_parents, unsigned long flags, void __iomem *reg, u8 shift, u32 mask, u8 clk_mux_flags, u32 *table, spinlock_t *lock); struct clk_hw *clk_hw_register_mux_table(struct device *dev, const char *name, const char * const *parent_names, u8 num_parents, unsigned long flags, void __iomem *reg, u8 shift, u32 mask, u8 clk_mux_flags, u32 *table, spinlock_t *lock); int clk_mux_val_to_index(struct clk_hw *hw, u32 *table, unsigned int flags, unsigned int val); unsigned int clk_mux_index_to_val(u32 *table, unsigned int flags, u8 index); void clk_unregister_mux(struct clk *clk); void clk_hw_unregister_mux(struct clk_hw *hw);
3.3.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等回调。
void of_fixed_factor_clk_setup(struct device_node *node); /** * struct clk_fixed_factor - fixed multiplier and divider clock * * @hw: handle between common and hardware-specific interfaces * @mult: multiplier * @div: divider * * Clock with a fixed multiplier and divider. The output frequency is the * parent clock rate divided by div and multiplied by mult. * Implements .recalc_rate, .set_rate and .round_rate */ struct clk_fixed_factor { struct clk_hw hw; unsigned int mult; unsigned int div; }; #define to_clk_fixed_factor(_hw) container_of(_hw, struct clk_fixed_factor, hw) extern const struct clk_ops clk_fixed_factor_ops; struct clk *clk_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div); void clk_unregister_fixed_factor(struct clk *clk); struct clk_hw *clk_hw_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div); void clk_hw_unregister_fixed_factor(struct clk_hw *hw);
3.3.6 composite clock
顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:
/*** * struct clk_composite - aggregate clock of mux, divider and gate clocks * * @hw: handle between common and hardware-specific interfaces * @mux_hw: handle between composite and hardware-specific mux clock * @rate_hw: handle between composite and hardware-specific rate clock * @gate_hw: handle between composite and hardware-specific gate clock * @mux_ops: clock ops for mux * @rate_ops: clock ops for rate * @gate_ops: clock ops for gate */ struct clk_composite { struct clk_hw hw; struct clk_ops ops; struct clk_hw *mux_hw; struct clk_hw *rate_hw; struct clk_hw *gate_hw; const struct clk_ops *mux_ops; const struct clk_ops *rate_ops; const struct clk_ops *gate_ops; }; #define to_clk_composite(_hw) container_of(_hw, struct clk_composite, hw) 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); void clk_unregister_composite(struct clk *clk); struct clk_hw *clk_hw_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); void clk_hw_unregister_composite(struct clk_hw *hw);
3.4 其他接口
int clk_notifier_register(struct clk *clk, struct notifier_block *nb); int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
这两个notify接口,用于注册/注销 clock rate改变的通知。例如某个driver关心某个clock,期望这个clock的rate改变时,通知到自己,就可以注册一个notify。
四、usb主机控制器驱动回顾
我们首先来回顾一下,我们在之前的设备驱动源码分析中有没有接触到clock consumer相关的代码呢?
实际上是有的,不知道你有没有留意到,在linux驱动移植-usb主机控制器驱动中我们介绍名字为"s3c2410-ohci"的platform设备和驱动注册的时候,我们分析了ohci_hcd_s3c2410_driver驱动的probe函数:ohci_hcd_s3c2410_probe。
4.1 ohci_hcd_s3c2410_driver
static struct platform_driver ohci_hcd_s3c2410_driver = { .probe = ohci_hcd_s3c2410_probe, .remove = ohci_hcd_s3c2410_remove, .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "s3c2410-ohci", .pm = &ohci_hcd_s3c2410_pm_ops, .of_match_table = ohci_hcd_s3c2410_dt_ids, }, };
4.2 ohci_hcd_s3c2410_probe
ohci_hcd_s3c2410_probe的函数流程不是我们这一节的主要内容,有兴趣的去回顾一下linux驱动移植-usb主机控制器驱动。
/** * ohci_hcd_s3c2410_probe - initialize S3C2410-based HCDs * Context: !in_interrupt() * * Allocates basic resources for this USB host controller, and * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. * */ static int ohci_hcd_s3c2410_probe(struct platform_device *dev) { struct usb_hcd *hcd = NULL; struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev); int retval; s3c2410_usb_set_power(info, 1, 1); s3c2410_usb_set_power(info, 2, 1); hcd = usb_create_hcd(&ohci_s3c2410_hc_driver, &dev->dev, "s3c24xx"); // 分配主机控制器usb_hcd结构,并初始化,绑定hc_driver等 if (hcd == NULL) return -ENOMEM; hcd->rsrc_start = dev->resource[0].start; // usb主机控制器起始地址 hcd->rsrc_len = resource_size(&dev->resource[0]); // 映射内存长度 hcd->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]); // 物理地址映射到虚拟地址 if (IS_ERR(hcd->regs)) { retval = PTR_ERR(hcd->regs); goto err_put; } clk = devm_clk_get(&dev->dev, "usb-host"); // 获取usb-host时钟 if (IS_ERR(clk)) { dev_err(&dev->dev, "cannot get usb-host clockn"); retval = PTR_ERR(clk); goto err_put; } usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); // 获取usb-bus-host时钟 if (IS_ERR(usb_clk)) { dev_err(&dev->dev, "cannot get usb-bus-host clockn"); retval = PTR_ERR(usb_clk); goto err_put; } s3c2410_start_hc(dev, hcd); // 使能时钟 retval = usb_add_hcd(hcd, dev->resource[1].start, 0); // 向linux内核注册usb主机控制器驱动ohci_s3c2410_hc_driver,创建根hub设备,并注册到内核设备链表 if (retval != 0) goto err_ioremap; device_wakeup_enable(hcd->self.controller); return 0; err_ioremap: s3c2410_stop_hc(dev); err_put: usb_put_hcd(hcd); return retval; }
我们后面重点来分析下面两行代码:
clk = devm_clk_get(&dev->dev, "usb-host"); // 获取usb-host时钟 usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); // 获取usb-bus-host时钟
五、devm_clk_get函数分析
我们直接全局搜索该函数:
root@zhengyang:/work/sambashare/linux-5.2.8# grep "devm_clk_get(struct " * -nR drivers/clk/clk-devres.c:12:struct clk *devm_clk_get(struct device *dev, const char *id) include/linux/clk.h:381:struct clk *devm_clk_get(struct device *dev, const char *id); include/linux/clk.h:724:static inline struct clk *devm_clk_get(struct device *dev, const char *id)
函数定义位于drivers/clk/clk-devres.c文件,从这里我们也不难猜出linux内核时钟相关代码都是放在drivers/clk文件夹下的。
5.1 devm_clk_get
struct clk *devm_clk_get(struct device *dev, const char *id) { struct clk **ptr, *clk; ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); clk = clk_get(dev, id); if (!IS_ERR(clk)) { *ptr = clk; devres_add(dev, ptr); } else { devres_free(ptr); } return clk; }
devm_clk_get函数有两个参数:
- 第一个参数为linux内核设备结构指针struct *dev,可以通过设备名字获取时钟;
- 第二个参数是一个字符指针,用于指向一个字符串,比如“usb_host”,这里起到一个唯一标识的作用,用来指定我们需要设置硬件上的哪部分时钟;
这个函数调用了clk_get函数,其他的可以忽略。
5.2 clk_get
clk_get函数定义在drivers/clk/clkdev.c文件:
struct clk *clk_get(struct device *dev, const char *con_id) { const char *dev_id = dev ? dev_name(dev) : NULL; // 如果指定了dev,获取设备名称 struct clk_hw *hw; if (dev && dev->of_node) { // 使用设备树 hw = of_clk_get_hw(dev->of_node, 0, con_id); if (!IS_ERR(hw) || PTR_ERR(hw) == -EPROBE_DEFER) return clk_hw_create_clk(dev, hw, dev_id, con_id); } return __clk_get_sys(dev, dev_id, con_id); // 未使用设备树 }
如果使用了设备树,则会调用of_clk_get_hw函数,否则执行了 __clk_get_sys(NULL, NULL, con_id)。
5.3 使用设备树
如果我们使用了设备树,比如在dts配置了clock consumer:
mmc0:mmc0@0x12345678{ compatible = "xx,xx-mmc0"; ...... clocks = <&peri PERI_MCI0>;//指定mmc0的时钟来自PERI_MCI0 clocks-names = "mmc0"; //时钟名,调用devm_clk_get获取时钟时,可以传入该名字 ...... };
以mmc的设备节点为例,上述设备节点指定了时钟来自PERI_MCI0,并将所指定的时钟给它命名为"mmc0"。
当在驱动中使用API接口:
clk = devm_clk_get(&pdev->dev, NULL); // 或者devm_clk_get(&pdev->dev, "mmc0")
最终执行的是of_clk_get_hw函数,of_clk_get_hw函数定义在drivers/clk/clk.c:
struct clk_hw *of_clk_get_hw(struct device_node *np, int index, // index = 0 con_id="mmc0" const char *con_id) { int ret; struct clk_hw *hw; struct of_phandle_args clkspec; ret = of_parse_clkspec(np, index, con_id, &clkspec); // 重点 if (ret) return ERR_PTR(ret); hw = of_clk_get_hw_from_clkspec(&clkspec); of_node_put(clkspec.np); return hw; }
5.3.1 of_parse_clkspec
/* * Beware the return values when np is valid, but no clock provider is found. * If name == NULL, the function returns -ENOENT. * If name != NULL, the function returns -EINVAL. This is because * of_parse_phandle_with_args() is called even if of_property_match_string() * returns an error. */ static int of_parse_clkspec(const struct device_node *np, int index, const char *name, struct of_phandle_args *out_args) // name传入了con_id { int ret = -ENOENT; /* Walk up the tree of devices looking for a clock property that matches */ while (np) { /* * For named clocks, first look up the name in the * "clock-names" property. If it cannot be found, then index * will be an error code and of_parse_phandle_with_args() will * return -EINVAL. */ if (name) index = of_property_match_string(np, "clock-names", name); // 根据name获取index ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, out_args); if (!ret) break; if (name && index >= 0) break; /* * No matching clock found on this node. If the parent node * has a "clock-ranges" property, then we can try one of its * clocks. */ np = np->parent; if (np && !of_get_property(np, "clock-ranges", NULL)) break; index = 0; } return ret; }
如果指定了name,即devm_clk_get传入了第二个参数con_id,则会调用of_property_match_string函数,查询con_id在设备节点clock-names属性值中的位置,并返回对应索引号,索引从0开始;比如示例mmc0节点中的"mmc0"的索引为0;
调用of_parse_phandle_with_args函数,根据索引从clocks属性中获取时钟,比如示例mmc0节点中的0索引对应的时钟为<&peri PERI_MCI0>,如果时钟解析成功则返回0;
5.4 未使用设备树
在没有使用设备树的请求下,执行__clk_get_sys函数,函数定义如下:
static struct clk *__clk_get_sys(struct device *dev, const char *dev_id, const char *con_id) { struct clk_hw *hw = clk_find_hw(dev_id, con_id); return clk_hw_create_clk(dev, hw, dev_id, con_id); }
__clk_get_sys里面通过clk_find_hw函数;
5.4.1 clk_find_hw
struct clk_hw *clk_find_hw(const char *dev_id, const char *con_id) { struct clk_lookup *cl; struct clk_hw *hw = ERR_PTR(-ENOENT); mutex_lock(&clocks_mutex); // 互斥锁,加锁,防止并发 cl = clk_find(dev_id, con_id); if (cl) hw = cl->clk_hw; mutex_unlock(&clocks_mutex); // 解锁 return hw; }
clk_find_hw里面通过clk_find函数;来查找我们传入的时钟别名,并返回clk_lookup类型的一个指针,clk_find函数里面就是我们最终需要查看的内容。
clk_lookup定义在include/linux/clkdev.h文件中:
struct clk_lookup { struct list_head node; const char *dev_id; // 设备名称 const char *con_id; // 时钟别名 struct clk *clk; // 时钟对应的struck clk结构 struct clk_hw *clk_hw; // 时钟对应的struct clk_hw结构 };
5.4.2 clk_find
/* * Find the correct struct clk for the device and connection ID. * We do slightly fuzzy matching here: * An entry with a NULL ID is assumed to be a wildcard. * If an entry has a device ID, it must match * If an entry has a connection ID, it must match * Then we take the most specific entry - with the following * order of precedence: dev+con > dev only > con only. */ static struct clk_lookup *clk_find(const char *dev_id, const char *con_id) { struct clk_lookup *p, *cl = NULL; int match, best_found = 0, best_possible = 0; if (dev_id) // 优先级高,占比2 best_possible += 2; if (con_id) // 优先级低 占比1 best_possible += 1; lockdep_assert_held(&clocks_mutex); list_for_each_entry(p, &clocks, node) { match = 0; if (p->dev_id) { if (!dev_id || strcmp(p->dev_id, dev_id)) // 如果指定了设备名称,比较设备名称dev_id和当前clk_lookup节点p->dev_id continue; match += 2; } if (p->con_id) { if (!con_id || strcmp(p->con_id, con_id)) // 如果指定了时钟别名con_id,比较时钟别名con_id和当前clk_lookup节点p->con_id continue; match += 1; } if (match > best_found) { cl = p; if (match != best_possible) best_found = match; else break; } } return cl; }
list_for_each_entry函数从clocks的链表中的表头,它受clocks_lock保护,开始查找和我们传入的时钟别名相比较,如果找到了就返回一个指向该时钟clk_lookup类型的指针。
dev_mclk_get函数到此为止分析完毕,这里还需要补充一点,那就是在没有设备树的情景下,第二个参数时钟别名在哪里定义的呢。
六、clocks链表初始化
clocks是一个双向链表,定义在drivers/clk/clkdev.c文件中,关于双向链表内容具体参考Linux内核中经典链表 list_head 常见使用方法解析:
static LIST_HEAD(clocks);
那双向链表clocks何时构建的的呢,我们再次定位到我们定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件中如下代码:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END
6.1 smdk2440_init_time
smdk2440_init_time函数用于初始化linux内核的时钟:
static void __init smdk2440_init_time(void) { s3c2440_init_clocks(12000000); samsung_timer_init(); }
6.2 s3c2440_init_clocks
s3c2440_init_clocks定义在arch/arm/mach-s3c24xx/common.c文件中:
void __init s3c2440_init_clocks(int xtal) { s3c2410_common_clk_init(NULL, xtal, 1, S3C24XX_VA_CLKPWR); // 1对应的枚举变量S3C2440 }
6.3 s3c2410_common_clk_init
s3c2410_common_clk_init定义在drivers/clk/samsung/clk-s3c2410.c:
void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f, // np为NULL、xti_f=12000000、current_soc=1 int current_soc, void __iomem *base) { struct samsung_clk_provider *ctx; reg_base = base; if (np) { // 不成立 reg_base = of_iomap(np, 0); if (!reg_base) panic("%s: failed to map registersn", __func__); } ctx = samsung_clk_init(np, reg_base, NR_CLKS); // 初始化时钟,主要是给ctx分配空间和填充一些数据,如寄存器基地址 /* Register external clocks only in non-dt cases */ if (!np) s3c2410_common_clk_register_fixed_ext(ctx, xti_f); // 注册通用的外部固定时钟,即注册晶振 if (current_soc == S3C2410) { if (_get_rate("xti") == 12 * MHZ) { s3c2410_plls[mpll].rate_table = pll_s3c2410_12mhz_tbl; s3c2410_plls[upll].rate_table = pll_s3c2410_12mhz_tbl; } /* Register PLLs. */ samsung_clk_register_pll(ctx, s3c2410_plls, ARRAY_SIZE(s3c2410_plls), reg_base); } else { /* S3C2440, S3C2442 */ if (_get_rate("xti") == 12 * MHZ) { /* * plls follow different calculation schemes, with the * upll following the same scheme as the s3c2410 plls */ s3c244x_common_plls[mpll].rate_table = pll_s3c244x_12mhz_tbl; s3c244x_common_plls[upll].rate_table = pll_s3c2410_12mhz_tbl; } /* Register PLLs. */ samsung_clk_register_pll(ctx, s3c244x_common_plls, // 注册clock mpll、clock upll ARRAY_SIZE(s3c244x_common_plls), reg_base); } /* Register common internal clocks. */ samsung_clk_register_mux(ctx, s3c2410_common_muxes, // 注册clock mux ARRAY_SIZE(s3c2410_common_muxes)); samsung_clk_register_div(ctx, s3c2410_common_dividers, // 注册clock divider ARRAY_SIZE(s3c2410_common_dividers)); samsung_clk_register_gate(ctx, s3c2410_common_gates, // 注册clock gate ARRAY_SIZE(s3c2410_common_gates)); if (current_soc == S3C2440 || current_soc == S3C2442) { // 因为2440和2440是相似的,这里实在2410基础上进行补充 samsung_clk_register_div(ctx, s3c244x_common_dividers, ARRAY_SIZE(s3c244x_common_dividers)); samsung_clk_register_gate(ctx, s3c244x_common_gates, ARRAY_SIZE(s3c244x_common_gates)); samsung_clk_register_mux(ctx, s3c244x_common_muxes, ARRAY_SIZE(s3c244x_common_muxes)); samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor, ARRAY_SIZE(s3c244x_common_ffactor)); } /* Register SoC-specific clocks. */ switch (current_soc) { case S3C2410: samsung_clk_register_div(ctx, s3c2410_dividers, ARRAY_SIZE(s3c2410_dividers)); samsung_clk_register_fixed_factor(ctx, s3c2410_ffactor, ARRAY_SIZE(s3c2410_ffactor)); samsung_clk_register_alias(ctx, s3c2410_aliases, ARRAY_SIZE(s3c2410_aliases)); break; case S3C2440: // 2440特有的时钟注册 samsung_clk_register_mux(ctx, s3c2440_muxes, ARRAY_SIZE(s3c2440_muxes)); samsung_clk_register_gate(ctx, s3c2440_gates, ARRAY_SIZE(s3c2440_gates)); break; case S3C2442: samsung_clk_register_mux(ctx, s3c2442_muxes, ARRAY_SIZE(s3c2442_muxes)); samsung_clk_register_fixed_factor(ctx, s3c2442_ffactor, ARRAY_SIZE(s3c2442_ffactor)); break; } /* * Register common aliases at the end, as some of the aliased clocks * are SoC specific. */ samsung_clk_register_alias(ctx, s3c2410_common_aliases, // 为每一个struct clk_hw注册一个clock lookup,从而可以通过clk_get获取指定时钟 ARRAY_SIZE(s3c2410_common_aliases)); if (current_soc == S3C2440 || current_soc == S3C2442) { samsung_clk_register_alias(ctx, s3c244x_common_aliases, ARRAY_SIZE(s3c244x_common_aliases)); } samsung_clk_sleep_init(reg_base, s3c2410_clk_regs, ARRAY_SIZE(s3c2410_clk_regs)); samsung_clk_of_add_provider(np, ctx); }
s3c2410_common_clk_init函数用来进行时钟的初始化,函数流程如下:
- 初始化时钟,主要是给ctx分配空间和填充一些数据,如寄存器基地址;
- 注册通用的外部固定时钟,即注册晶振;
- 注册clock mpll、clock upll;
- 注册clock mux;
- 注册clock divider;
- 注册clock gate;
- 注册clock fixed factor;
- 为每一个struct clk_hw注册一个clock lookup,从而可以通过clk_get获取指定时钟
samsung_clk_provider可以理解为CCF子系统时钟的上下文,成员clk_data保存了我们注册的所有时钟,其定义在 drivers/clk/samsung/clk.h:
/** * struct samsung_clk_provider: information about clock provider * @reg_base: virtual address for the register base. * @lock: maintains exclusion between callbacks for a given clock-provider. * @clk_data: holds clock related data like clk_hw* and number of clocks. */ struct samsung_clk_provider { void __iomem *reg_base; struct device *dev; spinlock_t lock; /* clk_data must be the last entry due to variable length 'hws' array */ struct clk_hw_onecell_data clk_data; };
clk_hw_onecell_data定义在 include/linux/clk-provider.h:
struct clk_hw_onecell_data { unsigned int num; // 注册的时钟数量 struct clk_hw *hws[]; // 数组类型,存储每一个时钟对应的struc clk_hw *结构; };
6.3.1 samsung_clk_init
samsung_clk_init定义在drivers/clk/samsung/clk.c:
/* setup the essentials required to support clock lookup using ccf */ struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np, void __iomem *base, unsigned long nr_clks) { struct samsung_clk_provider *ctx; int i; ctx = kzalloc(sizeof(struct samsung_clk_provider) + sizeof(*ctx->clk_data.hws) * nr_clks, GFP_KERNEL); // 动态申请内存 if (!ctx) panic("could not allocate clock provider context.n"); for (i = 0; i < nr_clks; ++i) ctx->clk_data.hws[i] = ERR_PTR(-ENOENT); ctx->reg_base = base; // 初始化 ctx->clk_data.num = nr_clks; spin_lock_init(&ctx->lock); // 初始化自旋锁 return ctx; }
6.3.2 注册晶振
s3c2410_common_clk_register_fixed_ext定义在drivers/clk/samsung/clk-s3c2410.c
static void __init s3c2410_common_clk_register_fixed_ext( struct samsung_clk_provider *ctx, unsigned long xti_f) // 时钟频率12000000 { struct samsung_clock_alias xti_alias = ALIAS(XTI, NULL, "xtal"); s3c2410_common_frate_clks[0].fixed_rate = xti_f; samsung_clk_register_fixed_rate(ctx, s3c2410_common_frate_clks, ARRAY_SIZE(s3c2410_common_frate_clks)); // 时钟个数为1 samsung_clk_register_alias(ctx, &xti_alias, 1); }
s3c2410_common_clk_register_fixed_ext用于注册通用的外部固定时钟(晶振),其中静态变量s3c2410_common_frate_clks结构体定义在drivers/clk/samsung/clk.h:
/** * struct samsung_fixed_rate_clock: information about fixed-rate clock * @id: platform specific id of the clock. * @name: name of this fixed-rate clock. * @parent_name: optional parent clock name. * @flags: optional fixed-rate clock flags. * @fixed-rate: fixed clock rate of this clock. */ struct samsung_fixed_rate_clock { unsigned int id; char *name; // 时钟名称 const char *parent_name; unsigned long flags; unsigned long fixed_rate; // 时钟频率 }; #define FRATE(_id, cname, pname, f, frate) { .id = _id, .name = cname, .parent_name = pname, .flags = f, .fixed_rate = frate, } /* * fixed rate clocks generated outside the soc * Only necessary until the devicetree-move is complete */ #define XTI 1 static struct samsung_fixed_rate_clock s3c2410_common_frate_clks[] __initdata = { FRATE(XTI, "xti", NULL, 0, 0), };
s3c2410_common_clk_register_fixed_ext函数调用如图所示:
clk_hw_register_fixed_rate_with_accuracy:
- 函数会动态分配一个struct clk_fixed_rate,初始化其成员fixed_rate=12000000,fixed_accuracy=0,同时初始化hw.init静态属性,设置时钟操作init.ops=&clk_fixed_rate_ops;
- 调用clk_hw_register:
- 动态分配hw->core,并初始化hw->core;
- 初始化hw->core->clks哈希双向链表,创建一个hw->clk链接到hw->core->clks上;
6.3.3 注册时钟PLL
接着是注册时钟PLL,samsung_clk_register_pll定义在drivers/clk/samsung/clk-pll.c文件中,其内部是调用clk_hw_register实现的,这里就不分析源码了。
我们看一下函数的参数s3c2410_plls,定义在drivers/clk/samsung/clk-s3c2410.c:
static struct samsung_pll_clock s3c2410_plls[] __initdata = { [mpll] = PLL(pll_s3c2410_mpll, MPLL, "mpll", "xti", LOCKTIME, MPLLCON, NULL), [upll] = PLL(pll_s3c2410_upll, UPLL, "upll", "xti", LOCKTIME, UPLLCON, NULL), };
其中struct samsung_pll_clock 结构体定义在drivers/clk/samsung/clk.h:
/** * struct samsung_pll_clock: information about pll clock * @id: platform specific id of the clock. * @name: name of this pll clock. * @parent_name: name of the parent clock. * @flags: optional flags for basic clock. * @con_offset: offset of the register for configuring the PLL. * @lock_offset: offset of the register for locking the PLL. * @type: Type of PLL to be registered. */ struct samsung_pll_clock { unsigned int id; const char *name; const char *parent_name; unsigned long flags; int con_offset; int lock_offset; enum samsung_pll_type type; const struct samsung_pll_rate_table *rate_table; }; #define __PLL(_typ, _id, _name, _pname, _flags, _lock, _con, _rtable) { .id = _id, .type = _typ, .name = _name, .parent_name = _pname, .flags = _flags, .con_offset = _con, .lock_offset = _lock, .rate_table = _rtable, } #define PLL(_typ, _id, _name, _pname, _lock, _con, _rtable) __PLL(_typ, _id, _name, _pname, CLK_GET_RATE_NOCACHE, _lock, _con, _rtable)
6.3.4 注册时钟gate
接着是注册时钟mux、div、gate,这里我们挑选samsung_clk_register_gate函数介绍,samsung_clk_register_gate定义在drivers/clk/samsung/clk.c文件中,其内部是调用clk_hw_register_gate实现的,这里就不分析源码了。
我们看一下函数的参数s3c2410_common_gates,定义在drivers/clk/samsung/clk-s3c2410.c:
static struct samsung_gate_clock s3c2410_common_gates[] __initdata = { GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0), GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0), GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0), GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0), GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0), GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0), GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0), GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0), GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0), GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0), GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0), GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0), GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0), GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0), GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0), };
从数组中,可以发现像i2c、uart等这些外设驱动,它们时钟源来自于pclk、hclk,只需要使能门控即可。如果直接调用clk_ser_rate函数设置频率,clk_set_rate会向上传递,即设置它的父时钟频率。
其中struct samsung_gate_clock 结构体定义在drivers/clk/samsung/clk.h:
/** * struct samsung_gate_clock: information about gate clock * @id: platform specific id of the clock. * @name: name of this gate clock. * @parent_name: name of the parent clock. * @flags: optional flags for basic clock. * @offset: offset of the register for configuring the gate. * @bit_idx: bit index of the gate control bit-field in @reg. * @gate_flags: flags for gate-type clock. */ struct samsung_gate_clock { unsigned int id; const char *name; const char *parent_name; unsigned long flags; unsigned long offset; u8 bit_idx; u8 gate_flags; }; #define __GATE(_id, cname, pname, o, b, f, gf) { .id = _id, .name = cname, .parent_name = pname, .flags = f, .offset = o, .bit_idx = b, .gate_flags = gf, } #define GATE(_id, cname, pname, o, b, f, gf) __GATE(_id, cname, pname, o, b, f, gf) #define PNAME(x) static const char * const x[] __initconst
我们以 GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0)为例:
- id表示为平台为时钟特定分配的id,这里被设置为了HCLK_USBH,也就是33;
- name表示时钟的名称,这里设置为usb-host;
- parent_name为父时钟的名称,这里设置为为hclk;
- offset表示控制时钟开关的寄存器地址,这里设置为CLKCON;
- bit_idx表示控制时钟开关bit位,这里设置为6;
在博客Mini2440裸机开发之系统时钟配置中我们已经介绍了CLKCON寄存器的bit[6]对应着usb host模块,通过置1使能时钟,默认情况下开启的,其父时钟为HCLK时钟信号。
在drivers/clk/samsung/clk-s3c2410.c中我们也可以找到hclk时钟的定义,而hclk时钟又是来自mpll分频,这里就不过多深究了,有兴趣的可以看看S3C2440的时钟结构图:
static struct samsung_div_clock s3c2410_dividers[] __initdata = { DIV(HCLK, "hclk", "mpll", CLKDIVN, 1, 1), };
6.3.5 注册时钟别名
samsung_clk_register_alias函数用于注册时钟别名,其参数s3c2410_common_aliases定义在drivers/clk/samsung/clk-s3c2410.c:
/* should be added _after_ the soc-specific clocks are created */ static struct samsung_clock_alias s3c2410_common_aliases[] __iitdata = { ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"), ALIAS(PCLK_ADC, NULL, "adc"), ALIAS(PCLK_RTC, NULL, "rtc"), ALIAS(PCLK_PWM, NULL, "timers"), ALIAS(HCLK_LCD, NULL, "lcd"), ALIAS(HCLK_USBD, NULL, "usb-device"), ALIAS(HCLK_USBH, NULL, "usb-host"), ALIAS(UCLK, NULL, "usb-bus-host"), ALIAS(UCLK, NULL, "usb-bus-gadget"), ALIAS(ARMCLK, NULL, "armclk"), ALIAS(UCLK, NULL, "uclk"), ALIAS(HCLK, NULL, "hclk"), ALIAS(MPLL, NULL, "mpll"), ALIAS(FCLK, NULL, "fclk"), ALIAS(PCLK, NULL, "watchdog"), ALIAS(PCLK_SDI, NULL, "sdi"), ALIAS(HCLK_NAND, NULL, "nand"), ALIAS(PCLK_I2S, NULL, "iis"), ALIAS(PCLK_I2C, NULL, "i2c"), };
其中structs samsung_clock_alias结构体定义在drivers/clk/samsung/clk.h:
/** * struct samsung_clock_alias: information about mux clock * @id: platform specific id of the clock. * @dev_name: name of the device to which this clock belongs. * @alias: optional clock alias name to be assigned to this clock. */ struct samsung_clock_alias { unsigned int id; const char *dev_name; const char *alias; }; #define ALIAS(_id, dname, a) { .id = _id, .dev_name = dname, .alias = a, }
第一个参数为:平台为某个时钟分配的唯一id,第二个参数为当前时钟所属的struct device设备的名字,第三个参数也就是时钟别名,也是我们调用clk_get需要传入的第二个参数;
samsung_clk_register_alias函数遍历list列表,查找struct clk_hw,并为其注册clock lookup,最后链接到clocks双向链表上。其代码位于drivers/clk/samsung/clk.c文件:
/* register a list of aliases */ void __init samsung_clk_register_alias(struct samsung_clk_provider *ctx, const struct samsung_clock_alias *list, unsigned int nr_clk) { struct clk_hw *clk_hw; unsigned int idx, ret; for (idx = 0; idx < nr_clk; idx++, list++) { if (!list->id) { pr_err("%s: clock id missing for index %dn", __func__, idx); continue; } clk_hw = ctx->clk_data.hws[list->id]; // 根据平台为时钟分配的特定的id获取其对应的struct clk_hw结构 if (!clk_hw) { pr_err("%s: failed to find clock %dn", __func__, list->id); continue; } ret = clk_hw_register_clkdev(clk_hw, list->alias, // 为struct clk_hw注册一个时钟查找 list->dev_name); if (ret) pr_err("%s: failed to register lookup %sn", __func__, list->alias); } }
clk_hw_register_clkdev为struct clk_hw注册一个时钟查找,并添加到双向链表clocks;函数定义在drivers/clk/clkdev.c:
/** * clk_hw_register_clkdev - register one clock lookup for a struct clk_hw * @hw: struct clk_hw to associate with all clk_lookups * @con_id: connection ID string on device * @dev_id: format string describing device name * * con_id or dev_id may be NULL as a wildcard, just as in the rest of * clkdev. * * To make things easier for mass registration, we detect error clk_hws * from a previous clk_hw_register_*() call, and return the error code for * those. This is to permit this function to be called immediately * after clk_hw_register_*(). */ int clk_hw_register_clkdev(struct clk_hw *hw, const char *con_id, const char *dev_id) { struct clk_lookup *cl; return do_clk_register_clkdev(hw, &cl, con_id, dev_id); } static int do_clk_register_clkdev(struct clk_hw *hw, struct clk_lookup **cl, const char *con_id, const char *dev_id) { if (IS_ERR(hw)) return PTR_ERR(hw); /* * Since dev_id can be NULL, and NULL is handled specially, we must * pass it as either a NULL format string, or with "%s". */ if (dev_id) *cl = __clk_register_clkdev(hw, con_id, "%s", dev_id); else *cl = __clk_register_clkdev(hw, con_id, NULL); return *cl ? 0 : -ENOMEM; } static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw, const char *con_id, const char *dev_id, ...) // 第三个参数是可变参数 { struct clk_lookup *cl; va_list ap; va_start(ap, dev_id); cl = vclkdev_create(hw, con_id, dev_id, ap); va_end(ap); return cl; } static struct clk_lookup * vclkdev_create(struct clk_hw *hw, const char *con_id, const char *dev_fmt, va_list ap) { struct clk_lookup *cl; cl = vclkdev_alloc(hw, con_id, dev_fmt, ap); if (cl) __clkdev_add(cl); return cl; } static void __clkdev_add(struct clk_lookup *cl) { mutex_lock(&clocks_mutex); list_add_tail(&cl->node, &clocks); // 将时钟对应的clk_loopup结构追加到双向列表的结尾,因此我们在调用clk_get时可以从clocks中获取时钟硬件对应的clk mutex_unlock(&clocks_mutex); }
关于可变长参数相关原理可以查看:va_list 可变长参数原理。
七、USB设备识别超时问题
我们再次回到usb主机控制器驱动代码:
clk = devm_clk_get(&dev->dev, "usb-host"); // 获取usb-host时钟 usb_clk = devm_clk_get(&dev->dev, "usb-bus-host"); // 获取usb-bus-host
经过我们这么多章节的介绍,我们已经明白如上代码是为了获取usb主机控制器、以及usb总线(UCLK)的时钟了。
usb-host时钟在上面我们已经介绍了,它是一个gate类型的时钟,提供了enable/disable功能,通过CLKCON寄存器bit[6]控制进入USB主机模块的HCLK。接下来我们介绍一下UCLK。
7.1 UCLK
UCLK是由UPLL分频得到,为USB提供工作评论,其定义:
static struct samsung_div_clock s3c244x_common_dividers[] __initdata = { DIV(UCLK, "uclk", "upll", CLKDIVN, 3, 1), DIV(0, "div_hclk", "fclk", CLKDIVN, 1, 1), DIV_T(0, "div_hclk_4", "fclk", CAMDIVN, 9, 1, div_hclk_4_d), DIV_T(0, "div_hclk_3", "fclk", CAMDIVN, 8, 1, div_hclk_3_d), DIV(0, "div_cam", "upll", CAMDIVN, 0, 3), };
可以看到uclk的父时钟为upll,其分频系数是通过CLKDIVN寄存器的bit[3]来控制的。
CLKDIVN | 位 | 描述 | 初始状态 |
DIVN_UPLL | [3] |
UCLK 选择寄存器(UCLK 必须为48MHz 给USB) 0:UCLK = UPLL时钟 1:UCLK = UPLL 时钟 / 2 当UPLL 时钟被设置为48MHz 时,设置为0 当UPLL 时钟被设置为96MHz 时,设置为1 |
0 |
那么问题来,UCLK的时钟有没有被设置为48MHz呢,我们继续分析代码s3c2410_start_hc。
7.2 s3c2410_start_hc
s3c2410_start_hc位于drivers/usb/host/ohci-s3c2410.c:
static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd) { struct s3c2410_hcd_info *info = dev_get_platdata(&dev->dev); dev_dbg(&dev->dev, "s3c2410_start_hc:n"); clk_prepare_enable(usb_clk); mdelay(2); /* let the bus clock stabilise */ clk_prepare_enable(clk); if (info != NULL) { info->hcd = hcd; info->report_oc = s3c2410_hcd_oc; if (info->enable_oc != NULL) (info->enable_oc)(info, 1); } }
这里首先执行了 clk_prepare_enable(usb_clk),那么问题来了,通过之前分析我们知道usb_clk时钟实际上就是时钟uclk,也就是divider类型的时钟;
这里仅仅进行了uclk时钟的准备和使能工作,那uclk时钟的频率是不是48MHz呢?
在SOC手册上我们有一句话需要留意: 在系统初始化阶段,当你设置mplll和upll的值时,你必须首先设置upll值再设置mpll值,因此最好的方式在uboot阶段中去修改upll的值。实际上如果去看我们之前介绍的uboot移植部分,我们会发现我们时钟设置的并没有问题。
网上给出的大部分解决方案是,修改内核s3c2410_start_hc函数,在函数开始添加设置upll时钟频率的代码:
int rate = clk_get_rate(usb_clk); printk("------------------------before %d",rate); while (upllvalue != __raw_readl(S3C2410_UPLLCON)) { __raw_writel(upllvalue, S3C2410_UPLLCON); mdelay(1); } rate = clk_get_rate(usb_clk); printk("--------------------------after %d",rate);
内核启动后我们发现控制台打印的信息如下:
ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver ohci-s3c2410: OHCI S3C2410 driver ------------------------before 48000000 --------------------------after 48000000 s3c2410-ohci s3c2410-ohci: OHCI Host Controller s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1 s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000 hub 1-0:1.0: USB hub found hub 1-0:1.0: 2 ports detected usbcore: registered new interface driver usbserial_generic usbserial: USB Serial support registered for generic usbcore: registered new interface driver ftdi_sio usbserial: USB Serial support registered for FTDI USB Serial Device usbcore: registered new interface driver pl2303 usbserial: USB Serial support registered for pl2303
可以看到修改前后的upll时钟并没有发生变化,都是48MHz,那这么修改有什么意义呢?
但是控制输出的信息可以看到usb设备的确识别到了,不过过了几分钟,usb设备又会自动断开,因此我怀疑是电源不稳定导致的usb断开。
这里upll时钟在修改前输出的48MHz是哪里设置的呢,实际上这是在uboot阶段我们设置的的时钟频率,在uboot启动阶段会在控制台输出FCLK、HCLK、PCLK频率,不过默认没有输出UCLK,有兴趣可以去修改uboot尝试一下。
CPUID: 32440001 FCLK: 400 MHz HCLK: 100 MHz PCLK: 50 MHz
参考文章
[1]嵌入式Linux驱动笔记(十四)------详解clock时钟(CCF)框架及clk_get函数
[2]linux clock头文件,Linux common clock framework(1)_概述
[3]Linux CommonClock Framework子系统分析之一 系统概述
[4]Linux CommonClock Framework分析之二 CCF子系统内部实现简述
[6]Linux common clock framework(3)_实现逻辑分析
[7]Linux common clock framework(2)_clock provider
[9]The Linux Kernel API Clock Framework
[10]Common Clock Framework -1- (초기화)
[11]Common Clock Framework -2- (APIs)
[13] Linux clock子系统【3】-i2c控制器打开时钟的流程分析(devm_clk_get)(consumer侧)
文章来源: 博客园
- 还没有人评论,欢迎说说您的想法!