diff options
| author | Meizu OpenSource <patchwork@meizu.com> | 2016-08-15 10:19:42 +0800 |
|---|---|---|
| committer | Meizu OpenSource <patchwork@meizu.com> | 2016-08-15 10:19:42 +0800 |
| commit | d2e1446d81725c351dc73a03b397ce043fb18452 (patch) | |
| tree | 4dbc616b7f92aea39cd697a9084205ddb805e344 /drivers/clk/versatile | |
first commit
Diffstat (limited to 'drivers/clk/versatile')
| -rw-r--r-- | drivers/clk/versatile/Makefile | 7 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-icst.c | 148 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-icst.h | 18 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-impd1.c | 97 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-integrator.c | 84 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-realview.c | 93 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-sp810.c | 188 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-vexpress-osc.c | 147 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-vexpress.c | 86 |
9 files changed, 868 insertions, 0 deletions
diff --git a/drivers/clk/versatile/Makefile b/drivers/clk/versatile/Makefile new file mode 100644 index 000000000..c16ca7871 --- /dev/null +++ b/drivers/clk/versatile/Makefile @@ -0,0 +1,7 @@ +# Makefile for Versatile-specific clocks +obj-$(CONFIG_ICST) += clk-icst.o +obj-$(CONFIG_ARCH_INTEGRATOR) += clk-integrator.o +obj-$(CONFIG_INTEGRATOR_IMPD1) += clk-impd1.o +obj-$(CONFIG_ARCH_REALVIEW) += clk-realview.o +obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o clk-sp810.o +obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o diff --git a/drivers/clk/versatile/clk-icst.c b/drivers/clk/versatile/clk-icst.c new file mode 100644 index 000000000..f5e4c21b3 --- /dev/null +++ b/drivers/clk/versatile/clk-icst.c @@ -0,0 +1,148 @@ +/* + * Driver for the ICST307 VCO clock found in the ARM Reference designs. + * We wrap the custom interface from <asm/hardware/icst.h> into the generic + * clock framework. + * + * Copyright (C) 2012 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: when all ARM reference designs are migrated to generic clocks, the + * ICST clock code from the ARM tree should probably be merged into this + * file. + */ +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/clk-provider.h> +#include <linux/io.h> + +#include "clk-icst.h" + +/** + * struct clk_icst - ICST VCO clock wrapper + * @hw: corresponding clock hardware entry + * @vcoreg: VCO register address + * @lockreg: VCO lock register address + * @params: parameters for this ICST instance + * @rate: current rate + */ +struct clk_icst { + struct clk_hw hw; + void __iomem *vcoreg; + void __iomem *lockreg; + const struct icst_params *params; + unsigned long rate; +}; + +#define to_icst(_hw) container_of(_hw, struct clk_icst, hw) + +/** + * vco_get() - get ICST VCO settings from a certain register + * @vcoreg: register containing the VCO settings + */ +static struct icst_vco vco_get(void __iomem *vcoreg) +{ + u32 val; + struct icst_vco vco; + + val = readl(vcoreg); + vco.v = val & 0x1ff; + vco.r = (val >> 9) & 0x7f; + vco.s = (val >> 16) & 03; + return vco; +} + +/** + * vco_set() - commit changes to an ICST VCO + * @locreg: register to poke to unlock the VCO for writing + * @vcoreg: register containing the VCO settings + * @vco: ICST VCO parameters to commit + */ +static void vco_set(void __iomem *lockreg, + void __iomem *vcoreg, + struct icst_vco vco) +{ + u32 val; + + val = readl(vcoreg) & ~0x7ffff; + val |= vco.v | (vco.r << 9) | (vco.s << 16); + + /* This magic unlocks the VCO so it can be controlled */ + writel(0xa05f, lockreg); + writel(val, vcoreg); + /* This locks the VCO again */ + writel(0, lockreg); +} + + +static unsigned long icst_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_icst *icst = to_icst(hw); + struct icst_vco vco; + + vco = vco_get(icst->vcoreg); + icst->rate = icst_hz(icst->params, vco); + return icst->rate; +} + +static long icst_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct clk_icst *icst = to_icst(hw); + struct icst_vco vco; + + vco = icst_hz_to_vco(icst->params, rate); + return icst_hz(icst->params, vco); +} + +static int icst_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_icst *icst = to_icst(hw); + struct icst_vco vco; + + vco = icst_hz_to_vco(icst->params, rate); + icst->rate = icst_hz(icst->params, vco); + vco_set(icst->lockreg, icst->vcoreg, vco); + return 0; +} + +static const struct clk_ops icst_ops = { + .recalc_rate = icst_recalc_rate, + .round_rate = icst_round_rate, + .set_rate = icst_set_rate, +}; + +struct clk *icst_clk_register(struct device *dev, + const struct clk_icst_desc *desc, + void __iomem *base) +{ + struct clk *clk; + struct clk_icst *icst; + struct clk_init_data init; + + icst = kzalloc(sizeof(struct clk_icst), GFP_KERNEL); + if (!icst) { + pr_err("could not allocate ICST clock!\n"); + return ERR_PTR(-ENOMEM); + } + init.name = "icst"; + init.ops = &icst_ops; + init.flags = CLK_IS_ROOT; + init.parent_names = NULL; + init.num_parents = 0; + icst->hw.init = &init; + icst->params = desc->params; + icst->vcoreg = base + desc->vco_offset; + icst->lockreg = base + desc->lock_offset; + + clk = clk_register(dev, &icst->hw); + if (IS_ERR(clk)) + kfree(icst); + + return clk; +} diff --git a/drivers/clk/versatile/clk-icst.h b/drivers/clk/versatile/clk-icst.h new file mode 100644 index 000000000..dad51b6ff --- /dev/null +++ b/drivers/clk/versatile/clk-icst.h @@ -0,0 +1,18 @@ +#include <asm/hardware/icst.h> + +/** + * struct clk_icst_desc - descriptor for the ICST VCO + * @params: ICST parameters + * @vco_offset: offset to the ICST VCO from the provided memory base + * @lock_offset: offset to the ICST VCO locking register from the provided + * memory base + */ +struct clk_icst_desc { + const struct icst_params *params; + u32 vco_offset; + u32 lock_offset; +}; + +struct clk *icst_clk_register(struct device *dev, + const struct clk_icst_desc *desc, + void __iomem *base); diff --git a/drivers/clk/versatile/clk-impd1.c b/drivers/clk/versatile/clk-impd1.c new file mode 100644 index 000000000..369139af2 --- /dev/null +++ b/drivers/clk/versatile/clk-impd1.c @@ -0,0 +1,97 @@ +/* + * Clock driver for the ARM Integrator/IM-PD1 board + * Copyright (C) 2012 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/clk-provider.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_data/clk-integrator.h> + +#include <mach/impd1.h> + +#include "clk-icst.h" + +struct impd1_clk { + struct clk *vcoclk; + struct clk *uartclk; + struct clk_lookup *clks[3]; +}; + +static struct impd1_clk impd1_clks[4]; + +/* + * There are two VCO's on the IM-PD1 but only one is used by the + * kernel, that is why we are only implementing the control of + * IMPD1_OSC1 here. + */ + +static const struct icst_params impd1_vco_params = { + .ref = 24000000, /* 24 MHz */ + .vco_max = ICST525_VCO_MAX_3V, + .vco_min = ICST525_VCO_MIN, + .vd_min = 12, + .vd_max = 519, + .rd_min = 3, + .rd_max = 120, + .s2div = icst525_s2div, + .idx2s = icst525_idx2s, +}; + +static const struct clk_icst_desc impd1_icst1_desc = { + .params = &impd1_vco_params, + .vco_offset = IMPD1_OSC1, + .lock_offset = IMPD1_LOCK, +}; + +/** + * integrator_impd1_clk_init() - set up the integrator clock tree + * @base: base address of the logic module (LM) + * @id: the ID of this LM + */ +void integrator_impd1_clk_init(void __iomem *base, unsigned int id) +{ + struct impd1_clk *imc; + struct clk *clk; + int i; + + if (id > 3) { + pr_crit("no more than 4 LMs can be attached\n"); + return; + } + imc = &impd1_clks[id]; + + clk = icst_clk_register(NULL, &impd1_icst1_desc, base); + imc->vcoclk = clk; + imc->clks[0] = clkdev_alloc(clk, NULL, "lm%x:01000", id); + + /* UART reference clock */ + clk = clk_register_fixed_rate(NULL, "uartclk", NULL, CLK_IS_ROOT, + 14745600); + imc->uartclk = clk; + imc->clks[1] = clkdev_alloc(clk, NULL, "lm%x:00100", id); + imc->clks[2] = clkdev_alloc(clk, NULL, "lm%x:00200", id); + + for (i = 0; i < ARRAY_SIZE(imc->clks); i++) + clkdev_add(imc->clks[i]); +} + +void integrator_impd1_clk_exit(unsigned int id) +{ + int i; + struct impd1_clk *imc; + + if (id > 3) + return; + imc = &impd1_clks[id]; + + for (i = 0; i < ARRAY_SIZE(imc->clks); i++) + clkdev_drop(imc->clks[i]); + clk_unregister(imc->uartclk); + clk_unregister(imc->vcoclk); +} diff --git a/drivers/clk/versatile/clk-integrator.c b/drivers/clk/versatile/clk-integrator.c new file mode 100644 index 000000000..08593b4ee --- /dev/null +++ b/drivers/clk/versatile/clk-integrator.c @@ -0,0 +1,84 @@ +/* + * Clock driver for the ARM Integrator/AP and Integrator/CP boards + * Copyright (C) 2012 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/clk-provider.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/platform_data/clk-integrator.h> + +#include <mach/hardware.h> +#include <mach/platform.h> + +#include "clk-icst.h" + +/* + * Implementation of the ARM Integrator/AP and Integrator/CP clock tree. + * Inspired by portions of: + * plat-versatile/clock.c and plat-versatile/include/plat/clock.h + */ + +static const struct icst_params cp_auxvco_params = { + .ref = 24000000, + .vco_max = ICST525_VCO_MAX_5V, + .vco_min = ICST525_VCO_MIN, + .vd_min = 8, + .vd_max = 263, + .rd_min = 3, + .rd_max = 65, + .s2div = icst525_s2div, + .idx2s = icst525_idx2s, +}; + +static const struct clk_icst_desc __initdata cp_icst_desc = { + .params = &cp_auxvco_params, + .vco_offset = 0x1c, + .lock_offset = INTEGRATOR_HDR_LOCK_OFFSET, +}; + +/* + * integrator_clk_init() - set up the integrator clock tree + * @is_cp: pass true if it's the Integrator/CP else AP is assumed + */ +void __init integrator_clk_init(bool is_cp) +{ + struct clk *clk; + + /* APB clock dummy */ + clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, CLK_IS_ROOT, 0); + clk_register_clkdev(clk, "apb_pclk", NULL); + + /* UART reference clock */ + clk = clk_register_fixed_rate(NULL, "uartclk", NULL, CLK_IS_ROOT, + 14745600); + clk_register_clkdev(clk, NULL, "uart0"); + clk_register_clkdev(clk, NULL, "uart1"); + if (is_cp) + clk_register_clkdev(clk, NULL, "mmci"); + + /* 24 MHz clock */ + clk = clk_register_fixed_rate(NULL, "clk24mhz", NULL, CLK_IS_ROOT, + 24000000); + clk_register_clkdev(clk, NULL, "kmi0"); + clk_register_clkdev(clk, NULL, "kmi1"); + if (!is_cp) + clk_register_clkdev(clk, NULL, "ap_timer"); + + if (!is_cp) + return; + + /* 1 MHz clock */ + clk = clk_register_fixed_rate(NULL, "clk1mhz", NULL, CLK_IS_ROOT, + 1000000); + clk_register_clkdev(clk, NULL, "sp804"); + + /* ICST VCO clock used on the Integrator/CP CLCD */ + clk = icst_clk_register(NULL, &cp_icst_desc, + __io_address(INTEGRATOR_HDR_BASE)); + clk_register_clkdev(clk, NULL, "clcd"); +} diff --git a/drivers/clk/versatile/clk-realview.c b/drivers/clk/versatile/clk-realview.c new file mode 100644 index 000000000..cda07e70a --- /dev/null +++ b/drivers/clk/versatile/clk-realview.c @@ -0,0 +1,93 @@ +/* + * Clock driver for the ARM RealView boards + * Copyright (C) 2012 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/clk-provider.h> + +#include <mach/hardware.h> +#include <mach/platform.h> + +#include "clk-icst.h" + +/* + * Implementation of the ARM RealView clock trees. + */ + +static const struct icst_params realview_oscvco_params = { + .ref = 24000000, + .vco_max = ICST307_VCO_MAX, + .vco_min = ICST307_VCO_MIN, + .vd_min = 4 + 8, + .vd_max = 511 + 8, + .rd_min = 1 + 2, + .rd_max = 127 + 2, + .s2div = icst307_s2div, + .idx2s = icst307_idx2s, +}; + +static const struct clk_icst_desc __initdata realview_osc0_desc = { + .params = &realview_oscvco_params, + .vco_offset = REALVIEW_SYS_OSC0_OFFSET, + .lock_offset = REALVIEW_SYS_LOCK_OFFSET, +}; + +static const struct clk_icst_desc __initdata realview_osc4_desc = { + .params = &realview_oscvco_params, + .vco_offset = REALVIEW_SYS_OSC4_OFFSET, + .lock_offset = REALVIEW_SYS_LOCK_OFFSET, +}; + +/* + * realview_clk_init() - set up the RealView clock tree + */ +void __init realview_clk_init(void __iomem *sysbase, bool is_pb1176) +{ + struct clk *clk; + + /* APB clock dummy */ + clk = clk_register_fixed_rate(NULL, "apb_pclk", NULL, CLK_IS_ROOT, 0); + clk_register_clkdev(clk, "apb_pclk", NULL); + + /* 24 MHz clock */ + clk = clk_register_fixed_rate(NULL, "clk24mhz", NULL, CLK_IS_ROOT, + 24000000); + clk_register_clkdev(clk, NULL, "dev:uart0"); + clk_register_clkdev(clk, NULL, "dev:uart1"); + clk_register_clkdev(clk, NULL, "dev:uart2"); + clk_register_clkdev(clk, NULL, "fpga:kmi0"); + clk_register_clkdev(clk, NULL, "fpga:kmi1"); + clk_register_clkdev(clk, NULL, "fpga:mmc0"); + clk_register_clkdev(clk, NULL, "dev:ssp0"); + if (is_pb1176) { + /* + * UART3 is on the dev chip in PB1176 + * UART4 only exists in PB1176 + */ + clk_register_clkdev(clk, NULL, "dev:uart3"); + clk_register_clkdev(clk, NULL, "dev:uart4"); + } else + clk_register_clkdev(clk, NULL, "fpga:uart3"); + + + /* 1 MHz clock */ + clk = clk_register_fixed_rate(NULL, "clk1mhz", NULL, CLK_IS_ROOT, + 1000000); + clk_register_clkdev(clk, NULL, "sp804"); + + /* ICST VCO clock */ + if (is_pb1176) + clk = icst_clk_register(NULL, &realview_osc0_desc, sysbase); + else + clk = icst_clk_register(NULL, &realview_osc4_desc, sysbase); + + clk_register_clkdev(clk, NULL, "dev:clcd"); + clk_register_clkdev(clk, NULL, "issp:clcd"); +} diff --git a/drivers/clk/versatile/clk-sp810.c b/drivers/clk/versatile/clk-sp810.c new file mode 100644 index 000000000..bf9b15a58 --- /dev/null +++ b/drivers/clk/versatile/clk-sp810.c @@ -0,0 +1,188 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * Copyright (C) 2013 ARM Limited + */ + +#include <linux/amba/sp810.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#define to_clk_sp810_timerclken(_hw) \ + container_of(_hw, struct clk_sp810_timerclken, hw) + +struct clk_sp810; + +struct clk_sp810_timerclken { + struct clk_hw hw; + struct clk *clk; + struct clk_sp810 *sp810; + int channel; +}; + +struct clk_sp810 { + struct device_node *node; + int refclk_index, timclk_index; + void __iomem *base; + spinlock_t lock; + struct clk_sp810_timerclken timerclken[4]; + struct clk *refclk; + struct clk *timclk; +}; + +static u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw) +{ + struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); + u32 val = readl(timerclken->sp810->base + SCCTRL); + + return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel))); +} + +static int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); + struct clk_sp810 *sp810 = timerclken->sp810; + u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel); + unsigned long flags = 0; + + if (WARN_ON(index > 1)) + return -EINVAL; + + spin_lock_irqsave(&sp810->lock, flags); + + val = readl(sp810->base + SCCTRL); + val &= ~(1 << shift); + val |= index << shift; + writel(val, sp810->base + SCCTRL); + + spin_unlock_irqrestore(&sp810->lock, flags); + + return 0; +} + +/* + * FIXME - setting the parent every time .prepare is invoked is inefficient. + * This is better handled by a dedicated clock tree configuration mechanism at + * init-time. Revisit this later when such a mechanism exists + */ +static int clk_sp810_timerclken_prepare(struct clk_hw *hw) +{ + struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); + struct clk_sp810 *sp810 = timerclken->sp810; + struct clk *old_parent = __clk_get_parent(hw->clk); + struct clk *new_parent; + + if (!sp810->refclk) + sp810->refclk = of_clk_get(sp810->node, sp810->refclk_index); + + if (!sp810->timclk) + sp810->timclk = of_clk_get(sp810->node, sp810->timclk_index); + + if (WARN_ON(IS_ERR(sp810->refclk) || IS_ERR(sp810->timclk))) + return -ENOENT; + + /* Select fastest parent */ + if (clk_get_rate(sp810->refclk) > clk_get_rate(sp810->timclk)) + new_parent = sp810->refclk; + else + new_parent = sp810->timclk; + + /* Switch the parent if necessary */ + if (old_parent != new_parent) { + clk_prepare(new_parent); + clk_set_parent(hw->clk, new_parent); + clk_unprepare(old_parent); + } + + return 0; +} + +static void clk_sp810_timerclken_unprepare(struct clk_hw *hw) +{ + struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); + struct clk_sp810 *sp810 = timerclken->sp810; + + clk_put(sp810->timclk); + clk_put(sp810->refclk); +} + +static const struct clk_ops clk_sp810_timerclken_ops = { + .prepare = clk_sp810_timerclken_prepare, + .unprepare = clk_sp810_timerclken_unprepare, + .get_parent = clk_sp810_timerclken_get_parent, + .set_parent = clk_sp810_timerclken_set_parent, +}; + +struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec, + void *data) +{ + struct clk_sp810 *sp810 = data; + + if (WARN_ON(clkspec->args_count != 1 || clkspec->args[0] > + ARRAY_SIZE(sp810->timerclken))) + return NULL; + + return sp810->timerclken[clkspec->args[0]].clk; +} + +void __init clk_sp810_of_setup(struct device_node *node) +{ + struct clk_sp810 *sp810 = kzalloc(sizeof(*sp810), GFP_KERNEL); + const char *parent_names[2]; + char name[12]; + struct clk_init_data init; + int i; + + if (!sp810) { + pr_err("Failed to allocate memory for SP810!\n"); + return; + } + + sp810->refclk_index = of_property_match_string(node, "clock-names", + "refclk"); + parent_names[0] = of_clk_get_parent_name(node, sp810->refclk_index); + + sp810->timclk_index = of_property_match_string(node, "clock-names", + "timclk"); + parent_names[1] = of_clk_get_parent_name(node, sp810->timclk_index); + + if (parent_names[0] <= 0 || parent_names[1] <= 0) { + pr_warn("Failed to obtain parent clocks for SP810!\n"); + return; + } + + sp810->node = node; + sp810->base = of_iomap(node, 0); + spin_lock_init(&sp810->lock); + + init.name = name; + init.ops = &clk_sp810_timerclken_ops; + init.flags = CLK_IS_BASIC; + init.parent_names = parent_names; + init.num_parents = ARRAY_SIZE(parent_names); + + for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) { + snprintf(name, ARRAY_SIZE(name), "timerclken%d", i); + + sp810->timerclken[i].sp810 = sp810; + sp810->timerclken[i].channel = i; + sp810->timerclken[i].hw.init = &init; + + sp810->timerclken[i].clk = clk_register(NULL, + &sp810->timerclken[i].hw); + WARN_ON(IS_ERR(sp810->timerclken[i].clk)); + } + + of_clk_add_provider(node, clk_sp810_timerclken_of_get, sp810); +} +CLK_OF_DECLARE(sp810, "arm,sp810", clk_sp810_of_setup); diff --git a/drivers/clk/versatile/clk-vexpress-osc.c b/drivers/clk/versatile/clk-vexpress-osc.c new file mode 100644 index 000000000..8b8798bb9 --- /dev/null +++ b/drivers/clk/versatile/clk-vexpress-osc.c @@ -0,0 +1,147 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#define pr_fmt(fmt) "vexpress-osc: " fmt + +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/vexpress.h> + +struct vexpress_osc { + struct vexpress_config_func *func; + struct clk_hw hw; + unsigned long rate_min; + unsigned long rate_max; +}; + +#define to_vexpress_osc(osc) container_of(osc, struct vexpress_osc, hw) + +static unsigned long vexpress_osc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct vexpress_osc *osc = to_vexpress_osc(hw); + u32 rate; + + vexpress_config_read(osc->func, 0, &rate); + + return rate; +} + +static long vexpress_osc_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct vexpress_osc *osc = to_vexpress_osc(hw); + + if (WARN_ON(osc->rate_min && rate < osc->rate_min)) + rate = osc->rate_min; + + if (WARN_ON(osc->rate_max && rate > osc->rate_max)) + rate = osc->rate_max; + + return rate; +} + +static int vexpress_osc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct vexpress_osc *osc = to_vexpress_osc(hw); + + return vexpress_config_write(osc->func, 0, rate); +} + +static struct clk_ops vexpress_osc_ops = { + .recalc_rate = vexpress_osc_recalc_rate, + .round_rate = vexpress_osc_round_rate, + .set_rate = vexpress_osc_set_rate, +}; + + +struct clk * __init vexpress_osc_setup(struct device *dev) +{ + struct clk_init_data init; + struct vexpress_osc *osc = kzalloc(sizeof(*osc), GFP_KERNEL); + + if (!osc) + return NULL; + + osc->func = vexpress_config_func_get_by_dev(dev); + if (!osc->func) { + kfree(osc); + return NULL; + } + + init.name = dev_name(dev); + init.ops = &vexpress_osc_ops; + init.flags = CLK_IS_ROOT; + init.num_parents = 0; + osc->hw.init = &init; + + return clk_register(NULL, &osc->hw); +} + +void __init vexpress_osc_of_setup(struct device_node *node) +{ + struct clk_init_data init; + struct vexpress_osc *osc; + struct clk *clk; + u32 range[2]; + + osc = kzalloc(sizeof(*osc), GFP_KERNEL); + if (!osc) + return; + + osc->func = vexpress_config_func_get_by_node(node); + if (!osc->func) { + pr_err("Failed to obtain config func for node '%s'!\n", + node->name); + goto error; + } + + if (of_property_read_u32_array(node, "freq-range", range, + ARRAY_SIZE(range)) == 0) { + osc->rate_min = range[0]; + osc->rate_max = range[1]; + } + + of_property_read_string(node, "clock-output-names", &init.name); + if (!init.name) + init.name = node->name; + + init.ops = &vexpress_osc_ops; + init.flags = CLK_IS_ROOT; + init.num_parents = 0; + + osc->hw.init = &init; + + clk = clk_register(NULL, &osc->hw); + if (IS_ERR(clk)) { + pr_err("Failed to register clock '%s'!\n", init.name); + goto error; + } + + of_clk_add_provider(node, of_clk_src_simple_get, clk); + + pr_debug("Registered clock '%s'\n", init.name); + + return; + +error: + if (osc->func) + vexpress_config_func_put(osc->func); + kfree(osc); +} +CLK_OF_DECLARE(vexpress_soc, "arm,vexpress-osc", vexpress_osc_of_setup); diff --git a/drivers/clk/versatile/clk-vexpress.c b/drivers/clk/versatile/clk-vexpress.c new file mode 100644 index 000000000..a4a728d05 --- /dev/null +++ b/drivers/clk/versatile/clk-vexpress.c @@ -0,0 +1,86 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#include <linux/amba/sp810.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/vexpress.h> + +static struct clk *vexpress_sp810_timerclken[4]; +static DEFINE_SPINLOCK(vexpress_sp810_lock); + +static void __init vexpress_sp810_init(void __iomem *base) +{ + int i; + + if (WARN_ON(!base)) + return; + + for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) { + char name[12]; + const char *parents[] = { + "v2m:refclk32khz", /* REFCLK */ + "v2m:refclk1mhz" /* TIMCLK */ + }; + + snprintf(name, ARRAY_SIZE(name), "timerclken%d", i); + + vexpress_sp810_timerclken[i] = clk_register_mux(NULL, name, + parents, 2, 0, base + SCCTRL, + SCCTRL_TIMERENnSEL_SHIFT(i), 1, + 0, &vexpress_sp810_lock); + + if (WARN_ON(IS_ERR(vexpress_sp810_timerclken[i]))) + break; + } +} + + +static const char * const vexpress_clk_24mhz_periphs[] __initconst = { + "mb:uart0", "mb:uart1", "mb:uart2", "mb:uart3", + "mb:mmci", "mb:kmi0", "mb:kmi1" +}; + +void __init vexpress_clk_init(void __iomem *sp810_base) +{ + struct clk *clk; + int i; + + clk = clk_register_fixed_rate(NULL, "dummy_apb_pclk", NULL, + CLK_IS_ROOT, 0); + WARN_ON(clk_register_clkdev(clk, "apb_pclk", NULL)); + + clk = clk_register_fixed_rate(NULL, "v2m:clk_24mhz", NULL, + CLK_IS_ROOT, 24000000); + for (i = 0; i < ARRAY_SIZE(vexpress_clk_24mhz_periphs); i++) + WARN_ON(clk_register_clkdev(clk, NULL, + vexpress_clk_24mhz_periphs[i])); + + clk = clk_register_fixed_rate(NULL, "v2m:refclk32khz", NULL, + CLK_IS_ROOT, 32768); + WARN_ON(clk_register_clkdev(clk, NULL, "v2m:wdt")); + + clk = clk_register_fixed_rate(NULL, "v2m:refclk1mhz", NULL, + CLK_IS_ROOT, 1000000); + + vexpress_sp810_init(sp810_base); + + for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) + WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i], clk)); + + WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0], + "v2m-timer0", "sp804")); + WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1], + "v2m-timer1", "sp804")); +} |
