我是靠谱客的博主 传统煎蛋,最近开发中收集的这篇文章主要介绍Linux启动过程分析(十一)--PLL各个外设时钟频率的设置,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

这部分内容从Board-da850-sdi.c (archarmmach-davinci)开始:

MACHINE_START(DAVINCI_DA850_SDI, "DA850 SDI Development Board")
	.atag_offset	= 0x100,
	.map_io		= da850_evm_map_io,
	.init_irq	= cp_intc_init,
	.timer		= &davinci_timer,
	.init_machine	= da850_evm_init,
	.dma_zone_size	= SZ_128M,
	.restart	= da8xx_restart,
MACHINE_END

->

static void __init da850_evm_map_io(void)
{
	da850_init();
}

->

在这里插入代码片

void __init da850_init(void)
{
	unsigned int v;

	davinci_common_init(&davinci_soc_info_da850);

	da8xx_syscfg0_base = ioremap(DA8XX_SYSCFG0_BASE, SZ_4K);
	if (WARN(!da8xx_syscfg0_base, "Unable to map syscfg0 module"))
		return;

	da8xx_syscfg1_base = ioremap(DA8XX_SYSCFG1_BASE, SZ_4K);
	if (WARN(!da8xx_syscfg1_base, "Unable to map syscfg1 module"))
		return;

	/*
	 * Move the clock source of Async3 domain to PLL1 SYSCLK2.
	 * This helps keeping the peripherals on this domain insulated
	 * from CPU frequency changes caused by DVFS. The firmware sets
	 * both PLL0 and PLL1 to the same frequency so, there should not
	 * be any noticeable change even in non-DVFS use cases.
	 */
	da850_set_async3_src(1);

	/* Unlock writing to PLL0 registers */
	v = __raw_readl(DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP0_REG));
	v &= ~CFGCHIP0_PLL_MASTER_LOCK;
	__raw_writel(v, DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP0_REG));

	/* Unlock writing to PLL1 registers */
	v = __raw_readl(DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP3_REG));
	v &= ~CFGCHIP3_PLL1_MASTER_LOCK;
	__raw_writel(v, DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP3_REG));
}

先看davinci_common_init这个函数,因为我们关注的是和时钟相关的,所以进入其中的
davinci_clk_init(davinci_soc_info.cpu_clks)这个函数。
davici_soc_info这个结构体的定义在Da850.c (archarmmach-davinci)中,其中我们关注的是这一行:

.cpu_clks		= da850_clks,

再对da850_clks进行追踪,内容如下:


static struct clk_lookup da850_clks[] = {
	CLK(NULL,		"ref",		&ref_clk),
	CLK(NULL,		"pll0",		&pll0_clk),
	CLK(NULL,		"pll0_aux",	&pll0_aux_clk),
	CLK(NULL,		"pll0_sysclk1", &pll0_sysclk1),
	CLK(NULL,		"pll0_sysclk2",	&pll0_sysclk2),
	CLK(NULL,		"pll0_sysclk3",	&pll0_sysclk3),
	CLK(NULL,		"pll0_sysclk4",	&pll0_sysclk4),
	CLK(NULL,		"pll0_sysclk5",	&pll0_sysclk5),
	CLK(NULL,		"pll0_sysclk6",	&pll0_sysclk6),
	CLK(NULL,		"pll0_sysclk7",	&pll0_sysclk7),
	CLK(NULL,		"pll1",		&pll1_clk),
	CLK(NULL,		"pll1_aux",	&pll1_aux_clk),
	CLK(NULL,		"pll1_sysclk2",	&pll1_sysclk2),
	CLK(NULL,		"pll1_sysclk3",	&pll1_sysclk3),
	CLK("i2c_davinci.1",	NULL,		&i2c0_clk),
	CLK(NULL,		"timer0",	&timerp64_0_clk),
	CLK("watchdog",		NULL,		&timerp64_1_clk),
	CLK(NULL,		"timer2",	&timerp64_2_clk),
	CLK(NULL,		"timer3",	&timerp64_3_clk),
	CLK(NULL,		"dsp",		&dsp_clk),
	CLK(NULL,		"arm_rom",	&arm_rom_clk),
	CLK(NULL,		"tpcc0",	&tpcc0_clk),
	CLK(NULL,		"tptc0",	&tptc0_clk),
	CLK(NULL,		"tptc1",	&tptc1_clk),
	CLK(NULL,		"tpcc1",	&tpcc1_clk),
	CLK(NULL,		"tptc2",	&tptc2_clk),
	CLK(NULL,		"pruss",	&pruss_clk),
	CLK(NULL,		"uart0",	&uart0_clk),
	CLK(NULL,		"uart1",	&uart1_clk),
	CLK(NULL,		"uart2",	&uart2_clk),
	CLK(NULL,		"aintc",	&aintc_clk),
	CLK(NULL,		"gpio",		&gpio_clk),
	CLK("i2c_davinci.2",	NULL,		&i2c1_clk),
	CLK(NULL,		"emif3",	&emif3_clk),
	CLK(NULL,		"arm",		&arm_clk),
	CLK(NULL,		"rmii",		&rmii_clk),
	CLK("davinci_emac.1",	NULL,		&emac_clk),
	CLK("davinci-mcasp.0",	NULL,		&mcasp_clk),
	CLK("da8xx_lcdc.0",	NULL,		&lcdc_clk),
	CLK("davinci_mmc.0",	NULL,		&mmcsd0_clk),
	CLK("davinci_mmc.1",	NULL,		&mmcsd1_clk),
	CLK("davinci-mcbsp.1",	NULL,		&mcbsp1_clk),
	CLK(NULL,		"aemif",	&aemif_clk),
	CLK(NULL,		"usb11",	&usb11_clk),
	CLK(NULL,		"usb20",	&usb20_clk),
	CLK("spi_davinci.0",	NULL,		&spi0_clk),
	CLK("spi_davinci.1",	NULL,		&spi1_clk),
	CLK(NULL,		"vpif",		&vpif_clk),
	CLK("ahci",		NULL,		&sata_clk),
	CLK(NULL,               "ehrpwm",       &ehrpwm_clk),
	CLK(NULL,		"ecap",		&ecap_clk),
	CLK(NULL,		NULL,		NULL),
};

