mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-18 16:08:53 +09:00
![Tom Rini](/assets/img/avatar_default.png)
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
436 lines
10 KiB
C
436 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Freescale i.MX23/i.MX28 clock setup code
|
|
*
|
|
* Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
|
|
* on behalf of DENX Software Engineering GmbH
|
|
*
|
|
* Based on code from LTIB:
|
|
* Copyright (C) 2010 Freescale Semiconductor, Inc.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <linux/errno.h>
|
|
#include <asm/io.h>
|
|
#include <asm/arch/clock.h>
|
|
#include <asm/arch/imx-regs.h>
|
|
|
|
/*
|
|
* The PLL frequency is 480MHz and XTAL frequency is 24MHz
|
|
* iMX23: datasheet section 4.2
|
|
* iMX28: datasheet section 10.2
|
|
*/
|
|
#define PLL_FREQ_KHZ 480000
|
|
#define PLL_FREQ_COEF 18
|
|
#define XTAL_FREQ_KHZ 24000
|
|
|
|
#define PLL_FREQ_MHZ (PLL_FREQ_KHZ / 1000)
|
|
#define XTAL_FREQ_MHZ (XTAL_FREQ_KHZ / 1000)
|
|
|
|
#if defined(CONFIG_MX23)
|
|
#define MXC_SSPCLK_MAX MXC_SSPCLK0
|
|
#elif defined(CONFIG_MX28)
|
|
#define MXC_SSPCLK_MAX MXC_SSPCLK3
|
|
#endif
|
|
|
|
static uint32_t mxs_get_pclk(void)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
|
|
uint32_t clkctrl, clkseq, div;
|
|
uint8_t clkfrac, frac;
|
|
|
|
clkctrl = readl(&clkctrl_regs->hw_clkctrl_cpu);
|
|
|
|
/* No support of fractional divider calculation */
|
|
if (clkctrl &
|
|
(CLKCTRL_CPU_DIV_XTAL_FRAC_EN | CLKCTRL_CPU_DIV_CPU_FRAC_EN)) {
|
|
return 0;
|
|
}
|
|
|
|
clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
|
|
|
|
/* XTAL Path */
|
|
if (clkseq & CLKCTRL_CLKSEQ_BYPASS_CPU) {
|
|
div = (clkctrl & CLKCTRL_CPU_DIV_XTAL_MASK) >>
|
|
CLKCTRL_CPU_DIV_XTAL_OFFSET;
|
|
return XTAL_FREQ_MHZ / div;
|
|
}
|
|
|
|
/* REF Path */
|
|
clkfrac = readb(&clkctrl_regs->hw_clkctrl_frac0[CLKCTRL_FRAC0_CPU]);
|
|
frac = clkfrac & CLKCTRL_FRAC_FRAC_MASK;
|
|
div = clkctrl & CLKCTRL_CPU_DIV_CPU_MASK;
|
|
return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
|
|
}
|
|
|
|
static uint32_t mxs_get_hclk(void)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
|
|
uint32_t div;
|
|
uint32_t clkctrl;
|
|
|
|
clkctrl = readl(&clkctrl_regs->hw_clkctrl_hbus);
|
|
|
|
/* No support of fractional divider calculation */
|
|
if (clkctrl & CLKCTRL_HBUS_DIV_FRAC_EN)
|
|
return 0;
|
|
|
|
div = clkctrl & CLKCTRL_HBUS_DIV_MASK;
|
|
return mxs_get_pclk() / div;
|
|
}
|
|
|
|
static uint32_t mxs_get_emiclk(void)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
|
|
uint32_t clkctrl, clkseq, div;
|
|
uint8_t clkfrac, frac;
|
|
|
|
clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
|
|
clkctrl = readl(&clkctrl_regs->hw_clkctrl_emi);
|
|
|
|
/* XTAL Path */
|
|
if (clkseq & CLKCTRL_CLKSEQ_BYPASS_EMI) {
|
|
div = (clkctrl & CLKCTRL_EMI_DIV_XTAL_MASK) >>
|
|
CLKCTRL_EMI_DIV_XTAL_OFFSET;
|
|
return XTAL_FREQ_MHZ / div;
|
|
}
|
|
|
|
/* REF Path */
|
|
clkfrac = readb(&clkctrl_regs->hw_clkctrl_frac0[CLKCTRL_FRAC0_EMI]);
|
|
frac = clkfrac & CLKCTRL_FRAC_FRAC_MASK;
|
|
div = clkctrl & CLKCTRL_EMI_DIV_EMI_MASK;
|
|
return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
|
|
}
|
|
|
|
static uint32_t mxs_get_gpmiclk(void)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
#if defined(CONFIG_MX23)
|
|
uint8_t *reg =
|
|
&clkctrl_regs->hw_clkctrl_frac0[CLKCTRL_FRAC0_CPU];
|
|
#elif defined(CONFIG_MX28)
|
|
uint8_t *reg =
|
|
&clkctrl_regs->hw_clkctrl_frac1[CLKCTRL_FRAC1_GPMI];
|
|
#endif
|
|
uint32_t clkctrl, clkseq, div;
|
|
uint8_t clkfrac, frac;
|
|
|
|
clkseq = readl(&clkctrl_regs->hw_clkctrl_clkseq);
|
|
clkctrl = readl(&clkctrl_regs->hw_clkctrl_gpmi);
|
|
|
|
/* XTAL Path */
|
|
if (clkseq & CLKCTRL_CLKSEQ_BYPASS_GPMI) {
|
|
div = clkctrl & CLKCTRL_GPMI_DIV_MASK;
|
|
return XTAL_FREQ_MHZ / div;
|
|
}
|
|
|
|
/* REF Path */
|
|
clkfrac = readb(reg);
|
|
frac = clkfrac & CLKCTRL_FRAC_FRAC_MASK;
|
|
div = clkctrl & CLKCTRL_GPMI_DIV_MASK;
|
|
return (PLL_FREQ_MHZ * PLL_FREQ_COEF / frac) / div;
|
|
}
|
|
|
|
/*
|
|
* Set IO clock frequency, in kHz
|
|
*/
|
|
void mxs_set_ioclk(enum mxs_ioclock io, uint32_t freq)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
uint32_t div;
|
|
int io_reg;
|
|
|
|
if (freq == 0)
|
|
return;
|
|
|
|
if ((io < MXC_IOCLK0) || (io > MXC_IOCLK1))
|
|
return;
|
|
|
|
div = (PLL_FREQ_KHZ * PLL_FREQ_COEF) / freq;
|
|
|
|
if (div < 18)
|
|
div = 18;
|
|
|
|
if (div > 35)
|
|
div = 35;
|
|
|
|
io_reg = CLKCTRL_FRAC0_IO0 - io; /* Register order is reversed */
|
|
writeb(CLKCTRL_FRAC_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_frac0_set[io_reg]);
|
|
writeb(CLKCTRL_FRAC_CLKGATE | (div & CLKCTRL_FRAC_FRAC_MASK),
|
|
&clkctrl_regs->hw_clkctrl_frac0[io_reg]);
|
|
writeb(CLKCTRL_FRAC_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_frac0_clr[io_reg]);
|
|
}
|
|
|
|
/*
|
|
* Get IO clock, returns IO clock in kHz
|
|
*/
|
|
static uint32_t mxs_get_ioclk(enum mxs_ioclock io)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
uint8_t ret;
|
|
int io_reg;
|
|
|
|
if ((io < MXC_IOCLK0) || (io > MXC_IOCLK1))
|
|
return 0;
|
|
|
|
io_reg = CLKCTRL_FRAC0_IO0 - io; /* Register order is reversed */
|
|
|
|
ret = readb(&clkctrl_regs->hw_clkctrl_frac0[io_reg]) &
|
|
CLKCTRL_FRAC_FRAC_MASK;
|
|
|
|
return (PLL_FREQ_KHZ * PLL_FREQ_COEF) / ret;
|
|
}
|
|
|
|
/*
|
|
* Configure SSP clock frequency, in kHz
|
|
*/
|
|
void mxs_set_sspclk(enum mxs_sspclock ssp, uint32_t freq, int xtal)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
uint32_t clk, clkreg;
|
|
|
|
if (ssp > MXC_SSPCLK_MAX)
|
|
return;
|
|
|
|
clkreg = (uint32_t)(&clkctrl_regs->hw_clkctrl_ssp0) +
|
|
(ssp * sizeof(struct mxs_register_32));
|
|
|
|
clrbits_le32(clkreg, CLKCTRL_SSP_CLKGATE);
|
|
while (readl(clkreg) & CLKCTRL_SSP_CLKGATE)
|
|
;
|
|
|
|
if (xtal)
|
|
clk = XTAL_FREQ_KHZ;
|
|
else
|
|
clk = mxs_get_ioclk(ssp >> 1);
|
|
|
|
if (freq > clk)
|
|
return;
|
|
|
|
/* Calculate the divider and cap it if necessary */
|
|
clk /= freq;
|
|
if (clk > CLKCTRL_SSP_DIV_MASK)
|
|
clk = CLKCTRL_SSP_DIV_MASK;
|
|
|
|
clrsetbits_le32(clkreg, CLKCTRL_SSP_DIV_MASK, clk);
|
|
while (readl(clkreg) & CLKCTRL_SSP_BUSY)
|
|
;
|
|
|
|
if (xtal)
|
|
writel(CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp,
|
|
&clkctrl_regs->hw_clkctrl_clkseq_set);
|
|
else
|
|
writel(CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp,
|
|
&clkctrl_regs->hw_clkctrl_clkseq_clr);
|
|
}
|
|
|
|
/*
|
|
* Return SSP frequency, in kHz
|
|
*/
|
|
static uint32_t mxs_get_sspclk(enum mxs_sspclock ssp)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
uint32_t clkreg;
|
|
uint32_t clk, tmp;
|
|
|
|
if (ssp > MXC_SSPCLK_MAX)
|
|
return 0;
|
|
|
|
tmp = readl(&clkctrl_regs->hw_clkctrl_clkseq);
|
|
if (tmp & (CLKCTRL_CLKSEQ_BYPASS_SSP0 << ssp))
|
|
return XTAL_FREQ_KHZ;
|
|
|
|
clkreg = (uint32_t)(&clkctrl_regs->hw_clkctrl_ssp0) +
|
|
(ssp * sizeof(struct mxs_register_32));
|
|
|
|
tmp = readl(clkreg) & CLKCTRL_SSP_DIV_MASK;
|
|
|
|
if (tmp == 0)
|
|
return 0;
|
|
|
|
clk = mxs_get_ioclk(ssp >> 1);
|
|
|
|
return clk / tmp;
|
|
}
|
|
|
|
/*
|
|
* Set SSP/MMC bus frequency, in kHz)
|
|
*/
|
|
void mxs_set_ssp_busclock(unsigned int bus, uint32_t freq)
|
|
{
|
|
struct mxs_ssp_regs *ssp_regs;
|
|
const enum mxs_sspclock clk = mxs_ssp_clock_by_bus(bus);
|
|
const uint32_t sspclk = mxs_get_sspclk(clk);
|
|
uint32_t reg;
|
|
uint32_t divide, rate, tgtclk;
|
|
|
|
ssp_regs = mxs_ssp_regs_by_bus(bus);
|
|
|
|
/*
|
|
* SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)),
|
|
* CLOCK_DIVIDE has to be an even value from 2 to 254, and
|
|
* CLOCK_RATE could be any integer from 0 to 255.
|
|
*/
|
|
for (divide = 2; divide < 254; divide += 2) {
|
|
rate = sspclk / freq / divide;
|
|
if (rate <= 256)
|
|
break;
|
|
}
|
|
|
|
tgtclk = sspclk / divide / rate;
|
|
while (tgtclk > freq) {
|
|
rate++;
|
|
tgtclk = sspclk / divide / rate;
|
|
}
|
|
if (rate > 256)
|
|
rate = 256;
|
|
|
|
/* Always set timeout the maximum */
|
|
reg = SSP_TIMING_TIMEOUT_MASK |
|
|
(divide << SSP_TIMING_CLOCK_DIVIDE_OFFSET) |
|
|
((rate - 1) << SSP_TIMING_CLOCK_RATE_OFFSET);
|
|
writel(reg, &ssp_regs->hw_ssp_timing);
|
|
|
|
debug("SPI%d: Set freq rate to %d KHz (requested %d KHz)\n",
|
|
bus, tgtclk, freq);
|
|
}
|
|
|
|
void mxs_set_lcdclk(uint32_t __maybe_unused lcd_base, uint32_t freq)
|
|
{
|
|
struct mxs_clkctrl_regs *clkctrl_regs =
|
|
(struct mxs_clkctrl_regs *)MXS_CLKCTRL_BASE;
|
|
uint32_t fp, x, k_rest, k_best, x_best, tk;
|
|
int32_t k_best_l = 999, k_best_t = 0, x_best_l = 0xff, x_best_t = 0xff;
|
|
|
|
if (freq == 0)
|
|
return;
|
|
|
|
#if defined(CONFIG_MX23)
|
|
writel(CLKCTRL_CLKSEQ_BYPASS_PIX, &clkctrl_regs->hw_clkctrl_clkseq_clr);
|
|
#elif defined(CONFIG_MX28)
|
|
writel(CLKCTRL_CLKSEQ_BYPASS_DIS_LCDIF, &clkctrl_regs->hw_clkctrl_clkseq_clr);
|
|
#endif
|
|
|
|
/*
|
|
* / 18 \ 1 1
|
|
* freq kHz = | 480000000 Hz * -- | * --- * ------
|
|
* \ x / k 1000
|
|
*
|
|
* 480000000 Hz 18
|
|
* ------------ * --
|
|
* freq kHz x
|
|
* k = -------------------
|
|
* 1000
|
|
*/
|
|
|
|
fp = ((PLL_FREQ_KHZ * 1000) / freq) * 18;
|
|
|
|
for (x = 18; x <= 35; x++) {
|
|
tk = fp / x;
|
|
if ((tk / 1000 == 0) || (tk / 1000 > 255))
|
|
continue;
|
|
|
|
k_rest = tk % 1000;
|
|
|
|
if (k_rest < (k_best_l % 1000)) {
|
|
k_best_l = tk;
|
|
x_best_l = x;
|
|
}
|
|
|
|
if (k_rest > (k_best_t % 1000)) {
|
|
k_best_t = tk;
|
|
x_best_t = x;
|
|
}
|
|
}
|
|
|
|
if (1000 - (k_best_t % 1000) > (k_best_l % 1000)) {
|
|
k_best = k_best_l;
|
|
x_best = x_best_l;
|
|
} else {
|
|
k_best = k_best_t;
|
|
x_best = x_best_t;
|
|
}
|
|
|
|
k_best /= 1000;
|
|
|
|
#if defined(CONFIG_MX23)
|
|
writeb(CLKCTRL_FRAC_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_frac0_set[CLKCTRL_FRAC0_PIX]);
|
|
writeb(CLKCTRL_FRAC_CLKGATE | (x_best & CLKCTRL_FRAC_FRAC_MASK),
|
|
&clkctrl_regs->hw_clkctrl_frac0[CLKCTRL_FRAC0_PIX]);
|
|
writeb(CLKCTRL_FRAC_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_frac0_clr[CLKCTRL_FRAC0_PIX]);
|
|
|
|
writel(CLKCTRL_PIX_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_pix_set);
|
|
clrsetbits_le32(&clkctrl_regs->hw_clkctrl_pix,
|
|
CLKCTRL_PIX_DIV_MASK | CLKCTRL_PIX_CLKGATE,
|
|
k_best << CLKCTRL_PIX_DIV_OFFSET);
|
|
|
|
while (readl(&clkctrl_regs->hw_clkctrl_pix) & CLKCTRL_PIX_BUSY)
|
|
;
|
|
#elif defined(CONFIG_MX28)
|
|
writeb(CLKCTRL_FRAC_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_frac1_set[CLKCTRL_FRAC1_PIX]);
|
|
writeb(CLKCTRL_FRAC_CLKGATE | (x_best & CLKCTRL_FRAC_FRAC_MASK),
|
|
&clkctrl_regs->hw_clkctrl_frac1[CLKCTRL_FRAC1_PIX]);
|
|
writeb(CLKCTRL_FRAC_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_frac1_clr[CLKCTRL_FRAC1_PIX]);
|
|
|
|
writel(CLKCTRL_DIS_LCDIF_CLKGATE,
|
|
&clkctrl_regs->hw_clkctrl_lcdif_set);
|
|
clrsetbits_le32(&clkctrl_regs->hw_clkctrl_lcdif,
|
|
CLKCTRL_DIS_LCDIF_DIV_MASK | CLKCTRL_DIS_LCDIF_CLKGATE,
|
|
k_best << CLKCTRL_DIS_LCDIF_DIV_OFFSET);
|
|
|
|
while (readl(&clkctrl_regs->hw_clkctrl_lcdif) & CLKCTRL_DIS_LCDIF_BUSY)
|
|
;
|
|
#endif
|
|
}
|
|
|
|
uint32_t mxc_get_clock(enum mxc_clock clk)
|
|
{
|
|
switch (clk) {
|
|
case MXC_ARM_CLK:
|
|
return mxs_get_pclk() * 1000000;
|
|
case MXC_GPMI_CLK:
|
|
return mxs_get_gpmiclk() * 1000000;
|
|
case MXC_AHB_CLK:
|
|
case MXC_IPG_CLK:
|
|
return mxs_get_hclk() * 1000000;
|
|
case MXC_EMI_CLK:
|
|
return mxs_get_emiclk();
|
|
case MXC_IO0_CLK:
|
|
return mxs_get_ioclk(MXC_IOCLK0);
|
|
case MXC_IO1_CLK:
|
|
return mxs_get_ioclk(MXC_IOCLK1);
|
|
case MXC_XTAL_CLK:
|
|
return XTAL_FREQ_KHZ * 1000;
|
|
case MXC_SSP0_CLK:
|
|
return mxs_get_sspclk(MXC_SSPCLK0);
|
|
#ifdef CONFIG_MX28
|
|
case MXC_SSP1_CLK:
|
|
return mxs_get_sspclk(MXC_SSPCLK1);
|
|
case MXC_SSP2_CLK:
|
|
return mxs_get_sspclk(MXC_SSPCLK2);
|
|
case MXC_SSP3_CLK:
|
|
return mxs_get_sspclk(MXC_SSPCLK3);
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|