aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clk/Makefile3
-rw-r--r--drivers/clk/ingenic/Makefile3
-rw-r--r--drivers/clk/ingenic/cgu.c711
-rw-r--r--drivers/clk/ingenic/cgu.h223
-rw-r--r--drivers/clk/ingenic/jz4740-cgu.c303
-rw-r--r--drivers/clk/ingenic/jz4780-cgu.c733
-rw-r--r--drivers/cpufreq/ls1x-cpufreq.c4
-rw-r--r--drivers/firmware/Kconfig1
-rw-r--r--drivers/firmware/Makefile1
-rw-r--r--drivers/firmware/broadcom/Kconfig11
-rw-r--r--drivers/firmware/broadcom/Makefile1
-rw-r--r--drivers/firmware/broadcom/bcm47xx_nvram.c248
-rw-r--r--drivers/irqchip/Kconfig12
-rw-r--r--drivers/irqchip/Makefile2
-rw-r--r--drivers/irqchip/irq-ingenic.c177
-rw-r--r--drivers/irqchip/irq-mips-cpu.c172
-rw-r--r--drivers/phy/Kconfig7
-rw-r--r--drivers/phy/Makefile1
-rw-r--r--drivers/phy/phy-pistachio-usb.c206
-rw-r--r--drivers/rtc/Kconfig2
-rw-r--r--drivers/rtc/rtc-ls1x.c2
-rw-r--r--drivers/tty/serial/8250/8250_ingenic.c266
-rw-r--r--drivers/tty/serial/8250/Kconfig9
-rw-r--r--drivers/tty/serial/8250/Makefile3
-rw-r--r--drivers/tty/sysrq.c1
-rw-r--r--drivers/usb/host/Kconfig4
26 files changed, 3098 insertions, 8 deletions
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 5b6af6a9319f..8732e4c5bf3c 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -24,7 +24,7 @@ obj-$(CONFIG_COMMON_CLK_CDCE706) += clk-cdce706.o
obj-$(CONFIG_ARCH_CLPS711X) += clk-clps711x.o
obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o
obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o
-obj-$(CONFIG_MACH_LOONGSON1) += clk-ls1x.o
+obj-$(CONFIG_MACH_LOONGSON32) += clk-ls1x.o
obj-$(CONFIG_COMMON_CLK_MAX_GEN) += clk-max-gen.o
obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
obj-$(CONFIG_COMMON_CLK_MAX77802) += clk-max77802.o
@@ -51,6 +51,7 @@ obj-$(CONFIG_ARCH_HI3xxx) += hisilicon/
obj-$(CONFIG_ARCH_HIP04) += hisilicon/
obj-$(CONFIG_ARCH_HIX5HD2) += hisilicon/
obj-$(CONFIG_ARCH_MXC) += imx/
+obj-$(CONFIG_MACH_INGENIC) += ingenic/
obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/
ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_ARCH_MMP) += mmp/
diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile
new file mode 100644
index 000000000000..cd47b0664c2b
--- /dev/null
+++ b/drivers/clk/ingenic/Makefile
@@ -0,0 +1,3 @@
+obj-y += cgu.o
+obj-$(CONFIG_MACH_JZ4740) += jz4740-cgu.o
+obj-$(CONFIG_MACH_JZ4780) += jz4780-cgu.o
diff --git a/drivers/clk/ingenic/cgu.c b/drivers/clk/ingenic/cgu.c
new file mode 100644
index 000000000000..b936cdd1a13c
--- /dev/null
+++ b/drivers/clk/ingenic/cgu.c
@@ -0,0 +1,711 @@
+/*
+ * Ingenic SoC CGU driver
+ *
+ * Copyright (c) 2013-2015 Imagination Technologies
+ * Author: Paul Burton <paul.burton@imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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/bitops.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/math64.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include "cgu.h"
+
+#define MHZ (1000 * 1000)
+
+/**
+ * ingenic_cgu_gate_get() - get the value of clock gate register bit
+ * @cgu: reference to the CGU whose registers should be read
+ * @info: info struct describing the gate bit
+ *
+ * Retrieves the state of the clock gate bit described by info. The
+ * caller must hold cgu->lock.
+ *
+ * Return: true if the gate bit is set, else false.
+ */
+static inline bool
+ingenic_cgu_gate_get(struct ingenic_cgu *cgu,
+ const struct ingenic_cgu_gate_info *info)
+{
+ return readl(cgu->base + info->reg) & BIT(info->bit);
+}
+
+/**
+ * ingenic_cgu_gate_set() - set the value of clock gate register bit
+ * @cgu: reference to the CGU whose registers should be modified
+ * @info: info struct describing the gate bit
+ * @val: non-zero to gate a clock, otherwise zero
+ *
+ * Sets the given gate bit in order to gate or ungate a clock.
+ *
+ * The caller must hold cgu->lock.
+ */
+static inline void
+ingenic_cgu_gate_set(struct ingenic_cgu *cgu,
+ const struct ingenic_cgu_gate_info *info, bool val)
+{
+ u32 clkgr = readl(cgu->base + info->reg);
+
+ if (val)
+ clkgr |= BIT(info->bit);
+ else
+ clkgr &= ~BIT(info->bit);
+
+ writel(clkgr, cgu->base + info->reg);
+}
+
+/*
+ * PLL operations
+ */
+
+static unsigned long
+ingenic_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ const struct ingenic_cgu_pll_info *pll_info;
+ unsigned m, n, od_enc, od;
+ bool bypass, enable;
+ unsigned long flags;
+ u32 ctl;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+ BUG_ON(clk_info->type != CGU_CLK_PLL);
+ pll_info = &clk_info->pll;
+
+ spin_lock_irqsave(&cgu->lock, flags);
+ ctl = readl(cgu->base + pll_info->reg);
+ spin_unlock_irqrestore(&cgu->lock, flags);
+
+ m = (ctl >> pll_info->m_shift) & GENMASK(pll_info->m_bits - 1, 0);
+ m += pll_info->m_offset;
+ n = (ctl >> pll_info->n_shift) & GENMASK(pll_info->n_bits - 1, 0);
+ n += pll_info->n_offset;
+ od_enc = ctl >> pll_info->od_shift;
+ od_enc &= GENMASK(pll_info->od_bits - 1, 0);
+ bypass = !!(ctl & BIT(pll_info->bypass_bit));
+ enable = !!(ctl & BIT(pll_info->enable_bit));
+
+ if (bypass)
+ return parent_rate;
+
+ if (!enable)
+ return 0;
+
+ for (od = 0; od < pll_info->od_max; od++) {
+ if (pll_info->od_encoding[od] == od_enc)
+ break;
+ }
+ BUG_ON(od == pll_info->od_max);
+ od++;
+
+ return div_u64((u64)parent_rate * m, n * od);
+}
+
+static unsigned long
+ingenic_pll_calc(const struct ingenic_cgu_clk_info *clk_info,
+ unsigned long rate, unsigned long parent_rate,
+ unsigned *pm, unsigned *pn, unsigned *pod)
+{
+ const struct ingenic_cgu_pll_info *pll_info;
+ unsigned m, n, od;
+
+ pll_info = &clk_info->pll;
+ od = 1;
+
+ /*
+ * The frequency after the input divider must be between 10 and 50 MHz.
+ * The highest divider yields the best resolution.
+ */
+ n = parent_rate / (10 * MHZ);
+ n = min_t(unsigned, n, 1 << clk_info->pll.n_bits);
+ n = max_t(unsigned, n, pll_info->n_offset);
+
+ m = (rate / MHZ) * od * n / (parent_rate / MHZ);
+ m = min_t(unsigned, m, 1 << clk_info->pll.m_bits);
+ m = max_t(unsigned, m, pll_info->m_offset);
+
+ if (pm)
+ *pm = m;
+ if (pn)
+ *pn = n;
+ if (pod)
+ *pod = od;
+
+ return div_u64((u64)parent_rate * m, n * od);
+}
+
+static long
+ingenic_pll_round_rate(struct clk_hw *hw, unsigned long req_rate,
+ unsigned long *prate)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+ BUG_ON(clk_info->type != CGU_CLK_PLL);
+
+ return ingenic_pll_calc(clk_info, req_rate, *prate, NULL, NULL, NULL);
+}
+
+static int
+ingenic_pll_set_rate(struct clk_hw *hw, unsigned long req_rate,
+ unsigned long parent_rate)
+{
+ const unsigned timeout = 100;
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ const struct ingenic_cgu_pll_info *pll_info;
+ unsigned long rate, flags;
+ unsigned m, n, od, i;
+ u32 ctl;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+ BUG_ON(clk_info->type != CGU_CLK_PLL);
+ pll_info = &clk_info->pll;
+
+ rate = ingenic_pll_calc(clk_info, req_rate, parent_rate,
+ &m, &n, &od);
+ if (rate != req_rate)
+ pr_info("ingenic-cgu: request '%s' rate %luHz, actual %luHz\n",
+ clk_info->name, req_rate, rate);
+
+ spin_lock_irqsave(&cgu->lock, flags);
+ ctl = readl(cgu->base + pll_info->reg);
+
+ ctl &= ~(GENMASK(pll_info->m_bits - 1, 0) << pll_info->m_shift);
+ ctl |= (m - pll_info->m_offset) << pll_info->m_shift;
+
+ ctl &= ~(GENMASK(pll_info->n_bits - 1, 0) << pll_info->n_shift);
+ ctl |= (n - pll_info->n_offset) << pll_info->n_shift;
+
+ ctl &= ~(GENMASK(pll_info->od_bits - 1, 0) << pll_info->od_shift);
+ ctl |= pll_info->od_encoding[od - 1] << pll_info->od_shift;
+
+ ctl &= ~BIT(pll_info->bypass_bit);
+ ctl |= BIT(pll_info->enable_bit);
+
+ writel(ctl, cgu->base + pll_info->reg);
+
+ /* wait for the PLL to stabilise */
+ for (i = 0; i < timeout; i++) {
+ ctl = readl(cgu->base + pll_info->reg);
+ if (ctl & BIT(pll_info->stable_bit))
+ break;
+ mdelay(1);
+ }
+
+ spin_unlock_irqrestore(&cgu->lock, flags);
+
+ if (i == timeout)
+ return -EBUSY;
+
+ return 0;
+}
+
+static const struct clk_ops ingenic_pll_ops = {
+ .recalc_rate = ingenic_pll_recalc_rate,
+ .round_rate = ingenic_pll_round_rate,
+ .set_rate = ingenic_pll_set_rate,
+};
+
+/*
+ * Operations for all non-PLL clocks
+ */
+
+static u8 ingenic_clk_get_parent(struct clk_hw *hw)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ u32 reg;
+ u8 i, hw_idx, idx = 0;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_MUX) {
+ reg = readl(cgu->base + clk_info->mux.reg);
+ hw_idx = (reg >> clk_info->mux.shift) &
+ GENMASK(clk_info->mux.bits - 1, 0);
+
+ /*
+ * Convert the hardware index to the parent index by skipping
+ * over any -1's in the parents array.
+ */
+ for (i = 0; i < hw_idx; i++) {
+ if (clk_info->parents[i] != -1)
+ idx++;
+ }
+ }
+
+ return idx;
+}
+
+static int ingenic_clk_set_parent(struct clk_hw *hw, u8 idx)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ unsigned long flags;
+ u8 curr_idx, hw_idx, num_poss;
+ u32 reg, mask;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_MUX) {
+ /*
+ * Convert the parent index to the hardware index by adding
+ * 1 for any -1 in the parents array preceding the given
+ * index. That is, we want the index of idx'th entry in
+ * clk_info->parents which does not equal -1.
+ */
+ hw_idx = curr_idx = 0;
+ num_poss = 1 << clk_info->mux.bits;
+ for (; hw_idx < num_poss; hw_idx++) {
+ if (clk_info->parents[hw_idx] == -1)
+ continue;
+ if (curr_idx == idx)
+ break;
+ curr_idx++;
+ }
+
+ /* idx should always be a valid parent */
+ BUG_ON(curr_idx != idx);
+
+ mask = GENMASK(clk_info->mux.bits - 1, 0);
+ mask <<= clk_info->mux.shift;
+
+ spin_lock_irqsave(&cgu->lock, flags);
+
+ /* write the register */
+ reg = readl(cgu->base + clk_info->mux.reg);
+ reg &= ~mask;
+ reg |= hw_idx << clk_info->mux.shift;
+ writel(reg, cgu->base + clk_info->mux.reg);
+
+ spin_unlock_irqrestore(&cgu->lock, flags);
+ return 0;
+ }
+
+ return idx ? -EINVAL : 0;
+}
+
+static unsigned long
+ingenic_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ unsigned long rate = parent_rate;
+ u32 div_reg, div;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_DIV) {
+ div_reg = readl(cgu->base + clk_info->div.reg);
+ div = (div_reg >> clk_info->div.shift) &
+ GENMASK(clk_info->div.bits - 1, 0);
+ div += 1;
+
+ rate /= div;
+ }
+
+ return rate;
+}
+
+static unsigned
+ingenic_clk_calc_div(const struct ingenic_cgu_clk_info *clk_info,
+ unsigned long parent_rate, unsigned long req_rate)
+{
+ unsigned div;
+
+ /* calculate the divide */
+ div = DIV_ROUND_UP(parent_rate, req_rate);
+
+ /* and impose hardware constraints */
+ div = min_t(unsigned, div, 1 << clk_info->div.bits);
+ div = max_t(unsigned, div, 1);
+
+ return div;
+}
+
+static long
+ingenic_clk_round_rate(struct clk_hw *hw, unsigned long req_rate,
+ unsigned long *parent_rate)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ long rate = *parent_rate;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_DIV)
+ rate /= ingenic_clk_calc_div(clk_info, *parent_rate, req_rate);
+ else if (clk_info->type & CGU_CLK_FIXDIV)
+ rate /= clk_info->fixdiv.div;
+
+ return rate;
+}
+
+static int
+ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
+ unsigned long parent_rate)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ const unsigned timeout = 100;
+ unsigned long rate, flags;
+ unsigned div, i;
+ u32 reg, mask;
+ int ret = 0;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_DIV) {
+ div = ingenic_clk_calc_div(clk_info, parent_rate, req_rate);
+ rate = parent_rate / div;
+
+ if (rate != req_rate)
+ return -EINVAL;
+
+ spin_lock_irqsave(&cgu->lock, flags);
+ reg = readl(cgu->base + clk_info->div.reg);
+
+ /* update the divide */
+ mask = GENMASK(clk_info->div.bits - 1, 0);
+ reg &= ~(mask << clk_info->div.shift);
+ reg |= (div - 1) << clk_info->div.shift;
+
+ /* clear the stop bit */
+ if (clk_info->div.stop_bit != -1)
+ reg &= ~BIT(clk_info->div.stop_bit);
+
+ /* set the change enable bit */
+ if (clk_info->div.ce_bit != -1)
+ reg |= BIT(clk_info->div.ce_bit);
+
+ /* update the hardware */
+ writel(reg, cgu->base + clk_info->div.reg);
+
+ /* wait for the change to take effect */
+ if (clk_info->div.busy_bit != -1) {
+ for (i = 0; i < timeout; i++) {
+ reg = readl(cgu->base + clk_info->div.reg);
+ if (!(reg & BIT(clk_info->div.busy_bit)))
+ break;
+ mdelay(1);
+ }
+ if (i == timeout)
+ ret = -EBUSY;
+ }
+
+ spin_unlock_irqrestore(&cgu->lock, flags);
+ return ret;
+ }
+
+ return -EINVAL;
+}
+
+static int ingenic_clk_enable(struct clk_hw *hw)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ unsigned long flags;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_GATE) {
+ /* ungate the clock */
+ spin_lock_irqsave(&cgu->lock, flags);
+ ingenic_cgu_gate_set(cgu, &clk_info->gate, false);
+ spin_unlock_irqrestore(&cgu->lock, flags);
+ }
+
+ return 0;
+}
+
+static void ingenic_clk_disable(struct clk_hw *hw)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ unsigned long flags;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_GATE) {
+ /* gate the clock */
+ spin_lock_irqsave(&cgu->lock, flags);
+ ingenic_cgu_gate_set(cgu, &clk_info->gate, true);
+ spin_unlock_irqrestore(&cgu->lock, flags);
+ }
+}
+
+static int ingenic_clk_is_enabled(struct clk_hw *hw)
+{
+ struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
+ struct ingenic_cgu *cgu = ingenic_clk->cgu;
+ const struct ingenic_cgu_clk_info *clk_info;
+ unsigned long flags;
+ int enabled = 1;
+
+ clk_info = &cgu->clock_info[ingenic_clk->idx];
+
+ if (clk_info->type & CGU_CLK_GATE) {
+ spin_lock_irqsave(&cgu->lock, flags);
+ enabled = !ingenic_cgu_gate_get(cgu, &clk_info->gate);
+ spin_unlock_irqrestore(&cgu->lock, flags);
+ }
+
+ return enabled;
+}
+
+static const struct clk_ops ingenic_clk_ops = {
+ .get_parent = ingenic_clk_get_parent,
+ .set_parent = ingenic_clk_set_parent,
+
+ .recalc_rate = ingenic_clk_recalc_rate,
+ .round_rate = ingenic_clk_round_rate,
+ .set_rate = ingenic_clk_set_rate,
+
+ .enable = ingenic_clk_enable,
+ .disable = ingenic_clk_disable,
+ .is_enabled = ingenic_clk_is_enabled,
+};
+
+/*
+ * Setup functions.
+ */
+
+static int ingenic_register_clock(struct ingenic_cgu *cgu, unsigned idx)
+{
+ const struct ingenic_cgu_clk_info *clk_info = &cgu->clock_info[idx];
+ struct clk_init_data clk_init;
+ struct ingenic_clk *ingenic_clk = NULL;
+ struct clk *clk, *parent;
+ const char *parent_names[4];
+ unsigned caps, i, num_possible;
+ int err = -EINVAL;
+
+ BUILD_BUG_ON(ARRAY_SIZE(clk_info->parents) > ARRAY_SIZE(parent_names));
+
+ if (clk_info->type == CGU_CLK_EXT) {
+ clk = of_clk_get_by_name(cgu->np, clk_info->name);
+ if (IS_ERR(clk)) {
+ pr_err("%s: no external clock '%s' provided\n",
+ __func__, clk_info->name);
+ err = -ENODEV;
+ goto out;
+ }
+ err = clk_register_clkdev(clk, clk_info->name, NULL);
+ if (err) {
+ clk_put(clk);
+ goto out;
+ }
+ cgu->clocks.clks[idx] = clk;
+ return 0;
+ }
+
+ if (!clk_info->type) {
+ pr_err("%s: no clock type specified for '%s'\n", __func__,
+ clk_info->name);
+ goto out;
+ }
+
+ ingenic_clk = kzalloc(sizeof(*ingenic_clk), GFP_KERNEL);
+ if (!ingenic_clk) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ ingenic_clk->hw.init = &clk_init;
+ ingenic_clk->cgu = cgu;
+ ingenic_clk->idx = idx;
+
+ clk_init.name = clk_info->name;
+ clk_init.flags = 0;
+ clk_init.parent_names = parent_names;
+
+ caps = clk_info->type;
+
+ if (caps & (CGU_CLK_MUX | CGU_CLK_CUSTOM)) {
+ clk_init.num_parents = 0;
+
+ if (caps & CGU_CLK_MUX)
+ num_possible = 1 << clk_info->mux.bits;
+ else
+ num_possible = ARRAY_SIZE(clk_info->parents);
+
+ for (i = 0; i < num_possible; i++) {
+ if (clk_info->parents[i] == -1)
+ continue;
+
+ parent = cgu->clocks.clks[clk_info->parents[i]];
+ parent_names[clk_init.num_parents] =
+ __clk_get_name(parent);
+ clk_init.num_parents++;
+ }
+
+ BUG_ON(!clk_init.num_parents);
+ BUG_ON(clk_init.num_parents > ARRAY_SIZE(parent_names));
+ } else {
+ BUG_ON(clk_info->parents[0] == -1);
+ clk_init.num_parents = 1;
+ parent = cgu->clocks.clks[clk_info->parents[0]];
+ parent_names[0] = __clk_get_name(parent);
+ }
+
+ if (caps & CGU_CLK_CUSTOM) {
+ clk_init.ops = clk_info->custom.clk_ops;
+
+ caps &= ~CGU_CLK_CUSTOM;
+
+ if (caps) {
+ pr_err("%s: custom clock may not be combined with type 0x%x\n",
+ __func__, caps);
+ goto out;
+ }
+ } else if (caps & CGU_CLK_PLL) {
+ clk_init.ops = &ingenic_pll_ops;
+
+ caps &= ~CGU_CLK_PLL;
+
+ if (caps) {
+ pr_err("%s: PLL may not be combined with type 0x%x\n",
+ __func__, caps);
+ goto out;
+ }
+ } else {
+ clk_init.ops = &ingenic_clk_ops;
+ }
+
+ /* nothing to do for gates or fixed dividers */
+ caps &= ~(CGU_CLK_GATE | CGU_CLK_FIXDIV);
+
+ if (caps & CGU_CLK_MUX) {
+ if (!(caps & CGU_CLK_MUX_GLITCHFREE))
+ clk_init.flags |= CLK_SET_PARENT_GATE;
+
+ caps &= ~(CGU_CLK_MUX | CGU_CLK_MUX_GLITCHFREE);
+ }
+
+ if (caps & CGU_CLK_DIV) {
+ caps &= ~CGU_CLK_DIV;
+ } else {
+ /* pass rate changes to the parent clock */
+ clk_init.flags |= CLK_SET_RATE_PARENT;
+ }
+
+ if (caps) {
+ pr_err("%s: unknown clock type 0x%x\n", __func__, caps);
+ goto out;
+ }
+
+ clk = clk_register(NULL, &ingenic_clk->hw);
+ if (IS_ERR(clk)) {
+ pr_err("%s: failed to register clock '%s'\n", __func__,
+ clk_info->name);
+ err = PTR_ERR(clk);
+ goto out;
+ }
+
+ err = clk_register_clkdev(clk, clk_info->name, NULL);
+ if (err)
+ goto out;
+
+ cgu->clocks.clks[idx] = clk;
+out:
+ if (err)
+ kfree(ingenic_clk);
+ return err;
+}
+
+struct ingenic_cgu *
+ingenic_cgu_new(const struct ingenic_cgu_clk_info *clock_info,
+ unsigned num_clocks, struct device_node *np)
+{
+ struct ingenic_cgu *cgu;
+
+ cgu = kzalloc(sizeof(*cgu), GFP_KERNEL);
+ if (!cgu)
+ goto err_out;
+
+ cgu->base = of_iomap(np, 0);
+ if (!cgu->base) {
+ pr_err("%s: failed to map CGU registers\n", __func__);
+ goto err_out_free;
+ }
+
+ cgu->np = np;
+ cgu->clock_info = clock_info;
+ cgu->clocks.clk_num = num_clocks;
+
+ spin_lock_init(&cgu->lock);
+
+ return cgu;
+
+err_out_free:
+ kfree(cgu);
+err_out:
+ return NULL;
+}
+
+int ingenic_cgu_register_clocks(struct ingenic_cgu *cgu)
+{
+ unsigned i;
+ int err;
+
+ cgu->clocks.clks = kcalloc(cgu->clocks.clk_num, sizeof(struct clk *),
+ GFP_KERNEL);
+ if (!cgu->clocks.clks) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ for (i = 0; i < cgu->clocks.clk_num; i++) {
+ err = ingenic_register_clock(cgu, i);
+ if (err)
+ goto err_out_unregister;
+ }
+
+ err = of_clk_add_provider(cgu->np, of_clk_src_onecell_get,
+ &cgu->clocks);
+ if (err)
+ goto err_out_unregister;
+
+ return 0;
+
+err_out_unregister:
+ for (i = 0; i < cgu->clocks.clk_num; i++) {
+ if (!cgu->clocks.clks[i])
+ continue;
+ if (cgu->clock_info[i].type & CGU_CLK_EXT)
+ clk_put(cgu->clocks.clks[i]);
+ else
+ clk_unregister(cgu->clocks.clks[i]);
+ }
+ kfree(cgu->clocks.clks);
+err_out:
+ return err;
+}
diff --git a/drivers/clk/ingenic/cgu.h b/drivers/clk/ingenic/cgu.h
new file mode 100644
index 000000000000..99347e2b97e8
--- /dev/null
+++ b/drivers/clk/ingenic/cgu.h
@@ -0,0 +1,223 @@
+/*
+ * Ingenic SoC CGU driver
+ *
+ * Copyright (c) 2013-2015 Imagination Technologies
+ * Author: Paul Burton <paul.burton@imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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_INGENIC_CGU_H__
+#define __DRIVERS_CLK_INGENIC_CGU_H__
+
+#include <linux/bitops.h>
+#include <linux/of.h>
+#include <linux/spinlock.h>
+
+/**
+ * struct ingenic_cgu_pll_info - information about a PLL
+ * @reg: the offset of the PLL's control register within the CGU
+ * @m_shift: the number of bits to shift the multiplier value by (ie. the
+ * index of the lowest bit of the multiplier value in the PLL's
+ * control register)
+ * @m_bits: the size of the multiplier field in bits
+ * @m_offset: the multiplier value which encodes to 0 in the PLL's control
+ * register
+ * @n_shift: the number of bits to shift the divider value by (ie. the
+ * index of the lowest bit of the divider value in the PLL's
+ * control register)
+ * @n_bits: the size of the divider field in bits
+ * @n_offset: the divider value which encodes to 0 in the PLL's control
+ * register
+ * @od_shift: the number of bits to shift the post-VCO divider value by (ie.
+ * the index of the lowest bit of the post-VCO divider value in
+ * the PLL's control register)
+ * @od_bits: the size of the post-VCO divider field in bits
+ * @od_max: the maximum post-VCO divider value
+ * @od_encoding: a pointer to an array mapping post-VCO divider values to
+ * their encoded values in the PLL control register, or -1 for
+ * unsupported values
+ * @bypass_bit: the index of the bypass bit in the PLL control register
+ * @enable_bit: the index of the enable bit in the PLL control register
+ * @stable_bit: the index of the stable bit in the PLL control register
+ */
+struct ingenic_cgu_pll_info {
+ unsigned reg;
+ const s8 *od_encoding;
+ u8 m_shift, m_bits, m_offset;
+ u8 n_shift, n_bits, n_offset;
+ u8 od_shift, od_bits, od_max;
+ u8 bypass_bit;
+ u8 enable_bit;
+ u8 stable_bit;
+};
+
+/**
+ * struct ingenic_cgu_mux_info - information about a clock mux
+ * @reg: offset of the mux control register within the CGU
+ * @shift: number of bits to shift the mux value by (ie. the index of
+ * the lowest bit of the mux value within its control register)
+ * @bits: the size of the mux value in bits
+ */
+struct ingenic_cgu_mux_info {
+ unsigned reg;
+ u8 shift;
+ u8 bits;
+};
+
+/**
+ * struct ingenic_cgu_div_info - information about a divider
+ * @reg: offset of the divider control register within the CGU
+ * @shift: number of bits to shift the divide value by (ie. the index of
+ * the lowest bit of the divide value within its control register)
+ * @bits: the size of the divide value in bits
+ * @ce_bit: the index of the change enable bit within reg, or -1 if there
+ * isn't one
+ * @busy_bit: the index of the busy bit within reg, or -1 if there isn't one
+ * @stop_bit: the index of the stop bit within reg, or -1 if there isn't one
+ */
+struct ingenic_cgu_div_info {
+ unsigned reg;
+ u8 shift;
+ u8 bits;
+ s8 ce_bit;
+ s8 busy_bit;
+ s8 stop_bit;
+};
+
+/**
+ * struct ingenic_cgu_fixdiv_info - information about a fixed divider
+ * @div: the divider applied to the parent clock
+ */
+struct ingenic_cgu_fixdiv_info {
+ unsigned div;
+};
+
+/**
+ * struct ingenic_cgu_gate_info - information about a clock gate
+ * @reg: offset of the gate control register within the CGU
+ * @bit: offset of the bit in the register that controls the gate
+ */
+struct ingenic_cgu_gate_info {
+ unsigned reg;
+ u8 bit;
+};
+
+/**
+ * struct ingenic_cgu_custom_info - information about a custom (SoC) clock
+ * @clk_ops: custom clock operation callbacks
+ */
+struct ingenic_cgu_custom_info {
+ struct clk_ops *clk_ops;
+};
+
+/**
+ * struct ingenic_cgu_clk_info - information about a clock
+ * @name: name of the clock
+ * @type: a bitmask formed from CGU_CLK_* values
+ * @parents: an array of the indices of potential parents of this clock
+ * within the clock_info array of the CGU, or -1 in entries
+ * which correspond to no valid parent
+ * @pll: information valid if type includes CGU_CLK_PLL
+ * @gate: information valid if type includes CGU_CLK_GATE
+ * @mux: information valid if type includes CGU_CLK_MUX
+ * @div: information valid if type includes CGU_CLK_DIV
+ * @fixdiv: information valid if type includes CGU_CLK_FIXDIV
+ * @custom: information valid if type includes CGU_CLK_CUSTOM
+ */
+struct ingenic_cgu_clk_info {
+ const char *name;
+
+ enum {
+ CGU_CLK_NONE = 0,
+ CGU_CLK_EXT = BIT(0),
+ CGU_CLK_PLL = BIT(1),
+ CGU_CLK_GATE = BIT(2),
+ CGU_CLK_MUX = BIT(3),
+ CGU_CLK_MUX_GLITCHFREE = BIT(4),
+ CGU_CLK_DIV = BIT(5),
+ CGU_CLK_FIXDIV = BIT(6),
+ CGU_CLK_CUSTOM = BIT(7),
+ } type;
+
+ int parents[4];
+
+ union {
+ struct ingenic_cgu_pll_info pll;
+
+ struct {
+ struct ingenic_cgu_gate_info gate;
+ struct ingenic_cgu_mux_info mux;
+ struct ingenic_cgu_div_info div;
+ struct ingenic_cgu_fixdiv_info fixdiv;
+ };
+
+ struct ingenic_cgu_custom_info custom;
+ };
+};
+
+/**
+ * struct ingenic_cgu - data about the CGU
+ * @np: the device tree node that caused the CGU to be probed
+ * @base: the ioremap'ed base address of the CGU registers
+ * @clock_info: an array containing information about implemented clocks
+ * @clocks: used to provide clocks to DT, allows lookup of struct clk*
+ * @lock: lock to be held whilst manipulating CGU registers
+ */
+struct ingenic_cgu {
+ struct device_node *np;
+ void __iomem *base;
+
+ const struct ingenic_cgu_clk_info *clock_info;
+ struct clk_onecell_data clocks;
+
+ spinlock_t lock;
+};
+
+/**
+ * struct ingenic_clk - private data for a clock
+ * @hw: see Documentation/clk.txt
+ * @cgu: a pointer to the CGU data
+ * @idx: the index of this clock in cgu->clock_info
+ */
+struct ingenic_clk {
+ struct clk_hw hw;
+ struct ingenic_cgu *cgu;
+ unsigned idx;
+};
+
+#define to_ingenic_clk(_hw) container_of(_hw, struct ingenic_clk, hw)
+
+/**
+ * ingenic_cgu_new() - create a new CGU instance
+ * @clock_info: an array of clock information structures describing the clocks
+ * which are implemented by the CGU
+ * @num_clocks: the number of entries in clock_info
+ * @np: the device tree node which causes this CGU to be probed
+ *
+ * Return: a pointer to the CGU instance if initialisation is successful,
+ * otherwise NULL.
+ */
+struct ingenic_cgu *
+ingenic_cgu_new(const struct ingenic_cgu_clk_info *clock_info,
+ unsigned num_clocks, struct device_node *np);
+
+/**
+ * ingenic_cgu_register_clocks() - Registers the clocks
+ * @cgu: pointer to cgu data
+ *
+ * Register the clocks described by the CGU with the common clock framework.
+ *
+ * Return: 0 on success or -errno if unsuccesful.
+ */
+int ingenic_cgu_register_clocks(struct ingenic_cgu *cgu);
+
+#endif /* __DRIVERS_CLK_INGENIC_CGU_H__ */
diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c
new file mode 100644
index 000000000000..305a26c2a800
--- /dev/null
+++ b/drivers/clk/ingenic/jz4740-cgu.c
@@ -0,0 +1,303 @@
+/*
+ * Ingenic JZ4740 SoC CGU driver
+ *
+ * Copyright (c) 2015 Imagination Technologies
+ * Author: Paul Burton <paul.burton@imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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-provider.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <dt-bindings/clock/jz4740-cgu.h>
+#include <asm/mach-jz4740/clock.h>
+#include "cgu.h"
+
+/* CGU register offsets */
+#define CGU_REG_CPCCR 0x00
+#define CGU_REG_LCR 0x04
+#define CGU_REG_CPPCR 0x10
+#define CGU_REG_CLKGR 0x20
+#define CGU_REG_SCR 0x24
+#define CGU_REG_I2SCDR 0x60
+#define CGU_REG_LPCDR 0x64
+#define CGU_REG_MSCCDR 0x68
+#define CGU_REG_UHCCDR 0x6c
+#define CGU_REG_SSICDR 0x74
+
+/* bits within a PLL control register */
+#define PLLCTL_M_SHIFT 23
+#define PLLCTL_M_MASK (0x1ff << PLLCTL_M_SHIFT)
+#define PLLCTL_N_SHIFT 18
+#define PLLCTL_N_MASK (0x1f << PLLCTL_N_SHIFT)
+#define PLLCTL_OD_SHIFT 16
+#define PLLCTL_OD_MASK (0x3 << PLLCTL_OD_SHIFT)
+#define PLLCTL_STABLE (1 << 10)
+#define PLLCTL_BYPASS (1 << 9)
+#define PLLCTL_ENABLE (1 << 8)
+
+/* bits within the LCR register */
+#define LCR_SLEEP (1 << 0)
+
+/* bits within the CLKGR register */
+#define CLKGR_UDC (1 << 11)
+
+static struct ingenic_cgu *cgu;
+
+static const s8 pll_od_encoding[4] = {
+ 0x0, 0x1, -1, 0x3,
+};
+
+static const struct ingenic_cgu_clk_info jz4740_cgu_clocks[] = {
+
+ /* External clocks */
+
+ [JZ4740_CLK_EXT] = { "ext", CGU_CLK_EXT },
+ [JZ4740_CLK_RTC] = { "rtc", CGU_CLK_EXT },
+
+ [JZ4740_CLK_PLL] = {
+ "pll", CGU_CLK_PLL,
+ .parents = { JZ4740_CLK_EXT, -1, -1, -1 },
+ .pll = {
+ .reg = CGU_REG_CPPCR,
+ .m_shift = 23,
+ .m_bits = 9,
+ .m_offset = 2,
+ .n_shift = 18,
+ .n_bits = 5,
+ .n_offset = 2,
+ .od_shift = 16,
+ .od_bits = 2,
+ .od_max = 4,
+ .od_encoding = pll_od_encoding,
+ .stable_bit = 10,
+ .bypass_bit = 9,
+ .enable_bit = 8,
+ },
+ },
+
+ /* Muxes & dividers */
+
+ [JZ4740_CLK_PLL_HALF] = {
+ "pll half", CGU_CLK_DIV,
+ .parents = { JZ4740_CLK_PLL, -1, -1, -1 },
+ .div = { CGU_REG_CPCCR, 21, 1, -1, -1, -1 },
+ },
+
+ [JZ4740_CLK_CCLK] = {
+ "cclk", CGU_CLK_DIV,
+ .parents = { JZ4740_CLK_PLL, -1, -1, -1 },
+ .div = { CGU_REG_CPCCR, 0, 4, 22, -1, -1 },
+ },
+
+ [JZ4740_CLK_HCLK] = {
+ "hclk", CGU_CLK_DIV,
+ .parents = { JZ4740_CLK_PLL, -1, -1, -1 },
+ .div = { CGU_REG_CPCCR, 4, 4, 22, -1, -1 },
+ },
+
+ [JZ4740_CLK_PCLK] = {
+ "pclk", CGU_CLK_DIV,
+ .parents = { JZ4740_CLK_PLL, -1, -1, -1 },
+ .div = { CGU_REG_CPCCR, 8, 4, 22, -1, -1 },
+ },
+
+ [JZ4740_CLK_MCLK] = {
+ "mclk", CGU_CLK_DIV,
+ .parents = { JZ4740_CLK_PLL, -1, -1, -1 },
+ .div = { CGU_REG_CPCCR, 12, 4, 22, -1, -1 },
+ },
+
+ [JZ4740_CLK_LCD] = {
+ "lcd", CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
+ .div = { CGU_REG_CPCCR, 16, 5, 22, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 10 },
+ },
+
+ [JZ4740_CLK_LCD_PCLK] = {
+ "lcd_pclk", CGU_CLK_DIV,
+ .parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
+ .div = { CGU_REG_LPCDR, 0, 11, -1, -1, -1 },
+ },
+
+ [JZ4740_CLK_I2S] = {
+ "i2s", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL_HALF, -1, -1 },
+ .mux = { CGU_REG_CPCCR, 31, 1 },
+ .div = { CGU_REG_I2SCDR, 0, 8, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 6 },
+ },
+
+ [JZ4740_CLK_SPI] = {
+ "spi", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL, -1, -1 },
+ .mux = { CGU_REG_SSICDR, 31, 1 },
+ .div = { CGU_REG_SSICDR, 0, 4, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 4 },
+ },
+
+ [JZ4740_CLK_MMC] = {
+ "mmc", CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
+ .div = { CGU_REG_MSCCDR, 0, 5, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 7 },
+ },
+
+ [JZ4740_CLK_UHC] = {
+ "uhc", CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_PLL_HALF, -1, -1, -1 },
+ .div = { CGU_REG_UHCCDR, 0, 4, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 14 },
+ },
+
+ [JZ4740_CLK_UDC] = {
+ "udc", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { JZ4740_CLK_EXT, JZ4740_CLK_PLL_HALF, -1, -1 },
+ .mux = { CGU_REG_CPCCR, 29, 1 },
+ .div = { CGU_REG_CPCCR, 23, 6, -1, -1, -1 },
+ .gate = { CGU_REG_SCR, 6 },
+ },
+
+ /* Gate-only clocks */
+
+ [JZ4740_CLK_UART0] = {
+ "uart0", CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_EXT, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 0 },
+ },
+
+ [JZ4740_CLK_UART1] = {
+ "uart1", CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_EXT, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 15 },
+ },
+
+ [JZ4740_CLK_DMA] = {
+ "dma", CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_PCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 12 },
+ },
+
+ [JZ4740_CLK_IPU] = {
+ "ipu", CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_PCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 13 },
+ },
+
+ [JZ4740_CLK_ADC] = {
+ "adc", CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_EXT, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 8 },
+ },
+
+ [JZ4740_CLK_I2C] = {
+ "i2c", CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_EXT, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 3 },
+ },
+
+ [JZ4740_CLK_AIC] = {
+ "aic", CGU_CLK_GATE,
+ .parents = { JZ4740_CLK_EXT, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR, 5 },
+ },
+};
+
+static void __init jz4740_cgu_init(struct device_node *np)
+{
+ int retval;
+
+ cgu = ingenic_cgu_new(jz4740_cgu_clocks,
+ ARRAY_SIZE(jz4740_cgu_clocks), np);
+ if (!cgu) {
+ pr_err("%s: failed to initialise CGU\n", __func__);
+ return;
+ }
+
+ retval = ingenic_cgu_register_clocks(cgu);
+ if (retval)
+ pr_err("%s: failed to register CGU Clocks\n", __func__);
+}
+CLK_OF_DECLARE(jz4740_cgu, "ingenic,jz4740-cgu", jz4740_cgu_init);
+
+void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode)
+{
+ uint32_t lcr = readl(cgu->base + CGU_REG_LCR);
+
+ switch (mode) {
+ case JZ4740_WAIT_MODE_IDLE:
+ lcr &= ~LCR_SLEEP;
+ break;
+
+ case JZ4740_WAIT_MODE_SLEEP:
+ lcr |= LCR_SLEEP;
+ break;
+ }
+
+ writel(lcr, cgu->base + CGU_REG_LCR);
+}
+
+void jz4740_clock_udc_disable_auto_suspend(void)
+{
+ uint32_t clkgr = readl(cgu->base + CGU_REG_CLKGR);
+
+ clkgr &= ~CLKGR_UDC;
+ writel(clkgr, cgu->base + CGU_REG_CLKGR);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_disable_auto_suspend);
+
+void jz4740_clock_udc_enable_auto_suspend(void)
+{
+ uint32_t clkgr = readl(cgu->base + CGU_REG_CLKGR);
+
+ clkgr |= CLKGR_UDC;
+ writel(clkgr, cgu->base + CGU_REG_CLKGR);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_enable_auto_suspend);
+
+#define JZ_CLOCK_GATE_UART0 BIT(0)
+#define JZ_CLOCK_GATE_TCU BIT(1)
+#define JZ_CLOCK_GATE_DMAC BIT(12)
+
+void jz4740_clock_suspend(void)
+{
+ uint32_t clkgr, cppcr;
+
+ clkgr = readl(cgu->base + CGU_REG_CLKGR);
+ clkgr |= JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0;
+ writel(clkgr, cgu->base + CGU_REG_CLKGR);
+
+ cppcr = readl(cgu->base + CGU_REG_CPPCR);
+ cppcr &= ~BIT(jz4740_cgu_clocks[JZ4740_CLK_PLL].pll.enable_bit);
+ writel(cppcr, cgu->base + CGU_REG_CPPCR);
+}
+
+void jz4740_clock_resume(void)
+{
+ uint32_t clkgr, cppcr, stable;
+
+ cppcr = readl(cgu->base + CGU_REG_CPPCR);
+ cppcr |= BIT(jz4740_cgu_clocks[JZ4740_CLK_PLL].pll.enable_bit);
+ writel(cppcr, cgu->base + CGU_REG_CPPCR);
+
+ stable = BIT(jz4740_cgu_clocks[JZ4740_CLK_PLL].pll.stable_bit);
+ do {
+ cppcr = readl(cgu->base + CGU_REG_CPPCR);
+ } while (!(cppcr & stable));
+
+ clkgr = readl(cgu->base + CGU_REG_CLKGR);
+ clkgr &= ~JZ_CLOCK_GATE_TCU;
+ clkgr &= ~JZ_CLOCK_GATE_DMAC;
+ clkgr &= ~JZ_CLOCK_GATE_UART0;
+ writel(clkgr, cgu->base + CGU_REG_CLKGR);
+}
diff --git a/drivers/clk/ingenic/jz4780-cgu.c b/drivers/clk/ingenic/jz4780-cgu.c
new file mode 100644
index 000000000000..431f962300b6
--- /dev/null
+++ b/drivers/clk/ingenic/jz4780-cgu.c
@@ -0,0 +1,733 @@
+/*
+ * Ingenic JZ4780 SoC CGU driver
+ *
+ * Copyright (c) 2013-2015 Imagination Technologies
+ * Author: Paul Burton <paul.burton@imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * 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-provider.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <dt-bindings/clock/jz4780-cgu.h>
+#include "cgu.h"
+
+/* CGU register offsets */
+#define CGU_REG_CLOCKCONTROL 0x00
+#define CGU_REG_PLLCONTROL 0x0c
+#define CGU_REG_APLL 0x10
+#define CGU_REG_MPLL 0x14
+#define CGU_REG_EPLL 0x18
+#define CGU_REG_VPLL 0x1c
+#define CGU_REG_CLKGR0 0x20
+#define CGU_REG_OPCR 0x24
+#define CGU_REG_CLKGR1 0x28
+#define CGU_REG_DDRCDR 0x2c
+#define CGU_REG_VPUCDR 0x30
+#define CGU_REG_USBPCR 0x3c
+#define CGU_REG_USBRDT 0x40
+#define CGU_REG_USBVBFIL 0x44
+#define CGU_REG_USBPCR1 0x48
+#define CGU_REG_LP0CDR 0x54
+#define CGU_REG_I2SCDR 0x60
+#define CGU_REG_LP1CDR 0x64
+#define CGU_REG_MSC0CDR 0x68
+#define CGU_REG_UHCCDR 0x6c
+#define CGU_REG_SSICDR 0x74
+#define CGU_REG_CIMCDR 0x7c
+#define CGU_REG_PCMCDR 0x84
+#define CGU_REG_GPUCDR 0x88
+#define CGU_REG_HDMICDR 0x8c
+#define CGU_REG_MSC1CDR 0xa4
+#define CGU_REG_MSC2CDR 0xa8
+#define CGU_REG_BCHCDR 0xac
+#define CGU_REG_CLOCKSTATUS 0xd4
+
+/* bits within the OPCR register */
+#define OPCR_SPENDN0 (1 << 7)
+#define OPCR_SPENDN1 (1 << 6)
+
+/* bits within the USBPCR register */
+#define USBPCR_USB_MODE BIT(31)
+#define USBPCR_IDPULLUP_MASK (0x3 << 28)
+#define USBPCR_COMMONONN BIT(25)
+#define USBPCR_VBUSVLDEXT BIT(24)
+#define USBPCR_VBUSVLDEXTSEL BIT(23)
+#define USBPCR_POR BIT(22)
+#define USBPCR_OTG_DISABLE BIT(20)
+#define USBPCR_COMPDISTUNE_MASK (0x7 << 17)
+#define USBPCR_OTGTUNE_MASK (0x7 << 14)
+#define USBPCR_SQRXTUNE_MASK (0x7 << 11)
+#define USBPCR_TXFSLSTUNE_MASK (0xf << 7)
+#define USBPCR_TXPREEMPHTUNE BIT(6)
+#define USBPCR_TXHSXVTUNE_MASK (0x3 << 4)
+#define USBPCR_TXVREFTUNE_MASK 0xf
+
+/* bits within the USBPCR1 register */
+#define USBPCR1_REFCLKSEL_SHIFT 26
+#define USBPCR1_REFCLKSEL_MASK (0x3 << USBPCR1_REFCLKSEL_SHIFT)
+#define USBPCR1_REFCLKSEL_CORE (0x2 << USBPCR1_REFCLKSEL_SHIFT)
+#define USBPCR1_REFCLKDIV_SHIFT 24
+#define USBPCR1_REFCLKDIV_MASK (0x3 << USBPCR1_REFCLKDIV_SHIFT)
+#define USBPCR1_REFCLKDIV_19_2 (0x3 << USBPCR1_REFCLKDIV_SHIFT)
+#define USBPCR1_REFCLKDIV_48 (0x2 << USBPCR1_REFCLKDIV_SHIFT)
+#define USBPCR1_REFCLKDIV_24 (0x1 << USBPCR1_REFCLKDIV_SHIFT)
+#define USBPCR1_REFCLKDIV_12 (0x0 << USBPCR1_REFCLKDIV_SHIFT)
+#define USBPCR1_USB_SEL BIT(28)
+#define USBPCR1_WORD_IF0 BIT(19)
+#define USBPCR1_WORD_IF1 BIT(18)
+
+/* bits within the USBRDT register */
+#define USBRDT_VBFIL_LD_EN BIT(25)
+#define USBRDT_USBRDT_MASK 0x7fffff
+
+/* bits within the USBVBFIL register */
+#define USBVBFIL_IDDIGFIL_SHIFT 16
+#define USBVBFIL_IDDIGFIL_MASK (0xffff << USBVBFIL_IDDIGFIL_SHIFT)
+#define USBVBFIL_USBVBFIL_MASK (0xffff)
+
+static struct ingenic_cgu *cgu;
+
+static u8 jz4780_otg_phy_get_parent(struct clk_hw *hw)
+{
+ /* we only use CLKCORE, revisit if that ever changes */
+ return 0;
+}
+
+static int jz4780_otg_phy_set_parent(struct clk_hw *hw, u8 idx)
+{
+ unsigned long flags;
+ u32 usbpcr1;
+
+ if (idx > 0)
+ return -EINVAL;
+
+ spin_lock_irqsave(&cgu->lock, flags);
+
+ usbpcr1 = readl(cgu->base + CGU_REG_USBPCR1);
+ usbpcr1 &= ~USBPCR1_REFCLKSEL_MASK;
+ /* we only use CLKCORE */
+ usbpcr1 |= USBPCR1_REFCLKSEL_CORE;
+ writel(usbpcr1, cgu->base + CGU_REG_USBPCR1);
+
+ spin_unlock_irqrestore(&cgu->lock, flags);
+ return 0;
+}
+
+static unsigned long jz4780_otg_phy_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u32 usbpcr1;
+ unsigned refclk_div;
+
+ usbpcr1 = readl(cgu->base + CGU_REG_USBPCR1);
+ refclk_div = usbpcr1 & USBPCR1_REFCLKDIV_MASK;
+
+ switch (refclk_div) {
+ case USBPCR1_REFCLKDIV_12:
+ return 12000000;
+
+ case USBPCR1_REFCLKDIV_24:
+ return 24000000;
+
+ case USBPCR1_REFCLKDIV_48:
+ return 48000000;
+
+ case USBPCR1_REFCLKDIV_19_2:
+ return 19200000;
+ }
+
+ BUG();
+ return parent_rate;
+}
+
+static long jz4780_otg_phy_round_rate(struct clk_hw *hw, unsigned long req_rate,
+ unsigned long *parent_rate)
+{
+ if (req_rate < 15600000)
+ return 12000000;
+
+ if (req_rate < 21600000)
+ return 19200000;
+
+ if (req_rate < 36000000)
+ return 24000000;
+
+ return 48000000;
+}
+
+static int jz4780_otg_phy_set_rate(struct clk_hw *hw, unsigned long req_rate,
+ unsigned long parent_rate)
+{
+ unsigned long flags;
+ u32 usbpcr1, div_bits;
+
+ switch (req_rate) {
+ case 12000000:
+ div_bits = USBPCR1_REFCLKDIV_12;
+ break;
+
+ case 19200000:
+ div_bits = USBPCR1_REFCLKDIV_19_2;
+ break;
+
+ case 24000000:
+ div_bits = USBPCR1_REFCLKDIV_24;
+ break;
+
+ case 48000000:
+ div_bits = USBPCR1_REFCLKDIV_48;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&cgu->lock, flags);
+
+ usbpcr1 = readl(cgu->base + CGU_REG_USBPCR1);
+ usbpcr1 &= ~USBPCR1_REFCLKDIV_MASK;
+ usbpcr1 |= div_bits;
+ writel(usbpcr1, cgu->base + CGU_REG_USBPCR1);
+
+ spin_unlock_irqrestore(&cgu->lock, flags);
+ return 0;
+}
+
+static struct clk_ops jz4780_otg_phy_ops = {
+ .get_parent = jz4780_otg_phy_get_parent,
+ .set_parent = jz4780_otg_phy_set_parent,
+
+ .recalc_rate = jz4780_otg_phy_recalc_rate,
+ .round_rate = jz4780_otg_phy_round_rate,
+ .set_rate = jz4780_otg_phy_set_rate,
+};
+
+static const s8 pll_od_encoding[16] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+ 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+};
+
+static const struct ingenic_cgu_clk_info jz4780_cgu_clocks[] = {
+
+ /* External clocks */
+
+ [JZ4780_CLK_EXCLK] = { "ext", CGU_CLK_EXT },
+ [JZ4780_CLK_RTCLK] = { "rtc", CGU_CLK_EXT },
+
+ /* PLLs */
+
+#define DEF_PLL(name) { \
+ .reg = CGU_REG_ ## name, \
+ .m_shift = 19, \
+ .m_bits = 13, \
+ .m_offset = 1, \
+ .n_shift = 13, \
+ .n_bits = 6, \
+ .n_offset = 1, \
+ .od_shift = 9, \
+ .od_bits = 4, \
+ .od_max = 16, \
+ .od_encoding = pll_od_encoding, \
+ .stable_bit = 6, \
+ .bypass_bit = 1, \
+ .enable_bit = 0, \
+}
+
+ [JZ4780_CLK_APLL] = {
+ "apll", CGU_CLK_PLL,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .pll = DEF_PLL(APLL),
+ },
+
+ [JZ4780_CLK_MPLL] = {
+ "mpll", CGU_CLK_PLL,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .pll = DEF_PLL(MPLL),
+ },
+
+ [JZ4780_CLK_EPLL] = {
+ "epll", CGU_CLK_PLL,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .pll = DEF_PLL(EPLL),
+ },
+
+ [JZ4780_CLK_VPLL] = {
+ "vpll", CGU_CLK_PLL,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .pll = DEF_PLL(VPLL),
+ },
+
+#undef DEF_PLL
+
+ /* Custom (SoC-specific) OTG PHY */
+
+ [JZ4780_CLK_OTGPHY] = {
+ "otg_phy", CGU_CLK_CUSTOM,
+ .parents = { -1, -1, JZ4780_CLK_EXCLK, -1 },
+ .custom = { &jz4780_otg_phy_ops },
+ },
+
+ /* Muxes & dividers */
+
+ [JZ4780_CLK_SCLKA] = {
+ "sclk_a", CGU_CLK_MUX,
+ .parents = { -1, JZ4780_CLK_APLL, JZ4780_CLK_EXCLK,
+ JZ4780_CLK_RTCLK },
+ .mux = { CGU_REG_CLOCKCONTROL, 30, 2 },
+ },
+
+ [JZ4780_CLK_CPUMUX] = {
+ "cpumux", CGU_CLK_MUX,
+ .parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_EPLL },
+ .mux = { CGU_REG_CLOCKCONTROL, 28, 2 },
+ },
+
+ [JZ4780_CLK_CPU] = {
+ "cpu", CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_CPUMUX, -1, -1, -1 },
+ .div = { CGU_REG_CLOCKCONTROL, 0, 4, 22, -1, -1 },
+ },
+
+ [JZ4780_CLK_L2CACHE] = {
+ "l2cache", CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_CPUMUX, -1, -1, -1 },
+ .div = { CGU_REG_CLOCKCONTROL, 4, 4, -1, -1, -1 },
+ },
+
+ [JZ4780_CLK_AHB0] = {
+ "ahb0", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_EPLL },
+ .mux = { CGU_REG_CLOCKCONTROL, 26, 2 },
+ .div = { CGU_REG_CLOCKCONTROL, 8, 4, 21, -1, -1 },
+ },
+
+ [JZ4780_CLK_AHB2PMUX] = {
+ "ahb2_apb_mux", CGU_CLK_MUX,
+ .parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_RTCLK },
+ .mux = { CGU_REG_CLOCKCONTROL, 24, 2 },
+ },
+
+ [JZ4780_CLK_AHB2] = {
+ "ahb2", CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_AHB2PMUX, -1, -1, -1 },
+ .div = { CGU_REG_CLOCKCONTROL, 12, 4, 20, -1, -1 },
+ },
+
+ [JZ4780_CLK_PCLK] = {
+ "pclk", CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_AHB2PMUX, -1, -1, -1 },
+ .div = { CGU_REG_CLOCKCONTROL, 16, 4, 20, -1, -1 },
+ },
+
+ [JZ4780_CLK_DDR] = {
+ "ddr", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1 },
+ .mux = { CGU_REG_DDRCDR, 30, 2 },
+ .div = { CGU_REG_DDRCDR, 0, 4, 29, 28, 27 },
+ },
+
+ [JZ4780_CLK_VPU] = {
+ "vpu", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_EPLL, -1 },
+ .mux = { CGU_REG_VPUCDR, 30, 2 },
+ .div = { CGU_REG_VPUCDR, 0, 4, 29, 28, 27 },
+ .gate = { CGU_REG_CLKGR1, 2 },
+ },
+
+ [JZ4780_CLK_I2SPLL] = {
+ "i2s_pll", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_EPLL, -1, -1 },
+ .mux = { CGU_REG_I2SCDR, 30, 1 },
+ .div = { CGU_REG_I2SCDR, 0, 8, 29, 28, 27 },
+ },
+
+ [JZ4780_CLK_I2S] = {
+ "i2s", CGU_CLK_MUX,
+ .parents = { JZ4780_CLK_EXCLK, JZ4780_CLK_I2SPLL, -1, -1 },
+ .mux = { CGU_REG_I2SCDR, 31, 1 },
+ },
+
+ [JZ4780_CLK_LCD0PIXCLK] = {
+ "lcd0pixclk", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_VPLL, -1 },
+ .mux = { CGU_REG_LP0CDR, 30, 2 },
+ .div = { CGU_REG_LP0CDR, 0, 8, 28, 27, 26 },
+ },
+
+ [JZ4780_CLK_LCD1PIXCLK] = {
+ "lcd1pixclk", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_VPLL, -1 },
+ .mux = { CGU_REG_LP1CDR, 30, 2 },
+ .div = { CGU_REG_LP1CDR, 0, 8, 28, 27, 26 },
+ },
+
+ [JZ4780_CLK_MSCMUX] = {
+ "msc_mux", CGU_CLK_MUX,
+ .parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1 },
+ .mux = { CGU_REG_MSC0CDR, 30, 2 },
+ },
+
+ [JZ4780_CLK_MSC0] = {
+ "msc0", CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_MSCMUX, -1, -1, -1 },
+ .div = { CGU_REG_MSC0CDR, 0, 8, 29, 28, 27 },
+ .gate = { CGU_REG_CLKGR0, 3 },
+ },
+
+ [JZ4780_CLK_MSC1] = {
+ "msc1", CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_MSCMUX, -1, -1, -1 },
+ .div = { CGU_REG_MSC1CDR, 0, 8, 29, 28, 27 },
+ .gate = { CGU_REG_CLKGR0, 11 },
+ },
+
+ [JZ4780_CLK_MSC2] = {
+ "msc2", CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_MSCMUX, -1, -1, -1 },
+ .div = { CGU_REG_MSC2CDR, 0, 8, 29, 28, 27 },
+ .gate = { CGU_REG_CLKGR0, 12 },
+ },
+
+ [JZ4780_CLK_UHC] = {
+ "uhc", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_EPLL, JZ4780_CLK_OTGPHY },
+ .mux = { CGU_REG_UHCCDR, 30, 2 },
+ .div = { CGU_REG_UHCCDR, 0, 8, 29, 28, 27 },
+ .gate = { CGU_REG_CLKGR0, 24 },
+ },
+
+ [JZ4780_CLK_SSIPLL] = {
+ "ssi_pll", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1, -1 },
+ .mux = { CGU_REG_SSICDR, 30, 1 },
+ .div = { CGU_REG_SSICDR, 0, 8, 29, 28, 27 },
+ },
+
+ [JZ4780_CLK_SSI] = {
+ "ssi", CGU_CLK_MUX,
+ .parents = { JZ4780_CLK_EXCLK, JZ4780_CLK_SSIPLL, -1, -1 },
+ .mux = { CGU_REG_SSICDR, 31, 1 },
+ },
+
+ [JZ4780_CLK_CIMMCLK] = {
+ "cim_mclk", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL, -1, -1 },
+ .mux = { CGU_REG_CIMCDR, 31, 1 },
+ .div = { CGU_REG_CIMCDR, 0, 8, 30, 29, 28 },
+ },
+
+ [JZ4780_CLK_PCMPLL] = {
+ "pcm_pll", CGU_CLK_MUX | CGU_CLK_DIV,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_EPLL, JZ4780_CLK_VPLL },
+ .mux = { CGU_REG_PCMCDR, 29, 2 },
+ .div = { CGU_REG_PCMCDR, 0, 8, 28, 27, 26 },
+ },
+
+ [JZ4780_CLK_PCM] = {
+ "pcm", CGU_CLK_MUX | CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, JZ4780_CLK_PCMPLL, -1, -1 },
+ .mux = { CGU_REG_PCMCDR, 31, 1 },
+ .gate = { CGU_REG_CLKGR1, 3 },
+ },
+
+ [JZ4780_CLK_GPU] = {
+ "gpu", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_EPLL },
+ .mux = { CGU_REG_GPUCDR, 30, 2 },
+ .div = { CGU_REG_GPUCDR, 0, 4, 29, 28, 27 },
+ .gate = { CGU_REG_CLKGR1, 4 },
+ },
+
+ [JZ4780_CLK_HDMI] = {
+ "hdmi", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_VPLL, -1 },
+ .mux = { CGU_REG_HDMICDR, 30, 2 },
+ .div = { CGU_REG_HDMICDR, 0, 8, 29, 28, 26 },
+ .gate = { CGU_REG_CLKGR1, 9 },
+ },
+
+ [JZ4780_CLK_BCH] = {
+ "bch", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+ .parents = { -1, JZ4780_CLK_SCLKA, JZ4780_CLK_MPLL,
+ JZ4780_CLK_EPLL },
+ .mux = { CGU_REG_BCHCDR, 30, 2 },
+ .div = { CGU_REG_BCHCDR, 0, 4, 29, 28, 27 },
+ .gate = { CGU_REG_CLKGR0, 1 },
+ },
+
+ /* Gate-only clocks */
+
+ [JZ4780_CLK_NEMC] = {
+ "nemc", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_AHB2, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 0 },
+ },
+
+ [JZ4780_CLK_OTG0] = {
+ "otg0", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 2 },
+ },
+
+ [JZ4780_CLK_SSI0] = {
+ "ssi0", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_SSI, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 4 },
+ },
+
+ [JZ4780_CLK_SMB0] = {
+ "smb0", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 5 },
+ },
+
+ [JZ4780_CLK_SMB1] = {
+ "smb1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 6 },
+ },
+
+ [JZ4780_CLK_SCC] = {
+ "scc", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 7 },
+ },
+
+ [JZ4780_CLK_AIC] = {
+ "aic", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 8 },
+ },
+
+ [JZ4780_CLK_TSSI0] = {
+ "tssi0", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 9 },
+ },
+
+ [JZ4780_CLK_OWI] = {
+ "owi", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 10 },
+ },
+
+ [JZ4780_CLK_KBC] = {
+ "kbc", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 13 },
+ },
+
+ [JZ4780_CLK_SADC] = {
+ "sadc", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 14 },
+ },
+
+ [JZ4780_CLK_UART0] = {
+ "uart0", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 15 },
+ },
+
+ [JZ4780_CLK_UART1] = {
+ "uart1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 16 },
+ },
+
+ [JZ4780_CLK_UART2] = {
+ "uart2", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 17 },
+ },
+
+ [JZ4780_CLK_UART3] = {
+ "uart3", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 18 },
+ },
+
+ [JZ4780_CLK_SSI1] = {
+ "ssi1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_SSI, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 19 },
+ },
+
+ [JZ4780_CLK_SSI2] = {
+ "ssi2", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_SSI, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 20 },
+ },
+
+ [JZ4780_CLK_PDMA] = {
+ "pdma", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 21 },
+ },
+
+ [JZ4780_CLK_GPS] = {
+ "gps", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 22 },
+ },
+
+ [JZ4780_CLK_MAC] = {
+ "mac", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 23 },
+ },
+
+ [JZ4780_CLK_SMB2] = {
+ "smb2", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 24 },
+ },
+
+ [JZ4780_CLK_CIM] = {
+ "cim", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 26 },
+ },
+
+ [JZ4780_CLK_LCD] = {
+ "lcd", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 28 },
+ },
+
+ [JZ4780_CLK_TVE] = {
+ "tve", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_LCD, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 27 },
+ },
+
+ [JZ4780_CLK_IPU] = {
+ "ipu", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 29 },
+ },
+
+ [JZ4780_CLK_DDR0] = {
+ "ddr0", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_DDR, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 30 },
+ },
+
+ [JZ4780_CLK_DDR1] = {
+ "ddr1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_DDR, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR0, 31 },
+ },
+
+ [JZ4780_CLK_SMB3] = {
+ "smb3", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 0 },
+ },
+
+ [JZ4780_CLK_TSSI1] = {
+ "tssi1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 1 },
+ },
+
+ [JZ4780_CLK_COMPRESS] = {
+ "compress", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 5 },
+ },
+
+ [JZ4780_CLK_AIC1] = {
+ "aic1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 6 },
+ },
+
+ [JZ4780_CLK_GPVLC] = {
+ "gpvlc", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 7 },
+ },
+
+ [JZ4780_CLK_OTG1] = {
+ "otg1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 8 },
+ },
+
+ [JZ4780_CLK_UART4] = {
+ "uart4", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 10 },
+ },
+
+ [JZ4780_CLK_AHBMON] = {
+ "ahb_mon", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 11 },
+ },
+
+ [JZ4780_CLK_SMB4] = {
+ "smb4", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_PCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 12 },
+ },
+
+ [JZ4780_CLK_DES] = {
+ "des", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 13 },
+ },
+
+ [JZ4780_CLK_X2D] = {
+ "x2d", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_EXCLK, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 14 },
+ },
+
+ [JZ4780_CLK_CORE1] = {
+ "core1", CGU_CLK_GATE,
+ .parents = { JZ4780_CLK_CPU, -1, -1, -1 },
+ .gate = { CGU_REG_CLKGR1, 15 },
+ },
+
+};
+
+static void __init jz4780_cgu_init(struct device_node *np)
+{
+ int retval;
+
+ cgu = ingenic_cgu_new(jz4780_cgu_clocks,
+ ARRAY_SIZE(jz4780_cgu_clocks), np);
+ if (!cgu) {
+ pr_err("%s: failed to initialise CGU\n", __func__);
+ return;
+ }
+
+ retval = ingenic_cgu_register_clocks(cgu);
+ if (retval) {
+ pr_err("%s: failed to register CGU Clocks\n", __func__);
+ return;
+ }
+}
+CLK_OF_DECLARE(jz4780_cgu, "ingenic,jz4780-cgu", jz4780_cgu_init);
diff --git a/drivers/cpufreq/ls1x-cpufreq.c b/drivers/cpufreq/ls1x-cpufreq.c
index f0913eee2f50..262581b3318d 100644
--- a/drivers/cpufreq/ls1x-cpufreq.c
+++ b/drivers/cpufreq/ls1x-cpufreq.c
@@ -17,8 +17,8 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include <asm/mach-loongson1/cpufreq.h>
-#include <asm/mach-loongson1/loongson1.h>
+#include <cpufreq.h>
+#include <loongson1.h>
static struct {
struct device *dev;
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 6517132e5d8b..99c69a3205c4 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -136,6 +136,7 @@ config QCOM_SCM
bool
depends on ARM || ARM64
+source "drivers/firmware/broadcom/Kconfig"
source "drivers/firmware/google/Kconfig"
source "drivers/firmware/efi/Kconfig"
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 3001f1ae1062..4a4b897f9314 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_QCOM_SCM) += qcom_scm.o
obj-$(CONFIG_QCOM_SCM) += qcom_scm-32.o
CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
+obj-y += broadcom/
obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
obj-$(CONFIG_EFI) += efi/
obj-$(CONFIG_UEFI_CPER) += efi/
diff --git a/drivers/firmware/broadcom/Kconfig b/drivers/firmware/broadcom/Kconfig
new file mode 100644
index 000000000000..6bed119930dd
--- /dev/null
+++ b/drivers/firmware/broadcom/Kconfig
@@ -0,0 +1,11 @@
+config BCM47XX_NVRAM
+ bool "Broadcom NVRAM driver"
+ depends on BCM47XX || ARCH_BCM_5301X
+ help
+ Broadcom home routers contain flash partition called "nvram" with all
+ important hardware configuration as well as some minor user setup.
+ NVRAM partition contains a text-like data representing name=value
+ pairs.
+ This driver provides an easy way to get value of requested parameter.
+ It simply reads content of NVRAM and parses it. It doesn't control any
+ hardware part itself.
diff --git a/drivers/firmware/broadcom/Makefile b/drivers/firmware/broadcom/Makefile
new file mode 100644
index 000000000000..d0e683583cd6
--- /dev/null
+++ b/drivers/firmware/broadcom/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_BCM47XX_NVRAM) += bcm47xx_nvram.o
diff --git a/drivers/firmware/broadcom/bcm47xx_nvram.c b/drivers/firmware/broadcom/bcm47xx_nvram.c
new file mode 100644
index 000000000000..87add3fdce52
--- /dev/null
+++ b/drivers/firmware/broadcom/bcm47xx_nvram.c
@@ -0,0 +1,248 @@
+/*
+ * BCM947xx nvram variable access
+ *
+ * Copyright (C) 2005 Broadcom Corporation
+ * Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2010-2012 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/mtd/mtd.h>
+#include <linux/bcm47xx_nvram.h>
+
+#define NVRAM_MAGIC 0x48534C46 /* 'FLSH' */
+#define NVRAM_SPACE 0x10000
+#define NVRAM_MAX_GPIO_ENTRIES 32
+#define NVRAM_MAX_GPIO_VALUE_LEN 30
+
+#define FLASH_MIN 0x00020000 /* Minimum flash size */
+
+struct nvram_header {
+ u32 magic;
+ u32 len;
+ u32 crc_ver_init; /* 0:7 crc, 8:15 ver, 16:31 sdram_init */
+ u32 config_refresh; /* 0:15 sdram_config, 16:31 sdram_refresh */
+ u32 config_ncdl; /* ncdl values for memc */
+};
+
+static char nvram_buf[NVRAM_SPACE];
+static size_t nvram_len;
+static const u32 nvram_sizes[] = {0x8000, 0xF000, 0x10000};
+
+static u32 find_nvram_size(void __iomem *end)
+{
+ struct nvram_header __iomem *header;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(nvram_sizes); i++) {
+ header = (struct nvram_header *)(end - nvram_sizes[i]);
+ if (header->magic == NVRAM_MAGIC)
+ return nvram_sizes[i];
+ }
+
+ return 0;
+}
+
+/* Probe for NVRAM header */
+static int nvram_find_and_copy(void __iomem *iobase, u32 lim)
+{
+ struct nvram_header __iomem *header;
+ int i;
+ u32 off;
+ u32 *src, *dst;
+ u32 size;
+
+ if (nvram_len) {
+ pr_warn("nvram already initialized\n");
+ return -EEXIST;
+ }
+
+ /* TODO: when nvram is on nand flash check for bad blocks first. */
+ off = FLASH_MIN;
+ while (off <= lim) {
+ /* Windowed flash access */
+ size = find_nvram_size(iobase + off);
+ if (size) {
+ header = (struct nvram_header *)(iobase + off - size);
+ goto found;
+ }
+ off <<= 1;
+ }
+
+ /* Try embedded NVRAM at 4 KB and 1 KB as last resorts */
+ header = (struct nvram_header *)(iobase + 4096);
+ if (header->magic == NVRAM_MAGIC) {
+ size = NVRAM_SPACE;
+ goto found;
+ }
+
+ header = (struct nvram_header *)(iobase + 1024);
+ if (header->magic == NVRAM_MAGIC) {
+ size = NVRAM_SPACE;
+ goto found;
+ }
+
+ pr_err("no nvram found\n");
+ return -ENXIO;
+
+found:
+ src = (u32 *)header;
+ dst = (u32 *)nvram_buf;
+ for (i = 0; i < sizeof(struct nvram_header); i += 4)
+ *dst++ = __raw_readl(src++);
+ header = (struct nvram_header *)nvram_buf;
+ nvram_len = header->len;
+ if (nvram_len > size) {
+ pr_err("The nvram size according to the header seems to be bigger than the partition on flash\n");
+ nvram_len = size;
+ }
+ if (nvram_len >= NVRAM_SPACE) {
+ pr_err("nvram on flash (%i bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n",
+ header->len, NVRAM_SPACE - 1);
+ nvram_len = NVRAM_SPACE - 1;
+ }
+ /* proceed reading data after header */
+ for (; i < nvram_len; i += 4)
+ *dst++ = readl(src++);
+ nvram_buf[NVRAM_SPACE - 1] = '\0';
+
+ return 0;
+}
+
+/*
+ * On bcm47xx we need access to the NVRAM very early, so we can't use mtd
+ * subsystem to access flash. We can't even use platform device / driver to
+ * store memory offset.
+ * To handle this we provide following symbol. It's supposed to be called as
+ * soon as we get info about flash device, before any NVRAM entry is needed.
+ */
+int bcm47xx_nvram_init_from_mem(u32 base, u32 lim)
+{
+ void __iomem *iobase;
+ int err;
+
+ iobase = ioremap_nocache(base, lim);
+ if (!iobase)
+ return -ENOMEM;
+
+ err = nvram_find_and_copy(iobase, lim);
+
+ iounmap(iobase);
+
+ return err;
+}
+
+static int nvram_init(void)
+{
+#ifdef CONFIG_MTD
+ struct mtd_info *mtd;
+ struct nvram_header header;
+ size_t bytes_read;
+ int err;
+
+ mtd = get_mtd_device_nm("nvram");
+ if (IS_ERR(mtd))
+ return -ENODEV;
+
+ err = mtd_read(mtd, 0, sizeof(header), &bytes_read, (uint8_t *)&header);
+ if (!err && header.magic == NVRAM_MAGIC &&
+ header.len > sizeof(header)) {
+ nvram_len = header.len;
+ if (nvram_len >= NVRAM_SPACE) {
+ pr_err("nvram on flash (%i bytes) is bigger than the reserved space in memory, will just copy the first %i bytes\n",
+ header.len, NVRAM_SPACE);
+ nvram_len = NVRAM_SPACE - 1;
+ }
+
+ err = mtd_read(mtd, 0, nvram_len, &nvram_len,
+ (u8 *)nvram_buf);
+ return err;
+ }
+#endif
+
+ return -ENXIO;
+}
+
+int bcm47xx_nvram_getenv(const char *name, char *val, size_t val_len)
+{
+ char *var, *value, *end, *eq;
+ int err;
+
+ if (!name)
+ return -EINVAL;
+
+ if (!nvram_len) {
+ err = nvram_init();
+ if (err)
+ return err;
+ }
+
+ /* Look for name=value and return value */
+ var = &nvram_buf[sizeof(struct nvram_header)];
+ end = nvram_buf + sizeof(nvram_buf);
+ while (var < end && *var) {
+ eq = strchr(var, '=');
+ if (!eq)
+ break;
+ value = eq + 1;
+ if (eq - var == strlen(name) &&
+ strncmp(var, name, eq - var) == 0)
+ return snprintf(val, val_len, "%s", value);
+ var = value + strlen(value) + 1;
+ }
+ return -ENOENT;
+}
+EXPORT_SYMBOL(bcm47xx_nvram_getenv);
+
+int bcm47xx_nvram_gpio_pin(const char *name)
+{
+ int i, err;
+ char nvram_var[] = "gpioXX";
+ char buf[NVRAM_MAX_GPIO_VALUE_LEN];
+
+ /* TODO: Optimize it to don't call getenv so many times */
+ for (i = 0; i < NVRAM_MAX_GPIO_ENTRIES; i++) {
+ err = snprintf(nvram_var, sizeof(nvram_var), "gpio%i", i);
+ if (err <= 0)
+ continue;
+ err = bcm47xx_nvram_getenv(nvram_var, buf, sizeof(buf));
+ if (err <= 0)
+ continue;
+ if (!strcmp(name, buf))
+ return i;
+ }
+ return -ENOENT;
+}
+EXPORT_SYMBOL(bcm47xx_nvram_gpio_pin);
+
+char *bcm47xx_nvram_get_contents(size_t *nvram_size)
+{
+ int err;
+ char *nvram;
+
+ if (!nvram_len) {
+ err = nvram_init();
+ if (err)
+ return NULL;
+ }
+
+ *nvram_size = nvram_len - sizeof(struct nvram_header);
+ nvram = vmalloc(*nvram_size);
+ if (!nvram)
+ return NULL;
+ memcpy(nvram, &nvram_buf[sizeof(struct nvram_header)], *nvram_size);
+
+ return nvram;
+}
+EXPORT_SYMBOL(bcm47xx_nvram_get_contents);
+
+MODULE_LICENSE("GPLv2");
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 8a7d7807b596..120d81543e53 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -86,6 +86,11 @@ config IMGPDC_IRQ
select GENERIC_IRQ_CHIP
select IRQ_DOMAIN
+config IRQ_MIPS_CPU
+ bool
+ select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN
+
config CLPS711X_IRQCHIP
bool
depends on ARCH_CLPS711X
@@ -160,10 +165,15 @@ config MIPS_GIC
bool
select MIPS_CM
+config INGENIC_IRQ
+ bool
+ depends on MACH_INGENIC
+ default y
+
config RENESAS_H8300H_INTC
bool
select IRQ_DOMAIN
config RENESAS_H8S_INTC
bool
- select IRQ_DOMAIN \ No newline at end of file
+ select IRQ_DOMAIN
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 5c9adf1f554d..b8d4e9691890 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_ARM_VIC) += irq-vic.o
obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic-common.o irq-atmel-aic.o
obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o irq-atmel-aic5.o
obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o
+obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o
obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o
obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o
obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o
@@ -50,3 +51,4 @@ obj-$(CONFIG_ARCH_DIGICOLOR) += irq-digicolor.o
obj-$(CONFIG_RENESAS_H8300H_INTC) += irq-renesas-h8300h.o
obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o
obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
+obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
diff --git a/drivers/irqchip/irq-ingenic.c b/drivers/irqchip/irq-ingenic.c
new file mode 100644
index 000000000000..005de3f932ae
--- /dev/null
+++ b/drivers/irqchip/irq-ingenic.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ * JZ4740 platform IRQ support
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/irqchip/ingenic.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/timex.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+#include <asm/mach-jz4740/irq.h>
+
+#include "irqchip.h"
+
+struct ingenic_intc_data {
+ void __iomem *base;
+ unsigned num_chips;
+};
+
+#define JZ_REG_INTC_STATUS 0x00
+#define JZ_REG_INTC_MASK 0x04
+#define JZ_REG_INTC_SET_MASK 0x08
+#define JZ_REG_INTC_CLEAR_MASK 0x0c
+#define JZ_REG_INTC_PENDING 0x10
+#define CHIP_SIZE 0x20
+
+static irqreturn_t intc_cascade(int irq, void *data)
+{
+ struct ingenic_intc_data *intc = irq_get_handler_data(irq);
+ uint32_t irq_reg;
+ unsigned i;
+
+ for (i = 0; i < intc->num_chips; i++) {
+ irq_reg = readl(intc->base + (i * CHIP_SIZE) +
+ JZ_REG_INTC_PENDING);
+ if (!irq_reg)
+ continue;
+
+ generic_handle_irq(__fls(irq_reg) + (i * 32) + JZ4740_IRQ_BASE);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void intc_irq_set_mask(struct irq_chip_generic *gc, uint32_t mask)
+{
+ struct irq_chip_regs *regs = &gc->chip_types->regs;
+
+ writel(mask, gc->reg_base + regs->enable);
+ writel(~mask, gc->reg_base + regs->disable);
+}
+
+void ingenic_intc_irq_suspend(struct irq_data *data)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
+ intc_irq_set_mask(gc, gc->wake_active);
+}
+
+void ingenic_intc_irq_resume(struct irq_data *data)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
+ intc_irq_set_mask(gc, gc->mask_cache);
+}
+
+static struct irqaction intc_cascade_action = {
+ .handler = intc_cascade,
+ .name = "SoC intc cascade interrupt",
+};
+
+static int __init ingenic_intc_of_init(struct device_node *node,
+ unsigned num_chips)
+{
+ struct ingenic_intc_data *intc;
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ struct irq_domain *domain;
+ int parent_irq, err = 0;
+ unsigned i;
+
+ intc = kzalloc(sizeof(*intc), GFP_KERNEL);
+ if (!intc) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+
+ parent_irq = irq_of_parse_and_map(node, 0);
+ if (!parent_irq) {
+ err = -EINVAL;
+ goto out_free;
+ }
+
+ err = irq_set_handler_data(parent_irq, intc);
+ if (err)
+ goto out_unmap_irq;
+
+ intc->num_chips = num_chips;
+ intc->base = of_iomap(node, 0);
+ if (!intc->base) {
+ err = -ENODEV;
+ goto out_unmap_irq;
+ }
+
+ for (i = 0; i < num_chips; i++) {
+ /* Mask all irqs */
+ writel(0xffffffff, intc->base + (i * CHIP_SIZE) +
+ JZ_REG_INTC_SET_MASK);
+
+ gc = irq_alloc_generic_chip("INTC", 1,
+ JZ4740_IRQ_BASE + (i * 32),
+ intc->base + (i * CHIP_SIZE),
+ handle_level_irq);
+
+ gc->wake_enabled = IRQ_MSK(32);
+
+ ct = gc->chip_types;
+ ct->regs.enable = JZ_REG_INTC_CLEAR_MASK;
+ ct->regs.disable = JZ_REG_INTC_SET_MASK;
+ ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
+ ct->chip.irq_mask = irq_gc_mask_disable_reg;
+ ct->chip.irq_mask_ack = irq_gc_mask_disable_reg;
+ ct->chip.irq_set_wake = irq_gc_set_wake;
+ ct->chip.irq_suspend = ingenic_intc_irq_suspend;
+ ct->chip.irq_resume = ingenic_intc_irq_resume;
+
+ irq_setup_generic_chip(gc, IRQ_MSK(32), 0, 0,
+ IRQ_NOPROBE | IRQ_LEVEL);
+ }
+
+ domain = irq_domain_add_legacy(node, num_chips * 32, JZ4740_IRQ_BASE, 0,
+ &irq_domain_simple_ops, NULL);
+ if (!domain)
+ pr_warn("unable to register IRQ domain\n");
+
+ setup_irq(parent_irq, &intc_cascade_action);
+ return 0;
+
+out_unmap_irq:
+ irq_dispose_mapping(parent_irq);
+out_free:
+ kfree(intc);
+out_err:
+ return err;
+}
+
+static int __init intc_1chip_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ return ingenic_intc_of_init(node, 1);
+}
+IRQCHIP_DECLARE(jz4740_intc, "ingenic,jz4740-intc", intc_1chip_of_init);
+
+static int __init intc_2chip_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ return ingenic_intc_of_init(node, 2);
+}
+IRQCHIP_DECLARE(jz4770_intc, "ingenic,jz4770-intc", intc_2chip_of_init);
+IRQCHIP_DECLARE(jz4775_intc, "ingenic,jz4775-intc", intc_2chip_of_init);
+IRQCHIP_DECLARE(jz4780_intc, "ingenic,jz4780-intc", intc_2chip_of_init);
diff --git a/drivers/irqchip/irq-mips-cpu.c b/drivers/irqchip/irq-mips-cpu.c
new file mode 100644
index 000000000000..a43c41988009
--- /dev/null
+++ b/drivers/irqchip/irq-mips-cpu.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2001 MontaVista Software Inc.
+ * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
+ *
+ * Copyright (C) 2001 Ralf Baechle
+ * Copyright (C) 2005 MIPS Technologies, Inc. All rights reserved.
+ * Author: Maciej W. Rozycki <macro@mips.com>
+ *
+ * This file define the irq handler for MIPS CPU interrupts.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+/*
+ * Almost all MIPS CPUs define 8 interrupt sources. They are typically
+ * level triggered (i.e., cannot be cleared from CPU; must be cleared from
+ * device). The first two are software interrupts which we don't really
+ * use or support. The last one is usually the CPU timer interrupt if
+ * counter register is present or, for CPUs with an external FPU, by
+ * convention it's the FPU exception interrupt.
+ *
+ * Don't even think about using this on SMP. You have been warned.
+ *
+ * This file exports one global function:
+ * void mips_cpu_irq_init(void);
+ */
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+
+#include <asm/irq_cpu.h>
+#include <asm/mipsregs.h>
+#include <asm/mipsmtregs.h>
+#include <asm/setup.h>
+
+#include "irqchip.h"
+
+static inline void unmask_mips_irq(struct irq_data *d)
+{
+ set_c0_status(0x100 << (d->irq - MIPS_CPU_IRQ_BASE));
+ irq_enable_hazard();
+}
+
+static inline void mask_mips_irq(struct irq_data *d)
+{
+ clear_c0_status(0x100 << (d->irq - MIPS_CPU_IRQ_BASE));
+ irq_disable_hazard();
+}
+
+static struct irq_chip mips_cpu_irq_controller = {
+ .name = "MIPS",
+ .irq_ack = mask_mips_irq,
+ .irq_mask = mask_mips_irq,
+ .irq_mask_ack = mask_mips_irq,
+ .irq_unmask = unmask_mips_irq,
+ .irq_eoi = unmask_mips_irq,
+ .irq_disable = mask_mips_irq,
+ .irq_enable = unmask_mips_irq,
+};
+
+/*
+ * Basically the same as above but taking care of all the MT stuff
+ */
+
+static unsigned int mips_mt_cpu_irq_startup(struct irq_data *d)
+{
+ unsigned int vpflags = dvpe();
+
+ clear_c0_cause(0x100 << (d->irq - MIPS_CPU_IRQ_BASE));
+ evpe(vpflags);
+ unmask_mips_irq(d);
+ return 0;
+}
+
+/*
+ * While we ack the interrupt interrupts are disabled and thus we don't need
+ * to deal with concurrency issues. Same for mips_cpu_irq_end.
+ */
+static void mips_mt_cpu_irq_ack(struct irq_data *d)
+{
+ unsigned int vpflags = dvpe();
+ clear_c0_cause(0x100 << (d->irq - MIPS_CPU_IRQ_BASE));
+ evpe(vpflags);
+ mask_mips_irq(d);
+}
+
+static struct irq_chip mips_mt_cpu_irq_controller = {
+ .name = "MIPS",
+ .irq_startup = mips_mt_cpu_irq_startup,
+ .irq_ack = mips_mt_cpu_irq_ack,
+ .irq_mask = mask_mips_irq,
+ .irq_mask_ack = mips_mt_cpu_irq_ack,
+ .irq_unmask = unmask_mips_irq,
+ .irq_eoi = unmask_mips_irq,
+ .irq_disable = mask_mips_irq,
+ .irq_enable = unmask_mips_irq,
+};
+
+asmlinkage void __weak plat_irq_dispatch(void)
+{
+ unsigned long pending = read_c0_cause() & read_c0_status() & ST0_IM;
+ int irq;
+
+ if (!pending) {
+ spurious_interrupt();
+ return;
+ }
+
+ pending >>= CAUSEB_IP;
+ while (pending) {
+ irq = fls(pending) - 1;
+ do_IRQ(MIPS_CPU_IRQ_BASE + irq);
+ pending &= ~BIT(irq);
+ }
+}
+
+static int mips_cpu_intc_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ static struct irq_chip *chip;
+
+ if (hw < 2 && cpu_has_mipsmt) {
+ /* Software interrupts are used for MT/CMT IPI */
+ chip = &mips_mt_cpu_irq_controller;
+ } else {
+ chip = &mips_cpu_irq_controller;
+ }
+
+ if (cpu_has_vint)
+ set_vi_handler(hw, plat_irq_dispatch);
+
+ irq_set_chip_and_handler(irq, chip, handle_percpu_irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops mips_cpu_intc_irq_domain_ops = {
+ .map = mips_cpu_intc_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static void __init __mips_cpu_irq_init(struct device_node *of_node)
+{
+ struct irq_domain *domain;
+
+ /* Mask interrupts. */
+ clear_c0_status(ST0_IM);
+ clear_c0_cause(CAUSEF_IP);
+
+ domain = irq_domain_add_legacy(of_node, 8, MIPS_CPU_IRQ_BASE, 0,
+ &mips_cpu_intc_irq_domain_ops, NULL);
+ if (!domain)
+ panic("Failed to add irqdomain for MIPS CPU");
+}
+
+void __init mips_cpu_irq_init(void)
+{
+ __mips_cpu_irq_init(NULL);
+}
+
+int __init mips_cpu_irq_of_init(struct device_node *of_node,
+ struct device_node *parent)
+{
+ __mips_cpu_irq_init(of_node);
+ return 0;
+}
+IRQCHIP_DECLARE(cpu_intc, "mti,cpu-interrupt-controller", mips_cpu_irq_of_init);
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 487d057431cc..c0e6ede3e27d 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -267,6 +267,13 @@ config PHY_EXYNOS5_USBDRD
This driver provides PHY interface for USB 3.0 DRD controller
present on Exynos5 SoC series.
+config PHY_PISTACHIO_USB
+ tristate "IMG Pistachio USB2.0 PHY driver"
+ depends on MACH_PISTACHIO
+ select GENERIC_PHY
+ help
+ Enable this to support the USB2.0 PHY on the IMG Pistachio SoC.
+
config PHY_QCOM_APQ8064_SATA
tristate "Qualcomm APQ8064 SATA SerDes/PHY driver"
depends on ARCH_QCOM
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 42f58e95aff0..f344e1b2e825 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -44,3 +44,4 @@ obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o
obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
obj-$(CONFIG_PHY_BRCMSTB_SATA) += phy-brcmstb-sata.o
+obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
diff --git a/drivers/phy/phy-pistachio-usb.c b/drivers/phy/phy-pistachio-usb.c
new file mode 100644
index 000000000000..c6db35e6bb63
--- /dev/null
+++ b/drivers/phy/phy-pistachio-usb.c
@@ -0,0 +1,206 @@
+/*
+ * IMG Pistachio USB PHY driver
+ *
+ * Copyright (C) 2015 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/phy/phy-pistachio-usb.h>
+
+#define USB_PHY_CONTROL1 0x04
+#define USB_PHY_CONTROL1_FSEL_SHIFT 2
+#define USB_PHY_CONTROL1_FSEL_MASK 0x7
+
+#define USB_PHY_STRAP_CONTROL 0x10
+#define USB_PHY_STRAP_CONTROL_REFCLK_SHIFT 4
+#define USB_PHY_STRAP_CONTROL_REFCLK_MASK 0x3
+
+#define USB_PHY_STATUS 0x14
+#define USB_PHY_STATUS_RX_PHY_CLK BIT(9)
+#define USB_PHY_STATUS_RX_UTMI_CLK BIT(8)
+#define USB_PHY_STATUS_VBUS_FAULT BIT(7)
+
+struct pistachio_usb_phy {
+ struct device *dev;
+ struct regmap *cr_top;
+ struct clk *phy_clk;
+ unsigned int refclk;
+};
+
+static const unsigned long fsel_rate_map[] = {
+ 9600000,
+ 10000000,
+ 12000000,
+ 19200000,
+ 20000000,
+ 24000000,
+ 0,
+ 50000000,
+};
+
+static int pistachio_usb_phy_power_on(struct phy *phy)
+{
+ struct pistachio_usb_phy *p_phy = phy_get_drvdata(phy);
+ unsigned long timeout, rate;
+ unsigned int i;
+ int ret;
+
+ ret = clk_prepare_enable(p_phy->phy_clk);
+ if (ret < 0) {
+ dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n", ret);
+ return ret;
+ }
+
+ regmap_update_bits(p_phy->cr_top, USB_PHY_STRAP_CONTROL,
+ USB_PHY_STRAP_CONTROL_REFCLK_MASK <<
+ USB_PHY_STRAP_CONTROL_REFCLK_SHIFT,
+ p_phy->refclk << USB_PHY_STRAP_CONTROL_REFCLK_SHIFT);
+
+ rate = clk_get_rate(p_phy->phy_clk);
+ if (p_phy->refclk == REFCLK_XO_CRYSTAL && rate != 12000000) {
+ dev_err(p_phy->dev, "Unsupported rate for XO crystal: %ld\n",
+ rate);
+ ret = -EINVAL;
+ goto disable_clk;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(fsel_rate_map); i++) {
+ if (rate == fsel_rate_map[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(fsel_rate_map)) {
+ dev_err(p_phy->dev, "Unsupported clock rate: %lu\n", rate);
+ ret = -EINVAL;
+ goto disable_clk;
+ }
+
+ regmap_update_bits(p_phy->cr_top, USB_PHY_CONTROL1,
+ USB_PHY_CONTROL1_FSEL_MASK <<
+ USB_PHY_CONTROL1_FSEL_SHIFT,
+ i << USB_PHY_CONTROL1_FSEL_SHIFT);
+
+ timeout = jiffies + msecs_to_jiffies(200);
+ while (time_before(jiffies, timeout)) {
+ unsigned int val;
+
+ regmap_read(p_phy->cr_top, USB_PHY_STATUS, &val);
+ if (val & USB_PHY_STATUS_VBUS_FAULT) {
+ dev_err(p_phy->dev, "VBUS fault detected\n");
+ ret = -EIO;
+ goto disable_clk;
+ }
+ if ((val & USB_PHY_STATUS_RX_PHY_CLK) &&
+ (val & USB_PHY_STATUS_RX_UTMI_CLK))
+ return 0;
+ usleep_range(1000, 1500);
+ }
+
+ dev_err(p_phy->dev, "Timed out waiting for PHY to power on\n");
+ ret = -ETIMEDOUT;
+
+disable_clk:
+ clk_disable_unprepare(p_phy->phy_clk);
+ return ret;
+}
+
+static int pistachio_usb_phy_power_off(struct phy *phy)
+{
+ struct pistachio_usb_phy *p_phy = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(p_phy->phy_clk);
+
+ return 0;
+}
+
+static const struct phy_ops pistachio_usb_phy_ops = {
+ .power_on = pistachio_usb_phy_power_on,
+ .power_off = pistachio_usb_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int pistachio_usb_phy_probe(struct platform_device *pdev)
+{
+ struct pistachio_usb_phy *p_phy;
+ struct phy_provider *provider;
+ struct phy *phy;
+ int ret;
+
+ p_phy = devm_kzalloc(&pdev->dev, sizeof(*p_phy), GFP_KERNEL);
+ if (!p_phy)
+ return -ENOMEM;
+ p_phy->dev = &pdev->dev;
+ platform_set_drvdata(pdev, p_phy);
+
+ p_phy->cr_top = syscon_regmap_lookup_by_phandle(p_phy->dev->of_node,
+ "img,cr-top");
+ if (IS_ERR(p_phy->cr_top)) {
+ dev_err(p_phy->dev, "Failed to get CR_TOP registers: %ld\n",
+ PTR_ERR(p_phy->cr_top));
+ return PTR_ERR(p_phy->cr_top);
+ }
+
+ p_phy->phy_clk = devm_clk_get(p_phy->dev, "usb_phy");
+ if (IS_ERR(p_phy->phy_clk)) {
+ dev_err(p_phy->dev, "Failed to get usb_phy clock: %ld\n",
+ PTR_ERR(p_phy->phy_clk));
+ return PTR_ERR(p_phy->phy_clk);
+ }
+
+ ret = of_property_read_u32(p_phy->dev->of_node, "img,refclk",
+ &p_phy->refclk);
+ if (ret < 0) {
+ dev_err(p_phy->dev, "No reference clock selector specified\n");
+ return ret;
+ }
+
+ phy = devm_phy_create(p_phy->dev, NULL, &pistachio_usb_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(p_phy->dev, "Failed to create PHY: %ld\n",
+ PTR_ERR(phy));
+ return PTR_ERR(phy);
+ }
+ phy_set_drvdata(phy, p_phy);
+
+ provider = devm_of_phy_provider_register(p_phy->dev,
+ of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(p_phy->dev, "Failed to register PHY provider: %ld\n",
+ PTR_ERR(provider));
+ return PTR_ERR(provider);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id pistachio_usb_phy_of_match[] = {
+ { .compatible = "img,pistachio-usb-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pistachio_usb_phy_of_match);
+
+static struct platform_driver pistachio_usb_phy_driver = {
+ .probe = pistachio_usb_phy_probe,
+ .driver = {
+ .name = "pistachio-usb-phy",
+ .of_match_table = pistachio_usb_phy_of_match,
+ },
+};
+module_platform_driver(pistachio_usb_phy_driver);
+
+MODULE_AUTHOR("Andrew Bresticker <abrestic@chromium.org>");
+MODULE_DESCRIPTION("IMG Pistachio USB2.0 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index db2fe4ab4b4a..83b4b89b9d5a 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1503,7 +1503,7 @@ config RTC_DRV_PUV3
config RTC_DRV_LOONGSON1
tristate "loongson1 RTC support"
- depends on MACH_LOONGSON1
+ depends on MACH_LOONGSON32
help
This is a driver for the loongson1 on-chip Counter0 (Time-Of-Year
counter) to be used as a RTC.
diff --git a/drivers/rtc/rtc-ls1x.c b/drivers/rtc/rtc-ls1x.c
index 8445e564094a..22a9ec4f2b83 100644
--- a/drivers/rtc/rtc-ls1x.c
+++ b/drivers/rtc/rtc-ls1x.c
@@ -17,7 +17,7 @@
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/io.h>
-#include <asm/mach-loongson1/loongson1.h>
+#include <loongson1.h>
#define LS1X_RTC_REG_OFFSET (LS1X_RTC_BASE + 0x20)
#define LS1X_RTC_REGS(x) \
diff --git a/drivers/tty/serial/8250/8250_ingenic.c b/drivers/tty/serial/8250/8250_ingenic.c
new file mode 100644
index 000000000000..21bf81fe794f
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_ingenic.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2010 Lars-Peter Clausen <lars@metafoo.de>
+ * Copyright (C) 2015 Imagination Technologies
+ *
+ * Ingenic SoC UART support
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/io.h>
+#include <linux/libfdt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_fdt.h>
+#include <linux/platform_device.h>
+#include <linux/serial_8250.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+
+struct ingenic_uart_data {
+ struct clk *clk_module;
+ struct clk *clk_baud;
+ int line;
+};
+
+#define UART_FCR_UME BIT(4)
+
+static struct earlycon_device *early_device;
+
+static uint8_t __init early_in(struct uart_port *port, int offset)
+{
+ return readl(port->membase + (offset << 2));
+}
+
+static void __init early_out(struct uart_port *port, int offset, uint8_t value)
+{
+ writel(value, port->membase + (offset << 2));
+}
+
+static void __init ingenic_early_console_putc(struct uart_port *port, int c)
+{
+ uint8_t lsr;
+
+ do {
+ lsr = early_in(port, UART_LSR);
+ } while ((lsr & UART_LSR_TEMT) == 0);
+
+ early_out(port, UART_TX, c);
+}
+
+static void __init ingenic_early_console_write(struct console *console,
+ const char *s, unsigned int count)
+{
+ uart_console_write(&early_device->port, s, count,
+ ingenic_early_console_putc);
+}
+
+static void __init ingenic_early_console_setup_clock(struct earlycon_device *dev)
+{
+ void *fdt = initial_boot_params;
+ const __be32 *prop;
+ int offset;
+
+ offset = fdt_path_offset(fdt, "/ext");
+ if (offset < 0)
+ return;
+
+ prop = fdt_getprop(fdt, offset, "clock-frequency", NULL);
+ if (!prop)
+ return;
+
+ dev->port.uartclk = be32_to_cpup(prop);
+}
+
+static int __init ingenic_early_console_setup(struct earlycon_device *dev,
+ const char *opt)
+{
+ struct uart_port *port = &dev->port;
+ unsigned int baud, divisor;
+
+ if (!dev->port.membase)
+ return -ENODEV;
+
+ ingenic_early_console_setup_clock(dev);
+
+ baud = dev->baud ?: 115200;
+ divisor = DIV_ROUND_CLOSEST(port->uartclk, 16 * baud);
+
+ early_out(port, UART_IER, 0);
+ early_out(port, UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8);
+ early_out(port, UART_DLL, 0);
+ early_out(port, UART_DLM, 0);
+ early_out(port, UART_LCR, UART_LCR_WLEN8);
+ early_out(port, UART_FCR, UART_FCR_UME | UART_FCR_CLEAR_XMIT |
+ UART_FCR_CLEAR_RCVR | UART_FCR_ENABLE_FIFO);
+ early_out(port, UART_MCR, UART_MCR_RTS | UART_MCR_DTR);
+
+ early_out(port, UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8);
+ early_out(port, UART_DLL, divisor & 0xff);
+ early_out(port, UART_DLM, (divisor >> 8) & 0xff);
+ early_out(port, UART_LCR, UART_LCR_WLEN8);
+
+ early_device = dev;
+ dev->con->write = ingenic_early_console_write;
+
+ return 0;
+}
+
+EARLYCON_DECLARE(jz4740_uart, ingenic_early_console_setup);
+OF_EARLYCON_DECLARE(jz4740_uart, "ingenic,jz4740-uart",
+ ingenic_early_console_setup);
+
+EARLYCON_DECLARE(jz4775_uart, ingenic_early_console_setup);
+OF_EARLYCON_DECLARE(jz4775_uart, "ingenic,jz4775-uart",
+ ingenic_early_console_setup);
+
+EARLYCON_DECLARE(jz4780_uart, ingenic_early_console_setup);
+OF_EARLYCON_DECLARE(jz4780_uart, "ingenic,jz4780-uart",
+ ingenic_early_console_setup);
+
+static void ingenic_uart_serial_out(struct uart_port *p, int offset, int value)
+{
+ switch (offset) {
+ case UART_FCR:
+ /* UART module enable */
+ value |= UART_FCR_UME;
+ break;
+
+ case UART_IER:
+ value |= (value & 0x4) << 2;
+ break;
+
+ default:
+ break;
+ }
+
+ writeb(value, p->membase + (offset << p->regshift));
+}
+
+static int ingenic_uart_probe(struct platform_device *pdev)
+{
+ struct uart_8250_port uart = {};
+ struct resource *regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ struct ingenic_uart_data *data;
+ int err, line;
+
+ if (!regs || !irq) {
+ dev_err(&pdev->dev, "no registers/irq defined\n");
+ return -EINVAL;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ spin_lock_init(&uart.port.lock);
+ uart.port.type = PORT_16550;
+ uart.port.flags = UPF_SKIP_TEST | UPF_IOREMAP | UPF_FIXED_TYPE;
+ uart.port.iotype = UPIO_MEM;
+ uart.port.mapbase = regs->start;
+ uart.port.regshift = 2;
+ uart.port.serial_out = ingenic_uart_serial_out;
+ uart.port.irq = irq->start;
+ uart.port.dev = &pdev->dev;
+
+ /* Check for a fixed line number */
+ line = of_alias_get_id(pdev->dev.of_node, "serial");
+ if (line >= 0)
+ uart.port.line = line;
+
+ uart.port.membase = devm_ioremap(&pdev->dev, regs->start,
+ resource_size(regs));
+ if (!uart.port.membase)
+ return -ENOMEM;
+
+ data->clk_module = devm_clk_get(&pdev->dev, "module");
+ if (IS_ERR(data->clk_module)) {
+ err = PTR_ERR(data->clk_module);
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "unable to get module clock: %d\n", err);
+ return err;
+ }
+
+ data->clk_baud = devm_clk_get(&pdev->dev, "baud");
+ if (IS_ERR(data->clk_baud)) {
+ err = PTR_ERR(data->clk_baud);
+ if (err != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "unable to get baud clock: %d\n", err);
+ return err;
+ }
+
+ err = clk_prepare_enable(data->clk_module);
+ if (err) {
+ dev_err(&pdev->dev, "could not enable module clock: %d\n", err);
+ goto out;
+ }
+
+ err = clk_prepare_enable(data->clk_baud);
+ if (err) {
+ dev_err(&pdev->dev, "could not enable baud clock: %d\n", err);
+ goto out_disable_moduleclk;
+ }
+ uart.port.uartclk = clk_get_rate(data->clk_baud);
+
+ data->line = serial8250_register_8250_port(&uart);
+ if (data->line < 0) {
+ err = data->line;
+ goto out_disable_baudclk;
+ }
+
+ platform_set_drvdata(pdev, data);
+ return 0;
+
+out_disable_baudclk:
+ clk_disable_unprepare(data->clk_baud);
+out_disable_moduleclk:
+ clk_disable_unprepare(data->clk_module);
+out:
+ return err;
+}
+
+static int ingenic_uart_remove(struct platform_device *pdev)
+{
+ struct ingenic_uart_data *data = platform_get_drvdata(pdev);
+
+ serial8250_unregister_port(data->line);
+ clk_disable_unprepare(data->clk_module);
+ clk_disable_unprepare(data->clk_baud);
+ return 0;
+}
+
+static const struct of_device_id of_match[] = {
+ { .compatible = "ingenic,jz4740-uart" },
+ { .compatible = "ingenic,jz4775-uart" },
+ { .compatible = "ingenic,jz4780-uart" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_match);
+
+static struct platform_driver ingenic_uart_platform_driver = {
+ .driver = {
+ .name = "ingenic-uart",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match,
+ },
+ .probe = ingenic_uart_probe,
+ .remove = ingenic_uart_remove,
+};
+
+module_platform_driver(ingenic_uart_platform_driver);
+
+MODULE_AUTHOR("Paul Burton");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Ingenic SoC UART driver");
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index a74a8e4717d4..e1de1181b322 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -357,3 +357,12 @@ config SERIAL_8250_UNIPHIER
help
If you have a UniPhier based board and want to use the on-chip
serial ports, say Y to this option. If unsure, say N.
+
+config SERIAL_8250_INGENIC
+ bool "Support for Ingenic SoC serial ports"
+ depends on SERIAL_8250_CONSOLE && OF_FLATTREE
+ select LIBFDT
+ select SERIAL_EARLYCON
+ help
+ If you have a system using an Ingenic SoC and wish to make use of
+ its UARTs, say Y to this option. If unsure, say N.
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index 6fa22ffad63d..706295913c34 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -25,3 +25,6 @@ obj-$(CONFIG_SERIAL_8250_FINTEK) += 8250_fintek.o
obj-$(CONFIG_SERIAL_8250_LPC18XX) += 8250_lpc18xx.o
obj-$(CONFIG_SERIAL_8250_MT6577) += 8250_mtk.o
obj-$(CONFIG_SERIAL_8250_UNIPHIER) += 8250_uniphier.o
+obj-$(CONFIG_SERIAL_8250_INGENIC) += 8250_ingenic.o
+
+CFLAGS_8250_ingenic.o += -I$(srctree)/scripts/dtc/libfdt
diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index 1c4791033b72..2847108cc8dd 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -462,6 +462,7 @@ static struct sysrq_key_op *sysrq_key_table[36] = {
/* v: May be registered for frame buffer console restore */
NULL, /* v */
&sysrq_showstate_blocked_op, /* w */
+ /* x: May be registered on mips for TLB dump */
/* x: May be registered on ppc/powerpc for xmon */
/* x: May be registered on sparc64 for global PMU dump */
NULL, /* x */
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 547cee83400b..8afc3c1efdab 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -295,7 +295,7 @@ config USB_OCTEON_EHCI
bool "Octeon on-chip EHCI support (DEPRECATED)"
depends on CAVIUM_OCTEON_SOC
default n
- select USB_EHCI_BIG_ENDIAN_MMIO
+ select USB_EHCI_BIG_ENDIAN_MMIO if CPU_BIG_ENDIAN
select USB_EHCI_HCD_PLATFORM
help
This option is deprecated now and the driver was removed, use
@@ -568,7 +568,7 @@ config USB_OCTEON_OHCI
bool "Octeon on-chip OHCI support (DEPRECATED)"
depends on CAVIUM_OCTEON_SOC
default USB_OCTEON_EHCI
- select USB_OHCI_BIG_ENDIAN_MMIO
+ select USB_OHCI_BIG_ENDIAN_MMIO if CPU_BIG_ENDIAN
select USB_OHCI_LITTLE_ENDIAN
select USB_OHCI_HCD_PLATFORM
help