diff options
| author | Dinh Nguyen <dinguyen@opensource.altera.com> | 2015-05-19 22:22:42 -0500 | 
|---|---|---|
| committer | Stephen Boyd <sboyd@codeaurora.org> | 2015-05-21 15:16:04 -0700 | 
| commit | 5343325ff3dd299f459fa9dacbd95dca5c9bf215 (patch) | |
| tree | d24df3d0835c546b13ec9bca3ba2b0551aeac33a /drivers/clk/socfpga | |
| parent | 5611a5ba8e5435740df99235b262b553f687b13b (diff) | |
| download | linux-5343325ff3dd299f459fa9dacbd95dca5c9bf215.tar.bz2 | |
clk: socfpga: add a clock driver for the Arria 10 platform
The clocks on the Arria 10 platform is a bit different than the
Cyclone/Arria 5 platform that it should just have it's own
driver.
Signed-off-by: Dinh Nguyen <dinguyen@opensource.altera.com>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
Diffstat (limited to 'drivers/clk/socfpga')
| -rw-r--r-- | drivers/clk/socfpga/Makefile | 1 | ||||
| -rw-r--r-- | drivers/clk/socfpga/clk-gate-a10.c | 190 | ||||
| -rw-r--r-- | drivers/clk/socfpga/clk-periph-a10.c | 138 | ||||
| -rw-r--r-- | drivers/clk/socfpga/clk-pll-a10.c | 129 | ||||
| -rw-r--r-- | drivers/clk/socfpga/clk.c | 7 | ||||
| -rw-r--r-- | drivers/clk/socfpga/clk.h | 5 | 
6 files changed, 469 insertions, 1 deletions
diff --git a/drivers/clk/socfpga/Makefile b/drivers/clk/socfpga/Makefile index 7e2d15a0c7b8..d8bb239753a4 100644 --- a/drivers/clk/socfpga/Makefile +++ b/drivers/clk/socfpga/Makefile @@ -2,3 +2,4 @@ obj-y += clk.o  obj-y += clk-gate.o  obj-y += clk-pll.o  obj-y += clk-periph.o +obj-y += clk-pll-a10.o clk-periph-a10.o clk-gate-a10.o diff --git a/drivers/clk/socfpga/clk-gate-a10.c b/drivers/clk/socfpga/clk-gate-a10.c new file mode 100644 index 000000000000..be3e998b85d0 --- /dev/null +++ b/drivers/clk/socfpga/clk-gate-a10.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2015 Altera Corporation. All rights reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/regmap.h> + +#include "clk.h" + +#define streq(a, b) (strcmp((a), (b)) == 0) + +#define to_socfpga_gate_clk(p) container_of(p, struct socfpga_gate_clk, hw.hw) + +/* SDMMC Group for System Manager defines */ +#define SYSMGR_SDMMCGRP_CTRL_OFFSET	0x28 + +static unsigned long socfpga_gate_clk_recalc_rate(struct clk_hw *hwclk, +	unsigned long parent_rate) +{ +	struct socfpga_gate_clk *socfpgaclk = to_socfpga_gate_clk(hwclk); +	u32 div = 1, val; + +	if (socfpgaclk->fixed_div) +		div = socfpgaclk->fixed_div; +	else if (socfpgaclk->div_reg) { +		val = readl(socfpgaclk->div_reg) >> socfpgaclk->shift; +		val &= div_mask(socfpgaclk->width); +			div = (1 << val); +	} + +	return parent_rate / div; +} + +static int socfpga_clk_prepare(struct clk_hw *hwclk) +{ +	struct socfpga_gate_clk *socfpgaclk = to_socfpga_gate_clk(hwclk); +	int i; +	u32 hs_timing; +	u32 clk_phase[2]; + +	if (socfpgaclk->clk_phase[0] || socfpgaclk->clk_phase[1]) { +		for (i = 0; i < ARRAY_SIZE(clk_phase); i++) { +			switch (socfpgaclk->clk_phase[i]) { +			case 0: +				clk_phase[i] = 0; +				break; +			case 45: +				clk_phase[i] = 1; +				break; +			case 90: +				clk_phase[i] = 2; +				break; +			case 135: +				clk_phase[i] = 3; +				break; +			case 180: +				clk_phase[i] = 4; +				break; +			case 225: +				clk_phase[i] = 5; +				break; +			case 270: +				clk_phase[i] = 6; +				break; +			case 315: +				clk_phase[i] = 7; +				break; +			default: +				clk_phase[i] = 0; +				break; +			} +		} + +		hs_timing = SYSMGR_SDMMC_CTRL_SET(clk_phase[0], clk_phase[1]); +		if (!IS_ERR(socfpgaclk->sys_mgr_base_addr)) +			regmap_write(socfpgaclk->sys_mgr_base_addr, +				     SYSMGR_SDMMCGRP_CTRL_OFFSET, hs_timing); +		else +			pr_err("%s: cannot set clk_phase because sys_mgr_base_addr is not available!\n", +					__func__); +	} +	return 0; +} + +static struct clk_ops gateclk_ops = { +	.prepare = socfpga_clk_prepare, +	.recalc_rate = socfpga_gate_clk_recalc_rate, +}; + +static void __init __socfpga_gate_init(struct device_node *node, +	const struct clk_ops *ops) +{ +	u32 clk_gate[2]; +	u32 div_reg[3]; +	u32 clk_phase[2]; +	u32 fixed_div; +	struct clk *clk; +	struct socfpga_gate_clk *socfpga_clk; +	const char *clk_name = node->name; +	const char *parent_name[SOCFPGA_MAX_PARENTS]; +	struct clk_init_data init; +	int rc; +	int i = 0; + +	socfpga_clk = kzalloc(sizeof(*socfpga_clk), GFP_KERNEL); +	if (WARN_ON(!socfpga_clk)) +		return; + +	rc = of_property_read_u32_array(node, "clk-gate", clk_gate, 2); +	if (rc) +		clk_gate[0] = 0; + +	if (clk_gate[0]) { +		socfpga_clk->hw.reg = clk_mgr_a10_base_addr + clk_gate[0]; +		socfpga_clk->hw.bit_idx = clk_gate[1]; + +		gateclk_ops.enable = clk_gate_ops.enable; +		gateclk_ops.disable = clk_gate_ops.disable; +	} + +	rc = of_property_read_u32(node, "fixed-divider", &fixed_div); +	if (rc) +		socfpga_clk->fixed_div = 0; +	else +		socfpga_clk->fixed_div = fixed_div; + +	rc = of_property_read_u32_array(node, "div-reg", div_reg, 3); +	if (!rc) { +		socfpga_clk->div_reg = clk_mgr_a10_base_addr + div_reg[0]; +		socfpga_clk->shift = div_reg[1]; +		socfpga_clk->width = div_reg[2]; +	} else { +		socfpga_clk->div_reg = NULL; +	} + +	rc = of_property_read_u32_array(node, "clk-phase", clk_phase, 2); +	if (!rc) { +		socfpga_clk->clk_phase[0] = clk_phase[0]; +		socfpga_clk->clk_phase[1] = clk_phase[1]; + +		socfpga_clk->sys_mgr_base_addr = +			syscon_regmap_lookup_by_compatible("altr,sys-mgr"); +		if (IS_ERR(socfpga_clk->sys_mgr_base_addr)) { +			pr_err("%s: failed to find altr,sys-mgr regmap!\n", +					__func__); +			return; +		} +	} + +	of_property_read_string(node, "clock-output-names", &clk_name); + +	init.name = clk_name; +	init.ops = ops; +	init.flags = 0; +	while (i < SOCFPGA_MAX_PARENTS && (parent_name[i] = +			of_clk_get_parent_name(node, i)) != NULL) +		i++; + +	init.parent_names = parent_name; +	init.num_parents = i; +	socfpga_clk->hw.hw.init = &init; + +	clk = clk_register(NULL, &socfpga_clk->hw.hw); +	if (WARN_ON(IS_ERR(clk))) { +		kfree(socfpga_clk); +		return; +	} +	rc = of_clk_add_provider(node, of_clk_src_simple_get, clk); +	if (WARN_ON(rc)) +		return; +} + +void __init socfpga_a10_gate_init(struct device_node *node) +{ +	__socfpga_gate_init(node, &gateclk_ops); +} diff --git a/drivers/clk/socfpga/clk-periph-a10.c b/drivers/clk/socfpga/clk-periph-a10.c new file mode 100644 index 000000000000..9d0181b5a6a4 --- /dev/null +++ b/drivers/clk/socfpga/clk-periph-a10.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 Altera Corporation. All rights reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> + +#include "clk.h" + +#define CLK_MGR_FREE_SHIFT		16 +#define CLK_MGR_FREE_MASK		0x7 + +#define SOCFPGA_MPU_FREE_CLK		"mpu_free_clk" +#define SOCFPGA_NOC_FREE_CLK		"noc_free_clk" +#define SOCFPGA_SDMMC_FREE_CLK		"sdmmc_free_clk" +#define to_socfpga_periph_clk(p) container_of(p, struct socfpga_periph_clk, hw.hw) + +static unsigned long clk_periclk_recalc_rate(struct clk_hw *hwclk, +					     unsigned long parent_rate) +{ +	struct socfpga_periph_clk *socfpgaclk = to_socfpga_periph_clk(hwclk); +	u32 div; + +	if (socfpgaclk->fixed_div) { +		div = socfpgaclk->fixed_div; +	} else if (socfpgaclk->div_reg) { +		div = readl(socfpgaclk->div_reg) >> socfpgaclk->shift; +		div &= div_mask(socfpgaclk->width); +		div += 1; +	} else { +		div = ((readl(socfpgaclk->hw.reg) & 0x7ff) + 1); +	} + +	return parent_rate / div; +} + +static u8 clk_periclk_get_parent(struct clk_hw *hwclk) +{ +	struct socfpga_periph_clk *socfpgaclk = to_socfpga_periph_clk(hwclk); +	u32 clk_src; + +	clk_src = readl(socfpgaclk->hw.reg); +	if (streq(hwclk->init->name, SOCFPGA_MPU_FREE_CLK) || +	    streq(hwclk->init->name, SOCFPGA_NOC_FREE_CLK) || +	    streq(hwclk->init->name, SOCFPGA_SDMMC_FREE_CLK)) +		return (clk_src >> CLK_MGR_FREE_SHIFT) & +			CLK_MGR_FREE_MASK; +	else +		return 0; +} + +static const struct clk_ops periclk_ops = { +	.recalc_rate = clk_periclk_recalc_rate, +	.get_parent = clk_periclk_get_parent, +}; + +static __init void __socfpga_periph_init(struct device_node *node, +	const struct clk_ops *ops) +{ +	u32 reg; +	struct clk *clk; +	struct socfpga_periph_clk *periph_clk; +	const char *clk_name = node->name; +	const char *parent_name; +	struct clk_init_data init; +	int rc; +	u32 fixed_div; +	u32 div_reg[3]; + +	of_property_read_u32(node, "reg", ®); + +	periph_clk = kzalloc(sizeof(*periph_clk), GFP_KERNEL); +	if (WARN_ON(!periph_clk)) +		return; + +	periph_clk->hw.reg = clk_mgr_a10_base_addr + reg; + +	rc = of_property_read_u32_array(node, "div-reg", div_reg, 3); +	if (!rc) { +		periph_clk->div_reg = clk_mgr_a10_base_addr + div_reg[0]; +		periph_clk->shift = div_reg[1]; +		periph_clk->width = div_reg[2]; +	} else { +		periph_clk->div_reg = NULL; +	} + +	rc = of_property_read_u32(node, "fixed-divider", &fixed_div); +	if (rc) +		periph_clk->fixed_div = 0; +	else +		periph_clk->fixed_div = fixed_div; + +	of_property_read_string(node, "clock-output-names", &clk_name); + +	init.name = clk_name; +	init.ops = ops; +	init.flags = 0; + +	parent_name = of_clk_get_parent_name(node, 0); +	init.num_parents = 1; +	init.parent_names = &parent_name; + +	periph_clk->hw.hw.init = &init; + +	clk = clk_register(NULL, &periph_clk->hw.hw); +	if (WARN_ON(IS_ERR(clk))) { +		kfree(periph_clk); +		return; +	} +	rc = of_clk_add_provider(node, of_clk_src_simple_get, clk); +	if (rc < 0) { +		pr_err("Could not register clock provider for node:%s\n", +		       clk_name); +		goto err_clk; +	} + +	return; + +err_clk: +	clk_unregister(clk); +} + +void __init socfpga_a10_periph_init(struct device_node *node) +{ +	__socfpga_periph_init(node, &periclk_ops); +} diff --git a/drivers/clk/socfpga/clk-pll-a10.c b/drivers/clk/socfpga/clk-pll-a10.c new file mode 100644 index 000000000000..1178b11babca --- /dev/null +++ b/drivers/clk/socfpga/clk-pll-a10.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 Altera Corporation. All rights reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "clk.h" + +/* Clock Manager offsets */ +#define CLK_MGR_PLL_CLK_SRC_SHIFT	8 +#define CLK_MGR_PLL_CLK_SRC_MASK	0x3 + +/* Clock bypass bits */ +#define SOCFPGA_PLL_BG_PWRDWN		0 +#define SOCFPGA_PLL_PWR_DOWN		1 +#define SOCFPGA_PLL_EXT_ENA		2 +#define SOCFPGA_PLL_DIVF_MASK		0x00001FFF +#define SOCFPGA_PLL_DIVF_SHIFT	0 +#define SOCFPGA_PLL_DIVQ_MASK		0x003F0000 +#define SOCFPGA_PLL_DIVQ_SHIFT	16 +#define SOCFGPA_MAX_PARENTS	5 + +#define SOCFPGA_MAIN_PLL_CLK		"main_pll" +#define SOCFPGA_PERIP_PLL_CLK		"periph_pll" + +#define to_socfpga_clk(p) container_of(p, struct socfpga_pll, hw.hw) + +void __iomem *clk_mgr_a10_base_addr; + +static unsigned long clk_pll_recalc_rate(struct clk_hw *hwclk, +					 unsigned long parent_rate) +{ +	struct socfpga_pll *socfpgaclk = to_socfpga_clk(hwclk); +	unsigned long divf, divq, reg; +	unsigned long long vco_freq; + +	/* read VCO1 reg for numerator and denominator */ +	reg = readl(socfpgaclk->hw.reg + 0x4); +	divf = (reg & SOCFPGA_PLL_DIVF_MASK) >> SOCFPGA_PLL_DIVF_SHIFT; +	divq = (reg & SOCFPGA_PLL_DIVQ_MASK) >> SOCFPGA_PLL_DIVQ_SHIFT; +	vco_freq = (unsigned long long)parent_rate * (divf + 1); +	do_div(vco_freq, (1 + divq)); +	return (unsigned long)vco_freq; +} + +static u8 clk_pll_get_parent(struct clk_hw *hwclk) +{ +	struct socfpga_pll *socfpgaclk = to_socfpga_clk(hwclk); +	u32 pll_src; + +	pll_src = readl(socfpgaclk->hw.reg); + +	return (pll_src >> CLK_MGR_PLL_CLK_SRC_SHIFT) & +		CLK_MGR_PLL_CLK_SRC_MASK; +} + +static struct clk_ops clk_pll_ops = { +	.recalc_rate = clk_pll_recalc_rate, +	.get_parent = clk_pll_get_parent, +}; + +static struct __init clk * __socfpga_pll_init(struct device_node *node, +	const struct clk_ops *ops) +{ +	u32 reg; +	struct clk *clk; +	struct socfpga_pll *pll_clk; +	const char *clk_name = node->name; +	const char *parent_name[SOCFGPA_MAX_PARENTS]; +	struct clk_init_data init; +	struct device_node *clkmgr_np; +	int rc; +	int i = 0; + +	of_property_read_u32(node, "reg", ®); + +	pll_clk = kzalloc(sizeof(*pll_clk), GFP_KERNEL); +	if (WARN_ON(!pll_clk)) +		return NULL; + +	clkmgr_np = of_find_compatible_node(NULL, NULL, "altr,clk-mgr"); +	clk_mgr_a10_base_addr = of_iomap(clkmgr_np, 0); +	BUG_ON(!clk_mgr_a10_base_addr); +	pll_clk->hw.reg = clk_mgr_a10_base_addr + reg; + +	of_property_read_string(node, "clock-output-names", &clk_name); + +	init.name = clk_name; +	init.ops = ops; +	init.flags = 0; + +	while (i < SOCFGPA_MAX_PARENTS && (parent_name[i] = +			of_clk_get_parent_name(node, i)) != NULL) +		i++; +	init.num_parents = i; +	init.parent_names = parent_name; +	pll_clk->hw.hw.init = &init; + +	pll_clk->hw.bit_idx = SOCFPGA_PLL_EXT_ENA; +	clk_pll_ops.enable = clk_gate_ops.enable; +	clk_pll_ops.disable = clk_gate_ops.disable; + +	clk = clk_register(NULL, &pll_clk->hw.hw); +	if (WARN_ON(IS_ERR(clk))) { +		kfree(pll_clk); +		return NULL; +	} +	rc = of_clk_add_provider(node, of_clk_src_simple_get, clk); +	return clk; +} + +void __init socfpga_a10_pll_init(struct device_node *node) +{ +	__socfpga_pll_init(node, &clk_pll_ops); +} diff --git a/drivers/clk/socfpga/clk.c b/drivers/clk/socfpga/clk.c index 43db947e5f0e..7564d2e35f32 100644 --- a/drivers/clk/socfpga/clk.c +++ b/drivers/clk/socfpga/clk.c @@ -24,4 +24,9 @@  CLK_OF_DECLARE(socfpga_pll_clk, "altr,socfpga-pll-clock", socfpga_pll_init);  CLK_OF_DECLARE(socfpga_perip_clk, "altr,socfpga-perip-clk", socfpga_periph_init);  CLK_OF_DECLARE(socfpga_gate_clk, "altr,socfpga-gate-clk", socfpga_gate_init); - +CLK_OF_DECLARE(socfpga_a10_pll_clk, "altr,socfpga-a10-pll-clock", +	       socfpga_a10_pll_init); +CLK_OF_DECLARE(socfpga_a10_perip_clk, "altr,socfpga-a10-perip-clk", +	       socfpga_a10_periph_init); +CLK_OF_DECLARE(socfpga_a10_gate_clk, "altr,socfpga-a10-gate-clk", +	       socfpga_a10_gate_init); diff --git a/drivers/clk/socfpga/clk.h b/drivers/clk/socfpga/clk.h index b09a5d50547e..603973ab7e29 100644 --- a/drivers/clk/socfpga/clk.h +++ b/drivers/clk/socfpga/clk.h @@ -34,10 +34,14 @@  	((((smplsel) & 0x7) << 3) | (((drvsel) & 0x7) << 0))  extern void __iomem *clk_mgr_base_addr; +extern void __iomem *clk_mgr_a10_base_addr;  void __init socfpga_pll_init(struct device_node *node);  void __init socfpga_periph_init(struct device_node *node);  void __init socfpga_gate_init(struct device_node *node); +void socfpga_a10_pll_init(struct device_node *node); +void socfpga_a10_periph_init(struct device_node *node); +void socfpga_a10_gate_init(struct device_node *node);  struct socfpga_pll {  	struct clk_gate	hw; @@ -48,6 +52,7 @@ struct socfpga_gate_clk {  	char *parent_name;  	u32 fixed_div;  	void __iomem *div_reg; +	struct regmap *sys_mgr_base_addr;  	u32 width;	/* only valid if div_reg != 0 */  	u32 shift;	/* only valid if div_reg != 0 */  	u32 clk_phase[2];  |