Exynos4412裸机开发系列教程--时钟管理

有了前几章的基础教程,我相信大家已经迫不及待的想编写main函数了,是的,完全可以这样,跟MCU有何差异呢,直接操作寄存器就可以了实现各种外设的开发工作了。但是、但是,我们是不是可以做的更好呢,STM32单片机为何能流行,只是因为性价比?我想更为关键的还是ST公司不遗余力的开发出一套可能方便快捷调用的函数库,开发效率高,周期短,完成度高,才是一款方案的真正竞争力。

好了,我们也需要将我们所常用的各种外设编写成可以方便调用的库函数。其实大家看过前面的教程就可以发现,有这样的趋势了,比如LED灯,蜂鸣器示例,都是先包装成方便调用接口了。回忆一下当时的定义:

LED灯的库函数头文件:

enum led_name {
	LED_NAME_LED1		= 1,
	LED_NAME_LED2		= 2,
	LED_NAME_LED3		= 3,
	LED_NAME_LED4		= 4,
};

enum led_status {
	LED_STATUS_OFF		= 0,
	LED_STATUS_ON		= 1,
};

void led_initial(void);
void led_set_status(enum led_name name, enum led_status status);
蜂鸣器的库函数头文件:

enum beep_status {
	BEEP_STATUS_OFF		= 0,
	BEEP_STATUS_ON		= 1,
};

void beep_initial(void);
void beep_set_status(enum beep_status status);
恩,这看起来,都是些比较简单的外设驱动包装,比如再复杂点呢,像时钟,中断,串口,液晶屏显示这些,也可以这样么,回答当然是肯定的,先从时钟管理开始,因为是裸机,我们包装也不需太过高层次,比如支持调用参考计数,支持父子关系,像内核时钟管理一样强大,就做过了,简洁好用,才是王道。

其实,时钟管理,本质上是要能提供一个方便调用的接口函数,给各种外设驱动调用,比如串口,需要设置波特率,那我们不可能使用硬编码那种十分不灵活的方式,我们完全可以写出一个公式,自动进行各种波特率运算,代码可读性强,可移植性高。

首先,我们时钟管理只需提供一个获取某个时钟的频率即可,只读方式,不提供设置功能,这样,我们的问题就简化很多,定义如下:

bool_t clk_get_rate(const char * name, u64_t * rate);

那我们如果实现这个函数呢,看代码:

struct clk_t
{
	/* The clk name */
	const char * name;

	/* The clk's rate, HZ */
	u64_t rate;
};

bool_t clk_get_rate(const char * name, u64_t * rate)
{
	int i;
	for(i=0; i< ARRAY_SIZE(exynos4412_clocks); i++)
	{
		if(strcmp(exynos4412_clocks[i].name, name) == 0)
		{
			if(rate)
				*rate = exynos4412_clocks[i].rate;
			return TRUE;
		}
	}

	return FALSE;
}
是从一个数组里匹配相应的时钟名字,然后返回该数组里标注的时钟值。那这个数组又是怎么来的呢
static u64_t exynos4412_get_pll_clk(u64_t fin, enum EXYNOS4412_PLL pll)
{
	u32_t r, k, m, p, s;
	u64_t fout;

	switch(pll)
	{
	/*
	 * FOUT = MDIV * FIN / (PDIV * 2^(SDIV))
	 */
	case EXYNOS4412_APLL:
		r = readl(EXYNOS4412_APLL_CON0);
		m = (r >> 16) & 0x3ff;
		p = (r >> 8) & 0x3f;
		s = r & 0x7;
		fout = m * (fin / (p * (1 << s)));
		break;

	/*
	 * FOUT = MDIV * FIN / (PDIV * 2^(SDIV))
	 */
	case EXYNOS4412_MPLL:
		r = readl(EXYNOS4412_MPLL_CON0);
		m = (r >> 16) & 0x3ff;
		p = (r >> 8) & 0x3f;
		s = r & 0x7;
		fout = m * (fin / (p * (1 << s)));
		break;

	/*
	 * FOUT = (MDIV + K / 65536) * FIN / (PDIV * 2^SDIV)
	 */
	case EXYNOS4412_EPLL:
		r = readl(EXYNOS4412_EPLL_CON0);
		k = readl(EXYNOS4412_EPLL_CON1);
		m = (r >> 16) & 0x1ff;
		p = (r >> 8) & 0x3f;
		s = r & 0x7;
		k = k & 0xffff;
		fout = (m + k / 65536) * (fin / (p * (1 << s)));
		break;

	/*
	 * FOUT = (MDIV + K / 65535) * FIN / (PDIV * 2^SDIV)
	 */
	case EXYNOS4412_VPLL:
		r = readl(EXYNOS4412_VPLL_CON0);
		k = readl(EXYNOS4412_VPLL_CON1);
		m = (r >> 16) & 0x1ff;
		p = (r >> 8) & 0x3f;
		s = r & 0x7;
		k = k & 0xffff;
		fout = (m + k / 65535) * (fin / (p * (1 << s)));
		break;

	default:
		return 0;
	}

	return fout;
}

static u64_t exynos4412_get_mout_apll(u64_t fin)
{
	u32_t reg;
	u64_t ret;

	reg = readl(EXYNOS4412_CLK_SRC_CPU);

	if( ((reg >> 0) & 0x1) == 0x1 )
		ret = exynos4412_get_pll_clk(fin, EXYNOS4412_APLL);
	else
		ret = fin;

	return ret;
}

static u64_t exynos4412_get_sclk_apll(u64_t fin)
{
	u32_t reg;
	u64_t ret, mout_apll;

	mout_apll = exynos4412_get_mout_apll(fin);
	reg = (readl(EXYNOS4412_CLK_DIV_CPU0) >> 24) & 0x7;

	ret = mout_apll / (reg + 1);
	return ret;
}

static u64_t exynos4412_get_mout_mpll(u64_t fin)
{
	u32_t reg;
	u64_t ret;

	reg = readl(EXYNOS4412_CLK_SRC_CPU);

	if( ((reg >> 24) & 0x1) == 0x1 )
		ret = exynos4412_get_pll_clk(fin, EXYNOS4412_MPLL);
	else
		ret = fin;

	return ret;
}

static u64_t exynos4412_get_sclk_mpll(u64_t fin)
{
	return exynos4412_get_mout_mpll(fin);
}

static u64_t exynos4412_get_sclk_mpll_user_c(u64_t fin)
{
	return exynos4412_get_sclk_mpll(fin);
}

static u64_t exynos4412_get_sclk_mpll_user_t(u64_t fin)
{
	return exynos4412_get_sclk_mpll(fin);
}

static u64_t exynos4412_get_mout_core(u64_t fin)
{
	u32_t reg;
	u64_t ret;

	reg = readl(EXYNOS4412_CLK_SRC_CPU);

	if( ((reg >> 16) & 0x1) == 0x1 )
		ret = exynos4412_get_sclk_mpll_user_c(fin);
	else
		ret = exynos4412_get_mout_apll(fin);

	return ret;
}

static u64_t exynos4412_get_sclk_epll(u64_t fin)
{
	u32_t reg;
	u64_t ret;

	reg = readl(EXYNOS4412_CLK_SRC_TOP0);

	if( ((reg >> 4) & 0x1) == 0x1 )
		ret = exynos4412_get_pll_clk(fin, EXYNOS4412_EPLL);
	else
		ret = fin;

	return ret;
}

static u64_t exynos4412_get_sclk_vpll(u64_t fin)
{
	u32_t reg;
	u64_t ret;

	reg = readl(EXYNOS4412_CLK_SRC_TOP0);

	if( ((reg >> 8) & 0x1) == 0x1 )
		ret = exynos4412_get_pll_clk(fin, EXYNOS4412_VPLL);
	else
		ret = fin;

	return ret;
}

static u64_t exynos4412_get_armclk(u64_t fin)
{
	u32_t reg, core, core2;
	u64_t ret, mout_core;

	mout_core = exynos4412_get_mout_core(fin);
	reg = readl(EXYNOS4412_CLK_DIV_CPU0);
	core = (reg >> 0) & 0x7;
	core2 = (reg >> 28) & 0x7;

	ret = mout_core / (core + 1) / (core2 + 1);
	return ret;
}

