3f17745930
Do not rely on a bootloader initializing the HW, RB initializes and configures GPIO, I2C, and PMU at startup. Change-Id: If7f856b1f345f63de584aa4e4fc22d130cd66c80
351 lines
9.9 KiB
C
351 lines
9.9 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id:
|
|
*
|
|
* Copyright (C) 2015 by Cástor Muñoz
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include <stdbool.h>
|
|
|
|
#include "config.h"
|
|
#include "system.h" /* udelay() */
|
|
#include "s5l8702.h"
|
|
#include "clocking-s5l8702.h"
|
|
|
|
/* returns configured frequency (PLLxFreq, when locked) */
|
|
unsigned pll_get_cfg_freq(int pll)
|
|
{
|
|
unsigned pdiv, mdiv, sdiv, f_in;
|
|
uint32_t pllpms;
|
|
|
|
pllpms = PLLPMS(pll);
|
|
|
|
pdiv = (pllpms >> PLLPMS_PDIV_POS) & PLLPMS_PDIV_MSK;
|
|
if (pdiv == 0) return 0;
|
|
mdiv = (pllpms >> PLLPMS_MDIV_POS) & PLLPMS_MDIV_MSK;
|
|
sdiv = (pllpms >> PLLPMS_SDIV_POS) & PLLPMS_SDIV_MSK;
|
|
|
|
/* experimental results sugests that the HW is working this way */
|
|
if (mdiv < 2) mdiv += 256;
|
|
|
|
if (GET_PMSMOD(pll) == PMSMOD_DIV)
|
|
{
|
|
f_in = (GET_DMOSC(pll))
|
|
? ((PLLMOD2 & PLLMOD2_ALTOSC_BIT(pll))
|
|
? S5L8702_ALTOSC1_HZ
|
|
: S5L8702_ALTOSC0_HZ)
|
|
: S5L8702_OSC0_HZ;
|
|
|
|
return (f_in * mdiv / pdiv) >> sdiv; /* divide */
|
|
}
|
|
else
|
|
{
|
|
/* XXX: overflows for high f_in, safe for 32768 Hz */
|
|
f_in = S5L8702_OSC1_HZ;
|
|
return (f_in * mdiv * pdiv) >> sdiv; /* multiply */
|
|
}
|
|
}
|
|
|
|
/* returns PLLxClk */
|
|
unsigned pll_get_out_freq(int pll)
|
|
{
|
|
uint32_t pllmode = PLLMODE;
|
|
|
|
if ((pllmode & PLLMODE_PLLOUT_BIT(pll)) == 0)
|
|
return S5L8702_OSC1_HZ; /* slow mode */
|
|
|
|
if ((pllmode & PLLMODE_EN_BIT(pll)) == 0)
|
|
return 0;
|
|
|
|
return pll_get_cfg_freq(pll);
|
|
}
|
|
|
|
/* returns selected oscillator for CG16_SEL_OSC source */
|
|
unsigned soc_get_oscsel_freq(void)
|
|
{
|
|
return (PLLMODE & PLLMODE_OSCSEL_BIT) ? S5L8702_OSC1_HZ
|
|
: S5L8702_OSC0_HZ;
|
|
}
|
|
|
|
/* returns output frequency */
|
|
unsigned cg16_get_freq(volatile uint16_t* cg16)
|
|
{
|
|
unsigned sel, freq;
|
|
uint16_t val16 = *cg16;
|
|
|
|
if (val16 & CG16_DISABLE_BIT)
|
|
return 0;
|
|
|
|
sel = (val16 >> CG16_SEL_POS) & CG16_SEL_MSK;
|
|
|
|
if (val16 & CG16_UNKOSC_BIT)
|
|
freq = S5L8702_UNKOSC_HZ;
|
|
else if (sel == CG16_SEL_OSC)
|
|
freq = soc_get_oscsel_freq();
|
|
else
|
|
freq = pll_get_out_freq(--sel);
|
|
|
|
freq /= (((val16 >> CG16_DIV1_POS) & CG16_DIV1_MSK) + 1) *
|
|
(((val16 >> CG16_DIV2_POS) & CG16_DIV2_MSK) + 1);
|
|
|
|
return freq;
|
|
}
|
|
|
|
void soc_set_system_divs(unsigned cdiv, unsigned hdiv, unsigned hprat)
|
|
{
|
|
uint32_t val = 0;
|
|
unsigned pdiv = hdiv * hprat;
|
|
|
|
if (cdiv > 1)
|
|
val |= CLKCON1_CDIV_EN_BIT |
|
|
((((cdiv >> 1) - 1) & CLKCON1_CDIV_MSK) << CLKCON1_CDIV_POS);
|
|
if (hdiv > 1)
|
|
val |= CLKCON1_HDIV_EN_BIT |
|
|
((((hdiv >> 1) - 1) & CLKCON1_HDIV_MSK) << CLKCON1_HDIV_POS);
|
|
if (pdiv > 1)
|
|
val |= CLKCON1_PDIV_EN_BIT |
|
|
((((pdiv >> 1) - 1) & CLKCON1_PDIV_MSK) << CLKCON1_PDIV_POS);
|
|
|
|
val |= ((hprat - 1) & CLKCON1_HPRAT_MSK) << CLKCON1_HPRAT_POS;
|
|
|
|
CLKCON1 = val;
|
|
|
|
while ((CLKCON1 >> 8) != (val >> 8));
|
|
}
|
|
|
|
unsigned soc_get_system_divs(unsigned *cdiv, unsigned *hdiv, unsigned *pdiv)
|
|
{
|
|
uint32_t val = CLKCON1;
|
|
|
|
if (cdiv)
|
|
*cdiv = !(val & CLKCON1_CDIV_EN_BIT) ? 1 :
|
|
(((val >> CLKCON1_CDIV_POS) & CLKCON1_CDIV_MSK) + 1) << 1;
|
|
if (hdiv)
|
|
*hdiv = !(val & CLKCON1_HDIV_EN_BIT) ? 1 :
|
|
(((val >> CLKCON1_HDIV_POS) & CLKCON1_HDIV_MSK) + 1) << 1;
|
|
if (pdiv)
|
|
*pdiv = !(val & CLKCON1_PDIV_EN_BIT) ? 1 :
|
|
(((val >> CLKCON1_PDIV_POS) & CLKCON1_PDIV_MSK) + 1) << 1;
|
|
|
|
return cg16_get_freq(&CG16_SYS); /* FClk */
|
|
}
|
|
|
|
unsigned get_system_freqs(unsigned *cclk, unsigned *hclk, unsigned *pclk)
|
|
{
|
|
unsigned fclk, cdiv, hdiv, pdiv;
|
|
|
|
fclk = soc_get_system_divs(&cdiv, &hdiv, &pdiv);
|
|
|
|
if (cclk) *cclk = fclk / cdiv;
|
|
if (hclk) *hclk = fclk / hdiv;
|
|
if (pclk) *pclk = fclk / pdiv;
|
|
|
|
return fclk;
|
|
}
|
|
|
|
void soc_set_hsdiv(int hsdiv)
|
|
{
|
|
SM1_DIV = hsdiv - 1;
|
|
}
|
|
|
|
int soc_get_hsdiv(void)
|
|
{
|
|
return (SM1_DIV & 0xf) + 1;
|
|
}
|
|
|
|
/* each target/app could define its own clk_modes table */
|
|
struct clocking_mode *clk_modes;
|
|
int cur_level = -1;
|
|
|
|
void clocking_init(struct clocking_mode *modes, int level)
|
|
{
|
|
/* at this point, CK16_SYS should be already configured
|
|
and enabled by emCORE/bootloader */
|
|
|
|
/* initialize global clocking */
|
|
clk_modes = modes;
|
|
cur_level = level;
|
|
|
|
/* start initial level */
|
|
struct clocking_mode *m = clk_modes + cur_level;
|
|
soc_set_hsdiv(m->hsdiv);
|
|
soc_set_system_divs(m->cdiv, m->hdiv, m->hprat);
|
|
}
|
|
|
|
void set_clocking_level(int level)
|
|
{
|
|
struct clocking_mode *cur, *next;
|
|
|
|
int step = (level < cur_level) ? -1 : 1;
|
|
|
|
while (cur_level != level)
|
|
{
|
|
cur = clk_modes + cur_level;
|
|
next = cur + step;
|
|
|
|
/* step-up */
|
|
if (next->hsdiv > cur->hsdiv)
|
|
soc_set_hsdiv(next->hsdiv);
|
|
|
|
/* step up/down */
|
|
soc_set_system_divs(next->cdiv, next->hdiv, next->hprat);
|
|
|
|
/* step-down */
|
|
if (next->hsdiv < cur->hsdiv)
|
|
soc_set_hsdiv(next->hsdiv);
|
|
|
|
cur_level += step;
|
|
}
|
|
|
|
udelay(50); /* TBC: probably not needed */
|
|
}
|
|
|
|
void clockgate_enable(int gate, bool enable)
|
|
{
|
|
int i = (gate >> 5) & 1;
|
|
uint32_t bit = 1 << (gate & 0x1f);
|
|
if (enable) PWRCON(i) &= ~bit;
|
|
else PWRCON(i) |= bit;
|
|
}
|
|
|
|
#ifdef BOOTLOADER
|
|
int pll_config(int pll, int op_mode, int p, int m, int s, int lock_time)
|
|
{
|
|
PLLPMS(pll) = ((p & PLLPMS_PDIV_MSK) << PLLPMS_PDIV_POS)
|
|
| ((m & PLLPMS_MDIV_MSK) << PLLPMS_MDIV_POS)
|
|
| ((s & PLLPMS_SDIV_MSK) << PLLPMS_SDIV_POS);
|
|
|
|
/* lock_time are PClk ticks */
|
|
PLLCNT(pll) = lock_time & PLLCNT_MSK;
|
|
|
|
if (pll < 2)
|
|
{
|
|
if (op_mode == PLLOP_MM) {
|
|
PLLMODE &= ~PLLMODE_PMSMOD_BIT(pll); /* MM */
|
|
return 0;
|
|
}
|
|
|
|
PLLMODE |= PLLMODE_PMSMOD_BIT(pll);
|
|
|
|
if (op_mode == PLLOP_DM) {
|
|
PLLMOD2 &= ~PLLMOD2_DMOSC_BIT(pll); /* DM */
|
|
return 0;
|
|
}
|
|
|
|
PLLMOD2 |= PLLMOD2_DMOSC_BIT(pll);
|
|
}
|
|
else
|
|
{
|
|
if (op_mode == PLLOP_MM)
|
|
return -1; /* PLL2 does not support MM */
|
|
|
|
if (op_mode == PLLOP_DM) {
|
|
PLLMODE &= ~PLLMODE_PLL2DMOSC_BIT; /* DM */
|
|
return 0;
|
|
}
|
|
|
|
PLLMODE |= PLLMODE_PLL2DMOSC_BIT;
|
|
}
|
|
|
|
/* ALTOSCx */
|
|
PLLMOD2 = (PLLMOD2 & ~PLLMOD2_ALTOSC_BIT(pll)) |
|
|
((op_mode == PLLOP_ALT0) ? 0 : PLLMOD2_ALTOSC_BIT(pll));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pll_onoff(int pll, bool onoff)
|
|
{
|
|
if (onoff)
|
|
{
|
|
PLLMODE |= PLLMODE_EN_BIT(pll); /* start PLL */
|
|
while (!(PLLLOCK & PLLLOCK_LCK_BIT(pll))); /* locking... */
|
|
PLLMODE |= PLLMODE_PLLOUT_BIT(pll); /* slow mode OFF */
|
|
|
|
/* returns DMLCK status, only meaningful for Divisor Mode (DM) */
|
|
return (PLLLOCK & PLLLOCK_DMLCK_BIT(pll)) ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
PLLMODE &= ~PLLMODE_PLLOUT_BIT(pll); /* slow mode ON */
|
|
udelay(50); /* TBC: needed when current F_in is 0 Hz */
|
|
PLLMODE &= ~PLLMODE_EN_BIT(pll); /* stop PLL */
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* configure and enable/disable 16-bit clockgate */
|
|
void cg16_config(volatile uint16_t* cg16,
|
|
bool onoff, int clksel, int div1, int div2)
|
|
{
|
|
uint16_t val16 = ((clksel & CG16_SEL_MSK) << CG16_SEL_POS)
|
|
| (((div1 - 1) & CG16_DIV1_MSK) << CG16_DIV1_POS)
|
|
| (((div2 - 1) & CG16_DIV2_MSK) << CG16_DIV2_POS)
|
|
| (onoff ? 0 : CG16_DISABLE_BIT);
|
|
|
|
volatile uint32_t* reg32 = (uint32_t *)((int)cg16 & ~3);
|
|
int shift = ((int)cg16 & 2) << 3;
|
|
|
|
*reg32 = (*reg32 & (0xffff0000 >> shift)) | (val16 << shift);
|
|
|
|
/*udelay(100);*/ /* probably not needed */
|
|
|
|
while (*cg16 != val16);
|
|
}
|
|
|
|
/* Configures EClk for USEC_TIMER. DRAM refresh also depends on EClk,
|
|
* this clock should be initialized by the bootloader, so USEC_TIMER
|
|
* is ready to use for RB.
|
|
*/
|
|
void usec_timer_init(void)
|
|
{
|
|
/* select OSC0 for CG16 SEL_OSC */
|
|
PLLMODE &= ~PLLMODE_OSCSEL_BIT;
|
|
|
|
/* configure and enable ECLK */
|
|
cg16_config(&CG16_RTIME, true, CG16_SEL_OSC, 1, 1);
|
|
|
|
/* unmask timer controller clock gate */
|
|
clockgate_enable(CLOCKGATE_TIMER, true);
|
|
|
|
/* configure and start timer E */
|
|
TECON = (4 << 8) | /* TE_CS = ECLK / 1 */
|
|
(1 << 6) | /* select ECLK (12 MHz on Classic) */
|
|
(0 << 4); /* TE_MODE_SEL = interval mode */
|
|
TEPRE = (S5L8702_OSC0_HZ / 1000000) - 1; /* prescaler */
|
|
TEDATA0 = ~0;
|
|
TECMD = (1 << 1) | /* TE_CLR = initialize timer */
|
|
(1 << 0); /* TE_EN = enable */
|
|
}
|
|
|
|
#if 0
|
|
/* - This function is mainly to documment how s5l8702 ROMBOOT and iPod
|
|
* Classic diagnostic OF detects primary external clock.
|
|
* - ATM it is unknown if 24 MHz are used on other targets (i.e. Nano 3G),
|
|
* other SoC (ROMBOOT identifies itself as s5l8900/s5l8702), a Classic
|
|
* prototype, or (probably) never used...
|
|
* - This function should be called only at boot time, GPIO3.5 is also
|
|
* used for ATA controller.
|
|
*/
|
|
unsigned soc_get_osc0(void)
|
|
{
|
|
GPIOCMD = 0x30500; /* configure GPIO3.5 as input */
|
|
return (PDAT3 & 0x20) ? 24000000 : 12000000;
|
|
}
|
|
#endif
|
|
|
|
#endif /* BOOTLOADER */
|