天天看点

i.MX8MPlus中的CLK子系统

芯片手册中的clk框架

CCM(Clock Control Module)框架图

i.MX8MPlus中的CLK子系统

外部时钟的输入源有24MHz,32.768KHz以及四个EXT CLK。这7个输入源都可以直接连接到CCM,但是PLL只能以24MHz,32.768KHz作为输入。从PLL和分频器出来的时钟也可以作为CCM的输入。每一个Slice在经过MUX模块后,由分频器产生我们需要的时钟频率,然后再输出给Gate模块,以便控制时钟的开关。

CLK ROOT相关寄存器

i.MX8MPlus中的CLK子系统

每一个Slice都有自己的index、偏移地址和时钟源配置寄存器CCM_TARGET_ROOTn。这里的偏移地址是相对于CCM的基地址而言的。

PLL输出

PLL的输出即CCM的输入,下图是所有的PLL信息。PLL的参数设置由CCM_ANALOG系列寄存器,例如PLL配置寄存器(外部时钟输入选择(24MHz/PAD_CLK),分频系数P、M、K、S设置寄存器。PAD_CLK可以选择CLKIN1 或 CLKIN2。

i.MX8MPlus中的CLK子系统
i.MX8MPlus中的CLK子系统

PLL的计算公式如下:

其中Fin = 24MHz;

1 ≤ p ≤ 63;

64 ≤ m ≤ 1023;

0 ≤ s ≤ 6;

-32768 ≤ k ≤ 32767;

在频率范围内的clock基本上都能用此公式计算出来。ARM PLL, GPU PLL, VPU PLL, DRAM PLL, Audio PLL1/2, and Video PLL1都是基本PLL。

时钟分频器

同步时钟分频器(PRE_PODF和POST_PODF)可用于对源时钟频率执行整数除法。在除法因子变化期间,分频器保证其输出上有一个干净的时钟信号。分频器执行**1/(N+1)**除法。故障可能导致同步分频器进入无法恢复的状态,因此必须向同步分频器提供干净的时钟信号。

时钟门控器

CCM可以根据系统低功耗模式对片上外围设备进行自动时钟关闭。每个逻辑域可以分别声明其在每个时钟上的依赖级别。如果检测到不依赖任何域的时钟,它将被关闭以节省电力。时钟门控单元用于控制时钟输出。时钟门控器在LPCG中使用活动时钟门控,这意味着需要活动时钟根。时钟生成模块仅对时钟源执行多路复用、门控和除分。因此,当相应的时钟源停止时,生成模块的时钟根将停止。必须为LPCG主动门控的时钟根设置ENABLE位。

8对1多路时钟复位器

8对1多路复用器是一种组合多路复用器,可以随时切换时钟源。多路复用器输出不能保证干净的时钟信号。在更改多路复用器选择之前,必须对多路复用器的输出时钟路径进行时钟门控。

时钟切片

每个切片单源都连接着8个来自PLL/PLL分频器的输入时钟,切片分为五大类,分别为Core, Bus, Peripheral (IP), 和DRAM。

i.MX8MPlus中的CLK子系统

下图显示了时钟切片可以包含的CCM时钟组件,以及相关的寄存器控件。并非所有时钟切片类型都将包含下图中提供的所有组件。请参考以下部分,以确定特定时钟切片类型中包含的组件。下面显示的切片由一个后分隔器和一个带有2个输入源的时钟开关多路复用器组成。每个输入源内部都有一个预分频器、一个时钟门和一个时钟多路复用器。

i.MX8MPlus中的CLK子系统

IP切片和上图有些不同,为了满足更小的外设时钟要求,支持两个分频器设置,最大分频数为512。

i.MX8MPlus中的CLK子系统

CCGR(CCM Clock Gating Register )接口(翻译)

当进入或离开低功耗模式时,CCM可以控制CCM_ANALOG中的PLL。在开机重置(POR)后进入低功耗模式之前,软件必须在CCM_ANALOG中设置PLL覆盖。

一个逻辑领域有四个级别的低功耗模式:

  • No need
  • RUN
  • STOP
  • WAIT

CCM仅在域状态在STOP之间切换时采取行动(深度睡眠模式与STOP相同)。有4个域可以分配。RDC可以将任何CPU平台分配给任何域。如果域为空,则该域被视为STOP。

每个域都可以向CCM声明其依赖性。不允许使用任何时钟,而不在自己的域中声明。域通过写入依赖级别来声明其对时钟的依赖性。针对低功耗模式下行为的设置如下:

i.MX8MPlus中的CLK子系统

每个域只能更改分配给控制访问的位。任何无关紧要的写给它的东西都会被忽略。例如,域0只能写入位[1:0]。除位[1:0]外,写入的任何位都将被忽略。其他域可以读取所有其他域设置。域0的默认值为2,关机后将进入STOP模式。当其他域设置的默认值为0时,将不需要它。

设置时钟源时,设置不会立即生效。该设置将首先进入影子寄存器。如果PLL关闭或新设置进入影子寄存器以声明对PLL的依赖性,PLL将立即打开。当PLL准备就绪时,影子寄存器中的设置将更新为新设置。在此期间,将设置和清除待处理位。然后,CCM将发送PLL控制信号作为影子寄存器,并根据设置寄存器通知GPCPLL状态。在其他情况下,设置将立即从影子寄存器中更新。时钟源相互依赖。

i.MX8MPlus中的CLK子系统

子CLK分频公式

驱动中clk的一些概念

(1)fixed rate clock

这一类clock具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类clock。可以直接通过DTS配置的方式支持,clock framework core能直接从DTS中解出clock信息,并自动注册到kernel,不需要任何driver支持。例如24MHz晶振和32.768K的晶振。

i.MX8MPlus中的CLK子系统
i.MX8MPlus中的CLK子系统

(2)fixed factor clock

这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调。以第一行的clk为例,这里的"sys_pll1_50m"就是我们想要的fixed factor clock。“sys_pll1_50m"的clk的父时钟节点为"sys_pll2_out”(1000MHz),倍频系数为1,分频系数为20。

i.MX8MPlus中的CLK子系统

(3)mux clock

这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调。该接口可注册mux控制比较规则的clock(类似divider clock):

name:clk名字;

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

num_parents:parent clock的个数;

reg、shift、width,选择parent的寄存器、偏移、宽度,默认情况下,寄存器值为0时,对应第一个parent,依此类推;

i.MX8MPlus中的CLK子系统

(4)divider clock

这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调)。

