一、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,根据设备指针或者时钟别名参数查找时钟;
- 如果使用设备树,支持根据设备节点,以及时钟别名(clocks-names属性)获取时钟,需要同时传入dev、con_id;
- 如果没有指定设备树,支持根据设备名称、时钟别名获取时钟,dev、con_id可以任选一个,或者同时传入;
- 时钟别名是在时钟初始化的时候调用samsung_clk_register_alias注册的,每个时钟别名对应一个clock lookup,clock lookup中包含了dev_id、con_id、clk、clk_hw等成员。
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 clock\n");
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 clock\n");
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 registers\n", __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,定义在include/dt-bindings/clock/s3c2410.h,值为33;这个id通常在设备树中使用,比如clocks = <&clocks HCLK_USBH>;
- 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 %d\n", __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 %d\n", __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 %s\n",
__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侧)