diff options
| author | Taniya Das <tdas@codeaurora.org> | 2016-01-07 18:14:25 +0530 |
|---|---|---|
| committer | Rohit Vaswani <rvaswani@codeaurora.org> | 2016-03-01 13:00:24 -0800 |
| commit | 1ec7358a6161bdfe0482e4bd0d2b4448eea54449 (patch) | |
| tree | 13fabf9d4d7b06870ef3f0ac0031d04733b5967e | |
| parent | edf938dde9457d3c45a70ed3228c4c4ff61f1e11 (diff) | |
clk: msm: Add support for MSM clocks
Support added for MSM clock and modifications in the clk framework to use
the MSM clock framework.
Change-Id: Ibbcf0ffbf9d30dde2dcb0e943225ad95dd4e857d
Signed-off-by: Taniya Das <tdas@codeaurora.org>
34 files changed, 12041 insertions, 13 deletions
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index beea70ba7995..a24e372c842a 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -23,7 +23,7 @@ config ARM64 select ARM_PSCI_FW select BUILDTIME_EXTABLE_SORT select CLONE_BACKWARDS - select COMMON_CLK + select COMMON_CLK if !ARCH_QCOM select CPU_PM if (SUSPEND || CPU_IDLE) select DCACHE_WORD_ACCESS select EDAC_SUPPORT diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 4043c35962cc..227870c95f7e 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -49,6 +49,10 @@ config ARCH_MEDIATEK config ARCH_QCOM bool "Qualcomm Platforms" select PINCTRL + select CLKDEV_LOOKUP + select HAVE_CLK + select HAVE_CLK_PREPARE + select PM_OPP help This enables support for the ARMv8 based Qualcomm chipsets. diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index c3e3a02f7f1f..b00e391238b8 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -42,7 +42,7 @@ config COMMON_CLK_MAX77686 depends on MFD_MAX77686 select COMMON_CLK_MAX_GEN ---help--- - This driver supports Maxim 77686 crystal oscillator clock. + This driver supports Maxim 77686 crystal oscillator clock. config COMMON_CLK_MAX77802 tristate "Clock driver for Maxim 77802 PMIC" @@ -198,3 +198,4 @@ source "drivers/clk/mvebu/Kconfig" source "drivers/clk/samsung/Kconfig" source "drivers/clk/tegra/Kconfig" +source "drivers/clk/msm/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 820714c72d36..37f67c77fe7c 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -1,7 +1,7 @@ # common clock types obj-$(CONFIG_HAVE_CLK) += clk-devres.o obj-$(CONFIG_CLKDEV_LOOKUP) += clkdev.o -obj-$(CONFIG_COMMON_CLK) += clk.o +obj-$(CONFIG_OF) += clk.o obj-$(CONFIG_COMMON_CLK) += clk-divider.o obj-$(CONFIG_COMMON_CLK) += clk-fixed-factor.o obj-$(CONFIG_COMMON_CLK) += clk-fixed-rate.o @@ -82,3 +82,4 @@ obj-$(CONFIG_X86) += x86/ obj-$(CONFIG_ARCH_ZX) += zte/ obj-$(CONFIG_ARCH_ZYNQ) += zynq/ obj-$(CONFIG_H8300) += h8300/ +obj-$(CONFIG_ARCH_QCOM) += msm/ diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index f13c3f4228d4..cd54a184e997 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -26,6 +26,8 @@ #include "clk.h" +#if defined(CONFIG_COMMON_CLK) + static DEFINE_SPINLOCK(enable_lock); static DEFINE_MUTEX(prepare_lock); @@ -2901,6 +2903,8 @@ int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb) } EXPORT_SYMBOL_GPL(clk_notifier_unregister); +#endif /* CONFIG_COMMON_CLK */ + #ifdef CONFIG_OF /** * struct of_clk_provider - Clock provider registration structure @@ -2931,6 +2935,8 @@ struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec, } EXPORT_SYMBOL_GPL(of_clk_src_simple_get); +#if defined(CONFIG_COMMON_CLK) + struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data) { struct clk_onecell_data *clk_data = data; @@ -2945,6 +2951,11 @@ struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data) } EXPORT_SYMBOL_GPL(of_clk_src_onecell_get); +#endif /* CONFIG_COMMON_CLK */ + +/* forward declaration */ +void of_clk_del_provider(struct device_node *np); + /** * of_clk_add_provider() - Register a clock provider for a node * @np: Device node pointer associated with clock provider @@ -3100,8 +3111,10 @@ const char *of_clk_get_parent_name(struct device_node *np, int index) else clk_name = NULL; } else { +#if defined(CONFIG_COMMON_CLK) clk_name = __clk_get_name(clk); clk_put(clk); +#endif } } @@ -3132,6 +3145,8 @@ int of_clk_parent_fill(struct device_node *np, const char **parents, } EXPORT_SYMBOL_GPL(of_clk_parent_fill); +#if defined(CONFIG_COMMON_CLK) + struct clock_provider { of_clk_init_cb_t clk_init_cb; struct device_node *np; @@ -3240,4 +3255,7 @@ void __init of_clk_init(const struct of_device_id *matches) force = true; } } + +#endif /* CONFIG_COMMON_CLK */ + #endif diff --git a/drivers/clk/clk.h b/drivers/clk/clk.h index 00b35a13cdf3..97941b0f8f32 100644 --- a/drivers/clk/clk.h +++ b/drivers/clk/clk.h @@ -11,7 +11,7 @@ struct clk_hw; -#if defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK) +#if defined(CONFIG_OF) struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec, const char *dev_id, const char *con_id); #endif diff --git a/drivers/clk/clkdev.c b/drivers/clk/clkdev.c index 779b6ff0c7ad..ee1a9992c47f 100644 --- a/drivers/clk/clkdev.c +++ b/drivers/clk/clkdev.c @@ -27,7 +27,7 @@ static LIST_HEAD(clocks); static DEFINE_MUTEX(clocks_mutex); -#if defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK) +#if defined(CONFIG_OF) static struct clk *__of_clk_get(struct device_node *np, int index, const char *dev_id, const char *con_id) { @@ -73,14 +73,10 @@ static struct clk *__of_clk_get_by_name(struct device_node *np, if (name) index = of_property_match_string(np, "clock-names", name); clk = __of_clk_get(np, index, dev_id, name); - if (!IS_ERR(clk)) { + if (!IS_ERR(clk)) break; - } else if (name && index >= 0) { - if (PTR_ERR(clk) != -EPROBE_DEFER) - pr_err("ERROR: could not get clock %s:%s(%i)\n", - np->full_name, name ? name : "", index); + else if (name && index >= 0) return clk; - } /* * No matching clock found on this node. If the parent node @@ -190,7 +186,7 @@ struct clk *clk_get_sys(const char *dev_id, const char *con_id) out: mutex_unlock(&clocks_mutex); - return cl ? clk : ERR_PTR(-ENOENT); + return cl ? cl->clk : ERR_PTR(-EPROBE_DEFER); } EXPORT_SYMBOL(clk_get_sys); diff --git a/drivers/clk/msm/Kconfig b/drivers/clk/msm/Kconfig new file mode 100644 index 000000000000..7928fc7cfd85 --- /dev/null +++ b/drivers/clk/msm/Kconfig @@ -0,0 +1,5 @@ +config MSM_CLK_CONTROLLER_V2 + bool "QTI clock driver" + ---help--- + Generate clock data structures from definitions found in + device tree. diff --git a/drivers/clk/msm/Makefile b/drivers/clk/msm/Makefile new file mode 100644 index 000000000000..1e865e033810 --- /dev/null +++ b/drivers/clk/msm/Makefile @@ -0,0 +1,14 @@ +obj-y += clock.o +obj-y += clock-dummy.o +obj-y += clock-generic.o +obj-y += clock-local2.o +obj-y += clock-pll.o +obj-y += clock-alpha-pll.o +obj-y += clock-rpm.o +obj-y += clock-voter.o + +obj-$(CONFIG_MSM_CLK_CONTROLLER_V2) += msm-clock-controller.o + +obj-y += clock-debug.o + +obj-y += gdsc.o diff --git a/drivers/clk/msm/clock-alpha-pll.c b/drivers/clk/msm/clock-alpha-pll.c new file mode 100644 index 000000000000..ad1f62467771 --- /dev/null +++ b/drivers/clk/msm/clock-alpha-pll.c @@ -0,0 +1,1214 @@ +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <soc/qcom/clock-alpha-pll.h> +#include <soc/qcom/msm-clock-controller.h> + +#include "clock.h" + +#define WAIT_MAX_LOOPS 100 + +#define MODE_REG(pll) (*pll->base + pll->offset + 0x0) +#define LOCK_REG(pll) (*pll->base + pll->offset + 0x0) +#define ACTIVE_REG(pll) (*pll->base + pll->offset + 0x0) +#define UPDATE_REG(pll) (*pll->base + pll->offset + 0x0) +#define L_REG(pll) (*pll->base + pll->offset + 0x4) +#define A_REG(pll) (*pll->base + pll->offset + 0x8) +#define VCO_REG(pll) (*pll->base + pll->offset + 0x10) +#define ALPHA_EN_REG(pll) (*pll->base + pll->offset + 0x10) +#define OUTPUT_REG(pll) (*pll->base + pll->offset + 0x10) +#define VOTE_REG(pll) (*pll->base + pll->fsm_reg_offset) +#define USER_CTL_LO_REG(pll) (*pll->base + pll->offset + 0x10) +#define USER_CTL_HI_REG(pll) (*pll->base + pll->offset + 0x14) +#define CONFIG_CTL_REG(pll) (*pll->base + pll->offset + 0x18) +#define TEST_CTL_LO_REG(pll) (*pll->base + pll->offset + 0x1c) +#define TEST_CTL_HI_REG(pll) (*pll->base + pll->offset + 0x20) + +#define PLL_BYPASSNL 0x2 +#define PLL_RESET_N 0x4 +#define PLL_OUTCTRL 0x1 +#define PLL_LATCH_INTERFACE BIT(11) + +#define FABIA_CONFIG_CTL_REG(pll) (*pll->base + pll->offset + 0x14) +#define FABIA_USER_CTL_LO_REG(pll) (*pll->base + pll->offset + 0xc) +#define FABIA_USER_CTL_HI_REG(pll) (*pll->base + pll->offset + 0x10) +#define FABIA_TEST_CTL_LO_REG(pll) (*pll->base + pll->offset + 0x1c) +#define FABIA_TEST_CTL_HI_REG(pll) (*pll->base + pll->offset + 0x20) +#define FABIA_L_REG(pll) (*pll->base + pll->offset + 0x4) +#define FABIA_FRAC_REG(pll) (*pll->base + pll->offset + 0x38) +#define FABIA_PLL_OPMODE(pll) (*pll->base + pll->offset + 0x2c) + +#define FABIA_PLL_STANDBY 0x0 +#define FABIA_PLL_RUN 0x1 +#define FABIA_PLL_OUT_MAIN 0x7 +#define FABIA_RATE_MARGIN 500 +#define FABIA_PLL_ACK_LATCH BIT(29) +#define FABIA_PLL_HW_UPDATE_LOGIC_BYPASS BIT(23) + +/* + * Even though 40 bits are present, use only 32 for ease of calculation. + */ +#define ALPHA_REG_BITWIDTH 40 +#define ALPHA_BITWIDTH 32 +#define FABIA_ALPHA_BITWIDTH 16 + +/* + * Enable/disable registers could be shared among PLLs when FSM voting + * is used. This lock protects against potential race when multiple + * PLLs are being enabled/disabled together. + */ +static DEFINE_SPINLOCK(alpha_pll_reg_lock); + +static unsigned long compute_rate(struct alpha_pll_clk *pll, + u32 l_val, u32 a_val) +{ + u64 rate, parent_rate; + int alpha_bw = ALPHA_BITWIDTH; + + if (pll->is_fabia) + alpha_bw = FABIA_ALPHA_BITWIDTH; + + parent_rate = clk_get_rate(pll->c.parent); + rate = parent_rate * l_val; + rate += (parent_rate * a_val) >> alpha_bw; + return rate; +} + +static bool is_locked(struct alpha_pll_clk *pll) +{ + u32 reg = readl_relaxed(LOCK_REG(pll)); + u32 mask = pll->masks->lock_mask; + return (reg & mask) == mask; +} + +static bool is_active(struct alpha_pll_clk *pll) +{ + u32 reg = readl_relaxed(ACTIVE_REG(pll)); + u32 mask = pll->masks->active_mask; + return (reg & mask) == mask; +} + +/* + * Check active_flag if PLL is in FSM mode, otherwise check lock_det + * bit. This function assumes PLLs are already configured to the + * right mode. + */ +static bool update_finish(struct alpha_pll_clk *pll) +{ + if (pll->fsm_en_mask) + return is_active(pll); + else + return is_locked(pll); +} + +static int wait_for_update(struct alpha_pll_clk *pll) +{ + int count; + + for (count = WAIT_MAX_LOOPS; count > 0; count--) { + if (update_finish(pll)) + break; + udelay(1); + } + + if (!count) { + pr_err("%s didn't lock after enabling it!\n", pll->c.dbg_name); + return -EINVAL; + } + + return 0; +} + +static int __alpha_pll_vote_enable(struct alpha_pll_clk *pll) +{ + u32 ena; + + ena = readl_relaxed(VOTE_REG(pll)); + ena |= pll->fsm_en_mask; + writel_relaxed(ena, VOTE_REG(pll)); + mb(); + + return wait_for_update(pll); +} + +static int __alpha_pll_enable(struct alpha_pll_clk *pll, int enable_output) +{ + int rc; + u32 mode; + + mode = readl_relaxed(MODE_REG(pll)); + mode |= PLL_BYPASSNL; + writel_relaxed(mode, MODE_REG(pll)); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. + */ + mb(); + udelay(5); + + mode |= PLL_RESET_N; + writel_relaxed(mode, MODE_REG(pll)); + + rc = wait_for_update(pll); + if (rc < 0) + return rc; + + /* Enable PLL output. */ + if (enable_output) { + mode |= PLL_OUTCTRL; + writel_relaxed(mode, MODE_REG(pll)); + } + + /* Ensure that the write above goes through before returning. */ + mb(); + return 0; +} + +static void setup_alpha_pll_values(u64 a_val, u32 l_val, u32 vco_val, + struct alpha_pll_clk *pll) +{ + struct alpha_pll_masks *masks = pll->masks; + u32 regval; + + a_val = a_val << (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); + + writel_relaxed(l_val, L_REG(pll)); + __iowrite32_copy(A_REG(pll), &a_val, 2); + + if (vco_val != UINT_MAX) { + regval = readl_relaxed(VCO_REG(pll)); + regval &= ~(masks->vco_mask << masks->vco_shift); + regval |= vco_val << masks->vco_shift; + writel_relaxed(regval, VCO_REG(pll)); + } + + regval = readl_relaxed(ALPHA_EN_REG(pll)); + regval |= masks->alpha_en_mask; + writel_relaxed(regval, ALPHA_EN_REG(pll)); +} + +static int alpha_pll_enable(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long flags; + int rc; + + if (unlikely(!pll->inited)) + __init_alpha_pll(c); + + spin_lock_irqsave(&alpha_pll_reg_lock, flags); + if (pll->fsm_en_mask) + rc = __alpha_pll_vote_enable(pll); + else + rc = __alpha_pll_enable(pll, true); + spin_unlock_irqrestore(&alpha_pll_reg_lock, flags); + + return rc; +} + +static int __calibrate_alpha_pll(struct alpha_pll_clk *pll); +static int dyna_alpha_pll_enable(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long flags; + int rc; + + if (unlikely(!pll->inited)) + __init_alpha_pll(c); + + spin_lock_irqsave(&alpha_pll_reg_lock, flags); + + if (pll->slew) + __calibrate_alpha_pll(pll); + + if (pll->fsm_en_mask) + rc = __alpha_pll_vote_enable(pll); + else + rc = __alpha_pll_enable(pll, true); + spin_unlock_irqrestore(&alpha_pll_reg_lock, flags); + + return rc; +} + +#define PLL_OFFLINE_REQ_BIT BIT(7) +#define PLL_FSM_ENA_BIT BIT(20) +#define PLL_OFFLINE_ACK_BIT BIT(28) +#define PLL_ACTIVE_FLAG BIT(30) + +static int alpha_pll_enable_hwfsm(struct clk *c) +{ + u32 mode; + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + + /* Re-enable HW FSM mode, clear OFFLINE request */ + mode = readl_relaxed(MODE_REG(pll)); + mode |= PLL_FSM_ENA_BIT; + mode &= ~PLL_OFFLINE_REQ_BIT; + writel_relaxed(mode, MODE_REG(pll)); + + /* Make sure enable request goes through before waiting for update */ + mb(); + + if (wait_for_update(pll) < 0) + panic("PLL %s failed to lock", c->dbg_name); + + return 0; +} + +static void alpha_pll_disable_hwfsm(struct clk *c) +{ + u32 mode; + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + + /* Request PLL_OFFLINE and wait for ack */ + mode = readl_relaxed(MODE_REG(pll)); + writel_relaxed(mode | PLL_OFFLINE_REQ_BIT, MODE_REG(pll)); + while (!(readl_relaxed(MODE_REG(pll)) & PLL_OFFLINE_ACK_BIT)) + ; + + /* Disable HW FSM */ + mode = readl_relaxed(MODE_REG(pll)); + mode &= ~PLL_FSM_ENA_BIT; + if (pll->offline_bit_workaround) + mode &= ~PLL_OFFLINE_REQ_BIT; + writel_relaxed(mode, MODE_REG(pll)); + + while (readl_relaxed(MODE_REG(pll)) & PLL_ACTIVE_FLAG) + ; +} + +static void __alpha_pll_vote_disable(struct alpha_pll_clk *pll) +{ + u32 ena; + + ena = readl_relaxed(VOTE_REG(pll)); + ena &= ~pll->fsm_en_mask; + writel_relaxed(ena, VOTE_REG(pll)); +} + +static void __alpha_pll_disable(struct alpha_pll_clk *pll) +{ + u32 mode; + + mode = readl_relaxed(MODE_REG(pll)); + mode &= ~PLL_OUTCTRL; + writel_relaxed(mode, MODE_REG(pll)); + + /* Delay of 2 output clock ticks required until output is disabled */ + mb(); + udelay(1); + + mode &= ~(PLL_BYPASSNL | PLL_RESET_N); + writel_relaxed(mode, MODE_REG(pll)); +} + +static void alpha_pll_disable(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long flags; + + spin_lock_irqsave(&alpha_pll_reg_lock, flags); + if (pll->fsm_en_mask) + __alpha_pll_vote_disable(pll); + else + __alpha_pll_disable(pll); + spin_unlock_irqrestore(&alpha_pll_reg_lock, flags); +} + +static void dyna_alpha_pll_disable(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long flags; + + spin_lock_irqsave(&alpha_pll_reg_lock, flags); + if (pll->fsm_en_mask) + __alpha_pll_vote_disable(pll); + else + __alpha_pll_disable(pll); + + spin_unlock_irqrestore(&alpha_pll_reg_lock, flags); +} + +static u32 find_vco(struct alpha_pll_clk *pll, unsigned long rate) +{ + unsigned long i; + struct alpha_pll_vco_tbl *v = pll->vco_tbl; + + for (i = 0; i < pll->num_vco; i++) { + if (rate >= v[i].min_freq && rate <= v[i].max_freq) + return v[i].vco_val; + } + + return -EINVAL; +} + +static unsigned long __calc_values(struct alpha_pll_clk *pll, + unsigned long rate, int *l_val, u64 *a_val, bool round_up) +{ + u32 parent_rate; + u64 remainder; + u64 quotient; + unsigned long freq_hz; + int alpha_bw = ALPHA_BITWIDTH; + + parent_rate = clk_get_rate(pll->c.parent); + quotient = rate; + remainder = do_div(quotient, parent_rate); + *l_val = quotient; + + if (!remainder) { + *a_val = 0; + return rate; + } + + if (pll->is_fabia) + alpha_bw = FABIA_ALPHA_BITWIDTH; + + /* Upper ALPHA_BITWIDTH bits of Alpha */ + quotient = remainder << alpha_bw; + remainder = do_div(quotient, parent_rate); + + if (remainder && round_up) + quotient++; + + *a_val = quotient; + freq_hz = compute_rate(pll, *l_val, *a_val); + return freq_hz; +} + +static unsigned long round_rate_down(struct alpha_pll_clk *pll, + unsigned long rate, int *l_val, u64 *a_val) +{ + return __calc_values(pll, rate, l_val, a_val, false); +} + +static unsigned long round_rate_up(struct alpha_pll_clk *pll, + unsigned long rate, int *l_val, u64 *a_val) +{ + return __calc_values(pll, rate, l_val, a_val, true); +} + +static bool dynamic_update_finish(struct alpha_pll_clk *pll) +{ + u32 reg = readl_relaxed(UPDATE_REG(pll)); + u32 mask = pll->masks->update_mask; + + return (reg & mask) == 0; +} + +static int wait_for_dynamic_update(struct alpha_pll_clk *pll) +{ + int count; + + for (count = WAIT_MAX_LOOPS; count > 0; count--) { + if (dynamic_update_finish(pll)) + break; + udelay(1); + } + + if (!count) { + pr_err("%s didn't latch after updating it!\n", pll->c.dbg_name); + return -EINVAL; + } + + return 0; +} + +static int dyna_alpha_pll_dynamic_update(struct alpha_pll_clk *pll) +{ + struct alpha_pll_masks *masks = pll->masks; + u32 regval; + int rc; + + regval = readl_relaxed(UPDATE_REG(pll)); + regval |= masks->update_mask; + writel_relaxed(regval, UPDATE_REG(pll)); + + rc = wait_for_dynamic_update(pll); + if (rc < 0) + return rc; + + /* + * HPG mandates a wait of at least 570ns before polling the LOCK + * detect bit. Have a delay of 1us just to be safe. + */ + mb(); + udelay(1); + + rc = wait_for_update(pll); + if (rc < 0) + return rc; + + return 0; +} + +static int alpha_pll_set_rate(struct clk *c, unsigned long rate); +static int dyna_alpha_pll_set_rate(struct clk *c, unsigned long rate) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long freq_hz, flags; + u32 l_val, vco_val; + u64 a_val; + int ret; + + freq_hz = round_rate_up(pll, rate, &l_val, &a_val); + if (freq_hz != rate) { + pr_err("alpha_pll: Call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + vco_val = find_vco(pll, freq_hz); + + /* + * Dynamic pll update will not support switching frequencies across + * vco ranges. In those cases fall back to normal alpha set rate. + */ + if (pll->current_vco_val != vco_val) { + ret = alpha_pll_set_rate(c, rate); + if (!ret) + pll->current_vco_val = vco_val; + else + return ret; + return 0; + } + + spin_lock_irqsave(&c->lock, flags); + + a_val = a_val << (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); + + writel_relaxed(l_val, L_REG(pll)); + __iowrite32_copy(A_REG(pll), &a_val, 2); + + /* Ensure that the write above goes through before proceeding. */ + mb(); + + if (c->count) + dyna_alpha_pll_dynamic_update(pll); + + spin_unlock_irqrestore(&c->lock, flags); + return 0; +} + +/* + * Slewing plls should be bought up at frequency which is in the middle of the + * desired VCO range. So after bringing up the pll at calibration freq, set it + * back to desired frequency(that was set by previous clk_set_rate). + */ +static int __calibrate_alpha_pll(struct alpha_pll_clk *pll) +{ + unsigned long calibration_freq, freq_hz; + struct alpha_pll_vco_tbl *vco_tbl = pll->vco_tbl; + u64 a_val; + u32 l_val, vco_val; + int rc; + + vco_val = find_vco(pll, pll->c.rate); + if (IS_ERR_VALUE(vco_val)) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + calibration_freq = (vco_tbl[vco_val].min_freq + + vco_tbl[vco_val].max_freq)/2; + + freq_hz = round_rate_up(pll, calibration_freq, &l_val, &a_val); + if (freq_hz != calibration_freq) { + pr_err("alpha_pll: call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + setup_alpha_pll_values(a_val, l_val, vco_tbl->vco_val, pll); + + /* Bringup the pll at calibration frequency */ + rc = __alpha_pll_enable(pll, false); + if (rc) { + pr_err("alpha pll calibration failed\n"); + return rc; + } + + /* + * PLL is already running at calibration frequency. + * So slew pll to the previously set frequency. + */ + pr_debug("pll %s: setting back to required rate %lu\n", pll->c.dbg_name, + pll->c.rate); + freq_hz = round_rate_up(pll, pll->c.rate, &l_val, &a_val); + setup_alpha_pll_values(a_val, l_val, UINT_MAX, pll); + dyna_alpha_pll_dynamic_update(pll); + + return 0; +} + +static int alpha_pll_set_rate(struct clk *c, unsigned long rate) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + struct alpha_pll_masks *masks = pll->masks; + unsigned long flags, freq_hz; + u32 regval, l_val; + int vco_val; + u64 a_val; + + freq_hz = round_rate_up(pll, rate, &l_val, &a_val); + if (freq_hz != rate) { + pr_err("alpha_pll: Call clk_set_rate with rounded rates!\n"); + return -EINVAL; + } + + vco_val = find_vco(pll, freq_hz); + if (IS_ERR_VALUE(vco_val)) { + pr_err("alpha pll: not in a valid vco range\n"); + return -EINVAL; + } + + /* + * Ensure PLL is off before changing rate. For optimization reasons, + * assume no downstream clock is actively using it. No support + * for dynamic update at the moment. + */ + spin_lock_irqsave(&c->lock, flags); + if (c->count) + c->ops->disable(c); + + a_val = a_val << (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); + + writel_relaxed(l_val, L_REG(pll)); + __iowrite32_copy(A_REG(pll), &a_val, 2); + + if (masks->vco_mask) { + regval = readl_relaxed(VCO_REG(pll)); + regval &= ~(masks->vco_mask << masks->vco_shift); + regval |= vco_val << masks->vco_shift; + writel_relaxed(regval, VCO_REG(pll)); + } + + regval = readl_relaxed(ALPHA_EN_REG(pll)); + regval |= masks->alpha_en_mask; + writel_relaxed(regval, ALPHA_EN_REG(pll)); + + if (c->count) + c->ops->enable(c); + + spin_unlock_irqrestore(&c->lock, flags); + return 0; +} + +static long alpha_pll_round_rate(struct clk *c, unsigned long rate) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + struct alpha_pll_vco_tbl *v = pll->vco_tbl; + int ret; + u32 l_val; + unsigned long freq_hz; + u64 a_val; + int i; + + if (pll->no_prepared_reconfig && c->prepare_count) + return -EINVAL; + + freq_hz = round_rate_up(pll, rate, &l_val, &a_val); + if (pll->is_fabia) { + if (rate < pll->min_supported_freq) + return pll->min_supported_freq; + return freq_hz; + } + ret = find_vco(pll, freq_hz); + if (!IS_ERR_VALUE(ret)) + return freq_hz; + + freq_hz = 0; + for (i = 0; i < pll->num_vco; i++) { + if (is_better_rate(rate, freq_hz, v[i].min_freq)) + freq_hz = v[i].min_freq; + if (is_better_rate(rate, freq_hz, v[i].max_freq)) + freq_hz = v[i].max_freq; + } + if (!freq_hz) + return -EINVAL; + return freq_hz; +} + +static void update_vco_tbl(struct alpha_pll_clk *pll) +{ + int i, l_val; + u64 a_val; + unsigned long hz; + + /* Round vco limits to valid rates */ + for (i = 0; i < pll->num_vco; i++) { + hz = round_rate_up(pll, pll->vco_tbl[i].min_freq, &l_val, + &a_val); + pll->vco_tbl[i].min_freq = hz; + + hz = round_rate_down(pll, pll->vco_tbl[i].max_freq, &l_val, + &a_val); + pll->vco_tbl[i].max_freq = hz; + } +} + +/* + * Program bias count to be 0x6 (corresponds to 5us), and lock count + * bits to 0 (check lock_det for locking). + */ +static void __set_fsm_mode(void __iomem *mode_reg) +{ + u32 regval = readl_relaxed(mode_reg); + + /* De-assert reset to FSM */ + regval &= ~BIT(21); + writel_relaxed(regval, mode_reg); + + /* Program bias count */ + regval &= ~BM(19, 14); + regval |= BVAL(19, 14, 0x6); + writel_relaxed(regval, mode_reg); + + /* Program lock count */ + regval &= ~BM(13, 8); + regval |= BVAL(13, 8, 0x0); + writel_relaxed(regval, mode_reg); + + /* Enable PLL FSM voting */ + regval |= BIT(20); + writel_relaxed(regval, mode_reg); +} + +static bool is_fsm_mode(void __iomem *mode_reg) +{ + return !!(readl_relaxed(mode_reg) & BIT(20)); +} + +void __init_alpha_pll(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + struct alpha_pll_masks *masks = pll->masks; + u32 regval; + + if (pll->config_ctl_val) + writel_relaxed(pll->config_ctl_val, CONFIG_CTL_REG(pll)); + + if (masks->output_mask && pll->enable_config) { + regval = readl_relaxed(OUTPUT_REG(pll)); + regval &= ~masks->output_mask; + regval |= pll->enable_config; + writel_relaxed(regval, OUTPUT_REG(pll)); + } + + if (masks->post_div_mask) { + regval = readl_relaxed(USER_CTL_LO_REG(pll)); + regval &= ~masks->post_div_mask; + regval |= pll->post_div_config; + writel_relaxed(regval, USER_CTL_LO_REG(pll)); + } + + if (pll->slew) { + regval = readl_relaxed(USER_CTL_HI_REG(pll)); + regval &= ~PLL_LATCH_INTERFACE; + writel_relaxed(regval, USER_CTL_HI_REG(pll)); + } + + if (masks->test_ctl_lo_mask) { + regval = readl_relaxed(TEST_CTL_LO_REG(pll)); + regval &= ~masks->test_ctl_lo_mask; + regval |= pll->test_ctl_lo_val; + writel_relaxed(regval, TEST_CTL_LO_REG(pll)); + } + + if (masks->test_ctl_hi_mask) { + regval = readl_relaxed(TEST_CTL_HI_REG(pll)); + regval &= ~masks->test_ctl_hi_mask; + regval |= pll->test_ctl_hi_val; + writel_relaxed(regval, TEST_CTL_HI_REG(pll)); + } + + if (pll->fsm_en_mask) + __set_fsm_mode(MODE_REG(pll)); + + pll->inited = true; +} + +static enum handoff alpha_pll_handoff(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + struct alpha_pll_masks *masks = pll->masks; + u64 a_val; + u32 alpha_en, l_val; + + update_vco_tbl(pll); + + if (!is_locked(pll)) { + if (c->rate && alpha_pll_set_rate(c, c->rate)) + WARN(1, "%s: Failed to configure rate\n", c->dbg_name); + __init_alpha_pll(c); + return HANDOFF_DISABLED_CLK; + } else if (pll->fsm_en_mask && !is_fsm_mode(MODE_REG(pll))) { + WARN(1, "%s should be in FSM mode but is not\n", c->dbg_name); + } + + l_val = readl_relaxed(L_REG(pll)); + /* read u64 in two steps to satisfy alignment constraint */ + a_val = readl_relaxed(A_REG(pll) + 0x4); + a_val = a_val << 32 | readl_relaxed(A_REG(pll)); + /* get upper 32 bits */ + a_val = a_val >> (ALPHA_REG_BITWIDTH - ALPHA_BITWIDTH); + + alpha_en = readl_relaxed(ALPHA_EN_REG(pll)); + alpha_en &= masks->alpha_en_mask; + if (!alpha_en) + a_val = 0; + + c->rate = compute_rate(pll, l_val, a_val); + + /* + * Unconditionally vote for the PLL; it might be on because of + * another master's vote. + */ + if (pll->fsm_en_mask) + __alpha_pll_vote_enable(pll); + + return HANDOFF_ENABLED_CLK; +} + +static void __iomem *alpha_pll_list_registers(struct clk *clk, int n, + struct clk_register_data **regs, u32 *size) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(clk); + static struct clk_register_data data[] = { + {"PLL_MODE", 0x0}, + {"PLL_L_VAL", 0x4}, + {"PLL_ALPHA_VAL", 0x8}, + {"PLL_ALPHA_VAL_U", 0xC}, + {"PLL_USER_CTL", 0x10}, + {"PLL_CONFIG_CTL", 0x18}, + }; + + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return MODE_REG(pll); +} + +static int __fabia_alpha_pll_enable(struct alpha_pll_clk *pll) +{ + int rc; + u32 mode; + + /* Disable PLL output */ + mode = readl_relaxed(MODE_REG(pll)); + mode &= ~PLL_OUTCTRL; + writel_relaxed(mode, MODE_REG(pll)); + + /* Set operation mode to STANDBY */ + writel_relaxed(FABIA_PLL_STANDBY, FABIA_PLL_OPMODE(pll)); + + /* PLL should be in STANDBY mode before continuing */ + mb(); + + /* Bring PLL out of reset */ + mode = readl_relaxed(MODE_REG(pll)); + mode |= PLL_RESET_N; + writel_relaxed(mode, MODE_REG(pll)); + + /* Set operation mode to RUN */ + writel_relaxed(FABIA_PLL_RUN, FABIA_PLL_OPMODE(pll)); + + rc = wait_for_update(pll); + if (rc < 0) + return rc; + + /* Enable the main PLL output */ + mode = readl_relaxed(FABIA_USER_CTL_LO_REG(pll)); + mode |= FABIA_PLL_OUT_MAIN; + writel_relaxed(mode, FABIA_USER_CTL_LO_REG(pll)); + + /* Enable PLL outputs */ + mode = readl_relaxed(MODE_REG(pll)); + mode |= PLL_OUTCTRL; + writel_relaxed(mode, MODE_REG(pll)); + + /* Ensure that the write above goes through before returning. */ + mb(); + return 0; +} + +static int fabia_alpha_pll_enable(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long flags; + int rc; + + spin_lock_irqsave(&alpha_pll_reg_lock, flags); + if (pll->fsm_en_mask) + rc = __alpha_pll_vote_enable(pll); + else + rc = __fabia_alpha_pll_enable(pll); + spin_unlock_irqrestore(&alpha_pll_reg_lock, flags); + + return rc; +} + +static void __fabia_alpha_pll_disable(struct alpha_pll_clk *pll) +{ + u32 mode; + + /* Disable PLL outputs */ + mode = readl_relaxed(MODE_REG(pll)); + mode &= ~PLL_OUTCTRL; + writel_relaxed(mode, MODE_REG(pll)); + + /* Disable the main PLL output */ + mode = readl_relaxed(FABIA_USER_CTL_LO_REG(pll)); + mode &= ~FABIA_PLL_OUT_MAIN; + writel_relaxed(mode, FABIA_USER_CTL_LO_REG(pll)); + + /* Place PLL is the OFF state */ + mode = readl_relaxed(MODE_REG(pll)); + mode &= ~PLL_RESET_N; + writel_relaxed(mode, MODE_REG(pll)); + + /* Place the PLL mode in STANDBY */ + writel_relaxed(FABIA_PLL_STANDBY, FABIA_PLL_OPMODE(pll)); +} + +static void fabia_alpha_pll_disable(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long flags; + + spin_lock_irqsave(&alpha_pll_reg_lock, flags); + if (pll->fsm_en_mask) + __alpha_pll_vote_disable(pll); + else + __fabia_alpha_pll_disable(pll); + spin_unlock_irqrestore(&alpha_pll_reg_lock, flags); +} + +static int fabia_alpha_pll_set_rate(struct clk *c, unsigned long rate) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + unsigned long flags, freq_hz; + u32 regval, l_val; + u64 a_val; + + freq_hz = round_rate_up(pll, rate, &l_val, &a_val); + if (freq_hz > rate + FABIA_RATE_MARGIN || freq_hz < rate) { + pr_err("%s: Call clk_set_rate with rounded rates!\n", + c->dbg_name); + return -EINVAL; + } + + spin_lock_irqsave(&c->lock, flags); + /* Set the new L value */ + writel_relaxed(l_val, FABIA_L_REG(pll)); + writel_relaxed(a_val, FABIA_FRAC_REG(pll)); + + /* Latch the input to the PLL */ + regval = readl_relaxed(MODE_REG(pll)); + regval |= pll->masks->update_mask; + writel_relaxed(regval, MODE_REG(pll)); + + /* Wait for 2 reference cycle before checking ACK bit */ + udelay(1); + if (!(readl_relaxed(MODE_REG(pll)) & FABIA_PLL_ACK_LATCH)) { + pr_err("%s: PLL latch failed. Leaving PLL disabled\n", + c->dbg_name); + goto ret; + } + + /* Return latch input to 0 */ + regval = readl_relaxed(MODE_REG(pll)); + regval &= ~pll->masks->update_mask; + writel_relaxed(regval, MODE_REG(pll)); +ret: + spin_unlock_irqrestore(&c->lock, flags); + return 0; +} + +void __init_fabia_alpha_pll(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + struct alpha_pll_masks *masks = pll->masks; + u32 regval; + + if (pll->config_ctl_val) + writel_relaxed(pll->config_ctl_val, FABIA_CONFIG_CTL_REG(pll)); + + if (masks->output_mask && pll->enable_config) { + regval = readl_relaxed(FABIA_USER_CTL_LO_REG(pll)); + regval &= ~masks->output_mask; + regval |= pll->enable_config; + writel_relaxed(regval, FABIA_USER_CTL_LO_REG(pll)); + } + + if (masks->post_div_mask) { + regval = readl_relaxed(FABIA_USER_CTL_LO_REG(pll)); + regval &= ~masks->post_div_mask; + regval |= pll->post_div_config; + writel_relaxed(regval, FABIA_USER_CTL_LO_REG(pll)); + } + + if (pll->slew) { + regval = readl_relaxed(FABIA_USER_CTL_HI_REG(pll)); + regval &= ~PLL_LATCH_INTERFACE; + writel_relaxed(regval, FABIA_USER_CTL_HI_REG(pll)); + } + + if (masks->test_ctl_lo_mask) { + regval = readl_relaxed(FABIA_TEST_CTL_LO_REG(pll)); + regval &= ~masks->test_ctl_lo_mask; + regval |= pll->test_ctl_lo_val; + writel_relaxed(regval, FABIA_TEST_CTL_LO_REG(pll)); + } + + if (masks->test_ctl_hi_mask) { + regval = readl_relaxed(FABIA_TEST_CTL_HI_REG(pll)); + regval &= ~masks->test_ctl_hi_mask; + regval |= pll->test_ctl_hi_val; + writel_relaxed(regval, FABIA_TEST_CTL_HI_REG(pll)); + } + + if (pll->fsm_en_mask) + __set_fsm_mode(MODE_REG(pll)); + + pll->inited = true; +} + +static enum handoff fabia_alpha_pll_handoff(struct clk *c) +{ + struct alpha_pll_clk *pll = to_alpha_pll_clk(c); + u64 a_val; + u32 l_val, regval; + + /* Set the PLL_HW_UPDATE_LOGIC_BYPASS bit before continuing */ + regval = readl_relaxed(MODE_REG(pll)); + regval |= FABIA_PLL_HW_UPDATE_LOGIC_BYPASS; + writel_relaxed(regval, MODE_REG(pll)); + + if (!is_locked(pll)) { + if (c->rate && fabia_alpha_pll_set_rate(c, c->rate)) + WARN(1, "%s: Failed to configure rate\n", c->dbg_name); + __init_alpha_pll(c); + return HANDOFF_DISABLED_CLK; + } else if (pll->fsm_en_mask && !is_fsm_mode(MODE_REG(pll))) { + WARN(1, "%s should be in FSM mode but is not\n", c->dbg_name); + } + + l_val = readl_relaxed(FABIA_L_REG(pll)); + a_val = readl_relaxed(FABIA_FRAC_REG(pll)); + + c->rate = compute_rate(pll, l_val, a_val); + + /* + * Unconditionally vote for the PLL; it might be on because of + * another master's vote. + */ + if (pll->fsm_en_mask) + __alpha_pll_vote_enable(pll); + + return HANDOFF_ENABLED_CLK; +} + +struct clk_ops clk_ops_alpha_pll = { + .enable = alpha_pll_enable, + .disable = alpha_pll_disable, + .round_rate = alpha_pll_round_rate, + .set_rate = alpha_pll_set_rate, + .handoff = alpha_pll_handoff, + .list_registers = alpha_pll_list_registers, +}; + +struct clk_ops clk_ops_alpha_pll_hwfsm = { + .enable = alpha_pll_enable_hwfsm, + .disable = alpha_pll_disable_hwfsm, + .round_rate = alpha_pll_round_rate, + .set_rate = alpha_pll_set_rate, + .handoff = alpha_pll_handoff, + .list_registers = alpha_pll_list_registers, +}; + +struct clk_ops clk_ops_fixed_alpha_pll = { + .enable = alpha_pll_enable, + .disable = alpha_pll_disable, + .handoff = alpha_pll_handoff, + .list_registers = alpha_pll_list_registers, +}; + +struct clk_ops clk_ops_fixed_fabia_alpha_pll = { + .enable = fabia_alpha_pll_enable, + .disable = fabia_alpha_pll_disable, + .handoff = fabia_alpha_pll_handoff, +}; + +struct clk_ops clk_ops_fabia_alpha_pll = { + .enable = fabia_alpha_pll_enable, + .disable = fabia_alpha_pll_disable, + .round_rate = alpha_pll_round_rate, + .set_rate = fabia_alpha_pll_set_rate, + .handoff = fabia_alpha_pll_handoff, +}; + +struct clk_ops clk_ops_dyna_alpha_pll = { + .enable = dyna_alpha_pll_enable, + .disable = dyna_alpha_pll_disable, + .round_rate = alpha_pll_round_rate, + .set_rate = dyna_alpha_pll_set_rate, + .handoff = alpha_pll_handoff, + .list_registers = alpha_pll_list_registers, +}; + +static struct alpha_pll_masks masks_20nm_p = { + .lock_mask = BIT(31), + .active_mask = BIT(30), + .vco_mask = BM(21, 20) >> 20, + .vco_shift = 20, + .alpha_en_mask = BIT(24), + .output_mask = 0xF, + .post_div_mask = 0xF00, +}; + +static struct alpha_pll_vco_tbl vco_20nm_p[] = { + VCO(3, 250000000, 500000000), + VCO(2, 500000000, 1000000000), + VCO(1, 1000000000, 1500000000), + VCO(0, 1500000000, 2000000000), +}; + +static struct alpha_pll_masks masks_20nm_t = { + .lock_mask = BIT(31), + .alpha_en_mask = BIT(24), + .output_mask = 0xf, +}; + +static struct alpha_pll_vco_tbl vco_20nm_t[] = { + VCO(0, 500000000, 1250000000), +}; + +static struct alpha_pll_clk *alpha_pll_dt_parser(struct device *dev, + struct device_node *np) +{ + struct alpha_pll_clk *pll; + struct msmclk_data *drv; + + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + if (of_property_read_u32(np, "qcom,base-offset", &pll->offset)) { + dt_err(np, "missing qcom,base-offset\n"); + return ERR_PTR(-EINVAL); + } + + /* Optional property */ + of_property_read_u32(np, "qcom,post-div-config", + &pll->post_div_config); + + pll->masks = devm_kzalloc(dev, sizeof(*pll->masks), GFP_KERNEL); + if (!pll->masks) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + if (of_device_is_compatible(np, "qcom,fixed-alpha-pll-20p") || + of_device_is_compatible(np, "qcom,alpha-pll-20p")) { + *pll->masks = masks_20nm_p; + pll->vco_tbl = vco_20nm_p; + pll->num_vco = ARRAY_SIZE(vco_20nm_p); + } else if (of_device_is_compatible(np, "qcom,fixed-alpha-pll-20t") || + of_device_is_compatible(np, "qcom,alpha-pll-20t")) { + *pll->masks = masks_20nm_t; + pll->vco_tbl = vco_20nm_t; + pll->num_vco = ARRAY_SIZE(vco_20nm_t); + } else { + dt_err(np, "unexpected compatible string\n"); + return ERR_PTR(-EINVAL); + } + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return ERR_CAST(drv); + pll->base = &drv->base; + return pll; +} + +static void *variable_rate_alpha_pll_dt_parser(struct device *dev, + struct device_node *np) +{ + struct alpha_pll_clk *pll; + + pll = alpha_pll_dt_parser(dev, np); + if (IS_ERR(pll)) + return pll; + + /* Optional Property */ + of_property_read_u32(np, "qcom,output-enable", &pll->enable_config); + + pll->c.ops = &clk_ops_alpha_pll; + return msmclk_generic_clk_init(dev, np, &pll->c); +} + +static void *fixed_rate_alpha_pll_dt_parser(struct device *dev, + struct device_node *np) +{ + struct alpha_pll_clk *pll; + int rc; + u32 val; + + pll = alpha_pll_dt_parser(dev, np); + if (IS_ERR(pll)) + return pll; + + rc = of_property_read_u32(np, "qcom,pll-config-rate", &val); + if (rc) { + dt_err(np, "missing qcom,pll-config-rate\n"); + return ERR_PTR(-EINVAL); + } + pll->c.rate = val; + + rc = of_property_read_u32(np, "qcom,output-enable", + &pll->enable_config); + if (rc) { + dt_err(np, "missing qcom,output-enable\n"); + return ERR_PTR(-EINVAL); + } + + /* Optional Property */ + rc = of_property_read_u32(np, "qcom,fsm-en-bit", &val); + if (!rc) { + rc = of_property_read_u32(np, "qcom,fsm-en-offset", + &pll->fsm_reg_offset); + if (rc) { + dt_err(np, "missing qcom,fsm-en-offset\n"); + return ERR_PTR(-EINVAL); + } + pll->fsm_en_mask = BIT(val); + } + + pll->c.ops = &clk_ops_fixed_alpha_pll; + return msmclk_generic_clk_init(dev, np, &pll->c); +} + +MSMCLK_PARSER(fixed_rate_alpha_pll_dt_parser, "qcom,fixed-alpha-pll-20p", 0); +MSMCLK_PARSER(fixed_rate_alpha_pll_dt_parser, "qcom,fixed-alpha-pll-20t", 1); +MSMCLK_PARSER(variable_rate_alpha_pll_dt_parser, "qcom,alpha-pll-20p", 0); +MSMCLK_PARSER(variable_rate_alpha_pll_dt_parser, "qcom,alpha-pll-20t", 1); diff --git a/drivers/clk/msm/clock-debug.c b/drivers/clk/msm/clock-debug.c new file mode 100644 index 000000000000..d0ff821eb203 --- /dev/null +++ b/drivers/clk/msm/clock-debug.c @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2014, The Linux Foundation. All rights reserved. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/ctype.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/clk.h> +#include <linux/list.h> +#include <linux/clkdev.h> +#include <linux/uaccess.h> +#include <linux/mutex.h> +#include <linux/io.h> +#include <linux/clk/msm-clk-provider.h> +#include <trace/events/power.h> + + +#include "clock.h" + +static LIST_HEAD(clk_list); +static DEFINE_MUTEX(clk_list_lock); + +static struct dentry *debugfs_base; +static u32 debug_suspend; + +static int clock_debug_rate_set(void *data, u64 val) +{ + struct clk *clock = data; + int ret; + + /* Only increases to max rate will succeed, but that's actually good + * for debugging purposes so we don't check for error. */ + if (clock->flags & CLKFLAG_MAX) + clk_set_max_rate(clock, val); + ret = clk_set_rate(clock, val); + if (ret) + pr_err("clk_set_rate(%s, %lu) failed (%d)\n", clock->dbg_name, + (unsigned long)val, ret); + + return ret; +} + +static int clock_debug_rate_get(void *data, u64 *val) +{ + struct clk *clock = data; + *val = clk_get_rate(clock); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get, + clock_debug_rate_set, "%llu\n"); + +static struct clk *measure; + +static int clock_debug_measure_get(void *data, u64 *val) +{ + struct clk *clock = data, *par; + int ret, is_hw_gated; + unsigned long meas_rate, sw_rate; + + /* Check to see if the clock is in hardware gating mode */ + if (clock->ops->in_hwcg_mode) + is_hw_gated = clock->ops->in_hwcg_mode(clock); + else + is_hw_gated = 0; + + ret = clk_set_parent(measure, clock); + if (!ret) { + /* + * Disable hw gating to get accurate rate measurements. Only do + * this if the clock is explictly enabled by software. This + * allows us to detect errors where clocks are on even though + * software is not requesting them to be on due to broken + * hardware gating signals. + */ + if (is_hw_gated && clock->count) + clock->ops->disable_hwcg(clock); + par = measure; + while (par && par != clock) { + if (par->ops->enable) + par->ops->enable(par); + par = par->parent; + } + *val = clk_get_rate(measure); + /* Reenable hwgating if it was disabled */ + if (is_hw_gated && clock->count) + clock->ops->enable_hwcg(clock); + } + + /* + * If there's a divider on the path from the clock output to the + * measurement circuitry, account for it by dividing the original clock + * rate with the rate set on the parent of the measure clock. + */ + meas_rate = clk_get_rate(clock); + sw_rate = clk_get_rate(measure->parent); + if (sw_rate && meas_rate >= (sw_rate * 2)) + *val *= DIV_ROUND_CLOSEST(meas_rate, sw_rate); + + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_measure_fops, clock_debug_measure_get, + NULL, "%lld\n"); + +static int clock_debug_enable_set(void *data, u64 val) +{ + struct clk *clock = data; + int rc = 0; + + if (val) + rc = clk_prepare_enable(clock); + else + clk_disable_unprepare(clock); + + return rc; +} + +static int clock_debug_enable_get(void *data, u64 *val) +{ + struct clk *clock = data; + int enabled; + + if (clock->ops->is_enabled) + enabled = clock->ops->is_enabled(clock); + else + enabled = !!(clock->count); + + *val = enabled; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, + clock_debug_enable_set, "%lld\n"); + +static int clock_debug_local_get(void *data, u64 *val) +{ + struct clk *clock = data; + + if (!clock->ops->is_local) + *val = true; + else + *val = clock->ops->is_local(clock); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_local_fops, clock_debug_local_get, + NULL, "%llu\n"); + +static int clock_debug_hwcg_get(void *data, u64 *val) +{ + struct clk *clock = data; + if (clock->ops->in_hwcg_mode) + *val = !!clock->ops->in_hwcg_mode(clock); + else + *val = 0; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_hwcg_fops, clock_debug_hwcg_get, + NULL, "%llu\n"); + +static void clock_print_fmax_by_level(struct seq_file *m, int level) +{ + struct clk *clock = m->private; + struct clk_vdd_class *vdd_class = clock->vdd_class; + int off, i, vdd_level, nregs = vdd_class->num_regulators; + + vdd_level = find_vdd_level(clock, clock->rate); + + seq_printf(m, "%2s%10lu", vdd_level == level ? "[" : "", + clock->fmax[level]); + for (i = 0; i < nregs; i++) { + off = nregs*level + i; + if (vdd_class->vdd_uv) + seq_printf(m, "%10u", vdd_class->vdd_uv[off]); + if (vdd_class->vdd_ua) + seq_printf(m, "%10u", vdd_class->vdd_ua[off]); + } + + if (vdd_level == level) + seq_puts(m, "]"); + seq_puts(m, "\n"); +} + +static int fmax_rates_show(struct seq_file *m, void *unused) +{ + struct clk *clock = m->private; + struct clk_vdd_class *vdd_class = clock->vdd_class; + int level = 0, i, nregs = vdd_class->num_regulators; + char reg_name[10]; + + int vdd_level = find_vdd_level(clock, clock->rate); + if (vdd_level < 0) { + seq_printf(m, "could not find_vdd_level for %s, %ld\n", + clock->dbg_name, clock->rate); + return 0; + } + + seq_printf(m, "%12s", ""); + for (i = 0; i < nregs; i++) { + snprintf(reg_name, ARRAY_SIZE(reg_name), "reg %d", i); + seq_printf(m, "%10s", reg_name); + if (vdd_class->vdd_ua) + seq_printf(m, "%10s", ""); + } + + seq_printf(m, "\n%12s", "freq"); + for (i = 0; i < nregs; i++) { + seq_printf(m, "%10s", "uV"); + if (vdd_class->vdd_ua) + seq_printf(m, "%10s", "uA"); + } + seq_printf(m, "\n"); + + for (level = 0; level < clock->num_fmax; level++) + clock_print_fmax_by_level(m, level); + + return 0; +} + +static int fmax_rates_open(struct inode *inode, struct file *file) +{ + return single_open(file, fmax_rates_show, inode->i_private); +} + +static const struct file_operations fmax_rates_fops = { + .open = fmax_rates_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int orphan_list_show(struct seq_file *m, void *unused) +{ + struct clk *c, *safe; + + list_for_each_entry_safe(c, safe, &orphan_clk_list, list) + seq_printf(m, "%s\n", c->dbg_name); + + return 0; +} + +static int orphan_list_open(struct inode *inode, struct file *file) +{ + return single_open(file, orphan_list_show, inode->i_private); +} + +static const struct file_operations orphan_list_fops = { + .open = orphan_list_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +#define clock_debug_output(m, c, fmt, ...) \ +do { \ + if (m) \ + seq_printf(m, fmt, ##__VA_ARGS__); \ + else if (c) \ + pr_cont(fmt, ##__VA_ARGS__); \ + else \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) + +static int clock_debug_print_clock(struct clk *c, struct seq_file *m) +{ + char *start = ""; + + if (!c || !c->prepare_count) + return 0; + + clock_debug_output(m, 0, "\t"); + do { + if (c->vdd_class) + clock_debug_output(m, 1, "%s%s:%u:%u [%ld, %d]", start, + c->dbg_name, c->prepare_count, c->count, + c->rate, find_vdd_level(c, c->rate)); + else + clock_debug_output(m, 1, "%s%s:%u:%u [%ld]", start, + c->dbg_name, c->prepare_count, c->count, + c->rate); + start = " -> "; + } while ((c = clk_get_parent(c))); + + clock_debug_output(m, 1, "\n"); + + return 1; +} + +/** + * clock_debug_print_enabled_clocks() - Print names of enabled clocks + * + */ +static void clock_debug_print_enabled_clocks(struct seq_file *m) +{ + struct clk *c; + int cnt = 0; + + if (!mutex_trylock(&clk_list_lock)) { + pr_err("clock-debug: Clocks are being registered. Cannot print clock state now.\n"); + return; + } + clock_debug_output(m, 0, "Enabled clocks:\n"); + list_for_each_entry(c, &clk_list, list) { + cnt += clock_debug_print_clock(c, m); + } + mutex_unlock(&clk_list_lock); + + if (cnt) + clock_debug_output(m, 0, "Enabled clock count: %d\n", cnt); + else + clock_debug_output(m, 0, "No clocks enabled.\n"); +} + +static int enabled_clocks_show(struct seq_file *m, void *unused) +{ + clock_debug_print_enabled_clocks(m); + return 0; +} + +static int enabled_clocks_open(struct inode *inode, struct file *file) +{ + return single_open(file, enabled_clocks_show, inode->i_private); +} + +static const struct file_operations enabled_clocks_fops = { + .open = enabled_clocks_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int trace_clocks_show(struct seq_file *m, void *unused) +{ + struct clk *c; + int total_cnt = 0; + + if (!mutex_trylock(&clk_list_lock)) { + pr_err("trace_clocks: Clocks are being registered. Cannot trace clock state now.\n"); + return 1; + } + list_for_each_entry(c, &clk_list, list) { + trace_clock_state(c->dbg_name, c->prepare_count, c->count, + c->rate); + total_cnt++; + } + mutex_unlock(&clk_list_lock); + clock_debug_output(m, 0, "Total clock count: %d\n", total_cnt); + + return 0; +} + +static int trace_clocks_open(struct inode *inode, struct file *file) +{ + return single_open(file, trace_clocks_show, inode->i_private); +} +static const struct file_operations trace_clocks_fops = { + .open = trace_clocks_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int list_rates_show(struct seq_file *m, void *unused) +{ + struct clk *clock = m->private; + int level, i = 0; + unsigned long rate, fmax = 0; + + /* Find max frequency supported within voltage constraints. */ + if (!clock->vdd_class) { + fmax = ULONG_MAX; + } else { + for (level = 0; level < clock->num_fmax; level++) + if (clock->fmax[level]) + fmax = clock->fmax[level]; + } + + /* + * List supported frequencies <= fmax. Higher frequencies may appear in + * the frequency table, but are not valid and should not be listed. + */ + while (!IS_ERR_VALUE(rate = clock->ops->list_rate(clock, i++))) { + if (rate <= fmax) + seq_printf(m, "%lu\n", rate); + } + + return 0; +} + +static int list_rates_open(struct inode *inode, struct file *file) +{ + return single_open(file, list_rates_show, inode->i_private); +} + +static const struct file_operations list_rates_fops = { + .open = list_rates_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static ssize_t clock_parent_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct clk *clock = filp->private_data; + struct clk *p = clock->parent; + char name[256] = {0}; + + snprintf(name, sizeof(name), "%s\n", p ? p->dbg_name : "None\n"); + + return simple_read_from_buffer(ubuf, cnt, ppos, name, strlen(name)); +} + + +static ssize_t clock_parent_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + struct clk *clock = filp->private_data; + char buf[256]; + char *cmp; + int ret; + struct clk *parent = NULL; + + cnt = min(cnt, sizeof(buf) - 1); + if (copy_from_user(&buf, ubuf, cnt)) + return -EFAULT; + buf[cnt] = '\0'; + cmp = strstrip(buf); + + mutex_lock(&clk_list_lock); + list_for_each_entry(parent, &clk_list, list) { + if (!strcmp(cmp, parent->dbg_name)) + break; + } + + if (&parent->list == &clk_list) { + ret = -EINVAL; + goto err; + } + + mutex_unlock(&clk_list_lock); + ret = clk_set_parent(clock, parent); + if (ret) + return ret; + + return cnt; +err: + mutex_unlock(&clk_list_lock); + return ret; +} + + +static const struct file_operations clock_parent_fops = { + .open = simple_open, + .read = clock_parent_read, + .write = clock_parent_write, +}; + +void clk_debug_print_hw(struct clk *clk, struct seq_file *f) +{ + void __iomem *base; + struct clk_register_data *regs; + u32 i, j, size; + + if (IS_ERR_OR_NULL(clk)) + return; + + clk_debug_print_hw(clk->parent, f); + + clock_debug_output(f, false, "%s\n", clk->dbg_name); + + if (!clk->ops->list_registers) + return; + + j = 0; + base = clk->ops->list_registers(clk, j, ®s, &size); + while (!IS_ERR(base)) { + for (i = 0; i < size; i++) { + u32 val = readl_relaxed(base + regs[i].offset); + clock_debug_output(f, false, "%20s: 0x%.8x\n", + regs[i].name, val); + } + j++; + base = clk->ops->list_registers(clk, j, ®s, &size); + } +} + +static int print_hw_show(struct seq_file *m, void *unused) +{ + struct clk *c = m->private; + clk_debug_print_hw(c, m); + + return 0; +} + +static int print_hw_open(struct inode *inode, struct file *file) +{ + return single_open(file, print_hw_show, inode->i_private); +} + +static const struct file_operations clock_print_hw_fops = { + .open = print_hw_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + + +static void clock_measure_add(struct clk *clock) +{ + if (IS_ERR_OR_NULL(measure)) + return; + + if (clk_set_parent(measure, clock)) + return; + + debugfs_create_file("measure", S_IRUGO, clock->clk_dir, clock, + &clock_measure_fops); +} + +static int clock_debug_add(struct clk *clock) +{ + char temp[50], *ptr; + struct dentry *clk_dir; + + if (!debugfs_base) + return -ENOMEM; + + strlcpy(temp, clock->dbg_name, ARRAY_SIZE(temp)); + for (ptr = temp; *ptr; ptr++) + *ptr = tolower(*ptr); + + clk_dir = debugfs_create_dir(temp, debugfs_base); + if (!clk_dir) + return -ENOMEM; + + clock->clk_dir = clk_dir; + + if (!debugfs_create_file("rate", S_IRUGO | S_IWUSR, clk_dir, + clock, &clock_rate_fops)) + goto error; + + if (!debugfs_create_file("enable", S_IRUGO | S_IWUSR, clk_dir, + clock, &clock_enable_fops)) + goto error; + + if (!debugfs_create_file("is_local", S_IRUGO, clk_dir, clock, + &clock_local_fops)) + goto error; + + if (!debugfs_create_file("has_hw_gating", S_IRUGO, clk_dir, clock, + &clock_hwcg_fops)) + goto error; + + if (clock->ops->list_rate) + if (!debugfs_create_file("list_rates", + S_IRUGO, clk_dir, clock, &list_rates_fops)) + goto error; + + if (clock->vdd_class && !debugfs_create_file("fmax_rates", + S_IRUGO, clk_dir, clock, &fmax_rates_fops)) + goto error; + + if (!debugfs_create_file("parent", S_IRUGO, clk_dir, clock, + &clock_parent_fops)) + goto error; + + if (!debugfs_create_file("print", S_IRUGO, clk_dir, clock, + &clock_print_hw_fops)) + goto error; + + clock_measure_add(clock); + + return 0; +error: + debugfs_remove_recursive(clk_dir); + return -ENOMEM; +} +static DEFINE_MUTEX(clk_debug_lock); +static int clk_debug_init_once; + +/** + * clock_debug_init() - Initialize clock debugfs + * Lock clk_debug_lock before invoking this function. + */ +static int clock_debug_init(void) +{ + if (clk_debug_init_once) + return 0; + + clk_debug_init_once = 1; + + debugfs_base = debugfs_create_dir("clk", NULL); + if (!debugfs_base) + return -ENOMEM; + + if (!debugfs_create_u32("debug_suspend", S_IRUGO | S_IWUSR, + debugfs_base, &debug_suspend)) { + debugfs_remove_recursive(debugfs_base); + return -ENOMEM; + } + + if (!debugfs_create_file("enabled_clocks", S_IRUGO, debugfs_base, NULL, + &enabled_clocks_fops)) + return -ENOMEM; + + if (!debugfs_create_file("orphan_list", S_IRUGO, debugfs_base, NULL, + &orphan_list_fops)) + return -ENOMEM; + + if (!debugfs_create_file("trace_clocks", S_IRUGO, debugfs_base, NULL, + &trace_clocks_fops)) + return -ENOMEM; + + return 0; +} + +/** + * clock_debug_register() - Add additional clocks to clock debugfs hierarchy + * @list: List of clocks to create debugfs nodes for + */ +int clock_debug_register(struct clk *clk) +{ + int ret = 0; + struct clk *c; + + mutex_lock(&clk_list_lock); + if (!list_empty(&clk->list)) + goto out; + + ret = clock_debug_init(); + if (ret) + goto out; + + if (IS_ERR_OR_NULL(measure)) { + if (clk->flags & CLKFLAG_MEASURE) + measure = clk; + if (!IS_ERR_OR_NULL(measure)) { + list_for_each_entry(c, &clk_list, list) + clock_measure_add(c); + } + } + + list_add_tail(&clk->list, &clk_list); + clock_debug_add(clk); +out: + mutex_unlock(&clk_list_lock); + return ret; +} + +/* + * Print the names of enabled clocks and their parents if debug_suspend is set + */ +void clock_debug_print_enabled(void) +{ + if (likely(!debug_suspend)) + return; + + clock_debug_print_enabled_clocks(NULL); +} diff --git a/drivers/clk/msm/clock-dummy.c b/drivers/clk/msm/clock-dummy.c new file mode 100644 index 000000000000..e5339b110cd6 --- /dev/null +++ b/drivers/clk/msm/clock-dummy.c @@ -0,0 +1,113 @@ +/* Copyright (c) 2011,2013-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#include <linux/clk/msm-clk-provider.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <soc/qcom/msm-clock-controller.h> + +static int dummy_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return 0; +} + +static int dummy_clk_set_rate(struct clk *clk, unsigned long rate) +{ + clk->rate = rate; + return 0; +} + +static int dummy_clk_set_max_rate(struct clk *clk, unsigned long rate) +{ + return 0; +} + +static int dummy_clk_set_flags(struct clk *clk, unsigned flags) +{ + return 0; +} + +static unsigned long dummy_clk_get_rate(struct clk *clk) +{ + return clk->rate; +} + +static long dummy_clk_round_rate(struct clk *clk, unsigned long rate) +{ + return rate; +} + +struct clk_ops clk_ops_dummy = { + .reset = dummy_clk_reset, + .set_rate = dummy_clk_set_rate, + .set_max_rate = dummy_clk_set_max_rate, + .set_flags = dummy_clk_set_flags, + .get_rate = dummy_clk_get_rate, + .round_rate = dummy_clk_round_rate, +}; + +struct clk dummy_clk = { + .dbg_name = "dummy_clk", + .ops = &clk_ops_dummy, + CLK_INIT(dummy_clk), +}; + +static void *dummy_clk_dt_parser(struct device *dev, struct device_node *np) +{ + struct clk *c; + c = devm_kzalloc(dev, sizeof(*c), GFP_KERNEL); + if (!c) { + dev_err(dev, "failed to map memory for %s\n", np->name); + return ERR_PTR(-ENOMEM); + } + c->ops = &clk_ops_dummy; + return msmclk_generic_clk_init(dev, np, c); +} +MSMCLK_PARSER(dummy_clk_dt_parser, "qcom,dummy-clk", 0); + +static struct clk *of_dummy_get(struct of_phandle_args *clkspec, + void *data) +{ + return &dummy_clk; +} + +static struct of_device_id msm_clock_dummy_match_table[] = { + { .compatible = "qcom,dummycc" }, + {} +}; + +static int msm_clock_dummy_probe(struct platform_device *pdev) +{ + int ret; + + ret = of_clk_add_provider(pdev->dev.of_node, of_dummy_get, NULL); + if (ret) + return -ENOMEM; + + dev_info(&pdev->dev, "Registered DUMMY provider.\n"); + return ret; +} + +static struct platform_driver msm_clock_dummy_driver = { + .probe = msm_clock_dummy_probe, + .driver = { + .name = "clock-dummy", + .of_match_table = msm_clock_dummy_match_table, + .owner = THIS_MODULE, + }, +}; + +int __init msm_dummy_clk_init(void) +{ + return platform_driver_register(&msm_clock_dummy_driver); +} +arch_initcall(msm_dummy_clk_init); diff --git a/drivers/clk/msm/clock-generic.c b/drivers/clk/msm/clock-generic.c new file mode 100644 index 000000000000..dbe378407885 --- /dev/null +++ b/drivers/clk/msm/clock-generic.c @@ -0,0 +1,909 @@ +/* + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/clk/msm-clk-provider.h> +#include <linux/clk/msm-clock-generic.h> +#include <soc/qcom/msm-clock-controller.h> + +/* ==================== Mux clock ==================== */ + +static int mux_parent_to_src_sel(struct mux_clk *mux, struct clk *p) +{ + return parent_to_src_sel(mux->parents, mux->num_parents, p); +} + +static int mux_set_parent(struct clk *c, struct clk *p) +{ + struct mux_clk *mux = to_mux_clk(c); + int sel = mux_parent_to_src_sel(mux, p); + struct clk *old_parent; + int rc = 0, i; + unsigned long flags; + + if (sel < 0 && mux->rec_parents) { + for (i = 0; i < mux->num_rec_parents; i++) { + rc = clk_set_parent(mux->rec_parents[i], p); + if (!rc) { + /* + * This is necessary to ensure prepare/enable + * counts get propagated correctly. + */ + p = mux->rec_parents[i]; + sel = mux_parent_to_src_sel(mux, p); + break; + } + } + } + + if (sel < 0) + return sel; + + rc = __clk_pre_reparent(c, p, &flags); + if (rc) + goto out; + + rc = mux->ops->set_mux_sel(mux, sel); + if (rc) + goto set_fail; + + old_parent = c->parent; + c->parent = p; + c->rate = clk_get_rate(p); + __clk_post_reparent(c, old_parent, &flags); + + return 0; + +set_fail: + __clk_post_reparent(c, p, &flags); +out: + return rc; +} + +static long mux_round_rate(struct clk *c, unsigned long rate) +{ + struct mux_clk *mux = to_mux_clk(c); + int i; + unsigned long prate, rrate = 0; + + for (i = 0; i < mux->num_parents; i++) { + prate = clk_round_rate(mux->parents[i].src, rate); + if (is_better_rate(rate, rrate, prate)) + rrate = prate; + } + if (!rrate) + return -EINVAL; + + return rrate; +} + +static int mux_set_rate(struct clk *c, unsigned long rate) +{ + struct mux_clk *mux = to_mux_clk(c); + struct clk *new_parent = NULL; + int rc = 0, i; + unsigned long new_par_curr_rate; + unsigned long flags; + + /* + * Check if one of the possible parents is already at the requested + * rate. + */ + for (i = 0; i < mux->num_parents && mux->try_get_rate; i++) { + struct clk *p = mux->parents[i].src; + if (p->rate == rate && clk_round_rate(p, rate) == rate) { + new_parent = mux->parents[i].src; + break; + } + } + + for (i = 0; i < mux->num_parents && !(!i && new_parent); i++) { + if (clk_round_rate(mux->parents[i].src, rate) == rate) { + new_parent = mux->parents[i].src; + if (!mux->try_new_parent) + break; + if (mux->try_new_parent && new_parent != c->parent) + break; + } + } + + if (new_parent == NULL) + return -EINVAL; + + /* + * Switch to safe parent since the old and new parent might be the + * same and the parent might temporarily turn off while switching + * rates. If the mux can switch between distinct sources safely + * (indicated by try_new_parent), and the new source is not the current + * parent, do not switch to the safe parent. + */ + if (mux->safe_sel >= 0 && + !(mux->try_new_parent && (new_parent != c->parent))) { + /* + * The safe parent might be a clock with multiple sources; + * to select the "safe" source, set a safe frequency. + */ + if (mux->safe_freq) { + rc = clk_set_rate(mux->safe_parent, mux->safe_freq); + if (rc) { + pr_err("Failed to set safe rate on %s\n", + clk_name(mux->safe_parent)); + return rc; + } + } + + /* + * Some mux implementations might switch to/from a low power + * parent as part of their disable/enable ops. Grab the + * enable lock to avoid racing with these implementations. + */ + spin_lock_irqsave(&c->lock, flags); + rc = mux->ops->set_mux_sel(mux, mux->safe_sel); + spin_unlock_irqrestore(&c->lock, flags); + if (rc) + return rc; + + } + + new_par_curr_rate = clk_get_rate(new_parent); + rc = clk_set_rate(new_parent, rate); + if (rc) + goto set_rate_fail; + + rc = mux_set_parent(c, new_parent); + if (rc) + goto set_par_fail; + + return 0; + +set_par_fail: + clk_set_rate(new_parent, new_par_curr_rate); +set_rate_fail: + WARN(mux->ops->set_mux_sel(mux, + mux_parent_to_src_sel(mux, c->parent)), + "Set rate failed for %s. Also in bad state!\n", c->dbg_name); + return rc; +} + +static int mux_enable(struct clk *c) +{ + struct mux_clk *mux = to_mux_clk(c); + if (mux->ops->enable) + return mux->ops->enable(mux); + return 0; +} + +static void mux_disable(struct clk *c) +{ + struct mux_clk *mux = to_mux_clk(c); + if (mux->ops->disable) + return mux->ops->disable(mux); +} + +static struct clk *mux_get_parent(struct clk *c) +{ + struct mux_clk *mux = to_mux_clk(c); + int sel = mux->ops->get_mux_sel(mux); + int i; + + for (i = 0; i < mux->num_parents; i++) { + if (mux->parents[i].sel == sel) + return mux->parents[i].src; + } + + /* Unfamiliar parent. */ + return NULL; +} + +static enum handoff mux_handoff(struct clk *c) +{ + struct mux_clk *mux = to_mux_clk(c); + + c->rate = clk_get_rate(c->parent); + mux->safe_sel = mux_parent_to_src_sel(mux, mux->safe_parent); + + if (mux->en_mask && mux->ops && mux->ops->is_enabled) + return mux->ops->is_enabled(mux) + ? HANDOFF_ENABLED_CLK + : HANDOFF_DISABLED_CLK; + + /* + * If this function returns 'enabled' even when the clock downstream + * of this clock is disabled, then handoff code will unnecessarily + * enable the current parent of this clock. If this function always + * returns 'disabled' and a clock downstream is on, the clock handoff + * code will bump up the ref count for this clock and its current + * parent as necessary. So, clocks without an actual HW gate can + * always return disabled. + */ + return HANDOFF_DISABLED_CLK; +} + +static void __iomem *mux_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct mux_clk *mux = to_mux_clk(c); + + if (mux->ops && mux->ops->list_registers) + return mux->ops->list_registers(mux, n, regs, size); + + return ERR_PTR(-EINVAL); +} + +struct clk_ops clk_ops_gen_mux = { + .enable = mux_enable, + .disable = mux_disable, + .set_parent = mux_set_parent, + .round_rate = mux_round_rate, + .set_rate = mux_set_rate, + .handoff = mux_handoff, + .get_parent = mux_get_parent, + .list_registers = mux_clk_list_registers, +}; + +/* ==================== Divider clock ==================== */ + +static long __div_round_rate(struct div_data *data, unsigned long rate, + struct clk *parent, unsigned int *best_div, unsigned long *best_prate) +{ + unsigned int div, min_div, max_div, _best_div = 1; + unsigned long prate, _best_prate = 0, rrate = 0, req_prate, actual_rate; + unsigned int numer; + + rate = max(rate, 1UL); + + min_div = max(data->min_div, 1U); + max_div = min(data->max_div, (unsigned int) (ULONG_MAX / rate)); + + /* + * div values are doubled for half dividers. + * Adjust for that by picking a numer of 2. + */ + numer = data->is_half_divider ? 2 : 1; + + for (div = min_div; div <= max_div; div++) { + if (data->skip_odd_div && (div & 1)) + if (!(data->allow_div_one && (div == 1))) + continue; + if (data->skip_even_div && !(div & 1)) + continue; + req_prate = mult_frac(rate, div, numer); + prate = clk_round_rate(parent, req_prate); + if (IS_ERR_VALUE(prate)) + break; + + actual_rate = mult_frac(prate, numer, div); + if (is_better_rate(rate, rrate, actual_rate)) { + rrate = actual_rate; + _best_div = div; + _best_prate = prate; + } + + /* + * Trying higher dividers is only going to ask the parent for + * a higher rate. If it can't even output a rate higher than + * the one we request for this divider, the parent is not + * going to be able to output an even higher rate required + * for a higher divider. So, stop trying higher dividers. + */ + if (actual_rate < rate) + break; + + if (rrate <= rate + data->rate_margin) + break; + } + + if (!rrate) + return -EINVAL; + if (best_div) + *best_div = _best_div; + if (best_prate) + *best_prate = _best_prate; + + return rrate; +} + +static long div_round_rate(struct clk *c, unsigned long rate) +{ + struct div_clk *d = to_div_clk(c); + + return __div_round_rate(&d->data, rate, c->parent, NULL, NULL); +} + +static int _find_safe_div(struct clk *c, unsigned long rate) +{ + struct div_clk *d = to_div_clk(c); + struct div_data *data = &d->data; + unsigned long fast = max(rate, c->rate); + unsigned int numer = data->is_half_divider ? 2 : 1; + int i, safe_div = 0; + + if (!d->safe_freq) + return 0; + + /* Find the max safe freq that is lesser than fast */ + for (i = data->max_div; i >= data->min_div; i--) + if (mult_frac(d->safe_freq, numer, i) <= fast) + safe_div = i; + + return safe_div ?: -EINVAL; +} + +static int div_set_rate(struct clk *c, unsigned long rate) +{ + struct div_clk *d = to_div_clk(c); + int safe_div, div, rc = 0; + long rrate, old_prate, new_prate; + struct div_data *data = &d->data; + + rrate = __div_round_rate(data, rate, c->parent, &div, &new_prate); + if (rrate < rate || rrate > rate + data->rate_margin) + return -EINVAL; + + /* + * For fixed divider clock we don't want to return an error if the + * requested rate matches the achievable rate. So, don't check for + * !d->ops and return an error. __div_round_rate() ensures div == + * d->div if !d->ops. + */ + + safe_div = _find_safe_div(c, rate); + if (d->safe_freq && safe_div < 0) { + pr_err("No safe div on %s for transitioning from %lu to %lu\n", + c->dbg_name, c->rate, rate); + return -EINVAL; + } + + safe_div = max(safe_div, div); + + if (safe_div > data->div) { + rc = d->ops->set_div(d, safe_div); + if (rc) { + pr_err("Failed to set div %d on %s\n", safe_div, + c->dbg_name); + return rc; + } + } + + old_prate = clk_get_rate(c->parent); + rc = clk_set_rate(c->parent, new_prate); + if (rc) + goto set_rate_fail; + + if (div < data->div) + rc = d->ops->set_div(d, div); + else if (div < safe_div) + rc = d->ops->set_div(d, div); + if (rc) + goto div_dec_fail; + + data->div = div; + + return 0; + +div_dec_fail: + WARN(clk_set_rate(c->parent, old_prate), + "Set rate failed for %s. Also in bad state!\n", c->dbg_name); +set_rate_fail: + if (safe_div > data->div) + WARN(d->ops->set_div(d, data->div), + "Set rate failed for %s. Also in bad state!\n", + c->dbg_name); + return rc; +} + +static int div_enable(struct clk *c) +{ + struct div_clk *d = to_div_clk(c); + if (d->ops && d->ops->enable) + return d->ops->enable(d); + return 0; +} + +static void div_disable(struct clk *c) +{ + struct div_clk *d = to_div_clk(c); + if (d->ops && d->ops->disable) + return d->ops->disable(d); +} + +static enum handoff div_handoff(struct clk *c) +{ + struct div_clk *d = to_div_clk(c); + unsigned int div = d->data.div; + + if (d->ops && d->ops->get_div) + div = max(d->ops->get_div(d), 1); + div = max(div, 1U); + c->rate = clk_get_rate(c->parent) / div; + + if (!d->ops || !d->ops->set_div) + d->data.min_div = d->data.max_div = div; + d->data.div = div; + + if (d->en_mask && d->ops && d->ops->is_enabled) + return d->ops->is_enabled(d) + ? HANDOFF_ENABLED_CLK + : HANDOFF_DISABLED_CLK; + + /* + * If this function returns 'enabled' even when the clock downstream + * of this clock is disabled, then handoff code will unnecessarily + * enable the current parent of this clock. If this function always + * returns 'disabled' and a clock downstream is on, the clock handoff + * code will bump up the ref count for this clock and its current + * parent as necessary. So, clocks without an actual HW gate can + * always return disabled. + */ + return HANDOFF_DISABLED_CLK; +} + +static void __iomem *div_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct div_clk *d = to_div_clk(c); + + if (d->ops && d->ops->list_registers) + return d->ops->list_registers(d, n, regs, size); + + return ERR_PTR(-EINVAL); +} + +struct clk_ops clk_ops_div = { + .enable = div_enable, + .disable = div_disable, + .round_rate = div_round_rate, + .set_rate = div_set_rate, + .handoff = div_handoff, + .list_registers = div_clk_list_registers, +}; + +static long __slave_div_round_rate(struct clk *c, unsigned long rate, + int *best_div) +{ + struct div_clk *d = to_div_clk(c); + unsigned int div, min_div, max_div; + long p_rate; + + rate = max(rate, 1UL); + + min_div = d->data.min_div; + max_div = d->data.max_div; + + p_rate = clk_get_rate(c->parent); + div = DIV_ROUND_CLOSEST(p_rate, rate); + div = max(div, min_div); + div = min(div, max_div); + if (best_div) + *best_div = div; + + return p_rate / div; +} + +static long slave_div_round_rate(struct clk *c, unsigned long rate) +{ + return __slave_div_round_rate(c, rate, NULL); +} + +static int slave_div_set_rate(struct clk *c, unsigned long rate) +{ + struct div_clk *d = to_div_clk(c); + int div, rc = 0; + long rrate; + + rrate = __slave_div_round_rate(c, rate, &div); + if (rrate != rate) + return -EINVAL; + + if (div == d->data.div) + return 0; + + /* + * For fixed divider clock we don't want to return an error if the + * requested rate matches the achievable rate. So, don't check for + * !d->ops and return an error. __slave_div_round_rate() ensures + * div == d->data.div if !d->ops. + */ + rc = d->ops->set_div(d, div); + if (rc) + return rc; + + d->data.div = div; + + return 0; +} + +static unsigned long slave_div_get_rate(struct clk *c) +{ + struct div_clk *d = to_div_clk(c); + if (!d->data.div) + return 0; + return clk_get_rate(c->parent) / d->data.div; +} + +struct clk_ops clk_ops_slave_div = { + .enable = div_enable, + .disable = div_disable, + .round_rate = slave_div_round_rate, + .set_rate = slave_div_set_rate, + .get_rate = slave_div_get_rate, + .handoff = div_handoff, + .list_registers = div_clk_list_registers, +}; + + +/** + * External clock + * Some clock controllers have input clock signal that come from outside the + * clock controller. That input clock signal might then be used as a source for + * several clocks inside the clock controller. This external clock + * implementation models this input clock signal by just passing on the requests + * to the clock's parent, the original external clock source. The driver for the + * clock controller should clk_get() the original external clock in the probe + * function and set is as a parent to this external clock.. + */ + +long parent_round_rate(struct clk *c, unsigned long rate) +{ + return clk_round_rate(c->parent, rate); +} + +int parent_set_rate(struct clk *c, unsigned long rate) +{ + return clk_set_rate(c->parent, rate); +} + +unsigned long parent_get_rate(struct clk *c) +{ + return clk_get_rate(c->parent); +} + +static int ext_set_parent(struct clk *c, struct clk *p) +{ + return clk_set_parent(c->parent, p); +} + +static struct clk *ext_get_parent(struct clk *c) +{ + struct ext_clk *ext = to_ext_clk(c); + + if (!IS_ERR_OR_NULL(c->parent)) + return c->parent; + return clk_get(ext->dev, ext->clk_id); +} + +static enum handoff ext_handoff(struct clk *c) +{ + c->rate = clk_get_rate(c->parent); + /* Similar reasoning applied in div_handoff, see comment there. */ + return HANDOFF_DISABLED_CLK; +} + +struct clk_ops clk_ops_ext = { + .handoff = ext_handoff, + .round_rate = parent_round_rate, + .set_rate = parent_set_rate, + .get_rate = parent_get_rate, + .set_parent = ext_set_parent, + .get_parent = ext_get_parent, +}; + +static void *ext_clk_dt_parser(struct device *dev, struct device_node *np) +{ + struct ext_clk *ext; + const char *str; + int rc; + + ext = devm_kzalloc(dev, sizeof(*ext), GFP_KERNEL); + if (!ext) { + dev_err(dev, "memory allocation failure\n"); + return ERR_PTR(-ENOMEM); + } + + ext->dev = dev; + rc = of_property_read_string(np, "qcom,clock-names", &str); + if (!rc) + ext->clk_id = (void *)str; + + ext->c.ops = &clk_ops_ext; + return msmclk_generic_clk_init(dev, np, &ext->c); +} +MSMCLK_PARSER(ext_clk_dt_parser, "qcom,ext-clk", 0); + +/* ==================== Mux_div clock ==================== */ + +static int mux_div_clk_enable(struct clk *c) +{ + struct mux_div_clk *md = to_mux_div_clk(c); + + if (md->ops->enable) + return md->ops->enable(md); + return 0; +} + +static void mux_div_clk_disable(struct clk *c) +{ + struct mux_div_clk *md = to_mux_div_clk(c); + + if (md->ops->disable) + return md->ops->disable(md); +} + +static long __mux_div_round_rate(struct clk *c, unsigned long rate, + struct clk **best_parent, int *best_div, unsigned long *best_prate) +{ + struct mux_div_clk *md = to_mux_div_clk(c); + unsigned int i; + unsigned long rrate, best = 0, _best_div = 0, _best_prate = 0; + struct clk *_best_parent = 0; + + if (md->try_get_rate) { + for (i = 0; i < md->num_parents; i++) { + int divider; + unsigned long p_rate; + + rrate = __div_round_rate(&md->data, rate, + md->parents[i].src, + ÷r, &p_rate); + /* + * Check if one of the possible parents is already at + * the requested rate. + */ + if (p_rate == clk_get_rate(md->parents[i].src) + && rrate == rate) { + best = rrate; + _best_div = divider; + _best_prate = p_rate; + _best_parent = md->parents[i].src; + goto end; + } + } + } + + for (i = 0; i < md->num_parents; i++) { + int div; + unsigned long prate; + + rrate = __div_round_rate(&md->data, rate, md->parents[i].src, + &div, &prate); + + if (is_better_rate(rate, best, rrate)) { + best = rrate; + _best_div = div; + _best_prate = prate; + _best_parent = md->parents[i].src; + } + + if (rate <= rrate && rrate <= rate + md->data.rate_margin) + break; + } +end: + if (best_div) + *best_div = _best_div; + if (best_prate) + *best_prate = _best_prate; + if (best_parent) + *best_parent = _best_parent; + + if (best) + return best; + return -EINVAL; +} + +static long mux_div_clk_round_rate(struct clk *c, unsigned long rate) +{ + return __mux_div_round_rate(c, rate, NULL, NULL, NULL); +} + +/* requires enable lock to be held */ +static int __set_src_div(struct mux_div_clk *md, struct clk *parent, u32 div) +{ + u32 rc = 0, src_sel; + + src_sel = parent_to_src_sel(md->parents, md->num_parents, parent); + /* + * If the clock is disabled, don't change to the new settings until + * the clock is reenabled + */ + if (md->c.count) + rc = md->ops->set_src_div(md, src_sel, div); + if (!rc) { + md->data.div = div; + md->src_sel = src_sel; + } + + return rc; +} + +static int set_src_div(struct mux_div_clk *md, struct clk *parent, u32 div) +{ + unsigned long flags; + u32 rc; + + spin_lock_irqsave(&md->c.lock, flags); + rc = __set_src_div(md, parent, div); + spin_unlock_irqrestore(&md->c.lock, flags); + + return rc; +} + +/* Must be called after handoff to ensure parent clock rates are initialized */ +static int safe_parent_init_once(struct clk *c) +{ + unsigned long rrate; + u32 best_div; + struct clk *best_parent; + struct mux_div_clk *md = to_mux_div_clk(c); + + if (IS_ERR(md->safe_parent)) + return -EINVAL; + if (!md->safe_freq || md->safe_parent) + return 0; + + rrate = __mux_div_round_rate(c, md->safe_freq, &best_parent, + &best_div, NULL); + + if (rrate == md->safe_freq) { + md->safe_div = best_div; + md->safe_parent = best_parent; + } else { + md->safe_parent = ERR_PTR(-EINVAL); + return -EINVAL; + } + return 0; +} + +static int mux_div_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct mux_div_clk *md = to_mux_div_clk(c); + unsigned long flags, rrate; + unsigned long new_prate, new_parent_orig_rate; + struct clk *old_parent, *new_parent; + u32 new_div, old_div; + int rc; + + rc = safe_parent_init_once(c); + if (rc) + return rc; + + rrate = __mux_div_round_rate(c, rate, &new_parent, &new_div, + &new_prate); + if (rrate < rate || rrate > rate + md->data.rate_margin) + return -EINVAL; + + old_parent = c->parent; + old_div = md->data.div; + + /* Refer to the description of safe_freq in clock-generic.h */ + if (md->safe_freq) + rc = set_src_div(md, md->safe_parent, md->safe_div); + + else if (new_parent == old_parent && new_div >= old_div) { + /* + * If both the parent_rate and divider changes, there may be an + * intermediate frequency generated. Ensure this intermediate + * frequency is less than both the new rate and previous rate. + */ + rc = set_src_div(md, old_parent, new_div); + } + if (rc) + return rc; + + new_parent_orig_rate = clk_get_rate(new_parent); + rc = clk_set_rate(new_parent, new_prate); + if (rc) { + pr_err("failed to set %s to %ld\n", + clk_name(new_parent), new_prate); + goto err_set_rate; + } + + rc = __clk_pre_reparent(c, new_parent, &flags); + if (rc) + goto err_pre_reparent; + + /* Set divider and mux src atomically */ + rc = __set_src_div(md, new_parent, new_div); + if (rc) + goto err_set_src_div; + + c->parent = new_parent; + + __clk_post_reparent(c, old_parent, &flags); + return 0; + +err_set_src_div: + /* Not switching to new_parent, so disable it */ + __clk_post_reparent(c, new_parent, &flags); +err_pre_reparent: + rc = clk_set_rate(new_parent, new_parent_orig_rate); + WARN(rc, "%s: error changing new_parent (%s) rate back to %ld\n", + clk_name(c), clk_name(new_parent), new_parent_orig_rate); +err_set_rate: + rc = set_src_div(md, old_parent, old_div); + WARN(rc, "%s: error changing back to original div (%d) and parent (%s)\n", + clk_name(c), old_div, clk_name(old_parent)); + + return rc; +} + +static struct clk *mux_div_clk_get_parent(struct clk *c) +{ + struct mux_div_clk *md = to_mux_div_clk(c); + u32 i, div, src_sel; + + md->ops->get_src_div(md, &src_sel, &div); + + md->data.div = div; + md->src_sel = src_sel; + + for (i = 0; i < md->num_parents; i++) { + if (md->parents[i].sel == src_sel) + return md->parents[i].src; + } + + return NULL; +} + +static enum handoff mux_div_clk_handoff(struct clk *c) +{ + struct mux_div_clk *md = to_mux_div_clk(c); + unsigned long parent_rate; + unsigned int numer; + + parent_rate = clk_get_rate(c->parent); + if (!parent_rate) + return HANDOFF_DISABLED_CLK; + /* + * div values are doubled for half dividers. + * Adjust for that by picking a numer of 2. + */ + numer = md->data.is_half_divider ? 2 : 1; + + if (md->data.div) { + c->rate = mult_frac(parent_rate, numer, md->data.div); + } else { + c->rate = 0; + return HANDOFF_DISABLED_CLK; + } + + if (!md->ops->is_enabled) + return HANDOFF_DISABLED_CLK; + if (md->ops->is_enabled(md)) + return HANDOFF_ENABLED_CLK; + return HANDOFF_DISABLED_CLK; +} + +static void __iomem *mux_div_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct mux_div_clk *md = to_mux_div_clk(c); + + if (md->ops && md->ops->list_registers) + return md->ops->list_registers(md, n , regs, size); + + return ERR_PTR(-EINVAL); +} + +struct clk_ops clk_ops_mux_div_clk = { + .enable = mux_div_clk_enable, + .disable = mux_div_clk_disable, + .set_rate = mux_div_clk_set_rate, + .round_rate = mux_div_clk_round_rate, + .get_parent = mux_div_clk_get_parent, + .handoff = mux_div_clk_handoff, + .list_registers = mux_div_clk_list_registers, +}; diff --git a/drivers/clk/msm/clock-local2.c b/drivers/clk/msm/clock-local2.c new file mode 100644 index 000000000000..6744330b8657 --- /dev/null +++ b/drivers/clk/msm/clock-local2.c @@ -0,0 +1,2682 @@ +/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/ctype.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk-provider.h> +#include <linux/clk/msm-clk.h> +#include <linux/clk/msm-clock-generic.h> +#include <soc/qcom/clock-local2.h> +#include <soc/qcom/msm-clock-controller.h> + +/* + * When enabling/disabling a clock, check the halt bit up to this number + * number of times (with a 1 us delay in between) before continuing. + */ +#define HALT_CHECK_MAX_LOOPS 500 +/* For clock without halt checking, wait this long after enables/disables. */ +#define HALT_CHECK_DELAY_US 500 + +/* + * When updating an RCG configuration, check the update bit up to this number + * number of times (with a 1 us delay in between) before continuing. + */ +#define UPDATE_CHECK_MAX_LOOPS 500 + +DEFINE_SPINLOCK(local_clock_reg_lock); +struct clk_freq_tbl rcg_dummy_freq = F_END; + +#define CMD_RCGR_REG(x) (*(x)->base + (x)->cmd_rcgr_reg) +#define CFG_RCGR_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0x4) +#define M_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0x8) +#define N_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0xC) +#define D_REG(x) (*(x)->base + (x)->cmd_rcgr_reg + 0x10) +#define CBCR_REG(x) (*(x)->base + (x)->cbcr_reg) +#define BCR_REG(x) (*(x)->base + (x)->bcr_reg) +#define RST_REG(x) (*(x)->base + (x)->reset_reg) +#define VOTE_REG(x) (*(x)->base + (x)->vote_reg) +#define GATE_EN_REG(x) (*(x)->base + (x)->en_reg) +#define DIV_REG(x) (*(x)->base + (x)->offset) +#define MUX_REG(x) (*(x)->base + (x)->offset) + +/* + * Important clock bit positions and masks + */ +#define CMD_RCGR_ROOT_ENABLE_BIT BIT(1) +#define CBCR_BRANCH_ENABLE_BIT BIT(0) +#define CBCR_BRANCH_OFF_BIT BIT(31) +#define CMD_RCGR_CONFIG_UPDATE_BIT BIT(0) +#define CMD_RCGR_ROOT_STATUS_BIT BIT(31) +#define BCR_BLK_ARES_BIT BIT(0) +#define CBCR_HW_CTL_BIT BIT(1) +#define CFG_RCGR_DIV_MASK BM(4, 0) +#define CFG_RCGR_SRC_SEL_MASK BM(10, 8) +#define MND_MODE_MASK BM(13, 12) +#define MND_DUAL_EDGE_MODE_BVAL BVAL(13, 12, 0x2) +#define CMD_RCGR_CONFIG_DIRTY_MASK BM(7, 4) +#define CBCR_CDIV_LSB 16 +#define CBCR_CDIV_MSB 19 + +enum branch_state { + BRANCH_ON, + BRANCH_OFF, +}; + +static struct clk_freq_tbl cxo_f = { + .freq_hz = 19200000, + .m_val = 0, + .n_val = 0, + .d_val = 0, + .div_src_val = 0, +}; + +struct div_map { + u32 mask; + int div; +}; + +/* + * RCG functions + */ + +/* + * Update an RCG with a new configuration. This may include a new M, N, or D + * value, source selection or pre-divider value. + * + */ +static void rcg_update_config(struct rcg_clk *rcg) +{ + u32 cmd_rcgr_regval, count; + + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + cmd_rcgr_regval |= CMD_RCGR_CONFIG_UPDATE_BIT; + writel_relaxed(cmd_rcgr_regval, CMD_RCGR_REG(rcg)); + + /* Wait for update to take effect */ + for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) { + if (!(readl_relaxed(CMD_RCGR_REG(rcg)) & + CMD_RCGR_CONFIG_UPDATE_BIT)) + return; + udelay(1); + } + + CLK_WARN(&rcg->c, count == 0, "rcg didn't update its configuration."); +} + +static void rcg_on_check(struct rcg_clk *rcg) +{ + int count; + + /* Wait for RCG to turn on */ + for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) { + if (!(readl_relaxed(CMD_RCGR_REG(rcg)) & + CMD_RCGR_ROOT_STATUS_BIT)) + return; + udelay(1); + } + CLK_WARN(&rcg->c, count == 0, "rcg didn't turn on."); +} + +/* RCG set rate function for clocks with Half Integer Dividers. */ +static void __set_rate_hid(struct rcg_clk *rcg, struct clk_freq_tbl *nf) +{ + u32 cfg_regval; + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + cfg_regval &= ~(CFG_RCGR_DIV_MASK | CFG_RCGR_SRC_SEL_MASK); + cfg_regval |= nf->div_src_val; + writel_relaxed(cfg_regval, CFG_RCGR_REG(rcg)); + + rcg_update_config(rcg); +} + +void set_rate_hid(struct rcg_clk *rcg, struct clk_freq_tbl *nf) +{ + unsigned long flags; + spin_lock_irqsave(&local_clock_reg_lock, flags); + __set_rate_hid(rcg, nf); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +/* RCG set rate function for clocks with MND & Half Integer Dividers. */ +static void __set_rate_mnd(struct rcg_clk *rcg, struct clk_freq_tbl *nf) +{ + u32 cfg_regval; + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + writel_relaxed(nf->m_val, M_REG(rcg)); + writel_relaxed(nf->n_val, N_REG(rcg)); + writel_relaxed(nf->d_val, D_REG(rcg)); + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + cfg_regval &= ~(CFG_RCGR_DIV_MASK | CFG_RCGR_SRC_SEL_MASK); + cfg_regval |= nf->div_src_val; + + /* Activate or disable the M/N:D divider as necessary */ + cfg_regval &= ~MND_MODE_MASK; + if (nf->n_val != 0) + cfg_regval |= MND_DUAL_EDGE_MODE_BVAL; + writel_relaxed(cfg_regval, CFG_RCGR_REG(rcg)); + + rcg_update_config(rcg); +} + +void set_rate_mnd(struct rcg_clk *rcg, struct clk_freq_tbl *nf) +{ + unsigned long flags; + spin_lock_irqsave(&local_clock_reg_lock, flags); + __set_rate_mnd(rcg, nf); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static void rcg_set_force_enable(struct rcg_clk *rcg) +{ + u32 cmd_rcgr_regval; + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + cmd_rcgr_regval |= CMD_RCGR_ROOT_ENABLE_BIT; + writel_relaxed(cmd_rcgr_regval, CMD_RCGR_REG(rcg)); + rcg_on_check(rcg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static void rcg_clear_force_enable(struct rcg_clk *rcg) +{ + u32 cmd_rcgr_regval; + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + cmd_rcgr_regval &= ~CMD_RCGR_ROOT_ENABLE_BIT; + writel_relaxed(cmd_rcgr_regval, CMD_RCGR_REG(rcg)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static int rcg_clk_enable(struct clk *c) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + + WARN(rcg->current_freq == &rcg_dummy_freq, + "Attempting to prepare %s before setting its rate. " + "Set the rate first!\n", rcg->c.dbg_name); + + if (rcg->force_enable_rcgr) { + rcg_set_force_enable(rcg); + return 0; + } + + if (!rcg->non_local_children || rcg->current_freq == &rcg_dummy_freq) + return 0; + /* + * Switch from CXO to saved mux value. Force enable/disable while + * switching. The current parent is already prepared and enabled + * at this point, and the CXO source is always-on. Therefore the + * RCG can safely execute a dynamic switch. + */ + rcg_set_force_enable(rcg); + rcg->set_rate(rcg, rcg->current_freq); + rcg_clear_force_enable(rcg); + + return 0; +} + +static void rcg_clk_disable(struct clk *c) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + + if (rcg->force_enable_rcgr) { + rcg_clear_force_enable(rcg); + return; + } + + if (!rcg->non_local_children) + return; + + /* + * Save mux select and switch to CXO. Force enable/disable while + * switching. The current parent is still prepared and enabled at this + * point, and the CXO source is always-on. Therefore the RCG can safely + * execute a dynamic switch. + */ + rcg_set_force_enable(rcg); + rcg->set_rate(rcg, &cxo_f); + rcg_clear_force_enable(rcg); +} + +static int rcg_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct clk_freq_tbl *cf, *nf; + struct rcg_clk *rcg = to_rcg_clk(c); + int rc; + unsigned long flags; + + for (nf = rcg->freq_tbl; nf->freq_hz != FREQ_END + && nf->freq_hz != rate; nf++) + ; + + if (nf->freq_hz == FREQ_END) + return -EINVAL; + + cf = rcg->current_freq; + if (nf->src_freq != FIXED_CLK_SRC) { + rc = clk_set_rate(nf->src_clk, nf->src_freq); + if (rc) + return rc; + } + + rc = __clk_pre_reparent(c, nf->src_clk, &flags); + if (rc) + return rc; + + BUG_ON(!rcg->set_rate); + + /* + * Perform clock-specific frequency switch operations. + * + * For RCGs with non_local_children set to true: + * If this RCG has at least one branch that is controlled by another + * execution entity, ensure that the enable/disable and mux switch + * are staggered. + */ + if (!rcg->non_local_children) { + rcg->set_rate(rcg, nf); + } else if (c->count) { + /* + * Force enable the RCG here since there could be a disable + * call happening between pre_reparent and set_rate. + */ + rcg_set_force_enable(rcg); + rcg->set_rate(rcg, nf); + rcg_clear_force_enable(rcg); + } + /* + * If non_local_children is set and the RCG is not enabled, + * the following operations switch parent in software and cache + * the frequency. The mux switch will occur when the RCG is enabled. + */ + rcg->current_freq = nf; + c->parent = nf->src_clk; + + __clk_post_reparent(c, cf->src_clk, &flags); + + return 0; +} + +/* + * Return a supported rate that's at least the specified rate or + * the max supported rate if the specified rate is larger than the + * max supported rate. + */ +static long rcg_clk_round_rate(struct clk *c, unsigned long rate) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + struct clk_freq_tbl *f; + + for (f = rcg->freq_tbl; f->freq_hz != FREQ_END; f++) + if (f->freq_hz >= rate) + return f->freq_hz; + + f--; + return f->freq_hz; +} + +/* Return the nth supported frequency for a given clock. */ +static long rcg_clk_list_rate(struct clk *c, unsigned n) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + + if (!rcg->freq_tbl || rcg->freq_tbl->freq_hz == FREQ_END) + return -ENXIO; + + return (rcg->freq_tbl + n)->freq_hz; +} + +static struct clk *_rcg_clk_get_parent(struct rcg_clk *rcg, bool has_mnd, + bool match_rate) +{ + u32 n_regval = 0, m_regval = 0, d_regval = 0; + u32 cfg_regval, div, div_regval; + struct clk_freq_tbl *freq; + u32 cmd_rcgr_regval; + + if (!rcg->freq_tbl) { + WARN(1, "No frequency table present for rcg %s\n", + rcg->c.dbg_name); + return NULL; + } + + /* Is there a pending configuration? */ + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + if (cmd_rcgr_regval & CMD_RCGR_CONFIG_DIRTY_MASK) { + WARN(1, "Pending transaction for rcg %s\n", rcg->c.dbg_name); + return NULL; + } + + /* Get values of m, n, d, div and src_sel registers. */ + if (has_mnd) { + m_regval = readl_relaxed(M_REG(rcg)); + n_regval = readl_relaxed(N_REG(rcg)); + d_regval = readl_relaxed(D_REG(rcg)); + + /* + * The n and d values stored in the frequency tables are sign + * extended to 32 bits. The n and d values in the registers are + * sign extended to 8 or 16 bits. Sign extend the values read + * from the registers so that they can be compared to the + * values in the frequency tables. + */ + n_regval |= (n_regval >> 8) ? BM(31, 16) : BM(31, 8); + d_regval |= (d_regval >> 8) ? BM(31, 16) : BM(31, 8); + } + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + cfg_regval &= CFG_RCGR_SRC_SEL_MASK | CFG_RCGR_DIV_MASK + | MND_MODE_MASK; + + /* If mnd counter is present, check if it's in use. */ + has_mnd = (has_mnd) && + ((cfg_regval & MND_MODE_MASK) == MND_DUAL_EDGE_MODE_BVAL); + + /* + * Clear out the mn counter mode bits since we now want to compare only + * the source mux selection and pre-divider values in the registers. + */ + cfg_regval &= ~MND_MODE_MASK; + + /* Figure out what rate the rcg is running at */ + for (freq = rcg->freq_tbl; freq->freq_hz != FREQ_END; freq++) { + /* source select does not match */ + if ((freq->div_src_val & CFG_RCGR_SRC_SEL_MASK) + != (cfg_regval & CFG_RCGR_SRC_SEL_MASK)) + continue; + /* + * Stop if we found the required parent in the frequency table + * and only care if the source matches but dont care if the + * frequency matches + */ + if (!match_rate) + break; + /* divider does not match */ + div = freq->div_src_val & CFG_RCGR_DIV_MASK; + div_regval = cfg_regval & CFG_RCGR_DIV_MASK; + if (div != div_regval && (div > 1 || div_regval > 1)) + continue; + + if (has_mnd) { + if (freq->m_val != m_regval) + continue; + if (freq->n_val != n_regval) + continue; + if (freq->d_val != d_regval) + continue; + } else if (freq->n_val) { + continue; + } + break; + } + + /* No known frequency found */ + if (freq->freq_hz == FREQ_END) { + /* + * If we can't recognize the frequency and non_local_children is + * set, switch to safe frequency. It is assumed the current + * parent has been turned on by the bootchain if the RCG is on. + */ + if (rcg->non_local_children) { + rcg->set_rate(rcg, &cxo_f); + WARN(1, "don't recognize rcg frequency for %s\n", + rcg->c.dbg_name); + } + return NULL; + } + + rcg->current_freq = freq; + return freq->src_clk; +} + +static enum handoff _rcg_clk_handoff(struct rcg_clk *rcg) +{ + u32 cmd_rcgr_regval; + + if (rcg->current_freq && rcg->current_freq->freq_hz != FREQ_END) + rcg->c.rate = rcg->current_freq->freq_hz; + + /* Is the root enabled? */ + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + if ((cmd_rcgr_regval & CMD_RCGR_ROOT_STATUS_BIT)) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +static struct clk *display_clk_get_parent(struct clk *c) +{ + return _rcg_clk_get_parent(to_rcg_clk(c), false, false); +} + +static struct clk *rcg_mnd_clk_get_parent(struct clk *c) +{ + return _rcg_clk_get_parent(to_rcg_clk(c), true, true); +} + +static struct clk *rcg_clk_get_parent(struct clk *c) +{ + return _rcg_clk_get_parent(to_rcg_clk(c), false, true); +} + +static enum handoff rcg_mnd_clk_handoff(struct clk *c) +{ + return _rcg_clk_handoff(to_rcg_clk(c)); +} + +static enum handoff rcg_clk_handoff(struct clk *c) +{ + return _rcg_clk_handoff(to_rcg_clk(c)); +} + +static void __iomem *rcg_hid_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + static struct clk_register_data data[] = { + {"CMD_RCGR", 0x0}, + {"CFG_RCGR", 0x4}, + }; + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return CMD_RCGR_REG(rcg); +} + +static void __iomem *rcg_mnd_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + static struct clk_register_data data[] = { + {"CMD_RCGR", 0x0}, + {"CFG_RCGR", 0x4}, + {"M_VAL", 0x8}, + {"N_VAL", 0xC}, + {"D_VAL", 0x10}, + }; + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return CMD_RCGR_REG(rcg); +} + +#define BRANCH_CHECK_MASK BM(31, 28) +#define BRANCH_ON_VAL BVAL(31, 28, 0x0) +#define BRANCH_OFF_VAL BVAL(31, 28, 0x8) +#define BRANCH_NOC_FSM_ON_VAL BVAL(31, 28, 0x2) + +/* + * Branch clock functions + */ +static void branch_clk_halt_check(struct clk *c, u32 halt_check, + void __iomem *cbcr_reg, enum branch_state br_status) +{ + char *status_str = (br_status == BRANCH_ON) ? "off" : "on"; + + /* + * Use a memory barrier since some halt status registers are + * not within the same 1K segment as the branch/root enable + * registers. It's also needed in the udelay() case to ensure + * the delay starts after the branch disable. + */ + mb(); + + if (halt_check == DELAY || halt_check == HALT_VOTED) { + udelay(HALT_CHECK_DELAY_US); + } else if (halt_check == HALT) { + int count; + u32 val; + for (count = HALT_CHECK_MAX_LOOPS; count > 0; count--) { + val = readl_relaxed(cbcr_reg); + val &= BRANCH_CHECK_MASK; + switch (br_status) { + case BRANCH_ON: + if (val == BRANCH_ON_VAL + || val == BRANCH_NOC_FSM_ON_VAL) + return; + break; + + case BRANCH_OFF: + if (val == BRANCH_OFF_VAL) + return; + break; + }; + udelay(1); + } + CLK_WARN(c, count == 0, "status stuck %s", status_str); + } +} + +static int cbcr_set_flags(void * __iomem regaddr, unsigned flags) +{ + u32 cbcr_val; + unsigned long irq_flags; + int delay_us = 0, ret = 0; + + spin_lock_irqsave(&local_clock_reg_lock, irq_flags); + cbcr_val = readl_relaxed(regaddr); + switch (flags) { + case CLKFLAG_RETAIN_PERIPH: + cbcr_val |= BIT(13); + delay_us = 1; + break; + case CLKFLAG_NORETAIN_PERIPH: + cbcr_val &= ~BIT(13); + break; + case CLKFLAG_RETAIN_MEM: + cbcr_val |= BIT(14); + delay_us = 1; + break; + case CLKFLAG_NORETAIN_MEM: + cbcr_val &= ~BIT(14); + break; + default: + ret = -EINVAL; + } + writel_relaxed(cbcr_val, regaddr); + /* Make sure power is enabled before returning. */ + mb(); + udelay(delay_us); + + spin_unlock_irqrestore(&local_clock_reg_lock, irq_flags); + + return ret; +} + +static int branch_clk_set_flags(struct clk *c, unsigned flags) +{ + return cbcr_set_flags(CBCR_REG(to_branch_clk(c)), flags); +} + +static int branch_clk_enable(struct clk *c) +{ + unsigned long flags; + u32 cbcr_val; + struct branch_clk *branch = to_branch_clk(c); + + if (branch->toggle_memory) { + branch_clk_set_flags(c, CLKFLAG_RETAIN_MEM); + branch_clk_set_flags(c, CLKFLAG_RETAIN_PERIPH); + } + spin_lock_irqsave(&local_clock_reg_lock, flags); + cbcr_val = readl_relaxed(CBCR_REG(branch)); + cbcr_val |= CBCR_BRANCH_ENABLE_BIT; + writel_relaxed(cbcr_val, CBCR_REG(branch)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* + * For clocks controlled by other masters via voting registers, + * delay polling for the status bit to allow previous clk_disable + * by the GDS controller to go through. + */ + if (branch->no_halt_check_on_disable) + udelay(5); + + /* Wait for clock to enable before continuing. */ + branch_clk_halt_check(c, branch->halt_check, CBCR_REG(branch), + BRANCH_ON); + + return 0; +} + +static void branch_clk_disable(struct clk *c) +{ + unsigned long flags; + struct branch_clk *branch = to_branch_clk(c); + u32 reg_val; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + reg_val = readl_relaxed(CBCR_REG(branch)); + reg_val &= ~CBCR_BRANCH_ENABLE_BIT; + writel_relaxed(reg_val, CBCR_REG(branch)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Wait for clock to disable before continuing. */ + if (!branch->no_halt_check_on_disable) + branch_clk_halt_check(c, branch->halt_check, CBCR_REG(branch), + BRANCH_OFF); + + if (branch->toggle_memory) { + branch_clk_set_flags(c, CLKFLAG_NORETAIN_MEM); + branch_clk_set_flags(c, CLKFLAG_NORETAIN_PERIPH); + } +} + +static int branch_cdiv_set_rate(struct branch_clk *branch, unsigned long rate) +{ + unsigned long flags; + u32 regval; + + if (rate > branch->max_div) + return -EINVAL; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(CBCR_REG(branch)); + regval &= ~BM(CBCR_CDIV_MSB, CBCR_CDIV_LSB); + regval |= BVAL(CBCR_CDIV_MSB, CBCR_CDIV_LSB, rate); + writel_relaxed(regval, CBCR_REG(branch)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +static int branch_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct branch_clk *branch = to_branch_clk(c); + + if (branch->max_div) + return branch_cdiv_set_rate(branch, rate); + + if (!branch->has_sibling) + return clk_set_rate(c->parent, rate); + + return -EPERM; +} + +static long branch_clk_round_rate(struct clk *c, unsigned long rate) +{ + struct branch_clk *branch = to_branch_clk(c); + + if (branch->max_div) + return rate <= (branch->max_div) ? rate : -EPERM; + + if (!branch->has_sibling) + return clk_round_rate(c->parent, rate); + + return -EPERM; +} + +static unsigned long branch_clk_get_rate(struct clk *c) +{ + struct branch_clk *branch = to_branch_clk(c); + + if (branch->max_div) + return branch->c.rate; + + return clk_get_rate(c->parent); +} + +static long branch_clk_list_rate(struct clk *c, unsigned n) +{ + int level; + unsigned long fmax = 0, rate; + struct branch_clk *branch = to_branch_clk(c); + struct clk *parent = c->parent; + + if (branch->has_sibling == 1) + return -ENXIO; + + if (!parent || !parent->ops->list_rate) + return -ENXIO; + + /* Find max frequency supported within voltage constraints. */ + if (!parent->vdd_class) { + fmax = ULONG_MAX; + } else { + for (level = 0; level < parent->num_fmax; level++) + if (parent->fmax[level]) + fmax = parent->fmax[level]; + } + + rate = parent->ops->list_rate(parent, n); + if (rate <= fmax) + return rate; + else + return -ENXIO; +} + +static enum handoff branch_clk_handoff(struct clk *c) +{ + struct branch_clk *branch = to_branch_clk(c); + u32 cbcr_regval; + + cbcr_regval = readl_relaxed(CBCR_REG(branch)); + + /* Set the cdiv to c->rate for fixed divider branch clock */ + if (c->rate && (c->rate < branch->max_div)) { + cbcr_regval &= ~BM(CBCR_CDIV_MSB, CBCR_CDIV_LSB); + cbcr_regval |= BVAL(CBCR_CDIV_MSB, CBCR_CDIV_LSB, c->rate); + writel_relaxed(cbcr_regval, CBCR_REG(branch)); + } + + if ((cbcr_regval & CBCR_BRANCH_OFF_BIT)) + return HANDOFF_DISABLED_CLK; + + if (!(cbcr_regval & CBCR_BRANCH_ENABLE_BIT)) { + WARN(!branch->check_enable_bit, + "%s clock is enabled in HW even though ENABLE_BIT is not set\n", + c->dbg_name); + return HANDOFF_DISABLED_CLK; + } + + if (branch->max_div) { + cbcr_regval &= BM(CBCR_CDIV_MSB, CBCR_CDIV_LSB); + cbcr_regval >>= CBCR_CDIV_LSB; + c->rate = cbcr_regval; + } else if (!branch->has_sibling) { + c->rate = clk_get_rate(c->parent); + } + + return HANDOFF_ENABLED_CLK; +} + +static int __branch_clk_reset(void __iomem *bcr_reg, + enum clk_reset_action action) +{ + int ret = 0; + unsigned long flags; + u32 reg_val; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + reg_val = readl_relaxed(bcr_reg); + switch (action) { + case CLK_RESET_ASSERT: + reg_val |= BCR_BLK_ARES_BIT; + break; + case CLK_RESET_DEASSERT: + reg_val &= ~BCR_BLK_ARES_BIT; + break; + default: + ret = -EINVAL; + } + writel_relaxed(reg_val, bcr_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Make sure write is issued before returning. */ + mb(); + + return ret; +} + +static int branch_clk_reset(struct clk *c, enum clk_reset_action action) +{ + struct branch_clk *branch = to_branch_clk(c); + + if (!branch->bcr_reg) + return -EPERM; + return __branch_clk_reset(BCR_REG(branch), action); +} + +static void __iomem *branch_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct branch_clk *branch = to_branch_clk(c); + static struct clk_register_data data[] = { + {"CBCR", 0x0}, + }; + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return CBCR_REG(branch); +} + +/* + * Voteable clock functions + */ +static int local_vote_clk_reset(struct clk *c, enum clk_reset_action action) +{ + struct local_vote_clk *vclk = to_local_vote_clk(c); + + if (!vclk->bcr_reg) { + WARN("clk_reset called on an unsupported clock (%s)\n", + c->dbg_name); + return -EPERM; + } + return __branch_clk_reset(BCR_REG(vclk), action); +} + +static int local_vote_clk_enable(struct clk *c) +{ + unsigned long flags; + u32 ena; + struct local_vote_clk *vclk = to_local_vote_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + ena = readl_relaxed(VOTE_REG(vclk)); + ena |= vclk->en_mask; + writel_relaxed(ena, VOTE_REG(vclk)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + branch_clk_halt_check(c, vclk->halt_check, CBCR_REG(vclk), BRANCH_ON); + + return 0; +} + +static void local_vote_clk_disable(struct clk *c) +{ + unsigned long flags; + u32 ena; + struct local_vote_clk *vclk = to_local_vote_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + ena = readl_relaxed(VOTE_REG(vclk)); + ena &= ~vclk->en_mask; + writel_relaxed(ena, VOTE_REG(vclk)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static enum handoff local_vote_clk_handoff(struct clk *c) +{ + struct local_vote_clk *vclk = to_local_vote_clk(c); + u32 vote_regval; + + /* Is the branch voted on by apps? */ + vote_regval = readl_relaxed(VOTE_REG(vclk)); + if (!(vote_regval & vclk->en_mask)) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +/* Sample clock for 'ticks' reference clock ticks. */ +static u32 run_measurement(unsigned ticks, void __iomem *ctl_reg, + void __iomem *status_reg) +{ + /* Stop counters and set the XO4 counter start value. */ + writel_relaxed(ticks, ctl_reg); + + /* Wait for timer to become ready. */ + while ((readl_relaxed(status_reg) & BIT(25)) != 0) + cpu_relax(); + + /* Run measurement and wait for completion. */ + writel_relaxed(BIT(20)|ticks, ctl_reg); + while ((readl_relaxed(status_reg) & BIT(25)) == 0) + cpu_relax(); + + /* Return measured ticks. */ + return readl_relaxed(status_reg) & BM(24, 0); +} + +/* + * Perform a hardware rate measurement for a given clock. + * FOR DEBUG USE ONLY: Measurements take ~15 ms! + */ +unsigned long measure_get_rate(struct clk *c) +{ + unsigned long flags; + u32 gcc_xo4_reg, regval; + u64 raw_count_short, raw_count_full; + unsigned ret; + u32 sample_ticks = 0x10000; + u32 multiplier = 0x1; + struct measure_clk_data *data = to_mux_clk(c)->priv; + + regval = readl_relaxed(MUX_REG(to_mux_clk(c))); + /* clear post divider bits */ + regval &= ~BM(15, 12); + writel_relaxed(regval, MUX_REG(to_mux_clk(c))); + + ret = clk_prepare_enable(data->cxo); + if (ret) { + pr_warn("CXO clock failed to enable. Can't measure\n"); + return 0; + } + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Enable CXO/4 and RINGOSC branch. */ + gcc_xo4_reg = readl_relaxed(*data->base + data->xo_div4_cbcr); + gcc_xo4_reg |= CBCR_BRANCH_ENABLE_BIT; + writel_relaxed(gcc_xo4_reg, *data->base + data->xo_div4_cbcr); + + /* + * The ring oscillator counter will not reset if the measured clock + * is not running. To detect this, run a short measurement before + * the full measurement. If the raw results of the two are the same + * then the clock must be off. + */ + + /* Run a short measurement. (~1 ms) */ + raw_count_short = run_measurement(0x1000, *data->base + data->ctl_reg, + *data->base + data->status_reg); + /* Run a full measurement. (~14 ms) */ + raw_count_full = run_measurement(sample_ticks, + *data->base + data->ctl_reg, + *data->base + data->status_reg); + + gcc_xo4_reg &= ~CBCR_BRANCH_ENABLE_BIT; + writel_relaxed(gcc_xo4_reg, *data->base + data->xo_div4_cbcr); + + /* Return 0 if the clock is off. */ + if (raw_count_full == raw_count_short) { + ret = 0; + } else { + /* Compute rate in Hz. */ + raw_count_full = ((raw_count_full * 10) + 15) * 4800000; + do_div(raw_count_full, ((sample_ticks * 10) + 35)); + ret = (raw_count_full * multiplier); + } + writel_relaxed(data->plltest_val, *data->base + data->plltest_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + clk_disable_unprepare(data->cxo); + + return ret; +} + +struct frac_entry { + int num; + int den; +}; + +static void __iomem *local_vote_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct local_vote_clk *vclk = to_local_vote_clk(c); + static struct clk_register_data data1[] = { + {"CBCR", 0x0}, + }; + static struct clk_register_data data2[] = { + {"APPS_VOTE", 0x0}, + {"APPS_SLEEP_VOTE", 0x4}, + }; + switch (n) { + case 0: + *regs = data1; + *size = ARRAY_SIZE(data1); + return CBCR_REG(vclk); + case 1: + *regs = data2; + *size = ARRAY_SIZE(data2); + return VOTE_REG(vclk); + default: + return ERR_PTR(-EINVAL); + } +} + +static struct frac_entry frac_table_675m[] = { /* link rate of 270M */ + {52, 295}, /* 119 M */ + {11, 57}, /* 130.25 M */ + {63, 307}, /* 138.50 M */ + {11, 50}, /* 148.50 M */ + {47, 206}, /* 154 M */ + {31, 100}, /* 205.25 M */ + {107, 269}, /* 268.50 M */ + {0, 0}, +}; + +static struct frac_entry frac_table_810m[] = { /* Link rate of 162M */ + {31, 211}, /* 119 M */ + {32, 199}, /* 130.25 M */ + {63, 307}, /* 138.50 M */ + {11, 60}, /* 148.50 M */ + {50, 263}, /* 154 M */ + {31, 120}, /* 205.25 M */ + {119, 359}, /* 268.50 M */ + {0, 0}, +}; + +static bool is_same_rcg_config(struct rcg_clk *rcg, struct clk_freq_tbl *freq, + bool has_mnd) +{ + u32 cfg; + + /* RCG update pending */ + if (readl_relaxed(CMD_RCGR_REG(rcg)) & CMD_RCGR_CONFIG_DIRTY_MASK) + return false; + if (has_mnd) + if (readl_relaxed(M_REG(rcg)) != freq->m_val || + readl_relaxed(N_REG(rcg)) != freq->n_val || + readl_relaxed(D_REG(rcg)) != freq->d_val) + return false; + /* + * Both 0 and 1 represent same divider value in HW. + * Always use 0 to simplify comparison. + */ + if ((freq->div_src_val & CFG_RCGR_DIV_MASK) == 1) + freq->div_src_val &= ~CFG_RCGR_DIV_MASK; + cfg = readl_relaxed(CFG_RCGR_REG(rcg)); + if ((cfg & CFG_RCGR_DIV_MASK) == 1) + cfg &= ~CFG_RCGR_DIV_MASK; + if (cfg != freq->div_src_val) + return false; + + return true; +} + +static int set_rate_edp_pixel(struct clk *clk, unsigned long rate) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + struct clk_freq_tbl *pixel_freq = rcg->current_freq; + struct frac_entry *frac; + int delta = 100000; + s64 request; + s64 src_rate; + unsigned long flags; + + src_rate = clk_get_rate(clk->parent); + + if (src_rate == 810000000) + frac = frac_table_810m; + else + frac = frac_table_675m; + + while (frac->num) { + request = rate; + request *= frac->den; + request = div_s64(request, frac->num); + if ((src_rate < (request - delta)) || + (src_rate > (request + delta))) { + frac++; + continue; + } + + pixel_freq->div_src_val &= ~BM(4, 0); + if (frac->den == frac->num) { + pixel_freq->m_val = 0; + pixel_freq->n_val = 0; + } else { + pixel_freq->m_val = frac->num; + pixel_freq->n_val = ~(frac->den - frac->num); + pixel_freq->d_val = ~frac->den; + } + spin_lock_irqsave(&local_clock_reg_lock, flags); + if (!is_same_rcg_config(rcg, pixel_freq, true)) + __set_rate_mnd(rcg, pixel_freq); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + return 0; + } + return -EINVAL; +} + +enum handoff byte_rcg_handoff(struct clk *clk) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + u32 div_val; + unsigned long pre_div_rate, parent_rate = clk_get_rate(clk->parent); + + /* If the pre-divider is used, find the rate after the division */ + div_val = readl_relaxed(CFG_RCGR_REG(rcg)) & CFG_RCGR_DIV_MASK; + if (div_val > 1) + pre_div_rate = parent_rate / ((div_val + 1) >> 1); + else + pre_div_rate = parent_rate; + + clk->rate = pre_div_rate; + + if (readl_relaxed(CMD_RCGR_REG(rcg)) & CMD_RCGR_ROOT_STATUS_BIT) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +static int set_rate_byte(struct clk *clk, unsigned long rate) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + struct clk *pll = clk->parent; + unsigned long source_rate, div, flags; + struct clk_freq_tbl *byte_freq = rcg->current_freq; + int rc; + + if (rate == 0) + return -EINVAL; + + rc = clk_set_rate(pll, rate); + if (rc) + return rc; + + source_rate = clk_round_rate(pll, rate); + if ((2 * source_rate) % rate) + return -EINVAL; + + div = ((2 * source_rate)/rate) - 1; + if (div > CFG_RCGR_DIV_MASK) + return -EINVAL; + + /* + * Both 0 and 1 represent same divider value in HW. + * Always use 0 to simplify comparison. + */ + div = (div == 1) ? 0 : div; + + byte_freq->div_src_val &= ~CFG_RCGR_DIV_MASK; + byte_freq->div_src_val |= BVAL(4, 0, div); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + if (!is_same_rcg_config(rcg, byte_freq, false)) + __set_rate_hid(rcg, byte_freq); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +enum handoff pixel_rcg_handoff(struct clk *clk) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + u32 div_val = 0, mval = 0, nval = 0, cfg_regval; + unsigned long pre_div_rate, parent_rate = clk_get_rate(clk->parent); + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + + /* If the pre-divider is used, find the rate after the division */ + div_val = cfg_regval & CFG_RCGR_DIV_MASK; + if (div_val > 1) + pre_div_rate = parent_rate / ((div_val + 1) >> 1); + else + pre_div_rate = parent_rate; + + clk->rate = pre_div_rate; + + /* + * Pixel clocks have one frequency entry in their frequency table. + * Update that entry. + */ + if (rcg->current_freq) { + rcg->current_freq->div_src_val &= ~CFG_RCGR_DIV_MASK; + rcg->current_freq->div_src_val |= div_val; + } + + /* If MND is used, find the rate after the MND division */ + if ((cfg_regval & MND_MODE_MASK) == MND_DUAL_EDGE_MODE_BVAL) { + mval = readl_relaxed(M_REG(rcg)); + nval = readl_relaxed(N_REG(rcg)); + if (!nval) + return HANDOFF_DISABLED_CLK; + nval = (~nval) + mval; + if (rcg->current_freq) { + rcg->current_freq->n_val = ~(nval - mval); + rcg->current_freq->m_val = mval; + rcg->current_freq->d_val = ~nval; + } + clk->rate = (pre_div_rate * mval) / nval; + } + + if (readl_relaxed(CMD_RCGR_REG(rcg)) & CMD_RCGR_ROOT_STATUS_BIT) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +static long round_rate_pixel(struct clk *clk, unsigned long rate) +{ + int frac_num[] = {3, 2, 4, 1}; + int frac_den[] = {8, 9, 9, 1}; + int delta = 100000; + int i; + + for (i = 0; i < ARRAY_SIZE(frac_num); i++) { + unsigned long request = (rate * frac_den[i]) / frac_num[i]; + unsigned long src_rate; + + src_rate = clk_round_rate(clk->parent, request); + if ((src_rate < (request - delta)) || + (src_rate > (request + delta))) + continue; + + return (src_rate * frac_num[i]) / frac_den[i]; + } + + return -EINVAL; +} + + +static int set_rate_pixel(struct clk *clk, unsigned long rate) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + struct clk_freq_tbl *pixel_freq = rcg->current_freq; + int frac_num[] = {3, 2, 4, 1}; + int frac_den[] = {8, 9, 9, 1}; + int delta = 100000; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(frac_num); i++) { + unsigned long request = (rate * frac_den[i]) / frac_num[i]; + unsigned long src_rate; + + src_rate = clk_round_rate(clk->parent, request); + if ((src_rate < (request - delta)) || + (src_rate > (request + delta))) + continue; + + rc = clk_set_rate(clk->parent, src_rate); + if (rc) + return rc; + + pixel_freq->div_src_val &= ~BM(4, 0); + if (frac_den[i] == frac_num[i]) { + pixel_freq->m_val = 0; + pixel_freq->n_val = 0; + } else { + pixel_freq->m_val = frac_num[i]; + pixel_freq->n_val = ~(frac_den[i] - frac_num[i]); + pixel_freq->d_val = ~frac_den[i]; + } + set_rate_mnd(rcg, pixel_freq); + return 0; + } + return -EINVAL; +} + +static int rcg_clk_set_parent(struct clk *clk, struct clk *parent_clk) +{ + struct rcg_clk *rcg = to_rcg_clk(clk); + struct clk *old_parent = clk->parent; + struct clk_freq_tbl *nf; + unsigned long flags; + int rc = 0; + unsigned int parent_rate, rate; + u32 m_val, n_val, d_val, div_val; + u32 cfg_regval; + + /* Find the source clock freq tbl for the requested parent */ + if (!rcg->freq_tbl) + return -ENXIO; + + for (nf = rcg->freq_tbl; parent_clk != nf->src_clk; nf++) { + if (nf->freq_hz == FREQ_END) + return -ENXIO; + } + + /* This implementation recommends that the RCG be unprepared + * when switching RCG source since the divider configuration + * remains unchanged. + */ + WARN(clk->prepare_count, + "Trying to switch RCG source while it is prepared!\n"); + + parent_rate = clk_get_rate(parent_clk); + + div_val = (rcg->current_freq->div_src_val & CFG_RCGR_DIV_MASK); + if (div_val) + parent_rate /= ((div_val + 1) >> 1); + + /* Update divisor. Source select bits should already be as expected */ + nf->div_src_val &= ~CFG_RCGR_DIV_MASK; + nf->div_src_val |= div_val; + + cfg_regval = readl_relaxed(CFG_RCGR_REG(rcg)); + + if ((cfg_regval & MND_MODE_MASK) == MND_DUAL_EDGE_MODE_BVAL) { + nf->m_val = m_val = readl_relaxed(M_REG(rcg)); + n_val = readl_relaxed(N_REG(rcg)); + d_val = readl_relaxed(D_REG(rcg)); + + /* Sign extend the n and d values as those in registers are not + * sign extended. + */ + n_val |= (n_val >> 8) ? BM(31, 16) : BM(31, 8); + d_val |= (d_val >> 8) ? BM(31, 16) : BM(31, 8); + + nf->n_val = n_val; + nf->d_val = d_val; + + n_val = ~(n_val) + m_val; + rate = parent_rate * m_val; + if (n_val) + rate /= n_val; + else + WARN(1, "n_val was 0!!"); + } else + rate = parent_rate; + + /* Warn if switching to the new parent with the current m, n ,d values + * violates the voltage constraints for the RCG. + */ + WARN(!is_rate_valid(clk, rate) && clk->prepare_count, + "Switch to new RCG parent violates voltage requirement!\n"); + + rc = __clk_pre_reparent(clk, nf->src_clk, &flags); + if (rc) + return rc; + + /* Switch RCG source */ + rcg->set_rate(rcg, nf); + + rcg->current_freq = nf; + clk->parent = parent_clk; + clk->rate = rate; + + __clk_post_reparent(clk, old_parent, &flags); + + return 0; +} + +/* + * Unlike other clocks, the HDMI rate is adjusted through PLL + * re-programming. It is also routed through an HID divider. + */ +static int rcg_clk_set_rate_hdmi(struct clk *c, unsigned long rate) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + struct clk_freq_tbl *nf = rcg->freq_tbl; + int rc; + + rc = clk_set_rate(nf->src_clk, rate); + if (rc < 0) + goto out; + set_rate_hid(rcg, nf); + + rcg->current_freq = nf; +out: + return rc; +} + +static struct clk *rcg_hdmi_clk_get_parent(struct clk *c) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + struct clk_freq_tbl *freq = rcg->freq_tbl; + u32 cmd_rcgr_regval; + + /* Is there a pending configuration? */ + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + if (cmd_rcgr_regval & CMD_RCGR_CONFIG_DIRTY_MASK) + return NULL; + + rcg->current_freq->freq_hz = clk_get_rate(c->parent); + + return freq->src_clk; +} + +static int rcg_clk_set_rate_edp(struct clk *c, unsigned long rate) +{ + struct clk_freq_tbl *nf; + struct rcg_clk *rcg = to_rcg_clk(c); + int rc; + + for (nf = rcg->freq_tbl; nf->freq_hz != rate; nf++) + if (nf->freq_hz == FREQ_END) { + rc = -EINVAL; + goto out; + } + + rc = clk_set_rate(nf->src_clk, rate); + if (rc < 0) + goto out; + set_rate_hid(rcg, nf); + + rcg->current_freq = nf; + c->parent = nf->src_clk; +out: + return rc; +} + +static struct clk *edp_clk_get_parent(struct clk *c) +{ + struct rcg_clk *rcg = to_rcg_clk(c); + struct clk *clk; + struct clk_freq_tbl *freq; + unsigned long rate; + u32 cmd_rcgr_regval; + + /* Is there a pending configuration? */ + cmd_rcgr_regval = readl_relaxed(CMD_RCGR_REG(rcg)); + if (cmd_rcgr_regval & CMD_RCGR_CONFIG_DIRTY_MASK) + return NULL; + + /* Figure out what rate the rcg is running at */ + for (freq = rcg->freq_tbl; freq->freq_hz != FREQ_END; freq++) { + clk = freq->src_clk; + if (clk && clk->ops->get_rate) { + rate = clk->ops->get_rate(clk); + if (rate == freq->freq_hz) + break; + } + } + + /* No known frequency found */ + if (freq->freq_hz == FREQ_END) + return NULL; + + rcg->current_freq = freq; + return freq->src_clk; +} + +static int gate_clk_enable(struct clk *c) +{ + unsigned long flags; + u32 regval; + struct gate_clk *g = to_gate_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(GATE_EN_REG(g)); + regval |= g->en_mask; + writel_relaxed(regval, GATE_EN_REG(g)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + if (g->delay_us) + udelay(g->delay_us); + + return 0; +} + +static void gate_clk_disable(struct clk *c) +{ + unsigned long flags; + u32 regval; + struct gate_clk *g = to_gate_clk(c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(GATE_EN_REG(g)); + regval &= ~(g->en_mask); + writel_relaxed(regval, GATE_EN_REG(g)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + if (g->delay_us) + udelay(g->delay_us); +} + +static void __iomem *gate_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct gate_clk *g = to_gate_clk(c); + static struct clk_register_data data[] = { + {"EN_REG", 0x0}, + }; + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return GATE_EN_REG(g); +} + +static enum handoff gate_clk_handoff(struct clk *c) +{ + struct gate_clk *g = to_gate_clk(c); + u32 regval; + + regval = readl_relaxed(GATE_EN_REG(g)); + if (regval & g->en_mask) + return HANDOFF_ENABLED_CLK; + + return HANDOFF_DISABLED_CLK; +} + +static int gate_clk_set_flags(struct clk *c, unsigned flags) +{ + return cbcr_set_flags(GATE_EN_REG(to_gate_clk(c)), flags); +} + + +static int reset_clk_rst(struct clk *c, enum clk_reset_action action) +{ + struct reset_clk *rst = to_reset_clk(c); + + if (!rst->reset_reg) + return -EPERM; + + return __branch_clk_reset(RST_REG(rst), action); +} + +static void __iomem *reset_clk_list_registers(struct clk *clk, int n, + struct clk_register_data **regs, u32 *size) +{ + struct reset_clk *rst = to_reset_clk(clk); + static struct clk_register_data data[] = { + {"BCR", 0x0}, + }; + + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return RST_REG(rst); +} + +static DEFINE_SPINLOCK(mux_reg_lock); + +static int mux_reg_enable(struct mux_clk *clk) +{ + u32 regval; + unsigned long flags; + + if (!clk->en_mask) + return 0; + + spin_lock_irqsave(&mux_reg_lock, flags); + regval = readl_relaxed(*clk->base + clk->en_offset); + regval |= clk->en_mask; + writel_relaxed(regval, *clk->base + clk->en_offset); + /* Ensure enable request goes through before returning */ + mb(); + spin_unlock_irqrestore(&mux_reg_lock, flags); + + return 0; +} + +static void mux_reg_disable(struct mux_clk *clk) +{ + u32 regval; + unsigned long flags; + + if (!clk->en_mask) + return; + + spin_lock_irqsave(&mux_reg_lock, flags); + regval = readl_relaxed(*clk->base + clk->en_offset); + regval &= ~clk->en_mask; + writel_relaxed(regval, *clk->base + clk->en_offset); + spin_unlock_irqrestore(&mux_reg_lock, flags); +} + +static int mux_reg_set_mux_sel(struct mux_clk *clk, int sel) +{ + u32 regval; + unsigned long flags; + + spin_lock_irqsave(&mux_reg_lock, flags); + regval = readl_relaxed(MUX_REG(clk)); + regval &= ~(clk->mask << clk->shift); + regval |= (sel & clk->mask) << clk->shift; + writel_relaxed(regval, MUX_REG(clk)); + /* Ensure switch request goes through before returning */ + mb(); + spin_unlock_irqrestore(&mux_reg_lock, flags); + + return 0; +} + +static int mux_reg_get_mux_sel(struct mux_clk *clk) +{ + u32 regval = readl_relaxed(MUX_REG(clk)); + return (regval >> clk->shift) & clk->mask; +} + +static bool mux_reg_is_enabled(struct mux_clk *clk) +{ + u32 regval = readl_relaxed(MUX_REG(clk)); + return !!(regval & clk->en_mask); +} + +static void __iomem *mux_clk_list_registers(struct mux_clk *clk, int n, + struct clk_register_data **regs, u32 *size) +{ + static struct clk_register_data data[] = { + {"DEBUG_CLK_CTL", 0x0}, + }; + + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return *clk->base + clk->offset; +} + +/* PLL post-divider setting for each divider value */ +static struct div_map postdiv_map[] = { + { 0x0, 1 }, + { 0x1, 2 }, + { 0x3, 3 }, + { 0x3, 4 }, + { 0x5, 5 }, + { 0x7, 7 }, + { 0x7, 8 }, + { 0xF, 16 }, +}; + +static int postdiv_reg_set_div(struct div_clk *clk, int div) +{ + struct clk *parent = NULL; + u32 regval; + unsigned long flags; + unsigned int mask = -1; + int i, ret = 0; + + /* Divider is not configurable */ + if (!clk->mask) + return 0; + + for (i = 0; i < ARRAY_SIZE(postdiv_map); i++) { + if (postdiv_map[i].div == div) { + mask = postdiv_map[i].mask; + break; + } + } + + if (mask < 0) + return -EINVAL; + + spin_lock_irqsave(&clk->c.lock, flags); + parent = clk->c.parent; + if (parent->count && parent->ops->disable) + parent->ops->disable(parent); + + regval = readl_relaxed(DIV_REG(clk)); + regval &= ~(clk->mask << clk->shift); + regval |= (mask & clk->mask) << clk->shift; + writel_relaxed(regval, DIV_REG(clk)); + /* Ensure switch request goes through before returning */ + mb(); + + if (parent->count && parent->ops->enable) { + ret = parent->ops->enable(parent); + if (ret) + pr_err("Failed to force enable div parent!\n"); + } + + spin_unlock_irqrestore(&clk->c.lock, flags); + return ret; +} + +static int postdiv_reg_get_div(struct div_clk *clk) +{ + u32 regval; + int i, div = 0; + + /* Divider is not configurable */ + if (!clk->mask) + return clk->data.div; + + regval = readl_relaxed(DIV_REG(clk)); + regval = (regval >> clk->shift) & clk->mask; + for (i = 0; i < ARRAY_SIZE(postdiv_map); i++) { + if (postdiv_map[i].mask == regval) { + div = postdiv_map[i].div; + break; + } + } + if (!div) + return -EINVAL; + + return div; +} + +static int div_reg_set_div(struct div_clk *clk, int div) +{ + u32 regval; + unsigned long flags; + + /* Divider is not configurable */ + if (!clk->mask) + return 0; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(*clk->base + clk->offset); + regval &= ~(clk->mask << clk->shift); + regval |= (div & clk->mask) << clk->shift; + /* Ensure switch request goes through before returning */ + mb(); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +static int div_reg_get_div(struct div_clk *clk) +{ + u32 regval; + /* Divider is not configurable */ + if (!clk->mask) + return clk->data.div; + + regval = readl_relaxed(*clk->base + clk->offset); + return (regval >> clk->shift) & clk->mask; +} + +/* =================Half-integer RCG without MN counter================= */ +#define RCGR_CMD_REG(x) ((x)->base + (x)->div_offset) +#define RCGR_DIV_REG(x) ((x)->base + (x)->div_offset + 4) +#define RCGR_SRC_REG(x) ((x)->base + (x)->div_offset + 4) + +static int rcg_mux_div_update_config(struct mux_div_clk *md) +{ + u32 regval, count; + + regval = readl_relaxed(RCGR_CMD_REG(md)); + regval |= CMD_RCGR_CONFIG_UPDATE_BIT; + writel_relaxed(regval, RCGR_CMD_REG(md)); + + /* Wait for update to take effect */ + for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) { + if (!(readl_relaxed(RCGR_CMD_REG(md)) & + CMD_RCGR_CONFIG_UPDATE_BIT)) + return 0; + udelay(1); + } + + CLK_WARN(&md->c, true, "didn't update its configuration."); + + return -EBUSY; +} + +static void rcg_get_src_div(struct mux_div_clk *md, u32 *src_sel, u32 *div) +{ + u32 regval; + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + /* Is there a pending configuration? */ + regval = readl_relaxed(RCGR_CMD_REG(md)); + if (regval & CMD_RCGR_CONFIG_DIRTY_MASK) { + CLK_WARN(&md->c, true, "it's a pending configuration."); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + return; + } + + regval = readl_relaxed(RCGR_DIV_REG(md)); + regval &= (md->div_mask << md->div_shift); + *div = regval >> md->div_shift; + + /* bypass */ + if (*div == 0) + *div = 1; + /* the div is doubled here*/ + *div += 1; + + regval = readl_relaxed(RCGR_SRC_REG(md)); + regval &= (md->src_mask << md->src_shift); + *src_sel = regval >> md->src_shift; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static void mux_div_set_force_enable(struct mux_div_clk *md) +{ + u32 regval; + unsigned long flags; + int count; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(RCGR_CMD_REG(md)); + regval |= CMD_RCGR_ROOT_ENABLE_BIT; + writel_relaxed(regval, RCGR_CMD_REG(md)); + + /* Wait for RCG to turn ON */ + for (count = UPDATE_CHECK_MAX_LOOPS; count > 0; count--) { + if (!(readl_relaxed(RCGR_CMD_REG(md)) & + CMD_RCGR_CONFIG_UPDATE_BIT)) + goto exit; + udelay(1); + } + CLK_WARN(&md->c, count == 0, "rcg didn't turn on."); +exit: + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static void mux_div_clear_force_enable(struct mux_div_clk *md) +{ + u32 regval; + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(RCGR_CMD_REG(md)); + regval &= ~CMD_RCGR_ROOT_ENABLE_BIT; + writel_relaxed(regval, RCGR_CMD_REG(md)); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static int rcg_set_src_div(struct mux_div_clk *md, u32 src_sel, u32 div) +{ + u32 regval; + unsigned long flags; + int ret; + + /* for half-integer divider, div here is doubled */ + if (div) + div -= 1; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + regval = readl_relaxed(RCGR_DIV_REG(md)); + regval &= ~(md->div_mask << md->div_shift); + regval |= div << md->div_shift; + writel_relaxed(regval, RCGR_DIV_REG(md)); + + regval = readl_relaxed(RCGR_SRC_REG(md)); + regval &= ~(md->src_mask << md->src_shift); + regval |= src_sel << md->src_shift; + writel_relaxed(regval, RCGR_SRC_REG(md)); + + ret = rcg_mux_div_update_config(md); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + return ret; +} + +static int rcg_enable(struct mux_div_clk *md) +{ + if (md->force_enable_md) + mux_div_set_force_enable(md); + + return rcg_set_src_div(md, md->src_sel, md->data.div); +} + +static void rcg_disable(struct mux_div_clk *md) +{ + u32 src_sel; + + if (md->force_enable_md) + mux_div_clear_force_enable(md); + + if (!md->safe_freq) + return; + + src_sel = parent_to_src_sel(md->parents, md->num_parents, + md->safe_parent); + + rcg_set_src_div(md, src_sel, md->safe_div); +} + +static bool rcg_is_enabled(struct mux_div_clk *md) +{ + u32 regval; + + regval = readl_relaxed(RCGR_CMD_REG(md)); + if (regval & CMD_RCGR_ROOT_STATUS_BIT) + return false; + else + return true; +} + +static void __iomem *rcg_list_registers(struct mux_div_clk *md, int n, + struct clk_register_data **regs, u32 *size) +{ + static struct clk_register_data data[] = { + {"CMD_RCGR", 0x0}, + {"CFG_RCGR", 0x4}, + }; + + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return RCGR_CMD_REG(md); +} + +struct clk_ops clk_ops_empty; + +struct clk_ops clk_ops_rst = { + .reset = reset_clk_rst, + .list_registers = reset_clk_list_registers, +}; + +struct clk_ops clk_ops_rcg = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = rcg_clk_set_rate, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .handoff = rcg_clk_handoff, + .get_parent = rcg_clk_get_parent, + .set_parent = rcg_clk_set_parent, + .list_registers = rcg_hid_clk_list_registers, +}; + +struct clk_ops clk_ops_rcg_mnd = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = rcg_clk_set_rate, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .handoff = rcg_mnd_clk_handoff, + .get_parent = rcg_mnd_clk_get_parent, + .set_parent = rcg_clk_set_parent, + .list_registers = rcg_mnd_clk_list_registers, +}; + +struct clk_ops clk_ops_pixel = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = set_rate_pixel, + .list_rate = rcg_clk_list_rate, + .round_rate = round_rate_pixel, + .handoff = pixel_rcg_handoff, + .list_registers = rcg_mnd_clk_list_registers, +}; + +struct clk_ops clk_ops_pixel_multiparent = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = set_rate_pixel, + .list_rate = rcg_clk_list_rate, + .round_rate = round_rate_pixel, + .handoff = pixel_rcg_handoff, + .list_registers = rcg_mnd_clk_list_registers, + .get_parent = display_clk_get_parent, + .set_parent = rcg_clk_set_parent, +}; + +struct clk_ops clk_ops_edppixel = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = set_rate_edp_pixel, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .handoff = pixel_rcg_handoff, + .list_registers = rcg_mnd_clk_list_registers, +}; + +struct clk_ops clk_ops_byte = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = set_rate_byte, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .handoff = byte_rcg_handoff, + .list_registers = rcg_hid_clk_list_registers, +}; + +struct clk_ops clk_ops_byte_multiparent = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = set_rate_byte, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .handoff = byte_rcg_handoff, + .list_registers = rcg_hid_clk_list_registers, + .get_parent = display_clk_get_parent, + .set_parent = rcg_clk_set_parent, +}; + +struct clk_ops clk_ops_rcg_hdmi = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = rcg_clk_set_rate_hdmi, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .handoff = rcg_clk_handoff, + .get_parent = rcg_hdmi_clk_get_parent, + .list_registers = rcg_hid_clk_list_registers, +}; + +struct clk_ops clk_ops_rcg_edp = { + .enable = rcg_clk_enable, + .disable = rcg_clk_disable, + .set_rate = rcg_clk_set_rate_edp, + .list_rate = rcg_clk_list_rate, + .round_rate = rcg_clk_round_rate, + .handoff = rcg_clk_handoff, + .get_parent = edp_clk_get_parent, + .list_registers = rcg_hid_clk_list_registers, +}; + +struct clk_ops clk_ops_branch = { + .enable = branch_clk_enable, + .disable = branch_clk_disable, + .set_rate = branch_clk_set_rate, + .get_rate = branch_clk_get_rate, + .list_rate = branch_clk_list_rate, + .round_rate = branch_clk_round_rate, + .reset = branch_clk_reset, + .set_flags = branch_clk_set_flags, + .handoff = branch_clk_handoff, + .list_registers = branch_clk_list_registers, +}; + +struct clk_ops clk_ops_vote = { + .enable = local_vote_clk_enable, + .disable = local_vote_clk_disable, + .reset = local_vote_clk_reset, + .handoff = local_vote_clk_handoff, + .list_registers = local_vote_clk_list_registers, +}; + +struct clk_ops clk_ops_gate = { + .enable = gate_clk_enable, + .disable = gate_clk_disable, + .set_rate = parent_set_rate, + .get_rate = parent_get_rate, + .round_rate = parent_round_rate, + .set_flags = gate_clk_set_flags, + .handoff = gate_clk_handoff, + .list_registers = gate_clk_list_registers, +}; + +struct clk_mux_ops mux_reg_ops = { + .enable = mux_reg_enable, + .disable = mux_reg_disable, + .set_mux_sel = mux_reg_set_mux_sel, + .get_mux_sel = mux_reg_get_mux_sel, + .is_enabled = mux_reg_is_enabled, + .list_registers = mux_clk_list_registers, +}; + +struct clk_div_ops div_reg_ops = { + .set_div = div_reg_set_div, + .get_div = div_reg_get_div, +}; + +struct clk_div_ops postdiv_reg_ops = { + .set_div = postdiv_reg_set_div, + .get_div = postdiv_reg_get_div, +}; + +struct mux_div_ops rcg_mux_div_ops = { + .enable = rcg_enable, + .disable = rcg_disable, + .set_src_div = rcg_set_src_div, + .get_src_div = rcg_get_src_div, + .is_enabled = rcg_is_enabled, + .list_registers = rcg_list_registers, +}; + +static void *cbc_dt_parser(struct device *dev, struct device_node *np) +{ + struct msmclk_data *drv; + struct branch_clk *branch_clk; + u32 rc; + + branch_clk = devm_kzalloc(dev, sizeof(*branch_clk), GFP_KERNEL); + if (!branch_clk) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return ERR_CAST(drv); + branch_clk->base = &drv->base; + + rc = of_property_read_u32(np, "qcom,base-offset", + &branch_clk->cbcr_reg); + if (rc) { + dt_err(np, "missing/incorrect qcom,base-offset dt property\n"); + return ERR_PTR(rc); + } + + /* Optional property */ + of_property_read_u32(np, "qcom,bcr-offset", &branch_clk->bcr_reg); + + branch_clk->has_sibling = of_property_read_bool(np, + "qcom,has-sibling"); + + branch_clk->c.ops = &clk_ops_branch; + + return msmclk_generic_clk_init(dev, np, &branch_clk->c); +} +MSMCLK_PARSER(cbc_dt_parser, "qcom,cbc", 0); + +static void *local_vote_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct local_vote_clk *vote_clk; + struct msmclk_data *drv; + int rc, val; + + vote_clk = devm_kzalloc(dev, sizeof(*vote_clk), GFP_KERNEL); + if (!vote_clk) { + dt_err(np, "failed to alloc memory\n"); + return ERR_PTR(-ENOMEM); + } + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return ERR_CAST(drv); + vote_clk->base = &drv->base; + + rc = of_property_read_u32(np, "qcom,base-offset", + &vote_clk->cbcr_reg); + if (rc) { + dt_err(np, "missing/incorrect qcom,base-offset dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,en-offset", &vote_clk->vote_reg); + if (rc) { + dt_err(np, "missing/incorrect qcom,en-offset dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,en-bit", &val); + if (rc) { + dt_err(np, "missing/incorrect qcom,en-bit dt property\n"); + return ERR_PTR(-EINVAL); + } + vote_clk->en_mask = BIT(val); + + vote_clk->c.ops = &clk_ops_vote; + + /* Optional property */ + of_property_read_u32(np, "qcom,bcr-offset", &vote_clk->bcr_reg); + + return msmclk_generic_clk_init(dev, np, &vote_clk->c); +} +MSMCLK_PARSER(local_vote_clk_dt_parser, "qcom,local-vote-clk", 0); + +static void *gate_clk_dt_parser(struct device *dev, struct device_node *np) +{ + struct gate_clk *gate_clk; + struct msmclk_data *drv; + u32 en_bit, rc; + + gate_clk = devm_kzalloc(dev, sizeof(*gate_clk), GFP_KERNEL); + if (!gate_clk) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return ERR_CAST(drv); + gate_clk->base = &drv->base; + + rc = of_property_read_u32(np, "qcom,en-offset", &gate_clk->en_reg); + if (rc) { + dt_err(np, "missing qcom,en-offset dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,en-bit", &en_bit); + if (rc) { + dt_err(np, "missing qcom,en-bit dt property\n"); + return ERR_PTR(-EINVAL); + } + gate_clk->en_mask = BIT(en_bit); + + /* Optional Property */ + rc = of_property_read_u32(np, "qcom,delay", &gate_clk->delay_us); + if (rc) + gate_clk->delay_us = 0; + + gate_clk->c.ops = &clk_ops_gate; + return msmclk_generic_clk_init(dev, np, &gate_clk->c); +} +MSMCLK_PARSER(gate_clk_dt_parser, "qcom,gate-clk", 0); + + +static inline u32 rcg_calc_m(u32 m, u32 n) +{ + return m; +} + +static inline u32 rcg_calc_n(u32 m, u32 n) +{ + n = n > 1 ? n : 0; + return ~((n)-(m)) * !!(n); +} + +static inline u32 rcg_calc_duty_cycle(u32 m, u32 n) +{ + return ~n; +} + +static inline u32 rcg_calc_div_src(u32 div_int, u32 div_frac, u32 src_sel) +{ + int div = 2 * div_int + (div_frac ? 1 : 0) - 1; + /* set bypass mode instead of a divider of 1 */ + div = (div != 1) ? div : 0; + return BVAL(4, 0, max(div, 0)) + | BVAL(10, 8, src_sel); +} + +struct clk_src *msmclk_parse_clk_src(struct device *dev, + struct device_node *np, int *array_size) +{ + struct clk_src *clks; + const void *prop; + int num_parents, len, i, prop_len, rc; + char *name = "qcom,parents"; + + if (!array_size) { + dt_err(np, "array_size must be a valid pointer\n"); + return ERR_PTR(-EINVAL); + } + + prop = of_get_property(np, name, &prop_len); + if (!prop) { + dt_prop_err(np, name, "missing dt property\n"); + return ERR_PTR(-EINVAL); + } + + len = sizeof(phandle) + sizeof(u32); + if (prop_len % len) { + dt_prop_err(np, name, "invalid property length\n"); + return ERR_PTR(-EINVAL); + } + num_parents = prop_len / len; + + clks = devm_kzalloc(dev, sizeof(*clks) * num_parents, GFP_KERNEL); + if (!clks) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + /* Assume that u32 and phandle have the same size */ + for (i = 0; i < num_parents; i++) { + phandle p; + struct clk_src *a = &clks[i]; + + rc = of_property_read_u32_index(np, name, 2 * i, &a->sel); + rc |= of_property_read_phandle_index(np, name, 2 * i + 1, &p); + + if (rc) { + dt_prop_err(np, name, + "unable to read parent clock or mux index\n"); + return ERR_PTR(-EINVAL); + } + + a->src = msmclk_parse_phandle(dev, p); + if (IS_ERR(a->src)) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return ERR_CAST(a->src); + } + } + + *array_size = num_parents; + + return clks; +} + +static int rcg_parse_freq_tbl(struct device *dev, + struct device_node *np, struct rcg_clk *rcg) +{ + const void *prop; + u32 prop_len, num_rows, i, j = 0; + struct clk_freq_tbl *tbl; + int rc; + char *name = "qcom,freq-tbl"; + + prop = of_get_property(np, name, &prop_len); + if (!prop) { + dt_prop_err(np, name, "missing dt property\n"); + return -EINVAL; + } + + prop_len /= sizeof(u32); + if (prop_len % 6) { + dt_prop_err(np, name, "bad length\n"); + return -EINVAL; + } + + num_rows = prop_len / 6; + /* Array is null terminated. */ + rcg->freq_tbl = devm_kzalloc(dev, + sizeof(*rcg->freq_tbl) * (num_rows + 1), + GFP_KERNEL); + + if (!rcg->freq_tbl) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + tbl = rcg->freq_tbl; + for (i = 0; i < num_rows; i++, tbl++) { + phandle p; + u32 div_int, div_frac, m, n, src_sel, freq_hz; + + rc = of_property_read_u32_index(np, name, j++, &freq_hz); + rc |= of_property_read_u32_index(np, name, j++, &div_int); + rc |= of_property_read_u32_index(np, name, j++, &div_frac); + rc |= of_property_read_u32_index(np, name, j++, &m); + rc |= of_property_read_u32_index(np, name, j++, &n); + rc |= of_property_read_u32_index(np, name, j++, &p); + + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return -EINVAL; + } + + tbl->freq_hz = (unsigned long)freq_hz; + tbl->src_clk = msmclk_parse_phandle(dev, p); + if (IS_ERR_OR_NULL(tbl->src_clk)) { + dt_prop_err(np, name, "hashtable lookup failure\n"); + return PTR_ERR(tbl->src_clk); + } + + tbl->m_val = rcg_calc_m(m, n); + tbl->n_val = rcg_calc_n(m, n); + tbl->d_val = rcg_calc_duty_cycle(m, n); + + src_sel = parent_to_src_sel(rcg->c.parents, + rcg->c.num_parents, tbl->src_clk); + tbl->div_src_val = rcg_calc_div_src(div_int, div_frac, + src_sel); + } + /* End table with special value */ + tbl->freq_hz = FREQ_END; + return 0; +} + +static void *rcg_clk_dt_parser(struct device *dev, struct device_node *np) +{ + struct rcg_clk *rcg; + struct msmclk_data *drv; + int rc; + + rcg = devm_kzalloc(dev, sizeof(*rcg), GFP_KERNEL); + if (!rcg) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return drv; + rcg->base = &drv->base; + + rcg->c.parents = msmclk_parse_clk_src(dev, np, &rcg->c.num_parents); + if (IS_ERR(rcg->c.parents)) { + dt_err(np, "unable to read parents\n"); + return ERR_CAST(rcg->c.parents); + } + + rc = of_property_read_u32(np, "qcom,base-offset", &rcg->cmd_rcgr_reg); + if (rc) { + dt_err(np, "missing qcom,base-offset dt property\n"); + return ERR_PTR(rc); + } + + rc = rcg_parse_freq_tbl(dev, np, rcg); + if (rc) { + dt_err(np, "unable to read freq_tbl\n"); + return ERR_PTR(rc); + } + rcg->current_freq = &rcg_dummy_freq; + + if (of_device_is_compatible(np, "qcom,rcg-hid")) { + rcg->c.ops = &clk_ops_rcg; + rcg->set_rate = set_rate_hid; + } else if (of_device_is_compatible(np, "qcom,rcg-mn")) { + rcg->c.ops = &clk_ops_rcg_mnd; + rcg->set_rate = set_rate_mnd; + } else { + dt_err(np, "unexpected compatible string\n"); + return ERR_PTR(-EINVAL); + } + + return msmclk_generic_clk_init(dev, np, &rcg->c); +} +MSMCLK_PARSER(rcg_clk_dt_parser, "qcom,rcg-hid", 0); +MSMCLK_PARSER(rcg_clk_dt_parser, "qcom,rcg-mn", 1); + +static int parse_rec_parents(struct device *dev, + struct device_node *np, struct mux_clk *mux) +{ + int i, rc; + char *name = "qcom,recursive-parents"; + phandle p; + + mux->num_rec_parents = of_property_count_phandles(np, name); + if (mux->num_rec_parents <= 0) + return 0; + + mux->rec_parents = devm_kzalloc(dev, + sizeof(*mux->rec_parents) * mux->num_rec_parents, + GFP_KERNEL); + + if (!mux->rec_parents) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + for (i = 0; i < mux->num_rec_parents; i++) { + rc = of_property_read_phandle_index(np, name, i, &p); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + mux->rec_parents[i] = msmclk_parse_phandle(dev, p); + if (IS_ERR(mux->rec_parents[i])) { + dt_prop_err(np, name, "hashtable lookup failure\n"); + return PTR_ERR(mux->rec_parents[i]); + } + } + + return 0; +} + +static void *mux_reg_clk_dt_parser(struct device *dev, struct device_node *np) +{ + struct mux_clk *mux; + struct msmclk_data *drv; + int rc; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + mux->parents = msmclk_parse_clk_src(dev, np, &mux->num_parents); + if (IS_ERR(mux->parents)) + return mux->parents; + + mux->c.parents = mux->parents; + mux->c.num_parents = mux->num_parents; + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return drv; + mux->base = &drv->base; + + rc = parse_rec_parents(dev, np, mux); + if (rc) { + dt_err(np, "Incorrect qcom,recursive-parents dt property\n"); + return ERR_PTR(rc); + } + + rc = of_property_read_u32(np, "qcom,offset", &mux->offset); + if (rc) { + dt_err(np, "missing qcom,offset dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,mask", &mux->mask); + if (rc) { + dt_err(np, "missing qcom,mask dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,shift", &mux->shift); + if (rc) { + dt_err(np, "missing qcom,shift dt property\n"); + return ERR_PTR(-EINVAL); + } + + mux->c.ops = &clk_ops_gen_mux; + mux->ops = &mux_reg_ops; + + /* Optional Properties */ + of_property_read_u32(np, "qcom,en-offset", &mux->en_offset); + of_property_read_u32(np, "qcom,en-mask", &mux->en_mask); + + return msmclk_generic_clk_init(dev, np, &mux->c); +}; +MSMCLK_PARSER(mux_reg_clk_dt_parser, "qcom,mux-reg", 0); + +static void *measure_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct mux_clk *mux; + struct clk *c; + struct measure_clk_data *p; + struct clk_ops *clk_ops_measure_mux; + phandle cxo; + int rc; + + c = mux_reg_clk_dt_parser(dev, np); + if (IS_ERR(c)) + return c; + + mux = to_mux_clk(c); + + p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL); + if (!p) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + rc = of_property_read_phandle_index(np, "qcom,cxo", 0, &cxo); + if (rc) { + dt_err(np, "missing qcom,cxo\n"); + return ERR_PTR(-EINVAL); + } + p->cxo = msmclk_parse_phandle(dev, cxo); + if (IS_ERR_OR_NULL(p->cxo)) { + dt_prop_err(np, "qcom,cxo", "hashtable lookup failure\n"); + return p->cxo; + } + + rc = of_property_read_u32(np, "qcom,xo-div4-cbcr", &p->xo_div4_cbcr); + if (rc) { + dt_err(np, "missing qcom,xo-div4-cbcr dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,test-pad-config", &p->plltest_val); + if (rc) { + dt_err(np, "missing qcom,test-pad-config dt property\n"); + return ERR_PTR(-EINVAL); + } + + p->base = mux->base; + p->ctl_reg = mux->offset + 0x4; + p->status_reg = mux->offset + 0x8; + p->plltest_reg = mux->offset + 0xC; + mux->priv = p; + + clk_ops_measure_mux = devm_kzalloc(dev, sizeof(*clk_ops_measure_mux), + GFP_KERNEL); + if (!clk_ops_measure_mux) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + *clk_ops_measure_mux = clk_ops_gen_mux; + clk_ops_measure_mux->get_rate = measure_get_rate; + + mux->c.ops = clk_ops_measure_mux; + + /* Already did generic clk init */ + return &mux->c; +}; +MSMCLK_PARSER(measure_clk_dt_parser, "qcom,measure-mux", 0); + +static void *div_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct div_clk *div_clk; + struct msmclk_data *drv; + int rc; + + div_clk = devm_kzalloc(dev, sizeof(*div_clk), GFP_KERNEL); + if (!div_clk) { + dt_err(np, "memory alloc failed\n"); + return ERR_PTR(-ENOMEM); + } + + rc = of_property_read_u32(np, "qcom,max-div", &div_clk->data.max_div); + if (rc) { + dt_err(np, "missing qcom,max-div\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,min-div", &div_clk->data.min_div); + if (rc) { + dt_err(np, "missing qcom,min-div\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,base-offset", &div_clk->offset); + if (rc) { + dt_err(np, "missing qcom,base-offset\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,mask", &div_clk->mask); + if (rc) { + dt_err(np, "missing qcom,mask\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,shift", &div_clk->shift); + if (rc) { + dt_err(np, "missing qcom,shift\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_bool(np, "qcom,slave-div")) + div_clk->c.ops = &clk_ops_slave_div; + else + div_clk->c.ops = &clk_ops_div; + div_clk->ops = &div_reg_ops; + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return ERR_CAST(drv); + div_clk->base = &drv->base; + + return msmclk_generic_clk_init(dev, np, &div_clk->c); +}; +MSMCLK_PARSER(div_clk_dt_parser, "qcom,div-clk", 0); + +static void *fixed_div_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct div_clk *div_clk; + int rc; + + div_clk = devm_kzalloc(dev, sizeof(*div_clk), GFP_KERNEL); + if (!div_clk) { + dt_err(np, "memory alloc failed\n"); + return ERR_PTR(-ENOMEM); + } + + rc = of_property_read_u32(np, "qcom,div", &div_clk->data.div); + if (rc) { + dt_err(np, "missing qcom,div\n"); + return ERR_PTR(-EINVAL); + } + div_clk->data.min_div = div_clk->data.div; + div_clk->data.max_div = div_clk->data.div; + + if (of_property_read_bool(np, "qcom,slave-div")) + div_clk->c.ops = &clk_ops_slave_div; + else + div_clk->c.ops = &clk_ops_div; + div_clk->ops = &div_reg_ops; + + return msmclk_generic_clk_init(dev, np, &div_clk->c); +} +MSMCLK_PARSER(fixed_div_clk_dt_parser, "qcom,fixed-div-clk", 0); + +static void *reset_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct reset_clk *reset_clk; + struct msmclk_data *drv; + int rc; + + reset_clk = devm_kzalloc(dev, sizeof(*reset_clk), GFP_KERNEL); + if (!reset_clk) { + dt_err(np, "memory alloc failed\n"); + return ERR_PTR(-ENOMEM); + } + + rc = of_property_read_u32(np, "qcom,base-offset", + &reset_clk->reset_reg); + if (rc) { + dt_err(np, "missing qcom,base-offset\n"); + return ERR_PTR(-EINVAL); + } + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return ERR_CAST(drv); + reset_clk->base = &drv->base; + + reset_clk->c.ops = &clk_ops_rst; + return msmclk_generic_clk_init(dev, np, &reset_clk->c); +}; +MSMCLK_PARSER(reset_clk_dt_parser, "qcom,reset-clk", 0); diff --git a/drivers/clk/msm/clock-pll.c b/drivers/clk/msm/clock-pll.c new file mode 100644 index 000000000000..4e746b9754af --- /dev/null +++ b/drivers/clk/msm/clock-pll.c @@ -0,0 +1,1205 @@ +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <soc/qcom/clock-pll.h> +#include <soc/qcom/msm-clock-controller.h> + +#include "clock.h" + +#define PLL_OUTCTRL BIT(0) +#define PLL_BYPASSNL BIT(1) +#define PLL_RESET_N BIT(2) +#define PLL_MODE_MASK BM(3, 0) + +#define PLL_EN_REG(x) (*(x)->base + (unsigned long) (x)->en_reg) +#define PLL_STATUS_REG(x) (*(x)->base + (unsigned long) (x)->status_reg) +#define PLL_ALT_STATUS_REG(x) (*(x)->base + (unsigned long) \ + (x)->alt_status_reg) +#define PLL_MODE_REG(x) (*(x)->base + (unsigned long) (x)->mode_reg) +#define PLL_L_REG(x) (*(x)->base + (unsigned long) (x)->l_reg) +#define PLL_M_REG(x) (*(x)->base + (unsigned long) (x)->m_reg) +#define PLL_N_REG(x) (*(x)->base + (unsigned long) (x)->n_reg) +#define PLL_CONFIG_REG(x) (*(x)->base + (unsigned long) (x)->config_reg) +#define PLL_ALPHA_REG(x) (*(x)->base + (unsigned long) (x)->alpha_reg) +#define PLL_CFG_ALT_REG(x) (*(x)->base + (unsigned long) \ + (x)->config_alt_reg) +#define PLL_CFG_CTL_REG(x) (*(x)->base + (unsigned long) \ + (x)->config_ctl_reg) +#define PLL_CFG_CTL_HI_REG(x) (*(x)->base + (unsigned long) \ + (x)->config_ctl_hi_reg) +#define PLL_TEST_CTL_LO_REG(x) (*(x)->base + (unsigned long) \ + (x)->test_ctl_lo_reg) +#define PLL_TEST_CTL_HI_REG(x) (*(x)->base + (unsigned long) \ + (x)->test_ctl_hi_reg) +static DEFINE_SPINLOCK(pll_reg_lock); + +#define ENABLE_WAIT_MAX_LOOPS 200 +#define PLL_LOCKED_BIT BIT(16) + +#define SPM_FORCE_EVENT 0x4 + +static int pll_vote_clk_enable(struct clk *c) +{ + u32 ena, count; + unsigned long flags; + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + + spin_lock_irqsave(&pll_reg_lock, flags); + ena = readl_relaxed(PLL_EN_REG(pllv)); + ena |= pllv->en_mask; + writel_relaxed(ena, PLL_EN_REG(pllv)); + spin_unlock_irqrestore(&pll_reg_lock, flags); + + /* + * Use a memory barrier since some PLL status registers are + * not within the same 1K segment as the voting registers. + */ + mb(); + + /* Wait for pll to enable. */ + for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) { + if (readl_relaxed(PLL_STATUS_REG(pllv)) & pllv->status_mask) + return 0; + udelay(1); + } + + WARN("PLL %s didn't enable after voting for it!\n", c->dbg_name); + + return -ETIMEDOUT; +} + +static void pll_vote_clk_disable(struct clk *c) +{ + u32 ena; + unsigned long flags; + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + + spin_lock_irqsave(&pll_reg_lock, flags); + ena = readl_relaxed(PLL_EN_REG(pllv)); + ena &= ~(pllv->en_mask); + writel_relaxed(ena, PLL_EN_REG(pllv)); + spin_unlock_irqrestore(&pll_reg_lock, flags); +} + +static int pll_vote_clk_is_enabled(struct clk *c) +{ + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + return !!(readl_relaxed(PLL_STATUS_REG(pllv)) & pllv->status_mask); +} + +static enum handoff pll_vote_clk_handoff(struct clk *c) +{ + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + if (readl_relaxed(PLL_EN_REG(pllv)) & pllv->en_mask) + return HANDOFF_ENABLED_CLK; + + return HANDOFF_DISABLED_CLK; +} + +static void __iomem *pll_vote_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + static struct clk_register_data data1[] = { + {"APPS_VOTE", 0x0}, + }; + + if (n) + return ERR_PTR(-EINVAL); + + *regs = data1; + *size = ARRAY_SIZE(data1); + return PLL_EN_REG(pllv); +} + +struct clk_ops clk_ops_pll_vote = { + .enable = pll_vote_clk_enable, + .disable = pll_vote_clk_disable, + .is_enabled = pll_vote_clk_is_enabled, + .handoff = pll_vote_clk_handoff, + .list_registers = pll_vote_clk_list_registers, +}; + +/* + * spm_event() -- Set/Clear SPM events + * PLL off sequence -- enable (1) + * Set L2_SPM_FORCE_EVENT_EN[bit] register to 1 + * Set L2_SPM_FORCE_EVENT[bit] register to 1 + * PLL on sequence -- enable (0) + * Clear L2_SPM_FORCE_EVENT[bit] register to 0 + * Clear L2_SPM_FORCE_EVENT_EN[bit] register to 0 + */ +static void spm_event(void __iomem *base, u32 offset, u32 bit, + bool enable) +{ + uint32_t val; + + if (!base) + return; + + if (enable) { + /* L2_SPM_FORCE_EVENT_EN */ + val = readl_relaxed(base + offset); + val |= BIT(bit); + writel_relaxed(val, (base + offset)); + /* Ensure that the write above goes through. */ + mb(); + + /* L2_SPM_FORCE_EVENT */ + val = readl_relaxed(base + offset + SPM_FORCE_EVENT); + val |= BIT(bit); + writel_relaxed(val, (base + offset + SPM_FORCE_EVENT)); + /* Ensure that the write above goes through. */ + mb(); + } else { + /* L2_SPM_FORCE_EVENT */ + val = readl_relaxed(base + offset + SPM_FORCE_EVENT); + val &= ~BIT(bit); + writel_relaxed(val, (base + offset + SPM_FORCE_EVENT)); + /* Ensure that the write above goes through. */ + mb(); + + /* L2_SPM_FORCE_EVENT_EN */ + val = readl_relaxed(base + offset); + val &= ~BIT(bit); + writel_relaxed(val, (base + offset)); + /* Ensure that the write above goes through. */ + mb(); + } +} + +static void __pll_config_reg(void __iomem *pll_config, struct pll_freq_tbl *f, + struct pll_config_masks *masks) +{ + u32 regval; + + regval = readl_relaxed(pll_config); + + /* Enable the MN counter if used */ + if (f->m_val) + regval |= masks->mn_en_mask; + + /* Set pre-divider and post-divider values */ + regval &= ~masks->pre_div_mask; + regval |= f->pre_div_val; + regval &= ~masks->post_div_mask; + regval |= f->post_div_val; + + /* Select VCO setting */ + regval &= ~masks->vco_mask; + regval |= f->vco_val; + + /* Enable main output if it has not been enabled */ + if (masks->main_output_mask && !(regval & masks->main_output_mask)) + regval |= masks->main_output_mask; + + writel_relaxed(regval, pll_config); +} + +static int sr2_pll_clk_enable(struct clk *c) +{ + unsigned long flags; + struct pll_clk *pll = to_pll_clk(c); + int ret = 0, count; + u32 mode = readl_relaxed(PLL_MODE_REG(pll)); + u32 lockmask = pll->masks.lock_mask ?: PLL_LOCKED_BIT; + + spin_lock_irqsave(&pll_reg_lock, flags); + + spm_event(pll->spm_ctrl.spm_base, pll->spm_ctrl.offset, + pll->spm_ctrl.event_bit, false); + + /* Disable PLL bypass mode. */ + mode |= PLL_BYPASSNL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Delay 10us just to be safe. + */ + mb(); + udelay(10); + + /* De-assert active-low PLL reset. */ + mode |= PLL_RESET_N; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Wait for pll to lock. */ + for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) { + if (readl_relaxed(PLL_STATUS_REG(pll)) & lockmask) + break; + udelay(1); + } + + if (!(readl_relaxed(PLL_STATUS_REG(pll)) & lockmask)) + pr_err("PLL %s didn't lock after enabling it!\n", c->dbg_name); + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Ensure that the write above goes through before returning. */ + mb(); + + spin_unlock_irqrestore(&pll_reg_lock, flags); + return ret; +} + +void __variable_rate_pll_init(struct clk *c) +{ + struct pll_clk *pll = to_pll_clk(c); + u32 regval; + + regval = readl_relaxed(PLL_CONFIG_REG(pll)); + + if (pll->masks.post_div_mask) { + regval &= ~pll->masks.post_div_mask; + regval |= pll->vals.post_div_masked; + } + + if (pll->masks.pre_div_mask) { + regval &= ~pll->masks.pre_div_mask; + regval |= pll->vals.pre_div_masked; + } + + if (pll->masks.main_output_mask) + regval |= pll->masks.main_output_mask; + + if (pll->masks.early_output_mask) + regval |= pll->masks.early_output_mask; + + if (pll->vals.enable_mn) + regval |= pll->masks.mn_en_mask; + else + regval &= ~pll->masks.mn_en_mask; + + writel_relaxed(regval, PLL_CONFIG_REG(pll)); + + regval = readl_relaxed(PLL_MODE_REG(pll)); + if (pll->masks.apc_pdn_mask) + regval &= ~pll->masks.apc_pdn_mask; + writel_relaxed(regval, PLL_MODE_REG(pll)); + + writel_relaxed(pll->vals.alpha_val, PLL_ALPHA_REG(pll)); + writel_relaxed(pll->vals.config_ctl_val, PLL_CFG_CTL_REG(pll)); + if (pll->vals.config_ctl_hi_val) + writel_relaxed(pll->vals.config_ctl_hi_val, + PLL_CFG_CTL_HI_REG(pll)); + if (pll->init_test_ctl) { + writel_relaxed(pll->vals.test_ctl_lo_val, + PLL_TEST_CTL_LO_REG(pll)); + writel_relaxed(pll->vals.test_ctl_hi_val, + PLL_TEST_CTL_HI_REG(pll)); + } + + pll->inited = true; +} + +static int variable_rate_pll_clk_enable(struct clk *c) +{ + unsigned long flags; + struct pll_clk *pll = to_pll_clk(c); + int ret = 0, count; + u32 mode, testlo; + u32 lockmask = pll->masks.lock_mask ?: PLL_LOCKED_BIT; + u32 mode_lock; + u64 time; + bool early_lock = false; + + spin_lock_irqsave(&pll_reg_lock, flags); + + if (unlikely(!to_pll_clk(c)->inited)) + __variable_rate_pll_init(c); + + mode = readl_relaxed(PLL_MODE_REG(pll)); + + /* Set test control bits as required by HW doc */ + if (pll->test_ctl_lo_reg && pll->vals.test_ctl_lo_val && + pll->pgm_test_ctl_enable) + writel_relaxed(pll->vals.test_ctl_lo_val, + PLL_TEST_CTL_LO_REG(pll)); + + /* Enable test_ctl debug */ + mode |= BIT(3); + writel_relaxed(mode, PLL_MODE_REG(pll)); + + testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll)); + testlo &= ~BM(7, 6); + testlo |= 0xC0; + writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll)); + /* Wait for the write to complete */ + mb(); + + /* Disable PLL bypass mode. */ + mode |= PLL_BYPASSNL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Use 10us to be sure. + */ + mb(); + udelay(10); + + /* De-assert active-low PLL reset. */ + mode |= PLL_RESET_N; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* + * 5us delay mandated by HPG. However, put in a 200us delay here. + * This is to address possible locking issues with the PLL exhibit + * early "transient" locks about 16us from this point. With this + * higher delay, we avoid running into those transients. + */ + mb(); + udelay(200); + + /* Clear test control bits */ + if (pll->test_ctl_lo_reg && pll->vals.test_ctl_lo_val && + pll->pgm_test_ctl_enable) + writel_relaxed(0x0, PLL_TEST_CTL_LO_REG(pll)); + + + time = sched_clock(); + /* Wait for pll to lock. */ + for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) { + if (readl_relaxed(PLL_STATUS_REG(pll)) & lockmask) { + udelay(1); + /* + * Check again to be sure. This is to avoid + * breaking too early if there is a "transient" + * lock. + */ + if ((readl_relaxed(PLL_STATUS_REG(pll)) & lockmask)) + break; + else + early_lock = true; + } + udelay(1); + } + time = sched_clock() - time; + + mode_lock = readl_relaxed(PLL_STATUS_REG(pll)); + + if (!(mode_lock & lockmask)) { + pr_err("PLL lock bit detection total wait time: %lld ns", time); + pr_err("PLL %s didn't lock after enabling for L value 0x%x!\n", + c->dbg_name, readl_relaxed(PLL_L_REG(pll))); + pr_err("mode register is 0x%x\n", + readl_relaxed(PLL_STATUS_REG(pll))); + pr_err("user control register is 0x%x\n", + readl_relaxed(PLL_CONFIG_REG(pll))); + pr_err("config control register is 0x%x\n", + readl_relaxed(PLL_CFG_CTL_REG(pll))); + pr_err("test control high register is 0x%x\n", + readl_relaxed(PLL_TEST_CTL_HI_REG(pll))); + pr_err("test control low register is 0x%x\n", + readl_relaxed(PLL_TEST_CTL_LO_REG(pll))); + pr_err("early lock? %s\n", early_lock ? "yes" : "no"); + + testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll)); + testlo &= ~BM(7, 6); + writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll)); + /* Wait for the write to complete */ + mb(); + + pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n", + readl_relaxed(PLL_TEST_CTL_LO_REG(pll)), + readl_relaxed(PLL_ALT_STATUS_REG(pll))); + + testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll)); + testlo &= ~BM(7, 6); + testlo |= 0x40; + writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll)); + /* Wait for the write to complete */ + mb(); + pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n", + readl_relaxed(PLL_TEST_CTL_LO_REG(pll)), + readl_relaxed(PLL_ALT_STATUS_REG(pll))); + + testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll)); + testlo &= ~BM(7, 6); + testlo |= 0x80; + writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll)); + /* Wait for the write to complete */ + mb(); + + pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n", + readl_relaxed(PLL_TEST_CTL_LO_REG(pll)), + readl_relaxed(PLL_ALT_STATUS_REG(pll))); + + testlo = readl_relaxed(PLL_TEST_CTL_LO_REG(pll)); + testlo &= ~BM(7, 6); + testlo |= 0xC0; + writel_relaxed(testlo, PLL_TEST_CTL_LO_REG(pll)); + /* Wait for the write to complete */ + mb(); + + pr_err("test_ctl_lo = 0x%x, pll status is: 0x%x\n", + readl_relaxed(PLL_TEST_CTL_LO_REG(pll)), + readl_relaxed(PLL_ALT_STATUS_REG(pll))); + panic("failed to lock %s PLL\n", c->dbg_name); + } + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Ensure that the write above goes through before returning. */ + mb(); + + spin_unlock_irqrestore(&pll_reg_lock, flags); + + return ret; +} + +static void variable_rate_pll_clk_disable_hwfsm(struct clk *c) +{ + struct pll_clk *pll = to_pll_clk(c); + u32 regval; + + /* Set test control bit to stay-in-CFA if necessary */ + if (pll->test_ctl_lo_reg && pll->pgm_test_ctl_enable) { + regval = readl_relaxed(PLL_TEST_CTL_LO_REG(pll)); + writel_relaxed(regval | BIT(16), + PLL_TEST_CTL_LO_REG(pll)); + } + + /* 8 reference clock cycle delay mandated by the HPG */ + udelay(1); +} + +static int variable_rate_pll_clk_enable_hwfsm(struct clk *c) +{ + struct pll_clk *pll = to_pll_clk(c); + int count; + u32 lockmask = pll->masks.lock_mask ?: PLL_LOCKED_BIT; + unsigned long flags; + u32 regval; + + spin_lock_irqsave(&pll_reg_lock, flags); + + /* Clear test control bit if necessary */ + if (pll->test_ctl_lo_reg && pll->pgm_test_ctl_enable) { + regval = readl_relaxed(PLL_TEST_CTL_LO_REG(pll)); + regval &= ~BIT(16); + writel_relaxed(regval, PLL_TEST_CTL_LO_REG(pll)); + } + + /* Wait for 50us explicitly to avoid transient locks */ + udelay(50); + + for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) { + if (readl_relaxed(PLL_STATUS_REG(pll)) & lockmask) + break; + udelay(1); + } + + if (!(readl_relaxed(PLL_STATUS_REG(pll)) & lockmask)) + pr_err("PLL %s didn't lock after enabling it!\n", c->dbg_name); + + spin_unlock_irqrestore(&pll_reg_lock, flags); + + return 0; +} + +static void __pll_clk_enable_reg(void __iomem *mode_reg) +{ + u32 mode = readl_relaxed(mode_reg); + /* Disable PLL bypass mode. */ + mode |= PLL_BYPASSNL; + writel_relaxed(mode, mode_reg); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Delay 10us just to be safe. + */ + mb(); + udelay(10); + + /* De-assert active-low PLL reset. */ + mode |= PLL_RESET_N; + writel_relaxed(mode, mode_reg); + + /* Wait until PLL is locked. */ + mb(); + udelay(50); + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, mode_reg); + + /* Ensure that the write above goes through before returning. */ + mb(); +} + +static int local_pll_clk_enable(struct clk *c) +{ + unsigned long flags; + struct pll_clk *pll = to_pll_clk(c); + + spin_lock_irqsave(&pll_reg_lock, flags); + __pll_clk_enable_reg(PLL_MODE_REG(pll)); + spin_unlock_irqrestore(&pll_reg_lock, flags); + + return 0; +} + +static void __pll_clk_disable_reg(void __iomem *mode_reg) +{ + u32 mode = readl_relaxed(mode_reg); + mode &= ~PLL_MODE_MASK; + writel_relaxed(mode, mode_reg); +} + +static void local_pll_clk_disable(struct clk *c) +{ + unsigned long flags; + struct pll_clk *pll = to_pll_clk(c); + + /* + * Disable the PLL output, disable test mode, enable + * the bypass mode, and assert the reset. + */ + spin_lock_irqsave(&pll_reg_lock, flags); + spm_event(pll->spm_ctrl.spm_base, pll->spm_ctrl.offset, + pll->spm_ctrl.event_bit, true); + __pll_clk_disable_reg(PLL_MODE_REG(pll)); + spin_unlock_irqrestore(&pll_reg_lock, flags); +} + +static enum handoff local_pll_clk_handoff(struct clk *c) +{ + struct pll_clk *pll = to_pll_clk(c); + u32 mode = readl_relaxed(PLL_MODE_REG(pll)); + u32 mask = PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL; + unsigned long parent_rate; + u32 lval, mval, nval, userval; + + if ((mode & mask) != mask) + return HANDOFF_DISABLED_CLK; + + /* Assume bootloaders configure PLL to c->rate */ + if (c->rate) + return HANDOFF_ENABLED_CLK; + + parent_rate = clk_get_rate(c->parent); + lval = readl_relaxed(PLL_L_REG(pll)); + mval = readl_relaxed(PLL_M_REG(pll)); + nval = readl_relaxed(PLL_N_REG(pll)); + userval = readl_relaxed(PLL_CONFIG_REG(pll)); + + c->rate = parent_rate * lval; + + if (pll->masks.mn_en_mask && userval) { + if (!nval) + nval = 1; + c->rate += (parent_rate * mval) / nval; + } + + return HANDOFF_ENABLED_CLK; +} + +static long local_pll_clk_round_rate(struct clk *c, unsigned long rate) +{ + struct pll_freq_tbl *nf; + struct pll_clk *pll = to_pll_clk(c); + + if (!pll->freq_tbl) + return -EINVAL; + + for (nf = pll->freq_tbl; nf->freq_hz != PLL_FREQ_END; nf++) + if (nf->freq_hz >= rate) + return nf->freq_hz; + + nf--; + return nf->freq_hz; +} + +static int local_pll_clk_set_rate(struct clk *c, unsigned long rate) +{ + struct pll_freq_tbl *nf; + struct pll_clk *pll = to_pll_clk(c); + unsigned long flags; + + for (nf = pll->freq_tbl; nf->freq_hz != PLL_FREQ_END + && nf->freq_hz != rate; nf++) + ; + + if (nf->freq_hz == PLL_FREQ_END) + return -EINVAL; + + /* + * Ensure PLL is off before changing rate. For optimization reasons, + * assume no downstream clock is using actively using it. + */ + spin_lock_irqsave(&c->lock, flags); + if (c->count) + c->ops->disable(c); + + writel_relaxed(nf->l_val, PLL_L_REG(pll)); + writel_relaxed(nf->m_val, PLL_M_REG(pll)); + writel_relaxed(nf->n_val, PLL_N_REG(pll)); + + __pll_config_reg(PLL_CONFIG_REG(pll), nf, &pll->masks); + + if (c->count) + c->ops->enable(c); + + spin_unlock_irqrestore(&c->lock, flags); + return 0; +} + +static enum handoff variable_rate_pll_handoff(struct clk *c) +{ + struct pll_clk *pll = to_pll_clk(c); + u32 mode = readl_relaxed(PLL_MODE_REG(pll)); + u32 mask = PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL; + u32 lval; + + pll->src_rate = clk_get_rate(c->parent); + + lval = readl_relaxed(PLL_L_REG(pll)); + if (!lval) + return HANDOFF_DISABLED_CLK; + + c->rate = pll->src_rate * lval; + + if (c->rate > pll->max_rate || c->rate < pll->min_rate) { + WARN(1, "%s: Out of spec PLL", c->dbg_name); + return HANDOFF_DISABLED_CLK; + } + + if ((mode & mask) != mask) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +static long variable_rate_pll_round_rate(struct clk *c, unsigned long rate) +{ + struct pll_clk *pll = to_pll_clk(c); + + if (!pll->src_rate) + return 0; + + if (pll->no_prepared_reconfig && c->prepare_count && c->rate != rate) + return -EINVAL; + + if (rate < pll->min_rate) + rate = pll->min_rate; + if (rate > pll->max_rate) + rate = pll->max_rate; + + return min(pll->max_rate, + DIV_ROUND_UP(rate, pll->src_rate) * pll->src_rate); +} + +/* + * For optimization reasons, assumes no downstream clocks are actively using + * it. + */ +static int variable_rate_pll_set_rate(struct clk *c, unsigned long rate) +{ + struct pll_clk *pll = to_pll_clk(c); + unsigned long flags; + u32 l_val; + + if (rate != variable_rate_pll_round_rate(c, rate)) + return -EINVAL; + + l_val = rate / pll->src_rate; + + spin_lock_irqsave(&c->lock, flags); + + if (c->count && c->ops->disable) + c->ops->disable(c); + + writel_relaxed(l_val, PLL_L_REG(pll)); + + if (c->count && c->ops->enable) + c->ops->enable(c); + + spin_unlock_irqrestore(&c->lock, flags); + + return 0; +} + +int sr_pll_clk_enable(struct clk *c) +{ + u32 mode; + unsigned long flags; + struct pll_clk *pll = to_pll_clk(c); + + spin_lock_irqsave(&pll_reg_lock, flags); + mode = readl_relaxed(PLL_MODE_REG(pll)); + /* De-assert active-low PLL reset. */ + mode |= PLL_RESET_N; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Delay 10us just to be safe. + */ + mb(); + udelay(10); + + /* Disable PLL bypass mode. */ + mode |= PLL_BYPASSNL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Wait until PLL is locked. */ + mb(); + udelay(60); + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Ensure that the write above goes through before returning. */ + mb(); + + spin_unlock_irqrestore(&pll_reg_lock, flags); + + return 0; +} + +int sr_hpm_lp_pll_clk_enable(struct clk *c) +{ + unsigned long flags; + struct pll_clk *pll = to_pll_clk(c); + u32 count, mode; + int ret = 0; + + spin_lock_irqsave(&pll_reg_lock, flags); + + /* Disable PLL bypass mode and de-assert reset. */ + mode = PLL_BYPASSNL | PLL_RESET_N; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Wait for pll to lock. */ + for (count = ENABLE_WAIT_MAX_LOOPS; count > 0; count--) { + if (readl_relaxed(PLL_STATUS_REG(pll)) & PLL_LOCKED_BIT) + break; + udelay(1); + } + + if (!(readl_relaxed(PLL_STATUS_REG(pll)) & PLL_LOCKED_BIT)) { + WARN("PLL %s didn't lock after enabling it!\n", c->dbg_name); + ret = -ETIMEDOUT; + goto out; + } + + /* Enable PLL output. */ + mode |= PLL_OUTCTRL; + writel_relaxed(mode, PLL_MODE_REG(pll)); + + /* Ensure the write above goes through before returning. */ + mb(); + +out: + spin_unlock_irqrestore(&pll_reg_lock, flags); + return ret; +} + + +static void __iomem *variable_rate_pll_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + struct pll_clk *pll = to_pll_clk(c); + static struct clk_register_data data[] = { + {"MODE", 0x0}, + {"L", 0x4}, + {"ALPHA", 0x8}, + {"USER_CTL", 0x10}, + {"CONFIG_CTL", 0x14}, + {"STATUS", 0x1C}, + }; + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return PLL_MODE_REG(pll); +} + +static void __iomem *local_pll_clk_list_registers(struct clk *c, int n, + struct clk_register_data **regs, u32 *size) +{ + /* Not compatible with 8960 & friends */ + struct pll_clk *pll = to_pll_clk(c); + static struct clk_register_data data[] = { + {"MODE", 0x0}, + {"L", 0x4}, + {"M", 0x8}, + {"N", 0xC}, + {"USER", 0x10}, + {"CONFIG", 0x14}, + {"STATUS", 0x1C}, + }; + if (n) + return ERR_PTR(-EINVAL); + + *regs = data; + *size = ARRAY_SIZE(data); + return PLL_MODE_REG(pll); +} + + +struct clk_ops clk_ops_local_pll = { + .enable = local_pll_clk_enable, + .disable = local_pll_clk_disable, + .set_rate = local_pll_clk_set_rate, + .handoff = local_pll_clk_handoff, + .list_registers = local_pll_clk_list_registers, +}; + +struct clk_ops clk_ops_sr2_pll = { + .enable = sr2_pll_clk_enable, + .disable = local_pll_clk_disable, + .set_rate = local_pll_clk_set_rate, + .round_rate = local_pll_clk_round_rate, + .handoff = local_pll_clk_handoff, + .list_registers = local_pll_clk_list_registers, +}; + +struct clk_ops clk_ops_variable_rate_pll_hwfsm = { + .enable = variable_rate_pll_clk_enable_hwfsm, + .disable = variable_rate_pll_clk_disable_hwfsm, + .set_rate = variable_rate_pll_set_rate, + .round_rate = variable_rate_pll_round_rate, + .handoff = variable_rate_pll_handoff, + .list_registers = variable_rate_pll_list_registers, +}; + +struct clk_ops clk_ops_variable_rate_pll = { + .enable = variable_rate_pll_clk_enable, + .disable = local_pll_clk_disable, + .set_rate = variable_rate_pll_set_rate, + .round_rate = variable_rate_pll_round_rate, + .handoff = variable_rate_pll_handoff, + .list_registers = variable_rate_pll_list_registers, +}; + +static DEFINE_SPINLOCK(soft_vote_lock); + +static int pll_acpu_vote_clk_enable(struct clk *c) +{ + int ret = 0; + unsigned long flags; + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + + spin_lock_irqsave(&soft_vote_lock, flags); + + if (!*pllv->soft_vote) + ret = pll_vote_clk_enable(c); + if (ret == 0) + *pllv->soft_vote |= (pllv->soft_vote_mask); + + spin_unlock_irqrestore(&soft_vote_lock, flags); + return ret; +} + +static void pll_acpu_vote_clk_disable(struct clk *c) +{ + unsigned long flags; + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + + spin_lock_irqsave(&soft_vote_lock, flags); + + *pllv->soft_vote &= ~(pllv->soft_vote_mask); + if (!*pllv->soft_vote) + pll_vote_clk_disable(c); + + spin_unlock_irqrestore(&soft_vote_lock, flags); +} + +static enum handoff pll_acpu_vote_clk_handoff(struct clk *c) +{ + if (pll_vote_clk_handoff(c) == HANDOFF_DISABLED_CLK) + return HANDOFF_DISABLED_CLK; + + if (pll_acpu_vote_clk_enable(c)) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +struct clk_ops clk_ops_pll_acpu_vote = { + .enable = pll_acpu_vote_clk_enable, + .disable = pll_acpu_vote_clk_disable, + .is_enabled = pll_vote_clk_is_enabled, + .handoff = pll_acpu_vote_clk_handoff, + .list_registers = pll_vote_clk_list_registers, +}; + + +static int pll_sleep_clk_enable(struct clk *c) +{ + u32 ena; + unsigned long flags; + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + + spin_lock_irqsave(&pll_reg_lock, flags); + ena = readl_relaxed(PLL_EN_REG(pllv)); + ena &= ~(pllv->en_mask); + writel_relaxed(ena, PLL_EN_REG(pllv)); + spin_unlock_irqrestore(&pll_reg_lock, flags); + return 0; +} + +static void pll_sleep_clk_disable(struct clk *c) +{ + u32 ena; + unsigned long flags; + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + + spin_lock_irqsave(&pll_reg_lock, flags); + ena = readl_relaxed(PLL_EN_REG(pllv)); + ena |= pllv->en_mask; + writel_relaxed(ena, PLL_EN_REG(pllv)); + spin_unlock_irqrestore(&pll_reg_lock, flags); +} + +static enum handoff pll_sleep_clk_handoff(struct clk *c) +{ + struct pll_vote_clk *pllv = to_pll_vote_clk(c); + + if (!(readl_relaxed(PLL_EN_REG(pllv)) & pllv->en_mask)) + return HANDOFF_ENABLED_CLK; + + return HANDOFF_DISABLED_CLK; +} + +/* + * This .ops is meant to be used by gpll0_sleep_clk_src. The aim is to utilise + * the h/w feature of sleep enable bit to denote if the PLL can be turned OFF + * once APPS goes to PC. gpll0_sleep_clk_src will be enabled only if there is a + * peripheral client using it and disabled if there is none. The current + * implementation of enable .ops clears the h/w bit of sleep enable while the + * disable .ops asserts it. + */ + +struct clk_ops clk_ops_pll_sleep_vote = { + .enable = pll_sleep_clk_enable, + .disable = pll_sleep_clk_disable, + .handoff = pll_sleep_clk_handoff, + .list_registers = pll_vote_clk_list_registers, +}; + +static void __set_fsm_mode(void __iomem *mode_reg, + u32 bias_count, u32 lock_count) +{ + u32 regval = readl_relaxed(mode_reg); + + /* De-assert reset to FSM */ + regval &= ~BIT(21); + writel_relaxed(regval, mode_reg); + + /* Program bias count */ + regval &= ~BM(19, 14); + regval |= BVAL(19, 14, bias_count); + writel_relaxed(regval, mode_reg); + + /* Program lock count */ + regval &= ~BM(13, 8); + regval |= BVAL(13, 8, lock_count); + writel_relaxed(regval, mode_reg); + + /* Enable PLL FSM voting */ + regval |= BIT(20); + writel_relaxed(regval, mode_reg); +} + +static void __configure_alt_config(struct pll_alt_config config, + struct pll_config_regs *regs) +{ + u32 regval; + + regval = readl_relaxed(PLL_CFG_ALT_REG(regs)); + + if (config.mask) { + regval &= ~config.mask; + regval |= config.val; + } + + writel_relaxed(regval, PLL_CFG_ALT_REG(regs)); +} + +void __configure_pll(struct pll_config *config, + struct pll_config_regs *regs, u32 ena_fsm_mode) +{ + u32 regval; + + writel_relaxed(config->l, PLL_L_REG(regs)); + writel_relaxed(config->m, PLL_M_REG(regs)); + writel_relaxed(config->n, PLL_N_REG(regs)); + + regval = readl_relaxed(PLL_CONFIG_REG(regs)); + + /* Enable the MN accumulator */ + if (config->mn_ena_mask) { + regval &= ~config->mn_ena_mask; + regval |= config->mn_ena_val; + } + + /* Enable the main output */ + if (config->main_output_mask) { + regval &= ~config->main_output_mask; + regval |= config->main_output_val; + } + + /* Enable the aux output */ + if (config->aux_output_mask) { + regval &= ~config->aux_output_mask; + regval |= config->aux_output_val; + } + + /* Set pre-divider and post-divider values */ + regval &= ~config->pre_div_mask; + regval |= config->pre_div_val; + regval &= ~config->post_div_mask; + regval |= config->post_div_val; + + /* Select VCO setting */ + regval &= ~config->vco_mask; + regval |= config->vco_val; + + if (config->add_factor_mask) { + regval &= ~config->add_factor_mask; + regval |= config->add_factor_val; + } + + writel_relaxed(regval, PLL_CONFIG_REG(regs)); + + if (regs->config_alt_reg) + __configure_alt_config(config->alt_cfg, regs); + + if (regs->config_ctl_reg) + writel_relaxed(config->cfg_ctl_val, PLL_CFG_CTL_REG(regs)); +} + +void configure_sr_pll(struct pll_config *config, + struct pll_config_regs *regs, u32 ena_fsm_mode) +{ + __configure_pll(config, regs, ena_fsm_mode); + if (ena_fsm_mode) + __set_fsm_mode(PLL_MODE_REG(regs), 0x1, 0x8); +} + +void configure_sr_hpm_lp_pll(struct pll_config *config, + struct pll_config_regs *regs, u32 ena_fsm_mode) +{ + __configure_pll(config, regs, ena_fsm_mode); + if (ena_fsm_mode) + __set_fsm_mode(PLL_MODE_REG(regs), 0x1, 0x0); +} + +static void *votable_pll_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct pll_vote_clk *v, *peer; + struct clk *c; + u32 val, rc; + phandle p; + struct msmclk_data *drv; + + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); + if (!v) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + drv = msmclk_parse_phandle(dev, np->parent->phandle); + if (IS_ERR_OR_NULL(drv)) + return ERR_CAST(drv); + v->base = &drv->base; + + rc = of_property_read_u32(np, "qcom,en-offset", (u32 *)&v->en_reg); + if (rc) { + dt_err(np, "missing qcom,en-offset dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,en-bit", &val); + if (rc) { + dt_err(np, "missing qcom,en-bit dt property\n"); + return ERR_PTR(-EINVAL); + } + v->en_mask = BIT(val); + + rc = of_property_read_u32(np, "qcom,status-offset", + (u32 *)&v->status_reg); + if (rc) { + dt_err(np, "missing qcom,status-offset dt property\n"); + return ERR_PTR(-EINVAL); + } + + rc = of_property_read_u32(np, "qcom,status-bit", &val); + if (rc) { + dt_err(np, "missing qcom,status-bit dt property\n"); + return ERR_PTR(-EINVAL); + } + v->status_mask = BIT(val); + + rc = of_property_read_u32(np, "qcom,pll-config-rate", &val); + if (rc) { + dt_err(np, "missing qcom,pll-config-rate dt property\n"); + return ERR_PTR(-EINVAL); + } + v->c.rate = val; + + if (of_device_is_compatible(np, "qcom,active-only-pll")) + v->soft_vote_mask = PLL_SOFT_VOTE_ACPU; + else if (of_device_is_compatible(np, "qcom,sleep-active-pll")) + v->soft_vote_mask = PLL_SOFT_VOTE_PRIMARY; + + if (of_device_is_compatible(np, "qcom,votable-pll")) { + v->c.ops = &clk_ops_pll_vote; + return msmclk_generic_clk_init(dev, np, &v->c); + } + + rc = of_property_read_phandle_index(np, "qcom,peer", 0, &p); + if (rc) { + dt_err(np, "missing qcom,peer dt property\n"); + return ERR_PTR(-EINVAL); + } + + c = msmclk_lookup_phandle(dev, p); + if (!IS_ERR_OR_NULL(c)) { + v->soft_vote = devm_kzalloc(dev, sizeof(*v->soft_vote), + GFP_KERNEL); + if (!v->soft_vote) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + peer = to_pll_vote_clk(c); + peer->soft_vote = v->soft_vote; + } + + v->c.ops = &clk_ops_pll_acpu_vote; + return msmclk_generic_clk_init(dev, np, &v->c); +} +MSMCLK_PARSER(votable_pll_clk_dt_parser, "qcom,active-only-pll", 0); +MSMCLK_PARSER(votable_pll_clk_dt_parser, "qcom,sleep-active-pll", 1); +MSMCLK_PARSER(votable_pll_clk_dt_parser, "qcom,votable-pll", 2); diff --git a/drivers/clk/msm/clock-rpm.c b/drivers/clk/msm/clock-rpm.c new file mode 100644 index 000000000000..ac093463ff23 --- /dev/null +++ b/drivers/clk/msm/clock-rpm.c @@ -0,0 +1,472 @@ +/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/err.h> +#include <linux/rtmutex.h> +#include <linux/clk/msm-clk-provider.h> +#include <soc/qcom/clock-rpm.h> +#include <soc/qcom/msm-clock-controller.h> + +#define __clk_rpmrs_set_rate(r, value, ctx) \ + ((r)->rpmrs_data->set_rate_fn((r), (value), (ctx))) + +#define clk_rpmrs_set_rate_sleep(r, value) \ + __clk_rpmrs_set_rate((r), (value), (r)->rpmrs_data->ctx_sleep_id) + +#define clk_rpmrs_set_rate_active(r, value) \ + __clk_rpmrs_set_rate((r), (value), (r)->rpmrs_data->ctx_active_id) + +static int clk_rpmrs_set_rate_smd(struct rpm_clk *r, uint32_t value, + uint32_t context) +{ + int ret; + + struct msm_rpm_kvp kvp = { + .key = r->rpm_key, + .data = (void *)&value, + .length = sizeof(value), + }; + + switch (context) { + case MSM_RPM_CTX_ACTIVE_SET: + if (*r->last_active_set_vote == value) + return 0; + break; + case MSM_RPM_CTX_SLEEP_SET: + if (*r->last_sleep_set_vote == value) + return 0; + break; + default: + return -EINVAL; + }; + + ret = msm_rpm_send_message(context, r->rpm_res_type, r->rpm_clk_id, + &kvp, 1); + if (ret) + return ret; + + switch (context) { + case MSM_RPM_CTX_ACTIVE_SET: + *r->last_active_set_vote = value; + break; + case MSM_RPM_CTX_SLEEP_SET: + *r->last_sleep_set_vote = value; + break; + } + + return 0; +} + +static int clk_rpmrs_handoff_smd(struct rpm_clk *r) +{ + if (!r->branch) + r->c.rate = INT_MAX; + + return 0; +} + +static int clk_rpmrs_is_enabled_smd(struct rpm_clk *r) +{ + return !!r->c.prepare_count; +} + +struct clk_rpmrs_data { + int (*set_rate_fn)(struct rpm_clk *r, uint32_t value, uint32_t context); + int (*get_rate_fn)(struct rpm_clk *r); + int (*handoff_fn)(struct rpm_clk *r); + int (*is_enabled)(struct rpm_clk *r); + int ctx_active_id; + int ctx_sleep_id; +}; + +struct clk_rpmrs_data clk_rpmrs_data_smd = { + .set_rate_fn = clk_rpmrs_set_rate_smd, + .handoff_fn = clk_rpmrs_handoff_smd, + .is_enabled = clk_rpmrs_is_enabled_smd, + .ctx_active_id = MSM_RPM_CTX_ACTIVE_SET, + .ctx_sleep_id = MSM_RPM_CTX_SLEEP_SET, +}; + +static DEFINE_RT_MUTEX(rpm_clock_lock); + +static void to_active_sleep_khz(struct rpm_clk *r, unsigned long rate, + unsigned long *active_khz, unsigned long *sleep_khz) +{ + /* Convert the rate (hz) to khz */ + *active_khz = DIV_ROUND_UP(rate, 1000); + + /* + * Active-only clocks don't care what the rate is during sleep. So, + * they vote for zero. + */ + if (r->active_only) + *sleep_khz = 0; + else + *sleep_khz = *active_khz; +} + +static int rpm_clk_prepare(struct clk *clk) +{ + struct rpm_clk *r = to_rpm_clk(clk); + uint32_t value; + int rc = 0; + unsigned long this_khz, this_sleep_khz; + unsigned long peer_khz = 0, peer_sleep_khz = 0; + struct rpm_clk *peer = r->peer; + + rt_mutex_lock(&rpm_clock_lock); + + to_active_sleep_khz(r, r->c.rate, &this_khz, &this_sleep_khz); + + /* Don't send requests to the RPM if the rate has not been set. */ + if (this_khz == 0) + goto out; + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) + to_active_sleep_khz(peer, peer->c.rate, + &peer_khz, &peer_sleep_khz); + + value = max(this_khz, peer_khz); + if (r->branch) + value = !!value; + + rc = clk_rpmrs_set_rate_active(r, value); + if (rc) + goto out; + + value = max(this_sleep_khz, peer_sleep_khz); + if (r->branch) + value = !!value; + + rc = clk_rpmrs_set_rate_sleep(r, value); + if (rc) { + /* Undo the active set vote and restore it to peer_khz */ + value = peer_khz; + rc = clk_rpmrs_set_rate_active(r, value); + } + +out: + if (!rc) + r->enabled = true; + + rt_mutex_unlock(&rpm_clock_lock); + + return rc; +} + +static void rpm_clk_unprepare(struct clk *clk) +{ + struct rpm_clk *r = to_rpm_clk(clk); + + rt_mutex_lock(&rpm_clock_lock); + + if (r->c.rate) { + uint32_t value; + struct rpm_clk *peer = r->peer; + unsigned long peer_khz = 0, peer_sleep_khz = 0; + int rc; + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) + to_active_sleep_khz(peer, peer->c.rate, + &peer_khz, &peer_sleep_khz); + + value = r->branch ? !!peer_khz : peer_khz; + rc = clk_rpmrs_set_rate_active(r, value); + if (rc) + goto out; + + value = r->branch ? !!peer_sleep_khz : peer_sleep_khz; + rc = clk_rpmrs_set_rate_sleep(r, value); + } + r->enabled = false; +out: + rt_mutex_unlock(&rpm_clock_lock); + + return; +} + +static int rpm_clk_set_rate(struct clk *clk, unsigned long rate) +{ + struct rpm_clk *r = to_rpm_clk(clk); + unsigned long this_khz, this_sleep_khz; + int rc = 0; + + rt_mutex_lock(&rpm_clock_lock); + + if (r->enabled) { + uint32_t value; + struct rpm_clk *peer = r->peer; + unsigned long peer_khz = 0, peer_sleep_khz = 0; + + to_active_sleep_khz(r, rate, &this_khz, &this_sleep_khz); + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) + to_active_sleep_khz(peer, peer->c.rate, + &peer_khz, &peer_sleep_khz); + + value = max(this_khz, peer_khz); + rc = clk_rpmrs_set_rate_active(r, value); + if (rc) + goto out; + + value = max(this_sleep_khz, peer_sleep_khz); + rc = clk_rpmrs_set_rate_sleep(r, value); + } + +out: + rt_mutex_unlock(&rpm_clock_lock); + + return rc; +} + +static unsigned long rpm_clk_get_rate(struct clk *clk) +{ + struct rpm_clk *r = to_rpm_clk(clk); + if (r->rpmrs_data->get_rate_fn) + return r->rpmrs_data->get_rate_fn(r); + else + return clk->rate; +} + +static int rpm_clk_is_enabled(struct clk *clk) +{ + struct rpm_clk *r = to_rpm_clk(clk); + return r->rpmrs_data->is_enabled(r); +} + +static long rpm_clk_round_rate(struct clk *clk, unsigned long rate) +{ + /* Not supported. */ + return rate; +} + +static bool rpm_clk_is_local(struct clk *clk) +{ + return false; +} + +static enum handoff rpm_clk_handoff(struct clk *clk) +{ + struct rpm_clk *r = to_rpm_clk(clk); + int rc; + + /* + * Querying an RPM clock's status will return 0 unless the clock's + * rate has previously been set through the RPM. When handing off, + * assume these clocks are enabled (unless the RPM call fails) so + * child clocks of these RPM clocks can still be handed off. + */ + rc = r->rpmrs_data->handoff_fn(r); + if (rc < 0) + return HANDOFF_DISABLED_CLK; + + /* + * Since RPM handoff code may update the software rate of the clock by + * querying the RPM, we need to make sure our request to RPM now + * matches the software rate of the clock. When we send the request + * to RPM, we also need to update any other state info we would + * normally update. So, call the appropriate clock function instead + * of directly using the RPM driver APIs. + */ + rc = rpm_clk_prepare(clk); + if (rc < 0) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +#define RPM_MISC_CLK_TYPE 0x306b6c63 +#define RPM_SCALING_ENABLE_ID 0x2 + +int enable_rpm_scaling(void) +{ + int rc, value = 0x1; + static int is_inited; + + struct msm_rpm_kvp kvp = { + .key = RPM_SMD_KEY_ENABLE, + .data = (void *)&value, + .length = sizeof(value), + }; + + if (is_inited) + return 0; + + rc = msm_rpm_send_message_noirq(MSM_RPM_CTX_SLEEP_SET, + RPM_MISC_CLK_TYPE, RPM_SCALING_ENABLE_ID, &kvp, 1); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + WARN(1, "RPM clock scaling (sleep set) did not enable!\n"); + return rc; + } + + rc = msm_rpm_send_message_noirq(MSM_RPM_CTX_ACTIVE_SET, + RPM_MISC_CLK_TYPE, RPM_SCALING_ENABLE_ID, &kvp, 1); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + WARN(1, "RPM clock scaling (active set) did not enable!\n"); + return rc; + } + + is_inited++; + return 0; +} + +int vote_bimc(struct rpm_clk *r, uint32_t value) +{ + int rc; + + struct msm_rpm_kvp kvp = { + .key = r->rpm_key, + .data = (void *)&value, + .length = sizeof(value), + }; + + rc = msm_rpm_send_message_noirq(MSM_RPM_CTX_ACTIVE_SET, + r->rpm_res_type, r->rpmrs_data->ctx_active_id, + &kvp, 1); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + WARN(1, "BIMC vote not sent!\n"); + return rc; + } + + return rc; +} + +struct clk_ops clk_ops_rpm = { + .prepare = rpm_clk_prepare, + .unprepare = rpm_clk_unprepare, + .set_rate = rpm_clk_set_rate, + .get_rate = rpm_clk_get_rate, + .is_enabled = rpm_clk_is_enabled, + .round_rate = rpm_clk_round_rate, + .is_local = rpm_clk_is_local, + .handoff = rpm_clk_handoff, +}; + +struct clk_ops clk_ops_rpm_branch = { + .prepare = rpm_clk_prepare, + .unprepare = rpm_clk_unprepare, + .is_local = rpm_clk_is_local, + .handoff = rpm_clk_handoff, +}; + +static struct rpm_clk *rpm_clk_dt_parser_common(struct device *dev, + struct device_node *np) +{ + struct rpm_clk *rpm, *peer; + struct clk *c; + int rc = 0; + phandle p; + const char *str; + + rpm = devm_kzalloc(dev, sizeof(*rpm), GFP_KERNEL); + if (!rpm) { + dt_err(np, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + rc = of_property_read_phandle_index(np, "qcom,rpm-peer", 0, &p); + if (rc) { + dt_err(np, "missing qcom,rpm-peer dt property\n"); + return ERR_PTR(rc); + } + + /* Rely on whoever's called last to setup the circular ref */ + c = msmclk_lookup_phandle(dev, p); + if (!IS_ERR(c)) { + uint32_t *sleep = devm_kzalloc(dev, sizeof(uint32_t), + GFP_KERNEL); + uint32_t *active = + devm_kzalloc(dev, sizeof(uint32_t), + GFP_KERNEL); + + if (!sleep || !active) + return ERR_PTR(-ENOMEM); + peer = to_rpm_clk(c); + peer->peer = rpm; + rpm->peer = peer; + rpm->last_active_set_vote = active; + peer->last_active_set_vote = active; + rpm->last_sleep_set_vote = sleep; + peer->last_sleep_set_vote = sleep; + } + + rpm->rpmrs_data = &clk_rpmrs_data_smd; + rpm->active_only = of_device_is_compatible(np, "qcom,rpm-a-clk") || + of_device_is_compatible(np, "qcom,rpm-branch-a-clk"); + + rc = of_property_read_string(np, "qcom,res-type", &str); + if (rc) { + dt_err(np, "missing qcom,res-type dt property\n"); + return ERR_PTR(rc); + } + if (sscanf(str, "%4c", (char *) &rpm->rpm_res_type) <= 0) + return ERR_PTR(-EINVAL); + + rc = of_property_read_u32(np, "qcom,res-id", &rpm->rpm_clk_id); + if (rc) { + dt_err(np, "missing qcom,res-id dt property\n"); + return ERR_PTR(rc); + } + + rc = of_property_read_string(np, "qcom,key", &str); + if (rc) { + dt_err(np, "missing qcom,key dt property\n"); + return ERR_PTR(rc); + } + if (sscanf(str, "%4c", (char *) &rpm->rpm_key) <= 0) + return ERR_PTR(-EINVAL); + return rpm; +} + +static void *rpm_clk_dt_parser(struct device *dev, struct device_node *np) +{ + struct rpm_clk *rpm; + rpm = rpm_clk_dt_parser_common(dev, np); + if (IS_ERR(rpm)) + return rpm; + + rpm->c.ops = &clk_ops_rpm; + return msmclk_generic_clk_init(dev, np, &rpm->c); +} + +static void *rpm_branch_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct rpm_clk *rpm; + u32 rate; + int rc; + rpm = rpm_clk_dt_parser_common(dev, np); + if (IS_ERR(rpm)) + return rpm; + + rpm->c.ops = &clk_ops_rpm_branch; + rpm->branch = true; + + rc = of_property_read_u32(np, "qcom,rcg-init-rate", &rate); + if (!rc) + rpm->c.rate = rate; + + return msmclk_generic_clk_init(dev, np, &rpm->c); +} +MSMCLK_PARSER(rpm_clk_dt_parser, "qcom,rpm-clk", 0); +MSMCLK_PARSER(rpm_clk_dt_parser, "qcom,rpm-a-clk", 1); +MSMCLK_PARSER(rpm_branch_clk_dt_parser, "qcom,rpm-branch-clk", 0); +MSMCLK_PARSER(rpm_branch_clk_dt_parser, "qcom,rpm-branch-a-clk", 1); diff --git a/drivers/clk/msm/clock-voter.c b/drivers/clk/msm/clock-voter.c new file mode 100644 index 000000000000..0d609743ffc7 --- /dev/null +++ b/drivers/clk/msm/clock-voter.c @@ -0,0 +1,202 @@ +/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/err.h> +#include <linux/rtmutex.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk-provider.h> +#include <soc/qcom/clock-voter.h> +#include <soc/qcom/msm-clock-controller.h> + +static DEFINE_RT_MUTEX(voter_clk_lock); + +/* Aggregate the rate of clocks that are currently on. */ +static unsigned long voter_clk_aggregate_rate(const struct clk *parent) +{ + struct clk *clk; + unsigned long rate = 0; + + list_for_each_entry(clk, &parent->children, siblings) { + struct clk_voter *v = to_clk_voter(clk); + if (v->enabled) + rate = max(clk->rate, rate); + } + return rate; +} + +static int voter_clk_set_rate(struct clk *clk, unsigned long rate) +{ + int ret = 0; + struct clk *clkp; + struct clk_voter *clkh, *v = to_clk_voter(clk); + unsigned long cur_rate, new_rate, other_rate = 0; + + if (v->is_branch) + return 0; + + rt_mutex_lock(&voter_clk_lock); + + if (v->enabled) { + struct clk *parent = clk->parent; + + /* + * Get the aggregate rate without this clock's vote and update + * if the new rate is different than the current rate + */ + list_for_each_entry(clkp, &parent->children, siblings) { + clkh = to_clk_voter(clkp); + if (clkh->enabled && clkh != v) + other_rate = max(clkp->rate, other_rate); + } + + cur_rate = max(other_rate, clk->rate); + new_rate = max(other_rate, rate); + + if (new_rate != cur_rate) { + ret = clk_set_rate(parent, new_rate); + if (ret) + goto unlock; + } + } + clk->rate = rate; +unlock: + rt_mutex_unlock(&voter_clk_lock); + + return ret; +} + +static int voter_clk_prepare(struct clk *clk) +{ + int ret = 0; + unsigned long cur_rate; + struct clk *parent; + struct clk_voter *v = to_clk_voter(clk); + + rt_mutex_lock(&voter_clk_lock); + parent = clk->parent; + + if (v->is_branch) { + v->enabled = true; + goto out; + } + + /* + * Increase the rate if this clock is voting for a higher rate + * than the current rate. + */ + cur_rate = voter_clk_aggregate_rate(parent); + if (clk->rate > cur_rate) { + ret = clk_set_rate(parent, clk->rate); + if (ret) + goto out; + } + v->enabled = true; +out: + rt_mutex_unlock(&voter_clk_lock); + + return ret; +} + +static void voter_clk_unprepare(struct clk *clk) +{ + unsigned long cur_rate, new_rate; + struct clk *parent; + struct clk_voter *v = to_clk_voter(clk); + + + rt_mutex_lock(&voter_clk_lock); + parent = clk->parent; + + /* + * Decrease the rate if this clock was the only one voting for + * the highest rate. + */ + v->enabled = false; + if (v->is_branch) + goto out; + + new_rate = voter_clk_aggregate_rate(parent); + cur_rate = max(new_rate, clk->rate); + + if (new_rate < cur_rate) + clk_set_rate(parent, new_rate); + +out: + rt_mutex_unlock(&voter_clk_lock); +} + +static int voter_clk_is_enabled(struct clk *clk) +{ + struct clk_voter *v = to_clk_voter(clk); + return v->enabled; +} + +static long voter_clk_round_rate(struct clk *clk, unsigned long rate) +{ + return clk_round_rate(clk->parent, rate); +} + +static bool voter_clk_is_local(struct clk *clk) +{ + return true; +} + +static enum handoff voter_clk_handoff(struct clk *clk) +{ + if (!clk->rate) + return HANDOFF_DISABLED_CLK; + + /* + * Send the default rate to the parent if necessary and update the + * software state of the voter clock. + */ + if (voter_clk_prepare(clk) < 0) + return HANDOFF_DISABLED_CLK; + + return HANDOFF_ENABLED_CLK; +} + +struct clk_ops clk_ops_voter = { + .prepare = voter_clk_prepare, + .unprepare = voter_clk_unprepare, + .set_rate = voter_clk_set_rate, + .is_enabled = voter_clk_is_enabled, + .round_rate = voter_clk_round_rate, + .is_local = voter_clk_is_local, + .handoff = voter_clk_handoff, +}; + +static void *sw_vote_clk_dt_parser(struct device *dev, + struct device_node *np) +{ + struct clk_voter *v; + int rc; + u32 temp; + + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); + if (!v) { + dt_err(np, "failed to alloc memory\n"); + return ERR_PTR(-ENOMEM); + } + + rc = of_property_read_u32(np, "qcom,config-rate", &temp); + if (rc) { + dt_prop_err(np, "qcom,config-rate", "is missing"); + return ERR_PTR(rc); + } + + v->c.ops = &clk_ops_voter; + return msmclk_generic_clk_init(dev, np, &v->c); +} +MSMCLK_PARSER(sw_vote_clk_dt_parser, "qcom,sw-vote-clk", 0); diff --git a/drivers/clk/msm/clock.c b/drivers/clk/msm/clock.c new file mode 100644 index 000000000000..8b30a1bc7eb5 --- /dev/null +++ b/drivers/clk/msm/clock.c @@ -0,0 +1,1389 @@ +/* arch/arm/mach-msm/clock.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2015, The Linux Foundation. All rights reserved. + * + * 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/kernel.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/list.h> +#include <linux/regulator/consumer.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/clk/msm-clk-provider.h> +#include <linux/of_platform.h> +#include <linux/pm_opp.h> + +#include <trace/events/power.h> +#include "clock.h" + +struct handoff_clk { + struct list_head list; + struct clk *clk; +}; +static LIST_HEAD(handoff_list); + +struct handoff_vdd { + struct list_head list; + struct clk_vdd_class *vdd_class; +}; +static LIST_HEAD(handoff_vdd_list); + +static DEFINE_MUTEX(msm_clock_init_lock); +LIST_HEAD(orphan_clk_list); +static LIST_HEAD(clk_notifier_list); + +/* Find the voltage level required for a given rate. */ +int find_vdd_level(struct clk *clk, unsigned long rate) +{ + int level; + + for (level = 0; level < clk->num_fmax; level++) + if (rate <= clk->fmax[level]) + break; + + if (level == clk->num_fmax) { + pr_err("Rate %lu for %s is greater than highest Fmax\n", rate, + clk->dbg_name); + return -EINVAL; + } + + return level; +} + +/* Update voltage level given the current votes. */ +static int update_vdd(struct clk_vdd_class *vdd_class) +{ + int level, rc = 0, i, ignore; + struct regulator **r = vdd_class->regulator; + int *uv = vdd_class->vdd_uv; + int *ua = vdd_class->vdd_ua; + int n_reg = vdd_class->num_regulators; + int cur_lvl = vdd_class->cur_level; + int max_lvl = vdd_class->num_levels - 1; + int cur_base = cur_lvl * n_reg; + int new_base; + + /* aggregate votes */ + for (level = max_lvl; level > 0; level--) + if (vdd_class->level_votes[level]) + break; + + if (level == cur_lvl) + return 0; + + max_lvl = max_lvl * n_reg; + new_base = level * n_reg; + for (i = 0; i < vdd_class->num_regulators; i++) { + rc = regulator_set_voltage(r[i], uv[new_base + i], + vdd_class->use_max_uV ? INT_MAX : uv[max_lvl + i]); + if (rc) + goto set_voltage_fail; + + if (ua) { + rc = regulator_set_load(r[i], ua[new_base + i]); + rc = rc > 0 ? 0 : rc; + if (rc) + goto set_mode_fail; + } + if (cur_lvl == 0 || cur_lvl == vdd_class->num_levels) + rc = regulator_enable(r[i]); + else if (level == 0) + rc = regulator_disable(r[i]); + if (rc) + goto enable_disable_fail; + } + if (vdd_class->set_vdd && !vdd_class->num_regulators) + rc = vdd_class->set_vdd(vdd_class, level); + + if (!rc) + vdd_class->cur_level = level; + + return rc; + +enable_disable_fail: + /* + * set_optimum_mode could use voltage to derive mode. Restore + * previous voltage setting for r[i] first. + */ + if (ua) { + regulator_set_voltage(r[i], uv[cur_base + i], + vdd_class->use_max_uV ? INT_MAX : uv[max_lvl + i]); + regulator_set_load(r[i], ua[cur_base + i]); + } + +set_mode_fail: + regulator_set_voltage(r[i], uv[cur_base + i], + vdd_class->use_max_uV ? INT_MAX : uv[max_lvl + i]); + +set_voltage_fail: + for (i--; i >= 0; i--) { + regulator_set_voltage(r[i], uv[cur_base + i], + vdd_class->use_max_uV ? INT_MAX : uv[max_lvl + i]); + if (ua) + regulator_set_load(r[i], ua[cur_base + i]); + if (cur_lvl == 0 || cur_lvl == vdd_class->num_levels) + regulator_disable(r[i]); + else if (level == 0) + ignore = regulator_enable(r[i]); + } + return rc; +} + +/* Vote for a voltage level. */ +int vote_vdd_level(struct clk_vdd_class *vdd_class, int level) +{ + int rc; + + if (level >= vdd_class->num_levels) + return -EINVAL; + + mutex_lock(&vdd_class->lock); + vdd_class->level_votes[level]++; + rc = update_vdd(vdd_class); + if (rc) + vdd_class->level_votes[level]--; + mutex_unlock(&vdd_class->lock); + + return rc; +} + +/* Remove vote for a voltage level. */ +int unvote_vdd_level(struct clk_vdd_class *vdd_class, int level) +{ + int rc = 0; + + if (level >= vdd_class->num_levels) + return -EINVAL; + + mutex_lock(&vdd_class->lock); + if (WARN(!vdd_class->level_votes[level], + "Reference counts are incorrect for %s level %d\n", + vdd_class->class_name, level)) + goto out; + vdd_class->level_votes[level]--; + rc = update_vdd(vdd_class); + if (rc) + vdd_class->level_votes[level]++; +out: + mutex_unlock(&vdd_class->lock); + return rc; +} + +/* Vote for a voltage level corresponding to a clock's rate. */ +static int vote_rate_vdd(struct clk *clk, unsigned long rate) +{ + int level; + + if (!clk->vdd_class) + return 0; + + level = find_vdd_level(clk, rate); + if (level < 0) + return level; + + return vote_vdd_level(clk->vdd_class, level); +} + +/* Remove vote for a voltage level corresponding to a clock's rate. */ +static void unvote_rate_vdd(struct clk *clk, unsigned long rate) +{ + int level; + + if (!clk->vdd_class) + return; + + level = find_vdd_level(clk, rate); + if (level < 0) + return; + + unvote_vdd_level(clk->vdd_class, level); +} + +/* Check if the rate is within the voltage limits of the clock. */ +bool is_rate_valid(struct clk *clk, unsigned long rate) +{ + int level; + + if (!clk->vdd_class) + return true; + + level = find_vdd_level(clk, rate); + return level >= 0; +} + +/** + * __clk_pre_reparent() - Set up the new parent before switching to it and + * prevent the enable state of the child clock from changing. + * @c: The child clock that's going to switch parents + * @new: The new parent that the child clock is going to switch to + * @flags: Pointer to scratch space to save spinlock flags + * + * Cannot be called from atomic context. + * + * Use this API to set up the @new parent clock to be able to support the + * current prepare and enable state of the child clock @c. Once the parent is + * set up, the child clock can safely switch to it. + * + * The caller shall grab the prepare_lock of clock @c before calling this API + * and only release it after calling __clk_post_reparent() for clock @c (or + * if this API fails). This is necessary to prevent the prepare state of the + * child clock @c from changing while the reparenting is in progress. Since + * this API takes care of grabbing the enable lock of @c, only atomic + * operation are allowed between calls to __clk_pre_reparent and + * __clk_post_reparent() + * + * The scratch space pointed to by @flags should not be altered before + * calling __clk_post_reparent() for clock @c. + * + * See also: __clk_post_reparent() + */ +int __clk_pre_reparent(struct clk *c, struct clk *new, unsigned long *flags) +{ + int rc; + + if (c->prepare_count) { + rc = clk_prepare(new); + if (rc) + return rc; + } + + spin_lock_irqsave(&c->lock, *flags); + if (c->count) { + rc = clk_enable(new); + if (rc) { + spin_unlock_irqrestore(&c->lock, *flags); + clk_unprepare(new); + return rc; + } + } + return 0; +} + +/** + * __clk_post_reparent() - Release requirements on old parent after switching + * away from it and allow changes to the child clock's enable state. + * @c: The child clock that switched parents + * @old: The old parent that the child clock switched away from or the new + * parent of a failed reparent attempt. + * @flags: Pointer to scratch space where spinlock flags were saved + * + * Cannot be called from atomic context. + * + * This API works in tandem with __clk_pre_reparent. Use this API to + * - Remove prepare and enable requirements from the @old parent after + * switching away from it + * - Or, undo the effects of __clk_pre_reparent() after a failed attempt to + * change parents + * + * The caller shall release the prepare_lock of @c that was grabbed before + * calling __clk_pre_reparent() only after this API is called (or if + * __clk_pre_reparent() fails). This is necessary to prevent the prepare + * state of the child clock @c from changing while the reparenting is in + * progress. Since this API releases the enable lock of @c, the limit to + * atomic operations set by __clk_pre_reparent() is no longer present. + * + * The scratch space pointed to by @flags shall not be altered since the call + * to __clk_pre_reparent() for clock @c. + * + * See also: __clk_pre_reparent() + */ +void __clk_post_reparent(struct clk *c, struct clk *old, unsigned long *flags) +{ + if (c->count) + clk_disable(old); + spin_unlock_irqrestore(&c->lock, *flags); + + if (c->prepare_count) + clk_unprepare(old); +} + +int clk_prepare(struct clk *clk) +{ + int ret = 0; + struct clk *parent; + + if (!clk) + return 0; + if (IS_ERR(clk)) + return -EINVAL; + + mutex_lock(&clk->prepare_lock); + if (clk->prepare_count == 0) { + parent = clk->parent; + + ret = clk_prepare(parent); + if (ret) + goto out; + ret = clk_prepare(clk->depends); + if (ret) + goto err_prepare_depends; + + ret = vote_rate_vdd(clk, clk->rate); + if (ret) + goto err_vote_vdd; + if (clk->ops->prepare) + ret = clk->ops->prepare(clk); + if (ret) + goto err_prepare_clock; + } + clk->prepare_count++; +out: + mutex_unlock(&clk->prepare_lock); + return ret; +err_prepare_clock: + unvote_rate_vdd(clk, clk->rate); +err_vote_vdd: + clk_unprepare(clk->depends); +err_prepare_depends: + clk_unprepare(parent); + goto out; +} +EXPORT_SYMBOL(clk_prepare); + +/* + * Standard clock functions defined in include/linux/clk.h + */ +int clk_enable(struct clk *clk) +{ + int ret = 0; + unsigned long flags; + struct clk *parent; + const char *name; + + if (!clk) + return 0; + if (IS_ERR(clk)) + return -EINVAL; + name = clk->dbg_name; + + spin_lock_irqsave(&clk->lock, flags); + WARN(!clk->prepare_count, + "%s: Don't call enable on unprepared clocks\n", name); + if (clk->count == 0) { + parent = clk->parent; + + ret = clk_enable(parent); + if (ret) + goto err_enable_parent; + ret = clk_enable(clk->depends); + if (ret) + goto err_enable_depends; + + trace_clock_enable(name, 1, smp_processor_id()); + if (clk->ops->enable) + ret = clk->ops->enable(clk); + if (ret) + goto err_enable_clock; + } + clk->count++; + spin_unlock_irqrestore(&clk->lock, flags); + + return 0; + +err_enable_clock: + clk_disable(clk->depends); +err_enable_depends: + clk_disable(parent); +err_enable_parent: + spin_unlock_irqrestore(&clk->lock, flags); + return ret; +} +EXPORT_SYMBOL(clk_enable); + +void clk_disable(struct clk *clk) +{ + const char *name; + unsigned long flags; + + if (IS_ERR_OR_NULL(clk)) + return; + name = clk->dbg_name; + + spin_lock_irqsave(&clk->lock, flags); + WARN(!clk->prepare_count, + "%s: Never called prepare or calling disable after unprepare\n", + name); + if (WARN(clk->count == 0, "%s is unbalanced", name)) + goto out; + if (clk->count == 1) { + struct clk *parent = clk->parent; + + trace_clock_disable(name, 0, smp_processor_id()); + if (clk->ops->disable) + clk->ops->disable(clk); + clk_disable(clk->depends); + clk_disable(parent); + } + clk->count--; +out: + spin_unlock_irqrestore(&clk->lock, flags); +} +EXPORT_SYMBOL(clk_disable); + +void clk_unprepare(struct clk *clk) +{ + const char *name; + + if (IS_ERR_OR_NULL(clk)) + return; + name = clk->dbg_name; + + mutex_lock(&clk->prepare_lock); + if (WARN(!clk->prepare_count, "%s is unbalanced (prepare)", name)) + goto out; + if (clk->prepare_count == 1) { + struct clk *parent = clk->parent; + + WARN(clk->count, + "%s: Don't call unprepare when the clock is enabled\n", + name); + + if (clk->ops->unprepare) + clk->ops->unprepare(clk); + unvote_rate_vdd(clk, clk->rate); + clk_unprepare(clk->depends); + clk_unprepare(parent); + } + clk->prepare_count--; +out: + mutex_unlock(&clk->prepare_lock); +} +EXPORT_SYMBOL(clk_unprepare); + +int clk_reset(struct clk *clk, enum clk_reset_action action) +{ + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + + if (!clk->ops->reset) + return -ENOSYS; + + return clk->ops->reset(clk, action); +} +EXPORT_SYMBOL(clk_reset); + +/** + * __clk_notify - call clk notifier chain + * @clk: struct clk * that is changing rate + * @msg: clk notifier type (see include/linux/clk.h) + * @old_rate: old clk rate + * @new_rate: new clk rate + * + * Triggers a notifier call chain on the clk rate-change notification + * for 'clk'. Passes a pointer to the struct clk and the previous + * and current rates to the notifier callback. Intended to be called by + * internal clock code only. Returns NOTIFY_DONE from the last driver + * called if all went well, or NOTIFY_STOP or NOTIFY_BAD immediately if + * a driver returns that. + */ +static int __clk_notify(struct clk *clk, unsigned long msg, + unsigned long old_rate, unsigned long new_rate) +{ + struct msm_clk_notifier *cn; + struct msm_clk_notifier_data cnd; + int ret = NOTIFY_DONE; + + cnd.clk = clk; + cnd.old_rate = old_rate; + cnd.new_rate = new_rate; + + list_for_each_entry(cn, &clk_notifier_list, node) { + if (cn->clk == clk) { + ret = srcu_notifier_call_chain(&cn->notifier_head, msg, + &cnd); + break; + } + } + + return ret; +} + +/* + * clk rate change notifiers + * + * Note - The following notifier functionality is a verbatim copy + * of the implementation in the common clock framework, copied here + * until MSM switches to the common clock framework. + */ + +/** + * msm_clk_notif_register - add a clk rate change notifier + * @clk: struct clk * to watch + * @nb: struct notifier_block * with callback info + * + * Request notification when clk's rate changes. This uses an SRCU + * notifier because we want it to block and notifier unregistrations are + * uncommon. The callbacks associated with the notifier must not + * re-enter into the clk framework by calling any top-level clk APIs; + * this will cause a nested prepare_lock mutex. + * + * Pre-change notifier callbacks will be passed the current, pre-change + * rate of the clk via struct msm_clk_notifier_data.old_rate. The new, + * post-change rate of the clk is passed via struct + * msm_clk_notifier_data.new_rate. + * + * Post-change notifiers will pass the now-current, post-change rate of + * the clk in both struct msm_clk_notifier_data.old_rate and struct + * msm_clk_notifier_data.new_rate. + * + * Abort-change notifiers are effectively the opposite of pre-change + * notifiers: the original pre-change clk rate is passed in via struct + * msm_clk_notifier_data.new_rate and the failed post-change rate is passed + * in via struct msm_clk_notifier_data.old_rate. + * + * msm_clk_notif_register() must be called from non-atomic context. + * Returns -EINVAL if called with null arguments, -ENOMEM upon + * allocation failure; otherwise, passes along the return value of + * srcu_notifier_chain_register(). + */ +int msm_clk_notif_register(struct clk *clk, struct notifier_block *nb) +{ + struct msm_clk_notifier *cn; + int ret = -ENOMEM; + + if (!clk || !nb) + return -EINVAL; + + mutex_lock(&clk->prepare_lock); + + /* search the list of notifiers for this clk */ + list_for_each_entry(cn, &clk_notifier_list, node) + if (cn->clk == clk) + break; + + /* if clk wasn't in the notifier list, allocate new clk_notifier */ + if (cn->clk != clk) { + cn = kzalloc(sizeof(struct msm_clk_notifier), GFP_KERNEL); + if (!cn) + goto out; + + cn->clk = clk; + srcu_init_notifier_head(&cn->notifier_head); + + list_add(&cn->node, &clk_notifier_list); + } + + ret = srcu_notifier_chain_register(&cn->notifier_head, nb); + + clk->notifier_count++; + +out: + mutex_unlock(&clk->prepare_lock); + + return ret; +} + +/** + * msm_clk_notif_unregister - remove a clk rate change notifier + * @clk: struct clk * + * @nb: struct notifier_block * with callback info + * + * Request no further notification for changes to 'clk' and frees memory + * allocated in msm_clk_notifier_register. + * + * Returns -EINVAL if called with null arguments; otherwise, passes + * along the return value of srcu_notifier_chain_unregister(). + */ +int msm_clk_notif_unregister(struct clk *clk, struct notifier_block *nb) +{ + struct msm_clk_notifier *cn = NULL; + int ret = -EINVAL; + + if (!clk || !nb) + return -EINVAL; + + mutex_lock(&clk->prepare_lock); + + list_for_each_entry(cn, &clk_notifier_list, node) + if (cn->clk == clk) + break; + + if (cn->clk == clk) { + ret = srcu_notifier_chain_unregister(&cn->notifier_head, nb); + + clk->notifier_count--; + + /* XXX the notifier code should handle this better */ + if (!cn->notifier_head.head) { + srcu_cleanup_notifier_head(&cn->notifier_head); + list_del(&cn->node); + kfree(cn); + } + + } else { + ret = -ENOENT; + } + + mutex_unlock(&clk->prepare_lock); + + return ret; +} + +unsigned long clk_get_rate(struct clk *clk) +{ + if (IS_ERR_OR_NULL(clk)) + return 0; + + if (!clk->ops->get_rate) + return clk->rate; + + return clk->ops->get_rate(clk); +} +EXPORT_SYMBOL(clk_get_rate); + +int clk_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long start_rate; + int rc = 0; + const char *name; + + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + name = clk->dbg_name; + + if (!is_rate_valid(clk, rate)) + return -EINVAL; + + mutex_lock(&clk->prepare_lock); + + /* Return early if the rate isn't going to change */ + if (clk->rate == rate && !(clk->flags & CLKFLAG_NO_RATE_CACHE)) + goto out; + + if (!clk->ops->set_rate) { + rc = -ENOSYS; + goto out; + } + + trace_clock_set_rate(name, rate, raw_smp_processor_id()); + + start_rate = clk->rate; + + if (clk->notifier_count) + __clk_notify(clk, PRE_RATE_CHANGE, clk->rate, rate); + + if (clk->ops->pre_set_rate) { + rc = clk->ops->pre_set_rate(clk, rate); + if (rc) + goto abort_set_rate; + } + + /* Enforce vdd requirements for target frequency. */ + if (clk->prepare_count) { + rc = vote_rate_vdd(clk, rate); + if (rc) + goto err_vote_vdd; + } + + rc = clk->ops->set_rate(clk, rate); + if (rc) + goto err_set_rate; + clk->rate = rate; + + /* Release vdd requirements for starting frequency. */ + if (clk->prepare_count) + unvote_rate_vdd(clk, start_rate); + + if (clk->ops->post_set_rate) + clk->ops->post_set_rate(clk, start_rate); + + if (clk->notifier_count) + __clk_notify(clk, POST_RATE_CHANGE, start_rate, clk->rate); + + trace_clock_set_rate_complete(name, clk->rate, raw_smp_processor_id()); +out: + mutex_unlock(&clk->prepare_lock); + return rc; + +abort_set_rate: + __clk_notify(clk, ABORT_RATE_CHANGE, clk->rate, rate); +err_set_rate: + if (clk->prepare_count) + unvote_rate_vdd(clk, rate); +err_vote_vdd: + /* clk->rate is still the old rate. So, pass the new rate instead. */ + if (clk->ops->post_set_rate) + clk->ops->post_set_rate(clk, rate); + goto out; +} +EXPORT_SYMBOL(clk_set_rate); + +long clk_round_rate(struct clk *clk, unsigned long rate) +{ + long rrate; + unsigned long fmax = 0, i; + + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + + for (i = 0; i < clk->num_fmax; i++) + fmax = max(fmax, clk->fmax[i]); + if (!fmax) + fmax = ULONG_MAX; + rate = min(rate, fmax); + + if (clk->ops->round_rate) + rrate = clk->ops->round_rate(clk, rate); + else if (clk->rate) + rrate = clk->rate; + else + return -ENOSYS; + + if (rrate > fmax) + return -EINVAL; + return rrate; +} +EXPORT_SYMBOL(clk_round_rate); + +int clk_set_max_rate(struct clk *clk, unsigned long rate) +{ + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + + if (!clk->ops->set_max_rate) + return -ENOSYS; + + return clk->ops->set_max_rate(clk, rate); +} +EXPORT_SYMBOL(clk_set_max_rate); + +int parent_to_src_sel(struct clk_src *parents, int num_parents, struct clk *p) +{ + int i; + + for (i = 0; i < num_parents; i++) { + if (parents[i].src == p) + return parents[i].sel; + } + + return -EINVAL; +} +EXPORT_SYMBOL(parent_to_src_sel); + +int clk_get_parent_sel(struct clk *c, struct clk *parent) +{ + return parent_to_src_sel(c->parents, c->num_parents, parent); +} +EXPORT_SYMBOL(clk_get_parent_sel); + +int clk_set_parent(struct clk *clk, struct clk *parent) +{ + int rc = 0; + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + + if (!clk->ops->set_parent && clk->parent == parent) + return 0; + + if (!clk->ops->set_parent) + return -ENOSYS; + + mutex_lock(&clk->prepare_lock); + if (clk->parent == parent && !(clk->flags & CLKFLAG_NO_RATE_CACHE)) + goto out; + rc = clk->ops->set_parent(clk, parent); +out: + mutex_unlock(&clk->prepare_lock); + + return rc; +} +EXPORT_SYMBOL(clk_set_parent); + +struct clk *clk_get_parent(struct clk *clk) +{ + if (IS_ERR_OR_NULL(clk)) + return NULL; + + return clk->parent; +} +EXPORT_SYMBOL(clk_get_parent); + +int clk_set_flags(struct clk *clk, unsigned long flags) +{ + if (IS_ERR_OR_NULL(clk)) + return -EINVAL; + if (!clk->ops->set_flags) + return -ENOSYS; + + return clk->ops->set_flags(clk, flags); +} +EXPORT_SYMBOL(clk_set_flags); + +static LIST_HEAD(initdata_list); + +static void init_sibling_lists(struct clk_lookup *clock_tbl, size_t num_clocks) +{ + struct clk *clk, *parent; + unsigned n; + + for (n = 0; n < num_clocks; n++) { + clk = clock_tbl[n].clk; + parent = clk->parent; + if (parent && list_empty(&clk->siblings)) + list_add(&clk->siblings, &parent->children); + } +} + +static void vdd_class_init(struct clk_vdd_class *vdd) +{ + struct handoff_vdd *v; + + if (!vdd) + return; + + if (vdd->skip_handoff) + return; + + list_for_each_entry(v, &handoff_vdd_list, list) { + if (v->vdd_class == vdd) + return; + } + + pr_debug("voting for vdd_class %s\n", vdd->class_name); + if (vote_vdd_level(vdd, vdd->num_levels - 1)) + pr_err("failed to vote for %s\n", vdd->class_name); + + v = kmalloc(sizeof(*v), GFP_KERNEL); + if (!v) { + pr_err("Unable to kmalloc. %s will be stuck at max.\n", + vdd->class_name); + return; + } + + v->vdd_class = vdd; + list_add_tail(&v->list, &handoff_vdd_list); +} + +static int __handoff_clk(struct clk *clk) +{ + enum handoff state = HANDOFF_DISABLED_CLK; + struct handoff_clk *h = NULL; + int rc, i; + + if (clk == NULL || clk->flags & CLKFLAG_INIT_DONE || + clk->flags & CLKFLAG_SKIP_HANDOFF) + return 0; + + if (clk->flags & CLKFLAG_INIT_ERR) + return -ENXIO; + + if (clk->flags & CLKFLAG_EPROBE_DEFER) + return -EPROBE_DEFER; + + /* Handoff any 'depends' clock first. */ + rc = __handoff_clk(clk->depends); + if (rc) + goto err; + + /* + * Handoff functions for the parent must be called before the + * children can be handed off. Without handing off the parents and + * knowing their rate and state (on/off), it's impossible to figure + * out the rate and state of the children. + */ + if (clk->ops->get_parent) + clk->parent = clk->ops->get_parent(clk); + + if (IS_ERR(clk->parent)) { + rc = PTR_ERR(clk->parent); + goto err; + } + + rc = __handoff_clk(clk->parent); + if (rc) + goto err; + + for (i = 0; i < clk->num_parents; i++) { + rc = __handoff_clk(clk->parents[i].src); + if (rc) + goto err; + } + + if (clk->ops->handoff) + state = clk->ops->handoff(clk); + + if (state == HANDOFF_ENABLED_CLK) { + + h = kmalloc(sizeof(*h), GFP_KERNEL); + if (!h) { + rc = -ENOMEM; + goto err; + } + + rc = clk_prepare_enable(clk->parent); + if (rc) + goto err; + + rc = clk_prepare_enable(clk->depends); + if (rc) + goto err_depends; + + rc = vote_rate_vdd(clk, clk->rate); + WARN(rc, "%s unable to vote for voltage!\n", clk->dbg_name); + + clk->count = 1; + clk->prepare_count = 1; + h->clk = clk; + list_add_tail(&h->list, &handoff_list); + + pr_debug("Handed off %s rate=%lu\n", clk->dbg_name, clk->rate); + } + + if (clk->init_rate && clk_set_rate(clk, clk->init_rate)) + pr_err("failed to set an init rate of %lu on %s\n", + clk->init_rate, clk->dbg_name); + if (clk->always_on && clk_prepare_enable(clk)) + pr_err("failed to enable always-on clock %s\n", + clk->dbg_name); + + clk->flags |= CLKFLAG_INIT_DONE; + /* if the clk is on orphan list, remove it */ + list_del_init(&clk->list); + clock_debug_register(clk); + + return 0; + +err_depends: + clk_disable_unprepare(clk->parent); +err: + kfree(h); + if (rc == -EPROBE_DEFER) { + clk->flags |= CLKFLAG_EPROBE_DEFER; + if (list_empty(&clk->list)) + list_add_tail(&clk->list, &orphan_clk_list); + } else { + pr_err("%s handoff failed (%d)\n", clk->dbg_name, rc); + clk->flags |= CLKFLAG_INIT_ERR; + } + return rc; +} + +/** + * msm_clock_register() - Register additional clock tables + * @table: Table of clocks + * @size: Size of @table + * + * Upon return, clock APIs may be used to control clocks registered using this + * function. + */ +int msm_clock_register(struct clk_lookup *table, size_t size) +{ + int n = 0, rc; + struct clk *c, *safe; + bool found_more_clks; + + mutex_lock(&msm_clock_init_lock); + + init_sibling_lists(table, size); + + /* + * Enable regulators and temporarily set them up at maximum voltage. + * Once all the clocks have made their respective vote, remove this + * temporary vote. The removing of the temporary vote is done at + * late_init, by which time we assume all the clocks would have been + * handed off. + */ + for (n = 0; n < size; n++) + vdd_class_init(table[n].clk->vdd_class); + + /* + * Detect and preserve initial clock state until clock_late_init() or + * a driver explicitly changes it, whichever is first. + */ + + for (n = 0; n < size; n++) + __handoff_clk(table[n].clk); + + /* maintain backwards compatibility */ + if (table[0].con_id || table[0].dev_id) + clkdev_add_table(table, size); + + do { + found_more_clks = false; + /* clear cached __handoff_clk return values */ + list_for_each_entry_safe(c, safe, &orphan_clk_list, list) + c->flags &= ~CLKFLAG_EPROBE_DEFER; + + list_for_each_entry_safe(c, safe, &orphan_clk_list, list) { + rc = __handoff_clk(c); + if (!rc) + found_more_clks = true; + } + } while (found_more_clks); + + mutex_unlock(&msm_clock_init_lock); + + return 0; +} +EXPORT_SYMBOL(msm_clock_register); + +struct of_msm_provider_data { + struct clk_lookup *table; + size_t size; +}; + +static struct clk *of_clk_src_get(struct of_phandle_args *clkspec, + void *data) +{ + struct of_msm_provider_data *ofdata = data; + int n; + + for (n = 0; n < ofdata->size; n++) { + if (clkspec->args[0] == ofdata->table[n].of_idx) + return ofdata->table[n].clk; + } + return ERR_PTR(-ENOENT); +} + +#define MAX_LEN_OPP_HANDLE 50 +#define LEN_OPP_HANDLE 16 +#define LEN_OPP_VCORNER_HANDLE 22 + +static struct device **derive_device_list(struct clk *clk, + struct device_node *np, + char *clk_handle_name, int len) +{ + int j, count, cpu; + struct platform_device *pdev; + struct device_node *dev_node; + struct device **device_list; + + count = len/sizeof(u32); + device_list = kmalloc_array(count, sizeof(struct device *), + GFP_KERNEL); + if (!device_list) + return ERR_PTR(-ENOMEM); + + for (j = 0; j < count; j++) { + device_list[j] = NULL; + dev_node = of_parse_phandle(np, clk_handle_name, j); + if (!dev_node) { + pr_err("Unable to get device_node pointer for %s opp-handle (%s)\n", + clk->dbg_name, clk_handle_name); + goto err_parse_phandle; + } + + for_each_possible_cpu(cpu) { + if (of_get_cpu_node(cpu, NULL) == dev_node) { + device_list[j] = get_cpu_device(cpu); + } + } + + if (device_list[j]) + continue; + + pdev = of_find_device_by_node(dev_node); + if (!pdev) { + pr_err("Unable to find platform_device node for %s opp-handle\n", + clk->dbg_name); + goto err_parse_phandle; + } + device_list[j] = &pdev->dev; + } + return device_list; +err_parse_phandle: + kfree(device_list); + return ERR_PTR(-EINVAL); +} + +static int get_voltage(struct clk *clk, unsigned long rate, + int store_vcorner, int n) +{ + struct clk_vdd_class *vdd; + int uv, level, corner; + + /* + * Use the first regulator in the vdd class + * for the OPP table. + */ + vdd = clk->vdd_class; + if (vdd->num_regulators > 1) { + corner = vdd->vdd_uv[vdd->num_regulators * n]; + } else { + level = find_vdd_level(clk, rate); + if (level < 0) { + pr_err("Could not find vdd level\n"); + return -EINVAL; + } + corner = vdd->vdd_uv[level]; + } + + if (!corner) { + pr_err("%s: Unable to find vdd level for rate %lu\n", + clk->dbg_name, rate); + return -EINVAL; + } + + if (store_vcorner) { + uv = corner; + return uv; + } + + uv = regulator_list_corner_voltage(vdd->regulator[0], corner); + if (uv < 0) { + pr_err("%s: no uv for corner %d - err: %d\n", + clk->dbg_name, corner, uv); + return uv; + } + return uv; +} + +static int add_and_print_opp(struct clk *clk, struct device **device_list, + int count, unsigned long rate, int uv, int n) +{ + int j, ret = 0; + + for (j = 0; j < count; j++) { + ret = dev_pm_opp_add(device_list[j], rate, uv); + if (ret) { + pr_err("%s: couldn't add OPP for %lu - err: %d\n", + clk->dbg_name, rate, ret); + return ret; + } + if (n == 1 || n == clk->num_fmax - 1 || + rate == clk_round_rate(clk, INT_MAX)) + pr_info("%s: set OPP pair(%lu Hz: %u uV) on %s\n", + clk->dbg_name, rate, uv, + dev_name(device_list[j])); + } + return ret; +} + +static void populate_clock_opp_table(struct device_node *np, + struct clk_lookup *table, size_t size) +{ + struct device **device_list; + struct clk *clk; + char clk_handle_name[MAX_LEN_OPP_HANDLE]; + char clk_store_volt_corner[MAX_LEN_OPP_HANDLE]; + size_t i; + int n, len, count, uv = 0; + unsigned long rate, ret = 0; + bool store_vcorner; + + /* Iterate across all clocks in the clock controller */ + for (i = 0; i < size; i++) { + n = 1; + rate = 0; + + store_vcorner = false; + clk = table[i].clk; + if (!clk || !clk->num_fmax || clk->opp_table_populated) + continue; + + if (strlen(clk->dbg_name) + LEN_OPP_HANDLE + < MAX_LEN_OPP_HANDLE) { + ret = snprintf(clk_handle_name, + ARRAY_SIZE(clk_handle_name), + "qcom,%s-opp-handle", clk->dbg_name); + if (ret < strlen(clk->dbg_name) + LEN_OPP_HANDLE) { + pr_err("Failed to hold clk_handle_name\n"); + continue; + } + } else { + pr_err("clk name (%s) too large to fit in clk_handle_name\n", + clk->dbg_name); + continue; + } + + if (strlen(clk->dbg_name) + LEN_OPP_VCORNER_HANDLE + < MAX_LEN_OPP_HANDLE) { + ret = snprintf(clk_store_volt_corner, + ARRAY_SIZE(clk_store_volt_corner), + "qcom,%s-opp-store-vcorner", clk->dbg_name); + if (ret < strlen(clk->dbg_name) + + LEN_OPP_VCORNER_HANDLE) { + pr_err("Failed to hold clk_store_volt_corner\n"); + continue; + } + } else { + pr_err("clk name (%s) too large to fit in clk_store_volt_corner\n", + clk->dbg_name); + continue; + } + + if (!of_find_property(np, clk_handle_name, &len)) { + pr_debug("Unable to find %s\n", clk_handle_name); + if (!of_find_property(np, clk_store_volt_corner, + &len)) { + pr_debug("Unable to find %s\n", + clk_store_volt_corner); + continue; + } else { + store_vcorner = true; + device_list = derive_device_list(clk, np, + clk_store_volt_corner, len); + } + } else + device_list = derive_device_list(clk, np, + clk_handle_name, len); + if (IS_ERR_OR_NULL(device_list)) { + pr_err("Failed to fill device_list\n"); + continue; + } + + count = len/sizeof(u32); + while (1) { + /* + * Calling clk_round_rate will not work for all clocks + * (eg. mux_div). Use their fmax values instead to get + * list of all available frequencies. + */ + if (clk->ops->list_rate) { + ret = clk_round_rate(clk, rate + 1); + if (ret < 0) { + pr_err("clk_round_rate failed for %s\n", + clk->dbg_name); + goto err_round_rate; + } + /* + * If clk_round_rate give the same value on + * consecutive iterations, exit loop since + * we're at the maximum clock frequency. + */ + if (rate == ret) + break; + rate = ret; + } else { + if (n < clk->num_fmax) + rate = clk->fmax[n]; + else + break; + } + + uv = get_voltage(clk, rate, store_vcorner, n); + if (uv < 0) + goto err_round_rate; + + ret = add_and_print_opp(clk, device_list, count, + rate, uv , n); + if (ret) + goto err_round_rate; + + n++; + } +err_round_rate: + /* If OPP table population was successful, set the flag */ + if (uv >= 0 && ret >= 0) + clk->opp_table_populated = true; + kfree(device_list); + } +} + +/** + * of_msm_clock_register() - Register clock tables with clkdev and with the + * clock DT framework + * @table: Table of clocks + * @size: Size of @table + * @np: Device pointer corresponding to the clock-provider device + * + * Upon return, clock APIs may be used to control clocks registered using this + * function. + */ +int of_msm_clock_register(struct device_node *np, struct clk_lookup *table, + size_t size) +{ + int ret = 0; + struct of_msm_provider_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->table = table; + data->size = size; + + ret = of_clk_add_provider(np, of_clk_src_get, data); + if (ret) { + kfree(data); + return -ENOMEM; + } + + populate_clock_opp_table(np, table, size); + return msm_clock_register(table, size); +} +EXPORT_SYMBOL(of_msm_clock_register); + +/** + * msm_clock_init() - Register and initialize a clock driver + * @data: Driver-specific clock initialization data + * + * Upon return from this call, clock APIs may be used to control + * clocks registered with this API. + */ +int __init msm_clock_init(struct clock_init_data *data) +{ + if (!data) + return -EINVAL; + + if (data->pre_init) + data->pre_init(); + + mutex_lock(&msm_clock_init_lock); + if (data->late_init) + list_add(&data->list, &initdata_list); + mutex_unlock(&msm_clock_init_lock); + + msm_clock_register(data->table, data->size); + + if (data->post_init) + data->post_init(); + + return 0; +} + +static int __init clock_late_init(void) +{ + struct handoff_clk *h, *h_temp; + struct handoff_vdd *v, *v_temp; + struct clock_init_data *initdata, *initdata_temp; + int ret = 0; + + pr_info("%s: Removing enables held for handed-off clocks\n", __func__); + + mutex_lock(&msm_clock_init_lock); + + list_for_each_entry_safe(initdata, initdata_temp, + &initdata_list, list) { + ret = initdata->late_init(); + if (ret) + pr_err("%s: %pS failed late_init.\n", __func__, + initdata); + } + + list_for_each_entry_safe(h, h_temp, &handoff_list, list) { + clk_disable_unprepare(h->clk); + list_del(&h->list); + kfree(h); + } + + list_for_each_entry_safe(v, v_temp, &handoff_vdd_list, list) { + unvote_vdd_level(v->vdd_class, v->vdd_class->num_levels - 1); + list_del(&v->list); + kfree(v); + } + + mutex_unlock(&msm_clock_init_lock); + + return ret; +} +/* clock_late_init should run only after all deferred probing + * (excluding DLKM probes) has completed. + */ +late_initcall_sync(clock_late_init); diff --git a/drivers/clk/msm/clock.h b/drivers/clk/msm/clock.h new file mode 100644 index 000000000000..bee769921ff7 --- /dev/null +++ b/drivers/clk/msm/clock.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * + * 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. + * + */ + +#ifndef __DRIVERS_CLK_MSM_CLOCK_H +#define __DRIVERS_CLK_MSM_CLOCK_H + +#include <linux/clkdev.h> + +/** + * struct clock_init_data - SoC specific clock initialization data + * @table: table of lookups to add + * @size: size of @table + * @pre_init: called before initializing the clock driver. + * @post_init: called after registering @table. clock APIs can be called inside. + * @late_init: called during late init + */ +struct clock_init_data { + struct list_head list; + struct clk_lookup *table; + size_t size; + void (*pre_init)(void); + void (*post_init)(void); + int (*late_init)(void); +}; + +int msm_clock_init(struct clock_init_data *data); +int find_vdd_level(struct clk *clk, unsigned long rate); +extern struct list_head orphan_clk_list; + +#ifdef CONFIG_DEBUG_FS +int clock_debug_register(struct clk *clk); +void clock_debug_print_enabled(void); +#else +static inline int clock_debug_register(struct clk *unused) +{ + return 0; +} +static inline void clock_debug_print_enabled(void) { return; } +#endif + +#endif diff --git a/drivers/clk/msm/gdsc.c b/drivers/clk/msm/gdsc.c new file mode 100644 index 000000000000..7d5180502b94 --- /dev/null +++ b/drivers/clk/msm/gdsc.c @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/clk/msm-clk.h> + +#define PWR_ON_MASK BIT(31) +#define EN_REST_WAIT_MASK (0xF << 20) +#define EN_FEW_WAIT_MASK (0xF << 16) +#define CLK_DIS_WAIT_MASK (0xF << 12) +#define SW_OVERRIDE_MASK BIT(2) +#define HW_CONTROL_MASK BIT(1) +#define SW_COLLAPSE_MASK BIT(0) +#define GMEM_CLAMP_IO_MASK BIT(0) +#define BCR_BLK_ARES_BIT BIT(0) + +/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */ +#define EN_REST_WAIT_VAL (0x2 << 20) +#define EN_FEW_WAIT_VAL (0x8 << 16) +#define CLK_DIS_WAIT_VAL (0x2 << 12) + +#define TIMEOUT_US 100 +#define MAX_GDSCR_READS 100 +#define MAX_VOTABLE_GDSCR_READS 200 + +struct gdsc { + struct regulator_dev *rdev; + struct regulator_desc rdesc; + void __iomem *gdscr; + struct clk **clocks; + int clock_count; + bool toggle_mem; + bool toggle_periph; + bool toggle_logic; + bool resets_asserted; + bool root_en; + bool force_root_en; + int root_clk_idx; + bool no_status_check_on_disable; + bool is_gdsc_enabled; + bool allow_clear; + void __iomem *domain_addr; + void __iomem *hw_ctrl_addr; + void __iomem *sw_reset_addr; +}; + +enum gdscr_status { + ENABLED, + DISABLED, +}; + +static DEFINE_MUTEX(gdsc_seq_lock); + +void gdsc_allow_clear_retention(struct regulator *regulator) +{ + struct gdsc *sc = regulator_get_drvdata(regulator); + + if (sc) + sc->allow_clear = true; +} + +static int poll_gdsc_status(struct gdsc *sc, enum gdscr_status status) +{ + void __iomem *gdscr; + int count; + u32 val; + + if (sc->hw_ctrl_addr) { + gdscr = sc->hw_ctrl_addr; + count = MAX_VOTABLE_GDSCR_READS; + } else { + gdscr = sc->gdscr; + count = MAX_GDSCR_READS; + } + + for (; count > 0; count--) { + val = readl_relaxed(gdscr); + val &= PWR_ON_MASK; + switch (status) { + case ENABLED: + if (val) + return 0; + break; + case DISABLED: + if (!val) + return 0; + break; + } + /* + * There is no guarantee about the delay needed for the enable + * bit in the GDSCR to be set or reset after the GDSC state + * changes. Hence, keep on checking for a reasonable number + * of times until the bit is set with the least possible delay + * between succeessive tries. + */ + udelay(1); + } + return -ETIMEDOUT; +} + +static int gdsc_is_enabled(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + + if (!sc->toggle_logic) + return !sc->resets_asserted; + + regval = readl_relaxed(sc->gdscr); + if (regval & PWR_ON_MASK) { + /* + * The GDSC might be turned on due to TZ/HYP vote on the + * votable GDS registers. Check the SW_COLLAPSE_MASK to + * determine if HLOS has voted for it. + */ + if (!(regval & SW_COLLAPSE_MASK)) + return true; + } + return false; +} + +static int gdsc_enable(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval, hw_ctrl_regval = 0x0; + int i, ret = 0; + + mutex_lock(&gdsc_seq_lock); + + if (sc->root_en || sc->force_root_en) + clk_prepare_enable(sc->clocks[sc->root_clk_idx]); + + if (sc->toggle_logic) { + if (sc->sw_reset_addr) { + regval = readl_relaxed(sc->sw_reset_addr); + regval |= BCR_BLK_ARES_BIT; + writel_relaxed(regval, sc->sw_reset_addr); + /* + * BLK_ARES should be kept asserted for 1us before + * being de-asserted. + */ + wmb(); + udelay(1); + + regval &= ~BCR_BLK_ARES_BIT; + writel_relaxed(regval, sc->sw_reset_addr); + + /* Make sure de-assert goes through before continuing */ + wmb(); + } + + if (sc->domain_addr) { + regval = readl_relaxed(sc->domain_addr); + regval &= ~GMEM_CLAMP_IO_MASK; + writel_relaxed(regval, sc->domain_addr); + /* + * Make sure CLAMP_IO is de-asserted before continuing. + */ + wmb(); + } + + regval = readl_relaxed(sc->gdscr); + if (regval & HW_CONTROL_MASK) { + dev_warn(&rdev->dev, "Invalid enable while %s is under HW control\n", + sc->rdesc.name); + mutex_unlock(&gdsc_seq_lock); + return -EBUSY; + } + + regval &= ~SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + + /* Wait for 8 XO cycles before polling the status bit. */ + mb(); + udelay(1); + + ret = poll_gdsc_status(sc, ENABLED); + if (ret) { + dev_err(&rdev->dev, "%s enable timed out: 0x%x\n", + sc->rdesc.name, regval); + udelay(TIMEOUT_US); + regval = readl_relaxed(sc->gdscr); + if (sc->hw_ctrl_addr) { + hw_ctrl_regval = + readl_relaxed(sc->hw_ctrl_addr); + dev_err(&rdev->dev, "%s final state (%d us after timeout): 0x%x, GDS_HW_CTRL: 0x%x\n", + sc->rdesc.name, TIMEOUT_US, regval, + hw_ctrl_regval); + } else + dev_err(&rdev->dev, "%s final state: 0x%x (%d us after timeout)\n", + sc->rdesc.name, regval, TIMEOUT_US); + mutex_unlock(&gdsc_seq_lock); + return ret; + } + } else { + for (i = 0; i < sc->clock_count; i++) + if (likely(i != sc->root_clk_idx)) + clk_reset(sc->clocks[i], CLK_RESET_DEASSERT); + sc->resets_asserted = false; + } + + for (i = 0; i < sc->clock_count; i++) { + if (unlikely(i == sc->root_clk_idx)) + continue; + if (sc->toggle_mem) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_MEM); + if (sc->toggle_periph) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_PERIPH); + } + + /* + * If clocks to this power domain were already on, they will take an + * additional 4 clock cycles to re-enable after the rail is enabled. + * Delay to account for this. A delay is also needed to ensure clocks + * are not enabled within 400ns of enabling power to the memories. + */ + udelay(1); + + /* Delay to account for staggered memory powerup. */ + udelay(1); + + if (sc->force_root_en) + clk_disable_unprepare(sc->clocks[sc->root_clk_idx]); + sc->is_gdsc_enabled = true; + + mutex_unlock(&gdsc_seq_lock); + + return ret; +} + +static int gdsc_disable(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + int i, ret = 0; + + mutex_lock(&gdsc_seq_lock); + + if (sc->force_root_en) + clk_prepare_enable(sc->clocks[sc->root_clk_idx]); + + for (i = sc->clock_count-1; i >= 0; i--) { + if (unlikely(i == sc->root_clk_idx)) + continue; + if (sc->toggle_mem && sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_MEM); + if (sc->toggle_periph && sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_PERIPH); + } + + /* Delay to account for staggered memory powerdown. */ + udelay(1); + + if (sc->toggle_logic) { + regval = readl_relaxed(sc->gdscr); + if (regval & HW_CONTROL_MASK) { + dev_warn(&rdev->dev, "Invalid disable while %s is under HW control\n", + sc->rdesc.name); + mutex_unlock(&gdsc_seq_lock); + return -EBUSY; + } + + regval |= SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + /* Wait for 8 XO cycles before polling the status bit. */ + mb(); + udelay(1); + + if (sc->no_status_check_on_disable) { + /* + * Add a short delay here to ensure that gdsc_enable + * right after it was disabled does not put it in a + * wierd state. + */ + udelay(TIMEOUT_US); + } else { + ret = poll_gdsc_status(sc, DISABLED); + if (ret) + dev_err(&rdev->dev, "%s disable timed out: 0x%x\n", + sc->rdesc.name, regval); + } + + if (sc->domain_addr) { + regval = readl_relaxed(sc->domain_addr); + regval |= GMEM_CLAMP_IO_MASK; + writel_relaxed(regval, sc->domain_addr); + } + } else { + for (i = sc->clock_count-1; i >= 0; i--) + if (likely(i != sc->root_clk_idx)) + clk_reset(sc->clocks[i], CLK_RESET_ASSERT); + sc->resets_asserted = true; + } + + /* + * Check if gdsc_enable was called for this GDSC. If not, the root + * clock will not have been enabled prior to this. + */ + if ((sc->is_gdsc_enabled && sc->root_en) || sc->force_root_en) + clk_disable_unprepare(sc->clocks[sc->root_clk_idx]); + sc->is_gdsc_enabled = false; + + mutex_unlock(&gdsc_seq_lock); + + return ret; +} + +static unsigned int gdsc_get_mode(struct regulator_dev *rdev) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + + mutex_lock(&gdsc_seq_lock); + regval = readl_relaxed(sc->gdscr); + mutex_unlock(&gdsc_seq_lock); + if (regval & HW_CONTROL_MASK) + return REGULATOR_MODE_FAST; + return REGULATOR_MODE_NORMAL; +} + +static int gdsc_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct gdsc *sc = rdev_get_drvdata(rdev); + uint32_t regval; + int ret = 0; + + mutex_lock(&gdsc_seq_lock); + + regval = readl_relaxed(sc->gdscr); + + /* + * HW control can only be enable/disabled when SW_COLLAPSE + * indicates on. + */ + if (regval & SW_COLLAPSE_MASK) { + dev_err(&rdev->dev, "can't enable hw collapse now\n"); + mutex_unlock(&gdsc_seq_lock); + return -EBUSY; + } + + switch (mode) { + case REGULATOR_MODE_FAST: + /* Turn on HW trigger mode */ + regval |= HW_CONTROL_MASK; + writel_relaxed(regval, sc->gdscr); + /* + * There may be a race with internal HW trigger signal, + * that will result in GDSC going through a power down and + * up cycle. In case HW trigger signal is controlled by + * firmware that also poll same status bits as we do, FW + * might read an 'on' status before the GDSC can finish + * power cycle. We wait 1us before returning to ensure + * FW can't immediately poll the status bit. + */ + mb(); + udelay(1); + break; + + case REGULATOR_MODE_NORMAL: + /* Turn off HW trigger mode */ + regval &= ~HW_CONTROL_MASK; + writel_relaxed(regval, sc->gdscr); + /* + * There may be a race with internal HW trigger signal, + * that will result in GDSC going through a power down and + * up cycle. If we poll too early, status bit will + * indicate 'on' before the GDSC can finish the power cycle. + * Account for this case by waiting 1us before polling. + */ + mb(); + udelay(1); + + ret = poll_gdsc_status(sc, ENABLED); + if (ret) + dev_err(&rdev->dev, "%s set_mode timed out: 0x%x\n", + sc->rdesc.name, regval); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&gdsc_seq_lock); + + return ret; +} + +static struct regulator_ops gdsc_ops = { + .is_enabled = gdsc_is_enabled, + .enable = gdsc_enable, + .disable = gdsc_disable, + .set_mode = gdsc_set_mode, + .get_mode = gdsc_get_mode, +}; + +static int gdsc_probe(struct platform_device *pdev) +{ + static atomic_t gdsc_count = ATOMIC_INIT(-1); + struct regulator_config reg_config = {}; + struct regulator_init_data *init_data; + struct resource *res; + struct gdsc *sc; + uint32_t regval, clk_dis_wait_val = CLK_DIS_WAIT_VAL; + bool retain_mem, retain_periph, support_hw_trigger; + int i, ret; + + sc = devm_kzalloc(&pdev->dev, sizeof(struct gdsc), GFP_KERNEL); + if (sc == NULL) + return -ENOMEM; + + init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node, + &sc->rdesc); + if (init_data == NULL) + return -ENOMEM; + + if (of_get_property(pdev->dev.of_node, "parent-supply", NULL)) + init_data->supply_regulator = "parent"; + + ret = of_property_read_string(pdev->dev.of_node, "regulator-name", + &sc->rdesc.name); + if (ret) + return ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -EINVAL; + sc->gdscr = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (sc->gdscr == NULL) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "domain_addr"); + if (res) { + sc->domain_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (sc->domain_addr == NULL) + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "sw_reset"); + if (res) { + sc->sw_reset_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (sc->sw_reset_addr == NULL) + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "hw_ctrl_addr"); + if (res) { + sc->hw_ctrl_addr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (sc->hw_ctrl_addr == NULL) + return -ENOMEM; + } + + sc->clock_count = of_property_count_strings(pdev->dev.of_node, + "clock-names"); + if (sc->clock_count == -EINVAL) { + sc->clock_count = 0; + } else if (IS_ERR_VALUE(sc->clock_count)) { + dev_err(&pdev->dev, "Failed to get clock names\n"); + return -EINVAL; + } + + sc->clocks = devm_kzalloc(&pdev->dev, + sizeof(struct clk *) * sc->clock_count, GFP_KERNEL); + if (!sc->clocks) + return -ENOMEM; + + sc->root_clk_idx = -1; + + sc->root_en = of_property_read_bool(pdev->dev.of_node, + "qcom,enable-root-clk"); + sc->force_root_en = of_property_read_bool(pdev->dev.of_node, + "qcom,force-enable-root-clk"); + for (i = 0; i < sc->clock_count; i++) { + const char *clock_name; + of_property_read_string_index(pdev->dev.of_node, "clock-names", + i, &clock_name); + sc->clocks[i] = devm_clk_get(&pdev->dev, clock_name); + if (IS_ERR(sc->clocks[i])) { + int rc = PTR_ERR(sc->clocks[i]); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get %s\n", + clock_name); + return rc; + } + + if (!strcmp(clock_name, "core_root_clk")) + sc->root_clk_idx = i; + } + + if ((sc->root_en || sc->force_root_en) && (sc->root_clk_idx == -1)) { + dev_err(&pdev->dev, "Failed to get root clock name\n"); + return -EINVAL; + } + + sc->rdesc.id = atomic_inc_return(&gdsc_count); + sc->rdesc.ops = &gdsc_ops; + sc->rdesc.type = REGULATOR_VOLTAGE; + sc->rdesc.owner = THIS_MODULE; + platform_set_drvdata(pdev, sc); + + /* + * Disable HW trigger: collapse/restore occur based on registers writes. + * Disable SW override: Use hardware state-machine for sequencing. + */ + regval = readl_relaxed(sc->gdscr); + regval &= ~(HW_CONTROL_MASK | SW_OVERRIDE_MASK); + + if (!of_property_read_u32(pdev->dev.of_node, "qcom,clk-dis-wait-val", + &clk_dis_wait_val)) + clk_dis_wait_val = clk_dis_wait_val << 12; + + /* Configure wait time between states. */ + regval &= ~(EN_REST_WAIT_MASK | EN_FEW_WAIT_MASK | CLK_DIS_WAIT_MASK); + regval |= EN_REST_WAIT_VAL | EN_FEW_WAIT_VAL | clk_dis_wait_val; + writel_relaxed(regval, sc->gdscr); + + sc->no_status_check_on_disable = + of_property_read_bool(pdev->dev.of_node, + "qcom,no-status-check-on-disable"); + retain_mem = of_property_read_bool(pdev->dev.of_node, + "qcom,retain-mem"); + sc->toggle_mem = !retain_mem; + retain_periph = of_property_read_bool(pdev->dev.of_node, + "qcom,retain-periph"); + sc->toggle_periph = !retain_periph; + sc->toggle_logic = !of_property_read_bool(pdev->dev.of_node, + "qcom,skip-logic-collapse"); + support_hw_trigger = of_property_read_bool(pdev->dev.of_node, + "qcom,support-hw-trigger"); + if (support_hw_trigger) { + init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_MODE; + init_data->constraints.valid_modes_mask |= + REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST; + } + + if (!sc->toggle_logic) { + regval &= ~SW_COLLAPSE_MASK; + writel_relaxed(regval, sc->gdscr); + + ret = poll_gdsc_status(sc, ENABLED); + if (ret) { + dev_err(&pdev->dev, "%s enable timed out: 0x%x\n", + sc->rdesc.name, regval); + return ret; + } + } + + sc->allow_clear = of_property_read_bool(pdev->dev.of_node, + "qcom,disallow-clear"); + sc->allow_clear = !sc->allow_clear; + + for (i = 0; i < sc->clock_count; i++) { + if (retain_mem || (regval & PWR_ON_MASK) || !sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_MEM); + else + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_MEM); + + if (retain_periph || (regval & PWR_ON_MASK) || !sc->allow_clear) + clk_set_flags(sc->clocks[i], CLKFLAG_RETAIN_PERIPH); + else + clk_set_flags(sc->clocks[i], CLKFLAG_NORETAIN_PERIPH); + } + + reg_config.dev = &pdev->dev; + reg_config.init_data = init_data; + reg_config.driver_data = sc; + reg_config.of_node = pdev->dev.of_node; + sc->rdev = regulator_register(&sc->rdesc, ®_config); + if (IS_ERR(sc->rdev)) { + dev_err(&pdev->dev, "regulator_register(\"%s\") failed.\n", + sc->rdesc.name); + return PTR_ERR(sc->rdev); + } + + return 0; +} + +static int gdsc_remove(struct platform_device *pdev) +{ + struct gdsc *sc = platform_get_drvdata(pdev); + regulator_unregister(sc->rdev); + return 0; +} + +static struct of_device_id gdsc_match_table[] = { + { .compatible = "qcom,gdsc" }, + {} +}; + +static struct platform_driver gdsc_driver = { + .probe = gdsc_probe, + .remove = gdsc_remove, + .driver = { + .name = "gdsc", + .of_match_table = gdsc_match_table, + .owner = THIS_MODULE, + }, +}; + +static int __init gdsc_init(void) +{ + return platform_driver_register(&gdsc_driver); +} +subsys_initcall(gdsc_init); + +static void __exit gdsc_exit(void) +{ + platform_driver_unregister(&gdsc_driver); +} +module_exit(gdsc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM8974 GDSC power rail regulator driver"); diff --git a/drivers/clk/msm/msm-clock-controller.c b/drivers/clk/msm/msm-clock-controller.c new file mode 100644 index 000000000000..edce15bc3471 --- /dev/null +++ b/drivers/clk/msm/msm-clock-controller.c @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#define pr_fmt(fmt) "msmclock: %s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/of.h> +#include <linux/hashtable.h> + +#include <linux/clk/msm-clk-provider.h> +#include <soc/qcom/msm-clock-controller.h> +#include <soc/qcom/clock-rpm.h> + +/* Protects list operations */ +static DEFINE_MUTEX(msmclk_lock); +static LIST_HEAD(msmclk_parser_list); +static u32 msmclk_debug; + +struct hitem { + struct hlist_node list; + phandle key; + void *ptr; +}; + +int of_property_count_phandles(struct device_node *np, char *propname) +{ + const __be32 *phandle; + int size; + + phandle = of_get_property(np, propname, &size); + return phandle ? (size / sizeof(*phandle)) : -EINVAL; +} +EXPORT_SYMBOL(of_property_count_phandles); + +int of_property_read_phandle_index(struct device_node *np, char *propname, + int index, phandle *p) +{ + const __be32 *phandle; + int size; + + phandle = of_get_property(np, propname, &size); + if ((!phandle) || (size < sizeof(*phandle) * (index + 1))) + return -EINVAL; + + *p = be32_to_cpup(phandle + index); + return 0; +} +EXPORT_SYMBOL(of_property_read_phandle_index); + +static int generic_vdd_parse_regulators(struct device *dev, + struct clk_vdd_class *vdd, struct device_node *np) +{ + int num_regulators, i, rc; + char *name = "qcom,regulators"; + + num_regulators = of_property_count_phandles(np, name); + if (num_regulators <= 0) { + dt_prop_err(np, name, "missing dt property\n"); + return -EINVAL; + } + + vdd->regulator = devm_kzalloc(dev, + sizeof(*vdd->regulator) * num_regulators, + GFP_KERNEL); + if (!vdd->regulator) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + for (i = 0; i < num_regulators; i++) { + phandle p; + rc = of_property_read_phandle_index(np, name, i, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + vdd->regulator[i] = msmclk_parse_phandle(dev, p); + if (IS_ERR(vdd->regulator[i])) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(vdd->regulator[i]); + } + } + + vdd->num_regulators = num_regulators; + return 0; +} + +static int generic_vdd_parse_levels(struct device *dev, + struct clk_vdd_class *vdd, struct device_node *np) +{ + int len, rc; + char *name = "qcom,uV-levels"; + + if (!of_find_property(np, name, &len)) { + dt_prop_err(np, name, "missing dt property\n"); + return -EINVAL; + } + + len /= sizeof(u32); + if (len % vdd->num_regulators) { + dt_err(np, "mismatch beween qcom,uV-levels and qcom,regulators dt properties\n"); + return -EINVAL; + } + + vdd->num_levels = len / vdd->num_regulators; + vdd->vdd_uv = devm_kzalloc(dev, len * sizeof(*vdd->vdd_uv), + GFP_KERNEL); + vdd->level_votes = devm_kzalloc(dev, + vdd->num_levels * sizeof(*vdd->level_votes), + GFP_KERNEL); + + if (!vdd->vdd_uv || !vdd->level_votes) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + rc = of_property_read_u32_array(np, name, vdd->vdd_uv, + vdd->num_levels * vdd->num_regulators); + if (rc) { + dt_prop_err(np, name, "unable to read u32 array\n"); + return -EINVAL; + } + + /* Optional Property */ + name = "qcom,uA-levels"; + if (!of_find_property(np, name, &len)) + return 0; + + len /= sizeof(u32); + if (len / vdd->num_regulators != vdd->num_levels) { + dt_err(np, "size of qcom,uA-levels and qcom,uV-levels must match\n"); + return -EINVAL; + } + + vdd->vdd_ua = devm_kzalloc(dev, len * sizeof(*vdd->vdd_ua), + GFP_KERNEL); + if (!vdd->vdd_ua) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + rc = of_property_read_u32_array(np, name, vdd->vdd_ua, + vdd->num_levels * vdd->num_regulators); + if (rc) { + dt_prop_err(np, name, "unable to read u32 array\n"); + return -EINVAL; + } + + return 0; +} + +static void *simple_vdd_class_dt_parser(struct device *dev, + struct device_node *np) +{ + struct clk_vdd_class *vdd; + int rc = 0; + + vdd = devm_kzalloc(dev, sizeof(*vdd), GFP_KERNEL); + if (!vdd) { + dev_err(dev, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + mutex_init(&vdd->lock); + vdd->class_name = np->name; + + rc = generic_vdd_parse_regulators(dev, vdd, np); + rc |= generic_vdd_parse_levels(dev, vdd, np); + if (rc) { + dt_err(np, "unable to read vdd_class\n"); + return ERR_PTR(rc); + } + + return vdd; +} +MSMCLK_PARSER(simple_vdd_class_dt_parser, "qcom,simple-vdd-class", 0); + +static int generic_clk_parse_parents(struct device *dev, struct clk *c, + struct device_node *np) +{ + int rc; + phandle p; + char *name = "qcom,parent"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_phandle_index(np, name, 0, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + c->parent = msmclk_parse_phandle(dev, p); + if (IS_ERR(c->parent)) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(c->parent); + } + + return 0; +} + +static int generic_clk_parse_vdd(struct device *dev, struct clk *c, + struct device_node *np) +{ + phandle p; + int rc; + char *name = "qcom,supply-group"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_phandle_index(np, name, 0, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + c->vdd_class = msmclk_parse_phandle(dev, p); + if (IS_ERR(c->vdd_class)) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(c->vdd_class); + } + + return 0; +} + +static int generic_clk_parse_flags(struct device *dev, struct clk *c, + struct device_node *np) +{ + int rc; + char *name = "qcom,clk-flags"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_u32(np, name, &c->flags); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + return 0; +} + +static int generic_clk_parse_fmax(struct device *dev, struct clk *c, + struct device_node *np) +{ + u32 prop_len, i; + int rc; + char *name = "qcom,clk-fmax"; + + /* This property is optional */ + if (!of_find_property(np, name, &prop_len)) + return 0; + + if (!c->vdd_class) { + dt_err(np, "both qcom,clk-fmax and qcom,supply-group must be defined\n"); + return -EINVAL; + } + + prop_len /= sizeof(u32); + if (prop_len % 2) { + dt_prop_err(np, name, "bad length\n"); + return -EINVAL; + } + + /* Value at proplen - 2 is the index of the last entry in fmax array */ + rc = of_property_read_u32_index(np, name, prop_len - 2, &c->num_fmax); + c->num_fmax += 1; + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + c->fmax = devm_kzalloc(dev, sizeof(*c->fmax) * c->num_fmax, GFP_KERNEL); + if (!c->fmax) { + dev_err(dev, "memory alloc failure\n"); + return -ENOMEM; + } + + for (i = 0; i < prop_len; i += 2) { + u32 level, value; + rc = of_property_read_u32_index(np, name, i, &level); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + rc = of_property_read_u32_index(np, name, i + 1, &value); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + if (level >= c->num_fmax) { + dt_prop_err(np, name, "must be sorted\n"); + return -EINVAL; + } + c->fmax[level] = value; + } + + return 0; +} + +static int generic_clk_add_lookup_tbl_entry(struct device *dev, struct clk *c) +{ + struct msmclk_data *drv = dev_get_drvdata(dev); + struct clk_lookup *cl; + + if (drv->clk_tbl_size >= drv->max_clk_tbl_size) { + dev_err(dev, "child node count should be > clock_count?\n"); + return -EINVAL; + } + + cl = drv->clk_tbl + drv->clk_tbl_size; + cl->clk = c; + drv->clk_tbl_size++; + return 0; +} + +static int generic_clk_parse_depends(struct device *dev, struct clk *c, + struct device_node *np) +{ + phandle p; + int rc; + char *name = "qcom,depends"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_phandle_index(np, name, 0, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + c->depends = msmclk_parse_phandle(dev, p); + if (IS_ERR(c->depends)) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(c->depends); + } + + return 0; +} + +static int generic_clk_parse_init_config(struct device *dev, struct clk *c, + struct device_node *np) +{ + int rc; + u32 temp; + char *name = "qcom,always-on"; + + c->always_on = of_property_read_bool(np, name); + + name = "qcom,config-rate"; + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_u32(np, name, &temp); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + c->init_rate = temp; + + return rc; +} + +void *msmclk_generic_clk_init(struct device *dev, struct device_node *np, + struct clk *c) +{ + int rc; + + /* CLK_INIT macro */ + spin_lock_init(&c->lock); + mutex_init(&c->prepare_lock); + INIT_LIST_HEAD(&c->children); + INIT_LIST_HEAD(&c->siblings); + INIT_LIST_HEAD(&c->list); + c->dbg_name = np->name; + + rc = generic_clk_add_lookup_tbl_entry(dev, c); + rc |= generic_clk_parse_flags(dev, c, np); + rc |= generic_clk_parse_parents(dev, c, np); + rc |= generic_clk_parse_vdd(dev, c, np); + rc |= generic_clk_parse_fmax(dev, c, np); + rc |= generic_clk_parse_depends(dev, c, np); + rc |= generic_clk_parse_init_config(dev, c, np); + + if (rc) { + dt_err(np, "unable to read clk\n"); + return ERR_PTR(-EINVAL); + } + + return c; +} + +static struct msmclk_parser *msmclk_parser_lookup(struct device_node *np) +{ + struct msmclk_parser *item; + list_for_each_entry(item, &msmclk_parser_list, list) { + if (of_device_is_compatible(np, item->compatible)) + return item; + } + return NULL; +} +void msmclk_parser_register(struct msmclk_parser *item) +{ + mutex_lock(&msmclk_lock); + list_add(&item->list, &msmclk_parser_list); + mutex_unlock(&msmclk_lock); +} + +static int msmclk_htable_add(struct device *dev, void *result, phandle key); + +void *msmclk_parse_dt_node(struct device *dev, struct device_node *np) +{ + struct msmclk_parser *parser; + phandle key; + void *result; + int rc; + + key = np->phandle; + result = msmclk_lookup_phandle(dev, key); + if (!result) + return ERR_PTR(-EINVAL); + + if (!of_device_is_available(np)) { + dt_err(np, "node is disabled\n"); + return ERR_PTR(-EINVAL); + } + + parser = msmclk_parser_lookup(np); + if (IS_ERR(parser)) { + dt_err(np, "no parser found\n"); + return ERR_PTR(-EINVAL); + } + + /* This may return -EPROBE_DEFER */ + result = parser->parsedt(dev, np); + if (IS_ERR(result)) { + dt_err(np, "parsedt failed"); + return result; + } + + rc = msmclk_htable_add(dev, result, key); + if (rc) + return ERR_PTR(rc); + + return result; +} + +void *msmclk_parse_phandle(struct device *dev, phandle key) +{ + struct hitem *item; + struct device_node *np; + struct msmclk_data *drv = dev_get_drvdata(dev); + + /* + * the default phandle value is 0. Since hashtable keys must + * be unique, reject the default value. + */ + if (!key) + return ERR_PTR(-EINVAL); + + hash_for_each_possible(drv->htable, item, list, key) { + if (item->key == key) + return item->ptr; + } + + np = of_find_node_by_phandle(key); + if (!np) + return ERR_PTR(-EINVAL); + + return msmclk_parse_dt_node(dev, np); +} +EXPORT_SYMBOL(msmclk_parse_phandle); + +void *msmclk_lookup_phandle(struct device *dev, phandle key) +{ + struct hitem *item; + struct msmclk_data *drv = dev_get_drvdata(dev); + + hash_for_each_possible(drv->htable, item, list, key) { + if (item->key == key) + return item->ptr; + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL(msmclk_lookup_phandle); + +static int msmclk_htable_add(struct device *dev, void *data, phandle key) +{ + struct hitem *item; + struct msmclk_data *drv = dev_get_drvdata(dev); + + /* + * If there are no phandle references to a node, key == 0. However, if + * there is a second node like this, both will have key == 0. This + * violates the requirement that hashtable keys be unique. Skip it. + */ + if (!key) + return 0; + + if (!IS_ERR(msmclk_lookup_phandle(dev, key))) { + struct device_node *np = of_find_node_by_phandle(key); + dev_err(dev, "attempt to add duplicate entry for %s\n", + np ? np->name : "NULL"); + return -EINVAL; + } + + item = devm_kzalloc(dev, sizeof(*item), GFP_KERNEL); + if (!item) { + dev_err(dev, "memory alloc failure\n"); + return -ENOMEM; + } + + INIT_HLIST_NODE(&item->list); + item->key = key; + item->ptr = data; + + hash_add(drv->htable, &item->list, key); + return 0; +} + +/* + * Currently, regulators are the only elements capable of probe deferral. + * Check them first to handle probe deferal efficiently. +*/ +static int get_ext_regulators(struct device *dev) +{ + int num_strings, i, rc; + struct device_node *np; + void *item; + char *name = "qcom,regulator-names"; + + np = dev->of_node; + /* This property is optional */ + num_strings = of_property_count_strings(np, name); + if (num_strings <= 0) + return 0; + + for (i = 0; i < num_strings; i++) { + const char *str; + char buf[50]; + phandle key; + + rc = of_property_read_string_index(np, name, i, &str); + if (rc) { + dt_prop_err(np, name, "unable to read string\n"); + return rc; + } + + item = devm_regulator_get(dev, str); + if (IS_ERR(item)) { + dev_err(dev, "Failed to get regulator: %s\n", str); + return PTR_ERR(item); + } + + snprintf(buf, ARRAY_SIZE(buf), "%s-supply", str); + rc = of_property_read_phandle_index(np, buf, 0, &key); + if (rc) { + dt_prop_err(np, buf, "unable to read phandle\n"); + return rc; + } + + rc = msmclk_htable_add(dev, item, key); + if (rc) + return rc; + } + return 0; +} + +static struct clk *msmclk_clk_get(struct of_phandle_args *clkspec, void *data) +{ + phandle key; + struct clk *c = ERR_PTR(-ENOENT); + + key = clkspec->args[0]; + c = msmclk_lookup_phandle(data, key); + + if (!IS_ERR(c) && !(c->flags & CLKFLAG_INIT_DONE)) + return ERR_PTR(-EPROBE_DEFER); + + return c; +} + +static void *regulator_dt_parser(struct device *dev, struct device_node *np) +{ + dt_err(np, "regulators should be handled in probe()"); + return ERR_PTR(-EINVAL); +} +MSMCLK_PARSER(regulator_dt_parser, "qcom,rpm-smd-regulator", 0); + +static void *msmclk_dt_parser(struct device *dev, struct device_node *np) +{ + dt_err(np, "calling into other clock controllers isn't allowed"); + return ERR_PTR(-EINVAL); +} +MSMCLK_PARSER(msmclk_dt_parser, "qcom,msm-clock-controller", 0); + +static struct msmclk_data *msmclk_drv_init(struct device *dev) +{ + struct msmclk_data *drv; + size_t size; + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) { + dev_err(dev, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + dev_set_drvdata(dev, drv); + + drv->dev = dev; + INIT_LIST_HEAD(&drv->list); + + /* This overestimates size */ + drv->max_clk_tbl_size = of_get_child_count(dev->of_node); + size = sizeof(*drv->clk_tbl) * drv->max_clk_tbl_size; + drv->clk_tbl = devm_kzalloc(dev, size, GFP_KERNEL); + if (!drv->clk_tbl) { + dev_err(dev, "memory alloc failure clock table size %zu\n", + size); + return ERR_PTR(-ENOMEM); + } + + hash_init(drv->htable); + return drv; +} + +static int msmclk_probe(struct platform_device *pdev) +{ + struct resource *res; + struct device *dev; + struct msmclk_data *drv; + struct device_node *child; + void *result; + int rc = 0; + + dev = &pdev->dev; + drv = msmclk_drv_init(dev); + if (IS_ERR(drv)) + return PTR_ERR(drv); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cc-base"); + if (!res) { + dt_err(dev->of_node, "missing cc-base\n"); + return -EINVAL; + } + drv->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!drv->base) { + dev_err(dev, "ioremap failed for drv->base\n"); + return -ENOMEM; + } + rc = msmclk_htable_add(dev, drv, dev->of_node->phandle); + if (rc) + return rc; + + rc = enable_rpm_scaling(); + if (rc) + return rc; + + rc = get_ext_regulators(dev); + if (rc) + return rc; + + /* + * Returning -EPROBE_DEFER here is inefficient due to + * destroying work 'unnecessarily' + */ + for_each_available_child_of_node(dev->of_node, child) { + result = msmclk_parse_dt_node(dev, child); + if (!IS_ERR(result)) + continue; + if (!msmclk_debug) + return PTR_ERR(result); + /* + * Parse and report all errors instead of immediately + * exiting. Return the first error code. + */ + if (!rc) + rc = PTR_ERR(result); + } + if (rc) + return rc; + + rc = of_clk_add_provider(dev->of_node, msmclk_clk_get, dev); + if (rc) { + dev_err(dev, "of_clk_add_provider failed\n"); + return rc; + } + + /* + * can't fail after registering clocks, because users may have + * gotten clock references. Failing would delete the memory. + */ + WARN_ON(msm_clock_register(drv->clk_tbl, drv->clk_tbl_size)); + dev_info(dev, "registered clocks\n"); + + return 0; +} + +static struct of_device_id msmclk_match_table[] = { + {.compatible = "qcom,msm-clock-controller"}, + {} +}; + +static struct platform_driver msmclk_driver = { + .probe = msmclk_probe, + .driver = { + .name = "msm-clock-controller", + .of_match_table = msmclk_match_table, + .owner = THIS_MODULE, + }, +}; + +static bool initialized; +int __init msmclk_init(void) +{ + int rc; + if (initialized) + return 0; + + rc = platform_driver_register(&msmclk_driver); + if (rc) + return rc; + initialized = true; + return rc; +} +arch_initcall(msmclk_init); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index c56988ac63f7..03b9f6fab0ff 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -780,6 +780,13 @@ static inline void clk_writel(u32 val, u32 __iomem *reg) struct dentry *clk_debugfs_add_file(struct clk_hw *hw, char *name, umode_t mode, void *data, const struct file_operations *fops); #endif +#else +struct of_device_id; + +static inline void __init of_clk_init(const struct of_device_id *matches) +{ + return; +} #endif /* CONFIG_COMMON_CLK */ #endif /* CLK_PROVIDER_H */ diff --git a/include/linux/clk.h b/include/linux/clk.h index 0df4a51e1a78..c06bbd5ce952 100644 --- a/include/linux/clk.h +++ b/include/linux/clk.h @@ -488,7 +488,7 @@ static inline void clk_disable_unprepare(struct clk *clk) struct device_node; struct of_phandle_args; -#if defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK) +#if defined(CONFIG_OF) struct clk *of_clk_get(struct device_node *np, int index); struct clk *of_clk_get_by_name(struct device_node *np, const char *name); struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec); diff --git a/include/linux/clk/gdsc.h b/include/linux/clk/gdsc.h new file mode 100644 index 000000000000..b012ed06d3f4 --- /dev/null +++ b/include/linux/clk/gdsc.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#ifndef __GDSC_H +#define __GDSC_H + +#include <linux/regulator/consumer.h> + +/* Allow the clock memories to be turned off */ +void gdsc_allow_clear_retention(struct regulator *regulator); + +#endif diff --git a/include/linux/clk/msm-clk-provider.h b/include/linux/clk/msm-clk-provider.h new file mode 100644 index 000000000000..a09ce5c3b156 --- /dev/null +++ b/include/linux/clk/msm-clk-provider.h @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2015, The Linux Foundation. All rights reserved. + * + * 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. + * + */ + +#ifndef __MSM_CLK_PROVIDER_H +#define __MSM_CLK_PROVIDER_H + +#include <linux/types.h> +#include <linux/err.h> +#include <linux/list.h> +#include <linux/clkdev.h> +#include <linux/of.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/seq_file.h> +#include <linux/clk/msm-clk.h> + +/* + * Bit manipulation macros + */ +#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb) +#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb)) + +/* + * Halt/Status Checking Mode Macros + */ +#define HALT 0 /* Bit pol: 1 = halted */ +#define NOCHECK 1 /* No bit to check, do nothing */ +#define HALT_VOTED 2 /* Bit pol: 1 = halted; delay on disable */ +#define ENABLE 3 /* Bit pol: 1 = running */ +#define ENABLE_VOTED 4 /* Bit pol: 1 = running; delay on disable */ +#define DELAY 5 /* No bit to check, just delay */ + +struct clk_register_data { + char *name; + u32 offset; +}; +#ifdef CONFIG_DEBUG_FS +void clk_debug_print_hw(struct clk *clk, struct seq_file *f); +#else +static inline void clk_debug_print_hw(struct clk *clk, struct seq_file *f) {} +#endif + +#define CLK_WARN(clk, cond, fmt, ...) do { \ + clk_debug_print_hw(clk, NULL); \ + WARN(cond, "%s: " fmt, clk_name(clk), ##__VA_ARGS__); \ +} while (0) + +/** + * struct clk_vdd_class - Voltage scaling class + * @class_name: name of the class + * @regulator: array of regulators. + * @num_regulators: size of regulator array. Standard regulator APIs will be + used if this field > 0. + * @set_vdd: function to call when applying a new voltage setting. + * @vdd_uv: sorted 2D array of legal voltage settings. Indexed by level, then + regulator. + * @vdd_ua: sorted 2D array of legal cureent settings. Indexed by level, then + regulator. Optional parameter. + * @level_votes: array of votes for each level. + * @num_levels: specifies the size of level_votes array. + * @skip_handoff: do not vote for the max possible voltage during init + * @use_max_uV: use INT_MAX for max_uV when calling regulator_set_voltage + * This is useful when different vdd_class share same regulator. + * @cur_level: the currently set voltage level + * @lock: lock to protect this struct + */ +struct clk_vdd_class { + const char *class_name; + struct regulator **regulator; + int num_regulators; + int (*set_vdd)(struct clk_vdd_class *v_class, int level); + int *vdd_uv; + int *vdd_ua; + int *level_votes; + int num_levels; + bool skip_handoff; + bool use_max_uV; + unsigned long cur_level; + struct mutex lock; +}; + +#define DEFINE_VDD_CLASS(_name, _set_vdd, _num_levels) \ + struct clk_vdd_class _name = { \ + .class_name = #_name, \ + .set_vdd = _set_vdd, \ + .level_votes = (int [_num_levels]) {}, \ + .num_levels = _num_levels, \ + .cur_level = _num_levels, \ + .lock = __MUTEX_INITIALIZER(_name.lock) \ + } + +#define DEFINE_VDD_REGULATORS(_name, _num_levels, _num_regulators, _vdd_uv, \ + _vdd_ua) \ + struct clk_vdd_class _name = { \ + .class_name = #_name, \ + .vdd_uv = _vdd_uv, \ + .vdd_ua = _vdd_ua, \ + .regulator = (struct regulator * [_num_regulators]) {}, \ + .num_regulators = _num_regulators, \ + .level_votes = (int [_num_levels]) {}, \ + .num_levels = _num_levels, \ + .cur_level = _num_levels, \ + .lock = __MUTEX_INITIALIZER(_name.lock) \ + } + +#define DEFINE_VDD_REGS_INIT(_name, _num_regulators) \ + struct clk_vdd_class _name = { \ + .class_name = #_name, \ + .regulator = (struct regulator * [_num_regulators]) {}, \ + .num_regulators = _num_regulators, \ + .lock = __MUTEX_INITIALIZER(_name.lock) \ + } + +enum handoff { + HANDOFF_ENABLED_CLK, + HANDOFF_DISABLED_CLK, +}; + +struct clk_ops { + int (*prepare)(struct clk *clk); + int (*enable)(struct clk *clk); + void (*disable)(struct clk *clk); + void (*unprepare)(struct clk *clk); + void (*enable_hwcg)(struct clk *clk); + void (*disable_hwcg)(struct clk *clk); + int (*in_hwcg_mode)(struct clk *clk); + enum handoff (*handoff)(struct clk *clk); + int (*reset)(struct clk *clk, enum clk_reset_action action); + int (*pre_set_rate)(struct clk *clk, unsigned long new_rate); + int (*set_rate)(struct clk *clk, unsigned long rate); + void (*post_set_rate)(struct clk *clk, unsigned long old_rate); + int (*set_max_rate)(struct clk *clk, unsigned long rate); + int (*set_flags)(struct clk *clk, unsigned flags); + unsigned long (*get_rate)(struct clk *clk); + long (*list_rate)(struct clk *clk, unsigned n); + int (*is_enabled)(struct clk *clk); + long (*round_rate)(struct clk *clk, unsigned long rate); + int (*set_parent)(struct clk *clk, struct clk *parent); + struct clk *(*get_parent)(struct clk *clk); + bool (*is_local)(struct clk *clk); + void __iomem *(*list_registers)(struct clk *clk, int n, + struct clk_register_data **regs, u32 *size); +}; + +/** + * struct clk + * @prepare_count: prepare refcount + * @prepare_lock: protects clk_prepare()/clk_unprepare() path and @prepare_count + * @count: enable refcount + * @lock: protects clk_enable()/clk_disable() path and @count + * @depends: non-direct parent of clock to enable when this clock is enabled + * @vdd_class: voltage scaling requirement class + * @fmax: maximum frequency in Hz supported at each voltage level + * @parent: the current source of this clock + * @opp_table_populated: tracks if the OPP table of this clock has been filled + */ +struct clk { + uint32_t flags; + struct clk_ops *ops; + const char *dbg_name; + struct clk *depends; + struct clk_vdd_class *vdd_class; + unsigned long *fmax; + int num_fmax; + unsigned long rate; + struct clk *parent; + struct clk_src *parents; + unsigned int num_parents; + + struct list_head children; + struct list_head siblings; + struct list_head list; + + unsigned count; + unsigned notifier_count; + spinlock_t lock; + unsigned prepare_count; + struct mutex prepare_lock; + + unsigned long init_rate; + bool always_on; + bool opp_table_populated; + + struct dentry *clk_dir; +}; + +#define CLK_INIT(name) \ + .lock = __SPIN_LOCK_UNLOCKED((name).lock), \ + .prepare_lock = __MUTEX_INITIALIZER((name).prepare_lock), \ + .children = LIST_HEAD_INIT((name).children), \ + .siblings = LIST_HEAD_INIT((name).siblings), \ + .list = LIST_HEAD_INIT((name).list) + +bool is_rate_valid(struct clk *clk, unsigned long rate); +int vote_vdd_level(struct clk_vdd_class *vdd_class, int level); +int unvote_vdd_level(struct clk_vdd_class *vdd_class, int level); +int __clk_pre_reparent(struct clk *c, struct clk *new, unsigned long *flags); +void __clk_post_reparent(struct clk *c, struct clk *old, unsigned long *flags); + +/* Register clocks with the MSM clock driver */ +int msm_clock_register(struct clk_lookup *table, size_t size); +int of_msm_clock_register(struct device_node *np, struct clk_lookup *table, + size_t size); + +int clock_rcgwr_init(struct platform_device *pdev); +int clock_rcgwr_disable(struct platform_device *pdev); + +extern struct clk dummy_clk; +extern struct clk_ops clk_ops_dummy; + +#define CLK_DUMMY(clk_name, clk_id, clk_dev, flags) { \ + .con_id = clk_name, \ + .dev_id = clk_dev, \ + .clk = &dummy_clk, \ + } + +#define DEFINE_CLK_DUMMY(name, _rate) \ + static struct fixed_clk name = { \ + .c = { \ + .dbg_name = #name, \ + .rate = _rate, \ + .ops = &clk_ops_dummy, \ + CLK_INIT(name.c), \ + }, \ + }; + +#define CLK_LOOKUP(con, c, dev) { .con_id = con, .clk = &c, .dev_id = dev } +#define CLK_LOOKUP_OF(con, _c, dev) { .con_id = con, .clk = &(&_c)->c, \ + .dev_id = dev, .of_idx = clk_##_c } +#define CLK_LIST(_c) { .clk = &(&_c)->c, .of_idx = clk_##_c } + +static inline bool is_better_rate(unsigned long req, unsigned long best, + unsigned long new) +{ + if (IS_ERR_VALUE(new)) + return false; + + return (req <= new && new < best) || (best < req && best < new); +} + +extern int of_clk_add_provider(struct device_node *np, + struct clk *(*clk_src_get)(struct of_phandle_args *args, + void *data), + void *data); +extern void of_clk_del_provider(struct device_node *np); + +static inline const char *clk_name(struct clk *c) +{ + if (IS_ERR_OR_NULL(c)) + return "(null)"; + return c->dbg_name; +}; +#endif diff --git a/include/linux/clk/msm-clk.h b/include/linux/clk/msm-clk.h new file mode 100644 index 000000000000..1f613dfe33c1 --- /dev/null +++ b/include/linux/clk/msm-clk.h @@ -0,0 +1,122 @@ +/* Copyright (c) 2009, 2012-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ +#ifndef __MACH_CLK_H +#define __MACH_CLK_H + +#include <linux/notifier.h> + +#define CLKFLAG_INVERT 0x00000001 +#define CLKFLAG_NOINVERT 0x00000002 +#define CLKFLAG_NONEST 0x00000004 +#define CLKFLAG_NORESET 0x00000008 +#define CLKFLAG_RETAIN_PERIPH 0x00000010 +#define CLKFLAG_NORETAIN_PERIPH 0x00000020 +#define CLKFLAG_RETAIN_MEM 0x00000040 +#define CLKFLAG_NORETAIN_MEM 0x00000080 +#define CLKFLAG_SKIP_HANDOFF 0x00000100 +#define CLKFLAG_MIN 0x00000400 +#define CLKFLAG_MAX 0x00000800 +#define CLKFLAG_INIT_DONE 0x00001000 +#define CLKFLAG_INIT_ERR 0x00002000 +#define CLKFLAG_NO_RATE_CACHE 0x00004000 +#define CLKFLAG_MEASURE 0x00008000 +#define CLKFLAG_EPROBE_DEFER 0x00010000 + +struct clk_lookup; +struct clk; + +enum clk_reset_action { + CLK_RESET_DEASSERT = 0, + CLK_RESET_ASSERT = 1 +}; + +struct clk_src { + struct clk *src; + int sel; +}; + +/* Rate is maximum clock rate in Hz */ +int clk_set_max_rate(struct clk *clk, unsigned long rate); + +/* Assert/Deassert reset to a hardware block associated with a clock */ +int clk_reset(struct clk *clk, enum clk_reset_action action); + +/* Set clock-specific configuration parameters */ +int clk_set_flags(struct clk *clk, unsigned long flags); + +/* returns the mux selection index associated with a particular parent */ +int parent_to_src_sel(struct clk_src *parents, int num_parents, struct clk *p); + +/* returns the mux selection index associated with a particular parent */ +int clk_get_parent_sel(struct clk *c, struct clk *parent); + +/** + * DOC: clk notifier callback types + * + * PRE_RATE_CHANGE - called immediately before the clk rate is changed, + * to indicate that the rate change will proceed. Drivers must + * immediately terminate any operations that will be affected by the + * rate change. Callbacks may either return NOTIFY_DONE, NOTIFY_OK, + * NOTIFY_STOP or NOTIFY_BAD. + * + * ABORT_RATE_CHANGE: called if the rate change failed for some reason + * after PRE_RATE_CHANGE. In this case, all registered notifiers on + * the clk will be called with ABORT_RATE_CHANGE. Callbacks must + * always return NOTIFY_DONE or NOTIFY_OK. + * + * POST_RATE_CHANGE - called after the clk rate change has successfully + * completed. Callbacks must always return NOTIFY_DONE or NOTIFY_OK. + * + */ +#define PRE_RATE_CHANGE BIT(0) +#define POST_RATE_CHANGE BIT(1) +#define ABORT_RATE_CHANGE BIT(2) + +/** + * struct msm_clk_notifier - associate a clk with a notifier + * @clk: struct clk * to associate the notifier with + * @notifier_head: a blocking_notifier_head for this clk + * @node: linked list pointers + * + * A list of struct clk_notifier is maintained by the notifier code. + * An entry is created whenever code registers the first notifier on a + * particular @clk. Future notifiers on that @clk are added to the + * @notifier_head. + */ +struct msm_clk_notifier { + struct clk *clk; + struct srcu_notifier_head notifier_head; + struct list_head node; +}; + +/** + * struct msm_clk_notifier_data - rate data to pass to the notifier callback + * @clk: struct clk * being changed + * @old_rate: previous rate of this clk + * @new_rate: new rate of this clk + * + * For a pre-notifier, old_rate is the clk's rate before this rate + * change, and new_rate is what the rate will be in the future. For a + * post-notifier, old_rate and new_rate are both set to the clk's + * current rate (this was done to optimize the implementation). + */ +struct msm_clk_notifier_data { + struct clk *clk; + unsigned long old_rate; + unsigned long new_rate; +}; + +int msm_clk_notif_register(struct clk *clk, struct notifier_block *nb); + +int msm_clk_notif_unregister(struct clk *clk, struct notifier_block *nb); + +#endif diff --git a/include/linux/clk/msm-clock-generic.h b/include/linux/clk/msm-clock-generic.h new file mode 100644 index 000000000000..6d5468acf744 --- /dev/null +++ b/include/linux/clk/msm-clock-generic.h @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#ifndef __MSM_CLOCK_GENERIC_H +#define __MSM_CLOCK_GENERIC_H + +#include <linux/clk/msm-clk-provider.h> +#include <linux/of.h> + +/** + * struct fixed_clk - fixed rate clock + * @c: clk + */ +struct fixed_clk { + struct clk c; +}; + +/* ==================== Mux clock ==================== */ + +struct mux_clk; + +struct clk_mux_ops { + int (*set_mux_sel)(struct mux_clk *clk, int sel); + int (*get_mux_sel)(struct mux_clk *clk); + + /* Optional */ + bool (*is_enabled)(struct mux_clk *clk); + int (*enable)(struct mux_clk *clk); + void (*disable)(struct mux_clk *clk); + void __iomem *(*list_registers)(struct mux_clk *clk, int n, + struct clk_register_data **regs, u32 *size); +}; + +#define MUX_SRC_LIST(...) \ + .parents = (struct clk_src[]){__VA_ARGS__}, \ + .num_parents = ARRAY_SIZE(((struct clk_src[]){__VA_ARGS__})) + +#define MUX_REC_SRC_LIST(...) \ + .rec_parents = (struct clk * []){__VA_ARGS__}, \ + .num_rec_parents = ARRAY_SIZE(((struct clk * []){__VA_ARGS__})) + +struct mux_clk { + /* Parents in decreasing order of preference for obtaining rates. */ + struct clk_src *parents; + int num_parents; + /* Recursively search for the requested parent in rec_parents. */ + struct clk **rec_parents; + int num_rec_parents; + struct clk *safe_parent; + int safe_sel; + unsigned long safe_freq; + /* + * Before attempting a clk_round_rate on available sources, attempt a + * clk_get_rate on all those sources. If one of them is already at the + * necessary rate, that source will be used. + */ + bool try_get_rate; + struct clk_mux_ops *ops; + /* + * Set if you need the mux to try a new parent before falling back to + * the current parent. If the safe_parent field above is set, then the + * safe_sel intermediate source will only be used if we fall back to + * to the current parent during mux_set_rate. + */ + bool try_new_parent; + + /* Fields not used by helper function. */ + void *const __iomem *base; + u32 offset; + u32 en_offset; + u32 mask; + u32 shift; + u32 en_mask; + int low_power_sel; + void *priv; + + struct clk c; +}; + +static inline struct mux_clk *to_mux_clk(struct clk *c) +{ + return container_of(c, struct mux_clk, c); +} + +extern struct clk_ops clk_ops_gen_mux; + +/* ==================== Divider clock ==================== */ + +struct div_clk; + +struct clk_div_ops { + int (*set_div)(struct div_clk *clk, int div); + int (*get_div)(struct div_clk *clk); + bool (*is_enabled)(struct div_clk *clk); + int (*enable)(struct div_clk *clk); + void (*disable)(struct div_clk *clk); + void __iomem *(*list_registers)(struct div_clk *clk, int n, + struct clk_register_data **regs, u32 *size); +}; + +struct div_data { + unsigned int div; + unsigned int min_div; + unsigned int max_div; + unsigned long rate_margin; + /* + * Indicate whether this divider clock supports half-interger divider. + * If it is, all the min_div and max_div have been doubled. It means + * they are 2*N. + */ + bool is_half_divider; + /* + * Skip odd dividers since the hardware may not support them. + */ + bool skip_odd_div; + bool skip_even_div; + bool allow_div_one; + unsigned int cached_div; +}; + +struct div_clk { + struct div_data data; + + /* + * Some implementations may require the divider to be set to a "safe" + * value that allows reprogramming of upstream clocks without violating + * voltage constraints. + */ + unsigned long safe_freq; + + /* Optional */ + struct clk_div_ops *ops; + + /* Fields not used by helper function. */ + void *const __iomem *base; + u32 offset; + u32 mask; + u32 shift; + u32 en_mask; + void *priv; + struct clk c; +}; + +static inline struct div_clk *to_div_clk(struct clk *c) +{ + return container_of(c, struct div_clk, c); +} + +extern struct clk_ops clk_ops_div; +extern struct clk_ops clk_ops_slave_div; + +struct ext_clk { + struct clk c; + struct device *dev; + char *clk_id; +}; + +long parent_round_rate(struct clk *c, unsigned long rate); +unsigned long parent_get_rate(struct clk *c); +int parent_set_rate(struct clk *c, unsigned long rate); + +static inline struct ext_clk *to_ext_clk(struct clk *c) +{ + return container_of(c, struct ext_clk, c); +} + +extern struct clk_ops clk_ops_ext; + +#define DEFINE_FIXED_DIV_CLK(clk_name, _div, _parent) \ +static struct div_clk clk_name = { \ + .data = { \ + .max_div = _div, \ + .min_div = _div, \ + .div = _div, \ + }, \ + .c = { \ + .parent = _parent, \ + .dbg_name = #clk_name, \ + .ops = &clk_ops_div, \ + CLK_INIT(clk_name.c), \ + } \ +} + +#define DEFINE_FIXED_SLAVE_DIV_CLK(clk_name, _div, _parent) \ +static struct div_clk clk_name = { \ + .data = { \ + .max_div = _div, \ + .min_div = _div, \ + .div = _div, \ + }, \ + .c = { \ + .parent = _parent, \ + .dbg_name = #clk_name, \ + .ops = &clk_ops_slave_div, \ + CLK_INIT(clk_name.c), \ + } \ +} + +#define DEFINE_EXT_CLK(clk_name, _parent) \ +static struct ext_clk clk_name = { \ + .c = { \ + .parent = _parent, \ + .dbg_name = #clk_name, \ + .ops = &clk_ops_ext, \ + CLK_INIT(clk_name.c), \ + } \ +} + +/* ==================== Mux Div clock ==================== */ + +struct mux_div_clk; + +/* + * struct mux_div_ops + * the enable and disable ops are optional. + */ + +struct mux_div_ops { + int (*set_src_div)(struct mux_div_clk *, u32 src_sel, u32 div); + void (*get_src_div)(struct mux_div_clk *, u32 *src_sel, u32 *div); + int (*enable)(struct mux_div_clk *); + void (*disable)(struct mux_div_clk *); + bool (*is_enabled)(struct mux_div_clk *); + void __iomem *(*list_registers)(struct mux_div_clk *md, int n, + struct clk_register_data **regs, u32 *size); +}; + +/* + * struct mux_div_clk - combined mux/divider clock + * @priv + parameters needed by ops + * @safe_freq + when switching rates from A to B, the mux div clock will + instead switch from A -> safe_freq -> B. This allows the + mux_div clock to change rates while enabled, even if this + behavior is not supported by the parent clocks. + + If changing the rate of parent A also causes the rate of + parent B to change, then safe_freq must be defined. + + safe_freq is expected to have a source clock which is always + on and runs at only one rate. + * @parents + list of parents and mux indicies + * @ops + function pointers for hw specific operations + * @src_sel + the mux index which will be used if the clock is enabled. + * @try_get_rate + Set if you need the mux to directly jump to a source + that is at the desired rate currently. + * @force_enable_md + Set if the mux-div needs to be force enabled/disabled during + clk_enable/disable. + */ + +struct mux_div_clk { + /* Required parameters */ + struct mux_div_ops *ops; + struct div_data data; + struct clk_src *parents; + u32 num_parents; + + struct clk c; + + /* Internal */ + u32 src_sel; + + /* Optional parameters */ + void *priv; + void __iomem *base; + u32 div_mask; + u32 div_offset; + u32 div_shift; + u32 src_mask; + u32 src_offset; + u32 src_shift; + u32 en_mask; + u32 en_offset; + + u32 safe_div; + struct clk *safe_parent; + unsigned long safe_freq; + bool try_get_rate; + bool force_enable_md; +}; + +static inline struct mux_div_clk *to_mux_div_clk(struct clk *clk) +{ + return container_of(clk, struct mux_div_clk, c); +} + +extern struct clk_ops clk_ops_mux_div_clk; + +#endif diff --git a/include/linux/clkdev.h b/include/linux/clkdev.h index 08bffcc466de..0422183013ae 100644 --- a/include/linux/clkdev.h +++ b/include/linux/clkdev.h @@ -21,6 +21,7 @@ struct clk_lookup { struct list_head node; const char *dev_id; const char *con_id; + int of_idx; struct clk *clk; struct clk_hw *clk_hw; }; diff --git a/include/soc/qcom/clock-alpha-pll.h b/include/soc/qcom/clock-alpha-pll.h new file mode 100644 index 000000000000..b5a34b4cecb5 --- /dev/null +++ b/include/soc/qcom/clock-alpha-pll.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_ALPHA_PLL_H +#define __ARCH_ARM_MACH_MSM_CLOCK_ALPHA_PLL_H + +#include <linux/spinlock.h> +#include <linux/clk/msm-clk-provider.h> + +struct alpha_pll_masks { + u32 lock_mask; /* lock_det bit */ + u32 active_mask; /* active_flag in FSM mode */ + u32 update_mask; /* update bit for dynamic update */ + u32 vco_mask; /* vco_sel bits */ + u32 vco_shift; + u32 alpha_en_mask; /* alpha_en bit */ + u32 output_mask; /* pllout_* bits */ + u32 post_div_mask; + + u32 test_ctl_lo_mask; + u32 test_ctl_hi_mask; +}; + +struct alpha_pll_vco_tbl { + u32 vco_val; + unsigned long min_freq; + unsigned long max_freq; +}; + +#define VCO(a, b, c) { \ + .vco_val = a,\ + .min_freq = b,\ + .max_freq = c,\ +} + +struct alpha_pll_clk { + struct alpha_pll_masks *masks; + void *const __iomem *base; + u32 offset; + + /* if fsm_en_mask is set, config PLL to FSM mode */ + u32 fsm_reg_offset; + u32 fsm_en_mask; + + u32 enable_config; /* bitmask of outputs to be enabled */ + u32 post_div_config; /* masked post divider setting */ + u32 config_ctl_val; /* config register init value */ + u32 test_ctl_lo_val; /* test control settings */ + u32 test_ctl_hi_val; + + struct alpha_pll_vco_tbl *vco_tbl; + u32 num_vco; + u32 current_vco_val; + bool inited; + bool slew; + bool no_prepared_reconfig; + + /* + * Some chipsets need the offline request bit to be + * cleared on a second write to the register, even though + * SW wants the bit to be set. Set this flag to indicate + * that the workaround is required. + */ + bool offline_bit_workaround; + bool is_fabia; + unsigned long min_supported_freq; + struct clk c; +}; + +static inline struct alpha_pll_clk *to_alpha_pll_clk(struct clk *c) +{ + return container_of(c, struct alpha_pll_clk, c); +} + + +#endif +extern void __init_alpha_pll(struct clk *c); +extern struct clk_ops clk_ops_alpha_pll; +extern struct clk_ops clk_ops_alpha_pll_hwfsm; +extern struct clk_ops clk_ops_fixed_alpha_pll; +extern struct clk_ops clk_ops_dyna_alpha_pll; +extern struct clk_ops clk_ops_fixed_fabia_alpha_pll; +extern struct clk_ops clk_ops_fabia_alpha_pll; diff --git a/include/soc/qcom/clock-local2.h b/include/soc/qcom/clock-local2.h new file mode 100644 index 000000000000..42c991194391 --- /dev/null +++ b/include/soc/qcom/clock-local2.h @@ -0,0 +1,256 @@ +/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_2_H +#define __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_2_H + +#include <linux/spinlock.h> +#include <linux/clk/msm-clk-provider.h> +#include <linux/clk/msm-clk.h> + +/* + * Generic frequency-definition structs and macros + */ + +/** + * @freq_hz: output rate + * @src_freq: source freq for dynamic pll. For fixed plls, set to 0. + * @src_clk: source clock for freq_hz + * @m_val: M value corresponding to freq_hz + * @n_val: N value corresponding to freq_hz + * @d_val: D value corresponding to freq_hz + * @div_src_val: Pre divider value and source selection mux index for freq_hz + * @sys_vdd: Voltage level required for freq_hz + */ +struct clk_freq_tbl { + unsigned long freq_hz; + unsigned long src_freq; + struct clk *src_clk; + u32 m_val; + u32 n_val; + u32 d_val; + u32 div_src_val; + const unsigned sys_vdd; +}; + +#define FREQ_END (ULONG_MAX-1) +#define F_END { .freq_hz = FREQ_END } +#define FIXED_CLK_SRC 0 +/* + * Generic clock-definition struct and macros + */ +/** + * struct rcg_clk - root clock generator + * @cmd_rcgr_reg: command register + * @set_rate: function to set frequency + * @freq_tbl: frequency table for this RCG + * @current_freq: current RCG frequency + * @c: generic clock data + * @non_local_children: set if RCG has at least one branch owned by a diff EE + * @force_enable_rcgr: set if RCG needs to be force enabled/disabled during + * power sequence + * @base: pointer to base address of ioremapped registers. + */ +struct rcg_clk { + u32 cmd_rcgr_reg; + + void (*set_rate)(struct rcg_clk *, struct clk_freq_tbl *); + + struct clk_freq_tbl *freq_tbl; + struct clk_freq_tbl *current_freq; + struct clk c; + + bool non_local_children; + bool force_enable_rcgr; + void *const __iomem *base; +}; + +static inline struct rcg_clk *to_rcg_clk(struct clk *clk) +{ + return container_of(clk, struct rcg_clk, c); +} + +extern struct clk_freq_tbl rcg_dummy_freq; + +/** + * struct branch_clk - branch clock + * @set_rate: Set the frequency of this branch clock. + * @c: clk + * @cbcr_reg: branch control register + * @bcr_reg: block reset register + * @has_sibling: true if other branches are derived from this branch's source + * @cur_div: current branch divider value + * @max_div: maximum branch divider value (if zero, no divider exists) + * @halt_check: halt checking type + * @toggle_memory: toggle memory during enable/disable if true + * @no_halt_check_on_disable: When set, do not check status bit during + * clk_disable(). + * @check_enable_bit: Check the enable bit to determine clock status + during handoff. + * @base: pointer to base address of ioremapped registers. + */ +struct branch_clk { + void (*set_rate)(struct branch_clk *, struct clk_freq_tbl *); + struct clk c; + u32 cbcr_reg; + u32 bcr_reg; + int has_sibling; + u32 cur_div; + u32 max_div; + const u32 halt_check; + bool toggle_memory; + bool no_halt_check_on_disable; + bool check_enable_bit; + void *const __iomem *base; +}; + +static inline struct branch_clk *to_branch_clk(struct clk *clk) +{ + return container_of(clk, struct branch_clk, c); +} + +/** + * struct local_vote_clk - Voteable branch clock + * @c: clk + * @cbcr_reg: branch control register + * @vote_reg: voting register + * @en_mask: enable mask + * @halt_check: halt checking type + * @base: pointer to base address of ioremapped registers. + * An on/off switch with a rate derived from the parent. + */ +struct local_vote_clk { + struct clk c; + u32 cbcr_reg; + u32 vote_reg; + u32 bcr_reg; + u32 en_mask; + const u32 halt_check; + void * __iomem *base; +}; + +static inline struct local_vote_clk *to_local_vote_clk(struct clk *clk) +{ + return container_of(clk, struct local_vote_clk, c); +} + +/** + * struct reset_clk - Reset clock + * @c: clk + * @reset_reg: block reset register + * @base: pointer to base address of ioremapped registers. + */ +struct reset_clk { + struct clk c; + u32 reset_reg; + void *__iomem *base; +}; + +static inline struct reset_clk *to_reset_clk(struct clk *clk) +{ + return container_of(clk, struct reset_clk, c); +} +/** + * struct measure_clk - for rate measurement debug use + * @sample_ticks: sample period in reference clock ticks + * @multiplier: measurement scale-up factor + * @divider: measurement scale-down factor + * @c: clk +*/ +struct measure_clk { + u64 sample_ticks; + u32 multiplier; + u32 divider; + + struct clk c; +}; + +struct measure_clk_data { + struct clk *cxo; + u32 plltest_reg; + u32 plltest_val; + u32 xo_div4_cbcr; + u32 ctl_reg; + u32 status_reg; + void *const __iomem *base; +}; + +static inline struct measure_clk *to_measure_clk(struct clk *clk) +{ + return container_of(clk, struct measure_clk, c); +} + +/** + * struct gate_clk + * @c: clk + * @en_mask: ORed with @en_reg to enable gate clk + * @en_reg: register used to enable/disable gate clk + * @base: pointer to base address of ioremapped registers + */ +struct gate_clk { + struct clk c; + u32 en_mask; + u32 en_reg; + unsigned int delay_us; + void *const __iomem *base; +}; + +static inline struct gate_clk *to_gate_clk(struct clk *clk) +{ + return container_of(clk, struct gate_clk, c); +} + +/* + * Generic set-rate implementations + */ +void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf); +void set_rate_hid(struct rcg_clk *clk, struct clk_freq_tbl *nf); + +/* + * Variables from the clock-local driver + */ +extern spinlock_t local_clock_reg_lock; + +extern struct clk_ops clk_ops_empty; +extern struct clk_ops clk_ops_rcg; +extern struct clk_ops clk_ops_rcg_mnd; +extern struct clk_ops clk_ops_branch; +extern struct clk_ops clk_ops_vote; +extern struct clk_ops clk_ops_rcg_hdmi; +extern struct clk_ops clk_ops_rcg_edp; +extern struct clk_ops clk_ops_byte; +extern struct clk_ops clk_ops_pixel; +extern struct clk_ops clk_ops_byte_multiparent; +extern struct clk_ops clk_ops_pixel_multiparent; +extern struct clk_ops clk_ops_edppixel; +extern struct clk_ops clk_ops_gate; +extern struct clk_ops clk_ops_rst; +extern struct clk_mux_ops mux_reg_ops; +extern struct mux_div_ops rcg_mux_div_ops; +extern struct clk_div_ops postdiv_reg_ops; + +enum handoff pixel_rcg_handoff(struct clk *clk); +enum handoff byte_rcg_handoff(struct clk *clk); +unsigned long measure_get_rate(struct clk *c); + +/* + * Clock definition macros + */ +#define DEFINE_CLK_MEASURE(name) \ + struct clk name = { \ + .ops = &clk_ops_empty, \ + .dbg_name = #name, \ + CLK_INIT(name), \ + }; \ + +#endif /* __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_2_H */ diff --git a/include/soc/qcom/clock-pll.h b/include/soc/qcom/clock-pll.h new file mode 100644 index 000000000000..c17376966a09 --- /dev/null +++ b/include/soc/qcom/clock-pll.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_PLL_H +#define __ARCH_ARM_MACH_MSM_CLOCK_PLL_H + +#include <linux/clk/msm-clk-provider.h> + +/** + * struct pll_freq_tbl - generic PLL frequency definition + * @freq_hz: pll frequency in hz + * @l_val: pll l value + * @m_val: pll m value + * @n_val: pll n value + * @post_div_val: pll post divider value + * @pre_div_val: pll pre-divider value + * @vco_val: pll vco value + */ +struct pll_freq_tbl { + const u32 freq_hz; + const u32 l_val; + const u32 m_val; + const u32 n_val; + const u32 post_div_val; + const u32 pre_div_val; + const u32 vco_val; +}; + +/** + * struct pll_config_masks - PLL config masks struct + * @post_div_mask: mask for post divider bits location + * @pre_div_mask: mask for pre-divider bits location + * @vco_mask: mask for vco bits location + * @mn_en_mask: ORed with pll config register to enable the mn counter + * @main_output_mask: ORed with pll config register to enable the main output + * @apc_pdn_mask: ORed with pll config register to enable/disable APC PDN + * @lock_mask: Mask that indicates that the PLL has locked + */ +struct pll_config_masks { + u32 apc_pdn_mask; + u32 post_div_mask; + u32 pre_div_mask; + u32 vco_mask; + u32 mn_en_mask; + u32 main_output_mask; + u32 early_output_mask; + u32 lock_mask; +}; + +struct pll_config_vals { + u32 post_div_masked; + u32 pre_div_masked; + u32 config_ctl_val; + u32 config_ctl_hi_val; + u32 test_ctl_lo_val; + u32 test_ctl_hi_val; + u32 alpha_val; + bool enable_mn; +}; + +struct pll_spm_ctrl { + u32 offset; + u32 event_bit; + void __iomem *spm_base; +}; + +#define PLL_FREQ_END (UINT_MAX-1) +#define PLL_F_END { .freq_hz = PLL_FREQ_END } + +/** + * struct pll_vote_clk - phase locked loop (HW voteable) + * @soft_vote: soft voting variable for multiple PLL software instances + * @soft_vote_mask: soft voting mask for multiple PLL software instances + * @en_reg: enable register + * @en_mask: ORed with @en_reg to enable the clock + * @status_mask: ANDed with @status_reg to determine if PLL is active. + * @status_reg: status register + * @c: clock + */ +struct pll_vote_clk { + u32 *soft_vote; + u32 soft_vote_mask; + void __iomem *const en_reg; + u32 en_mask; + void __iomem *const status_reg; + u32 status_mask; + + struct clk c; + void *const __iomem *base; +}; + +extern struct clk_ops clk_ops_pll_vote; +extern struct clk_ops clk_ops_pll_acpu_vote; +extern struct clk_ops clk_ops_pll_sleep_vote; + +/* Soft voting values */ +#define PLL_SOFT_VOTE_PRIMARY BIT(0) +#define PLL_SOFT_VOTE_ACPU BIT(1) +#define PLL_SOFT_VOTE_AUX BIT(2) + +static inline struct pll_vote_clk *to_pll_vote_clk(struct clk *c) +{ + return container_of(c, struct pll_vote_clk, c); +} + +/** + * struct pll_clk - phase locked loop + * @mode_reg: enable register + * @l_reg: l value register + * @m_reg: m value register + * @n_reg: n value register + * @config_reg: configuration register, contains mn divider enable, pre divider, + * post divider and vco configuration. register name can be configure register + * or user_ctl register depending on targets + * @config_ctl_reg: "expert" configuration register + * @config_ctl_hi_reg: upper 32 bits of the "expert" configuration register + * @status_reg: status register, contains the lock detection bit + * @init_test_ctl: initialize the test control register + * @pgm_test_ctl_enable: program the test_ctl register in the enable sequence + * @test_ctl_dbg: if false will configure the test control registers. + * @masks: masks used for settings in config_reg + * @vals: configuration values to be written to PLL registers + * @freq_tbl: pll freq table + * @no_prepared_reconfig: Fail round_rate if pll is prepared + * @c: clk + * @base: pointer to base address of ioremapped registers. + */ +struct pll_clk { + void __iomem *const mode_reg; + void __iomem *const l_reg; + void __iomem *const m_reg; + void __iomem *const n_reg; + void __iomem *const alpha_reg; + void __iomem *const config_reg; + void __iomem *const config_ctl_reg; + void __iomem *const config_ctl_hi_reg; + void __iomem *const status_reg; + void __iomem *const alt_status_reg; + void __iomem *const test_ctl_lo_reg; + void __iomem *const test_ctl_hi_reg; + + bool init_test_ctl; + bool pgm_test_ctl_enable; + bool test_ctl_dbg; + + struct pll_config_masks masks; + struct pll_config_vals vals; + struct pll_freq_tbl *freq_tbl; + + unsigned long src_rate; + unsigned long min_rate; + unsigned long max_rate; + + bool inited; + bool no_prepared_reconfig; + + struct pll_spm_ctrl spm_ctrl; + struct clk c; + void *const __iomem *base; +}; + +extern struct clk_ops clk_ops_local_pll; +extern struct clk_ops clk_ops_sr2_pll; +extern struct clk_ops clk_ops_variable_rate_pll; +extern struct clk_ops clk_ops_variable_rate_pll_hwfsm; + +void __variable_rate_pll_init(struct clk *c); + +static inline struct pll_clk *to_pll_clk(struct clk *c) +{ + return container_of(c, struct pll_clk, c); +} + +int sr_pll_clk_enable(struct clk *c); +int sr_hpm_lp_pll_clk_enable(struct clk *c); + +struct pll_alt_config { + u32 val; + u32 mask; +}; + +struct pll_config { + u32 l; + u32 m; + u32 n; + u32 vco_val; + u32 vco_mask; + u32 pre_div_val; + u32 pre_div_mask; + u32 post_div_val; + u32 post_div_mask; + u32 mn_ena_val; + u32 mn_ena_mask; + u32 main_output_val; + u32 main_output_mask; + u32 aux_output_val; + u32 aux_output_mask; + u32 cfg_ctl_val; + /* SR2 PLL specific fields */ + u32 add_factor_val; + u32 add_factor_mask; + struct pll_alt_config alt_cfg; +}; + +struct pll_config_regs { + void __iomem *l_reg; + void __iomem *m_reg; + void __iomem *n_reg; + void __iomem *config_reg; + void __iomem *config_alt_reg; + void __iomem *config_ctl_reg; + void __iomem *mode_reg; + void *const __iomem *base; +}; + +void configure_sr_pll(struct pll_config *config, struct pll_config_regs *regs, + u32 ena_fsm_mode); +void configure_sr_hpm_lp_pll(struct pll_config *config, + struct pll_config_regs *, u32 ena_fsm_mode); +#endif diff --git a/include/soc/qcom/clock-rpm.h b/include/soc/qcom/clock-rpm.h new file mode 100644 index 000000000000..18fedd4c97f7 --- /dev/null +++ b/include/soc/qcom/clock-rpm.h @@ -0,0 +1,180 @@ +/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_RPM_H +#define __ARCH_ARM_MACH_MSM_CLOCK_RPM_H + +#include <linux/clk/msm-clk-provider.h> +#include <soc/qcom/rpm-smd.h> + +#define RPM_SMD_KEY_RATE 0x007A484B +#define RPM_SMD_KEY_ENABLE 0x62616E45 +#define RPM_SMD_KEY_STATE 0x54415453 + +#define RPM_CLK_BUFFER_A_REQ 0x616B6C63 +#define RPM_KEY_SOFTWARE_ENABLE 0x6E657773 +#define RPM_KEY_PIN_CTRL_CLK_BUFFER_ENABLE_KEY 0x62636370 + +struct clk_ops; +struct clk_rpmrs_data; +extern struct clk_ops clk_ops_rpm; +extern struct clk_ops clk_ops_rpm_branch; + +struct rpm_clk { + int rpm_res_type; + int rpm_key; + int rpm_clk_id; + const int rpm_status_id; + bool active_only; + bool enabled; + bool branch; /* true: RPM only accepts 1 for ON and 0 for OFF */ + struct clk_rpmrs_data *rpmrs_data; + struct rpm_clk *peer; + struct clk c; + uint32_t *last_active_set_vote; + uint32_t *last_sleep_set_vote; +}; + +static inline struct rpm_clk *to_rpm_clk(struct clk *clk) +{ + return container_of(clk, struct rpm_clk, c); +} + +/* + * RPM scaling enable function used for target that has an RPM resource for + * rpm clock scaling enable. + */ +int enable_rpm_scaling(void); + +int vote_bimc(struct rpm_clk *r, uint32_t value); + +extern struct clk_rpmrs_data clk_rpmrs_data_smd; + +/* + * A note on name##last_{active,sleep}_set_vote below: + * We track the last active and sleep set votes across both + * active-only and active+sleep set clocks. We use the same + * tracking variables for both clocks in order to keep both + * updated about the last vote irrespective of which clock + * actually made the request. This is the only way to allow + * optimizations that prevent duplicate requests from being sent + * to the RPM. Separate tracking does not work since it is not + * possible to know if the peer's last request was actually sent + * to the RPM. + */ + +#define __DEFINE_CLK_RPM(name, active, type, r_id, stat_id, dep, key, \ + rpmrsdata) \ + static struct rpm_clk active; \ + static uint32_t name##last_active_set_vote; \ + static uint32_t name##last_sleep_set_vote; \ + static struct rpm_clk name = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &active, \ + .rpmrs_data = (rpmrsdata),\ + .last_active_set_vote = &name##last_active_set_vote, \ + .last_sleep_set_vote = &name##last_sleep_set_vote, \ + .c = { \ + .ops = &clk_ops_rpm, \ + .dbg_name = #name, \ + CLK_INIT(name.c), \ + .depends = dep, \ + }, \ + }; \ + static struct rpm_clk active = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &name, \ + .active_only = true, \ + .rpmrs_data = (rpmrsdata),\ + .last_active_set_vote = &name##last_active_set_vote, \ + .last_sleep_set_vote = &name##last_sleep_set_vote, \ + .c = { \ + .ops = &clk_ops_rpm, \ + .dbg_name = #active, \ + CLK_INIT(active.c), \ + .depends = dep, \ + }, \ + }; + +#define __DEFINE_CLK_RPM_BRANCH(name, active, type, r_id, stat_id, r, \ + key, rpmrsdata) \ + static struct rpm_clk active; \ + static uint32_t name##last_active_set_vote; \ + static uint32_t name##last_sleep_set_vote; \ + static struct rpm_clk name = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &active, \ + .branch = true, \ + .rpmrs_data = (rpmrsdata),\ + .last_active_set_vote = &name##last_active_set_vote, \ + .last_sleep_set_vote = &name##last_sleep_set_vote, \ + .c = { \ + .ops = &clk_ops_rpm_branch, \ + .dbg_name = #name, \ + .rate = (r), \ + CLK_INIT(name.c), \ + }, \ + }; \ + static struct rpm_clk active = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &name, \ + .active_only = true, \ + .branch = true, \ + .rpmrs_data = (rpmrsdata),\ + .last_active_set_vote = &name##last_active_set_vote, \ + .last_sleep_set_vote = &name##last_sleep_set_vote, \ + .c = { \ + .ops = &clk_ops_rpm_branch, \ + .dbg_name = #active, \ + .rate = (r), \ + CLK_INIT(active.c), \ + }, \ + }; + +#define DEFINE_CLK_RPM_SMD(name, active, type, r_id, dep) \ + __DEFINE_CLK_RPM(name, active, type, r_id, 0, dep, \ + RPM_SMD_KEY_RATE, &clk_rpmrs_data_smd) + +#define DEFINE_CLK_RPM_SMD_BRANCH(name, active, type, r_id, r) \ + __DEFINE_CLK_RPM_BRANCH(name, active, type, r_id, 0, r, \ + RPM_SMD_KEY_ENABLE, &clk_rpmrs_data_smd) + +#define DEFINE_CLK_RPM_SMD_QDSS(name, active, type, r_id) \ + __DEFINE_CLK_RPM(name, active, type, r_id, \ + 0, 0, RPM_SMD_KEY_STATE, &clk_rpmrs_data_smd) +/* + * The RPM XO buffer clock management code aggregates votes for pin-control mode + * and software mode separately. Software-enable has higher priority over pin- + * control, and if the software-mode aggregation results in a 'disable', the + * buffer will be left in pin-control mode if a pin-control vote is in place. + */ +#define DEFINE_CLK_RPM_SMD_XO_BUFFER(name, active, r_id) \ + __DEFINE_CLK_RPM_BRANCH(name, active, RPM_CLK_BUFFER_A_REQ, r_id, 0, \ + 1000, RPM_KEY_SOFTWARE_ENABLE, &clk_rpmrs_data_smd) + +#define DEFINE_CLK_RPM_SMD_XO_BUFFER_PINCTRL(name, active, r_id) \ + __DEFINE_CLK_RPM_BRANCH(name, active, RPM_CLK_BUFFER_A_REQ, r_id, 0, \ + 1000, RPM_KEY_PIN_CTRL_CLK_BUFFER_ENABLE_KEY, &clk_rpmrs_data_smd) +#endif diff --git a/include/soc/qcom/clock-voter.h b/include/soc/qcom/clock-voter.h new file mode 100644 index 000000000000..9eb3898db1e8 --- /dev/null +++ b/include/soc/qcom/clock-voter.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2010-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_VOTER_H +#define __ARCH_ARM_MACH_MSM_CLOCK_VOTER_H + +#include <linux/clk/msm-clk-provider.h> + +struct clk_ops; +extern struct clk_ops clk_ops_voter; + +struct clk_voter { + int is_branch; + bool enabled; + struct clk c; +}; + +static inline struct clk_voter *to_clk_voter(struct clk *clk) +{ + return container_of(clk, struct clk_voter, c); +} + +#define __DEFINE_CLK_VOTER(clk_name, _parent, _default_rate, _is_branch) \ + struct clk_voter clk_name = { \ + .is_branch = (_is_branch), \ + .c = { \ + .parent = _parent, \ + .dbg_name = #clk_name, \ + .ops = &clk_ops_voter, \ + .rate = _default_rate, \ + CLK_INIT(clk_name.c), \ + }, \ + } + +#define DEFINE_CLK_VOTER(clk_name, _parent, _default_rate) \ + __DEFINE_CLK_VOTER(clk_name, _parent, _default_rate, 0) + +#define DEFINE_CLK_BRANCH_VOTER(clk_name, _parent) \ + __DEFINE_CLK_VOTER(clk_name, _parent, 1000, 1) + +#endif diff --git a/include/soc/qcom/msm-clock-controller.h b/include/soc/qcom/msm-clock-controller.h new file mode 100644 index 000000000000..d6bd4e03af0d --- /dev/null +++ b/include/soc/qcom/msm-clock-controller.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + * + */ + +#ifndef __ARCH_ARM_MSM_CLOCK_CONTROLLER_H +#define __ARCH_ARM_MSM_CLOCK_CONTROLLER_H + +#include <linux/list.h> +#include <linux/clkdev.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define dt_err(np, fmt, ...) \ + pr_err("%s: " fmt, np->name, ##__VA_ARGS__) +#define dt_prop_err(np, str, fmt, ...) \ + dt_err(np, "%s: " fmt, str, ##__VA_ARGS__) + +/** + * struct msmclk_parser + * @compatible + * matches compatible property from devicetree + * @parsedt + * constructs & returns an instance of the appropriate obj based on + * the data from devicetree. + */ +struct msmclk_parser { + struct list_head list; + char *compatible; + void * (*parsedt)(struct device *dev, struct device_node *of); +}; + +#define MSMCLK_PARSER(fn, str, id) \ +static struct msmclk_parser _msmclk_##fn##id = { \ + .list = LIST_HEAD_INIT(_msmclk_##fn##id.list), \ + .compatible = str, \ + .parsedt = fn, \ +}; \ +static int __init _msmclk_init_##fn##id(void) \ +{ \ + msmclk_parser_register(&_msmclk_##fn##id); \ + return 0; \ +} \ +early_initcall(_msmclk_init_##fn##id) + +/* + * struct msmclk_data + * @base + * ioremapped region for sub_devices + * @list + * tracks all registered driver instances + * @htable + * tracks all registered child clocks + * @clk_tbl + * array of clk_lookup to be registered with the clock framework + */ +#define HASHTABLE_SIZE 200 +struct msmclk_data { + void __iomem *base; + struct device *dev; + struct list_head list; + struct hlist_head htable[HASHTABLE_SIZE]; + struct clk_lookup *clk_tbl; + int clk_tbl_size; + int max_clk_tbl_size; +}; + +#if defined(CONFIG_MSM_CLK_CONTROLLER_V2) + +/* Utility functions */ +int of_property_count_phandles(struct device_node *np, char *propname); +int of_property_read_phandle_index(struct device_node *np, char *propname, + int index, phandle *p); +void *msmclk_generic_clk_init(struct device *dev, struct device_node *np, + struct clk *c); + +/* + * msmclk_parser_register + * Registers a parser which will be matched with a node from dt + * according to the compatible string. + */ +void msmclk_parser_register(struct msmclk_parser *); + +/* + * msmclk_parse_phandle + * On hashtable miss, the corresponding entry will be retrieved from + * devicetree, and added to the hashtable. + */ +void *msmclk_parse_phandle(struct device *dev, phandle key); +/* + * msmclk_lookup_phandle + * Straightforward hashtable lookup + */ +void *msmclk_lookup_phandle(struct device *dev, phandle key); + +int __init msmclk_init(void); +#else + +static inline int of_property_count_phandles(struct device_node *np, + char *propname) +{ + return 0; +} + +static inline int of_property_read_phandle_index(struct device_node *np, + char *propname, int index, phandle *p) +{ + return 0; +} + +static inline void *msmclk_generic_clk_init(struct device *dev, + struct device_node *np, struct clk *c) +{ + return ERR_PTR(-EINVAL); +} + +static inline void msmclk_parser_register(struct msmclk_parser *p) {}; + +static inline void *msmclk_parse_phandle(struct device *dev, phandle key) +{ + return ERR_PTR(-EINVAL); +} + +static inline void *msmclk_lookup_phandle(struct device *dev, phandle key) +{ + return ERR_PTR(-EINVAL); +} + +static inline int __init msmclk_init(void) +{ + return 0; +} + +#endif /* CONFIG_MSM_CLK_CONTROLLER_V2 */ +#endif /* __ARCH_ARM_MSM_CLOCK_CONTROLLER_H */ |