name:需要生成的divider cloclk名字;

parent:控制分频比的父时钟节点;

reg:控制clock分频比的寄存器地址;

shift:控制分频比的bit在寄存器中的偏移;

width:控制分频比的bit位数,默认情况下,实际的divider值是寄存器值加1。如果有其它例外,可使用下面的的flag指示;

i.MX8MPlus中的CLK子系统

(5)gate clock

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

name:clock的名称;

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

reg:控制该clock开关的寄存器地址;

shift:控制开关的bit在寄存器中的偏移;

CLK_IS_CRITICAL:不允许开关控制,属于系统核心clk;

i.MX8MPlus中的CLK子系统

(6)composite clock

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

reg:集成了mux、divider、gate功能的寄存器地址;

parent_names:可选择的parent名字数组;

i.MX8MPlus中的CLK子系统

i.MX平台的各类CLK API

上面简单列举了Linux驱动中的CLK类型,现在我们从API的角度深入分析这些类型的驱动。clk驱动是基于数据手册中的CCM_BASE(0x3038_0000)和CCM_ANALOG(0x30360000)寄存器组。

正式开始之前

驱动中的clk_hw_onecell_data结构存储clk的数量以及clk_hw结构,每一个clk都有自己的clk_hw结构。也就是说驱动中会使用hws[clk_index]的形式存储每一个clk信息。

struct clk_hw_onecell_data {
  unsigned int num;
  struct clk_hw *hws[];
};
struct clk_hw {
  struct clk_core *core;
  struct clk *clk;
  const struct clk_init_data *init;
};      

clk_core代表clock framework的核心驱动对象。

clk结构存储具体的clk信息,例如父节点、可选择的父节点列表和数量、寄存器中的位移和位宽、它们的子节点、enable和status寄存器地址,时钟速度、clk标志位等等信息。

struct clk {
  struct list_head  node;
  struct clk    *parent;
  struct clk    **parent_table; /* list of parents to */
  unsigned short    parent_num; /* choose between */
  unsigned char   src_shift;  /* source clock field in the */
  unsigned char   src_width;  /* configuration register */
  struct sh_clk_ops *ops;

  struct list_head  children;
  struct list_head  sibling;  /* node for children */

  int     usecount;

  unsigned long   rate;
  unsigned long   flags;

  void __iomem    *enable_reg;
  void __iomem    *status_reg;
  unsigned int    enable_bit;
  void __iomem    *mapped_reg;

