From d1e9a4f547354ca258b6200ab6b1f0e4909b92e8 Mon Sep 17 00:00:00 2001 From: James Hogan Date: Tue, 19 May 2015 09:50:29 +0100 Subject: MIPS: Add SysRq operation to dump TLBs on all CPUs Add a MIPS specific SysRq operation to dump the TLB entries on all CPUs, using the 'x' trigger key. Signed-off-by: James Hogan Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/10072/ Signed-off-by: Ralf Baechle --- drivers/tty/sysrq.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c index 843f2cdc280b..8ba52e56bb8b 100644 --- a/drivers/tty/sysrq.c +++ b/drivers/tty/sysrq.c @@ -463,6 +463,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 */ -- cgit v1.2.1 From 67e38cf2933e904426b428431961e4880d6d4b90 Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Tue, 26 May 2015 18:20:06 +0200 Subject: MIPS/IRQCHIP: Move irq_chip from arch/mips to drivers/irqchip. While at it, rename it because in drivers/irqchip no longer every CPU is a MIPS. Signed-off-by: Ralf Baechle --- drivers/irqchip/Kconfig | 5 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mips-cpu.c | 169 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 drivers/irqchip/irq-mips-cpu.c (limited to 'drivers') diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 6de62a96e79c..2b7531e0e84c 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -85,6 +85,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 diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index dda4927e47a6..129cde1ff5a7 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 diff --git a/drivers/irqchip/irq-mips-cpu.c b/drivers/irqchip/irq-mips-cpu.c new file mode 100644 index 000000000000..6eb7a3f515fc --- /dev/null +++ b/drivers/irqchip/irq-mips-cpu.c @@ -0,0 +1,169 @@ +/* + * 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 + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +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; +} -- cgit v1.2.1 From 892b8cf06d8a1e7f1e2b2084e9ca7e7e77f13402 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:16 +0100 Subject: IRQCHIP: irq_cpu: declare irqchip table entry Allow the MIPS CPU interrupt controller to be probed from DT using the generic __irqchip_of_table for platforms which use irqchip_init. This will avoid such platforms needing to duplicate the compatible string & init function pointer. [ralf@linux-mips.org: Resolved conflict due the preceeding commit that moves irq-cpu.c. Signed-off-by: Paul Burton Cc: Lars-Peter Clausen Cc: linux-mips@linux-mips.org Cc: Qais Yousef Cc: Andrew Bresticker Cc: linux-kernel@vger.kernel.org Cc: Felix Fietkau Patchwork: https://patchwork.linux-mips.org/patch/10131/ Signed-off-by: Ralf Baechle --- drivers/irqchip/irq-mips-cpu.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/irqchip/irq-mips-cpu.c b/drivers/irqchip/irq-mips-cpu.c index 6eb7a3f515fc..a43c41988009 100644 --- a/drivers/irqchip/irq-mips-cpu.c +++ b/drivers/irqchip/irq-mips-cpu.c @@ -38,6 +38,8 @@ #include #include +#include "irqchip.h" + static inline void unmask_mips_irq(struct irq_data *d) { set_c0_status(0x100 << (d->irq - MIPS_CPU_IRQ_BASE)); @@ -167,3 +169,4 @@ int __init mips_cpu_irq_of_init(struct device_node *of_node, __mips_cpu_irq_init(of_node); return 0; } +IRQCHIP_DECLARE(cpu_intc, "mti,cpu-interrupt-controller", mips_cpu_irq_of_init); -- cgit v1.2.1 From 44e08e7099c8de226606cfc989b45d6fa27f507f Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:31 +0100 Subject: MIPS/IRQCHIP: Move Ingenic SoC intc driver to drivers/irqchip Move the driver for Ingenic SoC interrupt controllers into drivers/irqchip where it belongs. Signed-off-by: Paul Burton Cc: Lars-Peter Clausen Cc: Thomas Gleixner Cc: Jason Cooper Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Cc: Brian Norris Patchwork: https://patchwork.linux-mips.org/patch/10147/ Signed-off-by: Ralf Baechle --- drivers/irqchip/Kconfig | 5 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-ingenic.c | 177 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 drivers/irqchip/irq-ingenic.c (limited to 'drivers') diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 2b7531e0e84c..746daf37454c 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -163,3 +163,8 @@ config KEYSTONE_IRQ config MIPS_GIC bool select MIPS_CM + +config INGENIC_IRQ + bool + depends on MACH_INGENIC + default y diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 129cde1ff5a7..db014e8e12c9 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -48,3 +48,4 @@ obj-$(CONFIG_KEYSTONE_IRQ) += irq-keystone.o obj-$(CONFIG_MIPS_GIC) += irq-mips-gic.o obj-$(CONFIG_ARCH_MEDIATEK) += irq-mtk-sysirq.o obj-$(CONFIG_ARCH_DIGICOLOR) += irq-digicolor.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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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); -- cgit v1.2.1 From b066303fb3e72a902a1f94dc06636ce82c3a5577 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:35 +0100 Subject: clk: ingenic: add driver for Ingenic SoC CGU clocks This driver supports the CGU clocks for Ingenic SoCs. It is generic enough to be usable across at least the JZ4740 to the JZ4780, and will be made use of on such devices in subsequent commits. This patch by itself only adds the SoC-agnostic infrastructure that forms the bulk of the CGU driver for the aforementioned further commits to make use of. Signed-off-by: Paul Burton Co-authored-by: Paul Cercueil Cc: Lars-Peter Clausen Cc: Mike Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Patchwork: https://patchwork.linux-mips.org/patch/10150/ Signed-off-by: Ralf Baechle --- drivers/clk/Makefile | 1 + drivers/clk/ingenic/Makefile | 1 + drivers/clk/ingenic/cgu.c | 711 +++++++++++++++++++++++++++++++++++++++++++ drivers/clk/ingenic/cgu.h | 223 ++++++++++++++ 4 files changed, 936 insertions(+) create mode 100644 drivers/clk/ingenic/Makefile create mode 100644 drivers/clk/ingenic/cgu.c create mode 100644 drivers/clk/ingenic/cgu.h (limited to 'drivers') diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 3d00c25382c5..cc773271b919 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_ARCH_BERLIN) += berlin/ obj-$(CONFIG_ARCH_HI3xxx) += hisilicon/ obj-$(CONFIG_ARCH_HIP04) += hisilicon/ obj-$(CONFIG_ARCH_HIX5HD2) += hisilicon/ +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..5ac2fd990b42 --- /dev/null +++ b/drivers/clk/ingenic/Makefile @@ -0,0 +1 @@ +obj-y += 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * + * 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 +#include +#include + +/** + * 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__ */ -- cgit v1.2.1 From ff1930c6bdf031e72e101a8aa47d54e73a745f93 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:36 +0100 Subject: MIPS,clk: migrate JZ4740 to common clock framework Migrate the JZ4740 & the qi_lb60 board to use common clock framework via the new Ingenic SoC CGU driver. Note that the JZ4740-specific debugfs code is removed since common clock framework provides its own debug capabilities. Signed-off-by: Paul Burton Co-authored-by: Paul Cercueil Cc: Ian Campbell Cc: Kumar Gala Cc: Lars-Peter Clausen Cc: Mark Rutland Cc: Mike Turquette Cc: Pawel Moll Cc: Rob Herring Cc: Stephen Boyd Cc: devicetree@vger.kernel.org Cc: linux-clk@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: Fabian Frederick Cc: Deng-Cheng Zhu Cc: Linus Walleij Cc: Stephen Warren Cc: linux-kernel@vger.kernel.org Cc: Brian Norris Patchwork: https://patchwork.linux-mips.org/patch/10151/ Signed-off-by: Ralf Baechle --- drivers/clk/ingenic/Makefile | 1 + drivers/clk/ingenic/jz4740-cgu.c | 222 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 drivers/clk/ingenic/jz4740-cgu.c (limited to 'drivers') diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile index 5ac2fd990b42..e6db7da03a1c 100644 --- a/drivers/clk/ingenic/Makefile +++ b/drivers/clk/ingenic/Makefile @@ -1 +1,2 @@ obj-y += cgu.o +obj-$(CONFIG_MACH_JZ4740) += jz4740-cgu.o diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c new file mode 100644 index 000000000000..d5bb7a39219e --- /dev/null +++ b/drivers/clk/ingenic/jz4740-cgu.c @@ -0,0 +1,222 @@ +/* + * Ingenic JZ4740 SoC CGU driver + * + * Copyright (c) 2015 Imagination Technologies + * Author: Paul Burton + * + * 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 +#include +#include +#include +#include "cgu.h" + +/* CGU register offsets */ +#define CGU_REG_CPCCR 0x00 +#define CGU_REG_CPPCR 0x10 +#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) + +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); -- cgit v1.2.1 From 41dd641e9a1a7229383f9b342a57cb6720e7ea46 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:37 +0100 Subject: MIPS,clk: move jz4740_clock_set_wait_mode to jz4740-cgu The jz4740-cgu driver already has access to the CGU, so it makes sense to move the few remaining accesses to the CGU from arch/mips/jz4740 there too. Move jz4740_clock_set_wait_mode for such consistency. Signed-off-by: Paul Burton Cc: Lars-Peter Clausen Cc: Mike Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Patchwork: https://patchwork.linux-mips.org/patch/10153/ Signed-off-by: Ralf Baechle --- drivers/clk/ingenic/jz4740-cgu.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers') diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c index d5bb7a39219e..0209ed6d412a 100644 --- a/drivers/clk/ingenic/jz4740-cgu.c +++ b/drivers/clk/ingenic/jz4740-cgu.c @@ -19,10 +19,12 @@ #include #include #include +#include #include "cgu.h" /* CGU register offsets */ #define CGU_REG_CPCCR 0x00 +#define CGU_REG_LCR 0x04 #define CGU_REG_CPPCR 0x10 #define CGU_REG_SCR 0x24 #define CGU_REG_I2SCDR 0x60 @@ -42,6 +44,9 @@ #define PLLCTL_BYPASS (1 << 9) #define PLLCTL_ENABLE (1 << 8) +/* bits within the LCR register */ +#define LCR_SLEEP (1 << 0) + static struct ingenic_cgu *cgu; static const s8 pll_od_encoding[4] = { @@ -220,3 +225,20 @@ static void __init jz4740_cgu_init(struct device_node *np) 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); +} -- cgit v1.2.1 From ed286ca5d627619673b7dad27f48252498e3905c Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:38 +0100 Subject: MIPS, clk: move jz4740 UDC auto suspend functions to jz4740-cgu The jz4740-cgu driver already has access to the CGU, so it makes sense to move the few remaining accesses to the CGU from arch/mips/jz4740 there too. Move the jz4740_clock_udc_{dis,en}able_auto_suspend functions there for such consistency. Signed-off-by: Paul Burton Cc: Lars-Peter Clausen Cc: Mike Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Patchwork: https://patchwork.linux-mips.org/patch/10154/ Signed-off-by: Ralf Baechle --- drivers/clk/ingenic/jz4740-cgu.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers') diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c index 0209ed6d412a..0e692ed632e5 100644 --- a/drivers/clk/ingenic/jz4740-cgu.c +++ b/drivers/clk/ingenic/jz4740-cgu.c @@ -26,6 +26,7 @@ #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 @@ -47,6 +48,9 @@ /* 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] = { @@ -242,3 +246,21 @@ void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode) 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); -- cgit v1.2.1 From 50d893ff7218e2298d91c0a637845d195d72ebcc Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:39 +0100 Subject: MIPS, clk: move jz4740 clock suspend, resume functions to jz4740-cgu The jz4740-cgu driver already has access to the CGU, so it makes sense to move the few remaining accesses to the CGU from arch/mips/jz4740 there too. Move the jz4740_clock_{suspend,resume} functions there for such consistency. The arch/mips/jz4740/clock.c file now contains nothing more of use & so is removed. Signed-off-by: Paul Burton Cc: Lars-Peter Clausen Cc: Mike Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: Deng-Cheng Zhu Cc: linux-kernel@vger.kernel.org Patchwork: https://patchwork.linux-mips.org/patch/10158/ Signed-off-by: Ralf Baechle --- drivers/clk/ingenic/jz4740-cgu.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'drivers') diff --git a/drivers/clk/ingenic/jz4740-cgu.c b/drivers/clk/ingenic/jz4740-cgu.c index 0e692ed632e5..305a26c2a800 100644 --- a/drivers/clk/ingenic/jz4740-cgu.c +++ b/drivers/clk/ingenic/jz4740-cgu.c @@ -264,3 +264,40 @@ void jz4740_clock_udc_enable_auto_suspend(void) 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); +} -- cgit v1.2.1 From 29136ad58164cbd7114412c0b4cfb3acd4559044 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:40 +0100 Subject: clk: ingenic: add JZ4780 CGU support Add support for the clocks provided by the CGU in the Ingenic JZ4780 SoC, making use of the SoC-agnostic CGU code to do the heavy lifting. Signed-off-by: Paul Burton Co-authored-by: Paul Cercueil Cc: Lars-Peter Clausen Cc: Mike Turquette Cc: Stephen Boyd Cc: linux-clk@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Patchwork: https://patchwork.linux-mips.org/patch/10157/ Signed-off-by: Ralf Baechle --- drivers/clk/ingenic/Makefile | 1 + drivers/clk/ingenic/jz4780-cgu.c | 733 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 734 insertions(+) create mode 100644 drivers/clk/ingenic/jz4780-cgu.c (limited to 'drivers') diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile index e6db7da03a1c..cd47b0664c2b 100644 --- a/drivers/clk/ingenic/Makefile +++ b/drivers/clk/ingenic/Makefile @@ -1,2 +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/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 + * + * 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 +#include +#include +#include +#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); -- cgit v1.2.1 From 0cf985f487be8e24237faaa8c96cbcf78382a498 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Sun, 24 May 2015 16:11:44 +0100 Subject: serial: 8250_ingenic: support for Ingenic SoC UARTs Introduce a driver suitable for use with the UARTs present in Ingenic SoCs such as the JZ4740 & JZ4780. These are described as being ns16550 compatible but aren't quite - they require the setting of an extra bit in the FCR register to enable the UART module. The serial_out implementation is the same as that in arch/mips/jz4740/serial.c - which will shortly be removed. Signed-off-by: Paul Burton Cc: Greg Kroah-Hartman Cc: Jiri Slaby Cc: Lars-Peter Clausen Cc: linux-serial@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: Sebastian Andrzej Siewior Cc: Peter Hurley Cc: Alan Cox Cc: linux-kernel@vger.kernel.org Cc: Matthias Brugger Cc: Ricardo Ribalda Delgado Cc: Tony Lindgren Cc: John Crispin Patchwork: https://patchwork.linux-mips.org/patch/10159/ Signed-off-by: Ralf Baechle --- drivers/tty/serial/8250/8250_ingenic.c | 266 +++++++++++++++++++++++++++++++++ drivers/tty/serial/8250/Kconfig | 9 ++ drivers/tty/serial/8250/Makefile | 3 + 3 files changed, 278 insertions(+) create mode 100644 drivers/tty/serial/8250/8250_ingenic.c (limited to 'drivers') 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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 c35070356528..43330ea95cfd 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -342,3 +342,12 @@ config SERIAL_8250_MT6577 help If you have a Mediatek based board and want to use the serial port, 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 31e7cdc6865c..98fba26e194a 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -23,3 +23,6 @@ obj-$(CONFIG_SERIAL_8250_EM) += 8250_em.o obj-$(CONFIG_SERIAL_8250_OMAP) += 8250_omap.o obj-$(CONFIG_SERIAL_8250_FINTEK) += 8250_fintek.o obj-$(CONFIG_SERIAL_8250_MT6577) += 8250_mtk.o +obj-$(CONFIG_SERIAL_8250_INGENIC) += 8250_ingenic.o + +CFLAGS_8250_ingenic.o += -I$(srctree)/scripts/dtc/libfdt -- cgit v1.2.1 From 57991ebaf91b1eda2abb586618aea6806fa86724 Mon Sep 17 00:00:00 2001 From: Andrew Bresticker Date: Tue, 7 Apr 2015 15:04:17 -0700 Subject: PHY: Add driver for Pistachio USB2.0 PHY Add a driver for the USB2.0 PHY found on the IMG Pistachio SoC. Signed-off-by: Andrew Bresticker Cc: Kishon Vijay Abraham I Cc: devicetree@vger.kernel.org Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Cc: James Hartley Cc: Damien Horsley Patchwork: https://patchwork.linux-mips.org/patch/9728/ Signed-off-by: Ralf Baechle --- drivers/phy/Kconfig | 7 ++ drivers/phy/Makefile | 1 + drivers/phy/phy-pistachio-usb.c | 206 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 drivers/phy/phy-pistachio-usb.c (limited to 'drivers') diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index fc9b9f0ea91e..e8402600fe9f 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -247,6 +247,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 f12625178780..75a37dc952f5 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -40,3 +40,4 @@ obj-$(CONFIG_PHY_STIH41X_USB) += phy-stih41x-usb.o obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o 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_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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +MODULE_DESCRIPTION("IMG Pistachio USB2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.1 From 30ad29bb48881ee11f5daf30c6fc50292ae08c57 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 21 Apr 2015 10:00:35 +0800 Subject: MIPS: Loongson: Naming style cleanup and rework Currently, code of Loongson-2/3 is under loongson directory and code of Loongson-1 is under loongson1 directory. Besides, there are Kconfig options such as MACH_LOONGSON and MACH_LOONGSON1. This naming style is very ugly and confusing. Since Loongson-2/3 are both 64-bit general- purpose CPU while Loongson-1 is 32-bit SoC, we rename both file names and Kconfig symbols from loongson/loongson1 to loongson64/loongson32. [ralf@linux-mips.org: Resolve a number of simple conflicts.] Signed-off-by: Huacai Chen Cc: Steven J. Hill Cc: linux-mips@linux-mips.org Cc: Fuxin Zhang Cc: Zhangjin Wu Cc: Kelvin Cheung Patchwork: https://patchwork.linux-mips.org/patch/9790/ Signed-off-by: Ralf Baechle --- drivers/clk/Makefile | 2 +- drivers/cpufreq/ls1x-cpufreq.c | 4 ++-- drivers/rtc/Kconfig | 2 +- drivers/rtc/rtc-ls1x.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index cc773271b919..78029bd2444b 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 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 #include -#include -#include +#include +#include static struct { struct device *dev; diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 0fe4ad8826b2..354b908a03d8 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1484,7 +1484,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 #include #include -#include +#include #define LS1X_RTC_REG_OFFSET (LS1X_RTC_BASE + 0x20) #define LS1X_RTC_REGS(x) \ -- cgit v1.2.1 From df115f3ee9ea703e1209392cd08f8d6783244721 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Mon, 25 May 2015 20:27:29 +0100 Subject: MIPS: Octeon: Set OHCI and EHCI MMIO byte order to match CPU The Octeon OHCI is now supported by the ohci-platform driver, and USB_OCTEON_OHCI is marked as deprecated. However, it is currently still necessary to enable it in order to select USB_OHCI_BIG_ENDIAN_MMIO. Make CPU_CAVIUM_OCTEON select that as well, so that USB_OCTEON_OHCI is really obsolete. The old ohci-octeon and ehci-octeon drivers also only enabled big-endian MMIO in case the CPU was big-endian. Make the selections of USB_EHCI_BIG_ENDIAN_MMIO and USB_OHCI_BIG_ENDIAN_MMIO conditional, to match this. Fixes: 2193dda5eec6 ("USB: host: Remove ehci-octeon and ohci-octeon drivers") Signed-off-by: Ben Hutchings Cc: Alan Stern Cc: linux-mips@linux-mips.org Cc: David Daney Cc: Chandrakala Chavva Cc: Paul Martin Patchwork: https://patchwork.linux-mips.org/patch/10178/ Signed-off-by: Ralf Baechle --- drivers/usb/host/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 197a6a3e613b..cff1c9134e6c 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 -- cgit v1.2.1 From f6e734a8c162297953d7bfc0f3f6bf4f8c33d72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= Date: Wed, 10 Jun 2015 23:05:08 +0200 Subject: MIPS: BCM47xx: Move NVRAM driver to the drivers/firmware/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After Broadcom switched from MIPS to ARM for their home routers we need to have NVRAM driver in some common place (not arch/mips/). As explained in Kconfig, this driver is responsible for parsing SoC configuration data that is passed to the kernel in flash from the bootloader firmware called "CFE". We were thinking about putting it in bus directory, however there are two possible buses for MIPS: drivers/ssb/ and drivers/bcma/. So this won't fit there and this is why I would like to move this driver to the drivers/firmware/. Signed-off-by: Rafał Miłecki Reviewed-by: Paul Walmsley Cc: linux-mips@linux-mips.org Cc: Hauke Mehrtens Cc: Seiji Aguchi Cc: Greg Kroah-Hartman Cc: Ard Biesheuvel Cc: Mike Waychison Cc: Roy Franz Cc: Matt Fleming Cc: Linus Torvalds Patchwork: https://patchwork.linux-mips.org/patch/10544/ Signed-off-by: Ralf Baechle --- drivers/firmware/Kconfig | 1 + drivers/firmware/Makefile | 1 + drivers/firmware/broadcom/Kconfig | 11 ++ drivers/firmware/broadcom/Makefile | 1 + drivers/firmware/broadcom/bcm47xx_nvram.c | 248 ++++++++++++++++++++++++++++++ 5 files changed, 262 insertions(+) create mode 100644 drivers/firmware/broadcom/Kconfig create mode 100644 drivers/firmware/broadcom/Makefile create mode 100644 drivers/firmware/broadcom/bcm47xx_nvram.c (limited to 'drivers') 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 3fdd3912709a..210c6e0550d3 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o obj-$(CONFIG_QCOM_SCM) += qcom_scm.o CFLAGS_qcom_scm.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 + * Copyright (C) 2010-2012 Hauke Mehrtens + * + * 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 +#include +#include +#include +#include +#include +#include + +#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"); -- cgit v1.2.1