了解完了传入到davinci_clk_init中的参数,再来分析这个函数里面的具体操作:


int __init davinci_clk_init(struct clk_lookup *clocks)
  {
	struct clk_lookup *c;
	struct clk *clk;
	size_t num_clocks = 0;
/*依次遍历da850_clks数组中的每一项*/
	for (c = clocks; c->clk; c++) {
		clk = c->clk;//(1)

		if (!clk->recalc) {

			/* Check if clock is a PLL */
			if (clk->pll_data)
				clk->recalc = clk_pllclk_recalc;

			/* Else, if it is a PLL-derived clock */
			else if (clk->flags & CLK_PLL)
				clk->recalc = clk_sysclk_recalc;

			/* Otherwise, it is a leaf clock (PSC clock) */
			else if (clk->parent)
				clk->recalc = clk_leafclk_recalc;
		}

		if (clk->pll_data) {
			struct pll_data *pll = clk->pll_data;

			if (!pll->div_ratio_mask)
				pll->div_ratio_mask = PLLDIV_RATIO_MASK;

			if (pll->phys_base && !pll->base) {
				pll->base = ioremap(pll->phys_base, SZ_4K);
				WARN_ON(!pll->base);
			}
		}

		if (clk->recalc)
			clk->rate = clk->recalc(clk);

		if (clk->lpsc)
			clk->flags |= CLK_PSC;

		clk_register(clk);
		num_clocks++;

		/* Turn on clocks that Linux doesn't otherwise manage */
		if (clk->flags & ALWAYS_ENABLED)
			clk_enable(clk);
	}

	clkdev_add_table(clocks, num_clocks);

	return 0;
}

(1)c的类型是clk_lookup的结构体,这个结构体定义的形式如下:
CLK(NULL, “ref”, &ref_clk),
CLK的定义可以在Clock.h (archarmmach-davinci)中找到:

#define CLK(dev, con, ck) 	
	{			
		.dev_id = dev,	
		.con_id = con,	
		.clk = ck,	
	}	

由此可以得出:类似CLK(NULL, “ref”, &ref_clk)这样的定义形式,
c->clk指向的就是ref_clk;
而ref_clk也是一个结构体,它的内容如下:


static struct clk ref_clk = {
	.name		= "ref_clk",
	.rate		= DA850_REF_FREQ,
	.set_rate	= davinci_simple_set_rate,
};

最后,将c->clk的值指向了clk,
经过分析,实际最终执行的是:

clk_register(clk)
->
if (clk->rate)
return 0;

所以相当于设置了一个DA850_REF_FREQ=24000000的参考时钟
再来分析几个其它的pll时钟设置


static struct clk pll0_clk = {
	.name		= "pll0",
	.parent		= &ref_clk,
	.pll_data	= &pll0_data,
	.flags		= CLK_PLL,
	.set_rate	= da850_set_pll0rate,
};

在davinci_clk_init中找到对应的执行函数,因为pll0_clk中没有recalc的定义,所以(if!recalc)成立;