  unsigned int    div_mask;
  unsigned long   arch_flags;
  void      *priv;
  struct clk_mapping  *mapping;
  struct cpufreq_frequency_table *freq_table;
  unsigned int    nr_freqs;
};      

clk_init_data中是clock框架中共享的初始化数据。

(1)fixed rate clock

这里以24MHz的晶振为例,hws是一个数组

hws[IMX8MP_CLK_24M] = imx_obtain_fixed_clk_hw(np, "osc_24m");      

imx_obtain_fixed_clk_hw解析24MHz晶振的设备树节点,然后使用__clk_get_hw添加进clk framework(core->hw)。IMX8MP_CLK_24M这里的索引值和dts保持一致。

(2)fixed factor clock

imx_clk_hw_fixed_factor主要用于产生固定的PLL分频。例如基于sys_pll1分出的固定频率组。

hws[IMX8MP_SYS_PLL1_400M] = imx_clk_hw_fixed_factor("sys_pll1_400m", "sys_pll1_out", 1, 2);
hws[IMX8MP_SYS_PLL1_800M] = imx_clk_hw_fixed_factor("sys_pll1_800m", "sys_pll1_out", 1, 1);      

最终会调用到__clk_hw_register_fixed_factor。

i.MX8MPlus中的CLK子系统

__clk_hw_register_fixed_factor函数的主要思想如下:

  1. 将倍频系数和分频系数存入fix结构;
  2. 设置init数据段中的clk_fixed_factor_ops(产生固定频率的函数集)和name(“sys_pll1_400m”),这里的clk_init_data在clk provider和clk framework之间是共享的。
  3. 将这个clk_hw硬件时钟对象注册进clk core。

    由于IMX8MP_SYS_PLL1_400M=55,最后上面那行clk代码的含义为:第55个hws结构中的clk名为"sys_pll1_400m",父节点"sys_pll1_out",分频系数2,倍频系数1。它所支持的ops为clk_fixed_factor_ops。

i.MX8MPlus中的CLK子系统

clk_fixed_factor_ops代码如下:

static unsigned long clk_factor_recalc_rate(struct clk_hw *hw,
    unsigned long parent_rate)
{
  struct clk_fixed_factor *fix = to_clk_fixed_factor(hw);
  unsigned long long int rate;

  rate = (unsigned long long int)parent_rate * fix->mult;
  do_div(rate, fix->div);
  return (unsigned long)rate;
}

static long clk_factor_round_rate(struct clk_hw *hw, unsigned long rate,
        unsigned long *prate)
{
  struct clk_fixed_factor *fix = to_clk_fixed_factor(hw);

  if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
    unsigned long best_parent;

    best_parent = (rate / fix->mult) * fix->div;
    *prate = clk_hw_round_rate(clk_hw_get_parent(hw), best_parent);
  }

  return (*prate / fix->div) * fix->mult;
}

static int clk_factor_set_rate(struct clk_hw *hw, unsigned long rate,
        unsigned long parent_rate)
{
  return 0;
}

const struct clk_ops clk_fixed_factor_ops = {
  .round_rate = clk_factor_round_rate,
  .set_rate = clk_factor_set_rate,
  .recalc_rate = clk_factor_recalc_rate,
};      

由于是固定频率的时钟,因此set_rate函数直接返回成功即可。

//待分析调用过程

(3)mux clock

hws[IMX8MP_VIDEO_PLL1_REF_SEL] = imx_clk_hw_mux("video_pll1_ref_sel", anatop_base + 0x28, 0, 2, pll_ref_sels, ARRAY_SIZE(pll_ref_sels));

static const char * const pll_ref_sels[] = { "osc_24m", "dummy", "dummy", "dummy", };      

imx_clk_hw_mux将"video_pll1_ref_sel"的偏移地址mux->reg,寄存器宽度2-1(mux->shift)以及可选的父节点source列表。__clk_hw_register_mux的调用流程和上一个类似,将相关ops结果体注册进clk core框架。注意整个注册过程必须锁定。

i.MX8MPlus中的CLK子系统

mux相关的ops代码如下,

static u8 clk_mux_get_parent(struct clk_hw *hw)
{
  struct clk_mux *mux = to_clk_mux(hw);
  u32 val;

  val = clk_mux_readl(mux) >> mux->shift;
  val &= mux->mask;
  //查表
  return clk_mux_val_to_index(hw, mux->table, mux->flags, val);
}

