From mboxrd@z Thu Jan 1 00:00:00 1970 From: sentient@xnet.co.nz (Tony Prisk) Date: Mon, 28 Feb 2011 11:27:14 +1300 Subject: PATCH [1/1] V4 - Clock Support for VT8500/WM8505 Message-ID: <001e01cbd6cd$7bcc2fd0$73648f70$@xnet.co.nz> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org Patch to provide clock support on VT8500-/WM8505- based SoC. V2: Included missing mach/clkdev.h Fix errors in clocks-vt8500.c V3: Correct code to allow VT8500 + WM8505 selected concurrently (for runtime detection) Added patch to initialize the clocks from existing board code (bv07.c, wm8505_7in.c) V4: Changed sending email address as seems mailing list rejecting Add error catch for failed ioremap Signed-off-by: Tony Prisk (linux at prisktech.co.nz) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 5e34579..0593323 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -878,6 +878,8 @@ config ARCH_VT8500 select GENERIC_CLOCKEVENTS select ARCH_REQUIRE_GPIOLIB select HAVE_PWM + select CLKDEV_LOOKUP + select HAVE_CLK help Support for VIA/WonderMedia VT8500/WM85xx System-on-Chip. endchoice diff --git a/arch/arm/mach-vt8500/Makefile b/arch/arm/mach-vt8500/Makefile index 81aedb7..c0ae083 100644 --- a/arch/arm/mach-vt8500/Makefile +++ b/arch/arm/mach-vt8500/Makefile @@ -1,7 +1,7 @@ -obj-y += devices.o gpio.o irq.o timer.o +obj-y += clock.o devices.o gpio.o irq.o timer.o -obj-$(CONFIG_VTWM_VERSION_VT8500) += devices-vt8500.o -obj-$(CONFIG_VTWM_VERSION_WM8505) += devices-wm8505.o +obj-$(CONFIG_VTWM_VERSION_VT8500) += devices-vt8500.o clocks-vt8500.o +obj-$(CONFIG_VTWM_VERSION_WM8505) += devices-wm8505.o clocks-wm8505.o obj-$(CONFIG_MACH_BV07) += bv07.o obj-$(CONFIG_MACH_WM8505_7IN_NETBOOK) += wm8505_7in.o diff --git a/arch/arm/mach-vt8500/bv07.c b/arch/arm/mach-vt8500/bv07.c index 94a261d..b637167 100644 --- a/arch/arm/mach-vt8500/bv07.c +++ b/arch/arm/mach-vt8500/bv07.c @@ -25,6 +25,7 @@ #include #include "devices.h" +#include "clock.h" static void __iomem *pmc_hiber; @@ -65,6 +66,7 @@ void __init bv07_init(void) vt8500_set_resources(); platform_add_devices(devices, ARRAY_SIZE(devices)); vt8500_gpio_init(); + vt8500_clock_init(); } MACHINE_START(BV07, "Benign BV07 Mini Netbook") diff --git a/arch/arm/mach-vt8500/clock.c b/arch/arm/mach-vt8500/clock.c new file mode 100644 index 0000000..9ce3a74 --- /dev/null +++ b/arch/arm/mach-vt8500/clock.c @@ -0,0 +1,164 @@ +/* + * arch/arm/mach-vt8500/clock.c + * + * Copyright (C) 2011 Tony Prisk + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clock.h" + +static LIST_HEAD(clocks); + +static DEFINE_SPINLOCK(clocks_lock); +static DEFINE_MUTEX(clocks_mutex); + +static void __clk_enable(struct clk *clk) +{ + if (clk->parent) + __clk_enable(clk->parent); + if (clk->usecount++ == 0) + if (clk->type & CLK_ENABLE) + clk->ops->enable(clk); +} + +static void __clk_disable(struct clk *clk) +{ + if (--clk->usecount == 0) + if (clk->type & CLK_ENABLE) + clk->ops->disable(clk); + if (clk->parent) + __clk_disable(clk->parent); +} + +int clk_enable(struct clk *clk) +{ + unsigned long flags; + + spin_lock_irqsave(&clocks_lock, flags); + __clk_enable(clk); + spin_unlock_irqrestore(&clocks_lock, flags); + + if (clk->delay) + udelay(clk->delay); + + return 0; +} +EXPORT_SYMBOL(clk_enable); + +void clk_disable(struct clk *clk) +{ + unsigned long flags; + + WARN_ON(clk->usecount == 0); + + spin_lock_irqsave(&clocks_lock, flags); + __clk_disable(clk); + spin_unlock_irqrestore(&clocks_lock, flags); +} +EXPORT_SYMBOL(clk_disable); + +unsigned long clk_get_rate(struct clk *clk) +{ + unsigned long rate; + + if (clk == NULL || IS_ERR(clk)) + return -EINVAL; + + rate = clk->rate; + if ((clk->ops->getrate) && (clk->type & CLK_PROGRAMABLE)) + rate = clk->ops->getrate(clk); + + return rate; +} +EXPORT_SYMBOL(clk_get_rate); + +static void propagate_rate(struct clk *root) +{ + struct clk *clk; + + list_for_each_entry(clk, &root->children, childnode) { + if (clk->ops->setrate) + clk->ops->setrate(clk, clk->rate); + if (clk->ops->getrate) + clk->rate = clk->ops->getrate(clk); + propagate_rate(clk); + } +} + +int clk_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long flags; + + if (clk == NULL || IS_ERR(clk)) + return -EINVAL; + + if (!(clk->type & CLK_PROGRAMABLE)) + return -EINVAL; + + if (clk->ops->setrate) + clk->ops->setrate(clk, rate); + + if (clk->ops->getrate) + clk->rate = clk->ops->getrate(clk); + + spin_lock_irqsave(&clocks_lock, flags); + if (!(clk->type & CLK_NO_PROPAGATE)) + propagate_rate(clk); + spin_unlock_irqrestore(&clocks_lock, flags); + + return 0; +} +EXPORT_SYMBOL(clk_set_rate); + +int clk_register(struct clk *clk) +{ + if (clk == NULL || IS_ERR(clk)) + return -EINVAL; + + if (clk->parent && !clk->parent->rate) + return -EINVAL; + + INIT_LIST_HEAD(&clk->children); + + mutex_lock(&clocks_mutex); + list_add_tail(&clk->node, &clocks); + if (clk->parent) + list_add_tail(&clk->childnode, &clk->parent->children); + mutex_unlock(&clocks_mutex); + + /* if rate already set, use it */ + if (clk->rate) + return 0; + + /* see if we can calculate the rate */ + if (clk->ops->getrate) + clk->ops->getrate(clk); + /* otherwise use the parents rate */ + else if (clk->parent) + clk->rate = clk->parent->rate; + + return 0; +} diff --git a/arch/arm/mach-vt8500/clock.h b/arch/arm/mach-vt8500/clock.h new file mode 100644 index 0000000..a4bd94f --- /dev/null +++ b/arch/arm/mach-vt8500/clock.h @@ -0,0 +1,130 @@ +/* + * arch/arm/mach-vt8500/clock.h + * + * Copyright (C) 2011 Tony Prisk + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __WMT_CLOCK_H +#define __WMT_CLOCK_H + +#include +#include + +#define CLK_PRIMARY 0x00 +#define CLK_PROGRAMABLE 0x01 +#define CLK_ENABLE 0x02 +#define CLK_NO_PROPAGATE 0x04 + +#define CLK_PM_STATUS_LOW 0x000 +#define CLK_PML_ANY_CLK_UPDATING 0x00000008 +#define CLK_PML_PLL_UPDATING 0x00000010 +#define CLK_PML_ARM_UPDATING 0x00000080 +#define CLK_PML_AHB_UPDATING 0x00000100 +#define CLK_PML_UPDATE_MASK 0x1FFF9B37 +#define CLK_PML_BUSY 0xFFFFFFFE +#define CLK_PM_STATUS_HIGH 0x004 + + +#define CLK_PLLA_MULTIPLIER 0x200 +#define CLK_PLLB_MULTIPLIER 0x204 +#define CLK_PLLC_MULTIPLIER 0x208 +#define CLK_PLLD_MULTIPLIER 0x20C + +#define CLK_EN_LOW 0x250 +#define CLK_EN_HIGH 0x254 + +/* common clocks */ +#define CLK_ARM_DIVISOR 0x300 +#define CLK_AHB_DIVISOR 0x304 +#define CLK_DDR_DIVISOR 0x310 /* mask = 0x1F special */ +#define CLK_SFM_DIVISOR 0x314 +#define CLK_SDMMC_DIVISOR 0x328 /* mask = 0x1F special */ +#define CLK_GENET_DIVISOR 0x32C +#define CLK_NAND_DIVISOR 0x330 +#define CLK_NOR_DIVISOR 0x334 +#define CLK_SPI0_DIVISOR 0x33C +#define CLK_SPI1_DIVISOR 0x340 +#define CLK_SPI2_DIVISOR 0x344 +#define CLK_PWM_DIVISOR 0x348 + +/* VT8500 clocks */ +#define CLK_DSP_DIVISOR 0x308 +#define CLK_LCD_DIVISOR 0x30C +#define CLK_PCM_DIVISOR 0x320 +#define CLK_PWM2_DIVISOR 0x324 +#define CLK_MSPRO_DIVISOR 0x32C +#define CLK_NAND_DIVISOR 0x330 +#define CLK_LCD_DIVISOR_HIGH 0x334 + +/* WM8505 clocks */ +#define CLK_KBD_DIVISOR_PRE 0x318 +#define CLK_KBD_DIVISOR 0x31C +#define CLK_APB_DIVISOR 0x350 +#define CLK_NA0_DIVISOR 0x358 +#define CLK_NA12_DIVISOR 0x35C +#define CLK_I2C0_DIVISOR 0x36C +#define CLK_I2C1_DIVISOR 0x370 +#define CLK_DVO_DIVISOR 0x374 +#define CLK_RO1_DIVISOR 0x378 +#define CLK_RO2_DIVISOR 0x37C + + +#define INIT_CLKREG(_clk, _devid, _conid) \ +{ \ + .clk = _clk, \ + .dev_id = _devid, \ + .con_id = _conid \ +} + +struct clkops { + void (*enable)(struct clk *); + void (*disable)(struct clk *); + + unsigned long (*getrate)(struct clk *); + int (*setrate)(struct clk *, unsigned long rate); +}; + +struct clk { + struct list_head node; + struct clk *parent; + const struct clkops *ops; + const char *name; + unsigned long rate; + unsigned int type; + unsigned int delay; + unsigned int usecount; + + unsigned int en_reg; + unsigned int en_bit; + + unsigned int div_reg; + + struct list_head children; + struct list_head childnode; +}; + +#ifdef CONFIG_VTWM_VERSION_WM8505 +extern int wm8505_clock_init(void); +#endif + +#ifdef CONFIG_VTWM_VERSION_VT8500 +extern int vt8500_clock_init(void); +#endif + +int clk_register(struct clk *clk); + +#endif diff --git a/arch/arm/mach-vt8500/clocks-vt8500.c b/arch/arm/mach-vt8500/clocks-vt8500.c new file mode 100644 index 0000000..e586b67 --- /dev/null +++ b/arch/arm/mach-vt8500/clocks-vt8500.c @@ -0,0 +1,622 @@ +/* + * arch/arm/mach-vt8500/clocks-vt8500.c + * + * Copyright (C) 2011 Tony Prisk + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include "clock.h" + +#define DEFINE_CKREF(_name, _rate) \ +struct clk clk_vt8500_##_name = { \ + .name = #_name, \ + .parent = NULL, \ + .ops = NULL, \ + .rate = _rate, \ + .type = CLK_PRIMARY, \ + .delay = 0, \ + .en_reg = 0, \ + .en_bit = 0 \ +} + +#define DEFINE_CKEN(_name, _ops, _enreg, _enbit) \ +struct clk clk_vt8500_##_name = { \ + .name = #_name, \ + .parent = NULL, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_ENABLE, \ + .delay = 0, \ + .en_reg = _enreg, \ + .en_bit = _enbit, \ + .div_reg = 0 \ +} + +#define DEFINE_CKPG(_name, _parent, _delay, _ops, _reg) \ +struct clk clk_vt8500_##_name = { \ + .name = #_name, \ + .parent = _parent, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_PROGRAMABLE, \ + .delay = _delay, \ + .en_reg = 0, \ + .en_bit = 0, \ + .div_reg = _reg \ +} + +#define DEFINE_CKPGNP(_name, _parent, _delay, _ops, _reg) \ +struct clk clk_vt8500_##_name = { \ + .name = #_name, \ + .parent = _parent, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_PROGRAMABLE | CLK_NO_PROPAGATE, \ + .delay = _delay, \ + .en_reg = 0, \ + .en_bit = 0, \ + .div_reg = _reg \ +} + +#define DEFINE_CKPGEN(_name, _parent, _delay, _ops, _reg, _enreg, _enbit) \ +struct clk clk_vt8500_##_name = { \ + .name = #_name, \ + .parent = _parent, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_PROGRAMABLE | CLK_ENABLE, \ + .delay = _delay, \ + .en_reg = _enreg, \ + .en_bit = _enbit, \ + .div_reg = _reg \ +} + +void __iomem *vt8500_pmc_base; + +static void wmt_pm_wait_update(void) +{ + int cnt = 1000000; + while (readl(vt8500_pmc_base + CLK_PM_STATUS_LOW) & CLK_PML_UPDATE_MASK) { + if (--cnt == 0) break; + cpu_relax(); + } + + if (cnt == 0) + printk(KERN_ERR "clock: pm_wait_update timeout\n"); +} + +static void wmt_pm_wait_busy(void) +{ + int cnt = 1000000; + while (readl(vt8500_pmc_base + CLK_PM_STATUS_LOW) & CLK_PML_BUSY) { + if (--cnt == 0) break; + cpu_relax(); + } + + if (cnt == 0) + printk(KERN_ERR "clock: pm_wait_busy timeout\n"); +} + +static void clk_cpu_speedstep(u32 plla_mul, u32 arm_div, u32 ahb_div) +{ + if (ahb_div > 1) { + /* + AHB, PLL, ARM + */ + wmt_pm_wait_busy(); + wmt_pm_wait_update(); + writel(ahb_div, vt8500_pmc_base + CLK_AHB_DIVISOR); + + wmt_pm_wait_update(); + writel(plla_mul, vt8500_pmc_base + CLK_PLLA_MULTIPLIER); + + wmt_pm_wait_update(); + writel(arm_div, vt8500_pmc_base + CLK_ARM_DIVISOR); + } else { + /* + PLL, ARM, AHB + */ + wmt_pm_wait_update(); + writel(plla_mul, vt8500_pmc_base + CLK_PLLA_MULTIPLIER); + + wmt_pm_wait_update(); + writel(arm_div, vt8500_pmc_base + CLK_ARM_DIVISOR); + + wmt_pm_wait_busy(); + wmt_pm_wait_update(); + writel(ahb_div, vt8500_pmc_base + CLK_AHB_DIVISOR); + } + wmt_pm_wait_update(); +} + +static unsigned long getrate_pll(struct clk *clk) +{ + unsigned long prate = clk->parent->rate; + u32 pll_mul = (readl(vt8500_pmc_base + clk->div_reg) & 0x1F); + u32 pll_prediv = (readl(vt8500_pmc_base + clk->div_reg) & 0x100) ? 1 : 2; + if (pll_mul < 4) + pll_mul = 1; + else + pll_mul *= 2; + + clk->rate = prate * pll_mul / pll_prediv; + return clk->rate; +} + +static int setrate_pll(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk->parent->rate; + unsigned long actual_plla_rate; + unsigned long actual_arm_rate; + u32 plla_mul; + u32 arm_div; + u32 ahb_div; + struct clk *arm_clk; + struct clk *ahb_clk; + + arm_clk = clk_get(NULL, "arm"); + ahb_clk = clk_get(NULL, "ahb"); + + /* calculate the PLL_A multiplier */ + if (rate % prate) + plla_mul = rate / prate + 1; + else + plla_mul = rate / prate; + + actual_plla_rate = prate * plla_mul; + + /* calculate the new ARM divisor */ + if (actual_plla_rate % arm_clk->rate) + arm_div = actual_plla_rate / arm_clk->rate + 1; + else + arm_div = actual_plla_rate / arm_clk->rate; + + actual_arm_rate = actual_plla_rate / arm_div; + + /* calculate the new AHB divisor */ + if (actual_arm_rate % ahb_clk->rate) + ahb_div = actual_arm_rate / ahb_clk->rate + 1; + else + ahb_div = actual_arm_rate / ahb_clk->rate; + + clk_cpu_speedstep(plla_mul, arm_div, ahb_div); + + return 0; +} + +static unsigned long getrate_ahb(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(vt8500_pmc_base + clk->div_reg) & 0x07); + if (clk_div == 0) + clk_div = 8; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_ahb(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* divisor only, so the clk can't be higher than the parent clk */ + if (rate > prate) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + /* handle special case */ + if (clk_div == 8) + clk_div = 0; + + if (clk_div > 8) + return -ENOENT; + + wmt_pm_wait_busy(); + wmt_pm_wait_update(); + writel(clk_div, vt8500_pmc_base + clk->div_reg); + + wmt_pm_wait_update(); + + return 0; +} + +static unsigned long getrate_arm(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(vt8500_pmc_base + clk->div_reg) & 0x1F); + if (clk_div == 0) + clk_div = 32; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_arm(struct clk *clk, unsigned long rate) +{ + unsigned long actual_plla_rate; + unsigned long actual_arm_rate; + + u32 plla_mul; + u32 arm_div; + u32 ahb_div; + + struct clk *plla_clk; + struct clk *ahb_clk; + + plla_clk = clk_get(NULL, "pll_a"); + ahb_clk = clk_get(NULL, "ahb"); + + /* + calculate the PLL_A multiplier + PLL_A will be set to 2 * ARM_clock (if possible) + */ + actual_plla_rate = 2 * rate; + if (actual_plla_rate % plla_clk->parent->rate) + plla_mul = actual_plla_rate / plla_clk->parent->rate + 1; + else + plla_mul = actual_plla_rate / plla_clk->parent->rate; + actual_plla_rate = plla_clk->parent->rate * plla_mul; + + /* calculate the new ARM divisor */ + if (actual_plla_rate % rate) + arm_div = actual_plla_rate / rate + 1; + else + arm_div = actual_plla_rate / rate; + + actual_arm_rate = actual_plla_rate / arm_div; + + /* calculate the new AHB divisor */ + if (actual_arm_rate % ahb_clk->rate) + ahb_div = actual_arm_rate / ahb_clk->rate + 1; + else + ahb_div = actual_arm_rate / ahb_clk->rate; + + clk_cpu_speedstep(plla_mul, arm_div, ahb_div); + + return 0; +} + +static int setrate_ddr(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* divisor only, so the clk can't be higher than the parent clk */ + if (rate > prate) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + /* clk_div must be even if != 1, so +1 if odd */ + if ((clk_div != 1) && (clk_div % 1)) + clk_div++; + + if (clk_div > 32) + return -ENOENT; + + clk->rate = prate / clk_div; + + if (clk_div == 32) + clk_div = 0; + + wmt_pm_wait_update(); + writel(clk_div, vt8500_pmc_base + clk->div_reg); + + return 0; +} + +static unsigned long getrate_sdmmc(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(vt8500_pmc_base + clk->div_reg) & 0x3F); + + if (clk_div == 0) + clk_div = 32; + + /* Check if fixed divisor is enabled (/64 fixed) */ + if (clk_div & 0x20) + clk_div = 64; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_sdmmc(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* + divisor only, so the clk can't be higher than the parent clk + also reject rate == 0 for now + */ + if ((rate > prate) || (rate == 0)) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + /* if clk_div > 32, enable the fixed divisor */ + if (clk_div > 32) + clk_div = 0x20; + + clk->rate = prate / clk_div; + + if (clk_div == 32) + clk_div = 0; + + writel(clk_div, vt8500_pmc_base + clk->div_reg); + + return 0; +} + +static unsigned long getrate_stdmask(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(vt8500_pmc_base + clk->div_reg) & 0x1F); + if (clk_div == 0) + clk_div = 32; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_stdmask(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* divisor only, so the clk can't be higher than the parent clk */ + if (rate > prate) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + if (clk_div > 32) + return -ENOENT; + + clk->rate = prate / clk_div; + + if (clk_div == 32) + clk_div = 0; + + wmt_pm_wait_update(); + writel(clk_div, vt8500_pmc_base + clk->div_reg); + + return 0; +} + +static void enable_std(struct clk *clk) +{ + u32 regtmp = readl(vt8500_pmc_base + clk->en_reg); + writel(regtmp | (1 << clk->en_bit), vt8500_pmc_base + clk->en_reg); +} + +static void disable_std(struct clk *clk) +{ + u32 regtmp = readl(vt8500_pmc_base + clk->en_reg); + writel(regtmp & ~(1 << clk->en_bit), vt8500_pmc_base + clk->en_reg); +} + + +static struct clkops clkops_pll = { + .getrate = &getrate_pll, + .setrate = &setrate_pll, +}; + +static struct clkops clkops_arm = { + .getrate = &getrate_arm, + .setrate = &setrate_arm, +}; + +static struct clkops clkops_ahb = { + .getrate = &getrate_ahb, + .setrate = &setrate_ahb, +}; + +static struct clkops clkops_ddr = { + .getrate = &getrate_stdmask, + .setrate = &setrate_ddr, +}; + +static struct clkops clkops_sdmmc = { + .getrate = &getrate_sdmmc, + .setrate = &setrate_sdmmc, + .enable = &enable_std, + .disable = &disable_std, +}; + +static struct clkops clkops_stdmask = { + .getrate = &getrate_stdmask, + .setrate = &setrate_stdmask, + .enable = &enable_std, + .disable = &disable_std, +}; + +DEFINE_CKREF(ref, 25000000); + +DEFINE_CKPGNP(pll_a, &clk_vt8500_ref, 0, &clkops_pll, CLK_PLLA_MULTIPLIER); +DEFINE_CKPGNP(arm, &clk_vt8500_pll_a, 0, &clkops_arm, CLK_ARM_DIVISOR); +DEFINE_CKPG(ahb, &clk_vt8500_arm, 0, &clkops_ahb, CLK_AHB_DIVISOR); + +DEFINE_CKPG(pll_b, &clk_vt8500_ref, 0, &clkops_pll, CLK_PLLB_MULTIPLIER); +DEFINE_CKPGEN(sdmmc, &clk_vt8500_pll_b, 0, &clkops_sdmmc, CLK_SDMMC_DIVISOR, \ + CLK_EN_HIGH, 18); +DEFINE_CKPGEN(nand, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_NAND_DIVISOR, \ + CLK_EN_HIGH, 16); +DEFINE_CKPGEN(sfm, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_SFM_DIVISOR, \ + CLK_EN_HIGH, 23); +DEFINE_CKPGEN(mspro, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_MSPRO_DIVISOR,\ + CLK_EN_HIGH, 17); +DEFINE_CKPGEN(spi0, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_SPI0_DIVISOR, \ + CLK_EN_LOW, 12); +DEFINE_CKPGEN(spi1, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_SPI1_DIVISOR, \ + CLK_EN_LOW, 13); +DEFINE_CKPG(spi2, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_SPI2_DIVISOR); +DEFINE_CKPG(dsp, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_DSP_DIVISOR); +DEFINE_CKPGEN(lcd, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_LCD_DIVISOR, \ + CLK_EN_HIGH, 10); +DEFINE_CKPGEN(lcd_high, &clk_vt8500_pll_b, 0, &clkops_stdmask, \ + CLK_LCD_DIVISOR_HIGH, CLK_EN_HIGH, 10); +DEFINE_CKPGEN(pcm, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_PCM_DIVISOR, \ + CLK_EN_LOW, 16); +DEFINE_CKPGEN(pwm, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_PWM_DIVISOR, \ + CLK_EN_LOW, 14); +DEFINE_CKPGEN(pwm2, &clk_vt8500_pll_b, 0, &clkops_stdmask, CLK_PWM2_DIVISOR, \ + CLK_EN_LOW, 14); + +DEFINE_CKPG(pll_c, &clk_vt8500_ref, 0, &clkops_pll, CLK_PLLC_MULTIPLIER); +DEFINE_CKPGEN(ddr, &clk_vt8500_pll_c, 0, &clkops_ddr, CLK_DDR_DIVISOR, \ + CLK_EN_HIGH, 0); + +/* Enable/Disable clocks - Non-programmable */ +DEFINE_CKEN(ahb3, &clkops_stdmask, CLK_EN_LOW, 31); +DEFINE_CKEN(ahb2_1, &clkops_stdmask, CLK_EN_LOW, 29); +DEFINE_CKEN(h264, &clkops_stdmask, CLK_EN_LOW, 28); +DEFINE_CKEN(ahb2_2, &clkops_stdmask, CLK_EN_LOW, 27); +DEFINE_CKEN(ahb2_3, &clkops_stdmask, CLK_EN_LOW, 26); +DEFINE_CKEN(ahb2_4, &clkops_stdmask, CLK_EN_LOW, 25); +DEFINE_CKEN(ahb2_5, &clkops_stdmask, CLK_EN_LOW, 24); +DEFINE_CKEN(vdu, &clkops_stdmask, CLK_EN_LOW, 23); +DEFINE_CKEN(stri, &clkops_stdmask, CLK_EN_LOW, 22); +DEFINE_CKEN(tsbk, &clkops_stdmask, CLK_EN_LOW, 21); +DEFINE_CKEN(ahb1, &clkops_stdmask, CLK_EN_LOW, 20); +DEFINE_CKEN(scc, &clkops_stdmask, CLK_EN_LOW, 19); +DEFINE_CKEN(keypad, &clkops_stdmask, CLK_EN_LOW, 18); +DEFINE_CKEN(ir, &clkops_stdmask, CLK_EN_LOW, 17); +DEFINE_CKEN(ac97, &clkops_stdmask, CLK_EN_LOW, 15); +DEFINE_CKEN(gpio, &clkops_stdmask, CLK_EN_LOW, 11); +DEFINE_CKEN(aam3, &clkops_stdmask, CLK_EN_LOW, 10); +DEFINE_CKEN(aam2, &clkops_stdmask, CLK_EN_LOW, 9); +DEFINE_CKEN(aam1, &clkops_stdmask, CLK_EN_LOW, 8); +DEFINE_CKEN(aam0, &clkops_stdmask, CLK_EN_LOW, 7); +DEFINE_CKEN(i2c, &clkops_stdmask, CLK_EN_LOW, 5); +DEFINE_CKEN(uart3, &clkops_stdmask, CLK_EN_LOW, 4); +DEFINE_CKEN(uart2, &clkops_stdmask, CLK_EN_LOW, 3); +DEFINE_CKEN(uart1, &clkops_stdmask, CLK_EN_LOW, 2); +DEFINE_CKEN(uart0, &clkops_stdmask, CLK_EN_LOW, 1); +DEFINE_CKEN(lpc, &clkops_stdmask, CLK_EN_HIGH, 27); +DEFINE_CKEN(eth_phy, &clkops_stdmask, CLK_EN_HIGH, 26); +DEFINE_CKEN(sae, &clkops_stdmask, CLK_EN_HIGH, 24); +DEFINE_CKEN(usb_otg, &clkops_stdmask, CLK_EN_HIGH, 22); +DEFINE_CKEN(eth_mac, &clkops_stdmask, CLK_EN_HIGH, 20); +DEFINE_CKEN(pci_bridge, &clkops_stdmask, CLK_EN_HIGH, 19); +DEFINE_CKEN(ahb_bridge, &clkops_stdmask, CLK_EN_HIGH, 13); +DEFINE_CKEN(cf, &clkops_stdmask, CLK_EN_HIGH, 12); +DEFINE_CKEN(ata, &clkops_stdmask, CLK_EN_HIGH, 11); +DEFINE_CKEN(dma1, &clkops_stdmask, CLK_EN_HIGH, 9); +DEFINE_CKEN(dma0, &clkops_stdmask, CLK_EN_HIGH, 8); + +static struct clk_lookup vt8500_clkregs[] = { + INIT_CLKREG(&clk_vt8500_ref, NULL, "ref"), + INIT_CLKREG(&clk_vt8500_pll_a, NULL, "pll_a"), + INIT_CLKREG(&clk_vt8500_pll_b, NULL, "pll_b"), + INIT_CLKREG(&clk_vt8500_pll_c, NULL, "pll_c"), + INIT_CLKREG(&clk_vt8500_arm, NULL, "arm"), + INIT_CLKREG(&clk_vt8500_ahb, NULL, "ahb"), + INIT_CLKREG(&clk_vt8500_ahb_bridge, NULL, "ahb_bridge"), + INIT_CLKREG(&clk_vt8500_ddr, NULL, "ddr"), + + INIT_CLKREG(&clk_vt8500_sdmmc, NULL, "sdmmc"), + + INIT_CLKREG(&clk_vt8500_ahb1, NULL, "ahb1"), + INIT_CLKREG(&clk_vt8500_ahb2_1, NULL, "ahb2-1"), + INIT_CLKREG(&clk_vt8500_ahb2_2, NULL, "ahb2-2"), + INIT_CLKREG(&clk_vt8500_ahb2_3, NULL, "ahb2-3"), + INIT_CLKREG(&clk_vt8500_ahb2_4, NULL, "ahb2-4"), + INIT_CLKREG(&clk_vt8500_ahb2_5, NULL, "ahb2-5"), + INIT_CLKREG(&clk_vt8500_ahb3, NULL, "ahb3"), + + INIT_CLKREG(&clk_vt8500_h264, NULL, "h264"), + INIT_CLKREG(&clk_vt8500_vdu, NULL, "vdu"), + INIT_CLKREG(&clk_vt8500_stri, NULL, "stri"), + INIT_CLKREG(&clk_vt8500_tsbk, NULL, "tsbk"), + + INIT_CLKREG(&clk_vt8500_scc, NULL, "scc"), + INIT_CLKREG(&clk_vt8500_keypad, NULL, "keypad"), + INIT_CLKREG(&clk_vt8500_ir, NULL, "ir"), + INIT_CLKREG(&clk_vt8500_ac97, NULL, "ac97"), + INIT_CLKREG(&clk_vt8500_gpio, NULL, "gpio"), + INIT_CLKREG(&clk_vt8500_aam3, NULL, "aam3"), + INIT_CLKREG(&clk_vt8500_aam2, NULL, "aam2"), + INIT_CLKREG(&clk_vt8500_aam1, NULL, "aam1"), + INIT_CLKREG(&clk_vt8500_aam0, NULL, "aam0"), + INIT_CLKREG(&clk_vt8500_i2c, NULL, "i2c"), + INIT_CLKREG(&clk_vt8500_uart3, NULL, "uart3"), + INIT_CLKREG(&clk_vt8500_uart2, NULL, "uart2"), + INIT_CLKREG(&clk_vt8500_uart1, NULL, "uart1"), + INIT_CLKREG(&clk_vt8500_uart0, NULL, "uart0"), + + INIT_CLKREG(&clk_vt8500_lpc, NULL, "lpc"), + INIT_CLKREG(&clk_vt8500_eth_phy, NULL, "eth-phy"), + INIT_CLKREG(&clk_vt8500_sae, NULL, "sae"), + INIT_CLKREG(&clk_vt8500_usb_otg, NULL, "usb-otg"), + INIT_CLKREG(&clk_vt8500_eth_mac, NULL, "eth-mac"), + INIT_CLKREG(&clk_vt8500_pci_bridge, NULL, "pci-bridge"), + INIT_CLKREG(&clk_vt8500_cf, NULL, "cf"), + INIT_CLKREG(&clk_vt8500_ata, NULL, "ata"), + INIT_CLKREG(&clk_vt8500_dma1, NULL, "dma1"), + INIT_CLKREG(&clk_vt8500_dma0, NULL, "dma0"), + + INIT_CLKREG(&clk_vt8500_nand, NULL, "nand"), + INIT_CLKREG(&clk_vt8500_sfm, NULL, "sfm"), + INIT_CLKREG(&clk_vt8500_mspro, NULL, "mspro"), + INIT_CLKREG(&clk_vt8500_spi0, NULL, "spi0"), + INIT_CLKREG(&clk_vt8500_spi1, NULL, "spi1"), + INIT_CLKREG(&clk_vt8500_spi2, NULL, "spi2"), + INIT_CLKREG(&clk_vt8500_pwm, NULL, "pwm"), + INIT_CLKREG(&clk_vt8500_pwm2, NULL, "pwm2"), + INIT_CLKREG(&clk_vt8500_dsp, NULL, "dsp"), + INIT_CLKREG(&clk_vt8500_lcd, NULL, "lcd"), + INIT_CLKREG(&clk_vt8500_lcd_high, NULL, "lcd-high"), + INIT_CLKREG(&clk_vt8500_pcm, NULL, "pcm"), +}; + +int vt8500_clock_init(void) +{ + struct clk_lookup *c; + + /* map to PMC io memory */ + vt8500_pmc_base = ioremap(VT8500_PMC_BASE, 0x380); + if (!vt8500_pmc_base) { + printk(KERN_ERR "clock: failed to remap io\n"); + return -ENOMEM; + } + + for (c = vt8500_clkregs; c->clk; c++) + clk_register(c->clk); + + clkdev_add_table(vt8500_clkregs, ARRAY_SIZE(vt8500_clkregs)); + + return 0; +} diff --git a/arch/arm/mach-vt8500/clocks-wm8505.c b/arch/arm/mach-vt8500/clocks-wm8505.c new file mode 100644 index 0000000..8e64190 --- /dev/null +++ b/arch/arm/mach-vt8500/clocks-wm8505.c @@ -0,0 +1,639 @@ +/* + * arch/arm/mach-vt8500/clocks-wm8505.c + * + * Copyright (C) 2011 Tony Prisk + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include "clock.h" + +#define DEFINE_CKREF(_name, _rate) \ +struct clk clk_wm8505_##_name = { \ + .name = #_name, \ + .parent = NULL, \ + .ops = NULL, \ + .rate = _rate, \ + .type = CLK_PRIMARY, \ + .delay = 0, \ + .en_reg = 0, \ + .en_bit = 0 \ +} + +#define DEFINE_CKEN(_name, _ops, _enreg, _enbit) \ +struct clk clk_wm8505_##_name = { \ + .name = #_name, \ + .parent = NULL, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_ENABLE, \ + .delay = 0, \ + .en_reg = _enreg, \ + .en_bit = _enbit, \ + .div_reg = 0 \ +} + +#define DEFINE_CKPG(_name, _parent, _delay, _ops, _reg) \ +struct clk clk_wm8505_##_name = { \ + .name = #_name, \ + .parent = _parent, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_PROGRAMABLE, \ + .delay = _delay, \ + .en_reg = 0, \ + .en_bit = 0, \ + .div_reg = _reg \ +} + +#define DEFINE_CKPGNP(_name, _parent, _delay, _ops, _reg) \ +struct clk clk_wm8505_##_name = { \ + .name = #_name, \ + .parent = _parent, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_PROGRAMABLE | CLK_NO_PROPAGATE, \ + .delay = _delay, \ + .en_reg = 0, \ + .en_bit = 0, \ + .div_reg = _reg \ +} + +#define DEFINE_CKPGEN(_name, _parent, _delay, _ops, _reg, _enreg, _enbit) \ +struct clk clk_wm8505_##_name = { \ + .name = #_name, \ + .parent = _parent, \ + .ops = _ops, \ + .rate = 0, \ + .type = CLK_PROGRAMABLE | CLK_ENABLE, \ + .delay = _delay, \ + .en_reg = _enreg, \ + .en_bit = _enbit, \ + .div_reg = _reg \ +} + +void __iomem *wm8505_pmc_base; + +static void wmt_pm_wait_update(void) +{ + int cnt = 1000000; + while (readl(wm8505_pmc_base + CLK_PM_STATUS_LOW) & CLK_PML_UPDATE_MASK) { + if (--cnt == 0) break; + cpu_relax(); + } + + if (cnt == 0) + printk(KERN_ERR "clock: pm_wait_update timeout\n"); +} + +static void wmt_pm_wait_busy(void) +{ + int cnt = 1000000; + while (readl(wm8505_pmc_base + CLK_PM_STATUS_LOW) & CLK_PML_BUSY) { + if (--cnt == 0) break; + cpu_relax(); + } + + if (cnt == 0) + printk(KERN_ERR "clock: pm_wait_busy timeout\n"); +} + +static void clk_cpu_speedstep(u32 plla_mul, u32 arm_div, u32 ahb_div) +{ + if (ahb_div > 1) { + /* + AHB, PLL, ARM + */ + wmt_pm_wait_busy(); + wmt_pm_wait_update(); + writel(ahb_div, wm8505_pmc_base + CLK_AHB_DIVISOR); + + wmt_pm_wait_update(); + writel(plla_mul, wm8505_pmc_base + CLK_PLLA_MULTIPLIER); + + wmt_pm_wait_update(); + writel(arm_div, wm8505_pmc_base + CLK_ARM_DIVISOR); + } else { + /* + PLL, ARM, AHB + */ + wmt_pm_wait_update(); + writel(plla_mul, wm8505_pmc_base + CLK_PLLA_MULTIPLIER); + + wmt_pm_wait_update(); + writel(arm_div, wm8505_pmc_base + CLK_ARM_DIVISOR); + + wmt_pm_wait_busy(); + wmt_pm_wait_update(); + writel(ahb_div, wm8505_pmc_base + CLK_AHB_DIVISOR); + } + wmt_pm_wait_update(); +} + +static unsigned long getrate_pll(struct clk *clk) +{ + unsigned long prate = clk->parent->rate; + u32 pll_mul = (readl(wm8505_pmc_base + clk->div_reg) & 0x1F); + u32 pll_prediv = (readl(wm8505_pmc_base + clk->div_reg) & 0x100) ? 1 : 2; + if (pll_mul < 4) + pll_mul = 1; + else + pll_mul *= 2; + + clk->rate = prate * pll_mul / pll_prediv; + return clk->rate; +} + +static int setrate_pll(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk->parent->rate; + unsigned long actual_plla_rate; + unsigned long actual_arm_rate; + u32 plla_mul; + u32 arm_div; + u32 ahb_div; + struct clk *arm_clk; + struct clk *ahb_clk; + + arm_clk = clk_get(NULL, "arm"); + ahb_clk = clk_get(NULL, "ahb"); + + /* calculate the PLL_A multiplier */ + if (rate % prate) + plla_mul = rate / prate + 1; + else + plla_mul = rate / prate; + + actual_plla_rate = prate * plla_mul; + + /* calculate the new ARM divisor */ + if (actual_plla_rate % arm_clk->rate) + arm_div = actual_plla_rate / arm_clk->rate + 1; + else + arm_div = actual_plla_rate / arm_clk->rate; + + actual_arm_rate = actual_plla_rate / arm_div; + + /* calculate the new AHB divisor */ + if (actual_arm_rate % ahb_clk->rate) + ahb_div = actual_arm_rate / ahb_clk->rate + 1; + else + ahb_div = actual_arm_rate / ahb_clk->rate; + + clk_cpu_speedstep(plla_mul, arm_div, ahb_div); + + return 0; +} + +static unsigned long getrate_ahb(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(wm8505_pmc_base + clk->div_reg) & 0x07); + if (clk_div == 0) + clk_div = 8; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_ahb(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* divisor only, so the clk can't be higher than the parent clk */ + if (rate > prate) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + /* handle special case */ + if (clk_div == 8) + clk_div = 0; + + if (clk_div > 8) + return -ENOENT; + + wmt_pm_wait_busy(); + wmt_pm_wait_update(); + writel(clk_div, wm8505_pmc_base + clk->div_reg); + + wmt_pm_wait_update(); + + return 0; +} + +static unsigned long getrate_arm(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(wm8505_pmc_base + clk->div_reg) & 0x1F); + if (clk_div == 0) + clk_div = 32; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_arm(struct clk *clk, unsigned long rate) +{ + unsigned long actual_plla_rate; + unsigned long actual_arm_rate; + + u32 plla_mul; + u32 arm_div; + u32 ahb_div; + + struct clk *plla_clk; + struct clk *ahb_clk; + + plla_clk = clk_get(NULL, "pll_a"); + ahb_clk = clk_get(NULL, "ahb"); + + /* + calculate the PLL_A multiplier + PLL_A will be set to 2 * ARM_clock (if possible) + */ + actual_plla_rate = 2 * rate; + if (actual_plla_rate % plla_clk->parent->rate) + plla_mul = actual_plla_rate / plla_clk->parent->rate + 1; + else + plla_mul = actual_plla_rate / plla_clk->parent->rate; + actual_plla_rate = plla_clk->parent->rate * plla_mul; + + /* calculate the new ARM divisor */ + if (actual_plla_rate % rate) + arm_div = actual_plla_rate / rate + 1; + else + arm_div = actual_plla_rate / rate; + + actual_arm_rate = actual_plla_rate / arm_div; + + /* calculate the new AHB divisor */ + if (actual_arm_rate % ahb_clk->rate) + ahb_div = actual_arm_rate / ahb_clk->rate + 1; + else + ahb_div = actual_arm_rate / ahb_clk->rate; + + clk_cpu_speedstep(plla_mul, arm_div, ahb_div); + + return 0; +} + +static int setrate_ddr(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* divisor only, so the clk can't be higher than the parent clk */ + if (rate > prate) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + /* clk_div must be even if != 1, so +1 if odd */ + if ((clk_div != 1) && (clk_div % 1)) + clk_div++; + + if (clk_div > 32) + return -ENOENT; + + clk->rate = prate / clk_div; + + if (clk_div == 32) + clk_div = 0; + + wmt_pm_wait_update(); + writel(clk_div, wm8505_pmc_base + clk->div_reg); + + return 0; +} + +static unsigned long getrate_sdmmc(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(wm8505_pmc_base + clk->div_reg) & 0x3F); + + if (clk_div == 0) + clk_div = 32; + + /* Check if fixed divisor is enabled (/64 fixed) */ + if (clk_div & 0x20) + clk_div = 64; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_sdmmc(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* + divisor only, so the clk can't be higher than the parent clk + also reject rate == 0 for now + */ + if ((rate > prate) || (rate == 0)) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + /* if clk_div > 32, enable the fixed divisor */ + if (clk_div > 32) + clk_div = 0x20; + + clk->rate = prate / clk_div; + + if (clk_div == 32) + clk_div = 0; + + writel(clk_div, wm8505_pmc_base + clk->div_reg); + + return 0; +} + +static unsigned long getrate_stdmask(struct clk *clk) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div = (readl(wm8505_pmc_base + clk->div_reg) & 0x1F); + if (clk_div == 0) + clk_div = 32; + + clk->rate = prate / clk_div; + return clk->rate; +} + +static int setrate_stdmask(struct clk *clk, unsigned long rate) +{ + unsigned long prate = clk_get_rate(clk->parent); + u32 clk_div; + + /* divisor only, so the clk can't be higher than the parent clk */ + if (rate > prate) + return -EINVAL; + + if (prate % rate) + clk_div = prate / rate + 1; + else + clk_div = prate / rate; + + if (clk_div > 32) + return -ENOENT; + + clk->rate = prate / clk_div; + + if (clk_div == 32) + clk_div = 0; + + wmt_pm_wait_update(); + writel(clk_div, wm8505_pmc_base + clk->div_reg); + + return 0; +} + +static void enable_std(struct clk *clk) +{ + u32 regtmp = readl(wm8505_pmc_base + clk->en_reg); + writel(regtmp | (1 << clk->en_bit), wm8505_pmc_base + clk->en_reg); +} + +static void disable_std(struct clk *clk) +{ + u32 regtmp = readl(wm8505_pmc_base + clk->en_reg); + writel(regtmp & ~(1 << clk->en_bit), wm8505_pmc_base + clk->en_reg); +} + + +static struct clkops clkops_pll = { + .getrate = &getrate_pll, + .setrate = &setrate_pll, +}; + +static struct clkops clkops_arm = { + .getrate = &getrate_arm, + .setrate = &setrate_arm, +}; + +static struct clkops clkops_ahb = { + .getrate = &getrate_ahb, + .setrate = &setrate_ahb, +}; + +static struct clkops clkops_ddr = { + .getrate = &getrate_stdmask, + .setrate = &setrate_ddr, +}; + +static struct clkops clkops_sdmmc = { + .getrate = &getrate_sdmmc, + .setrate = &setrate_sdmmc, + .enable = &enable_std, + .disable = &disable_std, +}; + +static struct clkops clkops_stdmask = { + .getrate = &getrate_stdmask, + .setrate = &setrate_stdmask, + .enable = &enable_std, + .disable = &disable_std, +}; + + +DEFINE_CKREF(ref, 25000000); + +DEFINE_CKPGNP(pll_a, &clk_wm8505_ref, 0, &clkops_pll, CLK_PLLA_MULTIPLIER); +DEFINE_CKPGNP(arm, &clk_wm8505_pll_a, 0, &clkops_arm, CLK_ARM_DIVISOR); +DEFINE_CKPG(ahb, &clk_wm8505_arm, 0, &clkops_ahb, CLK_AHB_DIVISOR); +DEFINE_CKPG(apb, &clk_wm8505_arm, 0, &clkops_stdmask, CLK_APB_DIVISOR); + +DEFINE_CKPG(pll_b, &clk_wm8505_ref, 0, &clkops_pll, CLK_PLLB_MULTIPLIER); +DEFINE_CKPGEN(sdmmc, &clk_wm8505_pll_b, 0, &clkops_sdmmc, CLK_SDMMC_DIVISOR, \ + CLK_EN_HIGH, 18); +DEFINE_CKPGEN(nand, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_NAND_DIVISOR, \ + CLK_EN_HIGH, 16); +DEFINE_CKPGEN(kbd_pre, &clk_wm8505_pll_b, 0, &clkops_stdmask, \ + CLK_KBD_DIVISOR_PRE, CLK_EN_HIGH, 4); +DEFINE_CKPGEN(kbd, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_KBD_DIVISOR, \ + CLK_EN_HIGH, 4); +DEFINE_CKPGEN(sfm, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_SFM_DIVISOR, \ + CLK_EN_HIGH, 23); +DEFINE_CKPG(genet, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_GENET_DIVISOR); +DEFINE_CKPGEN(nor, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_NOR_DIVISOR, \ + CLK_EN_HIGH, 3); +DEFINE_CKPGEN(spi0, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_SPI0_DIVISOR, \ + CLK_EN_LOW, 12); +DEFINE_CKPGEN(spi1, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_SPI1_DIVISOR, \ + CLK_EN_LOW, 13); +DEFINE_CKPGEN(spi2, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_SPI2_DIVISOR, \ + CLK_EN_LOW, 14); +DEFINE_CKPGEN(pwm, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_PWM_DIVISOR, \ + CLK_EN_LOW, 10); +DEFINE_CKPGEN(na0, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_NA0_DIVISOR, \ + CLK_EN_HIGH, 1); +DEFINE_CKPGEN(na12, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_NA12_DIVISOR, \ + CLK_EN_HIGH, 2); +DEFINE_CKPGEN(i2c0, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_I2C0_DIVISOR, \ + CLK_EN_LOW, 5); +DEFINE_CKPGEN(i2c1, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_I2C1_DIVISOR, \ + CLK_EN_LOW, 0); +DEFINE_CKPGEN(dvo, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_DVO_DIVISOR, \ + CLK_EN_LOW, 18); +DEFINE_CKPG(ro1, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_RO1_DIVISOR); +DEFINE_CKPG(ro2, &clk_wm8505_pll_b, 0, &clkops_stdmask, CLK_RO2_DIVISOR); + +DEFINE_CKPG(pll_c, &clk_wm8505_ref, 0, &clkops_pll, CLK_PLLC_MULTIPLIER); +DEFINE_CKPGEN(ddr, &clk_wm8505_pll_c, 0, &clkops_ddr, CLK_DDR_DIVISOR, \ + CLK_EN_HIGH, 0); + +DEFINE_CKPG(pll_d, &clk_wm8505_ref, 0, &clkops_pll, CLK_PLLD_MULTIPLIER); + + +/* Enable/Disable clocks - Non-programmable */ +DEFINE_CKEN(govrhd, &clkops_stdmask, CLK_EN_LOW, 30); +DEFINE_CKEN(ge, &clkops_stdmask, CLK_EN_LOW, 29); +DEFINE_CKEN(jenc, &clkops_stdmask, CLK_EN_LOW, 27); +DEFINE_CKEN(amp, &clkops_stdmask, CLK_EN_LOW, 24); +DEFINE_CKEN(uart5, &clkops_stdmask, CLK_EN_LOW, 23); +DEFINE_CKEN(uart4, &clkops_stdmask, CLK_EN_LOW, 22); +DEFINE_CKEN(scc, &clkops_stdmask, CLK_EN_LOW, 21); +DEFINE_CKEN(ac97, &clkops_stdmask, CLK_EN_LOW, 19); +DEFINE_CKEN(cir, &clkops_stdmask, CLK_EN_LOW, 17); +DEFINE_CKEN(i2s, &clkops_stdmask, CLK_EN_LOW, 16); +DEFINE_CKEN(gpio, &clkops_stdmask, CLK_EN_LOW, 11); +DEFINE_CKEN(keypad, &clkops_stdmask, CLK_EN_LOW, 9); +DEFINE_CKEN(rtc, &clkops_stdmask, CLK_EN_LOW, 7); +DEFINE_CKEN(i2c_slave, &clkops_stdmask, CLK_EN_LOW, 6); +DEFINE_CKEN(uart3, &clkops_stdmask, CLK_EN_LOW, 4); +DEFINE_CKEN(uart2, &clkops_stdmask, CLK_EN_LOW, 3); +DEFINE_CKEN(uart1, &clkops_stdmask, CLK_EN_LOW, 2); +DEFINE_CKEN(uart0, &clkops_stdmask, CLK_EN_LOW, 1); +DEFINE_CKEN(vpp, &clkops_stdmask, CLK_EN_HIGH, 31); +DEFINE_CKEN(vid, &clkops_stdmask, CLK_EN_HIGH, 30); +DEFINE_CKEN(govw, &clkops_stdmask, CLK_EN_HIGH, 29); +DEFINE_CKEN(scl444u, &clkops_stdmask, CLK_EN_HIGH, 28); +DEFINE_CKEN(eth_phy, &clkops_stdmask, CLK_EN_HIGH, 26); +DEFINE_CKEN(sae, &clkops_stdmask, CLK_EN_HIGH, 24); +DEFINE_CKEN(sys, &clkops_stdmask, CLK_EN_HIGH, 21); +DEFINE_CKEN(eth_mac0, &clkops_stdmask, CLK_EN_HIGH, 20); +DEFINE_CKEN(sdtv, &clkops_stdmask, CLK_EN_HIGH, 14); +DEFINE_CKEN(ahb_bridge, &clkops_stdmask, CLK_EN_HIGH, 13); +DEFINE_CKEN(pdma, &clkops_stdmask, CLK_EN_HIGH, 9); +DEFINE_CKEN(udc, &clkops_stdmask, CLK_EN_HIGH, 8); +DEFINE_CKEN(uhc, &clkops_stdmask, CLK_EN_HIGH, 7); +DEFINE_CKEN(dma, &clkops_stdmask, CLK_EN_HIGH, 5); + +static struct clk_lookup wm8505_clkregs[] = { + INIT_CLKREG(&clk_wm8505_ref, NULL, "ref"), + INIT_CLKREG(&clk_wm8505_pll_a, NULL, "pll_a"), + INIT_CLKREG(&clk_wm8505_pll_b, NULL, "pll_b"), + INIT_CLKREG(&clk_wm8505_pll_c, NULL, "pll_c"), + INIT_CLKREG(&clk_wm8505_pll_d, NULL, "pll_d"), + INIT_CLKREG(&clk_wm8505_arm, NULL, "arm"), + INIT_CLKREG(&clk_wm8505_ahb, NULL, "ahb"), + INIT_CLKREG(&clk_wm8505_ahb_bridge, NULL, "ahb_bridge"), + INIT_CLKREG(&clk_wm8505_apb, NULL, "apb"), + INIT_CLKREG(&clk_wm8505_ddr, NULL, "ddr"), + + INIT_CLKREG(&clk_wm8505_nor, NULL, "nor"), + INIT_CLKREG(&clk_wm8505_nand, NULL, "nand"), + INIT_CLKREG(&clk_wm8505_sdmmc, NULL, "sdmmc"), + + INIT_CLKREG(&clk_wm8505_keypad, NULL, "keypad"), + INIT_CLKREG(&clk_wm8505_kbd_pre, NULL, "kbd_pre"), + INIT_CLKREG(&clk_wm8505_kbd, NULL, "kbd"), + INIT_CLKREG(&clk_wm8505_sfm, NULL, "sfm"), + INIT_CLKREG(&clk_wm8505_genet, NULL, "genet"), + + INIT_CLKREG(&clk_wm8505_spi0, NULL, "spi0"), + INIT_CLKREG(&clk_wm8505_spi1, NULL, "spi1"), + INIT_CLKREG(&clk_wm8505_spi2, NULL, "spi2"), + + INIT_CLKREG(&clk_wm8505_govrhd, NULL, "govrhd"), + INIT_CLKREG(&clk_wm8505_govw, NULL, "govw"), + INIT_CLKREG(&clk_wm8505_ge, NULL, "ge"), + INIT_CLKREG(&clk_wm8505_pwm, NULL, "pwm"), + INIT_CLKREG(&clk_wm8505_dvo, NULL, "dvo"), + INIT_CLKREG(&clk_wm8505_jenc, NULL, "jenc"), + INIT_CLKREG(&clk_wm8505_sdtv, NULL, "sdtv"), + + INIT_CLKREG(&clk_wm8505_cir, NULL, "cir"), + INIT_CLKREG(&clk_wm8505_gpio, NULL, "gpio"), + INIT_CLKREG(&clk_wm8505_rtc, NULL, "rtc"), + + INIT_CLKREG(&clk_wm8505_i2c0, NULL, "i2c0"), + INIT_CLKREG(&clk_wm8505_i2c1, NULL, "i2c1"), + INIT_CLKREG(&clk_wm8505_i2c_slave, NULL, "i2c_slave"), + + INIT_CLKREG(&clk_wm8505_ac97, NULL, "ac97"), + INIT_CLKREG(&clk_wm8505_i2s, NULL, "i2s"), + + INIT_CLKREG(&clk_wm8505_eth_phy, NULL, "eth_phy"), + INIT_CLKREG(&clk_wm8505_eth_mac0, NULL, "eth_mac0"), + + INIT_CLKREG(&clk_wm8505_uart5, NULL, "uart5"), + INIT_CLKREG(&clk_wm8505_uart4, NULL, "uart4"), + INIT_CLKREG(&clk_wm8505_uart3, NULL, "uart3"), + INIT_CLKREG(&clk_wm8505_uart2, NULL, "uart2"), + INIT_CLKREG(&clk_wm8505_uart1, NULL, "uart1"), + INIT_CLKREG(&clk_wm8505_uart0, NULL, "uart0"), + + INIT_CLKREG(&clk_wm8505_na0, NULL, "na0"), + INIT_CLKREG(&clk_wm8505_na12, NULL, "na12"), + INIT_CLKREG(&clk_wm8505_ro1, NULL, "ro1"), + INIT_CLKREG(&clk_wm8505_ro2, NULL, "ro2"), + INIT_CLKREG(&clk_wm8505_amp, NULL, "amp"), + INIT_CLKREG(&clk_wm8505_scc, NULL, "scc"), + INIT_CLKREG(&clk_wm8505_vpp, NULL, "vpp"), + INIT_CLKREG(&clk_wm8505_vid, NULL, "vid"), + INIT_CLKREG(&clk_wm8505_scl444u, NULL, "scl444u"), + INIT_CLKREG(&clk_wm8505_sae, NULL, "sae"), + INIT_CLKREG(&clk_wm8505_sys, NULL, "sys"), + INIT_CLKREG(&clk_wm8505_udc, NULL, "udc"), + INIT_CLKREG(&clk_wm8505_uhc, NULL, "uhc"), + INIT_CLKREG(&clk_wm8505_dma, NULL, "dma"), + INIT_CLKREG(&clk_wm8505_pdma, NULL, "pdma"), +}; + +int wm8505_clock_init(void) +{ + struct clk_lookup *c; + + /* map to PMC io memory */ + wm8505_pmc_base = ioremap(WM8505_PMC_BASE, 0x380); + if (!wm8505_pmc_base) { + printk(KERN_ERR "clock: failed to remap io\n"); + return -ENOMEM; + } + + for (c = wm8505_clkregs; c->clk; c++) + clk_register(c->clk); + + clkdev_add_table(wm8505_clkregs, ARRAY_SIZE(wm8505_clkregs)); + + return 0; +} diff --git a/arch/arm/mach-vt8500/include/mach/clkdev.h b/arch/arm/mach-vt8500/include/mach/clkdev.h new file mode 100644 index 0000000..62b0a6a --- /dev/null +++ b/arch/arm/mach-vt8500/include/mach/clkdev.h @@ -0,0 +1,7 @@ +#ifndef __WMT_CLKDEV_H +#define __WMT_CLKDEV_H + +#define __clk_get(clk) ({ 1; }) +#define __clk_put(clk) do { } while (0) + +#endif diff --git a/arch/arm/mach-vt8500/wm8505_7in.c b/arch/arm/mach-vt8500/wm8505_7in.c index e73aadb..ce75c77 100644 --- a/arch/arm/mach-vt8500/wm8505_7in.c +++ b/arch/arm/mach-vt8500/wm8505_7in.c @@ -25,6 +25,7 @@ #include #include "devices.h" +#include "clock.h" static void __iomem *pmc_hiber; @@ -65,6 +66,7 @@ void __init wm8505_7in_init(void) wm8505_set_resources(); platform_add_devices(devices, ARRAY_SIZE(devices)); vt8500_gpio_init(); + wm8505_clock_init(); } MACHINE_START(WM8505_7IN_NETBOOK, "WM8505 7-inch generic netbook") -------------- next part -------------- An HTML attachment was scrubbed... URL: