@@ -999,6 +999,8 @@ config ARCH_VT8500
select ARCH_HAS_CPUFREQ
select GENERIC_CLOCKEVENTS
select ARCH_REQUIRE_GPIOLIB
+ select CLKDEV_LOOKUP
+ select COMMON_CLK
help
Support for VIA/WonderMedia VT8500/WM85xx System-on-Chip.
@@ -24,6 +24,7 @@
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <mach/restart.h>
+#include <mach/clk.h>
#include "devices.h"
@@ -67,6 +68,9 @@ void __init bv07_init(void)
vt8500_set_resources();
platform_add_devices(devices, ARRAY_SIZE(devices));
vt8500_gpio_init();
+
+ /* this call can be moved to timer_init once devicetree is ready */
+ vt8500_clk_init();
}
MACHINE_START(BV07, "Benign BV07 Mini Netbook")
new file mode 100644
@@ -0,0 +1,9 @@
+#ifndef __WMT_CLK
+#define __WMT_CLK
+
+void __init vt8500_clk_init(void);
+void __init wm8505_clk_init(void);
+/* WM8650 support not added yet, but included for completeness */
+void __init wm8650_clk_init(void);
+
+#endif
@@ -24,6 +24,7 @@
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <mach/restart.h>
+#include <mach/clk.h>
#include "devices.h"
@@ -66,6 +67,9 @@ void __init wm8505_7in_init(void)
wm8505_set_resources();
platform_add_devices(devices, ARRAY_SIZE(devices));
vt8500_gpio_init();
+
+ /* this call can be moved to timer_init once devicetree is ready */
+ wm8505_clk_init();
}
MACHINE_START(WM8505_7IN_NETBOOK, "WM8505 7-inch generic netbook")
@@ -10,6 +10,7 @@ obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/
obj-$(CONFIG_PLAT_SPEAR) += spear/
obj-$(CONFIG_ARCH_U300) += clk-u300.o
obj-$(CONFIG_ARCH_INTEGRATOR) += versatile/
+obj-$(CONFIG_ARCH_VT8500) += vt8500/
# Chip specific
obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
new file mode 100644
@@ -0,0 +1,12 @@
+#
+# Wondermedia Clock specific Makefile
+#
+obj-y += clk-gatediv.o
+
+obj-$(CONFIG_VTWM_VERSION_VT8500) += vt8500_clk.o wm85xx-pll.o
+obj-$(CONFIG_VTWM_VERSION_WM8505) += wm8505_clk.o wm85xx-pll.o
+
+# WM8650 board support missing - has different clock functions so
+# added for completeness. Will be needed for devicetree support.
+obj-$(CONFIG_VTWM_VERSION_WM8650) += wm8650_clk.o wm86xx-pll.o
+
new file mode 100644
@@ -0,0 +1,152 @@
+/* drivers/clk/wmt/clk-pll.c
+ *
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+
+#include "clk.h"
+
+#define to_clk_gatediv(_hw) container_of(_hw, struct clk_gatediv, hw)
+
+static long clk_gatediv_round_rate(struct clk_hw *hw, unsigned long drate,
+ unsigned long *prate)
+{
+ u32 div;
+ unsigned long parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
+
+ if ((drate == 0) || (parent_rate == 0))
+ return 0;
+
+ if (parent_rate % drate)
+ div = (parent_rate / drate) + 1;
+ else
+ div = parent_rate / drate;
+
+ return parent_rate / div;
+}
+
+static unsigned long clk_gatediv_recalc_rate(struct clk_hw *hw, unsigned long
+ parent_rate)
+{
+ struct clk_gatediv *gatediv = to_clk_gatediv(hw);
+
+ u32 div = (readl(gatediv->div_reg) & 0x1F);
+
+ if (div == 0)
+ div = 32;
+
+ return parent_rate / div;
+}
+
+static int clk_gatediv_set_rate(struct clk_hw *hw, unsigned long drate,
+ unsigned long prate)
+{
+ u32 div;
+ struct clk_gatediv *gatediv = to_clk_gatediv(hw);
+ unsigned long parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
+
+ if ((drate == 0) || (drate > prate) || (parent_rate == 0))
+ return -EINVAL;
+
+ if (parent_rate % drate)
+ div = (parent_rate / drate) + 1;
+ else
+ div = parent_rate / drate;
+
+ if (div > 32)
+ return -EINVAL;
+
+ if (div == 32)
+ div = 0;
+
+ writel(div, gatediv->div_reg);
+
+ return 0;
+}
+
+static int clk_gatediv_enable(struct clk_hw *hw)
+{
+ struct clk_gatediv *gatediv = to_clk_gatediv(hw);
+ u32 tmp = readl(gatediv->en_reg);
+
+ tmp |= BIT(gatediv->en_bit);
+ writel(tmp, gatediv->en_reg);
+
+ return 0;
+}
+
+static void clk_gatediv_disable(struct clk_hw *hw)
+{
+ struct clk_gatediv *gatediv = to_clk_gatediv(hw);
+ u32 tmp = readl(gatediv->en_reg);
+
+ tmp &= ~BIT(gatediv->en_bit);
+ writel(tmp, gatediv->en_reg);
+}
+
+static int clk_gatediv_is_enabled(struct clk_hw *hw)
+{
+ struct clk_gatediv *gatediv = to_clk_gatediv(hw);
+ u32 tmp = readl(gatediv->en_reg);
+ tmp &= BIT(gatediv->en_bit);
+
+ return tmp ? 1 : 0;
+}
+
+const struct clk_ops clk_gatediv_ops = {
+ .enable = clk_gatediv_enable,
+ .disable = clk_gatediv_disable,
+ .is_enabled = clk_gatediv_is_enabled,
+ .recalc_rate = clk_gatediv_recalc_rate,
+ .round_rate = clk_gatediv_round_rate,
+ .set_rate = clk_gatediv_set_rate,
+};
+
+struct clk *clk_register_gatediv(const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *div_reg,
+ void __iomem *en_reg, u8 en_bit, spinlock_t *lock)
+{
+ struct clk_init_data init;
+ struct clk_gatediv *gatediv;
+ struct clk *clk;
+
+ /* allocate the gatediv */
+ gatediv = kzalloc(sizeof(struct clk_gatediv), GFP_KERNEL);
+ if (!gatediv) {
+ pr_err("%s: could not allocate gatediv clk\n", __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ init.name = name;
+ init.ops = &clk_gatediv_ops;
+ init.flags = flags;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+
+ gatediv->div_reg = div_reg;
+ gatediv->en_reg = en_reg;
+ gatediv->en_bit = en_bit;
+ gatediv->lock = lock;
+ gatediv->hw.init = &init;
+
+ clk = clk_register(NULL, &gatediv->hw);
+
+ if (IS_ERR(clk))
+ kfree(gatediv);
+
+ return clk;
+}
new file mode 100644
@@ -0,0 +1,36 @@
+/* drivers/clk/wmt/clk.h
+ *
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+struct clk_pll {
+ struct clk_hw hw;
+ void __iomem *reg;
+ spinlock_t *lock;
+};
+
+struct clk_gatediv {
+ struct clk_hw hw;
+ void __iomem *en_reg;
+ u8 en_bit;
+ void __iomem *div_reg;
+ spinlock_t *lock;
+};
+
+struct clk *clk_register_pll_wm85xx(const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *reg, spinlock_t *lock);
+struct clk *clk_register_pll_wm86xx(const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *reg, spinlock_t *lock);
+struct clk *clk_register_gatediv(const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *div_reg,
+ void __iomem *en_reg, u8 en_bit, spinlock_t *lock);
new file mode 100644
@@ -0,0 +1,108 @@
+/* drivers/clk/wmt/wm8505-clk.c
+ *
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+
+#include <mach/vt8500_regs.h>
+
+#include "clk.h"
+
+static DEFINE_SPINLOCK(_lock);
+
+#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
+
+/* PLL A Divisor Regisiters */
+#define CLK_ARM_DIVISOR 0x300
+#define CLK_AHB_DIVISOR 0x304
+#define CLK_APB_DIVISOR 0x350
+
+/* PLL B Divisor Registers */
+#define CLK_SF_DIVISOR 0x314
+#define CLK_SDMMC0_DIVISOR 0x328
+#define CLK_NAND_DIVISOR 0x330
+#define CLK_SPI0_DIVISOR 0x33C
+#define CLK_PWM_DIVISOR 0x348
+
+/* PLL C Divisor Registers */
+#define CLK_DDR_DIVISOR 0x310
+
+void __init vt8500_clk_init(void)
+{
+ struct clk *clk;
+ void __iomem *pmc_base = ioremap(VT8500_PMC_BASE, 0x380);
+
+ clk = clk_register_fixed_rate(NULL, "ref", NULL, CLK_IS_ROOT, 25000000);
+ clk_register_clkdev(clk, "ref", NULL);
+
+ clk = clk_register_pll_wm85xx("pll-a", "ref", 0,
+ pmc_base+CLK_PLLA_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-a", NULL);
+
+ clk = clk_register_pll_wm85xx("pll-b", "ref", 0,
+ pmc_base+CLK_PLLB_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-b", NULL);
+
+ clk = clk_register_pll_wm85xx("pll-c", "ref", 0,
+ pmc_base+CLK_PLLC_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-c", NULL);
+
+ /* PLL A derived */
+ clk = clk_register_divider(NULL, "arm", "pll-a", 0,
+ pmc_base+CLK_ARM_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "arm", NULL);
+
+ clk = clk_register_divider(NULL, "ahb", "pll-a", 0,
+ pmc_base+CLK_AHB_DIVISOR, 0, 3, 0, &_lock);
+ clk_register_clkdev(clk, "ahb", NULL);
+
+ clk = clk_register_divider(NULL, "apb", "pll-a", 0,
+ pmc_base+CLK_APB_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "apb", NULL);
+
+ /* PLL B derived */
+ clk = clk_register_gatediv("sf", "pll-b", 0,
+ pmc_base+CLK_SF_DIVISOR, pmc_base+CLK_EN_HIGH, 23, &_lock);
+ clk_register_clkdev(clk, "sf", NULL);
+
+ clk = clk_register_gatediv("sdmmc", "pll-b", 0,
+ pmc_base+CLK_SDMMC0_DIVISOR, pmc_base+CLK_EN_HIGH, 18, &_lock);
+ clk_register_clkdev(clk, "sdmmc", NULL);
+
+ clk = clk_register_gatediv("nand", "pll-b", 0,
+ pmc_base+CLK_NAND_DIVISOR, pmc_base+CLK_EN_HIGH, 16, &_lock);
+ clk_register_clkdev(clk, "nand", NULL);
+
+ clk = clk_register_gatediv("spi0", "pll-b", 0,
+ pmc_base+CLK_SPI0_DIVISOR, pmc_base+CLK_EN_LOW, 12, &_lock);
+ clk_register_clkdev(clk, "spi0", NULL);
+
+ clk = clk_register_gatediv("pwm", "pll-b", 0,
+ pmc_base+CLK_PWM_DIVISOR, pmc_base+CLK_EN_LOW, 10, &_lock);
+ clk_register_clkdev(clk, "pwm", NULL);
+
+ /* PLL C derived */
+ clk = clk_register_divider(NULL, "ddr", "pll-c", 0,
+ pmc_base+CLK_DDR_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "ddr", NULL);
+}
new file mode 100644
@@ -0,0 +1,122 @@
+/* drivers/clk/wmt/wm8505-clk.c
+ *
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+
+#include <mach/wm8505_regs.h>
+
+#include "clk.h"
+
+static DEFINE_SPINLOCK(_lock);
+
+#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
+
+/* PLL A Divisor Regisiters */
+#define CLK_ARM_DIVISOR 0x300
+#define CLK_AHB_DIVISOR 0x304
+#define CLK_APB_DIVISOR 0x350
+
+/* PLL B Divisor Registers */
+#define CLK_SF_DIVISOR 0x314
+#define CLK_SDMMC0_DIVISOR 0x328
+#define CLK_NAND_DIVISOR 0x330
+#define CLK_SPI0_DIVISOR 0x33C
+#define CLK_PWM_DIVISOR 0x348
+#define CLK_I2C0_DIVISOR 0x36C
+#define CLK_I2C1_DIVISOR 0x370
+
+/* PLL C Divisor Registers */
+#define CLK_DDR_DIVISOR 0x310
+
+void __init wm8505_clk_init(void)
+{
+ struct clk *clk;
+ void __iomem *pmc_base = ioremap(WM8505_PMC_BASE, 0x380);
+
+ clk = clk_register_fixed_rate(NULL, "ref", NULL, CLK_IS_ROOT, 25000000);
+ clk_register_clkdev(clk, "ref", NULL);
+
+ clk = clk_register_pll_wm85xx("pll-a", "ref", 0,
+ pmc_base+CLK_PLLA_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-a", NULL);
+
+ clk = clk_register_pll_wm85xx("pll-b", "ref", 0,
+ pmc_base+CLK_PLLB_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-b", NULL);
+
+ clk = clk_register_pll_wm85xx("pll-c", "ref", 0,
+ pmc_base+CLK_PLLC_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-c", NULL);
+
+ clk = clk_register_pll_wm85xx("pll-d", "ref", 0,
+ pmc_base+CLK_PLLD_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-d", NULL);
+
+ /* PLL A derived */
+ clk = clk_register_divider(NULL, "arm", "pll-a", 0,
+ pmc_base+CLK_ARM_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "arm", NULL);
+
+ clk = clk_register_divider(NULL, "ahb", "pll-a", 0,
+ pmc_base+CLK_AHB_DIVISOR, 0, 3, 0, &_lock);
+ clk_register_clkdev(clk, "ahb", NULL);
+
+ clk = clk_register_divider(NULL, "apb", "pll-a", 0,
+ pmc_base+CLK_APB_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "apb", NULL);
+
+ /* PLL B derived */
+ clk = clk_register_gatediv("sf", "pll-b", 0, pmc_base+CLK_SF_DIVISOR,
+ pmc_base+CLK_EN_HIGH, 23, &_lock);
+ clk_register_clkdev(clk, "sf", NULL);
+
+ clk = clk_register_gatediv("sdmmc", "pll-b", 0,
+ pmc_base+CLK_SDMMC0_DIVISOR, pmc_base+CLK_EN_HIGH, 18, &_lock);
+ clk_register_clkdev(clk, "sdmmc", NULL);
+
+ clk = clk_register_gatediv("nand", "pll-b", 0,
+ pmc_base+CLK_NAND_DIVISOR, pmc_base+CLK_EN_HIGH, 16, &_lock);
+ clk_register_clkdev(clk, "nand", NULL);
+
+ clk = clk_register_gatediv("spi0", "pll-b", 0,
+ pmc_base+CLK_SPI0_DIVISOR, pmc_base+CLK_EN_LOW, 12, &_lock);
+ clk_register_clkdev(clk, "spi0", NULL);
+
+ clk = clk_register_gatediv("pwm", "pll-b", 0,
+ pmc_base+CLK_PWM_DIVISOR, pmc_base+CLK_EN_LOW, 10, &_lock);
+ clk_register_clkdev(clk, "pwm", NULL);
+
+ clk = clk_register_gatediv("i2c0", "pll-b", 0,
+ pmc_base+CLK_I2C0_DIVISOR, pmc_base+CLK_EN_LOW, 5, &_lock);
+ clk_register_clkdev(clk, "i2c0", NULL);
+
+ clk = clk_register_gatediv("i2c1", "pll-b", 0,
+ pmc_base+CLK_I2C1_DIVISOR, pmc_base+CLK_EN_LOW, 9, &_lock);
+ clk_register_clkdev(clk, "i2c1", NULL);
+
+ /* PLL C derived */
+ clk = clk_register_divider(NULL, "ddr", "pll-c", 0,
+ pmc_base+CLK_DDR_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "ddr", NULL);
+}
new file mode 100644
@@ -0,0 +1,103 @@
+/* drivers/clk/wmt/wm85xx-pll.c
+ *
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+
+#include "clk.h"
+
+#define to_clk_pll(_hw) container_of(_hw, struct clk_pll, hw)
+
+static long clk_pll_round_rate(struct clk_hw *hw, unsigned long drate,
+ unsigned long *prate)
+{
+ u32 mul;
+ unsigned long parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
+
+ mul = (drate / parent_rate) & 0xFE;
+
+ if (mul > 62)
+ return -EINVAL;
+
+ return parent_rate * mul;
+}
+
+static unsigned long clk_pll_recalc_rate(struct clk_hw *hw, unsigned long
+ parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ u32 pll_val = readl(pll->reg);
+ u32 divisor = (pll_val & 0x100) ? 2 : 1;
+
+ return (parent_rate * ((pll_val & 0x1F) << 1)) / divisor;
+}
+
+static int clk_pll_set_rate(struct clk_hw *hw, unsigned long drate,
+ unsigned long prate)
+{
+ u32 mul;
+ struct clk_pll *pll = to_clk_pll(hw);
+ unsigned long parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
+
+ mul = (drate / parent_rate) & 0xFE;
+
+ if (mul > 62)
+ return -EINVAL;
+
+ writel((mul >> 1), pll->reg);
+
+ return 0;
+}
+
+static const struct clk_ops clk_pll_ops = {
+ .recalc_rate = clk_pll_recalc_rate,
+ .round_rate = clk_pll_round_rate,
+ .set_rate = clk_pll_set_rate,
+};
+
+struct clk *clk_register_pll_wm85xx(const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *reg, spinlock_t *lock)
+{
+ struct clk_init_data init;
+ struct clk_pll *pll;
+ struct clk *clk;
+
+ /* allocate the pll */
+ pll = kzalloc(sizeof(struct clk_pll), GFP_KERNEL);
+ if (!pll) {
+ pr_err("%s: could not allocate pll clk\n", __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ init.name = name;
+ init.ops = &clk_pll_ops;
+ init.flags = flags;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+
+ pll->reg = reg;
+ pll->lock = lock;
+ pll->hw.init = &init;
+
+ clk = clk_register(NULL, &pll->hw);
+
+ if (IS_ERR(clk))
+ kfree(pll);
+
+ return clk;
+}
new file mode 100644
@@ -0,0 +1,127 @@
+/* drivers/clk/wmt/wm8650-clk.c
+ *
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+
+#include <mach/wm8650_regs.h>
+
+#include "clk.h"
+
+static DEFINE_SPINLOCK(_lock);
+
+#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
+
+/* PLL A Divisor Regisiters */
+#define CLK_ARM_DIVISOR 0x300
+#define CLK_AHB_DIVISOR 0x304
+#define CLK_APB_DIVISOR 0x320
+
+/* PLL B Divisor Registers */
+#define CLK_SF_DIVISOR 0x314
+#define CLK_SDMMC0_DIVISOR 0x328
+#define CLK_NAND_DIVISOR 0x330
+#define CLK_SPI0_DIVISOR 0x33C
+#define CLK_SDMMC1_DIVISOR 0x340
+#define CLK_PWM_DIVISOR 0x348
+#define CLK_I2C0_DIVISOR 0x36C
+#define CLK_I2C1_DIVISOR 0x378
+
+/* PLL C Divisor Registers */
+#define CLK_DDR_DIVISOR 0x310
+
+void __init wm8650_clk_init(void)
+{
+ struct clk *clk;
+ void __iomem *pmc_base = ioremap(WM8650_PMC_BASE, 0x380);
+
+ clk = clk_register_fixed_rate(NULL, "ref", NULL, CLK_IS_ROOT, 25000000);
+ clk_register_clkdev(clk, "ref", NULL);
+
+ clk = clk_register_pll_wm86xx("pll-a", "ref", 0,
+ pmc_base+CLK_PLLA_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-a", NULL);
+
+ clk = clk_register_pll_wm86xx("pll-b", "ref", 0,
+ pmc_base+CLK_PLLB_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-b", NULL);
+
+ clk = clk_register_pll_wm86xx("pll-c", "ref", 0,
+ pmc_base+CLK_PLLC_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-c", NULL);
+
+ clk = clk_register_pll_wm86xx("pll-d", "ref", 0,
+ pmc_base+CLK_PLLD_MULTIPLIER, &_lock);
+ clk_register_clkdev(clk, "pll-d", NULL);
+
+ /* PLL A derived */
+ clk = clk_register_divider(NULL, "arm", "pll-a", 0,
+ pmc_base+CLK_ARM_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "arm", NULL);
+
+ clk = clk_register_divider(NULL, "ahb", "pll-a", 0,
+ pmc_base+CLK_AHB_DIVISOR, 0, 3, 0, &_lock);
+ clk_register_clkdev(clk, "ahb", NULL);
+
+ clk = clk_register_divider(NULL, "apb", "pll-a", 0,
+ pmc_base+CLK_APB_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "apb", NULL);
+
+ /* PLL B derived */
+ clk = clk_register_gatediv("sf", "pll-b", 0,
+ pmc_base+CLK_SF_DIVISOR, pmc_base+CLK_EN_HIGH, 23, &_lock);
+ clk_register_clkdev(clk, "sf", NULL);
+
+ clk = clk_register_gatediv("sdmmc", "pll-b", 0,
+ pmc_base+CLK_SDMMC0_DIVISOR, pmc_base+CLK_EN_HIGH, 18, &_lock);
+ clk_register_clkdev(clk, "sdmmc", NULL);
+
+ clk = clk_register_gatediv("nand", "pll-b", 0,
+ pmc_base+CLK_NAND_DIVISOR, pmc_base+CLK_EN_HIGH, 16, &_lock);
+ clk_register_clkdev(clk, "nand", NULL);
+
+ clk = clk_register_gatediv("spi0", "pll-b", 0,
+ pmc_base+CLK_SPI0_DIVISOR, pmc_base+CLK_EN_LOW, 12, &_lock);
+ clk_register_clkdev(clk, "spi0", NULL);
+
+ clk = clk_register_gatediv("sdmmc1", "pll-b", 0,
+ pmc_base+CLK_SDMMC1_DIVISOR, pmc_base+CLK_EN_LOW, 4, &_lock);
+ clk_register_clkdev(clk, "sdmmc1", NULL);
+
+ clk = clk_register_gatediv("pwm", "pll-b", 0,
+ pmc_base+CLK_PWM_DIVISOR, pmc_base+CLK_EN_LOW, 10, &_lock);
+ clk_register_clkdev(clk, "pwm", NULL);
+
+ clk = clk_register_gatediv("i2c0", "pll-b", 0,
+ pmc_base+CLK_I2C0_DIVISOR, pmc_base+CLK_EN_LOW, 5, &_lock);
+ clk_register_clkdev(clk, "i2c0", NULL);
+
+ clk = clk_register_gatediv("i2c1", "pll-b", 0,
+ pmc_base+CLK_I2C1_DIVISOR, pmc_base+CLK_EN_LOW, 9, &_lock);
+ clk_register_clkdev(clk, "i2c1", NULL);
+
+ /* PLL D derived */
+ clk = clk_register_divider(NULL, "ddr", "pll-d", 0,
+ pmc_base+CLK_DDR_DIVISOR, 0, 5, 0, &_lock);
+ clk_register_clkdev(clk, "ddr", NULL);
+}
new file mode 100644
@@ -0,0 +1,135 @@
+/* drivers/clk/wmt/wm86xx-pll.c
+ *
+ * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+
+#include "clk.h"
+
+#define to_clk_pll(_hw) container_of(_hw, struct clk_pll, hw)
+
+static long clk_pll_round_rate(struct clk_hw *hw, unsigned long drate,
+ unsigned long *prate)
+{
+ u32 mul;
+ u32 div;
+ unsigned long parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
+
+ /* Multiplier only gives a range of (OSC..OSC*1023) */
+ mul = (drate / parent_rate);
+ if (mul < 0x400) {
+ /* No divider required */
+ return parent_rate * mul;
+ }
+
+ /* Simple divider gives a range of (OSC/7..OSC*511.5) */
+ for (div = 2; div < 8; div++) {
+ mul = (drate / div) / parent_rate;
+ if (mul < 0x400)
+ break;
+ }
+ if (mul < 0x400) {
+ /* Simple divider required */
+ return (parent_rate * mul) / div;
+ }
+
+ /*
+ * TODO: Add support for extended divider.
+ * Could be used to give better resolution for matches
+ */
+
+ return -EINVAL;
+}
+
+static unsigned long clk_pll_recalc_rate(struct clk_hw *hw, unsigned long
+ parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ u32 pll_val = readl(pll->reg);
+ u32 divisor = ((pll_val >> 10) & 7) * (1 << ((pll_val >> 13) & 3));
+
+ return (parent_rate * (pll_val & 0x3FF)) / divisor;
+}
+
+static int clk_pll_set_rate(struct clk_hw *hw, unsigned long drate,
+ unsigned long prate)
+{
+ u32 mul;
+ u32 div;
+ struct clk_pll *pll = to_clk_pll(hw);
+ unsigned long parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
+
+ /* Multiplier only gives a range of (OSC..OSC*1023) */
+ mul = (drate / parent_rate);
+ if (mul < 0x400) {
+ /* No divider required */
+ div = 1;
+ goto set_clock;
+ }
+
+ /* Simple divider gives a range of (OSC/7..OSC*511.5) */
+ for (div = 2; div < 8; div++) {
+ mul = (drate / div) / parent_rate;
+ if (mul < 0x400)
+ break;
+ }
+ if (mul > 0x3FF)
+ return -EINVAL;
+
+set_clock:
+ writel((div << 10)|mul, pll->reg);
+ return 0;
+}
+
+static const struct clk_ops clk_pll_ops = {
+ .recalc_rate = clk_pll_recalc_rate,
+ .round_rate = clk_pll_round_rate,
+ .set_rate = clk_pll_set_rate,
+};
+
+struct clk *clk_register_pll_wm86xx(const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *reg, spinlock_t *lock)
+{
+ struct clk_init_data init;
+ struct clk_pll *pll;
+ struct clk *clk;
+
+ /* allocate the pll */
+ pll = kzalloc(sizeof(struct clk_pll), GFP_KERNEL);
+ if (!pll) {
+ pr_err("%s: could not allocate pll clk\n", __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ init.name = name;
+ init.ops = &clk_pll_ops;
+ init.flags = flags;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+
+ pll->reg = reg;
+ pll->lock = lock;
+ pll->hw.init = &init;
+
+ clk = clk_register(NULL, &pll->hw);
+
+ if (IS_ERR(clk))
+ kfree(pll);
+
+ return clk;
+}
This patch adds common clock framework support for arch-vt8500. WM8650 support is included in preparation for devicetree support. Each SoC has a seperate initialization function due to no devicetree support to identify SoCs at the moment. Once devicetree is implemented, VT8500 and WM8505 should be combined. WM8650 uses different functions for clocks. Signed-off-by: Tony Prisk <linux@prisktech.co.nz> --- arch/arm/Kconfig | 2 + arch/arm/mach-vt8500/bv07.c | 4 + arch/arm/mach-vt8500/include/mach/clk.h | 9 ++ arch/arm/mach-vt8500/wm8505_7in.c | 4 + drivers/clk/Makefile | 1 + drivers/clk/vt8500/Makefile | 12 +++ drivers/clk/vt8500/clk-gatediv.c | 152 +++++++++++++++++++++++++++++++ drivers/clk/vt8500/clk.h | 36 +++++++ drivers/clk/vt8500/vt8500_clk.c | 108 ++++++++++++++++++++++ drivers/clk/vt8500/wm8505_clk.c | 122 +++++++++++++++++++++++++ drivers/clk/vt8500/wm85xx-pll.c | 103 +++++++++++++++++++++ drivers/clk/vt8500/wm8650_clk.c | 127 ++++++++++++++++++++++++++ drivers/clk/vt8500/wm86xx-pll.c | 135 +++++++++++++++++++++++++++ 13 files changed, 815 insertions(+), 0 deletions(-) create mode 100644 arch/arm/mach-vt8500/include/mach/clk.h create mode 100644 drivers/clk/vt8500/Makefile create mode 100644 drivers/clk/vt8500/clk-gatediv.c create mode 100644 drivers/clk/vt8500/clk.h create mode 100644 drivers/clk/vt8500/vt8500_clk.c create mode 100644 drivers/clk/vt8500/wm8505_clk.c create mode 100644 drivers/clk/vt8500/wm85xx-pll.c create mode 100644 drivers/clk/vt8500/wm8650_clk.c create mode 100644 drivers/clk/vt8500/wm86xx-pll.c