static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
{
  struct clk_mux *mux = to_clk_mux(hw);
  u32 val = clk_mux_index_to_val(mux->table, mux->flags, index);
  unsigned long flags = 0;
  u32 reg;

  if (mux->lock)
    spin_lock_irqsave(mux->lock, flags);
  else
    __acquire(mux->lock);

  if (mux->flags & CLK_MUX_HIWORD_MASK) {
    reg = mux->mask << (mux->shift + 16);
  } else {
    reg = clk_mux_readl(mux);
    reg &= ~(mux->mask << mux->shift);
  }
  val = val << mux->shift;
  reg |= val;
  clk_mux_writel(mux, reg);

  if (mux->lock)
    spin_unlock_irqrestore(mux->lock, flags);
  else
    __release(mux->lock);

  return 0;
}

static int clk_mux_determine_rate(struct clk_hw *hw,
          struct clk_rate_request *req)
{
  struct clk_mux *mux = to_clk_mux(hw);

  return clk_mux_determine_rate_flags(hw, req, mux->flags);
}

const struct clk_ops clk_mux_ops = {
  .get_parent = clk_mux_get_parent,
  .set_parent = clk_mux_set_parent,
  .determine_rate = clk_mux_determine_rate,
};      

(4)divider clock

hws[IMX8MP_CLK_IPG_ROOT] = imx_clk_hw_divider2("ipg_root", "ahb_root", ccm_base + 0x9080, 0, 1);      

实际调用__clk_hw_register_divider,核心过程和之前的mux时钟类似,也是设置divider clock相关的ops,供驱动调用。这里第三个参数是寄存器地址。

i.MX8MPlus中的CLK子系统

clk_divider_set_rate是如何拿到需要写入的寄存器地址呢?在__clk_hw_register_divider中会将传入的ccm_base+0x9080,shift和width依次存入clk_divider中的reg、shift和width,然后注册进了clk core系统。在驱动里调用set_rate函数时,core会根据传入的hw拿到clk_divider结构,因此也就找到了需要写入的寄存器信息。

static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
        unsigned long parent_rate)
{
  struct clk_divider *divider = to_clk_divider(hw);
  int value;
  unsigned long flags = 0;
  u32 val;

  value = divider_get_val(rate, parent_rate, divider->table,
        divider->width, divider->flags);
  if (value < 0)
    return value;

  if (divider->lock)
    spin_lock_irqsave(divider->lock, flags);
  else
    __acquire(divider->lock);

  if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
    val = clk_div_mask(divider->width) << (divider->shift + 16);
  } else {
    val = clk_div_readl(divider);
    val &= ~(clk_div_mask(divider->width) << divider->shift);
  }
  val |= (u32)value << divider->shift;
  clk_div_writel(divider, val);

  if (divider->lock)
    spin_unlock_irqrestore(divider->lock, flags);
  else
    __release(divider->lock);

  return 0;
}

const struct clk_ops clk_divider_ops = {
  .recalc_rate = clk_divider_recalc_rate,
  .round_rate = clk_divider_round_rate,
  .set_rate = clk_divider_set_rate,
};      

(5)gate clock

这里写入clk系统的的寄存器是CCM_ANALOG_VIDEO_PLL1_GEN_CTRL。

hws[IMX8MP_VIDEO_PLL1_OUT] = imx_clk_hw_gate("video_pll1_out", "video_pll1_bypass", anatop_base + 0x28, 13);      

其中控制gate的位是13位。

i.MX8MPlus中的CLK子系统

软件部分的逻辑和其他clock类似,通过将寄存器信息写入clk_gate,然后将其注册进clk core框架中。

i.MX8MPlus中的CLK子系统

(6)composite clock

这里API不再是通用API,而是位于drivers/clk/imx/clk-composite-8m.c里的imx8m_clk_hw_composite_flags,下面的API差异是传入的flag不同引起的。从而区别不同场景下的clk composite要求。

static const char * const imx8mp_media_apb_sels[] = {"osc_24m", "sys_pll2_125m", "sys_pll1_800m",
                 "sys_pll3_out", "sys_pll1_40m", "audio_pll2_out",
                 "clk_ext1", "sys_pll1_133m", };
hws[IMX8MP_CLK_MEDIA_APB] = imx8m_clk_hw_composite_bus("media_apb", imx8mp_media_apb_sels, ccm_base + 0x8a80);
hws[IMX8MP_CLK_PWM1] = imx8m_clk_hw_composite("pwm1", imx8mp_pwm1_sels, ccm_base + 0xb380);      