static u64_t exynos4412_get_sclk_uart(u64_t fin, u32_t ch)
{
	u32_t reg;
	u64_t ret, mout_uart;

	if(ch < 0 || ch > 4)
		return 0;

	reg = (readl(EXYNOS4412_CLK_SRC_PERIL0) >> (ch * 4)) & 0xf;
	switch(reg)
	{
	case 0x6:
		mout_uart = exynos4412_get_sclk_mpll_user_t(fin);
		break;

	case 0x7:
		mout_uart = exynos4412_get_sclk_epll(fin);
		break;

	case 0x8:
		mout_uart = exynos4412_get_sclk_vpll(fin);
		break;

	default:
		mout_uart = fin;
		break;
	}

	reg = (readl(EXYNOS4412_CLK_DIV_PERIL0) >> (ch * 4)) & 0xf;
	ret = mout_uart / (reg + 1);

	return ret;
}

/*
 * the array of clocks, which will to be setup.
 */
static struct clk_t exynos4412_clocks[13];

/*
 * setup the exynos4412's clock array.
 */
static void exynos4412_setup_clocks(u64_t xtal)
{
	exynos4412_clocks[0].name = "xtal";
	exynos4412_clocks[0].rate = xtal;

	exynos4412_clocks[1].name = "sclk_apll";
	exynos4412_clocks[1].rate = exynos4412_get_sclk_apll(xtal);

	exynos4412_clocks[2].name = "sclk_mpll";
	exynos4412_clocks[2].rate = exynos4412_get_sclk_mpll(xtal);

	exynos4412_clocks[3].name = "sclk_epll";
	exynos4412_clocks[3].rate = exynos4412_get_sclk_epll(xtal);

	exynos4412_clocks[4].name = "sclk_vpll";
	exynos4412_clocks[4].rate = exynos4412_get_sclk_vpll(xtal);

	exynos4412_clocks[5].name = "armclk";
	exynos4412_clocks[5].rate = exynos4412_get_armclk(xtal);

	exynos4412_clocks[6].name = "sclk_uart0";
	exynos4412_clocks[6].rate = exynos4412_get_sclk_uart(xtal, 0);

	exynos4412_clocks[7].name = "sclk_uart1";
	exynos4412_clocks[7].rate = exynos4412_get_sclk_uart(xtal, 1);

	exynos4412_clocks[8].name = "sclk_uart2";
	exynos4412_clocks[8].rate = exynos4412_get_sclk_uart(xtal, 2);

	exynos4412_clocks[9].name = "sclk_uart3";
	exynos4412_clocks[9].rate = exynos4412_get_sclk_uart(xtal, 3);

	exynos4412_clocks[10].name = "sclk_uart4";
	exynos4412_clocks[10].rate = exynos4412_get_sclk_uart(xtal, 4);

	exynos4412_clocks[11].name = "fimd";
	exynos4412_clocks[11].rate = 200 * 1000 * 1000;

	exynos4412_clocks[12].name = "pclk";
	exynos4412_clocks[12].rate = 100 * 1000 * 1000;
}

根据当前寄存器的配置,将相应的时钟名及时钟值直接赋值到查询数组,时钟值就是手册可以写出运算公式,直接换算出来结果,后期调用时,无需再次计算。

举个示例,LCD驱动里就需要获取当前的fimd时钟,然后根据fimd时钟以及屏参,计算出液晶屏的像素时钟,并设置相应寄存器。

static bool_t exynos4412_fb_set_clock(struct exynos4412_fb_data_t * dat)
{
	u64_t fimd, pixel_clock;
	u32_t div;
	u32_t cfg;

	if(!clk_get_rate("fimd", &fimd))
		return FALSE;

	pixel_clock = ( dat->freq * (dat->timing.h_fp + dat->timing.h_bp + dat->timing.h_sw + dat->width) *
			(dat->timing.v_fp + dat->timing.v_bp + dat->timing.v_sw + dat->height) );

	div = (u32_t)(fimd / pixel_clock);
	if((fimd % pixel_clock) > 0)
		div++;

	cfg = readl(dat->regbase + VIDCON0);
	cfg &= ~( (1 << 16) | (1 << 5) );
	cfg |= VIDCON0_CLKVAL_F(div - 1);
	writel(dat->regbase + VIDCON0, cfg);

	return TRUE;
}
本次教程就讲到这里,下一个教程,我们需要看看如何实现中断管理,貌似这个麻烦了点。有疑问的网友可以直接留言,或者加QQ8192542,欢迎探讨。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值