blob: eb56451cdd8416bd5881b08d3d883e1388a08fd0 [file] [log] [blame]
/*
* clock.c
*
* Copyright (C) 2009 Imagination Technologies Ltd.
*
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
#include <linux/mutex.h>
#include <asm/clock.h>
#include <asm/global_lock.h>
#include <asm/soc-tz1090/clock.h>
#include <asm/soc-tz1090/defs.h>
#include <asm/soc-tz1090/pdc.h>
static ATOMIC_NOTIFIER_HEAD(clk32k_notifier_list);
unsigned long get_xtal1(void)
{
u32 xtal_bits = (readl(CR_PERIP_RESET_CFG)
& CR_PERIP_RESET_CFG_FXTAL_BITS) >>
CR_PERIP_RESET_CFG_FXTAL_SHIFT;
switch (xtal_bits) {
case 0: return 16384000;
case 1: return 19200000;
case 2: return 24000000;
case 3: return 24576000;
case 4: return 26000000;
case 5: return 36000000;
case 6: return 36864000;
case 7: return 38400000;
case 8: return 40000000;
default:
printk(KERN_ERR"Comet Clocks:"
"Invalid XTAL1 selected\n");
BUG();
}
}
/*
* Return the XTAL 2 clock. On Comet this is not detectable
* but the recommended value is 12MHz, the method is declared weak so it can
* be overridden by a board specific function if necessary.
*/
unsigned long __weak get_xtal2(void)
{
return COMET_XTAL2;
}
/*
* Return the XTAL 3 clock. On Comet this is 32.768KHz for the RTC.
*/
unsigned long __weak get_xtal3(void)
{
return COMET_XTAL3;
}
/**
* get_32kclock() - Get frequency of 32.768KHz clock.
*
* Returns: The frequency of the 32KHz clock in Hz. This can be derived from
* a 32.768KHz oscillator in XTAL3, or XTAL1 divided down.
*/
unsigned long get_32kclock(void)
{
unsigned int soc_gpio0;
unsigned int divider;
soc_gpio0 = readl(PDC_BASE_ADDR + PDC_SOC_GPIO_CONTROL0);
if (soc_gpio0 & PDC_SOC_GPIO0_RTC_SW) {
return get_xtal3();
} else {
divider = 1 + ((soc_gpio0 & PDC_SOC_GPIO0_XTAL1_DIV)
>> PDC_SOC_GPIO0_XTAL1_DIV_SHIFT);
return get_xtal1() / divider;
}
}
EXPORT_SYMBOL_GPL(get_32kclock);
/**
* clk32k_register_notify() - Register a 32.768KHz clock notifier callback.
* @nb: pointer to the notifier block for the callback events.
*
* These occur when the frequency is changed.
*
* Returns: 0 on success.
*/
int clk32k_register_notify(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&clk32k_notifier_list, nb);
}
EXPORT_SYMBOL_GPL(clk32k_register_notify);
/**
* clk32k_unregister_notify() - Unregister a 32.768KHz clock notifier callback.
* @nb: pointer to the notifier block for the callback events.
*
* clk32k_register_notify() must have been previously called for this function
* to work properly.
*
* Returns: 0 on success.
*/
int clk32k_unregister_notify(struct notifier_block *nb)
{
return atomic_notifier_chain_unregister(&clk32k_notifier_list, nb);
}
EXPORT_SYMBOL_GPL(clk32k_unregister_notify);
/**
* set_32kclock_src() - Set source of 32.768KHz clock.
* @xtal1: Whether to derive from XTAL1 rather than XTAL3.
* @xtal1_div: Divider to use when deriving from XTAL1.
*
* Returns: The resulting frequency in Hz.
*/
unsigned long set_32kclock_src(int xtal1, unsigned int xtal1_div)
{
struct clk32k_change_freq change;
unsigned int soc_gpio0;
unsigned int divider;
unsigned int lstat;
__global_lock2(lstat);
change.old_freq = get_32kclock();
soc_gpio0 = readl(PDC_BASE_ADDR + PDC_SOC_GPIO_CONTROL0);
if (xtal1) {
soc_gpio0 &= ~PDC_SOC_GPIO0_RTC_SW;
divider = (xtal1_div - 1) << PDC_SOC_GPIO0_XTAL1_DIV_SHIFT;
soc_gpio0 &= ~PDC_SOC_GPIO0_XTAL1_DIV;
soc_gpio0 |= divider & PDC_SOC_GPIO0_XTAL1_DIV;
} else {
soc_gpio0 |= PDC_SOC_GPIO0_RTC_SW;
}
writel(soc_gpio0, PDC_BASE_ADDR + PDC_SOC_GPIO_CONTROL0);
change.new_freq = get_32kclock();
__global_unlock2(lstat);
/*
* Inform users so they can make the necessary adjustments to their
* timings.
*/
if (change.old_freq != change.new_freq)
atomic_notifier_call_chain(&clk32k_notifier_list,
CLK32K_CHANGE_FREQUENCY, &change);
return change.new_freq;
}
unsigned long get_sysclock_x2_undeleted(void)
{
u64 f_in;
u64 f_out;
u16 clk_f; /* Feedback divide */
u8 clk_od; /* Output Divider */
u8 clk_r; /* Reference divider */
u32 pll_ctrl0 = readl(CR_TOP_SYSPLL_CTL0);
u32 pll_ctrl1 = readl(CR_TOP_SYSPLL_CTL1);
u32 sys_clk_div = readl(CR_TOP_SYSCLK_DIV) & 0xFF;
if ((readl(CR_TOP_CLKSWITCH) & 0x2) == 0)
return get_xtal1();
if (readl(CR_TOP_CLKSWITCH) & 0x1)
f_in = get_xtal2();
else
f_in = get_xtal1();
if (pll_ctrl1 & (1<<25)) { /* bypass bit set */
f_out = f_in;
} else {
unsigned long divisor;
/* Get Divider Values */
clk_f = (pll_ctrl0 >> 4) & 0x1FFF;
clk_od = pll_ctrl0 & 0x7;
clk_r = pll_ctrl1 & 0x3F;
/*
* formula:
* fout = (fin / (clkr + 1)) * (((clkf/2) + 0.5)/(clkod + 1))
*
* note the equation has been re-arranged to avoid loss of
* precision due to integer maths.
*/
f_out = (u64)f_in * (clk_f + 1);
divisor = (2 * (clk_od + 1) * (clk_r + 1));
f_out = div_u64(f_out, divisor);
}
if (sys_clk_div)
return (unsigned long)f_out / (sys_clk_div + 1);
else
return (unsigned long)f_out;
}
unsigned long get_sysclock_undeleted(void)
{
#ifdef CONFIG_SOC_COMET_ES1
if (readl(CR_TOP_META_CLKDIV) & 0x1)
return get_sysclock_x2_undeleted() / 2;
else
return get_sysclock_x2_undeleted();
#else
u32 div = readl(CR_TOP_META_CLKDIV);
return get_sysclock_x2_undeleted() / (div + 1);
#endif
}
unsigned long get_sdhostclock(void)
{
u8 clk_div = readl(CR_TOP_SDHOSTCLK_DIV);
return get_sysclock_undeleted() / (clk_div + 1);
}
unsigned long set_sdhostclock(unsigned long f)
{
/*
* Note we only modify the SDHosts own clock divider to try
* and match the closest requested frequency - we do not
* modify the main PLL, we return the value we achieved
*/
unsigned long f_in = get_sysclock_undeleted();
unsigned long f_out;
int lstat;
u32 temp;
if (f > f_in) {
writel(0, CR_TOP_SDHOSTCLK_DIV);
f_out = f_in;
} else {
u8 divider = min_t(u32, ((f_in+f-1)/f), 0xFFU);
divider = divider ? divider : 1;
writel(divider-1, CR_TOP_SDHOSTCLK_DIV);
f_out = f_in / divider;
}
/* Top level clk enable */
__global_lock2(lstat);
temp = readl(CR_PERIP_CLK_EN);
temp |= (1<<CR_PERIP_SDHOST_CLK_EN_BIT);
writel(temp, CR_PERIP_CLK_EN);
__global_unlock2(lstat);
return f_out;
}
void pix_clk_set_limits(unsigned long min, unsigned long max)
{
}
unsigned long get_ddrclock(void)
{
u8 clk_div = readl(CR_TOP_DDR_CLKDIV);
return get_sysclock_x2_undeleted() / (clk_div + 1);
}
#ifdef CONFIG_METAG_SUSPEND_MEM
/* ======== UART clocks ======== */
/* Global UART */
#define CLKENAB_UART (1 << CR_TOP_UART_EN_BIT)
#define CLKSWITCH_UART (1 << CR_TOP_UART_SW_BIT)
#define UARTCLKDIV (~0)
/* UART0 */
#define PERIPCLKEN_UART0 (1 << CR_PERIP_UART0_CLK_EN_BIT)
/* UART1 */
#define PERIPCLKEN_UART1 (1 << CR_PERIP_UART1_CLK_EN_BIT)
/* ======== SCB (I2C) clocks ======== */
/* Global SCB */
#define CLKENAB_SCB (1 << CR_TOP_SCB_EN_BIT)
#define CLKSWITCH_SCB (1 << CR_TOP_SCB_SW_BIT)
/* SCB0 */
#define PERIPCLKEN_SCB0 (1 << CR_PERIP_I2C0_CLK_EN_BIT)
/* SCB1 */
#define PERIPCLKEN_SCB1 (1 << CR_PERIP_I2C1_CLK_EN_BIT)
/* SCB2 */
#define PERIPCLKEN_SCB2 (1 << CR_PERIP_I2C2_CLK_EN_BIT)
/* ======== SPI clocks ======== */
#define PERIPCLKEN_SPIM1 (1 << CR_PERIP_SPIM1_CLK_EN_BIT)
#define SPI1CLKDIV (~0)
/* ======== I2S clocks ======== */
#define PERIPCLKEN_I2S (1 << CR_PERIP_I2SOUT_CLK_EN_BIT)
#define I2SCLKDIV (~0)
/* ======== PDP / PDI clock ======== */
#define CLKENAB2_PIXEL (1 << CR_TOP_PIXEL_CLK_2_EN_BIT)
#define HEPCLKEN_PDP CR_PDP_PDI_CLK_EN
#define PIXELCLKDIV (~0)
/* ======== 2D clock ======== */
#define HEPCLKEN_2D CR_2D_CLK_EN
/* ======== Accumulated clock bits to preserve ======== */
#define CLKSWITCH_ALL (CLKSWITCH_UART | \
CLKSWITCH_SCB)
#define CLKENAB_ALL (CLKENAB_UART | \
CLKENAB_SCB)
#define CLKENAB2_ALL (CLKENAB2_PIXEL)
#define PERIPCLKEN_ALL (PERIPCLKEN_UART0 | \
PERIPCLKEN_UART1 | \
PERIPCLKEN_SCB0 | \
PERIPCLKEN_SCB1 | \
PERIPCLKEN_SCB2 | \
PERIPCLKEN_SPIM1 | \
PERIPCLKEN_I2S)
#define HEPCLKEN_ALL (HEPCLKEN_PDP | \
HEPCLKEN_2D)
/**
* enum comet_clk_op - Operations to perform on resume.
* @CLK_PRESERVE: Preserve these bits across suspend.
* @CLK_CLEAR: Clear these bits.
*/
enum comet_clk_op {
CLK_PRESERVE = 0,
CLK_CLEAR,
};
/**
* struct comet_clk_reg - Clock register field to preserve across suspend.
* @addr: Address of 32bit register to preserve.
* @mask: Mask of bits to preserve.
* @op: Operation to perform on resume (CLK_*).
*/
struct comet_clk_reg {
u32 addr;
u32 mask;
enum comet_clk_op op;
};
static struct comet_clk_reg comet_clk_regs[] = {
/* Address Mask Operation */
/* turn clocks off first */
{ CR_TOP_CLKENAB, CLKENAB_ALL, CLK_CLEAR },
{ CR_TOP_CLKENAB2, CLKENAB2_ALL, CLK_CLEAR },
{ CR_PERIP_CLK_EN, PERIPCLKEN_ALL, CLK_CLEAR },
/* restore clock settings */
{ CR_TOP_CLKSWITCH, CLKSWITCH_ALL, CLK_PRESERVE },
{ CR_TOP_UART_CLK_DIV, UARTCLKDIV, CLK_PRESERVE },
{ CR_TOP_SPI1CLK_DIV, SPI1CLKDIV, CLK_PRESERVE },
{ CR_TOP_I2SCLK_DIV, I2SCLKDIV, CLK_PRESERVE },
{ CR_TOP_PIXEL_CLK_DIV, PIXELCLKDIV, CLK_PRESERVE },
/* finally restore clock switches */
{ CR_TOP_CLKENAB, CLKENAB_ALL, CLK_PRESERVE },
{ CR_TOP_CLKENAB2, CLKENAB2_ALL, CLK_PRESERVE },
{ CR_PERIP_CLK_EN, PERIPCLKEN_ALL, CLK_PRESERVE },
{ CR_HEP_CLK_EN, HEPCLKEN_ALL, CLK_PRESERVE },
};
static struct {
u32 values[ARRAY_SIZE(comet_clk_regs)];
} *comet_clk_state;
/**
* comet_clk_suspend() - stores hardware state so it can be restored.
*
* Stores clock settings into comet_clk_state using comet_clk_regs.
*/
int comet_clk_suspend(void)
{
unsigned int i;
u32 val;
comet_clk_state = kzalloc(sizeof(*comet_clk_state), GFP_ATOMIC);
if (!comet_clk_state)
return -ENOMEM;
/* read the registers that need preserving */
for (i = 0; i < ARRAY_SIZE(comet_clk_regs); ++i) {
if (comet_clk_regs[i].op == CLK_PRESERVE) {
val = readl(comet_clk_regs[i].addr)
& comet_clk_regs[i].mask;
comet_clk_state->values[i] = val;
}
}
return 0;
}
/**
* comet_clk_resume() - restores hardware state.
*
* Restores clock settings from comet_clk_state.
*/
void comet_clk_resume(void)
{
unsigned int i, lstat;
u32 val;
/* restore the clocking registers */
__global_lock2(lstat);
for (i = 0; i < ARRAY_SIZE(comet_clk_regs); ++i) {
if (!comet_clk_regs[i].mask)
continue;
if (comet_clk_regs[i].mask != ~0u) {
val = readl(comet_clk_regs[i].addr);
val &= ~comet_clk_regs[i].mask;
val |= comet_clk_state->values[i];
} else {
val = comet_clk_state->values[i];
}
writel(val, comet_clk_regs[i].addr);
}
__global_unlock2(lstat);
kfree(comet_clk_state);
comet_clk_state = NULL;
}
struct syscore_ops comet_clk_syscore_ops = {
.suspend = comet_clk_suspend,
.resume = comet_clk_resume,
};
static int __init comet_clk_init(void)
{
register_syscore_ops(&comet_clk_syscore_ops);
return 0;
}
device_initcall(comet_clk_init);
#endif /* CONFIG_METAG_SUSPEND_MEM */