if (!clk->recalc) {

			/* Check if clock is a PLL */
			/*因为pll0_clk中有pll_data的定义,所以执行第一个if下面的语句*/
			if (clk->pll_data)
			/*
				clk->recalc = clk_pllclk_recalc;

			/* Else, if it is a PLL-derived clock */
			else if (clk->flags & CLK_PLL)
				clk->recalc = clk_sysclk_recalc;

			/* Otherwise, it is a leaf clock (PSC clock) */
			else if (clk->parent)
				clk->recalc = clk_leafclk_recalc;
		}

		

clk->recalc = clk_pllclk_recalc;

继续分析 davinci_clk_init:

/*&pll0_data,*/
if (clk->pll_data) {

           /*pll=pll0_data*/
			struct pll_data *pll = clk->pll_data;
			/*pll0_data中没有对div_ratio_mask的定义,所以会往下执行*/
			if (!pll->div_ratio_mask)
			
			/*pll->div_ratio_mask= PLLDIV_RATIO_MASK= 0x1f*/
				pll->div_ratio_mask = PLLDIV_RATIO_MASK;
				
			/*pll->phys_base在这里指向phys_base	= DA8XX_PLL0_BASE而且pll0_data中的pll->base
			确实没有定义*/
			if (pll->phys_base && !pll->base) {
			/*io_remap用来将 DA8XX_PLL0_BASE这个地址空间映射到内核的虚拟地址空间上去,便于访问*/
				pll->base = ioremap(pll->phys_base, SZ_4K);
				WARN_ON(!pll->base);
			}
		}

接下来还有几个函数需要分析

if (clk->recalc)
			clk->rate = clk->recalc(clk);

		if (clk->lpsc)
			clk->flags |= CLK_PSC;

在上面的分析中已经将 clk_pllclk_recalc赋值给clk->recalc ;
所以这里执行的就是,
那么实际上这个函数就是
clk->rate=clk_pllclk_recalc(pll0_clk)
下面再来分析一下clk_pllclk_recalc(pll0_clk)这个函数:


static unsigned long clk_pllclk_recalc(struct clk *clk)
{
	u32 ctrl, mult = 1, prediv = 1, postdiv = 1;
	u8 bypass;
	
	/*struct pll_data *pll=&pll0_data*/
	struct pll_data *pll = clk->pll_data;
	
	unsigned long rate = clk->rate;
	
/*
static struct pll_data pll0_data = {
	.num		= 1,
	.phys_base	= DA8XX_PLL0_BASE,
	.flags		= PLL_HAS_PREDIV | PLL_HAS_POSTDIV,
}
实际上读出的是PLLC0 Control Register的配置
*/
	ctrl = __raw_readl(pll->base + PLLCTL);
	
	/*rate=pll->input_rate=ref-clk->rate=DA850_REF_FREQ=24000 000*/
	rate = pll->input_rate = clk->parent->rate;

/*判断PLLC0 Control Register的第0位PLLEN到底配置为0还是1,如果是0的话,PLL0在bypass模式,如果
是1的话,PLL0模式被使能,而不是bypass模式*/
	if (ctrl & PLLCTL_PLLEN) {
	/*;这样的话!bypass=1,而使
		rate /= prediv;
		rate *= mult;
		rate /= postdiv;
		得以执行,从而使设置的倍频,预分频和后分频生效
		*/
		bypass = 0
		/读出PLLC0 PLL Multiplier Control Register的配置*/
		mult = __raw_readl(pll->base + PLLM);
		if (cpu_is_davinci_dm365())
			mult = 2 * (mult & PLLM_PLLM_MASK);
		else
		
		/*PLL multiplier select. Multiplier Value = PLLM + 1.*/
			mult = (mult & PLLM_PLLM_MASK) + 1;
	} else
		bypass = 1;
/*根据PLLC0 Pre-Divider Control Register (PREDIV)的配置,进行预分频*/
	if (pll->flags & PLL_HAS_PREDIV) {
		prediv = __raw_readl(pll->base + PREDIV);
		if (prediv & PLLDIV_EN)
			prediv = (prediv & pll->div_ratio_mask) + 1;
		else
			prediv = 1;
	}

	/* pre-divider is fixed, but (some?) chips won't report that */
	if (cpu_is_davinci_dm355() && pll->num == 1)
		prediv = 8;
/*根据PLLC0 PLL Post-Divider Control Register的配置,进行后分频*/
	if (pll->flags & PLL_HAS_POSTDIV) {
		postdiv = __raw_readl(pll->base + POSTDIV);
		if (postdiv & PLLDIV_EN)
			postdiv = (postdiv & pll->div_ratio_mask) + 1;
		else
			postdiv = 1;
	}
/*根据预分频,倍频,后分频的值,对频率进行改变*/
	if (!bypass) {
		rate /= prediv;
		rate *= mult;
		rate /= postdiv;
	}

	pr_debug("PLL%d: input = %lu MHz [ ",
		 pll->num, clk->parent->rate / 1000000);
	if (bypass)
		pr_debug("bypass ");
	if (prediv > 1)
		pr_debug("/ %d ", prediv);
	if (mult > 1)
		pr_debug("* %d ", mult);
	if (postdiv > 1)
		pr_debug("/ %d ", postdiv);
	pr_debug("] --> %lu MHz output.n", rate / 1000000);

	return rate;
}

