diff options
Diffstat (limited to 'drivers/clk/versatile')
| -rw-r--r-- | drivers/clk/versatile/Kconfig | 26 | ||||
| -rw-r--r-- | drivers/clk/versatile/Makefile | 8 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-icst.c | 164 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-icst.h | 20 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-impd1.c | 181 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-integrator.c | 69 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-realview.c | 95 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-sp810.c | 188 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-vexpress-osc.c | 137 | ||||
| -rw-r--r-- | drivers/clk/versatile/clk-vexpress.c | 86 | 
10 files changed, 974 insertions, 0 deletions
diff --git a/drivers/clk/versatile/Kconfig b/drivers/clk/versatile/Kconfig new file mode 100644 index 00000000000..1530c9352a7 --- /dev/null +++ b/drivers/clk/versatile/Kconfig @@ -0,0 +1,26 @@ +config COMMON_CLK_VERSATILE +	bool "Clock driver for ARM Reference designs" +	depends on ARCH_INTEGRATOR || ARCH_REALVIEW || ARCH_VEXPRESS || ARM64 +	---help--- +          Supports clocking on ARM Reference designs: +	  - Integrator/AP and Integrator/CP +	  - RealView PB1176, EB, PB11MP and PBX +	  - Versatile Express + +config CLK_SP810 +	bool "Clock driver for ARM SP810 System Controller" +	depends on COMMON_CLK_VERSATILE +	default y if ARCH_VEXPRESS +	---help--- +	  Supports clock muxing (REFCLK/TIMCLK to TIMERCLKEN0-3) capabilities +	  of the ARM SP810 System Controller cell. + +config CLK_VEXPRESS_OSC +	bool "Clock driver for Versatile Express OSC clock generators" +	depends on COMMON_CLK_VERSATILE +	depends on VEXPRESS_CONFIG +	default y if ARCH_VEXPRESS +	---help--- +	  Simple regmap-based driver driving clock generators on Versatile +	  Express platforms hidden behind its configuration infrastructure, +	  commonly known as OSCs. diff --git a/drivers/clk/versatile/Makefile b/drivers/clk/versatile/Makefile new file mode 100644 index 00000000000..fd449f9b006 --- /dev/null +++ b/drivers/clk/versatile/Makefile @@ -0,0 +1,8 @@ +# 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 +obj-$(CONFIG_CLK_SP810)		+= clk-sp810.o +obj-$(CONFIG_CLK_VEXPRESS_OSC)	+= 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 00000000000..bc96f103bd7 --- /dev/null +++ b/drivers/clk/versatile/clk-icst.c @@ -0,0 +1,164 @@ +/* + * 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; +	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; + +	if (parent_rate) +		icst->params->ref = parent_rate; +	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; + +	if (parent_rate) +		icst->params->ref = parent_rate; +	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, +			const char *name, +			const char *parent_name, +			void __iomem *base) +{ +	struct clk *clk; +	struct clk_icst *icst; +	struct clk_init_data init; +	struct icst_params *pclone; + +	icst = kzalloc(sizeof(struct clk_icst), GFP_KERNEL); +	if (!icst) { +		pr_err("could not allocate ICST clock!\n"); +		return ERR_PTR(-ENOMEM); +	} + +	pclone = kmemdup(desc->params, sizeof(*pclone), GFP_KERNEL); +	if (!pclone) { +		kfree(icst); +		pr_err("could not clone ICST params\n"); +		return ERR_PTR(-ENOMEM); +	} + +	init.name = name; +	init.ops = &icst_ops; +	init.flags = CLK_IS_ROOT; +	init.parent_names = (parent_name ? &parent_name : NULL); +	init.num_parents = (parent_name ? 1 : 0); +	icst->hw.init = &init; +	icst->params = pclone; +	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; +} +EXPORT_SYMBOL_GPL(icst_clk_register); diff --git a/drivers/clk/versatile/clk-icst.h b/drivers/clk/versatile/clk-icst.h new file mode 100644 index 00000000000..04e6f0aef58 --- /dev/null +++ b/drivers/clk/versatile/clk-icst.h @@ -0,0 +1,20 @@ +#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, +			      const char *name, +			      const char *parent_name, +			      void __iomem *base); diff --git a/drivers/clk/versatile/clk-impd1.c b/drivers/clk/versatile/clk-impd1.c new file mode 100644 index 00000000000..1cc1330dc57 --- /dev/null +++ b/drivers/clk/versatile/clk-impd1.c @@ -0,0 +1,181 @@ +/* + * Clock driver for the ARM Integrator/IM-PD1 board + * Copyright (C) 2012-2013 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 "clk-icst.h" + +#define IMPD1_OSC1	0x00 +#define IMPD1_OSC2	0x04 +#define IMPD1_LOCK	0x08 + +struct impd1_clk { +	char *pclkname; +	struct clk *pclk; +	char *vco1name; +	struct clk *vco1clk; +	char *vco2name; +	struct clk *vco2clk; +	struct clk *mmciclk; +	char *uartname; +	struct clk *uartclk; +	char *spiname; +	struct clk *spiclk; +	char *scname; +	struct clk *scclk; +	struct clk_lookup *clks[15]; +}; + +/* One entry for each connected IM-PD1 LM */ +static struct impd1_clk impd1_clks[4]; + +/* + * There are two VCO's on the IM-PD1 + */ + +static const struct icst_params impd1_vco1_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_vco1_params, +	.vco_offset = IMPD1_OSC1, +	.lock_offset = IMPD1_LOCK, +}; + +static const struct icst_params impd1_vco2_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_icst2_desc = { +	.params = &impd1_vco2_params, +	.vco_offset = IMPD1_OSC2, +	.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; +	struct clk *pclk; +	int i; + +	if (id > 3) { +		pr_crit("no more than 4 LMs can be attached\n"); +		return; +	} +	imc = &impd1_clks[id]; + +	/* Register the fixed rate PCLK */ +	imc->pclkname = kasprintf(GFP_KERNEL, "lm%x-pclk", id); +	pclk = clk_register_fixed_rate(NULL, imc->pclkname, NULL, +				      CLK_IS_ROOT, 0); +	imc->pclk = pclk; + +	imc->vco1name = kasprintf(GFP_KERNEL, "lm%x-vco1", id); +	clk = icst_clk_register(NULL, &impd1_icst1_desc, imc->vco1name, NULL, +				base); +	imc->vco1clk = clk; +	imc->clks[0] = clkdev_alloc(pclk, "apb_pclk", "lm%x:01000", id); +	imc->clks[1] = clkdev_alloc(clk, NULL, "lm%x:01000", id); + +	/* VCO2 is also called "CLK2" */ +	imc->vco2name = kasprintf(GFP_KERNEL, "lm%x-vco2", id); +	clk = icst_clk_register(NULL, &impd1_icst2_desc, imc->vco2name, NULL, +				base); +	imc->vco2clk = clk; + +	/* MMCI uses CLK2 right off */ +	imc->clks[2] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00700", id); +	imc->clks[3] = clkdev_alloc(clk, NULL, "lm%x:00700", id); + +	/* UART reference clock divides CLK2 by a fixed factor 4 */ +	imc->uartname = kasprintf(GFP_KERNEL, "lm%x-uartclk", id); +	clk = clk_register_fixed_factor(NULL, imc->uartname, imc->vco2name, +				   CLK_IGNORE_UNUSED, 1, 4); +	imc->uartclk = clk; +	imc->clks[4] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00100", id); +	imc->clks[5] = clkdev_alloc(clk, NULL, "lm%x:00100", id); +	imc->clks[6] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00200", id); +	imc->clks[7] = clkdev_alloc(clk, NULL, "lm%x:00200", id); + +	/* SPI PL022 clock divides CLK2 by a fixed factor 64 */ +	imc->spiname = kasprintf(GFP_KERNEL, "lm%x-spiclk", id); +	clk = clk_register_fixed_factor(NULL, imc->spiname, imc->vco2name, +				   CLK_IGNORE_UNUSED, 1, 64); +	imc->clks[8] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00300", id); +	imc->clks[9] = clkdev_alloc(clk, NULL, "lm%x:00300", id); + +	/* The GPIO blocks and AACI have only PCLK */ +	imc->clks[10] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00400", id); +	imc->clks[11] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00500", id); +	imc->clks[12] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00800", id); + +	/* Smart Card clock divides CLK2 by a fixed factor 4 */ +	imc->scname = kasprintf(GFP_KERNEL, "lm%x-scclk", id); +	clk = clk_register_fixed_factor(NULL, imc->scname, imc->vco2name, +				   CLK_IGNORE_UNUSED, 1, 4); +	imc->scclk = clk; +	imc->clks[13] = clkdev_alloc(pclk, "apb_pclk", "lm%x:00600", id); +	imc->clks[14] = clkdev_alloc(clk, NULL, "lm%x:00600", id); + +	for (i = 0; i < ARRAY_SIZE(imc->clks); i++) +		clkdev_add(imc->clks[i]); +} +EXPORT_SYMBOL_GPL(integrator_impd1_clk_init); + +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->spiclk); +	clk_unregister(imc->uartclk); +	clk_unregister(imc->vco2clk); +	clk_unregister(imc->vco1clk); +	clk_unregister(imc->pclk); +	kfree(imc->scname); +	kfree(imc->spiname); +	kfree(imc->uartname); +	kfree(imc->vco2name); +	kfree(imc->vco1name); +	kfree(imc->pclkname); +} +EXPORT_SYMBOL_GPL(integrator_impd1_clk_exit); diff --git a/drivers/clk/versatile/clk-integrator.c b/drivers/clk/versatile/clk-integrator.c new file mode 100644 index 00000000000..734c4b8fe6a --- /dev/null +++ b/drivers/clk/versatile/clk-integrator.c @@ -0,0 +1,69 @@ +/* + * 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/of.h> +#include <linux/of_address.h> + +#include "clk-icst.h" + +#define INTEGRATOR_HDR_LOCK_OFFSET	0x14 + +/* Base offset for the core module */ +static void __iomem *cm_base; + +static const struct icst_params cp_auxosc_params = { +	.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 cm_auxosc_desc = { +	.params = &cp_auxosc_params, +	.vco_offset = 0x1c, +	.lock_offset = INTEGRATOR_HDR_LOCK_OFFSET, +}; + +static void __init of_integrator_cm_osc_setup(struct device_node *np) +{ +	struct clk *clk = ERR_PTR(-EINVAL); +	const char *clk_name = np->name; +	const struct clk_icst_desc *desc = &cm_auxosc_desc; +	const char *parent_name; + +	if (!cm_base) { +		/* Remap the core module base if not done yet */ +		struct device_node *parent; + +		parent = of_get_parent(np); +		if (!np) { +			pr_err("no parent on core module clock\n"); +			return; +		} +		cm_base = of_iomap(parent, 0); +		if (!cm_base) { +			pr_err("could not remap core module base\n"); +			return; +		} +	} + +	parent_name = of_clk_get_parent_name(np, 0); +	clk = icst_clk_register(NULL, desc, clk_name, parent_name, cm_base); +	if (!IS_ERR(clk)) +		of_clk_add_provider(np, of_clk_src_simple_get, clk); +} +CLK_OF_DECLARE(integrator_cm_auxosc_clk, +	"arm,integrator-cm-auxosc", of_integrator_cm_osc_setup); diff --git a/drivers/clk/versatile/clk-realview.c b/drivers/clk/versatile/clk-realview.c new file mode 100644 index 00000000000..c8b523117fb --- /dev/null +++ b/drivers/clk/versatile/clk-realview.c @@ -0,0 +1,95 @@ +/* + * 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, +					"osc0", NULL, sysbase); +	else +		clk = icst_clk_register(NULL, &realview_osc4_desc, +					"osc4", NULL, 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 00000000000..c6e86a9a2aa --- /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, +}; + +static 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 00000000000..529a59c0fbf --- /dev/null +++ b/drivers/clk/versatile/clk-vexpress-osc.c @@ -0,0 +1,137 @@ +/* + * 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/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 regmap *reg; +	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; + +	regmap_read(osc->reg, 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 regmap_write(osc->reg, 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, +}; + + +static int vexpress_osc_probe(struct platform_device *pdev) +{ +	struct clk_lookup *cl = pdev->dev.platform_data; /* Non-DT lookup */ +	struct clk_init_data init; +	struct vexpress_osc *osc; +	struct clk *clk; +	u32 range[2]; + +	osc = devm_kzalloc(&pdev->dev, sizeof(*osc), GFP_KERNEL); +	if (!osc) +		return -ENOMEM; + +	osc->reg = devm_regmap_init_vexpress_config(&pdev->dev); +	if (IS_ERR(osc->reg)) +		return PTR_ERR(osc->reg); + +	if (of_property_read_u32_array(pdev->dev.of_node, "freq-range", range, +			ARRAY_SIZE(range)) == 0) { +		osc->rate_min = range[0]; +		osc->rate_max = range[1]; +	} + +	if (of_property_read_string(pdev->dev.of_node, "clock-output-names", +			&init.name) != 0) +		init.name = dev_name(&pdev->dev); + +	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)) +		return PTR_ERR(clk); + +	of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, clk); + +	/* Only happens for non-DT cases */ +	if (cl) { +		cl->clk = clk; +		clkdev_add(cl); +	} + +	dev_dbg(&pdev->dev, "Registered clock '%s'\n", init.name); + +	return 0; +} + +static struct of_device_id vexpress_osc_of_match[] = { +	{ .compatible = "arm,vexpress-osc", }, +	{} +}; + +static struct platform_driver vexpress_osc_driver = { +	.driver	= { +		.name = "vexpress-osc", +		.of_match_table = vexpress_osc_of_match, +	}, +	.probe = vexpress_osc_probe, +}; + +static int __init vexpress_osc_init(void) +{ +	return platform_driver_register(&vexpress_osc_driver); +} +core_initcall(vexpress_osc_init); diff --git a/drivers/clk/versatile/clk-vexpress.c b/drivers/clk/versatile/clk-vexpress.c new file mode 100644 index 00000000000..2d5e1b4820e --- /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, CLK_SET_RATE_NO_REPARENT, +				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")); +}  | 
