[PATCH 3/9] ARM: SPMP8000: Add clk support
Zoltan Devai
zoss at devai.org
Sun Oct 9 12:36:06 EDT 2011
Signed-off-by: Zoltan Devai <zoss at devai.org>
---
arch/arm/mach-spmp8000/clkdev.c | 586 +++++++++++++++++++++++++++
arch/arm/mach-spmp8000/clock.c | 155 +++++++
arch/arm/mach-spmp8000/include/mach/clock.h | 37 ++
3 files changed, 778 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-spmp8000/clkdev.c
create mode 100644 arch/arm/mach-spmp8000/clock.c
create mode 100644 arch/arm/mach-spmp8000/include/mach/clock.h
diff --git a/arch/arm/mach-spmp8000/clkdev.c b/arch/arm/mach-spmp8000/clkdev.c
new file mode 100644
index 0000000..c492d20
--- /dev/null
+++ b/arch/arm/mach-spmp8000/clkdev.c
@@ -0,0 +1,586 @@
+/*
+ * SPMP8000 machines clock support
+ *
+ * Copyright (C) 2011 Zoltan Devai <zoss at devai.org>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <mach/clock.h>
+#include <mach/scu.h>
+
+/* The SoC doesn't support anything else, so no need to make these machine
+ * configurable.
+ */
+#define XTAL_FREQ 27000000UL
+#define XTAL_32K_FREQ 32768UL
+#define APLL_48K_FREQ 73728000UL
+#define APLL_44K_FREQ 67737600UL
+
+static unsigned long spll_get_rate(struct clk *clk)
+{
+ unsigned int f, r, od;
+ unsigned long osc;
+ u32 cfg;
+
+ cfg = readl(REG_SCU_B(SCU_B_SPLL_CFG));
+ osc = XTAL_FREQ;
+
+ /* SPLL off ? */
+ if ((cfg & (SPLL_CFG_XR | SPLL_CFG_PD | SPLL_CFG_BP)) ||
+ (~cfg & (SPLL_CFG_PL | SPLL_CFG_XE | SPLL_CFG_SE))) {
+ return 0;
+ }
+
+ f = (cfg & SPLL_CFG_F_MASK) >> SPLL_CFG_F_SHIFT;
+ r = (cfg & SPLL_CFG_R_MASK) >> SPLL_CFG_R_SHIFT;
+ od = !!(cfg & SPLL_CFG_OD);
+ clk->rate = (osc * (f + 1) * 2) / ((r + 1) * (od + 1));
+
+ return clk->rate;
+}
+
+static unsigned long pll_mux_get_rate(int ratesel, struct clk *clk)
+{
+ int pll_rate;
+
+ switch (ratesel) {
+ case 0:
+ clk->rate = XTAL_FREQ;
+ break;
+ case 1:
+ clk->rate = XTAL_32K_FREQ;
+ break;
+ case 2:
+ pll_rate = clk_get_rate(clk->parent);
+ clk->rate = pll_rate / 2;
+ break;
+ case 3:
+ pll_rate = clk_get_rate(clk->parent);
+ clk->rate = pll_rate / 3;
+ break;
+ }
+
+ return clk->rate;
+}
+
+static unsigned long ref_arm_get_rate(struct clk *clk)
+{
+ int ratesel;
+ u32 spll_cfg;
+
+ spll_cfg = readl(REG_SCU_B(SCU_B_SPLL_CFG));
+ ratesel = (spll_cfg & SPLL_CFG_ASEL_MASK) >> SPLL_CFG_ASEL_SHIFT;
+ return pll_mux_get_rate(ratesel, clk);
+}
+
+static unsigned long ref_ceva_get_rate(struct clk *clk)
+{
+ int ratesel;
+ u32 spll_cfg;
+
+ spll_cfg = readl(REG_SCU_B(SCU_B_SPLL_CFG));
+ ratesel = (spll_cfg & SPLL_CFG_CSEL_MASK) >> SPLL_CFG_CSEL_SHIFT;
+ return pll_mux_get_rate(ratesel, clk);
+}
+
+static unsigned long apll_get_rate(void)
+{
+ u32 cfg = readl(REG_SCU_A(SCU_A_APLL_CFG));
+
+ if ((cfg & APLL_CFG_R) || (~cfg & (APLL_CFG_P | APLL_CFG_E)) ||
+ (((cfg & APLL_CFG_AS_MASK) >> APLL_CFG_AS_SHIFT)
+ != APLL_CFG_AS_MAGIC)) {
+ return 0;
+ }
+
+ /* Not dealing with the input oscillator frequency as the settings
+ * for non-27Mhz are unknown, and all platforms use that anyway */
+ if (cfg & APLL_CFG_S)
+ return APLL_44K_FREQ;
+ else
+ return APLL_48K_FREQ;
+}
+
+static void apll_enable(struct clk *clk)
+{
+ u32 cfg = readl(REG_SCU_A(SCU_A_APLL_CFG));
+
+ /* Store new config with reset, then disable reset */
+ cfg |= APLL_CFG_R;
+ cfg |= APLL_CFG_E | APLL_CFG_P | APLL_CFG_F;
+ cfg |= APLL_CFG_AS_MAGIC << APLL_CFG_AS_SHIFT;
+
+ writel(cfg, REG_SCU_A(SCU_A_APLL_CFG));
+
+ cfg &= ~APLL_CFG_R;
+ writel(cfg, REG_SCU_A(SCU_A_APLL_CFG));
+}
+
+static void apll_disable(struct clk *clk)
+{
+ u32 cfg = readl(REG_SCU_A(SCU_A_APLL_CFG));
+
+ cfg &= ~APLL_CFG_P;
+ cfg |= APLL_CFG_E;
+
+ writel(cfg, REG_SCU_A(SCU_A_APLL_CFG));
+}
+
+static int apll_set_rate(unsigned long rate)
+{
+ u32 cfg;
+
+ cfg = readl(REG_SCU_A(SCU_A_APLL_CFG));
+
+ switch (rate) {
+ case 67737600:
+ cfg |= APLL_CFG_S;
+ break;
+ case 73728000:
+ cfg &= ~APLL_CFG_S;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(cfg, REG_SCU_A(SCU_A_APLL_CFG));
+
+ return 0;
+}
+
+static void i2s_mck_switch(u32 mask, int enable)
+{
+ u32 cfg = readl(REG_SCU_A(SCU_A_APLL_CFG));
+ if (enable)
+ cfg &= ~mask;
+ else
+ cfg |= mask;
+
+ writel(cfg, REG_SCU_A(SCU_A_APLL_CFG));
+}
+
+static void i2stx_mck_enable(struct clk *clk)
+{
+ i2s_mck_switch(APLL_CFG_DS, 1);
+}
+
+static void i2stx_mck_disable(struct clk *clk)
+{
+ i2s_mck_switch(APLL_CFG_DS, 0);
+}
+
+static void i2srx_mck_enable(struct clk *clk)
+{
+ i2s_mck_switch(APLL_CFG_AS, 1);
+}
+
+static void i2srx_mck_disable(struct clk *clk)
+{
+ i2s_mck_switch(APLL_CFG_AS, 0);
+}
+
+static const int apll_dividers[7] = {
+ 3, 6, 9, 12, 18, 24, 32,
+};
+
+static unsigned long i2s_mck_get_rate(struct clk *clk, u32 msk, u32 sh)
+{
+ unsigned long apll_rate = apll_get_rate();
+ u32 cfg = readl(REG_SCU_A(SCU_A_APLL_CFG));
+ int divider;
+
+ divider = (cfg & msk) >> sh;
+
+ if (divider == ARRAY_SIZE(apll_dividers))
+ return 0;
+
+ divider = apll_dividers[divider];
+
+ return apll_rate / divider;
+}
+
+static unsigned long i2stx_mck_get_rate(struct clk *clk)
+{
+ return i2s_mck_get_rate(clk, APLL_CFG_DAR_MASK, APLL_CFG_DAR_SHIFT);
+}
+
+static unsigned long i2srx_mck_get_rate(struct clk *clk)
+{
+ return i2s_mck_get_rate(clk, APLL_CFG_ADR_MASK, APLL_CFG_ADR_SHIFT);
+}
+
+static int i2s_mck_set_rate(struct clk *clk, unsigned long rate,
+ u32 msk, u32 sh)
+{
+ int i = ARRAY_SIZE(apll_dividers) - 1;
+ unsigned long apll_rate;
+ int divider = -1;
+ u32 cfg;
+
+ /* Set up APLL */
+ if (rate % 8000)
+ apll_rate = APLL_44K_FREQ;
+ else
+ apll_rate = APLL_48K_FREQ;
+
+ apll_set_rate(apll_rate);
+
+ cfg = readl(REG_SCU_A(SCU_A_APLL_CFG));
+
+ /* Get the biggest possible divider for MCK */
+ while (i >= 0) {
+ if (apll_rate / apll_dividers[i] == rate) {
+ divider = apll_dividers[i];
+ break;
+ }
+
+ i--;
+ }
+
+ if (divider < 0)
+ return -EINVAL;
+
+ cfg &= ~msk;
+ cfg |= (i << sh);
+
+ writel(cfg, REG_SCU_A(SCU_A_APLL_CFG));
+
+ return 0;
+}
+
+static int i2stx_mck_set_rate(struct clk *clk, unsigned long rate)
+{
+ return i2s_mck_set_rate(clk, rate,
+ APLL_CFG_DAR_MASK, APLL_CFG_DAR_SHIFT);
+}
+
+static int i2srx_mck_set_rate(struct clk *clk, unsigned long rate)
+{
+ return i2s_mck_set_rate(clk, rate,
+ APLL_CFG_ADR_MASK, APLL_CFG_ADR_SHIFT);
+}
+
+static int divider_set_clock(struct clk *clk, int on)
+{
+ u32 divider;
+
+ if (!(clk->flags & CLK_DIVIDER_WITH_ENABLE))
+ return -EINVAL;
+
+ divider = readl(REG_SCU_BASE(clk->scu) + clk->dividerreg);
+ if (on)
+ divider |= DIVIDER_ENABLE_BIT;
+ else
+ divider = 0;
+ writel(divider, REG_SCU_BASE(clk->scu) + clk->dividerreg);
+
+ return 0;
+}
+
+static void divider_enable_clock(struct clk *clk)
+{
+ divider_set_clock(clk, 1);
+}
+
+static void divider_disable_clock(struct clk *clk)
+{
+ divider_set_clock(clk, 0);
+}
+
+static unsigned long divider_get_rate(struct clk *clk)
+{
+ u32 divider;
+ unsigned long parent_rate = clk_get_rate(clk->parent);
+
+ if (!parent_rate) {
+ clk->rate = 0;
+ return clk->rate;
+ }
+
+ divider = readl(REG_SCU_BASE(clk->scu) + clk->dividerreg);
+ if ((clk->flags & CLK_DIVIDER_WITH_ENABLE) &&
+ !(divider & DIVIDER_ENABLE_BIT)) {
+ clk->rate = 0UL;
+ return clk->rate;
+ }
+
+ clk->rate = parent_rate / ((divider & clk->divmask) + 1);
+
+ return clk->rate;
+}
+
+static int divider_set_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long parent_rate = clk_get_rate(clk->parent);
+ u32 divider, divider_old;
+
+ if (unlikely(!parent_rate || rate > parent_rate)) {
+ clk->rate = 0;
+ pr_debug("parent rate not sufficient\n");
+ return -EINVAL;
+ }
+
+ divider = (parent_rate / rate) - 1;
+
+ if (divider > clk->divmask) {
+ pr_debug("input clock too high\n");
+ return -EINVAL;
+ };
+
+ divider_old = readl(REG_SCU_BASE(clk->scu) + clk->dividerreg);
+ writel(0, REG_SCU_BASE(clk->scu) + clk->dividerreg);
+ writel(divider, REG_SCU_BASE(clk->scu) + clk->dividerreg);
+
+ /* Re-enable clock if it was enabled before */
+ if (divider_old & DIVIDER_ENABLE_BIT)
+ writel(divider | DIVIDER_ENABLE_BIT,
+ REG_SCU_BASE(clk->scu) + clk->dividerreg);
+
+ clk->rate = parent_rate / (divider + 1);
+
+ return 0;
+}
+static void periph_enable_clock(struct clk *clk)
+{
+ u32 scu_reg = readl(REG_SCU_CLKEN(clk->scu));
+
+ scu_reg |= (1 << clk->scu_periph_id);
+
+ writel(scu_reg, REG_SCU_CLKEN(clk->scu));
+}
+
+static void periph_disable_clock(struct clk *clk)
+{
+ u32 scu_reg = readl(REG_SCU_CLKEN(clk->scu));
+
+ scu_reg &= ~(1 << clk->scu_periph_id);
+
+ writel(scu_reg, REG_SCU_CLKEN(clk->scu));
+}
+
+void spmp8000_update_arm_freqs(void)
+{
+ writel(7, REG_SCU_B(SCU_B_UPDATE_ARM_RATIO));
+}
+EXPORT_SYMBOL(spmp8000_update_arm_freqs);
+
+static struct clk clk_spll = {
+ .get_rate = spll_get_rate,
+ .flags = CLK_FIXED_RATE,
+};
+
+static struct clk clk_ref_arm = {
+ .parent = &clk_spll,
+ .get_rate = ref_arm_get_rate,
+};
+
+static struct clk clk_ref_ceva = {
+ .parent = &clk_spll,
+ .get_rate = ref_ceva_get_rate,
+};
+
+static struct clk clk_apll = {
+ .name = "clk_apll",
+ .enable = apll_enable,
+ .disable = apll_disable,
+};
+
+static struct clk clk_i2stx_mck = {
+ .name = "clk_i2stx_mck",
+ .parent = &clk_apll,
+ .enable = i2stx_mck_enable,
+ .disable = i2stx_mck_disable,
+ .get_rate = i2stx_mck_get_rate,
+ .set_rate = i2stx_mck_set_rate,
+};
+
+static struct clk clk_i2srx_mck = {
+ .name = "clk_i2srx_mck",
+ .parent = &clk_apll,
+ .enable = i2srx_mck_enable,
+ .disable = i2srx_mck_disable,
+ .get_rate = i2srx_mck_get_rate,
+ .set_rate = i2srx_mck_set_rate,
+};
+
+#define SYSTEM_CLK(__name, __parent, __scu, __divider, __divmask, __flags) \
+static struct clk clk_##__name = { \
+ .name = "clk_" #__name, \
+ .parent = &clk_##__parent, \
+ .scu = REG_SCU_##__scu##_ID, \
+ .dividerreg = __divider, \
+ .divmask = __divmask, \
+ .flags = __flags, \
+ .enable = ÷r_enable_clock, \
+ .disable = ÷r_disable_clock, \
+ .get_rate = ÷r_get_rate, \
+ .set_rate = ÷r_set_rate, \
+}
+
+SYSTEM_CLK(arm, ref_arm, B, SCU_B_ARM_RATIO, 0x3F, 0);
+SYSTEM_CLK(arm_ahb, arm, B, SCU_B_ARM_AHB_RATIO, 0x3F, 0);
+SYSTEM_CLK(arm_apb, arm_ahb, B, SCU_B_ARM_APB_RATIO, 0x3F, 0);
+SYSTEM_CLK(ceva, ref_ceva, C, SCU_C_CEVA_RATIO, 0x3F, 0);
+SYSTEM_CLK(ceva_ahb, ceva, C, SCU_C_CEVA_AHB_RATIO, 0x3F, 0);
+SYSTEM_CLK(ceva_apb, ceva_ahb, C, SCU_C_CEVA_APB_RATIO, 0x3F, 0);
+SYSTEM_CLK(sys, ref_ceva, C, SCU_C_SYS_RATIO, 0x3F, 0);
+SYSTEM_CLK(sys_ahb, sys, C, SCU_C_SYS_AHB_RATIO, 0x3F, 0);
+SYSTEM_CLK(sys_apb, sys_ahb, C, SCU_C_SYS_APB_RATIO, 0x3F, 0);
+
+SYSTEM_CLK(uart, ref_ceva, A, SCU_A_UART_CFG, 0xFF, CLK_DIVIDER_WITH_ENABLE);
+SYSTEM_CLK(lcd, ref_ceva, A, SCU_A_LCD_CLK_CFG, 0xFF, CLK_DIVIDER_WITH_ENABLE);
+SYSTEM_CLK(csi, ref_ceva, A, SCU_A_CSI_CLK_CFG, 0xFF, CLK_DIVIDER_WITH_ENABLE);
+SYSTEM_CLK(i2srx_bck, i2srx_mck, A, SCU_A_I2S_BCK_CFG, 0xFF,
+ CLK_DIVIDER_WITH_ENABLE);
+SYSTEM_CLK(i2stx_bck, i2stx_mck, A, SCU_A_I2S_BCK_CFG, 0xFF,
+ CLK_DIVIDER_WITH_ENABLE);
+
+
+/* Peripherals */
+#define PERIPH_CLK(__name, __parent, __scu, __id) \
+static struct clk clk_##__name = { \
+ .name = "clk_" #__name, \
+ .parent = &__parent, \
+ .scu = REG_SCU_##__scu##_ID, \
+ .scu_periph_id = __id, \
+ .enable = periph_enable_clock, \
+ .disable = periph_disable_clock, \
+}
+
+/* Not needed yet for any driver */
+#if 0
+PERIPH_CLK(drm, clk_sys_ahb, A, 2);
+PERIPH_CLK(usb_host, clk_sys_ahb, A, 3);
+PERIPH_CLK(usb_device, clk_sys_ahb, A, 4);
+PERIPH_CLK(scu_a, clk_sys_ahb, A, 6);
+PERIPH_CLK(tvout, clk_sys, A, 7);
+PERIPH_CLK(csi_ctrl, clk_sys, A, 10);
+PERIPH_CLK(nand0, clk_sys_ahb, A, 11);
+PERIPH_CLK(nand1, clk_sys_ahb, A, 12);
+PERIPH_CLK(ecc, clk_sys_ahb, A, 13);
+PERIPH_CLK(uart_con, clk_sys_ahb, A, 15);
+PERIPH_CLK(aahbm212, clk_sys, A, 16);
+PERIPH_CLK(nand_abt, clk_sys_ahb, A, 20);
+PERIPH_CLK(rt_abt212, clk_sys, A, 22);
+PERIPH_CLK(cahbm212, clk_sys, A, 23);
+
+PERIPH_CLK(tcm_bist, clk_arm, B, 0);
+PERIPH_CLK(tcm_ctrl, clk_arm, B, 1);
+PERIPH_CLK(ahb2ahb, clk_arm, B, 2);
+PERIPH_CLK(ahbsw, clk_arm, B, 3);
+PERIPH_CLK(vic0, clk_arm_ahb, B, 4);
+PERIPH_CLK(vic1, clk_arm_ahb, B, 5);
+PERIPH_CLK(dpm, clk_arm_ahb, B, 6);
+PERIPH_CLK(apbb, clk_arm_ahb, B, 7);
+PERIPH_CLK(arm926, clk_arm, B, 8);
+PERIPH_CLK(wdt, clk_arm_apb, B, 10);
+PERIPH_CLK(uartapb, clk_arm_apb, B, 11);
+PERIPH_CLK(i2c, clk_arm_apb, B, 12);
+PERIPH_CLK(rand, clk_arm_apb, B, 13);
+PERIPH_CLK(gpio0, clk_arm_apb, B, 14);
+PERIPH_CLK(rtc, clk_arm_apb, B, 15);
+
+PERIPH_CLK(fabricc, clk_sys, C, 0);
+PERIPH_CLK(dmac0, clk_sys, C, 1);
+PERIPH_CLK(dmac1, clk_sys, C, 2);
+PERIPH_CLK(dram_ctrl, clk_sys, C, 4);
+PERIPH_CLK(scu_c, clk_sys, C, 5);
+PERIPH_CLK(i2c_ctrl, clk_sys, C, 6);
+PERIPH_CLK(2d, clk_sys, C, 8);
+PERIPH_CLK(extmem, clk_sys, C, 9);
+PERIPH_CLK(cf, clk_sys_apb, C, 10);
+PERIPH_CLK(ms, clk_sys_apb, C, 11);
+PERIPH_CLK(intmem, clk_sys, C, 12);
+PERIPH_CLK(uartc0, clk_sys_apb, C, 13);
+PERIPH_CLK(uartc1, clk_sys_apb, C, 14);
+PERIPH_CLK(uartc2, clk_sys_apb, C, 15);
+PERIPH_CLK(ssp0, clk_sys_apb, C, 16);
+PERIPH_CLK(ssp1, clk_sys_apb, C, 17);
+PERIPH_CLK(sd1, clk_sys_apb, C, 19);
+PERIPH_CLK(i2c_sys, clk_sys_apb, C, 20);
+PERIPH_CLK(scale, clk_sys, C, 21);
+PERIPH_CLK(scaleabt, clk_sys, C, 22);
+PERIPH_CLK(ti2c, clk_sys_apb, C, 23);
+PERIPH_CLK(fabric_a, clk_sys, C, 24);
+PERIPH_CLK(cxmp_sl, clk_sys, C, 25);
+PERIPH_CLK(cxmd_sl, clk_sys, C, 26);
+#endif
+
+PERIPH_CLK(rt_abt, clk_sys, A, 21);
+/* Make the parent rt_abt to auto-enable it when enabling the lcdc clock */
+PERIPH_CLK(lcd_ctrl, clk_rt_abt, A, 1);
+PERIPH_CLK(apbdma_a, clk_rt_abt, A, 9);
+PERIPH_CLK(apll_ctrl, clk_sys_ahb, A, 14);
+PERIPH_CLK(i2stx_ctrl, clk_apbdma_a, A, 17);
+PERIPH_CLK(i2srx_ctrl, clk_apbdma_a, A, 18);
+PERIPH_CLK(saacc, clk_sys_apb, A, 19);
+
+PERIPH_CLK(tmrb, clk_arm_apb, B, 9);
+PERIPH_CLK(apbdma_c, clk_sys_apb, C, 7);
+PERIPH_CLK(sd0, clk_sys_apb, C, 18);
+
+/* TODO use common macro once available */
+#define _DEFINE_CLKDEV_CON(n) \
+ { \
+ .dev_id = NULL, \
+ .con_id = #n, \
+ .clk = &clk_##n, \
+ }
+
+#define _DEFINE_CLKDEV_DEV(d, c) \
+ { \
+ .dev_id = d, \
+ .con_id = NULL, \
+ .clk = &clk_##c, \
+ }
+
+static struct clk_lookup lookups[] = {
+ _DEFINE_CLKDEV_CON(arm),
+ _DEFINE_CLKDEV_CON(arm_ahb),
+ _DEFINE_CLKDEV_CON(arm_apb),
+ _DEFINE_CLKDEV_CON(ceva),
+ _DEFINE_CLKDEV_CON(ceva_ahb),
+ _DEFINE_CLKDEV_CON(ceva_apb),
+ _DEFINE_CLKDEV_CON(sys),
+ _DEFINE_CLKDEV_CON(sys_ahb),
+ _DEFINE_CLKDEV_CON(sys_apb),
+ _DEFINE_CLKDEV_CON(lcd),
+ _DEFINE_CLKDEV_CON(apbdma_a),
+ _DEFINE_CLKDEV_CON(saacc),
+ _DEFINE_CLKDEV_CON(i2stx_mck),
+ _DEFINE_CLKDEV_CON(i2srx_mck),
+ _DEFINE_CLKDEV_CON(i2stx_bck),
+ _DEFINE_CLKDEV_CON(i2srx_bck),
+ _DEFINE_CLKDEV_DEV("uart.0", uart),
+ _DEFINE_CLKDEV_DEV("uart.1", uart),
+ _DEFINE_CLKDEV_DEV("uart.2", uart),
+ _DEFINE_CLKDEV_DEV("93000000.fb", lcd_ctrl),
+ _DEFINE_CLKDEV_DEV("90000000.pwm", tmrb),
+ _DEFINE_CLKDEV_DEV("92b00000.dma", apbdma_c),
+ _DEFINE_CLKDEV_DEV("92b0b000.mmc", sd0),
+ _DEFINE_CLKDEV_DEV("93010000.dma", apbdma_a),
+ _DEFINE_CLKDEV_DEV("93012000.i2s", i2stx_ctrl),
+ _DEFINE_CLKDEV_DEV("9301d000.i2s", i2srx_ctrl),
+};
+
+void __init spmp8000_init_clkdev(void)
+{
+ clkdev_add_table(lookups, ARRAY_SIZE(lookups));
+
+ /* Enable the apll control registers here, as the according clock
+ * isn't exported, but used by the mck/bck clocks. If we wouldn't
+ * enable it here, the freq of the pll couldn't be set up before
+ * enabling one of its child clocks.
+ * The PLL clock itself will be auto-enabled on demand by them.
+ */
+ clk_enable(&clk_apll_ctrl);
+}
diff --git a/arch/arm/mach-spmp8000/clock.c b/arch/arm/mach-spmp8000/clock.c
new file mode 100644
index 0000000..5652aff
--- /dev/null
+++ b/arch/arm/mach-spmp8000/clock.c
@@ -0,0 +1,155 @@
+/*
+ * Usual clk API boilerplate
+ *
+ * Copyright (C) 2011 Zoltan Devai <zoss at devai.org>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+
+#include <mach/clock.h>
+
+static DEFINE_MUTEX(clocks_mutex);
+
+static void __clk_disable(struct clk *clk)
+{
+ BUG_ON(clk->refcount == 0);
+
+ if (!(--clk->refcount) && clk->disable) {
+ clk->disable(clk);
+ if (clk->parent)
+ __clk_disable(clk->parent);
+ }
+}
+
+static int __clk_enable(struct clk *clk)
+{
+ int ret = 0;
+
+ if (clk->refcount++ == 0 && clk->enable) {
+ if (clk->parent)
+ ret = __clk_enable(clk->parent);
+ if (ret)
+ return ret;
+ else
+ clk->enable(clk);
+ }
+
+ return 0;
+}
+
+int clk_enable(struct clk *clk)
+{
+ int ret = 0;
+
+ if (unlikely(IS_ERR_OR_NULL(clk)))
+ return -EINVAL;
+
+ mutex_lock(&clocks_mutex);
+ ret = __clk_enable(clk);
+ mutex_unlock(&clocks_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(clk_enable);
+
+void clk_disable(struct clk *clk)
+{
+ if (unlikely(IS_ERR_OR_NULL(clk)))
+ return;
+
+ mutex_lock(&clocks_mutex);
+ __clk_disable(clk);
+ mutex_unlock(&clocks_mutex);
+}
+EXPORT_SYMBOL_GPL(clk_disable);
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ if (unlikely(IS_ERR_OR_NULL(clk)))
+ return 0UL;
+
+ /* Is fixed and has been calculated already */
+ if ((clk->flags & CLK_FIXED_RATE) && clk->rate)
+ return clk->rate;
+
+ if (clk->get_rate)
+ return clk->get_rate(clk);
+
+ return clk_get_rate(clk->parent);
+}
+EXPORT_SYMBOL_GPL(clk_get_rate);
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ if (unlikely(IS_ERR_OR_NULL(clk)))
+ return 0;
+ if (unlikely(!clk->round_rate))
+ return 0;
+
+ return clk->round_rate(clk, rate);
+}
+EXPORT_SYMBOL_GPL(clk_round_rate);
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ int ret = -EINVAL;
+
+ if (unlikely(IS_ERR_OR_NULL(clk)))
+ return ret;
+
+ if (unlikely(clk->flags & CLK_FIXED_RATE))
+ return ret;
+
+ if (unlikely(!clk->set_rate || !rate))
+ return ret;
+
+ mutex_lock(&clocks_mutex);
+ ret = clk->set_rate(clk, rate);
+ mutex_unlock(&clocks_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(clk_set_rate);
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ struct clk *old;
+ int ret = -EINVAL;
+
+ if (unlikely(IS_ERR_OR_NULL(clk)))
+ return ret;
+ if (unlikely(!clk->set_parent || !parent))
+ return ret;
+
+ mutex_lock(&clocks_mutex);
+ old = clk->parent;
+ if (clk->refcount)
+ __clk_enable(parent);
+ ret = clk->set_parent(clk, parent);
+ if (ret)
+ old = parent;
+ if (clk->refcount)
+ __clk_disable(old);
+ mutex_unlock(&clocks_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(clk_set_parent);
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+ if (unlikely(IS_ERR_OR_NULL(clk)))
+ return NULL;
+
+ return clk->parent;
+}
+EXPORT_SYMBOL_GPL(clk_get_parent);
diff --git a/arch/arm/mach-spmp8000/include/mach/clock.h b/arch/arm/mach-spmp8000/include/mach/clock.h
new file mode 100644
index 0000000..e5cdbd2
--- /dev/null
+++ b/arch/arm/mach-spmp8000/include/mach/clock.h
@@ -0,0 +1,37 @@
+/*
+ * SPMP8000 machines clock support
+ *
+ * Copyright (C) 2011 Zoltan Devai <zoss at devai.org>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+#ifndef __MACH_SPMP8000_CLOCK_H__
+#define __MACH_SPMP8000_CLOCK_H__
+
+struct clk {
+ char *name;
+ unsigned long rate;
+ unsigned int id;
+ unsigned int refcount;
+ int scu;
+ int scu_periph_id;
+ int dividerreg;
+ int divmask;
+ unsigned long flags;
+/* Either really fixed rate (crystal) or which we don't change for sure */
+#define CLK_FIXED_RATE BIT(0)
+/* SPMP8000 has two types of dividers, one of them needs enabling */
+#define CLK_DIVIDER_WITH_ENABLE BIT(1)
+
+ struct clk *parent;
+ unsigned long (*get_rate)(struct clk *clk);
+ unsigned long (*round_rate) (struct clk *, u32);
+ int (*set_rate) (struct clk *, unsigned long);
+ int (*set_parent) (struct clk *clk, struct clk *parent);
+ void (*enable) (struct clk *);
+ void (*disable) (struct clk *);
+};
+
+#endif /* __MACH_SPMP8000_CLOCK_H__ */
--
1.7.4.1
More information about the linux-arm-kernel
mailing list