PLLC0 Control Register等寄存器根据uboot.ais中的ROM进行初始化,参照https://blog.csdn.net/qq_40788950/article/details/85725898,PLL0的预分频为1,倍频为19,后分频为1。
得到的rate为456MHz。

static struct clk pll0_aux_clk = {
	.name		= "pll0_aux_clk",
	.parent		= &pll0_clk,
	.flags		= CLK_PLL | PRE_PLL,
};

它满足:

/* Else, if it is a PLL-derived clock */
			else if (clk->flags & CLK_PLL)
				clk->recalc = clk_sysclk_recalc;

接着执行:
if (clk->recalc)
clk->rate = clk->recalc(clk);
即clk->rate =clk_sysclk_recalc(pll0_aux_clk);

static unsigned long clk_sysclk_recalc(struct clk *clk)
{
	u32 v, plldiv;
	struct pll_data *pll;
	unsigned long rate = clk->rate;

	/* If this is the PLL base clock, no more calculations needed */
	if (clk->pll_data)
		return rate;

	if (WARN_ON(!clk->parent))
		return rate;

	rate = clk->parent->rate;

	/* Otherwise, the parent must be a PLL */
	if (WARN_ON(!clk->parent->pll_data))
		return rate;
/*pll=pll0_clk->pll_data=pll0_data*/
	pll = clk->parent->pll_data;
/*rate=pll0_data->input_rate=
	/* If pre-PLL, source clock is before the multiplier and divider(s) */
	if (clk->flags & PRE_PLL)
		rate = pll->input_rate;

	if (!clk->div_reg)
		return rate;

	v = __raw_readl(pll->base + clk->div_reg);
	if (v & PLLDIV_EN) {
		plldiv = (v & pll->div_ratio_mask) + 1;
		if (plldiv)
			rate /= plldiv;
	}

	return rate;
}

执行完之后,返回的rate是24Mhz。
再来分析:

static struct clk pll0_sysclk1 = {
	.name           = "pll0_sysclk1",
	.parent         = &pll0_clk,
	.flags          = CLK_PLL,
	.div_reg        = PLLDIV1,
};

和上一个类型,调用的函数是:

clk->rate = clk->recalc(clk);

即clk->rate =clk_sysclk_recalc( pll0_sysclk1);

rate = clk->parent->rate=456MHz;
pll = clk->parent->pll_data;

pll= &pll0_data

	但是在下一个if语句的判断上:
	if (!clk->div_reg)
		return rate;
	

是不满足的,所以不会返回,而是继续执行
pll0_sysclk1.div_reg = PLLDIV1,

v = __raw_readl(pll->base + clk->div_reg);
	if (v & PLLDIV_EN) {
		plldiv = (v & pll->div_ratio_mask) + 1;
		if (plldiv)
			rate /= plldiv;
	}

	return rate;

#define PLLDIV1 0x118
所以实际上v读取到的是PLLC0 Pre-Divider Control Register的值,如果预分频被使能的话,就会进行预分频,预分频值为1,所以最终的rate值为456MHz。
下面再分析一个dsp的CLOCK。

static struct clk dsp_clk = {
	.name           = "dsp",
	.parent         = &pll0_sysclk1,
	.domain 	= DAVINCI_GPSC_DSPDOMAIN,
	.lpsc           = DA8XX_LPSC0_GEM,
	.flags          = ALWAYS_ENABLED,
};

执行如下的函数:

clk_leafclk_recalc(dsp_clk)
{
	if (WARN_ON(!clk->parent))
		return clk->rate;

	return clk->parent->rate;
}

clk->parent->rate=pll0_sysclk1->rate=456Mhz。
所以DSP的主频也达到了456Mhz.
再来分析一下串口0的时钟:

 static struct clk uart0_clk = {
	.name		= "uart0",
	.parent		= &pll0_sysclk2,
	.lpsc		= DA8XX_LPSC0_UART0,
};

clk->parent->rate=pll0_sysclk2->rate
所以要先分析pll0_sysclk2的时钟来源,仿照上面pll0_sysclk1的分析,可以得到它的时钟速率为228Mhz。

最后

以上就是传统煎蛋为你收集整理的Linux启动过程分析(十一)--PLL各个外设时钟频率的设置的全部内容,希望文章能够帮你解决Linux启动过程分析(十一)--PLL各个外设时钟频率的设置所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(85)

评论列表共有 0 条评论

立即
投稿
返回
顶部