[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1412894671-5921-3-git-send-email-sbranden@broadcom.com>
Date: Thu, 9 Oct 2014 15:44:26 -0700
From: Scott Branden <sbranden@...adcom.com>
To: Christian Daudt <bcm@...thebug.org>,
Matt Porter <mporter@...aro.org>,
Russell King <linux@....linux.org.uk>,
<bcm-kernel-feedback-list@...adcom.com>,
Mike Turquette <mturquette@...aro.org>,
Alex Elder <elder@...aro.org>,
Rob Herring <robh+dt@...nel.org>,
Pawel Moll <pawel.moll@....com>,
Mark Rutland <mark.rutland@....com>,
Ian Campbell <ijc+devicetree@...lion.org.uk>,
"Kumar Gala" <galak@...eaurora.org>,
Andrew Morton <akpm@...ux-foundation.org>,
"David S. Miller" <davem@...emloft.net>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Joe Perches <joe@...ches.com>,
"Mauro Carvalho Chehab" <m.chehab@...sung.com>,
Antti Palosaari <crope@....fi>
CC: JD Zheng <jdzheng@...adcom.com>, Ray Jui <rjui@...adcom.com>,
<linux-arm-kernel@...ts.infradead.org>,
<linux-kernel@...r.kernel.org>, <devicetree@...r.kernel.org>,
Jonathan Richardson <jonathar@...adcom.com>,
Scott Branden <sbranden@...adcom.com>
Subject: [PATCH V4 2/7] clk: Clock driver support for Broadcom Cygnus SoC
From: Jonathan Richardson <jonathar@...adcom.com>
The iProc clock driver controls PLLs common across iProc chips. The
cygnus driver controls cygnus specific features and variations.
Reviewed-by: Ray Jui <rjui@...adcom.com>
Tested-by: Jonathan Richardson <jonathar@...adcom.com>
Reviewed-by: JD (Jiandong) Zheng <jdzheng@...adcom.com>
Signed-off-by: Scott Branden <sbranden@...adcom.com>
---
drivers/clk/Makefile | 1 +
drivers/clk/bcm/Makefile | 2 +
drivers/clk/bcm/clk-cygnus.c | 1186 ++++++++++++++++++++++++++++++++++++++++++
drivers/clk/bcm/clk-iproc.c | 451 ++++++++++++++++
4 files changed, 1640 insertions(+)
create mode 100644 drivers/clk/bcm/clk-cygnus.c
create mode 100644 drivers/clk/bcm/clk-iproc.c
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f537a0b..8ac0a31 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o
obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o
obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o
obj-$(CONFIG_COMMON_CLK_AT91) += at91/
+obj-$(CONFIG_ARCH_BCM_IPROC) += bcm/
obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm/
obj-$(CONFIG_ARCH_BERLIN) += berlin/
obj-$(CONFIG_ARCH_HI3xxx) += hisilicon/
diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile
index 6297d05..f803919 100644
--- a/drivers/clk/bcm/Makefile
+++ b/drivers/clk/bcm/Makefile
@@ -2,3 +2,5 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-kona.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-kona-setup.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm281xx.o
obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o
+obj-$(CONFIG_ARCH_BCM_IPROC) += clk-iproc.o
+obj-$(CONFIG_ARCH_BCM_CYGNUS) += clk-cygnus.o
diff --git a/drivers/clk/bcm/clk-cygnus.c b/drivers/clk/bcm/clk-cygnus.c
new file mode 100644
index 0000000..2a4f976
--- /dev/null
+++ b/drivers/clk/bcm/clk-cygnus.c
@@ -0,0 +1,1186 @@
+/*
+ * Copyright 2014 Broadcom Corporation. All rights reserved.
+ *
+ * Unless you and Broadcom execute a separate written software license
+ * agreement governing use of this software, this software is licensed to you
+ * under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; 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/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+
+/*
+ * The CRU contains two similar PLLs: LCPLL and GENPLL,
+ * both with several output channels divided from the PLL
+ * output.
+ */
+
+#define CRU_LCPLL_CONTROL1_OFFSET 0x04
+#define CRU_LCPLL_STATUS_OFFSET 0x18
+
+#define LCPLL0_PDIV_SHIFT 26
+#define LCPLL0_PDIV_MASK 0xf
+#define LCPLL0_NDIV_SHIFT 16
+#define LCPLL0_NDIV_MASK 0x3ff
+#define LCPLL_ENABLEB_CH_SHIFT 7
+#define LCPLL_ENABLEB_CH_MASK 0x3f
+#define LCPLL_MDIV_MASK 0xff
+#define LCPLL_STATUS_LOCK_SHIFT 12
+
+#define LCPLL0_CONTROL0_OFFSET 0x00
+#define LCPLL0_CONTROL1_OFFSET 0x04
+#define LCPLL0_CONTROL2_OFFSET 0x08
+#define LCPLL0_CONTROL3_OFFSET 0x0c
+
+#define GENPLL_CONTROL0_OFFSET 0x00
+#define GENPLL_CONTROL1_OFFSET 0x04
+#define GENPLL_CONTROL2_OFFSET 0x08
+#define GENPLL_CONTROL3_OFFSET 0x0c
+#define GENPLL_CONTROL4_OFFSET 0x10
+#define GENPLL_CONTROL5_OFFSET 0x14
+#define GENPLL_CONTROL6_OFFSET 0x18
+#define GENPLL_CONTROL7_OFFSET 0x1c
+#define GENPLL_CONTROL8_OFFSET 0x20
+#define GENPLL_CONTROL9_OFFSET 0x24
+#define GENPLL_STATUS_OFFSET 0x28
+
+#define GENPLL_ENABLEB_CH_SHIFT 0x6
+#define GENPLL_ENABLEB_CH_MASK 0x3f
+
+#define GENPLL_STATUS_LOCK_SHIFT 12
+#define GENPLL_STATUS_LOCK_MASK 1
+#define GENPLL_CONTROL4_NDIV_INT_SHIFT 20
+#define GENPLL_CONTROL4_NDIV_INT_MASK 0x3FF
+#define GENPLL_CONTROL4_NDIV_FRAC_SHIFT 0
+#define GENPLL_CONTROL4_NDIV_FRAC_MASK 0xFFFFF
+#define GENPLL_CONTROL5_PDIV_SHIFT 0
+#define GENPLL_CONTROL5_PDIV_MASK 0xF
+#define GENPLL_MDIV_MASK 0xff
+
+#define MIPI_DSI_GENPLL_ENABLEB_CH_SHIFT 12
+#define NDIV_FRAC_DIVISOR 0x100000
+
+#define ASIU_MIPI_GENPLL_PWRON_SHIFT 20
+#define ASIU_MIPI_GENPLL_PWRON_PLL_SHIFT 19
+#define ASIU_MIPI_GENPLL_PWRON_BG_SHIFT 18
+#define ASIU_MIPI_GENPLL_PWRON_LDO_SHIFT 17
+#define ASIU_MIPI_GENPLL_ISO_IN_SHIFT 16
+#define ASIU_AUDIO_GENPLL_PWRON_PLL_SHIFT 11
+#define ASIU_AUDIO_GENPLL_PWRON_BG_SHIFT 10
+#define ASIU_AUDIO_GENPLL_PWRON_LDO_SHIFT 9
+#define ASIU_AUDIO_GENPLL_ISO_IN 8
+
+#define CLK_RATE_NO_DIV -1
+
+/*
+ * The clock framework may call recalc even if a clock is is unused, and
+ * therefore before being prepared/enabled. State checking is done for the
+ * MIPI PLL to prevent reading from a MIPI DSI register before the PLL is
+ * powered up because it will cause corruption (imprecise external aborts)
+ * sometimer later on.
+ */
+enum clock_state {
+ CLK_ENABLED,
+ CLK_PREPARED,
+ CLK_DISABLED
+};
+
+struct cygnus_clk {
+ struct clk_hw hw;
+ void __iomem *regs_base;
+ void __iomem *pll_ctrl_reg;
+ void __iomem *clock_gate_ctrl_reg;
+ int chan;
+ int internal_div;
+ unsigned long rate;
+ enum clock_state state;
+};
+
+#define to_cygnus_clk(p) container_of(p, struct cygnus_clk, hw)
+
+/* Identifies LCPLL clock channels. */
+enum cygnus_lcpll_clk_chan {
+ LCPLL_CH0_PCIE_PHY_REF_CLK = 0,
+ LCPLL_CH1_DDR_CLK = 1,
+ LCPLL_CH2_SDIO_CLK = 2,
+ LCPLL_CH3_USB_PHY_REF_CLK = 3,
+ LCPLL_CH4_ASIU_SMART_CARD_CLK = 4,
+ LCPLL_CH5 = 5
+};
+
+/* Identifies GENPLL clock channels. */
+enum cygnus_genpll_clk_chan {
+ GENPLL_CH0_AXI21_CLK = 0,
+ GENPLL_CH1_25MHZ_CLK = 1,
+ GENPLL_CH2_SYS_CLK = 2,
+ GENPLL_CH3_ETHERNET_CLK = 3,
+ GENPLL_CH4_ASIU_AUDIO_CLK = 4,
+ GENPLL_CH5_ASIU_CAN_CLK = 5
+};
+
+/*
+ * Channels for Oscillator dervived clocks are values used to determine
+ * which clock to enable/disable from the top clock gating control.
+ */
+enum cygnus_osc_derived_clk_chan {
+ OSC_DERIVED_CH0_KEYPAD_CLK = 0,
+ OSC_DERIVED_CH1_ADC_CLK = 1,
+ OSC_DERIVED_CH2_PWM_CLK = 2,
+};
+
+enum cygnus_mipi_pll_clk_chan {
+ MIPI_PLL_CH0_MIPI_PHY_CLK = 0,
+ MIPI_PLL_CH1_LCD_CLK = 1,
+ MIPI_PLL_CH2_3D_GRAPHICS_CLK = 2,
+};
+
+/* Order of registers defined in DT. */
+enum cygnus_clk_dt_regs {
+ CYGNUS_CLK_BASE_REG = 0,
+ CYGNUS_CLK_GATE_CTRL_REG,
+ CYGNUS_PLL_CTRL_REG
+};
+
+enum cygnus_top_clk_gating_ctrl_offsets {
+ GFX_CLK_GATE_EN = 0,
+ AUD_CLK_GATE_EN,
+ CAM_CLK_GATE_EN,
+ MIPI_DSI_CLK_GATE_EN,
+ LCD_CLK_GATE_EN,
+ D1W_CLK_GATE_EN,
+ CAN_CLK_GATE_EN,
+ KEYPAD_CLK_GATE_EN,
+ SMARTCARD_CLK_GATE_EN,
+ ADC_CLK_GATE_EN,
+ CRYPTO_CLK_GATE_EN
+};
+
+/*
+ * Enable clocks controlled through the top clock gating control.
+ *
+ * @param enable true = enable clock, false = disable clock
+ */
+static void cygnus_clkgate_enable_disable(void __iomem *clkgate_reg,
+ enum cygnus_top_clk_gating_ctrl_offsets offset, bool enable)
+{
+ u32 val = readl(clkgate_reg);
+
+ /* Enable or disable the clock. */
+ if (enable)
+ val |= 1 << offset;
+ else
+ val &= ~(1 << offset);
+
+ writel(val, clkgate_reg);
+}
+
+/*
+ * Powers on/off the MIPI GENPLL using CRMU_PLL_AON_CTRL register.
+ *
+ * @param power_on true to power on PLL, false to power off
+ */
+static void cygnus_mipi_genpll_power_on_off(void __iomem *pll_ctrl_reg,
+ bool power_on)
+{
+ u32 val;
+ u32 pll_ldo_on = ((1 << ASIU_MIPI_GENPLL_PWRON_SHIFT) |
+ (1 << ASIU_MIPI_GENPLL_PWRON_PLL_SHIFT) |
+ (1 << ASIU_MIPI_GENPLL_PWRON_BG_SHIFT) |
+ (1 << ASIU_MIPI_GENPLL_PWRON_LDO_SHIFT));
+
+ val = readl(pll_ctrl_reg);
+
+ /*
+ * Set PLL on/off. Set input isolation mode to 1 when disabled, 0 when
+ * enabled.
+ */
+ if (power_on) {
+ val |= pll_ldo_on;
+ val &= ~(1 << ASIU_MIPI_GENPLL_ISO_IN_SHIFT);
+ } else {
+ val &= ~pll_ldo_on;
+ val |= 1 << ASIU_MIPI_GENPLL_ISO_IN_SHIFT;
+ }
+
+ writel(val, pll_ctrl_reg);
+}
+
+/*
+ * Powers on/off the audio PLL using CRMU_PLL_AON_CTRL register.
+ *
+ * @param power_on true to power on PLL, false to power off
+ */
+static void cygnus_audio_genpll_power_on_off(void __iomem *pll_ctrl_reg,
+ bool power_on)
+{
+ u32 val;
+ u32 pll_ldo_on = ((1 << ASIU_AUDIO_GENPLL_PWRON_PLL_SHIFT) |
+ (1 << ASIU_AUDIO_GENPLL_PWRON_BG_SHIFT) |
+ (1 << ASIU_AUDIO_GENPLL_PWRON_LDO_SHIFT));
+
+ val = readl(pll_ctrl_reg);
+
+ /*
+ * Set PLL on/off. Set input isolation mode to 1 when disabled, 0 when
+ * enabled.
+ */
+ if (power_on) {
+ val |= pll_ldo_on;
+ val &= ~(1 << ASIU_AUDIO_GENPLL_ISO_IN);
+ } else {
+ val &= ~pll_ldo_on;
+ val |= 1 << ASIU_AUDIO_GENPLL_ISO_IN;
+ }
+
+ writel(val, pll_ctrl_reg);
+}
+
+/*
+ * Get PLL running status and calculate output frequency
+ */
+static unsigned long cygnus_lcpll_status(struct cygnus_clk *clk,
+ unsigned long parent_rate)
+{
+ u32 reg;
+ unsigned pdiv, ndiv;
+
+ /* read status register */
+ reg = readl(clk->regs_base + CRU_LCPLL_STATUS_OFFSET);
+
+ /* Must be locked for proper PLL operation. */
+ if ((reg & (1 << LCPLL_STATUS_LOCK_SHIFT)) == 0) {
+ clk->rate = 0;
+ return -EIO;
+ }
+
+ /*
+ * Calculate PLL frequency based on LCPLL divider values:
+ * pdiv = LCPLL pre-divider ratio
+ * ndiv = LCPLL feedback divider
+ *
+ * The frequency is calculated by:
+ * ndiv * (parent clock rate / pdiv)
+ */
+
+ reg = readl(clk->regs_base + CRU_LCPLL_CONTROL1_OFFSET);
+
+ /* feedback divider integer and fraction parts */
+ pdiv = (reg >> LCPLL0_PDIV_SHIFT) & LCPLL0_PDIV_MASK;
+ ndiv = (reg >> LCPLL0_NDIV_SHIFT) & LCPLL0_NDIV_MASK;
+
+ if (pdiv == 0)
+ return -EIO;
+
+ clk->rate = ndiv * (parent_rate / pdiv);
+
+ return clk->rate;
+}
+
+static unsigned long cygnus_lcpll_clk_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *bcm_clk = to_cygnus_clk(hwclk);
+
+ return cygnus_lcpll_status(bcm_clk, parent_rate);
+}
+
+static const struct clk_ops cygnus_lcpll_ops = {
+ .recalc_rate = cygnus_lcpll_clk_recalc_rate,
+};
+
+static int cygnus_lcpll_chan_status(struct cygnus_clk *clk,
+ unsigned long parent_rate)
+{
+ void * __iomem base;
+ u32 reg;
+ unsigned enable;
+ unsigned mdiv;
+ int offset = 0;
+ int shift = 0;
+
+ /* Register address is only stored in PLL structure */
+ base = clk->regs_base;
+ BUG_ON(base == NULL);
+
+ /* enable bit is in enableb_ch[] inversed */
+ enable = ((readl(base + LCPLL0_CONTROL0_OFFSET) >>
+ LCPLL_ENABLEB_CH_SHIFT) & LCPLL_ENABLEB_CH_MASK) ^
+ LCPLL_ENABLEB_CH_MASK;
+
+ if ((enable & (1 << clk->chan)) == 0) {
+ clk->rate = 0;
+ return -EIO;
+ }
+
+ /* MDIV for the 6 channels is spread over two registers. */
+ switch (clk->chan) {
+ case LCPLL_CH0_PCIE_PHY_REF_CLK:
+ offset = LCPLL0_CONTROL2_OFFSET; shift = 0;
+ break;
+
+ case LCPLL_CH1_DDR_CLK:
+ offset = LCPLL0_CONTROL2_OFFSET; shift = 10;
+ break;
+
+ case LCPLL_CH2_SDIO_CLK:
+ offset = LCPLL0_CONTROL2_OFFSET; shift = 20;
+ break;
+
+ case LCPLL_CH3_USB_PHY_REF_CLK:
+ offset = LCPLL0_CONTROL3_OFFSET; shift = 0;
+ break;
+
+ case LCPLL_CH4_ASIU_SMART_CARD_CLK:
+ offset = LCPLL0_CONTROL3_OFFSET; shift = 10;
+ break;
+
+ case LCPLL_CH5:
+ offset = LCPLL0_CONTROL3_OFFSET; shift = 20;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Read MDIV for requested channel. */
+ reg = readl(base + offset);
+ mdiv = (reg >> shift) & LCPLL_MDIV_MASK;
+
+ /* when divisor is 0, it behaves as max+1 */
+ if (mdiv == 0)
+ mdiv = 256;
+
+ clk->rate = parent_rate / mdiv;
+
+ pr_debug("LCPLL[%d] mdiv=%u Prate=%lu rate=%lu\n",
+ clk->chan, mdiv, parent_rate, clk->rate);
+
+ return clk->rate;
+}
+
+static unsigned long cygnus_lcpll_chan_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *bcm_clk = to_cygnus_clk(hwclk);
+
+ return cygnus_lcpll_chan_status(bcm_clk, parent_rate);
+}
+
+static const struct clk_ops cygnus_lcpll_chan_ops = {
+ .recalc_rate = cygnus_lcpll_chan_recalc_rate,
+};
+
+/*
+ * Get PLL running status and calculate output frequency
+ */
+static unsigned long cygnus_genpll_status(struct cygnus_clk *clk,
+ unsigned long parent_rate)
+{
+ u32 reg;
+ unsigned pdiv;
+ unsigned ndiv_int;
+ unsigned ndiv_frac;
+
+ /* Read PLL status register. It must be locked. */
+ reg = readl(clk->regs_base + GENPLL_STATUS_OFFSET);
+ if ((reg & (1 << GENPLL_STATUS_LOCK_SHIFT)) == 0) {
+ clk->rate = 0;
+ return -EIO;
+ }
+
+ /* Calculate PLL frequency */
+
+ /* Get PLL feedback divider values. */
+ reg = readl(clk->regs_base + GENPLL_CONTROL4_OFFSET);
+
+ /* feedback divider integer and fraction parts */
+ ndiv_int = reg >> GENPLL_CONTROL4_NDIV_INT_SHIFT;
+ ndiv_frac = reg & GENPLL_CONTROL4_NDIV_INT_MASK;
+ ndiv_int += ndiv_frac / NDIV_FRAC_DIVISOR;
+
+ /* Get pdiv - first 4 bits. */
+ reg = readl(clk->regs_base + GENPLL_CONTROL5_OFFSET);
+ pdiv = reg & GENPLL_CONTROL5_PDIV_MASK;
+ if (pdiv == 0)
+ return -EIO;
+
+ clk->rate = (parent_rate / pdiv) * ndiv_int;
+
+ return clk->rate;
+}
+
+static unsigned long cygnus_genpll_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *bcm_clk = to_cygnus_clk(hwclk);
+
+ return cygnus_genpll_status(bcm_clk, parent_rate);
+}
+
+static const struct clk_ops cygnus_genpll_ops = {
+ .recalc_rate = cygnus_genpll_recalc_rate,
+};
+
+/*
+ * Calculates clock rate of the GENPLL channel requested. The clock rate is
+ * calculated as: the configured clock rate
+ * Parent clock rate / mdiv
+ */
+static unsigned long cygnus_genpll_chan_get_rate(struct cygnus_clk *clk,
+ unsigned long parent_rate, int enableb_ch_shift)
+{
+ u32 reg;
+ unsigned enable;
+ unsigned mdiv;
+ unsigned offset = 0;
+ unsigned shift = 0;
+
+ /*
+ * Read ENABLEB_CH to determine which channels are enabled. The enable
+ * bits are inversed: 0 = channel enabled, 1 = channel disabled.
+ */
+ reg = readl(clk->regs_base + GENPLL_CONTROL1_OFFSET);
+ enable = ((reg >> enableb_ch_shift) &
+ GENPLL_ENABLEB_CH_MASK) ^ GENPLL_ENABLEB_CH_MASK;
+
+ /* If channel is disabled the rate is 0. */
+ if ((enable & (1 << clk->chan)) == 0) {
+ clk->rate = 0;
+ return -EIO;
+ }
+
+ /* MDIV for the 6 channels is spread over two registers. */
+ switch (clk->chan) {
+ case 0:
+ offset = GENPLL_CONTROL8_OFFSET; shift = 0;
+ break;
+
+ case 1:
+ offset = GENPLL_CONTROL8_OFFSET; shift = 10;
+ break;
+
+ case 2:
+ offset = GENPLL_CONTROL8_OFFSET; shift = 20;
+ break;
+
+ case 3:
+ offset = GENPLL_CONTROL9_OFFSET; shift = 0;
+ break;
+
+ case 4:
+ offset = GENPLL_CONTROL9_OFFSET; shift = 10;
+ break;
+
+ case 5:
+ offset = GENPLL_CONTROL9_OFFSET; shift = 20;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Read MDIV (post divider ratio) for requested channel. */
+ reg = readl(clk->regs_base + offset);
+ mdiv = (reg >> shift) & GENPLL_MDIV_MASK;
+
+ /* When divisor is 0, it behaves as max+1. */
+ if (mdiv == 0)
+ mdiv = 256;
+
+ clk->rate = parent_rate / mdiv;
+
+ pr_debug("GENPLL[%d] mdiv=%u parent rate=%lu rate=%lu\n",
+ clk->chan, mdiv, parent_rate, clk->rate);
+
+ return clk->rate;
+}
+
+/*
+ * Powers on the audio PLL for the audio channel from the PLL. No other
+ * GENPLL channels require powering on.
+ */
+static int cygnus_genpll_chan_prepare(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+ struct clk *parent_clk = clk_get_parent(hwclk->clk);
+ struct cygnus_clk *cyg_parent_clk =
+ to_cygnus_clk(__clk_get_hw(parent_clk));
+
+ if (WARN_ON(!cyg_parent_clk->pll_ctrl_reg))
+ return -EIO;
+
+ if (clk->chan == GENPLL_CH4_ASIU_AUDIO_CLK) {
+ pr_debug("GENPLL[%d]: Powering on audio PLL/LDO\n", clk->chan);
+ cygnus_audio_genpll_power_on_off(
+ cyg_parent_clk->pll_ctrl_reg, true);
+ }
+
+ return 0;
+}
+
+/*
+ * Powers off the audio PLL for the audio channel from the PLL. No other
+ * GENPLL channels require powering off.
+ */
+static void cygnus_genpll_chan_unprepare(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+ struct clk *parent_clk = clk_get_parent(hwclk->clk);
+ struct cygnus_clk *cyg_parent_clk =
+ to_cygnus_clk(__clk_get_hw(parent_clk));
+
+ if (WARN_ON(!cyg_parent_clk->pll_ctrl_reg))
+ return;
+
+ if (clk->chan == GENPLL_CH4_ASIU_AUDIO_CLK) {
+ pr_debug("GENPLL[%d]: Powering down audio PLL and LDO\n",
+ clk->chan);
+ cygnus_audio_genpll_power_on_off(cyg_parent_clk->pll_ctrl_reg,
+ false);
+ }
+}
+
+static unsigned long cygnus_genpll_chan_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *bcm_clk = to_cygnus_clk(hwclk);
+
+ return cygnus_genpll_chan_get_rate(bcm_clk, parent_rate,
+ GENPLL_ENABLEB_CH_SHIFT);
+}
+
+/*
+ * Enables GENPLL channels. The only PLL channel that is controlled through
+ * the top clock gating control is the audio clock which requires enabling.
+ *
+ * Individual channels aren't enabled/disabled on the PLL because they are
+ * enabled by default and drivers don't always refer to them, meaning the
+ * clock framework would disable them. This can be added later when power
+ * saving is a concern.
+ */
+static int cygnus_genpll_chan_enable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+ struct clk *parent_clk = clk_get_parent(hwclk->clk);
+ struct cygnus_clk *cyg_parent_clk =
+ to_cygnus_clk(__clk_get_hw(parent_clk));
+ int parent_rate;
+
+ if (WARN_ON(!cyg_parent_clk->clock_gate_ctrl_reg))
+ return -EIO;
+
+ pr_debug("Enable GENPLL chan %d\n", clk->chan);
+
+ if (clk->chan == GENPLL_CH4_ASIU_AUDIO_CLK) {
+ cygnus_clkgate_enable_disable(
+ cyg_parent_clk->clock_gate_ctrl_reg,
+ AUD_CLK_GATE_EN, true);
+
+ /* Ensure parent's clock rate is calculated. */
+ parent_rate = clk_get_rate(parent_clk);
+ if (WARN_ON(!parent_rate))
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void cygnus_genpll_chan_disable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+ struct clk *parent_clk = clk_get_parent(hwclk->clk);
+ struct cygnus_clk *cyg_parent_clk =
+ to_cygnus_clk(__clk_get_hw(parent_clk));
+
+ if (WARN_ON(!cyg_parent_clk->clock_gate_ctrl_reg))
+ return;
+
+ pr_debug("GENPLL: disable chan %d\n", clk->chan);
+
+ /* Enable audio clock. */
+ if (clk->chan == GENPLL_CH4_ASIU_AUDIO_CLK)
+ cygnus_clkgate_enable_disable(
+ cyg_parent_clk->clock_gate_ctrl_reg,
+ AUD_CLK_GATE_EN, false);
+}
+
+static const struct clk_ops cygnus_genpll_chan_ops = {
+ .prepare = cygnus_genpll_chan_prepare,
+ .unprepare = cygnus_genpll_chan_unprepare,
+ .enable = cygnus_genpll_chan_enable,
+ .disable = cygnus_genpll_chan_disable,
+ .recalc_rate = cygnus_genpll_chan_recalc_rate,
+};
+
+static __init struct clk *cygnus_clock_init(struct device_node *node,
+ const struct clk_ops *ops)
+{
+ u32 channel = 0;
+ struct clk *clk;
+ struct cygnus_clk *cygnus_clk;
+ const char *clk_name = node->name;
+ const char *parent_name;
+ struct clk_init_data init;
+ int rc;
+
+ pr_debug("Clock name %s\n", node->name);
+
+ cygnus_clk = kzalloc(sizeof(*cygnus_clk), GFP_KERNEL);
+ if (WARN_ON(!cygnus_clk))
+ return NULL;
+
+ cygnus_clk->state = CLK_DISABLED;
+
+ /* Read base address from device tree and map to virtual address. */
+ cygnus_clk->regs_base = of_iomap(node, CYGNUS_CLK_BASE_REG);
+ if (WARN_ON(!cygnus_clk->regs_base))
+ goto err_alloc;
+
+ /* Read optional base addresses for PLL control and clock gating. */
+ cygnus_clk->clock_gate_ctrl_reg = of_iomap(node,
+ CYGNUS_CLK_GATE_CTRL_REG);
+ cygnus_clk->pll_ctrl_reg = of_iomap(node, CYGNUS_PLL_CTRL_REG);
+
+ of_property_read_u32(node, "channel", &channel);
+ cygnus_clk->chan = channel;
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ /*
+ * Internal divider is optional and used for PLL derived clocks with
+ * hardcoded dividers.
+ */
+ cygnus_clk->internal_div = CLK_RATE_NO_DIV;
+ of_property_read_u32(node, "div", &cygnus_clk->internal_div);
+
+ init.name = clk_name;
+ init.ops = ops;
+ init.flags = CLK_GET_RATE_NOCACHE;
+ parent_name = of_clk_get_parent_name(node, 0);
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ cygnus_clk->hw.init = &init;
+
+ clk = clk_register(NULL, &cygnus_clk->hw);
+ if (WARN_ON(IS_ERR(clk)))
+ goto err_unmap;
+
+ rc = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (WARN_ON(IS_ERR_VALUE(rc)))
+ goto err_unregister;
+
+ rc = clk_register_clkdev(clk, clk_name, NULL);
+ if (WARN_ON(IS_ERR_VALUE(rc)))
+ goto err_provider;
+
+ return clk;
+
+err_provider:
+ of_clk_del_provider(node);
+
+err_unregister:
+ clk_unregister(clk);
+
+err_unmap:
+ iounmap(cygnus_clk->regs_base);
+ iounmap(cygnus_clk->clock_gate_ctrl_reg);
+ iounmap(cygnus_clk->pll_ctrl_reg);
+
+err_alloc:
+ kfree(cygnus_clk);
+
+ return NULL;
+}
+
+static void __init cygnus_lcpll_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_lcpll_ops);
+}
+CLK_OF_DECLARE(cygnus_lcpll, "brcm,cygnus-lcpll-clk", cygnus_lcpll_init);
+
+static void __init cygnus_genpll_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_genpll_ops);
+}
+CLK_OF_DECLARE(cygnus_genpll, "brcm,cygnus-genpll-clk", cygnus_genpll_init);
+
+static void __init cygnus_lcpll_ch_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_lcpll_chan_ops);
+}
+CLK_OF_DECLARE(cygnus_lcpll_ch, "brcm,cygnus-lcpll-ch", cygnus_lcpll_ch_init);
+
+static void __init cygnus_genpll_ch_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_genpll_chan_ops);
+}
+CLK_OF_DECLARE(cygnus_genpll_ch, "brcm,cygnus-genpll-ch",
+ cygnus_genpll_ch_init);
+
+/*
+ * Some clocks on Cygnus are derived from the oscillator directly without
+ * going through either the GENPLL or LCPLL. These clocks have specific
+ * registers for their dividers. The clocks included are: keypad, ADC, PWM.
+ */
+
+#define ASIU_CLK_DIV_ENABLE_SHIFT 31
+#define ASIU_CLK_DIV_ENABLE_MASK 0x1
+#define ASIU_CLK_DIV_HIGH_SHIFT 16
+#define ASIU_CLK_DIV_HIGH_MASK 0x3ff
+#define ASIU_CLK_DIV_LOW_SHIFT 0
+#define ASIU_CLK_DIV_LOW_MASK 0x3ff
+
+/*
+ * Calculate clock frequency for clocks derived from oscillator.
+ *
+ * @return The clock rate in Hz
+ */
+static int cygnus_osc_derived_clk_get_rate(struct cygnus_clk *clk,
+ unsigned long parent_rate)
+{
+ int reg_val;
+ int enabled;
+ int clk_div_high;
+ int clk_div_low;
+ unsigned long rate = 0;
+
+ reg_val = readl(clk->regs_base);
+
+ /* Ensure clock is enabled. */
+ enabled = (reg_val >> ASIU_CLK_DIV_ENABLE_SHIFT) &
+ ASIU_CLK_DIV_ENABLE_MASK;
+ if (!enabled)
+ return rate;
+
+ clk_div_high = (reg_val >> ASIU_CLK_DIV_HIGH_SHIFT) &
+ ASIU_CLK_DIV_HIGH_MASK;
+ clk_div_high += 1;
+
+ clk_div_low = (reg_val >> ASIU_CLK_DIV_LOW_SHIFT) &
+ ASIU_CLK_DIV_LOW_MASK;
+ clk_div_low += 1;
+
+ /*
+ * Rate calculated as:
+ * (oscillator rate) / ((clk high + 1) + (clk_low + 1))
+ */
+ rate = parent_rate / (clk_div_high + clk_div_low);
+
+ pr_debug("Osc derived clk: Prate=%lu div_high=%d div_low=%d rate=%lu\n",
+ parent_rate, clk_div_high, clk_div_low, rate);
+
+ return rate;
+}
+
+static unsigned long cygnus_osc_derived_clk_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *bcm_clk = to_cygnus_clk(hwclk);
+
+ return cygnus_osc_derived_clk_get_rate(bcm_clk, parent_rate);
+}
+
+/*
+ * Enables the top clock gating control for clocks that require it.
+ */
+static int cygnus_osc_derived_clk_enable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+ struct clk *parent_clk = clk_get_parent(hwclk->clk);
+ int parent_rate;
+ u32 val;
+
+ if (WARN_ON(!clk->clock_gate_ctrl_reg))
+ return 0;
+
+ pr_debug("OSC derived clk enable chan %d\n", clk->chan);
+
+ /* Enable top clock gating control if necessary. */
+ if (clk->chan == OSC_DERIVED_CH0_KEYPAD_CLK)
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ KEYPAD_CLK_GATE_EN, true);
+ else if (clk->chan == OSC_DERIVED_CH1_ADC_CLK)
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ ADC_CLK_GATE_EN, true);
+
+ /* Set and enable divider if specified. */
+ if (clk->internal_div != CLK_RATE_NO_DIV) {
+ val = (1 << ASIU_CLK_DIV_ENABLE_SHIFT) |
+ ((clk->internal_div & ASIU_CLK_DIV_HIGH_MASK) <<
+ ASIU_CLK_DIV_HIGH_SHIFT) |
+ ((clk->internal_div & ASIU_CLK_DIV_LOW_MASK) <<
+ ASIU_CLK_DIV_LOW_SHIFT);
+ writel(val, clk->regs_base);
+ }
+
+ /* Ensure parent's clock rate is calculated. */
+ parent_rate = clk_get_rate(parent_clk);
+ if (WARN_ON(!parent_rate))
+ return -EIO;
+
+ return 0;
+}
+
+/*
+ * Disables top clock gating control for clocks that were enabled.
+ */
+static void cygnus_osc_derived_clk_disable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+
+ if (WARN_ON(!clk->clock_gate_ctrl_reg))
+ return;
+
+ pr_debug("OSC derived clk disable chan %d\n", clk->chan);
+
+ /* Disable top clock gating control if necessary. */
+ if (clk->chan == OSC_DERIVED_CH0_KEYPAD_CLK)
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ KEYPAD_CLK_GATE_EN, false);
+ else if (clk->chan == OSC_DERIVED_CH1_ADC_CLK)
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ ADC_CLK_GATE_EN, false);
+}
+
+static const struct clk_ops cygnus_osc_derived_clk_ops = {
+ .enable = cygnus_osc_derived_clk_enable,
+ .disable = cygnus_osc_derived_clk_disable,
+ .recalc_rate = cygnus_osc_derived_clk_recalc_rate,
+};
+
+static void __init cygnus_osc_derived_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_osc_derived_clk_ops);
+}
+
+CLK_OF_DECLARE(cygnus_osc_derived, "brcm,cygnus-osc-derived",
+ cygnus_osc_derived_init);
+
+/*
+ * Some clocks are derived from a PLL. The dividers are internal and can't
+ * be read from a register. If the parent clock rate changes then the derived
+ * clock rates scale accordingly.
+ */
+
+ /*
+ * Calculate clock frequency for clocks derived from oscillator.
+ * Rate calculated as: parent rate / internal divider
+ * The internal divider must be specified in DT.
+ *
+ * @return The clock rate in Hz.
+ */
+static unsigned long cygnus_pll_derived_clk_get_rate(struct cygnus_clk *clk,
+ unsigned long parent_rate)
+{
+ unsigned long rate = parent_rate / clk->internal_div;
+
+ pr_debug("PLL derived clk: Prate=%lu rate=%lu\n", parent_rate, rate);
+
+ return rate;
+}
+
+static unsigned long cygnus_pll_derived_clk_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *bcm_clk = to_cygnus_clk(hwclk);
+
+ return cygnus_pll_derived_clk_get_rate(bcm_clk, parent_rate);
+}
+
+static const struct clk_ops cygnus_pll_derived_clk_ops = {
+ .recalc_rate = cygnus_pll_derived_clk_recalc_rate,
+};
+
+static void __init cygnus_pll_derived_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_pll_derived_clk_ops);
+}
+
+CLK_OF_DECLARE(cygnus_pll_derived, "brcm,cygnus-pll-derived",
+ cygnus_pll_derived_init);
+
+/*
+ * MIPI DSI GENPLL
+ */
+
+/*
+ * Get PLL running status and calculate output frequency.
+ */
+static unsigned long cygnus_mipipll_get_rate(struct cygnus_clk *clk,
+ unsigned long parent_rate)
+{
+ u32 reg;
+ u32 rate;
+ u32 pdiv;
+ u32 ndiv_int;
+ u32 ndiv_frac;
+ int pll_locked;
+
+ /* Read lock field from PLL status register. It must be unlocked. */
+ reg = readl(clk->regs_base + GENPLL_STATUS_OFFSET);
+
+ pll_locked = (reg >> GENPLL_STATUS_LOCK_SHIFT) &
+ GENPLL_STATUS_LOCK_MASK;
+ if (pll_locked) {
+ clk->rate = 0;
+ return -EIO;
+ }
+ /*
+ * Calculate PLL frequency:
+ * PLL freq = ((crystal clock / pdiv) * ndiv ) / mdiv
+ */
+
+ /* Get PLL feedback divider values. */
+ reg = readl(clk->regs_base + GENPLL_CONTROL4_OFFSET);
+
+ /* Feedback divider integer and fractional parts. */
+ ndiv_int = (reg >> GENPLL_CONTROL4_NDIV_INT_SHIFT) &
+ GENPLL_CONTROL4_NDIV_INT_MASK;
+ ndiv_frac = (reg >> GENPLL_CONTROL4_NDIV_FRAC_SHIFT) &
+ GENPLL_CONTROL4_NDIV_FRAC_MASK;
+ ndiv_int += ndiv_frac / NDIV_FRAC_DIVISOR;
+
+ /* Get pdiv. */
+ reg = readl(clk->regs_base + GENPLL_CONTROL5_OFFSET);
+ pdiv = (reg >> GENPLL_CONTROL5_PDIV_SHIFT) &
+ GENPLL_CONTROL5_PDIV_MASK;
+
+ /* If pdiv is 0, divide by 0.5 - doubler. */
+ if (pdiv == 0)
+ rate = parent_rate * 2;
+ else
+ rate = parent_rate / pdiv;
+
+ clk->rate = rate * ndiv_int;
+
+ pr_debug("[MIPI PLL] parent rate=%lu, ndiv int=%d, pdiv=%d, rate=%lu\n",
+ parent_rate, ndiv_int, pdiv, clk->rate);
+
+ return clk->rate;
+}
+
+/*
+ * Powers on the necessary PLL's and LDO for MIPI GEN PLL.
+ */
+static int cygnus_mipipll_prepare(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+
+ if (WARN_ON(!clk->pll_ctrl_reg))
+ return -EIO;
+
+ pr_debug("Powering up MIPI PLL and LDO\n");
+
+ /* Power on the PLL. */
+ cygnus_mipi_genpll_power_on_off(clk->pll_ctrl_reg, true);
+
+ clk->state = CLK_PREPARED;
+
+ return 0;
+}
+
+/*
+ * Powers off the PLL's and LDO for MIPI GEN PLL.
+ */
+static void cygnus_mipipll_unprepare(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+
+ if (WARN_ON(!clk->pll_ctrl_reg))
+ return;
+
+ pr_debug("Powering down MIPI PLL and LDO\n");
+
+ /* Power off the PLL. */
+ cygnus_mipi_genpll_power_on_off(clk->pll_ctrl_reg, false);
+
+ clk->state = CLK_DISABLED;
+}
+
+static unsigned long cygnus_mipipll_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *bcm_clk = to_cygnus_clk(hwclk);
+
+ if (bcm_clk->state != CLK_ENABLED)
+ return 0;
+
+ return cygnus_mipipll_get_rate(bcm_clk, parent_rate);
+}
+
+/*
+ * Enables the MIPI DSI clock gate through the top clock gating control.
+ */
+static int cygnus_mipipll_enable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+
+ if (WARN_ON(!clk->clock_gate_ctrl_reg))
+ return -EIO;
+
+ pr_debug("Enable MIPI PLL\n");
+
+ /* Enable MIPI DSI clock. */
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ MIPI_DSI_CLK_GATE_EN, true);
+
+ clk->state = CLK_ENABLED;
+
+ return 0;
+}
+
+/*
+ * Turns off the MIPI PLL clock.
+ */
+static void cygnus_mipipll_disable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+
+ if (WARN_ON(!clk->clock_gate_ctrl_reg))
+ return;
+
+ pr_debug("Disabling MIPI PLL and LDO\n");
+
+ /* Disable MIPI DSI clock through top clock gating control. */
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ MIPI_DSI_CLK_GATE_EN, false);
+
+ clk->state = CLK_DISABLED;
+}
+
+static const struct clk_ops cygnus_mipipll_ops = {
+ .prepare = cygnus_mipipll_prepare,
+ .unprepare = cygnus_mipipll_unprepare,
+ .enable = cygnus_mipipll_enable,
+ .disable = cygnus_mipipll_disable,
+ .recalc_rate = cygnus_mipipll_recalc_rate,
+};
+
+static void __init cygnus_mipipll_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_mipipll_ops);
+}
+CLK_OF_DECLARE(cygnus_mipipll, "brcm,cygnus-mipipll-clk", cygnus_mipipll_init);
+
+/*
+ * MIPI PLL clock channel management.
+ */
+
+/*
+ * Enables a MIPI PLL channel.
+ */
+static void mipi_pll_enable_chan(void __iomem *base, int chan, bool state)
+{
+ u32 val;
+
+ val = readl(base + GENPLL_CONTROL1_OFFSET);
+
+ /* ENABLEB_CH bit set to 0 to enable channel, 1 to disable. */
+ if (state)
+ val &= ~(1 << (chan + MIPI_DSI_GENPLL_ENABLEB_CH_SHIFT));
+ else
+ val |= (1 << (chan + MIPI_DSI_GENPLL_ENABLEB_CH_SHIFT));
+
+ writel(val, base + GENPLL_CONTROL1_OFFSET);
+}
+
+static unsigned long cygnus_mipipll_chan_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+
+ if (WARN_ON(!clk->clock_gate_ctrl_reg))
+ return 0;
+
+ if (clk->state != CLK_ENABLED)
+ return 0;
+
+ return cygnus_genpll_chan_get_rate(clk, parent_rate,
+ MIPI_DSI_GENPLL_ENABLEB_CH_SHIFT);
+}
+
+/*
+ * Enables the PLL channel and the top clock gating control for clocks that
+ * are controlled through it.
+ */
+static int cygnus_mipipll_chan_enable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+ struct clk *parent_clk = clk_get_parent(hwclk->clk);
+ int parent_rate;
+
+ if (WARN_ON(!clk->clock_gate_ctrl_reg))
+ return -EIO;
+
+ pr_debug("Enable MIPI PLL chan %d\n", clk->chan);
+
+ /*
+ * Some MIPI PLL channels have to be enabled through the top clock
+ * gating ctrl. Add support for other channels here.
+ */
+ if (clk->chan == MIPI_PLL_CH1_LCD_CLK) {
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ LCD_CLK_GATE_EN, true);
+ }
+
+ /* Enable the PLL channel. */
+ mipi_pll_enable_chan(clk->regs_base, clk->chan, true);
+
+ clk->state = CLK_ENABLED;
+
+ /* Ensure parent's clock rate is calculated. */
+ parent_rate = clk_get_rate(parent_clk);
+ if (WARN_ON(!parent_rate))
+ return -EIO;
+
+ return 0;
+}
+
+/*
+ * Disables the PLL channel. Some channels also have to be shut down through
+ * the top clock gating control.
+ */
+static void cygnus_mipipll_chan_disable(struct clk_hw *hwclk)
+{
+ struct cygnus_clk *clk = to_cygnus_clk(hwclk);
+
+ if (WARN_ON(!clk->clock_gate_ctrl_reg))
+ return;
+
+ pr_debug("Disable MIPI PLL chan %d\n", clk->chan);
+
+ /* Disable LCD clock through top clock gating control. */
+ if (clk->chan == MIPI_PLL_CH1_LCD_CLK) {
+ cygnus_clkgate_enable_disable(clk->clock_gate_ctrl_reg,
+ LCD_CLK_GATE_EN, false);
+ }
+
+ /* Disable the PLL channel. */
+ mipi_pll_enable_chan(clk->regs_base, clk->chan, false);
+
+ clk->state = CLK_DISABLED;
+}
+
+static const struct clk_ops cygnus_mipipll_chan_ops = {
+ .enable = cygnus_mipipll_chan_enable,
+ .disable = cygnus_mipipll_chan_disable,
+ .recalc_rate = cygnus_mipipll_chan_recalc_rate,
+};
+
+static void __init cygnus_mipipll_ch_init(struct device_node *node)
+{
+ cygnus_clock_init(node, &cygnus_mipipll_chan_ops);
+}
+
+CLK_OF_DECLARE(cygnus_mipipll_ch, "brcm,cygnus-mipipll-ch",
+ cygnus_mipipll_ch_init);
diff --git a/drivers/clk/bcm/clk-iproc.c b/drivers/clk/bcm/clk-iproc.c
new file mode 100644
index 0000000..aca4851
--- /dev/null
+++ b/drivers/clk/bcm/clk-iproc.c
@@ -0,0 +1,451 @@
+/*
+ * Copyright 2014 Broadcom Corporation. All rights reserved.
+ *
+ * Unless you and Broadcom execute a separate written software license
+ * agreement governing use of this software, this software is licensed to you
+ * under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; 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/slab.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/of_address.h>
+
+#define IPROC_CLK_POLICY_FREQ_OFFSET 0x008
+#define IPROC_CLK_POLICY0_MSK_OFFSET 0x010
+#define IPROC_CLK_APB_SW_DIV_OFFSET 0xA10
+#define IPROC_CLK_PLL_ARMA_OFFSET 0xC00
+#define IPROC_CLK_PLL_ARMB_OFFSET 0xC04
+#define IPROC_CLK_PLL_ARMC_OFFSET 0xC08
+#define IPROC_CLK_PLL_ARMCTL5_OFFSET 0xC20
+#define IPROC_CLK_PLL_ARM_OFFSET_OFFSET 0xC24
+#define IPROC_CLK_ARM_DIV_OFFSET 0xE00
+#define IPROC_CLK_POLICY_DBG_OFFSET 0xEC0
+
+#define IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_OVERRIDE_SHIFT 4
+#define IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK 0xf
+#define IPROC_CLK_POLICY_FREQ_OFFSET_POLICY_FREQ_MASK 0xf
+#define IPROC_CLK_POLICY_FREQ_OFFSET_POLICY_FREQ_SHIFT 8
+#define IPROC_CLK_POLICY_DBG_OFFSET_ACT_FREQ_SHIFT 12
+#define IPROC_CLK_POLICY_DBG_OFFSET_ACT_FREQ_MASK 7
+#define IPROC_CLK_PLL_ARM_OFFSET_PLLARM_OFFSET_SW_CTL_SHIFT 29
+#define CLK_PLL_ARM_OFFSET_PLLARM_NDIV_INT_OFFSET 20
+#define CLK_PLL_ARM_OFFSET_PLLARM_NDIV_INT_MASK 0xff
+#define CLK_PLL_ARM_OFFSET_PLLARM_NDIV_FRAC_OFFSET 0xfffff
+#define CLK_PLL_ARMA_PLLARM_NDIV_INT_SHIFT 8
+#define CLK_PLL_ARMA_PLLARM_NDIV_INT_MASK 0x3ff
+#define CLK_PLL_ARMB_PLLARM_NDIV_FRAC_MASK 0xfffff
+#define CLK_PLL_ARMC_PLLARM_MDIV_MASK 0xff
+#define CLK_PLL_ARMCTL5_PLLARM_H_MDIV_MASK 0xff
+#define CLK_PLL_ARMC_PLLARM_BYPCLK_EN_SHIFT 8
+#define CLK_PLL_ARMA_PLLARM_PDIV_SHIFT 24
+#define CLK_PLL_ARMA_PLLARM_PDIV_MASK 0xf
+#define CLK_PLL_ARMA_PLLARM_LOCK_SHIFT 28
+#define CLK_ARM_DIV_APB0_FREE_DIV_SHIFT 8
+#define CLK_ARM_DIV_APB0_FREE_DIV_MASK 0x7
+#define CLK_ARM_DIV_ARM_SWITCH_DIV_SHIFT 8
+#define CLK_ARM_DIV_ARM_SWITCH_DIV_MASK 0x3
+#define CLK_APB_SW_DIV_APB_CLK_DIV_MASK 0x3
+
+struct brcm_clk {
+ struct clk_hw hw;
+ void __iomem *regs_base;
+ int chan;
+ unsigned long rate;
+};
+
+/* Identifies derived clocks from ARM PLL. */
+enum {
+ ARMPLL_APB0_FREE_CLK = 0,
+ ARMPLL_ARM_SWITCH_CLK = 1,
+ ARMPLL_ARM_APB_CLK = 2,
+ ARMPLL_ARM_PERIPH_CLK = 3
+};
+
+/* Frequency id's from policy0_freq field of POLICY_FREQ register. */
+enum a9pll_policy_freq {
+ PLL_CRYSTAL_CLK = 0,
+ PLL_SYS_CLK = 2,
+ PLL_CH0_SLOW_CLK = 6,
+ PLL_CH1_FAST_CLK = 7
+};
+
+#define to_brcm_clk(p) container_of(p, struct brcm_clk, hw)
+
+static int iproc_cru_arm_freq_id(void __iomem *regs_base)
+{
+ u32 reg_f, reg;
+ unsigned policy = 0;
+ unsigned fid;
+ unsigned active_freq;
+
+ /* Read policy frequency. */
+ reg_f = readl(regs_base + IPROC_CLK_POLICY_FREQ_OFFSET);
+
+ /* Check for PLL policy software override. */
+ reg = readl(regs_base + IPROC_CLK_ARM_DIV_OFFSET);
+ if (reg & (1 << IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_OVERRIDE_SHIFT))
+ policy = reg & IPROC_CLK_ARM_DIV_ARM_PLL_SELECT_MASK;
+
+ /* Get frequency ID based on policy. */
+ fid = (reg_f >>
+ (IPROC_CLK_POLICY_FREQ_OFFSET_POLICY_FREQ_SHIFT * policy)) &
+ IPROC_CLK_POLICY_FREQ_OFFSET_POLICY_FREQ_MASK;
+
+ /* Verify freq id from debug register. */
+ reg = readl(regs_base + IPROC_CLK_POLICY_DBG_OFFSET);
+ /* Read current active frequency id. */
+ active_freq = IPROC_CLK_POLICY_DBG_OFFSET_ACT_FREQ_MASK &
+ (reg >> IPROC_CLK_POLICY_DBG_OFFSET_ACT_FREQ_SHIFT);
+
+ if (fid != active_freq) {
+ pr_debug("IPROC CRU clock frequency id override %d->%d\n",
+ fid, active_freq);
+
+ fid = active_freq;
+ }
+
+ pr_debug("Active frequency ID %d\n", fid);
+
+ return fid;
+}
+
+/*
+ * Get ndiv integer and combine with fractional part to create 64 bit
+ * value.
+ */
+static u64 a9pll_get_ndiv(struct brcm_clk *clk)
+{
+ u32 arm_offset_reg;
+ u32 pllarma_reg;
+ u32 pllarmb_reg;
+ u32 ndiv_int;
+ u32 ndiv_frac;
+ u64 ndiv;
+
+ arm_offset_reg = readl(clk->regs_base +
+ IPROC_CLK_PLL_ARM_OFFSET_OFFSET);
+
+ /*
+ * Check if offset mode is active to determine which register to
+ * get ndiv from.
+ */
+ if (arm_offset_reg &
+ (1 << IPROC_CLK_PLL_ARM_OFFSET_PLLARM_OFFSET_SW_CTL_SHIFT)) {
+ /* Offset mode active. Get integer divide from offset reg. */
+ ndiv_int = (arm_offset_reg >>
+ CLK_PLL_ARM_OFFSET_PLLARM_NDIV_INT_OFFSET) &
+ CLK_PLL_ARM_OFFSET_PLLARM_NDIV_INT_MASK;
+
+ if (ndiv_int == 0)
+ ndiv_int = 256;
+
+ /* Get ndiv fractional divider. */
+ ndiv_frac = arm_offset_reg &
+ CLK_PLL_ARM_OFFSET_PLLARM_NDIV_FRAC_OFFSET;
+ } else {
+ /* Offset mode not active so read PLL ndiv from PLLARMA. */
+ pllarma_reg = readl(clk->regs_base + IPROC_CLK_PLL_ARMA_OFFSET);
+ ndiv_int = (pllarma_reg >> CLK_PLL_ARMA_PLLARM_NDIV_INT_SHIFT) &
+ CLK_PLL_ARMA_PLLARM_NDIV_INT_MASK;
+
+ if (ndiv_int == 0)
+ ndiv_int = 1024;
+
+ /* Get ndiv fractional divider. */
+ pllarmb_reg = readl(clk->regs_base + IPROC_CLK_PLL_ARMB_OFFSET);
+ ndiv_frac = pllarmb_reg & CLK_PLL_ARMB_PLLARM_NDIV_FRAC_MASK;
+ }
+
+ ndiv = ((u64) ndiv_int << 20) | ndiv_frac;
+
+ return ndiv;
+}
+
+/*
+ * Determine mdiv (post divider) based on the frequency id being used.
+ * There are 4 clocks that can be used to derive the output clock rate:
+ * - 25 MHz crystal
+ * - sys_clk
+ * - channel 0 (slow clock)
+ * - channel 1 (fast clock)
+ *
+ * If the slow clock is being used then mdiv is read from PLLARMC. If
+ * the fast clock is being used then the channel 1 mdiv is used.
+ * Otherwise there is no post divider.
+ *
+ * @return The mdiv value. -EIO if an error occurred.
+ */
+static int a9pll_get_mdiv(struct brcm_clk *clk)
+{
+ u32 mdiv;
+ u32 pllarmc_reg;
+ u32 armctl5_reg;
+ u32 freq_id;
+
+ /* Get the policy frequency. */
+ freq_id = iproc_cru_arm_freq_id(clk->regs_base);
+
+ switch (freq_id) {
+ /* There is no divider for these frequency id's. */
+ case PLL_CRYSTAL_CLK:
+ case PLL_SYS_CLK:
+ mdiv = 1;
+ break;
+
+ case PLL_CH0_SLOW_CLK: {
+ /* Read mdiv (post-divider) from PLLARMC bits 0:7 */
+ pllarmc_reg = readl(clk->regs_base + IPROC_CLK_PLL_ARMC_OFFSET);
+ mdiv = pllarmc_reg & CLK_PLL_ARMC_PLLARM_MDIV_MASK;
+ if (mdiv == 0)
+ mdiv = 256;
+ break;
+ }
+
+ case PLL_CH1_FAST_CLK: {
+ /* Post divider for channel 1 is in CTL5 (pllarm_h_mdiv). */
+ armctl5_reg = readl(clk->regs_base +
+ IPROC_CLK_PLL_ARMCTL5_OFFSET);
+ mdiv = armctl5_reg & CLK_PLL_ARMCTL5_PLLARM_H_MDIV_MASK;
+ if (mdiv == 0)
+ mdiv = 256;
+ break;
+ }
+
+ default:
+ return -EIO;
+ }
+
+ return mdiv;
+}
+
+/*
+ * Calculate the output frequency of the ARM PLL. The main output clock
+ * is 'arm_clk'.
+ *
+ * The frequency is calculated based on the ARM PLL divider values:
+ * pdiv = ARM PLL input pre-divider
+ * ndiv = ARM PLL feedback divider
+ * mdiv = ARM PLL post divider
+ *
+ * The frequency is calculated by:
+ * ((ndiv * parent clock rate) / pdiv) / mdiv
+ */
+static int a9pll_status(struct brcm_clk *clk, unsigned long parent_rate)
+{
+ u32 pllarma_reg;
+ u32 pllarmc_reg;
+ u32 pdiv;
+ u32 mdiv;
+ u64 ndiv;
+ u32 arm_clk_freq;
+
+ pr_debug("a9pll_status: clk 0x%x\n", (unsigned int)clk);
+
+ BUG_ON(!clk->regs_base);
+
+ pllarma_reg = readl(clk->regs_base + IPROC_CLK_PLL_ARMA_OFFSET);
+ pllarmc_reg = readl(clk->regs_base + IPROC_CLK_PLL_ARMC_OFFSET);
+
+ /* Check if PLL is in bypass mode - input frequency to output */
+ if (pllarmc_reg & (1 << CLK_PLL_ARMC_PLLARM_BYPCLK_EN_SHIFT)) {
+ clk->rate = parent_rate;
+ return 0;
+ }
+
+ /* Check if PLL is locked. It must be unlocked. */
+ if ((pllarma_reg &
+ (1 << CLK_PLL_ARMA_PLLARM_LOCK_SHIFT)) == 0) {
+ clk->rate = 0;
+ return -EIO;
+ }
+
+ /* Read pdiv from PLLARMA. */
+ pdiv = (pllarma_reg >> CLK_PLL_ARMA_PLLARM_PDIV_SHIFT) &
+ CLK_PLL_ARMA_PLLARM_PDIV_MASK;
+ if (pdiv == 0)
+ pdiv = 16;
+
+ /* Determine ndiv. */
+ ndiv = a9pll_get_ndiv(clk);
+
+ /* Determine mdiv (post divider). */
+ mdiv = a9pll_get_mdiv(clk);
+ if (mdiv == -EIO) {
+ clk->rate = 0;
+ return -EIO;
+ }
+
+ /* Calculate clock frequency. */
+ arm_clk_freq = (ndiv * parent_rate) >> 20;
+ arm_clk_freq = (arm_clk_freq / pdiv) / mdiv;
+
+ clk->rate = arm_clk_freq;
+
+ pr_debug("ARM PLL (arm_clk) rate %lu. parent rate = %lu, ",
+ clk->rate, parent_rate);
+ pr_debug("ndiv_int = %d, pdiv = %d, mdiv = %d\n",
+ (u32)ndiv >> 20, pdiv, mdiv);
+
+ return clk->rate;
+}
+
+static unsigned long clk_a9pll_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct brcm_clk *bcm_clk = to_brcm_clk(hwclk);
+
+ return a9pll_status(bcm_clk, parent_rate);
+}
+
+static const struct clk_ops a9pll_ops = {
+ .recalc_rate = clk_a9pll_recalc_rate,
+};
+
+/*
+ * Get status of any of the ARMPLL output channels
+ */
+static int a9pll_chan_status(struct brcm_clk *clk, unsigned long parent_rate)
+{
+ u32 reg;
+ unsigned div;
+
+ BUG_ON(!clk->regs_base);
+
+ reg = readl(clk->regs_base + IPROC_CLK_ARM_DIV_OFFSET);
+ pr_debug("Clock Div = %#x\n", reg);
+
+ switch (clk->chan) {
+ case ARMPLL_APB0_FREE_CLK:
+ /* apb0_free_div bits 10:8 */
+ div = (reg >> CLK_ARM_DIV_APB0_FREE_DIV_SHIFT) &
+ CLK_ARM_DIV_APB0_FREE_DIV_MASK;
+ div++;
+ break;
+
+ case ARMPLL_ARM_SWITCH_CLK:
+ /* arm_switch_div bits 6:5 */
+ div = (reg >> CLK_ARM_DIV_ARM_SWITCH_DIV_SHIFT) &
+ CLK_ARM_DIV_ARM_SWITCH_DIV_MASK;
+ div++;
+ break;
+
+ case ARMPLL_ARM_APB_CLK:
+ /* IPROC_CLK_APB_SW_DIV_REG apb_clk_div bits 1:0 */
+ reg = readl(clk->regs_base + IPROC_CLK_APB_SW_DIV_OFFSET);
+ div = reg & CLK_APB_SW_DIV_APB_CLK_DIV_MASK;
+ div++;
+ break;
+
+ case ARMPLL_ARM_PERIPH_CLK: /* periph_clk */
+ div = 2;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ clk->rate = parent_rate / div;
+ pr_debug("Clock rate A9PLL chan 0x%x: %lu, div: %d\n",
+ clk->chan, clk->rate, div);
+
+ return clk->rate;
+}
+
+static unsigned long clk_a9pll_chan_recalc_rate(struct clk_hw *hwclk,
+ unsigned long parent_rate)
+{
+ struct brcm_clk *bcm_clk = to_brcm_clk(hwclk);
+
+ return a9pll_chan_status(bcm_clk, parent_rate);
+}
+
+static const struct clk_ops a9pll_chan_ops = {
+ .recalc_rate = clk_a9pll_chan_recalc_rate,
+};
+
+static __init struct clk *iproc_clock_init(struct device_node *node,
+ const struct clk_ops *ops)
+{
+ u32 channel = 0;
+ struct clk *clk;
+ struct brcm_clk *brcm_clk;
+ const char *clk_name = node->name;
+ const char *parent_name;
+ struct clk_init_data init;
+ int rc;
+
+ pr_debug("Clock name %s\n", node->name);
+
+ rc = of_property_read_u32(node, "channel", &channel);
+ brcm_clk = kzalloc(sizeof(*brcm_clk), GFP_KERNEL);
+ if (WARN_ON(!brcm_clk))
+ return NULL;
+
+ /* Read base address from device tree and map to virtual address. */
+ brcm_clk->regs_base = of_iomap(node, 0);
+ if (WARN_ON(!brcm_clk->regs_base))
+ goto err_alloc;
+
+ brcm_clk->chan = channel;
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ init.name = clk_name;
+ init.ops = ops;
+ init.flags = 0;
+ parent_name = of_clk_get_parent_name(node, 0);
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ brcm_clk->hw.init = &init;
+
+ clk = clk_register(NULL, &brcm_clk->hw);
+ if (WARN_ON(IS_ERR(clk)))
+ goto err_unmap;
+
+ rc = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (WARN_ON(IS_ERR_VALUE(rc)))
+ goto err_unregister;
+
+ rc = clk_register_clkdev(clk, clk_name, NULL);
+ if (WARN_ON(IS_ERR_VALUE(rc)))
+ goto err_provider;
+
+ return clk;
+
+err_provider:
+ of_clk_del_provider(node);
+
+err_unregister:
+ clk_unregister(clk);
+
+err_unmap:
+ iounmap(brcm_clk->regs_base);
+
+err_alloc:
+ kfree(brcm_clk);
+
+ return NULL;
+}
+
+static void __init iproc_armpll_init(struct device_node *node)
+{
+ iproc_clock_init(node, &a9pll_ops);
+}
+CLK_OF_DECLARE(iproc_armpllx, "brcm,iproc-arm-a9pll", iproc_armpll_init);
+
+static void __init iproc_arm_ch_init(struct device_node *node)
+{
+ iproc_clock_init(node, &a9pll_chan_ops);
+}
+CLK_OF_DECLARE(iproc_arm_ch, "brcm,iproc-arm-ch", iproc_arm_ch_init);
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists