天天看點

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

繼續閱讀