• linux驱动移植通用时钟框架子系统


    一、CCF子系统概述

    在linux 3.10之后的版本,linux内核时钟子系统又叫通用时钟框架子系统,简称CCF子系统(common clock framework),是用来管理系统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;
    };
    View Code

    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);
    };
    View Code

    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,根据设备指针或者时钟索引id参数,查找时钟;dev和id任意一个可以为空,如果id为空,则必须有device tree的支持才能获得device对应的clk;

    根据具体的平台实现,id可以是一个简单的名称,也可以 是一个预先定义的、唯一的标识(一般在平台提供的头文件中定义,如mach/clk.h);

    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,每一类时钟都有相似的功能、相似的控制方式,因而可以使用相同的逻辑s,统一处理,这充分体现了面向对象的思想。

    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 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,一般我们设置为null;
    • 第二个参数是一个字符指针,用于指向一个字符串,比如“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);
    }

    假设我们设定dev为null,那么这里相当于执行了 __clk_get_sys(NULL, NULL, con_id)。

    5.3 __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 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.5 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和当前节点p->dev_id
                                    continue;
                            match += 2;
                    }
                    if (p->con_id) {
                            if (!con_id || strcmp(p->con_id, con_id))  // 如果指定了硬件名称con_id,比较硬件名称con_id和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,
                    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);
    }

    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

    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 samsung_clk_register_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 samsung_clk_register_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),
    };

    其中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表示时钟的名称,也是我们调用clk_get需要传入的第二个参数,这里设置为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

    samsung_clk_register_aliasa函数用于注册时钟别名,其参数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,                            \
            }

    samsung_clk_register_alias函数就是将con_id(这里即是alias)链接到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,
                                                 list->dev_name);
                    if (ret)
                            pr_err("%s: failed to register lookup %s\n",
                                            __func__, list->alias);
            }
    }

     clk_hw_register_clkdev定义在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);  // 将时钟对应的struck clk结构追加到双向列表的结尾,因此我们在调用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
    后来我又对这电路图测测开发板usb相关电路的电压啥的,usb设备识别出来,不自动断开了,我怀疑可能是开发板哪里电路有点虚焊了,被我用电表笔捯饬好了。

    参考文章

    [1]嵌入式Linux驱动笔记(十四)------详解clock时钟(CCF)框架及clk_get函数

    [2]linux clock头文件,Linux common clock framework(1)_概述

    [3]Linux CommonClock Framework子系统分析之一 系统概述

    [4]Linux CommonClock Framework分析之二 CCF子系统内部实现简述

    [5]转载:LINUX CCF框架简要分析和API调用

    [6]Linux common clock framework(3)_实现逻辑分析

    [7]Linux common clock framework(2)_clock provider

    [8]Common Clock Framework系统结构

    [9]The Linux Kernel API Clock Framework

  • 相关阅读:
    java连接Ldap
    REGEXP_LIKE,REGEXP_INSTR,REGEXP_SUBSTR,REGEXP_REPLACE
    正则表达式学习笔记
    旋转的播放按钮
    折叠table中的tr
    css选择器.md
    清除浮动.md
    jquery-validate使用.md
    EL表达式.md
    C标签的使用.md
  • 原文地址:https://www.cnblogs.com/zyly/p/16597019.html
Copyright © 2020-2023  润新知