对于 media apb clk,偏移量为8a80,

i.MX8MPlus中的CLK子系统

对于CORE的composite clk,divider_ops使用的是通用API;对于总线类型的composite clk,divider_ops使用的是自定义的API。

i.MX8MPlus中的CLK子系统
i.MX8MPlus中的CLK子系统
imx8m_clk_composite_mux_ops支持选择时钟的父节点、获取时钟的父节点。
imx8m_clk_composite_divider_ops支持计算预分频和分频参数、设置分频参数。

示例分析

1.驱动API加载过程

前面的章节已经说过,gate,mux,divider等api的使用将CLK SPEC操作相关的信息(名字,寄存器位移等等)写入了clk core框架。imx8mp vendor层的clk core驱动中的主要分为三步:

  • of_clk_add_hw_provider,将此soc dts节点,clk解析回调函数of_clk_hw_onecell_get和回调数据注册进clk core。其中clk_hw_data包含了所有的hw clk信息。这一步最重要。
  • imx_clk_init_on优先初始化dts中定义的"init-on-array"时钟节点,例如NAND、SD、HSIO时钟。
  • imx_register_uart_clocks注册串口时钟。
clk_hw_data->num = IMX8MP_CLK_END;
hws = clk_hw_data->hws;
of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_hw_data);

imx_clk_init_on(np, hws);

imx_register_uart_clocks(4);      

PLL驱动

drivers/clk/imx/clk-pll14xx.c,此驱动设置了基础PLL的值。pll1443x是基础PLL,pll1416x是整型PLL。pll1443的pmks表如下:

static const struct imx_pll14xx_rate_table imx_pll1443x_tbl[] = {
  PLL_1443X_RATE(1039500000U, 173, 2, 1, 16384),
  PLL_1443X_RATE(650000000U, 325, 3, 2, 0),
  PLL_1443X_RATE(594000000U, 198, 2, 2, 0),
  PLL_1443X_RATE(519750000U, 173, 2, 2, 16384),
  PLL_1443X_RATE(393216000U, 262, 2, 3, 9437),
  PLL_1443X_RATE(361267200U, 361, 3, 3, 17511),
};      

在clk信息写入时,已经将pll 表传入了clk core。这时imx_dev_clk_hw_pll14xx函数会用于初始化pll1443相关的信息,最重要的就是将clk_pll1443x_ops注册进框架里,尤其是PLL的prepare,set_rate等函数。

hws[IMX8MM_VIDEO_PLL1] = imx_clk_hw_pll14xx("video_pll1", "video_pll1_ref_sel", base + 0x28, &imx_1443x_pll);

static const struct clk_ops clk_pll1443x_ops = {
  .prepare  = clk_pll14xx_prepare,
  .unprepare  = clk_pll14xx_unprepare,
  .is_prepared  = clk_pll14xx_is_prepared,
  .recalc_rate  = clk_pll1443x_recalc_rate,
  .round_rate = clk_pll14xx_round_rate,
  .set_rate = clk_pll1443x_set_rate,
};      

2.LVDS 中CLK驱动API调用过程

驱动使用demo

下面是驱动中使用的常用API,我们会对其调用过程进行分析。

struct clk *clk_root;
clk_root = devm_clk_get(dev, "ldb");

clk_prepare(clk_root);
clk_enable(clk_root);
clk_set_rate(clk_root, rate_xxx);      

devm_clk_get

对于devm_clk_get函数来说,通过clk_get(dev, id)拿到ldb对于的clk数组。of_parse_clkspec函数会根据传入的clock names链表中查找匹配,找到到clock-names = "ldb"对应的索引值,然后在一个列表中查找由phandle指向的节点并返回给clkspec

clk_get-->of_clk_get_hw
  
struct clk_hw *of_clk_get_hw(struct device_node *np, int index,
           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;
}      

of_clk_get_hw_from_clkspec再根据clkspec查找到clk对于的clk_hw结构。由于在clk_imx8mp.c驱动的probe函数中提前讲hw clk provider注册进了clk框架,也就是我们在第三章提到了各类注册clk的数据。

of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_hw_data);      

对于我们这里ldb设备树中定义的clock index值(314),of_clk_get_hw_from_clkspec去hw clk provider中寻找clkspec所对应的索引数据。也就是我们在clk驱动中写入的ldb root相关的clk_gate信息。

hws[IMX8MP_CLK_MEDIA_LDB_ROOT] = imx_clk_hw_gate2_shared2("media_ldb_root_clk", "media_ldb", ccm_base + 0x45d0, 0, &share_count_media);      

