0
点赞
收藏
分享

微信扫一扫

i.MX8MPlus中的CLK子系统

zhaoxj0217 2022-04-19 阅读 58

芯片手册中的clk框架

CCM(Clock Control Module)框架图

image-20220330195915349

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

CLK ROOT相关寄存器

image-20220330202340123

每一个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。

image-20220330204755810 image-20220330204817223

PLL的计算公式如下:
F o u t = ( m + k 65536 ) ∗ F i n p ∗ 2 s F_{out} = \frac{(m+\frac{k}{65536})*F_{in}}{p*2^s} Fout=p2s(m+65536k)Fin

在频率范围内的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。

image-20220330210902568

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

image-20220330211442130

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

image-20220330211656751

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声明其依赖性。不允许使用任何时钟,而不在自己的域中声明。域通过写入依赖级别来声明其对时钟的依赖性。针对低功耗模式下行为的设置如下:

image-20220330221646165

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

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

image-20220330221715554

子CLK分频公式

F r e q = ( c l o c k s o u r c e f r e q ) ( p r e _ d i v + 1 ) ∗ ( p o s t _ d i v + 1 ) Freq = \frac{(clock source freq)}{(pre\_div+1)*(post\_div+1)} Freq=(pre_div+1)(post_div+1)(clocksourcefreq)

驱动中clk的一些概念

(1)fixed rate clock

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

image-20220327183934868 image-20220329220935311

(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。

image-20220329220732900

(3)mux clock

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

image-20220329221756156

(4)divider clock

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

image-20220329222033449

(5)gate clock

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

image-20220329222117052

(6)composite clock

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

image-20220329222137013

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。

image-20220401215412906

__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。

image-20220401220547940

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框架。注意整个注册过程必须锁定。

image-20220403161607307

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,供驱动调用。这里第三个参数是寄存器地址。

image-20220406191820977

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位。

image-20220407203210040

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

image-20220407202611334

(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,

image-20220408200009778 image-20220408195321948

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

image-20220408195346844 image-20220408195422769
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

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

相关推荐

0 条评论