diff options
Diffstat (limited to 'drivers/clk/keystone')
| -rw-r--r-- | drivers/clk/keystone/Makefile | 1 | ||||
| -rw-r--r-- | drivers/clk/keystone/gate.c | 269 | ||||
| -rw-r--r-- | drivers/clk/keystone/pll.c | 321 | 
3 files changed, 591 insertions, 0 deletions
diff --git a/drivers/clk/keystone/Makefile b/drivers/clk/keystone/Makefile new file mode 100644 index 00000000000..0477cf63f13 --- /dev/null +++ b/drivers/clk/keystone/Makefile @@ -0,0 +1 @@ +obj-y			+= pll.o gate.o diff --git a/drivers/clk/keystone/gate.c b/drivers/clk/keystone/gate.c new file mode 100644 index 00000000000..86f1e362eaf --- /dev/null +++ b/drivers/clk/keystone/gate.c @@ -0,0 +1,269 @@ +/* + * Clock driver for Keystone 2 based devices + * + * Copyright (C) 2013 Texas Instruments. + *	Murali Karicheri <m-karicheri2@ti.com> + *	Santosh Shilimkar <santosh.shilimkar@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/module.h> + +/* PSC register offsets */ +#define PTCMD			0x120 +#define PTSTAT			0x128 +#define PDSTAT			0x200 +#define PDCTL			0x300 +#define MDSTAT			0x800 +#define MDCTL			0xa00 + +/* PSC module states */ +#define PSC_STATE_SWRSTDISABLE	0 +#define PSC_STATE_SYNCRST	1 +#define PSC_STATE_DISABLE	2 +#define PSC_STATE_ENABLE	3 + +#define MDSTAT_STATE_MASK	0x3f +#define MDSTAT_MCKOUT		BIT(12) +#define PDSTAT_STATE_MASK	0x1f +#define MDCTL_FORCE		BIT(31) +#define MDCTL_LRESET		BIT(8) +#define PDCTL_NEXT		BIT(0) + +/* Maximum timeout to bail out state transition for module */ +#define STATE_TRANS_MAX_COUNT	0xffff + +static void __iomem *domain_transition_base; + +/** + * struct clk_psc_data - PSC data + * @control_base: Base address for a PSC control + * @domain_base: Base address for a PSC domain + * @domain_id: PSC domain id number + */ +struct clk_psc_data { +	void __iomem *control_base; +	void __iomem *domain_base; +	u32 domain_id; +}; + +/** + * struct clk_psc - PSC clock structure + * @hw: clk_hw for the psc + * @psc_data: PSC driver specific data + * @lock: Spinlock used by the driver + */ +struct clk_psc { +	struct clk_hw hw; +	struct clk_psc_data *psc_data; +	spinlock_t *lock; +}; + +static DEFINE_SPINLOCK(psc_lock); + +#define to_clk_psc(_hw) container_of(_hw, struct clk_psc, hw) + +static void psc_config(void __iomem *control_base, void __iomem *domain_base, +						u32 next_state, u32 domain_id) +{ +	u32 ptcmd, pdstat, pdctl, mdstat, mdctl, ptstat; +	u32 count = STATE_TRANS_MAX_COUNT; + +	mdctl = readl(control_base + MDCTL); +	mdctl &= ~MDSTAT_STATE_MASK; +	mdctl |= next_state; +	/* For disable, we always put the module in local reset */ +	if (next_state == PSC_STATE_DISABLE) +		mdctl &= ~MDCTL_LRESET; +	writel(mdctl, control_base + MDCTL); + +	pdstat = readl(domain_base + PDSTAT); +	if (!(pdstat & PDSTAT_STATE_MASK)) { +		pdctl = readl(domain_base + PDCTL); +		pdctl |= PDCTL_NEXT; +		writel(pdctl, domain_base + PDCTL); +	} + +	ptcmd = 1 << domain_id; +	writel(ptcmd, domain_transition_base + PTCMD); +	do { +		ptstat = readl(domain_transition_base + PTSTAT); +	} while (((ptstat >> domain_id) & 1) && count--); + +	count = STATE_TRANS_MAX_COUNT; +	do { +		mdstat = readl(control_base + MDSTAT); +	} while (!((mdstat & MDSTAT_STATE_MASK) == next_state) && count--); +} + +static int keystone_clk_is_enabled(struct clk_hw *hw) +{ +	struct clk_psc *psc = to_clk_psc(hw); +	struct clk_psc_data *data = psc->psc_data; +	u32 mdstat = readl(data->control_base + MDSTAT); + +	return (mdstat & MDSTAT_MCKOUT) ? 1 : 0; +} + +static int keystone_clk_enable(struct clk_hw *hw) +{ +	struct clk_psc *psc = to_clk_psc(hw); +	struct clk_psc_data *data = psc->psc_data; +	unsigned long flags = 0; + +	if (psc->lock) +		spin_lock_irqsave(psc->lock, flags); + +	psc_config(data->control_base, data->domain_base, +				PSC_STATE_ENABLE, data->domain_id); + +	if (psc->lock) +		spin_unlock_irqrestore(psc->lock, flags); + +	return 0; +} + +static void keystone_clk_disable(struct clk_hw *hw) +{ +	struct clk_psc *psc = to_clk_psc(hw); +	struct clk_psc_data *data = psc->psc_data; +	unsigned long flags = 0; + +	if (psc->lock) +		spin_lock_irqsave(psc->lock, flags); + +	psc_config(data->control_base, data->domain_base, +				PSC_STATE_DISABLE, data->domain_id); + +	if (psc->lock) +		spin_unlock_irqrestore(psc->lock, flags); +} + +static const struct clk_ops clk_psc_ops = { +	.enable = keystone_clk_enable, +	.disable = keystone_clk_disable, +	.is_enabled = keystone_clk_is_enabled, +}; + +/** + * clk_register_psc - register psc clock + * @dev: device that is registering this clock + * @name: name of this clock + * @parent_name: name of clock's parent + * @psc_data: platform data to configure this clock + * @lock: spinlock used by this clock + */ +static struct clk *clk_register_psc(struct device *dev, +			const char *name, +			const char *parent_name, +			struct clk_psc_data *psc_data, +			spinlock_t *lock) +{ +	struct clk_init_data init; +	struct clk_psc *psc; +	struct clk *clk; + +	psc = kzalloc(sizeof(*psc), GFP_KERNEL); +	if (!psc) +		return ERR_PTR(-ENOMEM); + +	init.name = name; +	init.ops = &clk_psc_ops; +	init.flags = 0; +	init.parent_names = (parent_name ? &parent_name : NULL); +	init.num_parents = (parent_name ? 1 : 0); + +	psc->psc_data = psc_data; +	psc->lock = lock; +	psc->hw.init = &init; + +	clk = clk_register(NULL, &psc->hw); +	if (IS_ERR(clk)) +		kfree(psc); + +	return clk; +} + +/** + * of_psc_clk_init - initialize psc clock through DT + * @node: device tree node for this clock + * @lock: spinlock used by this clock + */ +static void __init of_psc_clk_init(struct device_node *node, spinlock_t *lock) +{ +	const char *clk_name = node->name; +	const char *parent_name; +	struct clk_psc_data *data; +	struct clk *clk; +	int i; + +	data = kzalloc(sizeof(*data), GFP_KERNEL); +	if (!data) { +		pr_err("%s: Out of memory\n", __func__); +		return; +	} + +	i = of_property_match_string(node, "reg-names", "control"); +	data->control_base = of_iomap(node, i); +	if (!data->control_base) { +		pr_err("%s: control ioremap failed\n", __func__); +		goto out; +	} + +	i = of_property_match_string(node, "reg-names", "domain"); +	data->domain_base = of_iomap(node, i); +	if (!data->domain_base) { +		pr_err("%s: domain ioremap failed\n", __func__); +		goto unmap_ctrl; +	} + +	of_property_read_u32(node, "domain-id", &data->domain_id); + +	/* Domain transition registers at fixed address space of domain_id 0 */ +	if (!domain_transition_base && !data->domain_id) +		domain_transition_base = data->domain_base; + +	of_property_read_string(node, "clock-output-names", &clk_name); +	parent_name = of_clk_get_parent_name(node, 0); +	if (!parent_name) { +		pr_err("%s: Parent clock not found\n", __func__); +		goto unmap_domain; +	} + +	clk = clk_register_psc(NULL, clk_name, parent_name, data, lock); +	if (!IS_ERR(clk)) { +		of_clk_add_provider(node, of_clk_src_simple_get, clk); +		return; +	} + +	pr_err("%s: error registering clk %s\n", __func__, node->name); + +unmap_domain: +	iounmap(data->domain_base); +unmap_ctrl: +	iounmap(data->control_base); +out: +	kfree(data); +	return; +} + +/** + * of_keystone_psc_clk_init - initialize psc clock through DT + * @node: device tree node for this clock + */ +static void __init of_keystone_psc_clk_init(struct device_node *node) +{ +	of_psc_clk_init(node, &psc_lock); +} +CLK_OF_DECLARE(keystone_gate_clk, "ti,keystone,psc-clock", +					of_keystone_psc_clk_init); diff --git a/drivers/clk/keystone/pll.c b/drivers/clk/keystone/pll.c new file mode 100644 index 00000000000..0dd8a4b1274 --- /dev/null +++ b/drivers/clk/keystone/pll.c @@ -0,0 +1,321 @@ +/* + * PLL clock driver for Keystone devices + * + * Copyright (C) 2013 Texas Instruments Inc. + *	Murali Karicheri <m-karicheri2@ti.com> + *	Santosh Shilimkar <santosh.shilimkar@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/of.h> +#include <linux/module.h> + +#define PLLM_LOW_MASK		0x3f +#define PLLM_HIGH_MASK		0x7ffc0 +#define MAIN_PLLM_HIGH_MASK	0x7f000 +#define PLLM_HIGH_SHIFT		6 +#define PLLD_MASK		0x3f +#define CLKOD_MASK		0x780000 +#define CLKOD_SHIFT		19 + +/** + * struct clk_pll_data - pll data structure + * @has_pllctrl: If set to non zero, lower 6 bits of multiplier is in pllm + *	register of pll controller, else it is in the pll_ctrl0((bit 11-6) + * @phy_pllm: Physical address of PLLM in pll controller. Used when + *	has_pllctrl is non zero. + * @phy_pll_ctl0: Physical address of PLL ctrl0. This could be that of + *	Main PLL or any other PLLs in the device such as ARM PLL, DDR PLL + *	or PA PLL available on keystone2. These PLLs are controlled by + *	this register. Main PLL is controlled by a PLL controller. + * @pllm: PLL register map address + * @pll_ctl0: PLL controller map address + * @pllm_lower_mask: multiplier lower mask + * @pllm_upper_mask: multiplier upper mask + * @pllm_upper_shift: multiplier upper shift + * @plld_mask: divider mask + * @clkod_mask: output divider mask + * @clkod_shift: output divider shift + * @plld_mask: divider mask + * @postdiv: Fixed post divider + */ +struct clk_pll_data { +	bool has_pllctrl; +	u32 phy_pllm; +	u32 phy_pll_ctl0; +	void __iomem *pllm; +	void __iomem *pll_ctl0; +	u32 pllm_lower_mask; +	u32 pllm_upper_mask; +	u32 pllm_upper_shift; +	u32 plld_mask; +	u32 clkod_mask; +	u32 clkod_shift; +	u32 postdiv; +}; + +/** + * struct clk_pll - Main pll clock + * @hw: clk_hw for the pll + * @pll_data: PLL driver specific data + */ +struct clk_pll { +	struct clk_hw hw; +	struct clk_pll_data *pll_data; +}; + +#define to_clk_pll(_hw) container_of(_hw, struct clk_pll, hw) + +static unsigned long clk_pllclk_recalc(struct clk_hw *hw, +					unsigned long parent_rate) +{ +	struct clk_pll *pll = to_clk_pll(hw); +	struct clk_pll_data *pll_data = pll->pll_data; +	unsigned long rate = parent_rate; +	u32  mult = 0, prediv, postdiv, val; + +	/* +	 * get bits 0-5 of multiplier from pllctrl PLLM register +	 * if has_pllctrl is non zero +	 */ +	if (pll_data->has_pllctrl) { +		val = readl(pll_data->pllm); +		mult = (val & pll_data->pllm_lower_mask); +	} + +	/* bit6-12 of PLLM is in Main PLL control register */ +	val = readl(pll_data->pll_ctl0); +	mult |= ((val & pll_data->pllm_upper_mask) +			>> pll_data->pllm_upper_shift); +	prediv = (val & pll_data->plld_mask); + +	if (!pll_data->has_pllctrl) +		/* read post divider from od bits*/ +		postdiv = ((val & pll_data->clkod_mask) >> +				 pll_data->clkod_shift) + 1; +	else +		postdiv = pll_data->postdiv; + +	rate /= (prediv + 1); +	rate = (rate * (mult + 1)); +	rate /= postdiv; + +	return rate; +} + +static const struct clk_ops clk_pll_ops = { +	.recalc_rate = clk_pllclk_recalc, +}; + +static struct clk *clk_register_pll(struct device *dev, +			const char *name, +			const char *parent_name, +			struct clk_pll_data *pll_data) +{ +	struct clk_init_data init; +	struct clk_pll *pll; +	struct clk *clk; + +	pll = kzalloc(sizeof(*pll), GFP_KERNEL); +	if (!pll) +		return ERR_PTR(-ENOMEM); + +	init.name = name; +	init.ops = &clk_pll_ops; +	init.flags = 0; +	init.parent_names = (parent_name ? &parent_name : NULL); +	init.num_parents = (parent_name ? 1 : 0); + +	pll->pll_data	= pll_data; +	pll->hw.init = &init; + +	clk = clk_register(NULL, &pll->hw); +	if (IS_ERR(clk)) +		goto out; + +	return clk; +out: +	kfree(pll); +	return NULL; +} + +/** + * _of_clk_init - PLL initialisation via DT + * @node: device tree node for this clock + * @pllctrl: If true, lower 6 bits of multiplier is in pllm register of + *		pll controller, else it is in the control regsiter0(bit 11-6) + */ +static void __init _of_pll_clk_init(struct device_node *node, bool pllctrl) +{ +	struct clk_pll_data *pll_data; +	const char *parent_name; +	struct clk *clk; +	int i; + +	pll_data = kzalloc(sizeof(*pll_data), GFP_KERNEL); +	if (!pll_data) { +		pr_err("%s: Out of memory\n", __func__); +		return; +	} + +	parent_name = of_clk_get_parent_name(node, 0); +	if (of_property_read_u32(node, "fixed-postdiv",	&pll_data->postdiv)) { +		/* assume the PLL has output divider register bits */ +		pll_data->clkod_mask = CLKOD_MASK; +		pll_data->clkod_shift = CLKOD_SHIFT; +	} + +	i = of_property_match_string(node, "reg-names", "control"); +	pll_data->pll_ctl0 = of_iomap(node, i); +	if (!pll_data->pll_ctl0) { +		pr_err("%s: ioremap failed\n", __func__); +		goto out; +	} + +	pll_data->pllm_lower_mask = PLLM_LOW_MASK; +	pll_data->pllm_upper_shift = PLLM_HIGH_SHIFT; +	pll_data->plld_mask = PLLD_MASK; +	pll_data->has_pllctrl = pllctrl; +	if (!pll_data->has_pllctrl) { +		pll_data->pllm_upper_mask = PLLM_HIGH_MASK; +	} else { +		pll_data->pllm_upper_mask = MAIN_PLLM_HIGH_MASK; +		i = of_property_match_string(node, "reg-names", "multiplier"); +		pll_data->pllm = of_iomap(node, i); +		if (!pll_data->pllm) { +			iounmap(pll_data->pll_ctl0); +			goto out; +		} +	} + +	clk = clk_register_pll(NULL, node->name, parent_name, pll_data); +	if (clk) { +		of_clk_add_provider(node, of_clk_src_simple_get, clk); +		return; +	} + +out: +	pr_err("%s: error initializing pll %s\n", __func__, node->name); +	kfree(pll_data); +} + +/** + * of_keystone_pll_clk_init - PLL initialisation DT wrapper + * @node: device tree node for this clock + */ +static void __init of_keystone_pll_clk_init(struct device_node *node) +{ +	_of_pll_clk_init(node, false); +} +CLK_OF_DECLARE(keystone_pll_clock, "ti,keystone,pll-clock", +					of_keystone_pll_clk_init); + +/** + * of_keystone_pll_main_clk_init - Main PLL initialisation DT wrapper + * @node: device tree node for this clock + */ +static void __init of_keystone_main_pll_clk_init(struct device_node *node) +{ +	_of_pll_clk_init(node, true); +} +CLK_OF_DECLARE(keystone_main_pll_clock, "ti,keystone,main-pll-clock", +						of_keystone_main_pll_clk_init); + +/** + * of_pll_div_clk_init - PLL divider setup function + * @node: device tree node for this clock + */ +static void __init of_pll_div_clk_init(struct device_node *node) +{ +	const char *parent_name; +	void __iomem *reg; +	u32 shift, mask; +	struct clk *clk; +	const char *clk_name = node->name; + +	of_property_read_string(node, "clock-output-names", &clk_name); +	reg = of_iomap(node, 0); +	if (!reg) { +		pr_err("%s: ioremap failed\n", __func__); +		return; +	} + +	parent_name = of_clk_get_parent_name(node, 0); +	if (!parent_name) { +		pr_err("%s: missing parent clock\n", __func__); +		return; +	} + +	if (of_property_read_u32(node, "bit-shift", &shift)) { +		pr_err("%s: missing 'shift' property\n", __func__); +		return; +	} + +	if (of_property_read_u32(node, "bit-mask", &mask)) { +		pr_err("%s: missing 'bit-mask' property\n", __func__); +		return; +	} + +	clk = clk_register_divider(NULL, clk_name, parent_name, 0, reg, shift, +				 mask, 0, NULL); +	if (clk) +		of_clk_add_provider(node, of_clk_src_simple_get, clk); +	else +		pr_err("%s: error registering divider %s\n", __func__, clk_name); +} +CLK_OF_DECLARE(pll_divider_clock, "ti,keystone,pll-divider-clock", of_pll_div_clk_init); + +/** + * of_pll_mux_clk_init - PLL mux setup function + * @node: device tree node for this clock + */ +static void __init of_pll_mux_clk_init(struct device_node *node) +{ +	void __iomem *reg; +	u32 shift, mask; +	struct clk *clk; +	const char *parents[2]; +	const char *clk_name = node->name; + +	of_property_read_string(node, "clock-output-names", &clk_name); +	reg = of_iomap(node, 0); +	if (!reg) { +		pr_err("%s: ioremap failed\n", __func__); +		return; +	} + +	parents[0] = of_clk_get_parent_name(node, 0); +	parents[1] = of_clk_get_parent_name(node, 1); +	if (!parents[0] || !parents[1]) { +		pr_err("%s: missing parent clocks\n", __func__); +		return; +	} + +	if (of_property_read_u32(node, "bit-shift", &shift)) { +		pr_err("%s: missing 'shift' property\n", __func__); +		return; +	} + +	if (of_property_read_u32(node, "bit-mask", &mask)) { +		pr_err("%s: missing 'bit-mask' property\n", __func__); +		return; +	} + +	clk = clk_register_mux(NULL, clk_name, (const char **)&parents, +				ARRAY_SIZE(parents) , 0, reg, shift, mask, +				0, NULL); +	if (clk) +		of_clk_add_provider(node, of_clk_src_simple_get, clk); +	else +		pr_err("%s: error registering mux %s\n", __func__, clk_name); +} +CLK_OF_DECLARE(pll_mux_clock, "ti,keystone,pll-mux-clock", of_pll_mux_clk_init);  | 