clk_prepare_enable

clk_prepare_enable是.prepare和.enable函数的组合函数。先让clk framework做好,然后使能clk framework中的时钟资源。

ret = clk_prepare(clk);
if (ret)
    return ret;
ret = clk_enable(clk);      

clk_prepare的定义如下,clk_prepare可以睡眠,这使它与clk_enable不同。 在一个简单的例子中,如果操作可能会休眠,clk_prepare可以代替clk_enable来解除一个clk的门。 其中一个例子是一个通过I2c访问的clk。 在复杂的情况下,一个clk ungate操作可能需要一个快速和一个慢速部分。

正是这个原因,clk_prepare和clk_enable不是相互排斥的。 事实上,clk_prepare必须在clk_enable之前调用。clk_core_prepare_lock对与时钟的操作进行了加锁,然后调用 core->ops->prepare(core->hw)。clk_prepare会从parent开始逐级调用.prepare函数,例如在clk_pll14xx_prepare中会等待PLL锁定。

int clk_prepare(struct clk *clk)
{
  if (!clk)
    return 0;

  return clk_core_prepare_lock(clk->core);
}      

clk_enable

clk_enable的调用过程如下,最终会打开寄存器的gate enable位。

clk_enable(clk);
        clk->ops->enable(clk->hw);
        [resolves to...]
                clk_gate_enable(hw);
                [resolves struct clk gate with to_clk_gate(hw)]
                        clk_gate_set_bit(gate);      

clk_set_rate

clk_set_rate核心函数如下。

clk_set_rate(clk, rate);
      clk->ops->set_rate(clk, rate);
      clk->rate = clk->ops->recalc(clk);
      propagate_rate(clk);      

以IMX8MP_CLK_MEDIA_LDB举例,我们在驱动中需要将其设置为74.25*7MHz。在clk驱动中我们将其注册成了一个composite类型的clk,这意味着我们可以选择input source,设置分频,gate控制。

hws[IMX8MP_CLK_MEDIA_LDB] = imx8m_clk_hw_composite("media_ldb", imx8mp_media_ldb_sels, ccm_base + 0xbf00);      

因此在这里clk->ops->set_rate指imx8m_clk_composite_divider_set_rate,clk->ops->recalc(clk)指imx8m_clk_composite_divider_recalc_rate。

imx8m_clk_composite_divider_set_rate

imx8m_clk_composite_compute_dividers会根据所需的频率和parent source的频率计算预分频参数和分频参数,然后将两个参数设置进寄存器。

static int imx8m_clk_composite_divider_set_rate(struct clk_hw *hw,
          unsigned long rate,
          unsigned long parent_rate)
{
  struct clk_divider *divider = to_clk_divider(hw);
  unsigned long flags;
  int prediv_value;
  int div_value;
  int ret;
  u32 val;

  ret = imx8m_clk_composite_compute_dividers(rate, parent_rate,
            &prediv_value, &div_value);
  if (ret)
    return -EINVAL;

  spin_lock_irqsave(divider->lock, flags);

  val = readl(divider->reg);
  val &= ~((clk_div_mask(divider->width) << divider->shift) |
      (clk_div_mask(PCG_DIV_WIDTH) << PCG_DIV_SHIFT));

  val |= (u32)(prediv_value  - 1) << divider->shift;
  val |= (u32)(div_value - 1) << PCG_DIV_SHIFT;
  writel(val, divider->reg);

  spin_unlock_irqrestore(divider->lock, flags);

  return ret;
}      

imx8m_clk_composite_divider_recalc_rate

static unsigned long imx8m_clk_composite_divider_recalc_rate(struct clk_hw *hw,
            unsigned long parent_rate)
{
  struct clk_divider *divider = to_clk_divider(hw);
  unsigned long prediv_rate;
  unsigned int prediv_value;
  unsigned int div_value;

  prediv_value = readl(divider->reg) >> divider->shift;
  prediv_value &= clk_div_mask(divider->width);

  prediv_rate = divider_recalc_rate(hw, parent_rate, prediv_value,
            NULL, divider->flags,
            divider->width);

  div_value = readl(divider->reg) >> PCG_DIV_SHIFT;
  div_value &= clk_div_mask(PCG_DIV_WIDTH);

  return divider_recalc_rate(hw, prediv_rate, div_value, NULL,
           divider->flags, PCG_DIV_WIDTH);
}      

继续阅读