summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-05-19 10:02:26 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2016-05-19 10:02:26 -0700
commit07b75260ebc2c789724c594d7eaf0194fa47b3be (patch)
treed88b770bca479789e688d95e50aacd5d09b59b21 /drivers
parent0efacbbaee1e94e9942da0912f5b46ffd45a74bd (diff)
parent6e4ad1b413604b9130bdbe532aafdbd47ff5318e (diff)
Merge branch 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus
Pull MIPS updates from Ralf Baechle: "This is the main pull request for MIPS for 4.7. Here's the summary of the changes: - ATH79: Support for DTB passuing using the UHI boot protocol - ATH79: Remove support for builtin DTB. - ATH79: Add zboot debug serial support. - ATH79: Add initial support for Dragino MS14 (Dragine 2), Onion Omega and DPT-Module. - ATH79: Update devicetree clock support for AR9132 and AR9331. - ATH79: Cleanup the DT code. - ATH79: Support newer SOCs in ath79_ddr_ctrl_init. - ATH79: Fix regression in PCI window initialization. - BCM47xx: Move SPROM driver to drivers/firmware/ - BCM63xx: Enable partition parser in defconfig. - BMIPS: BMIPS5000 has I cache filing from D cache - BMIPS: BMIPS: Add cpu-feature-overrides.h - BMIPS: Add Whirlwind support - BMIPS: Adjust mips-hpt-frequency for BCM7435 - BMIPS: Remove maxcpus from BCM97435SVMB DTS - BMIPS: Add missing 7038 L1 register cells to BCM7435 - BMIPS: Various tweaks to initialization code. - BMIPS: Enable partition parser in defconfig. - BMIPS: Cache tweaks. - BMIPS: Add UART, I2C and SATA devices to DT. - BMIPS: Add BCM6358 and BCM63268support - BMIPS: Add device tree example for BCM6358. - BMIPS: Improve Improve BCM6328 and BCM6368 device trees - Lantiq: Add support for device tree file from boot loader - Lantiq: Allow build with no built-in DT. - Loongson 3: Reserve 32MB for RS780E integrated GPU. - Loongson 3: Fix build error after ld-version.sh modification - Loongson 3: Move chipset ACPI code from drivers to arch. - Loongson 3: Speedup irq processing. - Loongson 3: Add basic Loongson 3A support. - Loongson 3: Set cache flush handlers to nop. - Loongson 3: Invalidate special TLBs when needed. - Loongson 3: Fast TLB refill handler. - MT7620: Fallback strategy for invalid syscfg0. - Netlogic: Fix CP0_EBASE redefinition warnings - Octeon: Initialization fixes - Octeon: Add DTS files for the D-Link DSR-1000N and EdgeRouter Lite - Octeon: Enable add Octeon-drivers in cavium_octeon_defconfig - Octeon: Correctly handle endian-swapped initramfs images. - Octeon: Support CN73xx, CN75xx and CN78xx. - Octeon: Remove dead code from cvmx-sysinfo. - Octeon: Extend number of supported CPUs past 32. - Octeon: Remove some code limiting NR_IRQS to 255. - Octeon: Simplify octeon_irq_ciu_gpio_set_type. - Octeon: Mark some functions __init in smp.c - Octeon: Octeon: Add Octeon III CN7xxx interface detection - PIC32: Add serial driver and bindings for it. - PIC32: Add PIC32 deadman timer driver and bindings. - PIC32: Add PIC32 clock timer driver and bindings. - Pistachio: Determine SoC revision during boot - Sibyte: Fix Kconfig dependencies of SIBYTE_BUS_WATCHER. - Sibyte: Strip redundant comments from bcm1480_regs.h. - Panic immediately if panic_on_oops is set. - module: fix incorrect IS_ERR_VALUE macro usage. - module: Make consistent use of pr_* - Remove no longer needed work_on_cpu() call. - Remove CONFIG_IPV6_PRIVACY from defconfigs. - Fix registers of non-crashing CPUs in dumps. - Handle MIPSisms in new vmcore_elf32_check_arch. - Select CONFIG_HANDLE_DOMAIN_IRQ and make it work. - Allow RIXI to be used on non-R2 or R6 cores. - Reserve nosave data for hibernation - Fix siginfo.h to use strict POSIX types. - Don't unwind user mode with EVA. - Fix watchpoint restoration - Ptrace watchpoints for R6. - Sync icache when it fills from dcache - I6400 I-cache fills from dcache. - Various MSA fixes. - Cleanup MIPS_CPU_* definitions. - Signal: Move generic copy_siginfo to signal.h - Signal: Fix uapi include in exported asm/siginfo.h - Timer fixes for sake of KVM. - XPA TLB refill fixes. - Treat perf counter feature - Update John Crispin's email address - Add PIC32 watchdog and bindings. - Handle R10000 LL/SC bug in set_pte() - cpufreq: Various fixes for Longson1. - R6: Fix R2 emulation. - mathemu: Cosmetic fix to ADDIUPC emulation, plenty of other small fixes - ELF: ABI and FP fixes. - Allow for relocatable kernel and use that to support KASLR. - Fix CPC_BASE_ADDR mask - Plenty fo smp-cps, CM, R6 and M6250 fixes. - Make reset_control_ops const. - Fix kernel command line handling of leading whitespace. - Cleanups to cache handling. - Add brcm, bcm6345-l1-intc device tree bindings. - Use generic clkdev.h header - Remove CLK_IS_ROOT usage. - Misc small cleanups. - CM: Fix compilation error when !MIPS_CM - oprofile: Fix a preemption issue - Detect DSP ASE v3 support:1" * 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus: (275 commits) MIPS: pic32mzda: fix getting timer clock rate. MIPS: ath79: fix regression in PCI window initialization MIPS: ath79: make ath79_ddr_ctrl_init() compatible for newer SoCs MIPS: Fix VZ probe gas errors with binutils <2.24 MIPS: perf: Fix I6400 event numbers MIPS: DEC: Export `ioasic_ssr_lock' to modules MIPS: MSA: Fix a link error on `_init_msa_upper' with older GCC MIPS: CM: Fix compilation error when !MIPS_CM MIPS: Fix genvdso error on rebuild USB: ohci-jz4740: Remove obsolete driver MIPS: JZ4740: Probe OHCI platform device via DT MIPS: JZ4740: Qi LB60: Remove support for AVT2 variant MIPS: pistachio: Determine SoC revision during boot MIPS: BMIPS: Adjust mips-hpt-frequency for BCM7435 mips: mt7620: fallback to SDRAM when syscfg0 does not have a valid value for the memory type MIPS: Prevent "restoration" of MSA context in non-MSA kernels MIPS: cevt-r4k: Dynamically calculate min_delta_ns MIPS: malta-time: Take seconds into account MIPS: malta-time: Start GIC count before syncing to RTC MIPS: Force CPUs to lose FP context during mode switches ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/bus/brcmstb_gisb.c30
-rw-r--r--drivers/bus/mips_cdmm.c12
-rw-r--r--drivers/clk/Kconfig3
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/microchip/Makefile2
-rw-r--r--drivers/clk/microchip/clk-core.c1031
-rw-r--r--drivers/clk/microchip/clk-core.h84
-rw-r--r--drivers/clk/microchip/clk-pic32mzda.c275
-rw-r--r--drivers/cpufreq/Makefile2
-rw-r--r--drivers/cpufreq/loongson1-cpufreq.c (renamed from drivers/cpufreq/ls1x-cpufreq.c)114
-rw-r--r--drivers/firmware/broadcom/Kconfig11
-rw-r--r--drivers/firmware/broadcom/Makefile1
-rw-r--r--drivers/firmware/broadcom/bcm47xx_sprom.c737
-rw-r--r--drivers/irqchip/irq-mips-gic.c22
-rw-r--r--drivers/platform/mips/Kconfig4
-rw-r--r--drivers/platform/mips/Makefile1
-rw-r--r--drivers/platform/mips/acpi_init.c150
-rw-r--r--drivers/platform/mips/cpu_hwmon.c4
-rw-r--r--drivers/tty/serial/Kconfig21
-rw-r--r--drivers/tty/serial/Makefile1
-rw-r--r--drivers/tty/serial/pic32_uart.c960
-rw-r--r--drivers/tty/serial/pic32_uart.h126
-rw-r--r--drivers/usb/host/ohci-hcd.c5
-rw-r--r--drivers/usb/host/ohci-jz4740.c245
-rw-r--r--drivers/watchdog/Kconfig26
-rw-r--r--drivers/watchdog/Makefile2
-rw-r--r--drivers/watchdog/pic32-dmt.c257
-rw-r--r--drivers/watchdog/pic32-wdt.c263
28 files changed, 3915 insertions, 475 deletions
diff --git a/drivers/bus/brcmstb_gisb.c b/drivers/bus/brcmstb_gisb.c
index f364fa4d24eb..72fe0a5a8bf3 100644
--- a/drivers/bus/brcmstb_gisb.c
+++ b/drivers/bus/brcmstb_gisb.c
@@ -30,6 +30,10 @@
#include <asm/signal.h>
#endif
+#ifdef CONFIG_MIPS
+#include <asm/traps.h>
+#endif
+
#define ARB_ERR_CAP_CLEAR (1 << 0)
#define ARB_ERR_CAP_STATUS_TIMEOUT (1 << 12)
#define ARB_ERR_CAP_STATUS_TEA (1 << 11)
@@ -238,6 +242,29 @@ static int brcmstb_bus_error_handler(unsigned long addr, unsigned int fsr,
}
#endif
+#ifdef CONFIG_MIPS
+static int brcmstb_bus_error_handler(struct pt_regs *regs, int is_fixup)
+{
+ int ret = 0;
+ struct brcmstb_gisb_arb_device *gdev;
+ u32 cap_status;
+
+ list_for_each_entry(gdev, &brcmstb_gisb_arb_device_list, next) {
+ cap_status = gisb_read(gdev, ARB_ERR_CAP_STATUS);
+
+ /* Invalid captured address, bail out */
+ if (!(cap_status & ARB_ERR_CAP_STATUS_VALID)) {
+ is_fixup = 1;
+ goto out;
+ }
+
+ ret |= brcmstb_gisb_arb_decode_addr(gdev, "bus error");
+ }
+out:
+ return is_fixup ? MIPS_BE_FIXUP : MIPS_BE_FATAL;
+}
+#endif
+
static irqreturn_t brcmstb_gisb_timeout_handler(int irq, void *dev_id)
{
brcmstb_gisb_arb_decode_addr(dev_id, "timeout");
@@ -355,6 +382,9 @@ static int __init brcmstb_gisb_arb_probe(struct platform_device *pdev)
hook_fault_code(22, brcmstb_bus_error_handler, SIGBUS, 0,
"imprecise external abort");
#endif
+#ifdef CONFIG_MIPS
+ board_be_handler = brcmstb_bus_error_handler;
+#endif
dev_info(&pdev->dev, "registered mem: %p, irqs: %d, %d\n",
gdev->base, timeout_irq, tea_irq);
diff --git a/drivers/bus/mips_cdmm.c b/drivers/bus/mips_cdmm.c
index 1c543effe062..cad49bc38b3e 100644
--- a/drivers/bus/mips_cdmm.c
+++ b/drivers/bus/mips_cdmm.c
@@ -599,8 +599,8 @@ BUILD_PERDEV_HELPER(cpu_up) /* int mips_cdmm_cpu_up_helper(...) */
* mips_cdmm_bus_down() - Tear down the CDMM bus.
* @data: Pointer to unsigned int CPU number.
*
- * This work_on_cpu callback function is executed on a given CPU to call the
- * CDMM driver cpu_down callback for all devices on that CPU.
+ * This function is executed on the hotplugged CPU and calls the CDMM
+ * driver cpu_down callback for all devices on that CPU.
*/
static long mips_cdmm_bus_down(void *data)
{
@@ -630,7 +630,9 @@ static long mips_cdmm_bus_down(void *data)
* CDMM devices on that CPU, or to call the CDMM driver cpu_up callback for all
* devices already discovered on that CPU.
*
- * It is used during initialisation and when CPUs are brought online.
+ * It is used as work_on_cpu callback function during
+ * initialisation. When CPUs are brought online the function is
+ * invoked directly on the hotplugged CPU.
*/
static long mips_cdmm_bus_up(void *data)
{
@@ -677,10 +679,10 @@ static int mips_cdmm_cpu_notify(struct notifier_block *nb,
switch (action & ~CPU_TASKS_FROZEN) {
case CPU_ONLINE:
case CPU_DOWN_FAILED:
- work_on_cpu(cpu, mips_cdmm_bus_up, &cpu);
+ mips_cdmm_bus_up(&cpu);
break;
case CPU_DOWN_PREPARE:
- work_on_cpu(cpu, mips_cdmm_bus_down, &cpu);
+ mips_cdmm_bus_down(&cpu);
break;
default:
return NOTIFY_DONE;
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index c45554957499..90518cd7fc9c 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -197,6 +197,9 @@ config COMMON_CLK_PXA
---help---
Support for the Marvell PXA SoC.
+config COMMON_CLK_PIC32
+ def_bool COMMON_CLK && MACH_PIC32
+
source "drivers/clk/bcm/Kconfig"
source "drivers/clk/hisilicon/Kconfig"
source "drivers/clk/mvebu/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 46869d696e4d..18e64bbeeaf4 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_ARCH_MXC) += imx/
obj-$(CONFIG_MACH_INGENIC) += ingenic/
obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/
obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/
+obj-$(CONFIG_MACH_PIC32) += microchip/
ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_ARCH_MMP) += mmp/
endif
diff --git a/drivers/clk/microchip/Makefile b/drivers/clk/microchip/Makefile
new file mode 100644
index 000000000000..2152f418106a
--- /dev/null
+++ b/drivers/clk/microchip/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_COMMON_CLK_PIC32) += clk-core.o
+obj-$(CONFIG_PIC32MZDA) += clk-pic32mzda.o
diff --git a/drivers/clk/microchip/clk-core.c b/drivers/clk/microchip/clk-core.c
new file mode 100644
index 000000000000..ca85cea17839
--- /dev/null
+++ b/drivers/clk/microchip/clk-core.c
@@ -0,0 +1,1031 @@
+/*
+ * Purna Chandra Mandal,<purna.mandal@microchip.com>
+ * Copyright (C) 2015 Microchip Technology Inc. All rights reserved.
+ *
+ * This program is free software; you can distribute it and/or modify it
+ * under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <asm/mach-pic32/pic32.h>
+#include <asm/traps.h>
+
+#include "clk-core.h"
+
+/* OSCCON Reg fields */
+#define OSC_CUR_MASK 0x07
+#define OSC_CUR_SHIFT 12
+#define OSC_NEW_MASK 0x07
+#define OSC_NEW_SHIFT 8
+#define OSC_SWEN BIT(0)
+
+/* SPLLCON Reg fields */
+#define PLL_RANGE_MASK 0x07
+#define PLL_RANGE_SHIFT 0
+#define PLL_ICLK_MASK 0x01
+#define PLL_ICLK_SHIFT 7
+#define PLL_IDIV_MASK 0x07
+#define PLL_IDIV_SHIFT 8
+#define PLL_ODIV_MASK 0x07
+#define PLL_ODIV_SHIFT 24
+#define PLL_MULT_MASK 0x7F
+#define PLL_MULT_SHIFT 16
+#define PLL_MULT_MAX 128
+#define PLL_ODIV_MIN 1
+#define PLL_ODIV_MAX 5
+
+/* Peripheral Bus Clock Reg Fields */
+#define PB_DIV_MASK 0x7f
+#define PB_DIV_SHIFT 0
+#define PB_DIV_READY BIT(11)
+#define PB_DIV_ENABLE BIT(15)
+#define PB_DIV_MAX 128
+#define PB_DIV_MIN 0
+
+/* Reference Oscillator Control Reg fields */
+#define REFO_SEL_MASK 0x0f
+#define REFO_SEL_SHIFT 0
+#define REFO_ACTIVE BIT(8)
+#define REFO_DIVSW_EN BIT(9)
+#define REFO_OE BIT(12)
+#define REFO_ON BIT(15)
+#define REFO_DIV_SHIFT 16
+#define REFO_DIV_MASK 0x7fff
+
+/* Reference Oscillator Trim Register Fields */
+#define REFO_TRIM_REG 0x10
+#define REFO_TRIM_MASK 0x1ff
+#define REFO_TRIM_SHIFT 23
+#define REFO_TRIM_MAX 511
+
+/* Mux Slew Control Register fields */
+#define SLEW_BUSY BIT(0)
+#define SLEW_DOWNEN BIT(1)
+#define SLEW_UPEN BIT(2)
+#define SLEW_DIV 0x07
+#define SLEW_DIV_SHIFT 8
+#define SLEW_SYSDIV 0x0f
+#define SLEW_SYSDIV_SHIFT 20
+
+/* Clock Poll Timeout */
+#define LOCK_TIMEOUT_US USEC_PER_MSEC
+
+/* SoC specific clock needed during SPLL clock rate switch */
+static struct clk_hw *pic32_sclk_hw;
+
+/* add instruction pipeline delay while CPU clock is in-transition. */
+#define cpu_nop5() \
+do { \
+ __asm__ __volatile__("nop"); \
+ __asm__ __volatile__("nop"); \
+ __asm__ __volatile__("nop"); \
+ __asm__ __volatile__("nop"); \
+ __asm__ __volatile__("nop"); \
+} while (0)
+
+/* Perpheral bus clocks */
+struct pic32_periph_clk {
+ struct clk_hw hw;
+ void __iomem *ctrl_reg;
+ struct pic32_clk_common *core;
+};
+
+#define clkhw_to_pbclk(_hw) container_of(_hw, struct pic32_periph_clk, hw)
+
+static int pbclk_is_enabled(struct clk_hw *hw)
+{
+ struct pic32_periph_clk *pb = clkhw_to_pbclk(hw);
+
+ return readl(pb->ctrl_reg) & PB_DIV_ENABLE;
+}
+
+static int pbclk_enable(struct clk_hw *hw)
+{
+ struct pic32_periph_clk *pb = clkhw_to_pbclk(hw);
+
+ writel(PB_DIV_ENABLE, PIC32_SET(pb->ctrl_reg));
+ return 0;
+}
+
+static void pbclk_disable(struct clk_hw *hw)
+{
+ struct pic32_periph_clk *pb = clkhw_to_pbclk(hw);
+
+ writel(PB_DIV_ENABLE, PIC32_CLR(pb->ctrl_reg));
+}
+
+static unsigned long calc_best_divided_rate(unsigned long rate,
+ unsigned long parent_rate,
+ u32 divider_max,
+ u32 divider_min)
+{
+ unsigned long divided_rate, divided_rate_down, best_rate;
+ unsigned long div, div_up;
+
+ /* eq. clk_rate = parent_rate / divider.
+ *
+ * Find best divider to produce closest of target divided rate.
+ */
+ div = parent_rate / rate;
+ div = clamp_val(div, divider_min, divider_max);
+ div_up = clamp_val(div + 1, divider_min, divider_max);
+
+ divided_rate = parent_rate / div;
+ divided_rate_down = parent_rate / div_up;
+ if (abs(rate - divided_rate_down) < abs(rate - divided_rate))
+ best_rate = divided_rate_down;
+ else
+ best_rate = divided_rate;
+
+ return best_rate;
+}
+
+static inline u32 pbclk_read_pbdiv(struct pic32_periph_clk *pb)
+{
+ return ((readl(pb->ctrl_reg) >> PB_DIV_SHIFT) & PB_DIV_MASK) + 1;
+}
+
+static unsigned long pbclk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct pic32_periph_clk *pb = clkhw_to_pbclk(hw);
+
+ return parent_rate / pbclk_read_pbdiv(pb);
+}
+
+static long pbclk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ return calc_best_divided_rate(rate, *parent_rate,
+ PB_DIV_MAX, PB_DIV_MIN);
+}
+
+static int pbclk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct pic32_periph_clk *pb = clkhw_to_pbclk(hw);
+ unsigned long flags;
+ u32 v, div;
+ int err;
+
+ /* check & wait for DIV_READY */
+ err = readl_poll_timeout(pb->ctrl_reg, v, v & PB_DIV_READY,
+ 1, LOCK_TIMEOUT_US);
+ if (err)
+ return err;
+
+ /* calculate clkdiv and best rate */
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+
+ spin_lock_irqsave(&pb->core->reg_lock, flags);
+
+ /* apply new div */
+ v = readl(pb->ctrl_reg);
+ v &= ~PB_DIV_MASK;
+ v |= (div - 1);
+
+ pic32_syskey_unlock();
+
+ writel(v, pb->ctrl_reg);
+
+ spin_unlock_irqrestore(&pb->core->reg_lock, flags);
+
+ /* wait again, for pbdivready */
+ err = readl_poll_timeout_atomic(pb->ctrl_reg, v, v & PB_DIV_READY,
+ 1, LOCK_TIMEOUT_US);
+ if (err)
+ return err;
+
+ /* confirm that new div is applied correctly */
+ return (pbclk_read_pbdiv(pb) == div) ? 0 : -EBUSY;
+}
+
+const struct clk_ops pic32_pbclk_ops = {
+ .enable = pbclk_enable,
+ .disable = pbclk_disable,
+ .is_enabled = pbclk_is_enabled,
+ .recalc_rate = pbclk_recalc_rate,
+ .round_rate = pbclk_round_rate,
+ .set_rate = pbclk_set_rate,
+};
+
+struct clk *pic32_periph_clk_register(const struct pic32_periph_clk_data *desc,
+ struct pic32_clk_common *core)
+{
+ struct pic32_periph_clk *pbclk;
+ struct clk *clk;
+
+ pbclk = devm_kzalloc(core->dev, sizeof(*pbclk), GFP_KERNEL);
+ if (!pbclk)
+ return ERR_PTR(-ENOMEM);
+
+ pbclk->hw.init = &desc->init_data;
+ pbclk->core = core;
+ pbclk->ctrl_reg = desc->ctrl_reg + core->iobase;
+
+ clk = devm_clk_register(core->dev, &pbclk->hw);
+ if (IS_ERR(clk)) {
+ dev_err(core->dev, "%s: clk_register() failed\n", __func__);
+ devm_kfree(core->dev, pbclk);
+ }
+
+ return clk;
+}
+
+/* Reference oscillator operations */
+struct pic32_ref_osc {
+ struct clk_hw hw;
+ void __iomem *ctrl_reg;
+ const u32 *parent_map;
+ struct pic32_clk_common *core;
+};
+
+#define clkhw_to_refosc(_hw) container_of(_hw, struct pic32_ref_osc, hw)
+
+static int roclk_is_enabled(struct clk_hw *hw)
+{
+ struct pic32_ref_osc *refo = clkhw_to_refosc(hw);
+
+ return readl(refo->ctrl_reg) & REFO_ON;
+}
+
+static int roclk_enable(struct clk_hw *hw)
+{
+ struct pic32_ref_osc *refo = clkhw_to_refosc(hw);
+
+ writel(REFO_ON | REFO_OE, PIC32_SET(refo->ctrl_reg));
+ return 0;
+}
+
+static void roclk_disable(struct clk_hw *hw)
+{
+ struct pic32_ref_osc *refo = clkhw_to_refosc(hw);
+
+ writel(REFO_ON | REFO_OE, PIC32_CLR(refo->ctrl_reg));
+}
+
+static void roclk_init(struct clk_hw *hw)
+{
+ /* initialize clock in disabled state */
+ roclk_disable(hw);
+}
+
+static u8 roclk_get_parent(struct clk_hw *hw)
+{
+ struct pic32_ref_osc *refo = clkhw_to_refosc(hw);
+ u32 v, i;
+
+ v = (readl(refo->ctrl_reg) >> REFO_SEL_SHIFT) & REFO_SEL_MASK;
+
+ if (!refo->parent_map)
+ return v;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++)
+ if (refo->parent_map[i] == v)
+ return i;
+
+ return -EINVAL;
+}
+
+static unsigned long roclk_calc_rate(unsigned long parent_rate,
+ u32 rodiv, u32 rotrim)
+{
+ u64 rate64;
+
+ /* fout = fin / [2 * {div + (trim / 512)}]
+ * = fin * 512 / [1024 * div + 2 * trim]
+ * = fin * 256 / (512 * div + trim)
+ * = (fin << 8) / ((div << 9) + trim)
+ */
+ if (rotrim) {
+ rodiv = (rodiv << 9) + rotrim;
+ rate64 = parent_rate;
+ rate64 <<= 8;
+ do_div(rate64, rodiv);
+ } else if (rodiv) {
+ rate64 = parent_rate / (rodiv << 1);
+ } else {
+ rate64 = parent_rate;
+ }
+ return rate64;
+}
+
+static void roclk_calc_div_trim(unsigned long rate,
+ unsigned long parent_rate,
+ u32 *rodiv_p, u32 *rotrim_p)
+{
+ u32 div, rotrim, rodiv;
+ u64 frac;
+
+ /* Find integer approximation of floating-point arithmetic.
+ * fout = fin / [2 * {rodiv + (rotrim / 512)}] ... (1)
+ * i.e. fout = fin / 2 * DIV
+ * whereas DIV = rodiv + (rotrim / 512)
+ *
+ * Since kernel does not perform floating-point arithmatic so
+ * (rotrim/512) will be zero. And DIV & rodiv will result same.
+ *
+ * ie. fout = (fin * 256) / [(512 * rodiv) + rotrim] ... from (1)
+ * ie. rotrim = ((fin * 256) / fout) - (512 * DIV)
+ */
+ if (parent_rate <= rate) {
+ div = 0;
+ frac = 0;
+ rodiv = 0;
+ rotrim = 0;
+ } else {
+ div = parent_rate / (rate << 1);
+ frac = parent_rate;
+ frac <<= 8;
+ do_div(frac, rate);
+ frac -= (u64)(div << 9);
+
+ rodiv = (div > REFO_DIV_MASK) ? REFO_DIV_MASK : div;
+ rotrim = (frac >= REFO_TRIM_MAX) ? REFO_TRIM_MAX : frac;
+ }
+
+ if (rodiv_p)
+ *rodiv_p = rodiv;
+
+ if (rotrim_p)
+ *rotrim_p = rotrim;
+}
+
+static unsigned long roclk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct pic32_ref_osc *refo = clkhw_to_refosc(hw);
+ u32 v, rodiv, rotrim;
+
+ /* get rodiv */
+ v = readl(refo->ctrl_reg);
+ rodiv = (v >> REFO_DIV_SHIFT) & REFO_DIV_MASK;
+
+ /* get trim */
+ v = readl(refo->ctrl_reg + REFO_TRIM_REG);
+ rotrim = (v >> REFO_TRIM_SHIFT) & REFO_TRIM_MASK;
+
+ return roclk_calc_rate(parent_rate, rodiv, rotrim);
+}
+
+static long roclk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ u32 rotrim, rodiv;
+
+ /* calculate dividers for new rate */
+ roclk_calc_div_trim(rate, *parent_rate, &rodiv, &rotrim);
+
+ /* caclulate new rate (rounding) based on new rodiv & rotrim */
+ return roclk_calc_rate(*parent_rate, rodiv, rotrim);
+}
+
+static int roclk_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_hw *parent_clk, *best_parent_clk = NULL;
+ unsigned int i, delta, best_delta = -1;
+ unsigned long parent_rate, best_parent_rate = 0;
+ unsigned long best = 0, nearest_rate;
+
+ /* find a parent which can generate nearest clkrate >= rate */
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ /* get parent */
+ parent_clk = clk_hw_get_parent_by_index(hw, i);
+ if (!parent_clk)
+ continue;
+
+ /* skip if parent runs slower than target rate */
+ parent_rate = clk_hw_get_rate(parent_clk);
+ if (req->rate > parent_rate)
+ continue;
+
+ nearest_rate = roclk_round_rate(hw, req->rate, &parent_rate);
+ delta = abs(nearest_rate - req->rate);
+ if ((nearest_rate >= req->rate) && (delta < best_delta)) {
+ best_parent_clk = parent_clk;
+ best_parent_rate = parent_rate;
+ best = nearest_rate;
+ best_delta = delta;
+
+ if (delta == 0)
+ break;
+ }
+ }
+
+ /* if no match found, retain old rate */
+ if (!best_parent_clk) {
+ pr_err("%s:%s, no parent found for rate %lu.\n",
+ __func__, clk_hw_get_name(hw), req->rate);
+ return clk_hw_get_rate(hw);
+ }
+
+ pr_debug("%s,rate %lu, best_parent(%s, %lu), best %lu, delta %d\n",
+ clk_hw_get_name(hw), req->rate,
+ clk_hw_get_name(best_parent_clk), best_parent_rate,
+ best, best_delta);
+
+ if (req->best_parent_rate)
+ req->best_parent_rate = best_parent_rate;
+
+ if (req->best_parent_hw)
+ req->best_parent_hw = best_parent_clk;
+
+ return best;
+}
+
+static int roclk_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct pic32_ref_osc *refo = clkhw_to_refosc(hw);
+ unsigned long flags;
+ u32 v;
+ int err;
+
+ if (refo->parent_map)
+ index = refo->parent_map[index];
+
+ /* wait until ACTIVE bit is zero or timeout */
+ err = readl_poll_timeout(refo->ctrl_reg, v, !(v & REFO_ACTIVE),
+ 1, LOCK_TIMEOUT_US);
+ if (err) {
+ pr_err("%s: poll failed, clk active\n", clk_hw_get_name(hw));
+ return err;
+ }
+
+ spin_lock_irqsave(&refo->core->reg_lock, flags);
+
+ pic32_syskey_unlock();
+
+ /* calculate & apply new */
+ v = readl(refo->ctrl_reg);
+ v &= ~(REFO_SEL_MASK << REFO_SEL_SHIFT);
+ v |= index << REFO_SEL_SHIFT;
+
+ writel(v, refo->ctrl_reg);
+
+ spin_unlock_irqrestore(&refo->core->reg_lock, flags);
+
+ return 0;
+}
+
+static int roclk_set_rate_and_parent(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate,
+ u8 index)
+{
+ struct pic32_ref_osc *refo = clkhw_to_refosc(hw);
+ unsigned long flags;
+ u32 trim, rodiv, v;
+ int err;
+
+ /* calculate new rodiv & rotrim for new rate */
+ roclk_calc_div_trim(rate, parent_rate, &rodiv, &trim);
+
+ pr_debug("parent_rate = %lu, rate = %lu, div = %d, trim = %d\n",
+ parent_rate, rate, rodiv, trim);
+
+ /* wait till source change is active */
+ err = readl_poll_timeout(refo->ctrl_reg, v,
+ !(v & (REFO_ACTIVE | REFO_DIVSW_EN)),
+ 1, LOCK_TIMEOUT_US);
+ if (err) {
+ pr_err("%s: poll timedout, clock is still active\n", __func__);
+ return err;
+ }
+
+ spin_lock_irqsave(&refo->core->reg_lock, flags);
+ v = readl(refo->ctrl_reg);
+
+ pic32_syskey_unlock();
+
+ /* apply parent, if required */
+ if (refo->parent_map)
+ index = refo->parent_map[index];
+
+ v &= ~(REFO_SEL_MASK << REFO_SEL_SHIFT);
+ v |= index << REFO_SEL_SHIFT;
+
+ /* apply RODIV */
+ v &= ~(REFO_DIV_MASK << REFO_DIV_SHIFT);
+ v |= rodiv << REFO_DIV_SHIFT;
+ writel(v, refo->ctrl_reg);
+
+ /* apply ROTRIM */
+ v = readl(refo->ctrl_reg + REFO_TRIM_REG);
+ v &= ~(REFO_TRIM_MASK << REFO_TRIM_SHIFT);
+ v |= trim << REFO_TRIM_SHIFT;
+ writel(v, refo->ctrl_reg + REFO_TRIM_REG);
+
+ /* enable & activate divider switching */
+ writel(REFO_ON | REFO_DIVSW_EN, PIC32_SET(refo->ctrl_reg));
+
+ /* wait till divswen is in-progress */
+ err = readl_poll_timeout_atomic(refo->ctrl_reg, v, !(v & REFO_DIVSW_EN),
+ 1, LOCK_TIMEOUT_US);
+ /* leave the clk gated as it was */
+ writel(REFO_ON, PIC32_CLR(refo->ctrl_reg));
+
+ spin_unlock_irqrestore(&refo->core->reg_lock, flags);
+
+ return err;
+}
+
+static int roclk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ u8 index = roclk_get_parent(hw);
+
+ return roclk_set_rate_and_parent(hw, rate, parent_rate, index);
+}
+
+const struct clk_ops pic32_roclk_ops = {
+ .enable = roclk_enable,
+ .disable = roclk_disable,
+ .is_enabled = roclk_is_enabled,
+ .get_parent = roclk_get_parent,
+ .set_parent = roclk_set_parent,
+ .determine_rate = roclk_determine_rate,
+ .recalc_rate = roclk_recalc_rate,
+ .set_rate_and_parent = roclk_set_rate_and_parent,
+ .set_rate = roclk_set_rate,
+ .init = roclk_init,
+};
+
+struct clk *pic32_refo_clk_register(const struct pic32_ref_osc_data *data,
+ struct pic32_clk_common *core)
+{
+ struct pic32_ref_osc *refo;
+ struct clk *clk;
+
+ refo = devm_kzalloc(core->dev, sizeof(*refo), GFP_KERNEL);
+ if (!refo)
+ return ERR_PTR(-ENOMEM);
+
+ refo->core = core;
+ refo->hw.init = &data->init_data;
+ refo->ctrl_reg = data->ctrl_reg + core->iobase;
+ refo->parent_map = data->parent_map;
+
+ clk = devm_clk_register(core->dev, &refo->hw);
+ if (IS_ERR(clk))
+ dev_err(core->dev, "%s: clk_register() failed\n", __func__);
+
+ return clk;
+}
+
+struct pic32_sys_pll {
+ struct clk_hw hw;
+ void __iomem *ctrl_reg;
+ void __iomem *status_reg;
+ u32 lock_mask;
+ u32 idiv; /* PLL iclk divider, treated fixed */
+ struct pic32_clk_common *core;
+};
+
+#define clkhw_to_spll(_hw) container_of(_hw, struct pic32_sys_pll, hw)
+
+static inline u32 spll_odiv_to_divider(u32 odiv)
+{
+ odiv = clamp_val(odiv, PLL_ODIV_MIN, PLL_ODIV_MAX);
+
+ return 1 << odiv;
+}
+
+static unsigned long spll_calc_mult_div(struct pic32_sys_pll *pll,
+ unsigned long rate,
+ unsigned long parent_rate,
+ u32 *mult_p, u32 *odiv_p)
+{
+ u32 mul, div, best_mul = 1, best_div = 1;
+ unsigned long new_rate, best_rate = rate;
+ unsigned int best_delta = -1, delta, match_found = 0;
+ u64 rate64;
+
+ parent_rate /= pll->idiv;
+
+ for (mul = 1; mul <= PLL_MULT_MAX; mul++) {
+ for (div = PLL_ODIV_MIN; div <= PLL_ODIV_MAX; div++) {
+ rate64 = parent_rate;
+ rate64 *= mul;
+ do_div(rate64, 1 << div);
+ new_rate = rate64;
+ delta = abs(rate - new_rate);
+ if ((new_rate >= rate) && (delta < best_delta)) {
+ best_delta = delta;
+ best_rate = new_rate;
+ best_mul = mul;
+ best_div = div;
+ match_found = 1;
+ }
+ }
+ }
+
+ if (!match_found) {
+ pr_warn("spll: no match found\n");
+ return 0;
+ }
+
+ pr_debug("rate %lu, par_rate %lu/mult %u, div %u, best_rate %lu\n",
+ rate, parent_rate, best_mul, best_div, best_rate);
+
+ if (mult_p)
+ *mult_p = best_mul - 1;
+
+ if (odiv_p)
+ *odiv_p = best_div;
+
+ return best_rate;
+}
+
+static unsigned long spll_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct pic32_sys_pll *pll = clkhw_to_spll(hw);
+ unsigned long pll_in_rate;
+ u32 mult, odiv, div, v;
+ u64 rate64;
+
+ v = readl(pll->ctrl_reg);
+ odiv = ((v >> PLL_ODIV_SHIFT) & PLL_ODIV_MASK);
+ mult = ((v >> PLL_MULT_SHIFT) & PLL_MULT_MASK) + 1;
+ div = spll_odiv_to_divider(odiv);
+
+ /* pll_in_rate = parent_rate / idiv
+ * pll_out_rate = pll_in_rate * mult / div;
+ */
+ pll_in_rate = parent_rate / pll->idiv;
+ rate64 = pll_in_rate;
+ rate64 *= mult;
+ do_div(rate64, div);
+
+ return rate64;
+}
+
+static long spll_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct pic32_sys_pll *pll = clkhw_to_spll(hw);
+
+ return spll_calc_mult_div(pll, rate, *parent_rate, NULL, NULL);
+}
+
+static int spll_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct pic32_sys_pll *pll = clkhw_to_spll(hw);
+ unsigned long ret, flags;
+ u32 mult, odiv, v;
+ int err;
+
+ ret = spll_calc_mult_div(pll, rate, parent_rate, &mult, &odiv);
+ if (!ret)
+ return -EINVAL;
+
+ /*
+ * We can't change SPLL counters when it is in-active use
+ * by SYSCLK. So check before applying new counters/rate.
+ */
+
+ /* Is spll_clk active parent of sys_clk ? */
+ if (unlikely(clk_hw_get_parent(pic32_sclk_hw) == hw)) {
+ pr_err("%s: failed, clk in-use\n", __func__);
+ return -EBUSY;
+ }
+
+ spin_lock_irqsave(&pll->core->reg_lock, flags);
+
+ /* apply new multiplier & divisor */
+ v = readl(pll->ctrl_reg);
+ v &= ~(PLL_MULT_MASK << PLL_MULT_SHIFT);
+ v &= ~(PLL_ODIV_MASK << PLL_ODIV_SHIFT);
+ v |= (mult << PLL_MULT_SHIFT) | (odiv << PLL_ODIV_SHIFT);
+
+ /* sys unlock before write */
+ pic32_syskey_unlock();
+
+ writel(v, pll->ctrl_reg);
+ cpu_relax();
+
+ /* insert few nops (5-stage) to ensure CPU does not hang */
+ cpu_nop5();
+ cpu_nop5();
+
+ /* Wait until PLL is locked (maximum 100 usecs). */
+ err = readl_poll_timeout_atomic(pll->status_reg, v,
+ v & pll->lock_mask, 1, 100);
+ spin_unlock_irqrestore(&pll->core->reg_lock, flags);
+
+ return err;
+}
+
+/* SPLL clock operation */
+const struct clk_ops pic32_spll_ops = {
+ .recalc_rate = spll_clk_recalc_rate,
+ .round_rate = spll_clk_round_rate,
+ .set_rate = spll_clk_set_rate,
+};
+
+struct clk *pic32_spll_clk_register(const struct pic32_sys_pll_data *data,
+ struct pic32_clk_common *core)
+{
+ struct pic32_sys_pll *spll;
+ struct clk *clk;
+
+ spll = devm_kzalloc(core->dev, sizeof(*spll), GFP_KERNEL);
+ if (!spll)
+ return ERR_PTR(-ENOMEM);
+
+ spll->core = core;
+ spll->hw.init = &data->init_data;
+ spll->ctrl_reg = data->ctrl_reg + core->iobase;
+ spll->status_reg = data->status_reg + core->iobase;
+ spll->lock_mask = data->lock_mask;
+
+ /* cache PLL idiv; PLL driver uses it as constant.*/
+ spll->idiv = (readl(spll->ctrl_reg) >> PLL_IDIV_SHIFT) & PLL_IDIV_MASK;
+ spll->idiv += 1;
+
+ clk = devm_clk_register(core->dev, &spll->hw);
+ if (IS_ERR(clk))
+ dev_err(core->dev, "sys_pll: clk_register() failed\n");
+
+ return clk;
+}
+
+/* System mux clock(aka SCLK) */
+
+struct pic32_sys_clk {
+ struct clk_hw hw;
+ void __iomem *mux_reg;
+ void __iomem *slew_reg;
+ u32 slew_div;
+ const u32 *parent_map;
+ struct pic32_clk_common *core;
+};
+
+#define clkhw_to_sys_clk(_hw) container_of(_hw, struct pic32_sys_clk, hw)
+
+static unsigned long sclk_get_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct pic32_sys_clk *sclk = clkhw_to_sys_clk(hw);
+ u32 div;
+
+ div = (readl(sclk->slew_reg) >> SLEW_SYSDIV_SHIFT) & SLEW_SYSDIV;
+ div += 1; /* sys-div to divider */
+
+ return parent_rate / div;
+}
+
+static long sclk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ return calc_best_divided_rate(rate, *parent_rate, SLEW_SYSDIV, 1);
+}
+
+static int sclk_set_rate(struct clk_hw *hw,
+ unsigned long rate, unsigned long parent_rate)
+{
+ struct pic32_sys_clk *sclk = clkhw_to_sys_clk(hw);
+ unsigned long flags;
+ u32 v, div;
+ int err;
+
+ div = parent_rate / rate;
+
+ spin_lock_irqsave(&sclk->core->reg_lock, flags);
+
+ /* apply new div */
+ v = readl(sclk->slew_reg);
+ v &= ~(SLEW_SYSDIV << SLEW_SYSDIV_SHIFT);
+ v |= (div - 1) << SLEW_SYSDIV_SHIFT;
+
+ pic32_syskey_unlock();
+
+ writel(v, sclk->slew_reg);
+
+ /* wait until BUSY is cleared */
+ err = readl_poll_timeout_atomic(sclk->slew_reg, v,
+ !(v & SLEW_BUSY), 1, LOCK_TIMEOUT_US);
+
+ spin_unlock_irqrestore(&sclk->core->reg_lock, flags);
+
+ return err;
+}
+
+static u8 sclk_get_parent(struct clk_hw *hw)
+{
+ struct pic32_sys_clk *sclk = clkhw_to_sys_clk(hw);
+ u32 i, v;
+
+ v = (readl(sclk->mux_reg) >> OSC_CUR_SHIFT) & OSC_CUR_MASK;
+
+ if (!sclk->parent_map)
+ return v;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++)
+ if (sclk->parent_map[i] == v)
+ return i;
+ return -EINVAL;
+}
+
+static int sclk_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct pic32_sys_clk *sclk = clkhw_to_sys_clk(hw);
+ unsigned long flags;
+ u32 nosc, cosc, v;
+ int err;
+
+ spin_lock_irqsave(&sclk->core->reg_lock, flags);
+
+ /* find new_osc */
+ nosc = sclk->parent_map ? sclk->parent_map[index] : index;
+
+ /* set new parent */
+ v = readl(sclk->mux_reg);
+ v &= ~(OSC_NEW_MASK << OSC_NEW_SHIFT);
+ v |= nosc << OSC_NEW_SHIFT;
+
+ pic32_syskey_unlock();
+
+ writel(v, sclk->mux_reg);
+
+ /* initate switch */
+ writel(OSC_SWEN, PIC32_SET(sclk->mux_reg));
+ cpu_relax();
+
+ /* add nop to flush pipeline (as cpu_clk is in-flux) */
+ cpu_nop5();
+
+ /* wait for SWEN bit to clear */
+ err = readl_poll_timeout_atomic(sclk->slew_reg, v,
+ !(v & OSC_SWEN), 1, LOCK_TIMEOUT_US);
+
+ spin_unlock_irqrestore(&sclk->core->reg_lock, flags);
+
+ /*
+ * SCLK clock-switching logic might reject a clock switching request
+ * if pre-requisites (like new clk_src not present or unstable) are
+ * not met.
+ * So confirm before claiming success.
+ */
+ cosc = (readl(sclk->mux_reg) >> OSC_CUR_SHIFT) & OSC_CUR_MASK;
+ if (cosc != nosc) {
+ pr_err("%s: err, failed to set_parent() to %d, current %d\n",
+ clk_hw_get_name(hw), nosc, cosc);
+ err = -EBUSY;
+ }
+
+ return err;
+}
+
+static void sclk_init(struct clk_hw *hw)
+{
+ struct pic32_sys_clk *sclk = clkhw_to_sys_clk(hw);
+ unsigned long flags;
+ u32 v;
+
+ /* Maintain reference to this clk, required in spll_clk_set_rate() */
+ pic32_sclk_hw = hw;
+
+ /* apply slew divider on both up and down scaling */
+ if (sclk->slew_div) {
+ spin_lock_irqsave(&sclk->core->reg_lock, flags);
+ v = readl(sclk->slew_reg);
+ v &= ~(SLEW_DIV << SLEW_DIV_SHIFT);
+ v |= sclk->slew_div << SLEW_DIV_SHIFT;
+ v |= SLEW_DOWNEN | SLEW_UPEN;
+ writel(v, sclk->slew_reg);
+ spin_unlock_irqrestore(&sclk->core->reg_lock, flags);
+ }
+}
+
+/* sclk with post-divider */
+const struct clk_ops pic32_sclk_ops = {
+ .get_parent = sclk_get_parent,
+ .set_parent = sclk_set_parent,
+ .round_rate = sclk_round_rate,
+ .set_rate = sclk_set_rate,
+ .recalc_rate = sclk_get_rate,
+ .init = sclk_init,
+ .determine_rate = __clk_mux_determine_rate,
+};
+
+/* sclk with no slew and no post-divider */
+const struct clk_ops pic32_sclk_no_div_ops = {
+ .get_parent = sclk_get_parent,
+ .set_parent = sclk_set_parent,
+ .init = sclk_init,
+ .determine_rate = __clk_mux_determine_rate,
+};
+
+struct clk *pic32_sys_clk_register(const struct pic32_sys_clk_data *data,
+ struct pic32_clk_common *core)
+{
+ struct pic32_sys_clk *sclk;
+ struct clk *clk;
+
+ sclk = devm_kzalloc(core->dev, sizeof(*sclk), GFP_KERNEL);
+ if (!sclk)
+ return ERR_PTR(-ENOMEM);
+
+ sclk->core = core;
+ sclk->hw.init = &data->init_data;
+ sclk->mux_reg = data->mux_reg + core->iobase;
+ sclk->slew_reg = data->slew_reg + core->iobase;
+ sclk->slew_div = data->slew_div;
+ sclk->parent_map = data->parent_map;
+
+ clk = devm_clk_register(core->dev, &sclk->hw);
+ if (IS_ERR(clk))
+ dev_err(core->dev, "%s: clk register failed\n", __func__);
+
+ return clk;
+}
+
+/* secondary oscillator */
+struct pic32_sec_osc {
+ struct clk_hw hw;
+ void __iomem *enable_reg;
+ void __iomem *status_reg;
+ u32 enable_mask;
+ u32 status_mask;
+ unsigned long fixed_rate;
+ struct pic32_clk_common *core;
+};
+
+#define clkhw_to_sosc(_hw) container_of(_hw, struct pic32_sec_osc, hw)
+static int sosc_clk_enable(struct clk_hw *hw)
+{
+ struct pic32_sec_osc *sosc = clkhw_to_sosc(hw);
+ u32 v;
+
+ /* enable SOSC */
+ pic32_syskey_unlock();
+ writel(sosc->enable_mask, PIC32_SET(sosc->enable_reg));
+
+ /* wait till warm-up period expires or ready-status is updated */
+ return readl_poll_timeout_atomic(sosc->status_reg, v,
+ v & sosc->status_mask, 1, 100);
+}
+
+static void sosc_clk_disable(struct clk_hw *hw)
+{
+ struct pic32_sec_osc *sosc = clkhw_to_sosc(hw);
+
+ pic32_syskey_unlock();
+ writel(sosc->enable_mask, PIC32_CLR(sosc->enable_reg));
+}
+
+static int sosc_clk_is_enabled(struct clk_hw *hw)
+{
+ struct pic32_sec_osc *sosc = clkhw_to_sosc(hw);
+ u32 enabled, ready;
+
+ /* check enabled and ready status */
+ enabled = readl(sosc->enable_reg) & sosc->enable_mask;
+ ready = readl(sosc->status_reg) & sosc->status_mask;
+
+ return enabled && ready;
+}
+
+static unsigned long sosc_clk_calc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ return clkhw_to_sosc(hw)->fixed_rate;
+}
+
+const struct clk_ops pic32_sosc_ops = {
+ .enable = sosc_clk_enable,
+ .disable = sosc_clk_disable,
+ .is_enabled = sosc_clk_is_enabled,
+ .recalc_rate = sosc_clk_calc_rate,
+};
+
+struct clk *pic32_sosc_clk_register(const struct pic32_sec_osc_data *data,
+ struct pic32_clk_common *core)
+{
+ struct pic32_sec_osc *sosc;
+
+ sosc = devm_kzalloc(core->dev, sizeof(*sosc), GFP_KERNEL);
+ if (!sosc)
+ return ERR_PTR(-ENOMEM);
+
+ sosc->core = core;
+ sosc->hw.init = &data->init_data;
+ sosc->fixed_rate = data->fixed_rate;
+ sosc->enable_mask = data->enable_mask;
+ sosc->status_mask = data->status_mask;
+ sosc->enable_reg = data->enable_reg + core->iobase;
+ sosc->status_reg = data->status_reg + core->iobase;
+
+ return devm_clk_register(core->dev, &sosc->hw);
+}
diff --git a/drivers/clk/microchip/clk-core.h b/drivers/clk/microchip/clk-core.h
new file mode 100644
index 000000000000..856664277a29
--- /dev/null
+++ b/drivers/clk/microchip/clk-core.h
@@ -0,0 +1,84 @@
+/*
+ * Purna Chandra Mandal,<purna.mandal@microchip.com>
+ * Copyright (C) 2015 Microchip Technology Inc. All rights reserved.
+ *
+ * This program is free software; you can distribute it and/or modify it
+ * under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 __MICROCHIP_CLK_PIC32_H_
+#define __MICROCHIP_CLK_PIC32_H_
+
+#include <linux/clk-provider.h>
+
+/* PIC32 clock data */
+struct pic32_clk_common {
+ struct device *dev;
+ void __iomem *iobase;
+ spinlock_t reg_lock; /* clock lock */
+};
+
+/* System PLL clock */
+struct pic32_sys_pll_data {
+ struct clk_init_data init_data;
+ const u32 ctrl_reg;
+ const u32 status_reg;
+ const u32 lock_mask;
+};
+
+/* System clock */
+struct pic32_sys_clk_data {
+ struct clk_init_data init_data;
+ const u32 mux_reg;
+ const u32 slew_reg;
+ const u32 *parent_map;
+ const u32 slew_div;
+};
+
+/* Reference Oscillator clock */
+struct pic32_ref_osc_data {
+ struct clk_init_data init_data;
+ const u32 ctrl_reg;
+ const u32 *parent_map;
+};
+
+/* Peripheral Bus clock */
+struct pic32_periph_clk_data {
+ struct clk_init_data init_data;
+ const u32 ctrl_reg;
+};
+
+/* External Secondary Oscillator clock */
+struct pic32_sec_osc_data {
+ struct clk_init_data init_data;
+ const u32 enable_reg;
+ const u32 status_reg;
+ const u32 enable_mask;
+ const u32 status_mask;
+ const unsigned long fixed_rate;
+};
+
+extern const struct clk_ops pic32_pbclk_ops;
+extern const struct clk_ops pic32_sclk_ops;
+extern const struct clk_ops pic32_sclk_no_div_ops;
+extern const struct clk_ops pic32_spll_ops;
+extern const struct clk_ops pic32_roclk_ops;
+extern const struct clk_ops pic32_sosc_ops;
+
+struct clk *pic32_periph_clk_register(const struct pic32_periph_clk_data *data,
+ struct pic32_clk_common *core);
+struct clk *pic32_refo_clk_register(const struct pic32_ref_osc_data *data,
+ struct pic32_clk_common *core);
+struct clk *pic32_sys_clk_register(const struct pic32_sys_clk_data *data,
+ struct pic32_clk_common *core);
+struct clk *pic32_spll_clk_register(const struct pic32_sys_pll_data *data,
+ struct pic32_clk_common *core);
+struct clk *pic32_sosc_clk_register(const struct pic32_sec_osc_data *data,
+ struct pic32_clk_common *core);
+
+#endif /* __MICROCHIP_CLK_PIC32_H_*/
diff --git a/drivers/clk/microchip/clk-pic32mzda.c b/drivers/clk/microchip/clk-pic32mzda.c
new file mode 100644
index 000000000000..020a29acc5b0
--- /dev/null
+++ b/drivers/clk/microchip/clk-pic32mzda.c
@@ -0,0 +1,275 @@
+/*
+ * Purna Chandra Mandal,<purna.mandal@microchip.com>
+ * Copyright (C) 2015 Microchip Technology Inc. All rights reserved.
+ *
+ * This program is free software; you can distribute it and/or modify it
+ * under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <dt-bindings/clock/microchip,pic32-clock.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <asm/traps.h>
+
+#include "clk-core.h"
+
+/* FRC Postscaler */
+#define OSC_FRCDIV_MASK 0x07
+#define OSC_FRCDIV_SHIFT 24
+
+/* SPLL fields */
+#define PLL_ICLK_MASK 0x01
+#define PLL_ICLK_SHIFT 7
+
+#define DECLARE_PERIPHERAL_CLOCK(__clk_name, __reg, __flags) \
+ { \
+ .ctrl_reg = (__reg), \
+ .init_data = { \
+ .name = (__clk_name), \
+ .parent_names = (const char *[]) { \
+ "sys_clk" \
+ }, \
+ .num_parents = 1, \
+ .ops = &pic32_pbclk_ops, \
+ .flags = (__flags), \
+ }, \
+ }
+
+#define DECLARE_REFO_CLOCK(__clkid, __reg) \
+ { \
+ .ctrl_reg = (__reg), \
+ .init_data = { \
+ .name = "refo" #__clkid "_clk", \
+ .parent_names = (const char *[]) { \
+ "sys_clk", "pb1_clk", "posc_clk", \
+ "frc_clk", "lprc_clk", "sosc_clk", \
+ "sys_pll", "refi" #__clkid "_clk", \
+ "bfrc_clk", \
+ }, \
+ .num_parents = 9, \
+ .flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE,\
+ .ops = &pic32_roclk_ops, \
+ }, \
+ .parent_map = (const u32[]) { \
+ 0, 1, 2, 3, 4, 5, 7, 8, 9 \
+ }, \
+ }
+
+static const struct pic32_ref_osc_data ref_clks[] = {
+ DECLARE_REFO_CLOCK(1, 0x80),
+ DECLARE_REFO_CLOCK(2, 0xa0),
+ DECLARE_REFO_CLOCK(3, 0xc0),
+ DECLARE_REFO_CLOCK(4, 0xe0),
+ DECLARE_REFO_CLOCK(5, 0x100),
+};
+
+static const struct pic32_periph_clk_data periph_clocks[] = {
+ DECLARE_PERIPHERAL_CLOCK("pb1_clk", 0x140, 0),
+ DECLARE_PERIPHERAL_CLOCK("pb2_clk", 0x150, CLK_IGNORE_UNUSED),
+ DECLARE_PERIPHERAL_CLOCK("pb3_clk", 0x160, 0),
+ DECLARE_PERIPHERAL_CLOCK("pb4_clk", 0x170, 0),
+ DECLARE_PERIPHERAL_CLOCK("pb5_clk", 0x180, 0),
+ DECLARE_PERIPHERAL_CLOCK("pb6_clk", 0x190, 0),
+ DECLARE_PERIPHERAL_CLOCK("cpu_clk", 0x1a0, CLK_IGNORE_UNUSED),
+};
+
+static const struct pic32_sys_clk_data sys_mux_clk = {
+ .slew_reg = 0x1c0,
+ .slew_div = 2, /* step of div_4 -> div_2 -> no_div */
+ .init_data = {
+ .name = "sys_clk",
+ .parent_names = (const char *[]) {
+ "frcdiv_clk", "sys_pll", "posc_clk",
+ "sosc_clk", "lprc_clk", "frcdiv_clk",
+ },
+ .num_parents = 6,
+ .ops = &pic32_sclk_ops,
+ },
+ .parent_map = (const u32[]) {
+ 0, 1, 2, 4, 5, 7,
+ },
+};
+
+static const struct pic32_sys_pll_data sys_pll = {
+ .ctrl_reg = 0x020,
+ .status_reg = 0x1d0,
+ .lock_mask = BIT(7),
+ .init_data = {
+ .name = "sys_pll",
+ .parent_names = (const char *[]) {
+ "spll_mux_clk"
+ },
+ .num_parents = 1,
+ .ops = &pic32_spll_ops,
+ },
+};
+
+static const struct pic32_sec_osc_data sosc_clk = {
+ .status_reg = 0x1d0,
+ .enable_mask = BIT(1),
+ .status_mask = BIT(4),
+ .init_data = {
+ .name = "sosc_clk",
+ .parent_names = NULL,
+ .ops = &pic32_sosc_ops,
+ },
+};
+
+static int pic32mzda_critical_clks[] = {
+ PB2CLK, PB7CLK
+};
+
+/* PIC32MZDA clock data */
+struct pic32mzda_clk_data {
+ struct clk *clks[MAXCLKS];
+ struct pic32_clk_common core;
+ struct clk_onecell_data onecell_data;
+ struct notifier_block failsafe_notifier;
+};
+
+static int pic32_fscm_nmi(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct pic32mzda_clk_data *cd;
+
+ cd = container_of(nb, struct pic32mzda_clk_data, failsafe_notifier);
+
+ /* SYSCLK is now running from BFRCCLK. Report clock failure. */
+ if (readl(cd->core.iobase) & BIT(2))
+ pr_alert("pic32-clk: FSCM detected clk failure.\n");
+
+ /* TODO: detect reason of failure and recover accordingly */
+
+ return NOTIFY_OK;
+}
+
+static int pic32mzda_clk_probe(struct platform_device *pdev)
+{
+ const char *const pll_mux_parents[] = {"posc_clk", "frc_clk"};
+ struct device_node *np = pdev->dev.of_node;
+ struct pic32mzda_clk_data *cd;
+ struct pic32_clk_common *core;
+ struct clk *pll_mux_clk, *clk;
+ struct clk **clks;
+ int nr_clks, i, ret;
+
+ cd = devm_kzalloc(&pdev->dev, sizeof(*cd), GFP_KERNEL);
+ if (!cd)
+ return -ENOMEM;
+
+ core = &cd->core;
+ core->iobase = of_io_request_and_map(np, 0, of_node_full_name(np));
+ if (IS_ERR(core->iobase)) {
+ dev_err(&pdev->dev, "pic32-clk: failed to map registers\n");
+ return PTR_ERR(core->iobase);
+ }
+
+ spin_lock_init(&core->reg_lock);
+ core->dev = &pdev->dev;
+ clks = &cd->clks[0];
+
+ /* register fixed rate clocks */
+ clks[POSCCLK] = clk_register_fixed_rate(&pdev->dev, "posc_clk", NULL,
+ CLK_IS_ROOT, 24000000);
+ clks[FRCCLK] = clk_register_fixed_rate(&pdev->dev, "frc_clk", NULL,
+ CLK_IS_ROOT, 8000000);
+ clks[BFRCCLK] = clk_register_fixed_rate(&pdev->dev, "bfrc_clk", NULL,
+ CLK_IS_ROOT, 8000000);
+ clks[LPRCCLK] = clk_register_fixed_rate(&pdev->dev, "lprc_clk", NULL,
+ CLK_IS_ROOT, 32000);
+ clks[UPLLCLK] = clk_register_fixed_rate(&pdev->dev, "usbphy_clk", NULL,
+ CLK_IS_ROOT, 24000000);
+ /* fixed rate (optional) clock */
+ if (of_find_property(np, "microchip,pic32mzda-sosc", NULL)) {
+ pr_info("pic32-clk: dt requests SOSC.\n");
+ clks[SOSCCLK] = pic32_sosc_clk_register(&sosc_clk, core);
+ }
+ /* divider clock */
+ clks[FRCDIVCLK] = clk_register_divider(&pdev->dev, "frcdiv_clk",
+ "frc_clk", 0,
+ core->iobase,
+ OSC_FRCDIV_SHIFT,
+ OSC_FRCDIV_MASK,
+ CLK_DIVIDER_POWER_OF_TWO,
+ &core->reg_lock);
+ /* PLL ICLK mux */
+ pll_mux_clk = clk_register_mux(&pdev->dev, "spll_mux_clk",
+ pll_mux_parents, 2, 0,
+ core->iobase + 0x020,
+ PLL_ICLK_SHIFT, 1, 0, &core->reg_lock);
+ if (IS_ERR(pll_mux_clk))
+ pr_err("spll_mux_clk: clk register failed\n");
+
+ /* PLL */
+ clks[PLLCLK] = pic32_spll_clk_register(&sys_pll, core);
+ /* SYSTEM clock */
+ clks[SCLK] = pic32_sys_clk_register(&sys_mux_clk, core);
+ /* Peripheral bus clocks */
+ for (nr_clks = PB1CLK, i = 0; nr_clks <= PB7CLK; i++, nr_clks++)
+ clks[nr_clks] = pic32_periph_clk_register(&periph_clocks[i],
+ core);
+ /* Reference oscillator clock */
+ for (nr_clks = REF1CLK, i = 0; nr_clks <= REF5CLK; i++, nr_clks++)
+ clks[nr_clks] = pic32_refo_clk_register(&ref_clks[i], core);
+
+ /* register clkdev */
+ for (i = 0; i < MAXCLKS; i++) {
+ if (IS_ERR(clks[i]))
+ continue;
+ clk_register_clkdev(clks[i], NULL, __clk_get_name(clks[i]));
+ }
+
+ /* register clock provider */
+ cd->onecell_data.clks = clks;
+ cd->onecell_data.clk_num = MAXCLKS;
+ ret = of_clk_add_provider(np, of_clk_src_onecell_get,
+ &cd->onecell_data);
+ if (ret)
+ return ret;
+
+ /* force enable critical clocks */
+ for (i = 0; i < ARRAY_SIZE(pic32mzda_critical_clks); i++) {
+ clk = clks[pic32mzda_critical_clks[i]];
+ if (clk_prepare_enable(clk))
+ dev_err(&pdev->dev, "clk_prepare_enable(%s) failed\n",
+ __clk_get_name(clk));
+ }
+
+ /* register NMI for failsafe clock monitor */
+ cd->failsafe_notifier.notifier_call = pic32_fscm_nmi;
+ return register_nmi_notifier(&cd->failsafe_notifier);
+}
+
+static const struct of_device_id pic32mzda_clk_match_table[] = {
+ { .compatible = "microchip,pic32mzda-clk", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pic32mzda_clk_match_table);
+
+static struct platform_driver pic32mzda_clk_driver = {
+ .probe = pic32mzda_clk_probe,
+ .driver = {
+ .name = "clk-pic32mzda",
+ .of_match_table = pic32mzda_clk_match_table,
+ },
+};
+
+static int __init microchip_pic32mzda_clk_init(void)
+{
+ return platform_driver_register(&pic32mzda_clk_driver);
+}
+core_initcall(microchip_pic32mzda_clk_init);
+
+MODULE_DESCRIPTION("Microchip PIC32MZDA Clock Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:clk-pic32mzda");
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index e1eb11ee3570..0a9b6a093646 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -102,7 +102,7 @@ obj-$(CONFIG_CRIS_MACH_ARTPEC3) += cris-artpec3-cpufreq.o
obj-$(CONFIG_ETRAXFS) += cris-etraxfs-cpufreq.o
obj-$(CONFIG_IA64_ACPI_CPUFREQ) += ia64-acpi-cpufreq.o
obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o
-obj-$(CONFIG_LOONGSON1_CPUFREQ) += ls1x-cpufreq.o
+obj-$(CONFIG_LOONGSON1_CPUFREQ) += loongson1-cpufreq.o
obj-$(CONFIG_SH_CPU_FREQ) += sh-cpufreq.o
obj-$(CONFIG_SPARC_US2E_CPUFREQ) += sparc-us2e-cpufreq.o
obj-$(CONFIG_SPARC_US3_CPUFREQ) += sparc-us3-cpufreq.o
diff --git a/drivers/cpufreq/ls1x-cpufreq.c b/drivers/cpufreq/loongson1-cpufreq.c
index 262581b3318d..be89416e2358 100644
--- a/drivers/cpufreq/ls1x-cpufreq.c
+++ b/drivers/cpufreq/loongson1-cpufreq.c
@@ -1,7 +1,7 @@
/*
* CPU Frequency Scaling for Loongson 1 SoC
*
- * Copyright (C) 2014 Zhang, Keguang <keguang.zhang@gmail.com>
+ * Copyright (C) 2014-2016 Zhang, Keguang <keguang.zhang@gmail.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
@@ -20,7 +20,7 @@
#include <cpufreq.h>
#include <loongson1.h>
-static struct {
+struct ls1x_cpufreq {
struct device *dev;
struct clk *clk; /* CPU clk */
struct clk *mux_clk; /* MUX of CPU clk */
@@ -28,7 +28,9 @@ static struct {
struct clk *osc_clk; /* OSC clk */
unsigned int max_freq;
unsigned int min_freq;
-} ls1x_cpufreq;
+};
+
+static struct ls1x_cpufreq *cpufreq;
static int ls1x_cpufreq_notifier(struct notifier_block *nb,
unsigned long val, void *data)
@@ -46,6 +48,7 @@ static struct notifier_block ls1x_cpufreq_notifier_block = {
static int ls1x_cpufreq_target(struct cpufreq_policy *policy,
unsigned int index)
{
+ struct device *cpu_dev = get_cpu_device(policy->cpu);
unsigned int old_freq, new_freq;
old_freq = policy->cur;
@@ -60,53 +63,49 @@ static int ls1x_cpufreq_target(struct cpufreq_policy *policy,
* - Reparent CPU clk back to CPU DIV clk
*/
- dev_dbg(ls1x_cpufreq.dev, "%u KHz --> %u KHz\n", old_freq, new_freq);
- clk_set_parent(policy->clk, ls1x_cpufreq.osc_clk);
+ clk_set_parent(policy->clk, cpufreq->osc_clk);
__raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) | RST_CPU_EN | RST_CPU,
LS1X_CLK_PLL_DIV);
__raw_writel(__raw_readl(LS1X_CLK_PLL_DIV) & ~(RST_CPU_EN | RST_CPU),
LS1X_CLK_PLL_DIV);
- clk_set_rate(ls1x_cpufreq.mux_clk, new_freq * 1000);
- clk_set_parent(policy->clk, ls1x_cpufreq.mux_clk);
+ clk_set_rate(cpufreq->mux_clk, new_freq * 1000);
+ clk_set_parent(policy->clk, cpufreq->mux_clk);
+ dev_dbg(cpu_dev, "%u KHz --> %u KHz\n", old_freq, new_freq);
return 0;
}
static int ls1x_cpufreq_init(struct cpufreq_policy *policy)
{
+ struct device *cpu_dev = get_cpu_device(policy->cpu);
struct cpufreq_frequency_table *freq_tbl;
unsigned int pll_freq, freq;
int steps, i, ret;
- pll_freq = clk_get_rate(ls1x_cpufreq.pll_clk) / 1000;
+ pll_freq = clk_get_rate(cpufreq->pll_clk) / 1000;
steps = 1 << DIV_CPU_WIDTH;
- freq_tbl = kzalloc(sizeof(*freq_tbl) * steps, GFP_KERNEL);
- if (!freq_tbl) {
- dev_err(ls1x_cpufreq.dev,
- "failed to alloc cpufreq_frequency_table\n");
- ret = -ENOMEM;
- goto out;
- }
+ freq_tbl = kcalloc(steps, sizeof(*freq_tbl), GFP_KERNEL);
+ if (!freq_tbl)
+ return -ENOMEM;
for (i = 0; i < (steps - 1); i++) {
freq = pll_freq / (i + 1);
- if ((freq < ls1x_cpufreq.min_freq) ||
- (freq > ls1x_cpufreq.max_freq))
+ if ((freq < cpufreq->min_freq) || (freq > cpufreq->max_freq))
freq_tbl[i].frequency = CPUFREQ_ENTRY_INVALID;
else
freq_tbl[i].frequency = freq;
- dev_dbg(ls1x_cpufreq.dev,
+ dev_dbg(cpu_dev,
"cpufreq table: index %d: frequency %d\n", i,
freq_tbl[i].frequency);
}
freq_tbl[i].frequency = CPUFREQ_TABLE_END;
- policy->clk = ls1x_cpufreq.clk;
+ policy->clk = cpufreq->clk;
ret = cpufreq_generic_init(policy, freq_tbl, 0);
if (ret)
kfree(freq_tbl);
-out:
+
return ret;
}
@@ -138,85 +137,86 @@ static int ls1x_cpufreq_remove(struct platform_device *pdev)
static int ls1x_cpufreq_probe(struct platform_device *pdev)
{
- struct plat_ls1x_cpufreq *pdata = pdev->dev.platform_data;
+ struct plat_ls1x_cpufreq *pdata = dev_get_platdata(&pdev->dev);
struct clk *clk;
int ret;
- if (!pdata || !pdata->clk_name || !pdata->osc_clk_name)
+ if (!pdata || !pdata->clk_name || !pdata->osc_clk_name) {
+ dev_err(&pdev->dev, "platform data missing\n");
return -EINVAL;
+ }
- ls1x_cpufreq.dev = &pdev->dev;
+ cpufreq =
+ devm_kzalloc(&pdev->dev, sizeof(struct ls1x_cpufreq), GFP_KERNEL);
+ if (!cpufreq)
+ return -ENOMEM;
+
+ cpufreq->dev = &pdev->dev;
clk = devm_clk_get(&pdev->dev, pdata->clk_name);
if (IS_ERR(clk)) {
- dev_err(ls1x_cpufreq.dev, "unable to get %s clock\n",
+ dev_err(&pdev->dev, "unable to get %s clock\n",
pdata->clk_name);
- ret = PTR_ERR(clk);
- goto out;
+ return PTR_ERR(clk);
}
- ls1x_cpufreq.clk = clk;
+ cpufreq->clk = clk;
clk = clk_get_parent(clk);
if (IS_ERR(clk)) {
- dev_err(ls1x_cpufreq.dev, "unable to get parent of %s clock\n",
- __clk_get_name(ls1x_cpufreq.clk));
- ret = PTR_ERR(clk);
- goto out;
+ dev_err(&pdev->dev, "unable to get parent of %s clock\n",
+ __clk_get_name(cpufreq->clk));
+ return PTR_ERR(clk);
}
- ls1x_cpufreq.mux_clk = clk;
+ cpufreq->mux_clk = clk;
clk = clk_get_parent(clk);
if (IS_ERR(clk)) {
- dev_err(ls1x_cpufreq.dev, "unable to get parent of %s clock\n",
- __clk_get_name(ls1x_cpufreq.mux_clk));
- ret = PTR_ERR(clk);
- goto out;
+ dev_err(&pdev->dev, "unable to get parent of %s clock\n",
+ __clk_get_name(cpufreq->mux_clk));
+ return PTR_ERR(clk);
}
- ls1x_cpufreq.pll_clk = clk;
+ cpufreq->pll_clk = clk;
clk = devm_clk_get(&pdev->dev, pdata->osc_clk_name);
if (IS_ERR(clk)) {
- dev_err(ls1x_cpufreq.dev, "unable to get %s clock\n",
+ dev_err(&pdev->dev, "unable to get %s clock\n",
pdata->osc_clk_name);
- ret = PTR_ERR(clk);
- goto out;
+ return PTR_ERR(clk);
}
- ls1x_cpufreq.osc_clk = clk;
+ cpufreq->osc_clk = clk;
- ls1x_cpufreq.max_freq = pdata->max_freq;
- ls1x_cpufreq.min_freq = pdata->min_freq;
+ cpufreq->max_freq = pdata->max_freq;
+ cpufreq->min_freq = pdata->min_freq;
ret = cpufreq_register_driver(&ls1x_cpufreq_driver);
if (ret) {
- dev_err(ls1x_cpufreq.dev,
- "failed to register cpufreq driver: %d\n", ret);
- goto out;
+ dev_err(&pdev->dev,
+ "failed to register CPUFreq driver: %d\n", ret);
+ return ret;
}
ret = cpufreq_register_notifier(&ls1x_cpufreq_notifier_block,
CPUFREQ_TRANSITION_NOTIFIER);
- if (!ret)
- goto out;
-
- dev_err(ls1x_cpufreq.dev, "failed to register cpufreq notifier: %d\n",
- ret);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register CPUFreq notifier: %d\n",ret);
+ cpufreq_unregister_driver(&ls1x_cpufreq_driver);
+ }
- cpufreq_unregister_driver(&ls1x_cpufreq_driver);
-out:
return ret;
}
static struct platform_driver ls1x_cpufreq_platdrv = {
- .driver = {
+ .probe = ls1x_cpufreq_probe,
+ .remove = ls1x_cpufreq_remove,
+ .driver = {
.name = "ls1x-cpufreq",
},
- .probe = ls1x_cpufreq_probe,
- .remove = ls1x_cpufreq_remove,
};
module_platform_driver(ls1x_cpufreq_platdrv);
MODULE_AUTHOR("Kelvin Cheung <keguang.zhang@gmail.com>");
-MODULE_DESCRIPTION("Loongson 1 CPUFreq driver");
+MODULE_DESCRIPTION("Loongson1 CPUFreq driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/broadcom/Kconfig b/drivers/firmware/broadcom/Kconfig
index 6bed119930dd..3c7e5b741e37 100644
--- a/drivers/firmware/broadcom/Kconfig
+++ b/drivers/firmware/broadcom/Kconfig
@@ -9,3 +9,14 @@ config BCM47XX_NVRAM
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.
+
+config BCM47XX_SPROM
+ bool "Broadcom SPROM driver"
+ depends on BCM47XX_NVRAM
+ help
+ Broadcom devices store configuration data in SPROM. Accessing it is
+ specific to the bus host type, e.g. PCI(e) devices have it mapped in
+ a PCI BAR.
+ In case of SoC devices SPROM content is stored on a flash used by
+ bootloader firmware CFE. This driver provides method to ssb and bcma
+ drivers to read SPROM on SoC.
diff --git a/drivers/firmware/broadcom/Makefile b/drivers/firmware/broadcom/Makefile
index d0e683583cd6..f93efc479b8b 100644
--- a/drivers/firmware/broadcom/Makefile
+++ b/drivers/firmware/broadcom/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_BCM47XX_NVRAM) += bcm47xx_nvram.o
+obj-$(CONFIG_BCM47XX_SPROM) += bcm47xx_sprom.o
diff --git a/drivers/firmware/broadcom/bcm47xx_sprom.c b/drivers/firmware/broadcom/bcm47xx_sprom.c
new file mode 100644
index 000000000000..b6eb875d4af3
--- /dev/null
+++ b/drivers/firmware/broadcom/bcm47xx_sprom.c
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2004 Florian Schirmer <jolt@tuxbox.org>
+ * Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2006 Michael Buesch <m@bues.ch>
+ * Copyright (C) 2010 Waldemar Brodkorb <wbx@openadk.org>
+ * Copyright (C) 2010-2012 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/bcm47xx_nvram.h>
+#include <linux/bcma/bcma.h>
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
+#include <linux/ssb/ssb.h>
+
+static void create_key(const char *prefix, const char *postfix,
+ const char *name, char *buf, int len)
+{
+ if (prefix && postfix)
+ snprintf(buf, len, "%s%s%s", prefix, name, postfix);
+ else if (prefix)
+ snprintf(buf, len, "%s%s", prefix, name);
+ else if (postfix)
+ snprintf(buf, len, "%s%s", name, postfix);
+ else
+ snprintf(buf, len, "%s", name);
+}
+
+static int get_nvram_var(const char *prefix, const char *postfix,
+ const char *name, char *buf, int len, bool fallback)
+{
+ char key[40];
+ int err;
+
+ create_key(prefix, postfix, name, key, sizeof(key));
+
+ err = bcm47xx_nvram_getenv(key, buf, len);
+ if (fallback && err == -ENOENT && prefix) {
+ create_key(NULL, postfix, name, key, sizeof(key));
+ err = bcm47xx_nvram_getenv(key, buf, len);
+ }
+ return err;
+}
+
+#define NVRAM_READ_VAL(type) \
+static void nvram_read_ ## type(const char *prefix, \
+ const char *postfix, const char *name, \
+ type *val, type allset, bool fallback) \
+{ \
+ char buf[100]; \
+ int err; \
+ type var; \
+ \
+ err = get_nvram_var(prefix, postfix, name, buf, sizeof(buf), \
+ fallback); \
+ if (err < 0) \
+ return; \
+ err = kstrto ## type(strim(buf), 0, &var); \
+ if (err) { \
+ pr_warn("can not parse nvram name %s%s%s with value %s got %i\n", \
+ prefix, name, postfix, buf, err); \
+ return; \
+ } \
+ if (allset && var == allset) \
+ return; \
+ *val = var; \
+}
+
+NVRAM_READ_VAL(u8)
+NVRAM_READ_VAL(s8)
+NVRAM_READ_VAL(u16)
+NVRAM_READ_VAL(u32)
+
+#undef NVRAM_READ_VAL
+
+static void nvram_read_u32_2(const char *prefix, const char *name,
+ u16 *val_lo, u16 *val_hi, bool fallback)
+{
+ char buf[100];
+ int err;
+ u32 val;
+
+ err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
+ if (err < 0)
+ return;
+ err = kstrtou32(strim(buf), 0, &val);
+ if (err) {
+ pr_warn("can not parse nvram name %s%s with value %s got %i\n",
+ prefix, name, buf, err);
+ return;
+ }
+ *val_lo = (val & 0x0000FFFFU);
+ *val_hi = (val & 0xFFFF0000U) >> 16;
+}
+
+static void nvram_read_leddc(const char *prefix, const char *name,
+ u8 *leddc_on_time, u8 *leddc_off_time,
+ bool fallback)
+{
+ char buf[100];
+ int err;
+ u32 val;
+
+ err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
+ if (err < 0)
+ return;
+ err = kstrtou32(strim(buf), 0, &val);
+ if (err) {
+ pr_warn("can not parse nvram name %s%s with value %s got %i\n",
+ prefix, name, buf, err);
+ return;
+ }
+
+ if (val == 0xffff || val == 0xffffffff)
+ return;
+
+ *leddc_on_time = val & 0xff;
+ *leddc_off_time = (val >> 16) & 0xff;
+}
+
+static void bcm47xx_nvram_parse_macaddr(char *buf, u8 macaddr[6])
+{
+ if (strchr(buf, ':'))
+ sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &macaddr[0],
+ &macaddr[1], &macaddr[2], &macaddr[3], &macaddr[4],
+ &macaddr[5]);
+ else if (strchr(buf, '-'))
+ sscanf(buf, "%hhx-%hhx-%hhx-%hhx-%hhx-%hhx", &macaddr[0],
+ &macaddr[1], &macaddr[2], &macaddr[3], &macaddr[4],
+ &macaddr[5]);
+ else
+ pr_warn("Can not parse mac address: %s\n", buf);
+}
+
+static void nvram_read_macaddr(const char *prefix, const char *name,
+ u8 val[6], bool fallback)
+{
+ char buf[100];
+ int err;
+
+ err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
+ if (err < 0)
+ return;
+
+ bcm47xx_nvram_parse_macaddr(buf, val);
+}
+
+static void nvram_read_alpha2(const char *prefix, const char *name,
+ char val[2], bool fallback)
+{
+ char buf[10];
+ int err;
+
+ err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
+ if (err < 0)
+ return;
+ if (buf[0] == '0')
+ return;
+ if (strlen(buf) > 2) {
+ pr_warn("alpha2 is too long %s\n", buf);
+ return;
+ }
+ memcpy(val, buf, 2);
+}
+
+/* This is one-function-only macro, it uses local "sprom" variable! */
+#define ENTRY(_revmask, _type, _prefix, _name, _val, _allset, _fallback) \
+ if (_revmask & BIT(sprom->revision)) \
+ nvram_read_ ## _type(_prefix, NULL, _name, &sprom->_val, \
+ _allset, _fallback)
+/*
+ * Special version of filling function that can be safely called for any SPROM
+ * revision. For every NVRAM to SPROM mapping it contains bitmask of revisions
+ * for which the mapping is valid.
+ * It obviously requires some hexadecimal/bitmasks knowledge, but allows
+ * writing cleaner code (easy revisions handling).
+ * Note that while SPROM revision 0 was never used, we still keep BIT(0)
+ * reserved for it, just to keep numbering sane.
+ */
+static void bcm47xx_sprom_fill_auto(struct ssb_sprom *sprom,
+ const char *prefix, bool fallback)
+{
+ const char *pre = prefix;
+ bool fb = fallback;
+
+ /* Broadcom extracts it for rev 8+ but it was found on 2 and 4 too */
+ ENTRY(0xfffffffe, u16, pre, "devid", dev_id, 0, fallback);
+
+ ENTRY(0xfffffffe, u16, pre, "boardrev", board_rev, 0, true);
+ ENTRY(0xfffffffe, u32, pre, "boardflags", boardflags, 0, fb);
+ ENTRY(0xfffffff0, u32, pre, "boardflags2", boardflags2, 0, fb);
+ ENTRY(0xfffff800, u32, pre, "boardflags3", boardflags3, 0, fb);
+ ENTRY(0x00000002, u16, pre, "boardflags", boardflags_lo, 0, fb);
+ ENTRY(0xfffffffc, u16, pre, "boardtype", board_type, 0, true);
+ ENTRY(0xfffffffe, u16, pre, "boardnum", board_num, 0, fb);
+ ENTRY(0x00000002, u8, pre, "cc", country_code, 0, fb);
+ ENTRY(0xfffffff8, u8, pre, "regrev", regrev, 0, fb);
+
+ ENTRY(0xfffffffe, u8, pre, "ledbh0", gpio0, 0xff, fb);
+ ENTRY(0xfffffffe, u8, pre, "ledbh1", gpio1, 0xff, fb);
+ ENTRY(0xfffffffe, u8, pre, "ledbh2", gpio2, 0xff, fb);
+ ENTRY(0xfffffffe, u8, pre, "ledbh3", gpio3, 0xff, fb);
+
+ ENTRY(0x0000070e, u16, pre, "pa0b0", pa0b0, 0, fb);
+ ENTRY(0x0000070e, u16, pre, "pa0b1", pa0b1, 0, fb);
+ ENTRY(0x0000070e, u16, pre, "pa0b2", pa0b2, 0, fb);
+ ENTRY(0x0000070e, u8, pre, "pa0itssit", itssi_bg, 0, fb);
+ ENTRY(0x0000070e, u8, pre, "pa0maxpwr", maxpwr_bg, 0, fb);
+
+ ENTRY(0x0000070c, u8, pre, "opo", opo, 0, fb);
+ ENTRY(0xfffffffe, u8, pre, "aa2g", ant_available_bg, 0, fb);
+ ENTRY(0xfffffffe, u8, pre, "aa5g", ant_available_a, 0, fb);
+ ENTRY(0x000007fe, s8, pre, "ag0", antenna_gain.a0, 0, fb);
+ ENTRY(0x000007fe, s8, pre, "ag1", antenna_gain.a1, 0, fb);
+ ENTRY(0x000007f0, s8, pre, "ag2", antenna_gain.a2, 0, fb);
+ ENTRY(0x000007f0, s8, pre, "ag3", antenna_gain.a3, 0, fb);
+
+ ENTRY(0x0000070e, u16, pre, "pa1b0", pa1b0, 0, fb);
+ ENTRY(0x0000070e, u16, pre, "pa1b1", pa1b1, 0, fb);
+ ENTRY(0x0000070e, u16, pre, "pa1b2", pa1b2, 0, fb);
+ ENTRY(0x0000070c, u16, pre, "pa1lob0", pa1lob0, 0, fb);
+ ENTRY(0x0000070c, u16, pre, "pa1lob1", pa1lob1, 0, fb);
+ ENTRY(0x0000070c, u16, pre, "pa1lob2", pa1lob2, 0, fb);
+ ENTRY(0x0000070c, u16, pre, "pa1hib0", pa1hib0, 0, fb);
+ ENTRY(0x0000070c, u16, pre, "pa1hib1", pa1hib1, 0, fb);
+ ENTRY(0x0000070c, u16, pre, "pa1hib2", pa1hib2, 0, fb);
+ ENTRY(0x0000070e, u8, pre, "pa1itssit", itssi_a, 0, fb);
+ ENTRY(0x0000070e, u8, pre, "pa1maxpwr", maxpwr_a, 0, fb);
+ ENTRY(0x0000070c, u8, pre, "pa1lomaxpwr", maxpwr_al, 0, fb);
+ ENTRY(0x0000070c, u8, pre, "pa1himaxpwr", maxpwr_ah, 0, fb);
+
+ ENTRY(0x00000708, u8, pre, "bxa2g", bxa2g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "rssisav2g", rssisav2g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "rssismc2g", rssismc2g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "rssismf2g", rssismf2g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "bxa5g", bxa5g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "rssisav5g", rssisav5g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "rssismc5g", rssismc5g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "rssismf5g", rssismf5g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "tri2g", tri2g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "tri5g", tri5g, 0, fb);
+ ENTRY(0x00000708, u8, pre, "tri5gl", tri5gl, 0, fb);
+ ENTRY(0x00000708, u8, pre, "tri5gh", tri5gh, 0, fb);
+ ENTRY(0x00000708, s8, pre, "rxpo2g", rxpo2g, 0, fb);
+ ENTRY(0x00000708, s8, pre, "rxpo5g", rxpo5g, 0, fb);
+ ENTRY(0xfffffff0, u8, pre, "txchain", txchain, 0xf, fb);
+ ENTRY(0xfffffff0, u8, pre, "rxchain", rxchain, 0xf, fb);
+ ENTRY(0xfffffff0, u8, pre, "antswitch", antswitch, 0xff, fb);
+ ENTRY(0x00000700, u8, pre, "tssipos2g", fem.ghz2.tssipos, 0, fb);
+ ENTRY(0x00000700, u8, pre, "extpagain2g", fem.ghz2.extpa_gain, 0, fb);
+ ENTRY(0x00000700, u8, pre, "pdetrange2g", fem.ghz2.pdet_range, 0, fb);
+ ENTRY(0x00000700, u8, pre, "triso2g", fem.ghz2.tr_iso, 0, fb);
+ ENTRY(0x00000700, u8, pre, "antswctl2g", fem.ghz2.antswlut, 0, fb);
+ ENTRY(0x00000700, u8, pre, "tssipos5g", fem.ghz5.tssipos, 0, fb);
+ ENTRY(0x00000700, u8, pre, "extpagain5g", fem.ghz5.extpa_gain, 0, fb);
+ ENTRY(0x00000700, u8, pre, "pdetrange5g", fem.ghz5.pdet_range, 0, fb);
+ ENTRY(0x00000700, u8, pre, "triso5g", fem.ghz5.tr_iso, 0, fb);
+ ENTRY(0x00000700, u8, pre, "antswctl5g", fem.ghz5.antswlut, 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid2ga0", txpid2g[0], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid2ga1", txpid2g[1], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid2ga2", txpid2g[2], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid2ga3", txpid2g[3], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5ga0", txpid5g[0], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5ga1", txpid5g[1], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5ga2", txpid5g[2], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5ga3", txpid5g[3], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gla0", txpid5gl[0], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gla1", txpid5gl[1], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gla2", txpid5gl[2], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gla3", txpid5gl[3], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gha0", txpid5gh[0], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gha1", txpid5gh[1], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gha2", txpid5gh[2], 0, fb);
+ ENTRY(0x000000f0, u8, pre, "txpid5gha3", txpid5gh[3], 0, fb);
+
+ ENTRY(0xffffff00, u8, pre, "tempthresh", tempthresh, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "tempoffset", tempoffset, 0, fb);
+ ENTRY(0xffffff00, u16, pre, "rawtempsense", rawtempsense, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "measpower", measpower, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "tempsense_slope", tempsense_slope, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "tempcorrx", tempcorrx, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "tempsense_option", tempsense_option, 0, fb);
+ ENTRY(0x00000700, u8, pre, "freqoffset_corr", freqoffset_corr, 0, fb);
+ ENTRY(0x00000700, u8, pre, "iqcal_swp_dis", iqcal_swp_dis, 0, fb);
+ ENTRY(0x00000700, u8, pre, "hw_iqcal_en", hw_iqcal_en, 0, fb);
+ ENTRY(0x00000700, u8, pre, "elna2g", elna2g, 0, fb);
+ ENTRY(0x00000700, u8, pre, "elna5g", elna5g, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "phycal_tempdelta", phycal_tempdelta, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "temps_period", temps_period, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "temps_hysteresis", temps_hysteresis, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "measpower1", measpower1, 0, fb);
+ ENTRY(0xffffff00, u8, pre, "measpower2", measpower2, 0, fb);
+
+ ENTRY(0x000001f0, u16, pre, "cck2gpo", cck2gpo, 0, fb);
+ ENTRY(0x000001f0, u32, pre, "ofdm2gpo", ofdm2gpo, 0, fb);
+ ENTRY(0x000001f0, u32, pre, "ofdm5gpo", ofdm5gpo, 0, fb);
+ ENTRY(0x000001f0, u32, pre, "ofdm5glpo", ofdm5glpo, 0, fb);
+ ENTRY(0x000001f0, u32, pre, "ofdm5ghpo", ofdm5ghpo, 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo0", mcs2gpo[0], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo1", mcs2gpo[1], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo2", mcs2gpo[2], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo3", mcs2gpo[3], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo4", mcs2gpo[4], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo5", mcs2gpo[5], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo6", mcs2gpo[6], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs2gpo7", mcs2gpo[7], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo0", mcs5gpo[0], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo1", mcs5gpo[1], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo2", mcs5gpo[2], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo3", mcs5gpo[3], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo4", mcs5gpo[4], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo5", mcs5gpo[5], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo6", mcs5gpo[6], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5gpo7", mcs5gpo[7], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo0", mcs5glpo[0], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo1", mcs5glpo[1], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo2", mcs5glpo[2], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo3", mcs5glpo[3], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo4", mcs5glpo[4], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo5", mcs5glpo[5], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo6", mcs5glpo[6], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5glpo7", mcs5glpo[7], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo0", mcs5ghpo[0], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo1", mcs5ghpo[1], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo2", mcs5ghpo[2], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo3", mcs5ghpo[3], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo4", mcs5ghpo[4], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo5", mcs5ghpo[5], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo6", mcs5ghpo[6], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "mcs5ghpo7", mcs5ghpo[7], 0, fb);
+ ENTRY(0x000001f0, u16, pre, "cddpo", cddpo, 0, fb);
+ ENTRY(0x000001f0, u16, pre, "stbcpo", stbcpo, 0, fb);
+ ENTRY(0x000001f0, u16, pre, "bw40po", bw40po, 0, fb);
+ ENTRY(0x000001f0, u16, pre, "bwduppo", bwduppo, 0, fb);
+
+ ENTRY(0xfffffe00, u16, pre, "cckbw202gpo", cckbw202gpo, 0, fb);
+ ENTRY(0xfffffe00, u16, pre, "cckbw20ul2gpo", cckbw20ul2gpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw202gpo", legofdmbw202gpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw20ul2gpo", legofdmbw20ul2gpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw205glpo", legofdmbw205glpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw20ul5glpo", legofdmbw20ul5glpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw205gmpo", legofdmbw205gmpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw20ul5gmpo", legofdmbw20ul5gmpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw205ghpo", legofdmbw205ghpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "legofdmbw20ul5ghpo", legofdmbw20ul5ghpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw202gpo", mcsbw202gpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "mcsbw20ul2gpo", mcsbw20ul2gpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw402gpo", mcsbw402gpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw205glpo", mcsbw205glpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "mcsbw20ul5glpo", mcsbw20ul5glpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw405glpo", mcsbw405glpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw205gmpo", mcsbw205gmpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "mcsbw20ul5gmpo", mcsbw20ul5gmpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw405gmpo", mcsbw405gmpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw205ghpo", mcsbw205ghpo, 0, fb);
+ ENTRY(0x00000600, u32, pre, "mcsbw20ul5ghpo", mcsbw20ul5ghpo, 0, fb);
+ ENTRY(0xfffffe00, u32, pre, "mcsbw405ghpo", mcsbw405ghpo, 0, fb);
+ ENTRY(0x00000600, u16, pre, "mcs32po", mcs32po, 0, fb);
+ ENTRY(0x00000600, u16, pre, "legofdm40duppo", legofdm40duppo, 0, fb);
+ ENTRY(0x00000700, u8, pre, "pcieingress_war", pcieingress_war, 0, fb);
+
+ /* TODO: rev 11 support */
+ ENTRY(0x00000700, u8, pre, "rxgainerr2ga0", rxgainerr2ga[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr2ga1", rxgainerr2ga[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr2ga2", rxgainerr2ga[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gla0", rxgainerr5gla[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gla1", rxgainerr5gla[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gla2", rxgainerr5gla[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gma0", rxgainerr5gma[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gma1", rxgainerr5gma[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gma2", rxgainerr5gma[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gha0", rxgainerr5gha[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gha1", rxgainerr5gha[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gha2", rxgainerr5gha[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gua0", rxgainerr5gua[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gua1", rxgainerr5gua[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "rxgainerr5gua2", rxgainerr5gua[2], 0, fb);
+
+ ENTRY(0xfffffe00, u8, pre, "sar2g", sar2g, 0, fb);
+ ENTRY(0xfffffe00, u8, pre, "sar5g", sar5g, 0, fb);
+
+ /* TODO: rev 11 support */
+ ENTRY(0x00000700, u8, pre, "noiselvl2ga0", noiselvl2ga[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl2ga1", noiselvl2ga[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl2ga2", noiselvl2ga[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gla0", noiselvl5gla[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gla1", noiselvl5gla[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gla2", noiselvl5gla[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gma0", noiselvl5gma[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gma1", noiselvl5gma[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gma2", noiselvl5gma[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gha0", noiselvl5gha[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gha1", noiselvl5gha[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gha2", noiselvl5gha[2], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gua0", noiselvl5gua[0], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gua1", noiselvl5gua[1], 0, fb);
+ ENTRY(0x00000700, u8, pre, "noiselvl5gua2", noiselvl5gua[2], 0, fb);
+}
+#undef ENTRY /* It's specififc, uses local variable, don't use it (again). */
+
+static void bcm47xx_fill_sprom_path_r4589(struct ssb_sprom *sprom,
+ const char *prefix, bool fallback)
+{
+ char postfix[2];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sprom->core_pwr_info); i++) {
+ struct ssb_sprom_core_pwr_info *pwr_info;
+
+ pwr_info = &sprom->core_pwr_info[i];
+
+ snprintf(postfix, sizeof(postfix), "%i", i);
+ nvram_read_u8(prefix, postfix, "maxp2ga",
+ &pwr_info->maxpwr_2g, 0, fallback);
+ nvram_read_u8(prefix, postfix, "itt2ga",
+ &pwr_info->itssi_2g, 0, fallback);
+ nvram_read_u8(prefix, postfix, "itt5ga",
+ &pwr_info->itssi_5g, 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa2gw0a",
+ &pwr_info->pa_2g[0], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa2gw1a",
+ &pwr_info->pa_2g[1], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa2gw2a",
+ &pwr_info->pa_2g[2], 0, fallback);
+ nvram_read_u8(prefix, postfix, "maxp5ga",
+ &pwr_info->maxpwr_5g, 0, fallback);
+ nvram_read_u8(prefix, postfix, "maxp5gha",
+ &pwr_info->maxpwr_5gh, 0, fallback);
+ nvram_read_u8(prefix, postfix, "maxp5gla",
+ &pwr_info->maxpwr_5gl, 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5gw0a",
+ &pwr_info->pa_5g[0], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5gw1a",
+ &pwr_info->pa_5g[1], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5gw2a",
+ &pwr_info->pa_5g[2], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5glw0a",
+ &pwr_info->pa_5gl[0], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5glw1a",
+ &pwr_info->pa_5gl[1], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5glw2a",
+ &pwr_info->pa_5gl[2], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5ghw0a",
+ &pwr_info->pa_5gh[0], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5ghw1a",
+ &pwr_info->pa_5gh[1], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5ghw2a",
+ &pwr_info->pa_5gh[2], 0, fallback);
+ }
+}
+
+static void bcm47xx_fill_sprom_path_r45(struct ssb_sprom *sprom,
+ const char *prefix, bool fallback)
+{
+ char postfix[2];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sprom->core_pwr_info); i++) {
+ struct ssb_sprom_core_pwr_info *pwr_info;
+
+ pwr_info = &sprom->core_pwr_info[i];
+
+ snprintf(postfix, sizeof(postfix), "%i", i);
+ nvram_read_u16(prefix, postfix, "pa2gw3a",
+ &pwr_info->pa_2g[3], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5gw3a",
+ &pwr_info->pa_5g[3], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5glw3a",
+ &pwr_info->pa_5gl[3], 0, fallback);
+ nvram_read_u16(prefix, postfix, "pa5ghw3a",
+ &pwr_info->pa_5gh[3], 0, fallback);
+ }
+}
+
+static bool bcm47xx_is_valid_mac(u8 *mac)
+{
+ return mac && !(mac[0] == 0x00 && mac[1] == 0x90 && mac[2] == 0x4c);
+}
+
+static int bcm47xx_increase_mac_addr(u8 *mac, u8 num)
+{
+ u8 *oui = mac + ETH_ALEN/2 - 1;
+ u8 *p = mac + ETH_ALEN - 1;
+
+ do {
+ (*p) += num;
+ if (*p > num)
+ break;
+ p--;
+ num = 1;
+ } while (p != oui);
+
+ if (p == oui) {
+ pr_err("unable to fetch mac address\n");
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int mac_addr_used = 2;
+
+static void bcm47xx_fill_sprom_ethernet(struct ssb_sprom *sprom,
+ const char *prefix, bool fallback)
+{
+ bool fb = fallback;
+
+ nvram_read_macaddr(prefix, "et0macaddr", sprom->et0mac, fallback);
+ nvram_read_u8(prefix, NULL, "et0mdcport", &sprom->et0mdcport, 0,
+ fallback);
+ nvram_read_u8(prefix, NULL, "et0phyaddr", &sprom->et0phyaddr, 0,
+ fallback);
+
+ nvram_read_macaddr(prefix, "et1macaddr", sprom->et1mac, fallback);
+ nvram_read_u8(prefix, NULL, "et1mdcport", &sprom->et1mdcport, 0,
+ fallback);
+ nvram_read_u8(prefix, NULL, "et1phyaddr", &sprom->et1phyaddr, 0,
+ fallback);
+
+ nvram_read_macaddr(prefix, "et2macaddr", sprom->et2mac, fb);
+ nvram_read_u8(prefix, NULL, "et2mdcport", &sprom->et2mdcport, 0, fb);
+ nvram_read_u8(prefix, NULL, "et2phyaddr", &sprom->et2phyaddr, 0, fb);
+
+ nvram_read_macaddr(prefix, "macaddr", sprom->il0mac, fallback);
+ nvram_read_macaddr(prefix, "il0macaddr", sprom->il0mac, fallback);
+
+ /* The address prefix 00:90:4C is used by Broadcom in their initial
+ * configuration. When a mac address with the prefix 00:90:4C is used
+ * all devices from the same series are sharing the same mac address.
+ * To prevent mac address collisions we replace them with a mac address
+ * based on the base address.
+ */
+ if (!bcm47xx_is_valid_mac(sprom->il0mac)) {
+ u8 mac[6];
+
+ nvram_read_macaddr(NULL, "et0macaddr", mac, false);
+ if (bcm47xx_is_valid_mac(mac)) {
+ int err = bcm47xx_increase_mac_addr(mac, mac_addr_used);
+
+ if (!err) {
+ ether_addr_copy(sprom->il0mac, mac);
+ mac_addr_used++;
+ }
+ }
+ }
+}
+
+static void bcm47xx_fill_board_data(struct ssb_sprom *sprom, const char *prefix,
+ bool fallback)
+{
+ nvram_read_u32_2(prefix, "boardflags", &sprom->boardflags_lo,
+ &sprom->boardflags_hi, fallback);
+ nvram_read_u32_2(prefix, "boardflags2", &sprom->boardflags2_lo,
+ &sprom->boardflags2_hi, fallback);
+}
+
+void bcm47xx_fill_sprom(struct ssb_sprom *sprom, const char *prefix,
+ bool fallback)
+{
+ bcm47xx_fill_sprom_ethernet(sprom, prefix, fallback);
+ bcm47xx_fill_board_data(sprom, prefix, fallback);
+
+ nvram_read_u8(prefix, NULL, "sromrev", &sprom->revision, 0, fallback);
+
+ /* Entries requiring custom functions */
+ nvram_read_alpha2(prefix, "ccode", sprom->alpha2, fallback);
+ if (sprom->revision >= 3)
+ nvram_read_leddc(prefix, "leddc", &sprom->leddc_on_time,
+ &sprom->leddc_off_time, fallback);
+
+ switch (sprom->revision) {
+ case 4:
+ case 5:
+ bcm47xx_fill_sprom_path_r4589(sprom, prefix, fallback);
+ bcm47xx_fill_sprom_path_r45(sprom, prefix, fallback);
+ break;
+ case 8:
+ case 9:
+ bcm47xx_fill_sprom_path_r4589(sprom, prefix, fallback);
+ break;
+ }
+
+ bcm47xx_sprom_fill_auto(sprom, prefix, fallback);
+}
+
+#if IS_BUILTIN(CONFIG_SSB) && IS_ENABLED(CONFIG_SSB_SPROM)
+static int bcm47xx_get_sprom_ssb(struct ssb_bus *bus, struct ssb_sprom *out)
+{
+ char prefix[10];
+
+ switch (bus->bustype) {
+ case SSB_BUSTYPE_SSB:
+ bcm47xx_fill_sprom(out, NULL, false);
+ return 0;
+ case SSB_BUSTYPE_PCI:
+ memset(out, 0, sizeof(struct ssb_sprom));
+ snprintf(prefix, sizeof(prefix), "pci/%u/%u/",
+ bus->host_pci->bus->number + 1,
+ PCI_SLOT(bus->host_pci->devfn));
+ bcm47xx_fill_sprom(out, prefix, false);
+ return 0;
+ default:
+ pr_warn("Unable to fill SPROM for given bustype.\n");
+ return -EINVAL;
+ }
+}
+#endif
+
+#if IS_BUILTIN(CONFIG_BCMA)
+/*
+ * Having many NVRAM entries for PCI devices led to repeating prefixes like
+ * pci/1/1/ all the time and wasting flash space. So at some point Broadcom
+ * decided to introduce prefixes like 0: 1: 2: etc.
+ * If we find e.g. devpath0=pci/2/1 or devpath0=pci/2/1/ we should use 0:
+ * instead of pci/2/1/.
+ */
+static void bcm47xx_sprom_apply_prefix_alias(char *prefix, size_t prefix_size)
+{
+ size_t prefix_len = strlen(prefix);
+ size_t short_len = prefix_len - 1;
+ char nvram_var[10];
+ char buf[20];
+ int i;
+
+ /* Passed prefix has to end with a slash */
+ if (prefix_len <= 0 || prefix[prefix_len - 1] != '/')
+ return;
+
+ for (i = 0; i < 3; i++) {
+ if (snprintf(nvram_var, sizeof(nvram_var), "devpath%d", i) <= 0)
+ continue;
+ if (bcm47xx_nvram_getenv(nvram_var, buf, sizeof(buf)) < 0)
+ continue;
+ if (!strcmp(buf, prefix) ||
+ (short_len && strlen(buf) == short_len && !strncmp(buf, prefix, short_len))) {
+ snprintf(prefix, prefix_size, "%d:", i);
+ return;
+ }
+ }
+}
+
+static int bcm47xx_get_sprom_bcma(struct bcma_bus *bus, struct ssb_sprom *out)
+{
+ struct bcma_boardinfo *binfo = &bus->boardinfo;
+ struct bcma_device *core;
+ char buf[10];
+ char *prefix;
+ bool fallback = false;
+
+ switch (bus->hosttype) {
+ case BCMA_HOSTTYPE_PCI:
+ memset(out, 0, sizeof(struct ssb_sprom));
+ /* On BCM47XX all PCI buses share the same domain */
+ if (config_enabled(CONFIG_BCM47XX))
+ snprintf(buf, sizeof(buf), "pci/%u/%u/",
+ bus->host_pci->bus->number + 1,
+ PCI_SLOT(bus->host_pci->devfn));
+ else
+ snprintf(buf, sizeof(buf), "pci/%u/%u/",
+ pci_domain_nr(bus->host_pci->bus) + 1,
+ bus->host_pci->bus->number);
+ bcm47xx_sprom_apply_prefix_alias(buf, sizeof(buf));
+ prefix = buf;
+ break;
+ case BCMA_HOSTTYPE_SOC:
+ memset(out, 0, sizeof(struct ssb_sprom));
+ core = bcma_find_core(bus, BCMA_CORE_80211);
+ if (core) {
+ snprintf(buf, sizeof(buf), "sb/%u/",
+ core->core_index);
+ prefix = buf;
+ fallback = true;
+ } else {
+ prefix = NULL;
+ }
+ break;
+ default:
+ pr_warn("Unable to fill SPROM for given bustype.\n");
+ return -EINVAL;
+ }
+
+ nvram_read_u16(prefix, NULL, "boardvendor", &binfo->vendor, 0, true);
+ if (!binfo->vendor)
+ binfo->vendor = SSB_BOARDVENDOR_BCM;
+ nvram_read_u16(prefix, NULL, "boardtype", &binfo->type, 0, true);
+
+ bcm47xx_fill_sprom(out, prefix, fallback);
+
+ return 0;
+}
+#endif
+
+static unsigned int bcm47xx_sprom_registered;
+
+/*
+ * On bcm47xx we need to register SPROM fallback handler very early, so we can't
+ * use anything like platform device / driver for this.
+ */
+int bcm47xx_sprom_register_fallbacks(void)
+{
+ if (bcm47xx_sprom_registered)
+ return 0;
+
+#if IS_BUILTIN(CONFIG_SSB) && IS_ENABLED(CONFIG_SSB_SPROM)
+ if (ssb_arch_register_fallback_sprom(&bcm47xx_get_sprom_ssb))
+ pr_warn("Failed to register ssb SPROM handler\n");
+#endif
+
+#if IS_BUILTIN(CONFIG_BCMA)
+ if (bcma_arch_register_fallback_sprom(&bcm47xx_get_sprom_bcma))
+ pr_warn("Failed to register bcma SPROM handler\n");
+#endif
+
+ bcm47xx_sprom_registered = 1;
+
+ return 0;
+}
+
+fs_initcall(bcm47xx_sprom_register_fallbacks);
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 4dffccf532a2..c089f49b63fb 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -197,7 +197,7 @@ void gic_write_cpu_compare(cycle_t cnt, int cpu)
local_irq_save(flags);
- gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR), cpu);
+ gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR), mips_cm_vp_id(cpu));
if (mips_cm_is64) {
gic_write(GIC_REG(VPE_OTHER, GIC_VPE_COMPARE), cnt);
@@ -246,6 +246,14 @@ void gic_stop_count(void)
#endif
+unsigned gic_read_local_vp_id(void)
+{
+ unsigned long ident;
+
+ ident = gic_read(GIC_REG(VPE_LOCAL, GIC_VP_IDENT));
+ return ident & GIC_VP_IDENT_VCNUM_MSK;
+}
+
static bool gic_local_irq_is_routable(int intr)
{
u32 vpe_ctl;
@@ -553,7 +561,8 @@ static void gic_mask_local_irq_all_vpes(struct irq_data *d)
spin_lock_irqsave(&gic_lock, flags);
for (i = 0; i < gic_vpes; i++) {
- gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR), i);
+ gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR),
+ mips_cm_vp_id(i));
gic_write32(GIC_REG(VPE_OTHER, GIC_VPE_RMASK), 1 << intr);
}
spin_unlock_irqrestore(&gic_lock, flags);
@@ -567,7 +576,8 @@ static void gic_unmask_local_irq_all_vpes(struct irq_data *d)
spin_lock_irqsave(&gic_lock, flags);
for (i = 0; i < gic_vpes; i++) {
- gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR), i);
+ gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR),
+ mips_cm_vp_id(i));
gic_write32(GIC_REG(VPE_OTHER, GIC_VPE_SMASK), 1 << intr);
}
spin_unlock_irqrestore(&gic_lock, flags);
@@ -607,7 +617,8 @@ static void __init gic_basic_init(void)
for (i = 0; i < gic_vpes; i++) {
unsigned int j;
- gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR), i);
+ gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR),
+ mips_cm_vp_id(i));
for (j = 0; j < GIC_NUM_LOCAL_INTRS; j++) {
if (!gic_local_irq_is_routable(j))
continue;
@@ -652,7 +663,8 @@ static int gic_local_irq_domain_map(struct irq_domain *d, unsigned int virq,
for (i = 0; i < gic_vpes; i++) {
u32 val = GIC_MAP_TO_PIN_MSK | gic_cpu_pin;
- gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR), i);
+ gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_OTHER_ADDR),
+ mips_cm_vp_id(i));
switch (intr) {
case GIC_LOCAL_INT_WD:
diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
index 125e569017be..b3ae30a4c67b 100644
--- a/drivers/platform/mips/Kconfig
+++ b/drivers/platform/mips/Kconfig
@@ -15,10 +15,6 @@ menuconfig MIPS_PLATFORM_DEVICES
if MIPS_PLATFORM_DEVICES
-config MIPS_ACPI
- bool
- default y if LOONGSON_MACH3X
-
config CPU_HWMON
tristate "Loongson CPU HWMon Driver"
depends on LOONGSON_MACH3X
diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile
index 43412849b195..8dfd03924c37 100644
--- a/drivers/platform/mips/Makefile
+++ b/drivers/platform/mips/Makefile
@@ -1,2 +1 @@
-obj-$(CONFIG_MIPS_ACPI) += acpi_init.o
obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o
diff --git a/drivers/platform/mips/acpi_init.c b/drivers/platform/mips/acpi_init.c
deleted file mode 100644
index dbdad79ead8f..000000000000
--- a/drivers/platform/mips/acpi_init.c
+++ /dev/null
@@ -1,150 +0,0 @@
-#include <linux/io.h>
-#include <linux/init.h>
-#include <linux/ioport.h>
-#include <linux/export.h>
-
-#define SBX00_ACPI_IO_BASE 0x800
-#define SBX00_ACPI_IO_SIZE 0x100
-
-#define ACPI_PM_EVT_BLK (SBX00_ACPI_IO_BASE + 0x00) /* 4 bytes */
-#define ACPI_PM_CNT_BLK (SBX00_ACPI_IO_BASE + 0x04) /* 2 bytes */
-#define ACPI_PMA_CNT_BLK (SBX00_ACPI_IO_BASE + 0x0F) /* 1 byte */
-#define ACPI_PM_TMR_BLK (SBX00_ACPI_IO_BASE + 0x18) /* 4 bytes */
-#define ACPI_GPE0_BLK (SBX00_ACPI_IO_BASE + 0x10) /* 8 bytes */
-#define ACPI_END (SBX00_ACPI_IO_BASE + 0x80)
-
-#define PM_INDEX 0xCD6
-#define PM_DATA 0xCD7
-#define PM2_INDEX 0xCD0
-#define PM2_DATA 0xCD1
-
-/*
- * SCI interrupt need acpi space, allocate here
- */
-
-static int __init register_acpi_resource(void)
-{
- request_region(SBX00_ACPI_IO_BASE, SBX00_ACPI_IO_SIZE, "acpi");
- return 0;
-}
-
-static void pmio_write_index(u16 index, u8 reg, u8 value)
-{
- outb(reg, index);
- outb(value, index + 1);
-}
-
-static u8 pmio_read_index(u16 index, u8 reg)
-{
- outb(reg, index);
- return inb(index + 1);
-}
-
-void pm_iowrite(u8 reg, u8 value)
-{
- pmio_write_index(PM_INDEX, reg, value);
-}
-EXPORT_SYMBOL(pm_iowrite);
-
-u8 pm_ioread(u8 reg)
-{
- return pmio_read_index(PM_INDEX, reg);
-}
-EXPORT_SYMBOL(pm_ioread);
-
-void pm2_iowrite(u8 reg, u8 value)
-{
- pmio_write_index(PM2_INDEX, reg, value);
-}
-EXPORT_SYMBOL(pm2_iowrite);
-
-u8 pm2_ioread(u8 reg)
-{
- return pmio_read_index(PM2_INDEX, reg);
-}
-EXPORT_SYMBOL(pm2_ioread);
-
-static void acpi_hw_clear_status(void)
-{
- u16 value;
-
- /* PMStatus: Clear WakeStatus/PwrBtnStatus */
- value = inw(ACPI_PM_EVT_BLK);
- value |= (1 << 8 | 1 << 15);
- outw(value, ACPI_PM_EVT_BLK);
-
- /* GPEStatus: Clear all generated events */
- outl(inl(ACPI_GPE0_BLK), ACPI_GPE0_BLK);
-}
-
-void acpi_registers_setup(void)
-{
- u32 value;
-
- /* PM Status Base */
- pm_iowrite(0x20, ACPI_PM_EVT_BLK & 0xff);
- pm_iowrite(0x21, ACPI_PM_EVT_BLK >> 8);
-
- /* PM Control Base */
- pm_iowrite(0x22, ACPI_PM_CNT_BLK & 0xff);
- pm_iowrite(0x23, ACPI_PM_CNT_BLK >> 8);
-
- /* GPM Base */
- pm_iowrite(0x28, ACPI_GPE0_BLK & 0xff);
- pm_iowrite(0x29, ACPI_GPE0_BLK >> 8);
-
- /* ACPI End */
- pm_iowrite(0x2e, ACPI_END & 0xff);
- pm_iowrite(0x2f, ACPI_END >> 8);
-
- /* IO Decode: When AcpiDecodeEnable set, South-Bridge uses the contents
- * of the PM registers at index 0x20~0x2B to decode ACPI I/O address. */
- pm_iowrite(0x0e, 1 << 3);
-
- /* SCI_EN set */
- outw(1, ACPI_PM_CNT_BLK);
-
- /* Enable to generate SCI */
- pm_iowrite(0x10, pm_ioread(0x10) | 1);
-
- /* GPM3/GPM9 enable */
- value = inl(ACPI_GPE0_BLK + 4);
- outl(value | (1 << 14) | (1 << 22), ACPI_GPE0_BLK + 4);
-
- /* Set GPM9 as input */
- pm_iowrite(0x8d, pm_ioread(0x8d) & (~(1 << 1)));
-
- /* Set GPM9 as non-output */
- pm_iowrite(0x94, pm_ioread(0x94) | (1 << 3));
-
- /* GPM3 config ACPI trigger SCIOUT */
- pm_iowrite(0x33, pm_ioread(0x33) & (~(3 << 4)));
-
- /* GPM9 config ACPI trigger SCIOUT */
- pm_iowrite(0x3d, pm_ioread(0x3d) & (~(3 << 2)));
-
- /* GPM3 config falling edge trigger */
- pm_iowrite(0x37, pm_ioread(0x37) & (~(1 << 6)));
-
- /* No wait for STPGNT# in ACPI Sx state */
- pm_iowrite(0x7c, pm_ioread(0x7c) | (1 << 6));
-
- /* Set GPM3 pull-down enable */
- value = pm2_ioread(0xf6);
- value |= ((1 << 7) | (1 << 3));
- pm2_iowrite(0xf6, value);
-
- /* Set GPM9 pull-down enable */
- value = pm2_ioread(0xf8);
- value |= ((1 << 5) | (1 << 1));
- pm2_iowrite(0xf8, value);
-}
-
-int __init sbx00_acpi_init(void)
-{
- register_acpi_resource();
- acpi_registers_setup();
- acpi_hw_clear_status();
-
- return 0;
-}
diff --git a/drivers/platform/mips/cpu_hwmon.c b/drivers/platform/mips/cpu_hwmon.c
index 4993e19f1531..4300a558d0f3 100644
--- a/drivers/platform/mips/cpu_hwmon.c
+++ b/drivers/platform/mips/cpu_hwmon.c
@@ -20,9 +20,9 @@ int loongson3_cpu_temp(int cpu)
u32 reg;
reg = LOONGSON_CHIPTEMP(cpu);
- if (loongson_sysconf.cputype == Loongson_3A)
+ if ((read_c0_prid() & PRID_REV_MASK) == PRID_REV_LOONGSON3A_R1)
reg = (reg >> 8) & 0xff;
- else if (loongson_sysconf.cputype == Loongson_3B)
+ else
reg = ((reg >> 8) & 0xff) - 100;
return (int)reg * 1000;
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 13d4ed6caac4..7711b260dacf 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -900,6 +900,27 @@ config SERIAL_SGI_L1_CONSOLE
controller serial port as your console (you want this!),
say Y. Otherwise, say N.
+config SERIAL_PIC32
+ tristate "Microchip PIC32 serial support"
+ depends on MACH_PIC32
+ select SERIAL_CORE
+ help
+ If you have a PIC32, this driver supports the serial ports.
+
+ Say Y or M to use PIC32 serial ports, otherwise say N. Note that
+ to use a serial port as a console, this must be included in kernel and
+ not as a module.
+
+config SERIAL_PIC32_CONSOLE
+ bool "PIC32 serial console support"
+ depends on SERIAL_PIC32
+ select SERIAL_CORE_CONSOLE
+ help
+ If you have a PIC32, this driver supports the putting a console on one
+ of the serial ports.
+
+ Say Y to use the PIC32 console, otherwise say N.
+
config SERIAL_MPC52xx
tristate "Freescale MPC52xx/MPC512x family PSC serial support"
depends on PPC_MPC52xx || PPC_MPC512x
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 8c261adac04e..74914aafac61 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -91,6 +91,7 @@ obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o
obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o
obj-$(CONFIG_SERIAL_STM32) += stm32-usart.o
obj-$(CONFIG_SERIAL_MVEBU_UART) += mvebu-uart.o
+obj-$(CONFIG_SERIAL_PIC32) += pic32_uart.o
# GPIOLIB helpers for modem control lines
obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
diff --git a/drivers/tty/serial/pic32_uart.c b/drivers/tty/serial/pic32_uart.c
new file mode 100644
index 000000000000..62a43bf5698e
--- /dev/null
+++ b/drivers/tty/serial/pic32_uart.c
@@ -0,0 +1,960 @@
+/*
+ * PIC32 Integrated Serial Driver.
+ *
+ * Copyright (C) 2015 Microchip Technology, Inc.
+ *
+ * Authors:
+ * Sorin-Andrei Pistirica <andrei.pistirica@microchip.com>
+ *
+ * Licensed under GPLv2 or later.
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/console.h>
+#include <linux/clk.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/delay.h>
+
+#include <asm/mach-pic32/pic32.h>
+#include "pic32_uart.h"
+
+/* UART name and device definitions */
+#define PIC32_DEV_NAME "pic32-uart"
+#define PIC32_MAX_UARTS 6
+#define PIC32_SDEV_NAME "ttyPIC"
+
+/* pic32_sport pointer for console use */
+static struct pic32_sport *pic32_sports[PIC32_MAX_UARTS];
+
+static inline void pic32_wait_deplete_txbuf(struct pic32_sport *sport)
+{
+ /* wait for tx empty, otherwise chars will be lost or corrupted */
+ while (!(pic32_uart_readl(sport, PIC32_UART_STA) & PIC32_UART_STA_TRMT))
+ udelay(1);
+}
+
+static inline int pic32_enable_clock(struct pic32_sport *sport)
+{
+ int ret = clk_prepare_enable(sport->clk);
+
+ if (ret)
+ return ret;
+
+ sport->ref_clk++;
+ return 0;
+}
+
+static inline void pic32_disable_clock(struct pic32_sport *sport)
+{
+ sport->ref_clk--;
+ clk_disable_unprepare(sport->clk);
+}
+
+/* serial core request to check if uart tx buffer is empty */
+static unsigned int pic32_uart_tx_empty(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ u32 val = pic32_uart_readl(sport, PIC32_UART_STA);
+
+ return (val & PIC32_UART_STA_TRMT) ? 1 : 0;
+}
+
+/* serial core request to set UART outputs */
+static void pic32_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ /* set loopback mode */
+ if (mctrl & TIOCM_LOOP)
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_MODE),
+ PIC32_UART_MODE_LPBK);
+ else
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_LPBK);
+}
+
+/* get the state of CTS input pin for this port */
+static unsigned int get_cts_state(struct pic32_sport *sport)
+{
+ /* read and invert UxCTS */
+ if (gpio_is_valid(sport->cts_gpio))
+ return !gpio_get_value(sport->cts_gpio);
+
+ return 1;
+}
+
+/* serial core request to return the state of misc UART input pins */
+static unsigned int pic32_uart_get_mctrl(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ unsigned int mctrl = 0;
+
+ if (!sport->hw_flow_ctrl)
+ mctrl |= TIOCM_CTS;
+ else if (get_cts_state(sport))
+ mctrl |= TIOCM_CTS;
+
+ /* DSR and CD are not supported in PIC32, so return 1
+ * RI is not supported in PIC32, so return 0
+ */
+ mctrl |= TIOCM_CD;
+ mctrl |= TIOCM_DSR;
+
+ return mctrl;
+}
+
+/* stop tx and start tx are not called in pairs, therefore a flag indicates
+ * the status of irq to control the irq-depth.
+ */
+static inline void pic32_uart_irqtxen(struct pic32_sport *sport, u8 en)
+{
+ if (en && !tx_irq_enabled(sport)) {
+ enable_irq(sport->irq_tx);
+ tx_irq_enabled(sport) = 1;
+ } else if (!en && tx_irq_enabled(sport)) {
+ /* use disable_irq_nosync() and not disable_irq() to avoid self
+ * imposed deadlock by not waiting for irq handler to end,
+ * since this callback is called from interrupt context.
+ */
+ disable_irq_nosync(sport->irq_tx);
+ tx_irq_enabled(sport) = 0;
+ }
+}
+
+/* serial core request to disable tx ASAP (used for flow control) */
+static void pic32_uart_stop_tx(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ if (!(pic32_uart_readl(sport, PIC32_UART_MODE) & PIC32_UART_MODE_ON))
+ return;
+
+ if (!(pic32_uart_readl(sport, PIC32_UART_STA) & PIC32_UART_STA_UTXEN))
+ return;
+
+ /* wait for tx empty */
+ pic32_wait_deplete_txbuf(sport);
+
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
+ PIC32_UART_STA_UTXEN);
+ pic32_uart_irqtxen(sport, 0);
+}
+
+/* serial core request to (re)enable tx */
+static void pic32_uart_start_tx(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ pic32_uart_irqtxen(sport, 1);
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_STA),
+ PIC32_UART_STA_UTXEN);
+}
+
+/* serial core request to stop rx, called before port shutdown */
+static void pic32_uart_stop_rx(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ /* disable rx interrupts */
+ disable_irq(sport->irq_rx);
+
+ /* receiver Enable bit OFF */
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
+ PIC32_UART_STA_URXEN);
+}
+
+/* serial core request to start/stop emitting break char */
+static void pic32_uart_break_ctl(struct uart_port *port, int ctl)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ if (ctl)
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_STA),
+ PIC32_UART_STA_UTXBRK);
+ else
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
+ PIC32_UART_STA_UTXBRK);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+/* get port type in string format */
+static const char *pic32_uart_type(struct uart_port *port)
+{
+ return (port->type == PORT_PIC32) ? PIC32_DEV_NAME : NULL;
+}
+
+/* read all chars in rx fifo and send them to core */
+static void pic32_uart_do_rx(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ struct tty_port *tty;
+ unsigned int max_count;
+
+ /* limit number of char read in interrupt, should not be
+ * higher than fifo size anyway since we're much faster than
+ * serial port
+ */
+ max_count = PIC32_UART_RX_FIFO_DEPTH;
+
+ spin_lock(&port->lock);
+
+ tty = &port->state->port;
+
+ do {
+ u32 sta_reg, c;
+ char flag;
+
+ /* get overrun/fifo empty information from status register */
+ sta_reg = pic32_uart_readl(sport, PIC32_UART_STA);
+ if (unlikely(sta_reg & PIC32_UART_STA_OERR)) {
+
+ /* fifo reset is required to clear interrupt */
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
+ PIC32_UART_STA_OERR);
+
+ port->icount.overrun++;
+ tty_insert_flip_char(tty, 0, TTY_OVERRUN);
+ }
+
+ /* Can at least one more character can be read? */
+ if (!(sta_reg & PIC32_UART_STA_URXDA))
+ break;
+
+ /* read the character and increment the rx counter */
+ c = pic32_uart_readl(sport, PIC32_UART_RX);
+
+ port->icount.rx++;
+ flag = TTY_NORMAL;
+ c &= 0xff;
+
+ if (unlikely((sta_reg & PIC32_UART_STA_PERR) ||
+ (sta_reg & PIC32_UART_STA_FERR))) {
+
+ /* do stats first */
+ if (sta_reg & PIC32_UART_STA_PERR)
+ port->icount.parity++;
+ if (sta_reg & PIC32_UART_STA_FERR)
+ port->icount.frame++;
+
+ /* update flag wrt read_status_mask */
+ sta_reg &= port->read_status_mask;
+
+ if (sta_reg & PIC32_UART_STA_FERR)
+ flag = TTY_FRAME;
+ if (sta_reg & PIC32_UART_STA_PERR)
+ flag = TTY_PARITY;
+ }
+
+ if (uart_handle_sysrq_char(port, c))
+ continue;
+
+ if ((sta_reg & port->ignore_status_mask) == 0)
+ tty_insert_flip_char(tty, c, flag);
+
+ } while (--max_count);
+
+ spin_unlock(&port->lock);
+
+ tty_flip_buffer_push(tty);
+}
+
+/* fill tx fifo with chars to send, stop when fifo is about to be full
+ * or when all chars have been sent.
+ */
+static void pic32_uart_do_tx(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ struct circ_buf *xmit = &port->state->xmit;
+ unsigned int max_count = PIC32_UART_TX_FIFO_DEPTH;
+
+ if (port->x_char) {
+ pic32_uart_writel(sport, PIC32_UART_TX, port->x_char);
+ port->icount.tx++;
+ port->x_char = 0;
+ return;
+ }
+
+ if (uart_tx_stopped(port)) {
+ pic32_uart_stop_tx(port);
+ return;
+ }
+
+ if (uart_circ_empty(xmit))
+ goto txq_empty;
+
+ /* keep stuffing chars into uart tx buffer
+ * 1) until uart fifo is full
+ * or
+ * 2) until the circ buffer is empty
+ * (all chars have been sent)
+ * or
+ * 3) until the max count is reached
+ * (prevents lingering here for too long in certain cases)
+ */
+ while (!(PIC32_UART_STA_UTXBF &
+ pic32_uart_readl(sport, PIC32_UART_STA))) {
+ unsigned int c = xmit->buf[xmit->tail];
+
+ pic32_uart_writel(sport, PIC32_UART_TX, c);
+
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ port->icount.tx++;
+ if (uart_circ_empty(xmit))
+ break;
+ if (--max_count == 0)
+ break;
+ }
+
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+
+ if (uart_circ_empty(xmit))
+ goto txq_empty;
+
+ return;
+
+txq_empty:
+ pic32_uart_irqtxen(sport, 0);
+}
+
+/* RX interrupt handler */
+static irqreturn_t pic32_uart_rx_interrupt(int irq, void *dev_id)
+{
+ struct uart_port *port = dev_id;
+
+ pic32_uart_do_rx(port);
+
+ return IRQ_HANDLED;
+}
+
+/* TX interrupt handler */
+static irqreturn_t pic32_uart_tx_interrupt(int irq, void *dev_id)
+{
+ struct uart_port *port = dev_id;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+ pic32_uart_do_tx(port);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+/* FAULT interrupt handler */
+static irqreturn_t pic32_uart_fault_interrupt(int irq, void *dev_id)
+{
+ /* do nothing: pic32_uart_do_rx() handles faults. */
+ return IRQ_HANDLED;
+}
+
+/* enable rx & tx operation on uart */
+static void pic32_uart_en_and_unmask(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_STA),
+ PIC32_UART_STA_UTXEN | PIC32_UART_STA_URXEN);
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_MODE),
+ PIC32_UART_MODE_ON);
+}
+
+/* disable rx & tx operation on uart */
+static void pic32_uart_dsbl_and_mask(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ /* wait for tx empty, otherwise chars will be lost or corrupted */
+ pic32_wait_deplete_txbuf(sport);
+
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
+ PIC32_UART_STA_UTXEN | PIC32_UART_STA_URXEN);
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_ON);
+}
+
+/* serial core request to initialize uart and start rx operation */
+static int pic32_uart_startup(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ u32 dflt_baud = (port->uartclk / PIC32_UART_DFLT_BRATE / 16) - 1;
+ unsigned long flags;
+ int ret;
+
+ local_irq_save(flags);
+
+ ret = pic32_enable_clock(sport);
+ if (ret) {
+ local_irq_restore(flags);
+ goto out_done;
+ }
+
+ /* clear status and mode registers */
+ pic32_uart_writel(sport, PIC32_UART_MODE, 0);
+ pic32_uart_writel(sport, PIC32_UART_STA, 0);
+
+ /* disable uart and mask all interrupts */
+ pic32_uart_dsbl_and_mask(port);
+
+ /* set default baud */
+ pic32_uart_writel(sport, PIC32_UART_BRG, dflt_baud);
+
+ local_irq_restore(flags);
+
+ /* Each UART of a PIC32 has three interrupts therefore,
+ * we setup driver to register the 3 irqs for the device.
+ *
+ * For each irq request_irq() is called with interrupt disabled.
+ * And the irq is enabled as soon as we are ready to handle them.
+ */
+ tx_irq_enabled(sport) = 0;
+
+ sport->irq_fault_name = kasprintf(GFP_KERNEL, "%s%d-fault",
+ pic32_uart_type(port),
+ sport->idx);
+ if (!sport->irq_fault_name) {
+ dev_err(port->dev, "%s: kasprintf err!", __func__);
+ ret = -ENOMEM;
+ goto out_done;
+ }
+ irq_set_status_flags(sport->irq_fault, IRQ_NOAUTOEN);
+ ret = request_irq(sport->irq_fault, pic32_uart_fault_interrupt,
+ sport->irqflags_fault, sport->irq_fault_name, port);
+ if (ret) {
+ dev_err(port->dev, "%s: request irq(%d) err! ret:%d name:%s\n",
+ __func__, sport->irq_fault, ret,
+ pic32_uart_type(port));
+ goto out_f;
+ }
+
+ sport->irq_rx_name = kasprintf(GFP_KERNEL, "%s%d-rx",
+ pic32_uart_type(port),
+ sport->idx);
+ if (!sport->irq_rx_name) {
+ dev_err(port->dev, "%s: kasprintf err!", __func__);
+ kfree(sport->irq_fault_name);
+ ret = -ENOMEM;
+ goto out_f;
+ }
+ irq_set_status_flags(sport->irq_rx, IRQ_NOAUTOEN);
+ ret = request_irq(sport->irq_rx, pic32_uart_rx_interrupt,
+ sport->irqflags_rx, sport->irq_rx_name, port);
+ if (ret) {
+ dev_err(port->dev, "%s: request irq(%d) err! ret:%d name:%s\n",
+ __func__, sport->irq_rx, ret,
+ pic32_uart_type(port));
+ goto out_r;
+ }
+
+ sport->irq_tx_name = kasprintf(GFP_KERNEL, "%s%d-tx",
+ pic32_uart_type(port),
+ sport->idx);
+ if (!sport->irq_tx_name) {
+ dev_err(port->dev, "%s: kasprintf err!", __func__);
+ ret = -ENOMEM;
+ goto out_r;
+ }
+ irq_set_status_flags(sport->irq_tx, IRQ_NOAUTOEN);
+ ret = request_irq(sport->irq_tx, pic32_uart_tx_interrupt,
+ sport->irqflags_tx, sport->irq_tx_name, port);
+ if (ret) {
+ dev_err(port->dev, "%s: request irq(%d) err! ret:%d name:%s\n",
+ __func__, sport->irq_tx, ret,
+ pic32_uart_type(port));
+ goto out_t;
+ }
+
+ local_irq_save(flags);
+
+ /* set rx interrupt on first receive */
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
+ PIC32_UART_STA_URXISEL1 | PIC32_UART_STA_URXISEL0);
+
+ /* set interrupt on empty */
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
+ PIC32_UART_STA_UTXISEL1);
+
+ /* enable all interrupts and eanable uart */
+ pic32_uart_en_and_unmask(port);
+
+ enable_irq(sport->irq_rx);
+
+ return 0;
+
+out_t:
+ kfree(sport->irq_tx_name);
+ free_irq(sport->irq_tx, sport);
+out_r:
+ kfree(sport->irq_rx_name);
+ free_irq(sport->irq_rx, sport);
+out_f:
+ kfree(sport->irq_fault_name);
+ free_irq(sport->irq_fault, sport);
+out_done:
+ return ret;
+}
+
+/* serial core request to flush & disable uart */
+static void pic32_uart_shutdown(struct uart_port *port)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ unsigned long flags;
+
+ /* disable uart */
+ spin_lock_irqsave(&port->lock, flags);
+ pic32_uart_dsbl_and_mask(port);
+ spin_unlock_irqrestore(&port->lock, flags);
+ pic32_disable_clock(sport);
+
+ /* free all 3 interrupts for this UART */
+ free_irq(sport->irq_fault, port);
+ free_irq(sport->irq_tx, port);
+ free_irq(sport->irq_rx, port);
+}
+
+/* serial core request to change current uart setting */
+static void pic32_uart_set_termios(struct uart_port *port,
+ struct ktermios *new,
+ struct ktermios *old)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+ unsigned int baud;
+ unsigned int quot;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ /* disable uart and mask all interrupts while changing speed */
+ pic32_uart_dsbl_and_mask(port);
+
+ /* stop bit options */
+ if (new->c_cflag & CSTOPB)
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_MODE),
+ PIC32_UART_MODE_STSEL);
+ else
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_STSEL);
+
+ /* parity options */
+ if (new->c_cflag & PARENB) {
+ if (new->c_cflag & PARODD) {
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_MODE),
+ PIC32_UART_MODE_PDSEL1);
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_PDSEL0);
+ } else {
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_MODE),
+ PIC32_UART_MODE_PDSEL0);
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_PDSEL1);
+ }
+ } else {
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_PDSEL1 |
+ PIC32_UART_MODE_PDSEL0);
+ }
+ /* if hw flow ctrl, then the pins must be specified in device tree */
+ if ((new->c_cflag & CRTSCTS) && sport->hw_flow_ctrl) {
+ /* enable hardware flow control */
+ pic32_uart_writel(sport, PIC32_SET(PIC32_UART_MODE),
+ PIC32_UART_MODE_UEN1);
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_UEN0);
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_RTSMD);
+ } else {
+ /* disable hardware flow control */
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_UEN1);
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_UEN0);
+ pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
+ PIC32_UART_MODE_RTSMD);
+ }
+
+ /* Always 8-bit */
+ new->c_cflag |= CS8;
+
+ /* Mark/Space parity is not supported */
+ new->c_cflag &= ~CMSPAR;
+
+ /* update baud */
+ baud = uart_get_baud_rate(port, new, old, 0, port->uartclk / 16);
+ quot = uart_get_divisor(port, baud) - 1;
+ pic32_uart_writel(sport, PIC32_UART_BRG, quot);
+ uart_update_timeout(port, new->c_cflag, baud);
+
+ if (tty_termios_baud_rate(new))
+ tty_termios_encode_baud_rate(new, baud, baud);
+
+ /* enable uart */
+ pic32_uart_en_and_unmask(port);
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+/* serial core request to claim uart iomem */
+static int pic32_uart_request_port(struct uart_port *port)
+{
+ struct platform_device *pdev = to_platform_device(port->dev);
+ struct resource *res_mem;
+
+ res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (unlikely(!res_mem))
+ return -EINVAL;
+
+ if (!request_mem_region(port->mapbase, resource_size(res_mem),
+ "pic32_uart_mem"))
+ return -EBUSY;
+
+ port->membase = devm_ioremap_nocache(port->dev, port->mapbase,
+ resource_size(res_mem));
+ if (!port->membase) {
+ dev_err(port->dev, "Unable to map registers\n");
+ release_mem_region(port->mapbase, resource_size(res_mem));
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/* serial core request to release uart iomem */
+static void pic32_uart_release_port(struct uart_port *port)
+{
+ struct platform_device *pdev = to_platform_device(port->dev);
+ struct resource *res_mem;
+ unsigned int res_size;
+
+ res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (unlikely(!res_mem))
+ return;
+ res_size = resource_size(res_mem);
+
+ release_mem_region(port->mapbase, res_size);
+}
+
+/* serial core request to do any port required auto-configuration */
+static void pic32_uart_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE) {
+ if (pic32_uart_request_port(port))
+ return;
+ port->type = PORT_PIC32;
+ }
+}
+
+/* serial core request to check that port information in serinfo are suitable */
+static int pic32_uart_verify_port(struct uart_port *port,
+ struct serial_struct *serinfo)
+{
+ if (port->type != PORT_PIC32)
+ return -EINVAL;
+ if (port->irq != serinfo->irq)
+ return -EINVAL;
+ if (port->iotype != serinfo->io_type)
+ return -EINVAL;
+ if (port->mapbase != (unsigned long)serinfo->iomem_base)
+ return -EINVAL;
+
+ return 0;
+}
+
+/* serial core callbacks */
+static const struct uart_ops pic32_uart_ops = {
+ .tx_empty = pic32_uart_tx_empty,
+ .get_mctrl = pic32_uart_get_mctrl,
+ .set_mctrl = pic32_uart_set_mctrl,
+ .start_tx = pic32_uart_start_tx,
+ .stop_tx = pic32_uart_stop_tx,
+ .stop_rx = pic32_uart_stop_rx,
+ .break_ctl = pic32_uart_break_ctl,
+ .startup = pic32_uart_startup,
+ .shutdown = pic32_uart_shutdown,
+ .set_termios = pic32_uart_set_termios,
+ .type = pic32_uart_type,
+ .release_port = pic32_uart_release_port,
+ .request_port = pic32_uart_request_port,
+ .config_port = pic32_uart_config_port,
+ .verify_port = pic32_uart_verify_port,
+};
+
+#ifdef CONFIG_SERIAL_PIC32_CONSOLE
+/* output given char */
+static void pic32_console_putchar(struct uart_port *port, int ch)
+{
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ if (!(pic32_uart_readl(sport, PIC32_UART_MODE) & PIC32_UART_MODE_ON))
+ return;
+
+ if (!(pic32_uart_readl(sport, PIC32_UART_STA) & PIC32_UART_STA_UTXEN))
+ return;
+
+ /* wait for tx empty */
+ pic32_wait_deplete_txbuf(sport);
+
+ pic32_uart_writel(sport, PIC32_UART_TX, ch & 0xff);
+}
+
+/* console core request to output given string */
+static void pic32_console_write(struct console *co, const char *s,
+ unsigned int count)
+{
+ struct pic32_sport *sport = pic32_sports[co->index];
+ struct uart_port *port = pic32_get_port(sport);
+
+ /* call uart helper to deal with \r\n */
+ uart_console_write(port, s, count, pic32_console_putchar);
+}
+
+/* console core request to setup given console, find matching uart
+ * port and setup it.
+ */
+static int pic32_console_setup(struct console *co, char *options)
+{
+ struct pic32_sport *sport;
+ struct uart_port *port = NULL;
+ int baud = 115200;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+ int ret = 0;
+
+ if (unlikely(co->index < 0 || co->index >= PIC32_MAX_UARTS))
+ return -ENODEV;
+
+ sport = pic32_sports[co->index];
+ if (!sport)
+ return -ENODEV;
+ port = pic32_get_port(sport);
+
+ ret = pic32_enable_clock(sport);
+ if (ret)
+ return ret;
+
+ if (options)
+ uart_parse_options(options, &baud, &parity, &bits, &flow);
+
+ return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct uart_driver pic32_uart_driver;
+static struct console pic32_console = {
+ .name = PIC32_SDEV_NAME,
+ .write = pic32_console_write,
+ .device = uart_console_device,
+ .setup = pic32_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ .data = &pic32_uart_driver,
+};
+#define PIC32_SCONSOLE (&pic32_console)
+
+static int __init pic32_console_init(void)
+{
+ register_console(&pic32_console);
+ return 0;
+}
+console_initcall(pic32_console_init);
+
+static inline bool is_pic32_console_port(struct uart_port *port)
+{
+ return port->cons && port->cons->index == port->line;
+}
+
+/*
+ * Late console initialization.
+ */
+static int __init pic32_late_console_init(void)
+{
+ if (!(pic32_console.flags & CON_ENABLED))
+ register_console(&pic32_console);
+
+ return 0;
+}
+
+core_initcall(pic32_late_console_init);
+
+#else
+#define PIC32_SCONSOLE NULL
+#endif
+
+static struct uart_driver pic32_uart_driver = {
+ .owner = THIS_MODULE,
+ .driver_name = PIC32_DEV_NAME,
+ .dev_name = PIC32_SDEV_NAME,
+ .nr = PIC32_MAX_UARTS,
+ .cons = PIC32_SCONSOLE,
+};
+
+static int pic32_uart_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct pic32_sport *sport;
+ int uart_idx = 0;
+ struct resource *res_mem;
+ struct uart_port *port;
+ int ret;
+
+ uart_idx = of_alias_get_id(np, "serial");
+ if (uart_idx < 0 || uart_idx >= PIC32_MAX_UARTS)
+ return -EINVAL;
+
+ res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_mem)
+ return -EINVAL;
+
+ sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
+ if (!sport)
+ return -ENOMEM;
+
+ sport->idx = uart_idx;
+ sport->irq_fault = irq_of_parse_and_map(np, 0);
+ sport->irqflags_fault = IRQF_NO_THREAD;
+ sport->irq_rx = irq_of_parse_and_map(np, 1);
+ sport->irqflags_rx = IRQF_NO_THREAD;
+ sport->irq_tx = irq_of_parse_and_map(np, 2);
+ sport->irqflags_tx = IRQF_NO_THREAD;
+ sport->clk = devm_clk_get(&pdev->dev, NULL);
+ sport->cts_gpio = -EINVAL;
+ sport->dev = &pdev->dev;
+
+ /* Hardware flow control: gpios
+ * !Note: Basically, CTS is needed for reading the status.
+ */
+ sport->hw_flow_ctrl = false;
+ sport->cts_gpio = of_get_named_gpio(np, "cts-gpios", 0);
+ if (gpio_is_valid(sport->cts_gpio)) {
+ sport->hw_flow_ctrl = true;
+
+ ret = devm_gpio_request(sport->dev,
+ sport->cts_gpio, "CTS");
+ if (ret) {
+ dev_err(&pdev->dev,
+ "error requesting CTS GPIO\n");
+ goto err;
+ }
+
+ ret = gpio_direction_input(sport->cts_gpio);
+ if (ret) {
+ dev_err(&pdev->dev, "error setting CTS GPIO\n");
+ goto err;
+ }
+ }
+
+ pic32_sports[uart_idx] = sport;
+ port = &sport->port;
+ memset(port, 0, sizeof(*port));
+ port->iotype = UPIO_MEM;
+ port->mapbase = res_mem->start;
+ port->ops = &pic32_uart_ops;
+ port->flags = UPF_BOOT_AUTOCONF;
+ port->dev = &pdev->dev;
+ port->fifosize = PIC32_UART_TX_FIFO_DEPTH;
+ port->uartclk = clk_get_rate(sport->clk);
+ port->line = uart_idx;
+
+ ret = uart_add_one_port(&pic32_uart_driver, port);
+ if (ret) {
+ port->membase = NULL;
+ dev_err(port->dev, "%s: uart add port error!\n", __func__);
+ goto err;
+ }
+
+#ifdef CONFIG_SERIAL_PIC32_CONSOLE
+ if (is_pic32_console_port(port) &&
+ (pic32_console.flags & CON_ENABLED)) {
+ /* The peripheral clock has been enabled by console_setup,
+ * so disable it till the port is used.
+ */
+ pic32_disable_clock(sport);
+ }
+#endif
+
+ platform_set_drvdata(pdev, port);
+
+ dev_info(&pdev->dev, "%s: uart(%d) driver initialized.\n",
+ __func__, uart_idx);
+
+ return 0;
+err:
+ /* automatic unroll of sport and gpios */
+ return ret;
+}
+
+static int pic32_uart_remove(struct platform_device *pdev)
+{
+ struct uart_port *port = platform_get_drvdata(pdev);
+ struct pic32_sport *sport = to_pic32_sport(port);
+
+ uart_remove_one_port(&pic32_uart_driver, port);
+ pic32_disable_clock(sport);
+ platform_set_drvdata(pdev, NULL);
+ pic32_sports[sport->idx] = NULL;
+
+ /* automatic unroll of sport and gpios */
+ return 0;
+}
+
+static const struct of_device_id pic32_serial_dt_ids[] = {
+ { .compatible = "microchip,pic32mzda-uart" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pic32_serial_dt_ids);
+
+static struct platform_driver pic32_uart_platform_driver = {
+ .probe = pic32_uart_probe,
+ .remove = pic32_uart_remove,
+ .driver = {
+ .name = PIC32_DEV_NAME,
+ .of_match_table = of_match_ptr(pic32_serial_dt_ids),
+ },
+};
+
+static int __init pic32_uart_init(void)
+{
+ int ret;
+
+ ret = uart_register_driver(&pic32_uart_driver);
+ if (ret) {
+ pr_err("failed to register %s:%d\n",
+ pic32_uart_driver.driver_name, ret);
+ return ret;
+ }
+
+ ret = platform_driver_register(&pic32_uart_platform_driver);
+ if (ret) {
+ pr_err("fail to register pic32 uart\n");
+ uart_unregister_driver(&pic32_uart_driver);
+ }
+
+ return ret;
+}
+arch_initcall(pic32_uart_init);
+
+static void __exit pic32_uart_exit(void)
+{
+#ifdef CONFIG_SERIAL_PIC32_CONSOLE
+ unregister_console(&pic32_console);
+#endif
+ platform_driver_unregister(&pic32_uart_platform_driver);
+ uart_unregister_driver(&pic32_uart_driver);
+}
+module_exit(pic32_uart_exit);
+
+MODULE_AUTHOR("Sorin-Andrei Pistirica <andrei.pistirica@microchip.com>");
+MODULE_DESCRIPTION("Microchip PIC32 integrated serial port driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/tty/serial/pic32_uart.h b/drivers/tty/serial/pic32_uart.h
new file mode 100644
index 000000000000..ec379da55ebb
--- /dev/null
+++ b/drivers/tty/serial/pic32_uart.h
@@ -0,0 +1,126 @@
+/*
+ * PIC32 Integrated Serial Driver.
+ *
+ * Copyright (C) 2015 Microchip Technology, Inc.
+ *
+ * Authors:
+ * Sorin-Andrei Pistirica <andrei.pistirica@microchip.com>
+ *
+ * Licensed under GPLv2 or later.
+ */
+#ifndef __DT_PIC32_UART_H__
+#define __DT_PIC32_UART_H__
+
+#define PIC32_UART_DFLT_BRATE (9600)
+#define PIC32_UART_TX_FIFO_DEPTH (8)
+#define PIC32_UART_RX_FIFO_DEPTH (8)
+
+#define PIC32_UART_MODE 0x00
+#define PIC32_UART_STA 0x10
+#define PIC32_UART_TX 0x20
+#define PIC32_UART_RX 0x30
+#define PIC32_UART_BRG 0x40
+
+struct pic32_console_opt {
+ int baud;
+ int parity;
+ int bits;
+ int flow;
+};
+
+/* struct pic32_sport - pic32 serial port descriptor
+ * @port: uart port descriptor
+ * @idx: port index
+ * @irq_fault: virtual fault interrupt number
+ * @irqflags_fault: flags related to fault irq
+ * @irq_fault_name: irq fault name
+ * @irq_rx: virtual rx interrupt number
+ * @irqflags_rx: flags related to rx irq
+ * @irq_rx_name: irq rx name
+ * @irq_tx: virtual tx interrupt number
+ * @irqflags_tx: : flags related to tx irq
+ * @irq_tx_name: irq tx name
+ * @cts_gpio: clear to send gpio
+ * @dev: device descriptor
+ **/
+struct pic32_sport {
+ struct uart_port port;
+ struct pic32_console_opt opt;
+ int idx;
+
+ int irq_fault;
+ int irqflags_fault;
+ const char *irq_fault_name;
+ int irq_rx;
+ int irqflags_rx;
+ const char *irq_rx_name;
+ int irq_tx;
+ int irqflags_tx;
+ const char *irq_tx_name;
+ u8 enable_tx_irq;
+
+ bool hw_flow_ctrl;
+ int cts_gpio;
+
+ int ref_clk;
+ struct clk *clk;
+
+ struct device *dev;
+};
+#define to_pic32_sport(c) container_of(c, struct pic32_sport, port)
+#define pic32_get_port(sport) (&sport->port)
+#define pic32_get_opt(sport) (&sport->opt)
+#define tx_irq_enabled(sport) (sport->enable_tx_irq)
+
+static inline void pic32_uart_writel(struct pic32_sport *sport,
+ u32 reg, u32 val)
+{
+ struct uart_port *port = pic32_get_port(sport);
+
+ __raw_writel(val, port->membase + reg);
+}
+
+static inline u32 pic32_uart_readl(struct pic32_sport *sport, u32 reg)
+{
+ struct uart_port *port = pic32_get_port(sport);
+
+ return __raw_readl(port->membase + reg);
+}
+
+/* pic32 uart mode register bits */
+#define PIC32_UART_MODE_ON BIT(15)
+#define PIC32_UART_MODE_FRZ BIT(14)
+#define PIC32_UART_MODE_SIDL BIT(13)
+#define PIC32_UART_MODE_IREN BIT(12)
+#define PIC32_UART_MODE_RTSMD BIT(11)
+#define PIC32_UART_MODE_RESV1 BIT(10)
+#define PIC32_UART_MODE_UEN1 BIT(9)
+#define PIC32_UART_MODE_UEN0 BIT(8)
+#define PIC32_UART_MODE_WAKE BIT(7)
+#define PIC32_UART_MODE_LPBK BIT(6)
+#define PIC32_UART_MODE_ABAUD BIT(5)
+#define PIC32_UART_MODE_RXINV BIT(4)
+#define PIC32_UART_MODE_BRGH BIT(3)
+#define PIC32_UART_MODE_PDSEL1 BIT(2)
+#define PIC32_UART_MODE_PDSEL0 BIT(1)
+#define PIC32_UART_MODE_STSEL BIT(0)
+
+/* pic32 uart status register bits */
+#define PIC32_UART_STA_UTXISEL1 BIT(15)
+#define PIC32_UART_STA_UTXISEL0 BIT(14)
+#define PIC32_UART_STA_UTXINV BIT(13)
+#define PIC32_UART_STA_URXEN BIT(12)
+#define PIC32_UART_STA_UTXBRK BIT(11)
+#define PIC32_UART_STA_UTXEN BIT(10)
+#define PIC32_UART_STA_UTXBF BIT(9)
+#define PIC32_UART_STA_TRMT BIT(8)
+#define PIC32_UART_STA_URXISEL1 BIT(7)
+#define PIC32_UART_STA_URXISEL0 BIT(6)
+#define PIC32_UART_STA_ADDEN BIT(5)
+#define PIC32_UART_STA_RIDLE BIT(4)
+#define PIC32_UART_STA_PERR BIT(3)
+#define PIC32_UART_STA_FERR BIT(2)
+#define PIC32_UART_STA_OERR BIT(1)
+#define PIC32_UART_STA_URXDA BIT(0)
+
+#endif /* __DT_PIC32_UART_H__ */
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 04dcedfdebf8..0449235d4f22 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -1245,11 +1245,6 @@ MODULE_LICENSE ("GPL");
#define TMIO_OHCI_DRIVER ohci_hcd_tmio_driver
#endif
-#ifdef CONFIG_MACH_JZ4740
-#include "ohci-jz4740.c"
-#define PLATFORM_DRIVER ohci_hcd_jz4740_driver
-#endif
-
#ifdef CONFIG_TILE_USB
#include "ohci-tilegx.c"
#define PLATFORM_DRIVER ohci_hcd_tilegx_driver
diff --git a/drivers/usb/host/ohci-jz4740.c b/drivers/usb/host/ohci-jz4740.c
deleted file mode 100644
index 4db78f169256..000000000000
--- a/drivers/usb/host/ohci-jz4740.c
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
-
-#include <linux/platform_device.h>
-#include <linux/clk.h>
-#include <linux/regulator/consumer.h>
-
-struct jz4740_ohci_hcd {
- struct ohci_hcd ohci_hcd;
-
- struct regulator *vbus;
- bool vbus_enabled;
- struct clk *clk;
-};
-
-static inline struct jz4740_ohci_hcd *hcd_to_jz4740_hcd(struct usb_hcd *hcd)
-{
- return (struct jz4740_ohci_hcd *)(hcd->hcd_priv);
-}
-
-static inline struct usb_hcd *jz4740_hcd_to_hcd(struct jz4740_ohci_hcd *jz4740_ohci)
-{
- return container_of((void *)jz4740_ohci, struct usb_hcd, hcd_priv);
-}
-
-static int ohci_jz4740_start(struct usb_hcd *hcd)
-{
- struct ohci_hcd *ohci = hcd_to_ohci(hcd);
- int ret;
-
- ret = ohci_init(ohci);
- if (ret < 0)
- return ret;
-
- ohci->num_ports = 1;
-
- ret = ohci_run(ohci);
- if (ret < 0) {
- dev_err(hcd->self.controller, "Can not start %s",
- hcd->self.bus_name);
- ohci_stop(hcd);
- return ret;
- }
- return 0;
-}
-
-static int ohci_jz4740_set_vbus_power(struct jz4740_ohci_hcd *jz4740_ohci,
- bool enabled)
-{
- int ret = 0;
-
- if (!jz4740_ohci->vbus)
- return 0;
-
- if (enabled && !jz4740_ohci->vbus_enabled) {
- ret = regulator_enable(jz4740_ohci->vbus);
- if (ret)
- dev_err(jz4740_hcd_to_hcd(jz4740_ohci)->self.controller,
- "Could not power vbus\n");
- } else if (!enabled && jz4740_ohci->vbus_enabled) {
- ret = regulator_disable(jz4740_ohci->vbus);
- }
-
- if (ret == 0)
- jz4740_ohci->vbus_enabled = enabled;
-
- return ret;
-}
-
-static int ohci_jz4740_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
- u16 wIndex, char *buf, u16 wLength)
-{
- struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd);
- int ret = 0;
-
- switch (typeReq) {
- case SetPortFeature:
- if (wValue == USB_PORT_FEAT_POWER)
- ret = ohci_jz4740_set_vbus_power(jz4740_ohci, true);
- break;
- case ClearPortFeature:
- if (wValue == USB_PORT_FEAT_POWER)
- ret = ohci_jz4740_set_vbus_power(jz4740_ohci, false);
- break;
- }
-
- if (ret)
- return ret;
-
- return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
-}
-
-
-static const struct hc_driver ohci_jz4740_hc_driver = {
- .description = hcd_name,
- .product_desc = "JZ4740 OHCI",
- .hcd_priv_size = sizeof(struct jz4740_ohci_hcd),
-
- /*
- * generic hardware linkage
- */
- .irq = ohci_irq,
- .flags = HCD_USB11 | HCD_MEMORY,
-
- /*
- * basic lifecycle operations
- */
- .start = ohci_jz4740_start,
- .stop = ohci_stop,
- .shutdown = ohci_shutdown,
-
- /*
- * managing i/o requests and associated device resources
- */
- .urb_enqueue = ohci_urb_enqueue,
- .urb_dequeue = ohci_urb_dequeue,
- .endpoint_disable = ohci_endpoint_disable,
-
- /*
- * scheduling support
- */
- .get_frame_number = ohci_get_frame,
-
- /*
- * root hub support
- */
- .hub_status_data = ohci_hub_status_data,
- .hub_control = ohci_jz4740_hub_control,
-#ifdef CONFIG_PM
- .bus_suspend = ohci_bus_suspend,
- .bus_resume = ohci_bus_resume,
-#endif
- .start_port_reset = ohci_start_port_reset,
-};
-
-
-static int jz4740_ohci_probe(struct platform_device *pdev)
-{
- int ret;
- struct usb_hcd *hcd;
- struct jz4740_ohci_hcd *jz4740_ohci;
- struct resource *res;
- int irq;
-
- irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
- dev_err(&pdev->dev, "Failed to get platform irq\n");
- return irq;
- }
-
- hcd = usb_create_hcd(&ohci_jz4740_hc_driver, &pdev->dev, "jz4740");
- if (!hcd) {
- dev_err(&pdev->dev, "Failed to create hcd.\n");
- return -ENOMEM;
- }
-
- jz4740_ohci = hcd_to_jz4740_hcd(hcd);
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- hcd->regs = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(hcd->regs)) {
- ret = PTR_ERR(hcd->regs);
- goto err_free;
- }
- hcd->rsrc_start = res->start;
- hcd->rsrc_len = resource_size(res);
-
- jz4740_ohci->clk = devm_clk_get(&pdev->dev, "uhc");
- if (IS_ERR(jz4740_ohci->clk)) {
- ret = PTR_ERR(jz4740_ohci->clk);
- dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
- goto err_free;
- }
-
- jz4740_ohci->vbus = devm_regulator_get(&pdev->dev, "vbus");
- if (IS_ERR(jz4740_ohci->vbus))
- jz4740_ohci->vbus = NULL;
-
-
- clk_set_rate(jz4740_ohci->clk, 48000000);
- clk_enable(jz4740_ohci->clk);
- if (jz4740_ohci->vbus)
- ohci_jz4740_set_vbus_power(jz4740_ohci, true);
-
- platform_set_drvdata(pdev, hcd);
-
- ohci_hcd_init(hcd_to_ohci(hcd));
-
- ret = usb_add_hcd(hcd, irq, 0);
- if (ret) {
- dev_err(&pdev->dev, "Failed to add hcd: %d\n", ret);
- goto err_disable;
- }
- device_wakeup_enable(hcd->self.controller);
-
- return 0;
-
-err_disable:
- if (jz4740_ohci->vbus)
- regulator_disable(jz4740_ohci->vbus);
- clk_disable(jz4740_ohci->clk);
-
-err_free:
- usb_put_hcd(hcd);
-
- return ret;
-}
-
-static int jz4740_ohci_remove(struct platform_device *pdev)
-{
- struct usb_hcd *hcd = platform_get_drvdata(pdev);
- struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd);
-
- usb_remove_hcd(hcd);
-
- if (jz4740_ohci->vbus)
- regulator_disable(jz4740_ohci->vbus);
-
- clk_disable(jz4740_ohci->clk);
-
- usb_put_hcd(hcd);
-
- return 0;
-}
-
-static struct platform_driver ohci_hcd_jz4740_driver = {
- .probe = jz4740_ohci_probe,
- .remove = jz4740_ohci_remove,
- .driver = {
- .name = "jz4740-ohci",
- },
-};
-
-MODULE_ALIAS("platform:jz4740-ohci");
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index fb947655badd..9c4143112e6c 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1475,6 +1475,32 @@ config MT7621_WDT
help
Hardware driver for the Mediatek/Ralink MT7621/8 SoC Watchdog Timer.
+config PIC32_WDT
+ tristate "Microchip PIC32 hardware watchdog"
+ select WATCHDOG_CORE
+ depends on MACH_PIC32
+ help
+ Watchdog driver for the built in watchdog hardware in a PIC32.
+
+ Configuration bits must be set appropriately for the watchdog to be
+ controlled by this driver.
+
+ To compile this driver as a loadable module, choose M here.
+ The module will be called pic32-wdt.
+
+config PIC32_DMT
+ tristate "Microchip PIC32 Deadman Timer"
+ select WATCHDOG_CORE
+ depends on MACH_PIC32
+ help
+ Watchdog driver for PIC32 instruction fetch counting timer. This specific
+ timer is typically be used in misson critical and safety critical
+ applications, where any single failure of the software functionality
+ and sequencing must be detected.
+
+ To compile this driver as a loadable module, choose M here.
+ The module will be called pic32-dmt.
+
# PARISC Architecture
# POWERPC Architecture
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index feb6270fdbde..9bde095ff691 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -157,6 +157,8 @@ obj-$(CONFIG_LANTIQ_WDT) += lantiq_wdt.o
obj-$(CONFIG_RALINK_WDT) += rt2880_wdt.o
obj-$(CONFIG_IMGPDC_WDT) += imgpdc_wdt.o
obj-$(CONFIG_MT7621_WDT) += mt7621_wdt.o
+obj-$(CONFIG_PIC32_WDT) += pic32-wdt.o
+obj-$(CONFIG_PIC32_DMT) += pic32-dmt.o
# PARISC Architecture
diff --git a/drivers/watchdog/pic32-dmt.c b/drivers/watchdog/pic32-dmt.c
new file mode 100644
index 000000000000..962f58c03353
--- /dev/null
+++ b/drivers/watchdog/pic32-dmt.c
@@ -0,0 +1,257 @@
+/*
+ * PIC32 deadman timer driver
+ *
+ * Purna Chandra Mandal <purna.mandal@microchip.com>
+ * Copyright (c) 2016, Microchip Technology Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/watchdog.h>
+
+#include <asm/mach-pic32/pic32.h>
+
+/* Deadman Timer Regs */
+#define DMTCON_REG 0x00
+#define DMTPRECLR_REG 0x10
+#define DMTCLR_REG 0x20
+#define DMTSTAT_REG 0x30
+#define DMTCNT_REG 0x40
+#define DMTPSCNT_REG 0x60
+#define DMTPSINTV_REG 0x70
+
+/* Deadman Timer Regs fields */
+#define DMT_ON BIT(15)
+#define DMT_STEP1_KEY BIT(6)
+#define DMT_STEP2_KEY BIT(3)
+#define DMTSTAT_WINOPN BIT(0)
+#define DMTSTAT_EVENT BIT(5)
+#define DMTSTAT_BAD2 BIT(6)
+#define DMTSTAT_BAD1 BIT(7)
+
+/* Reset Control Register fields for watchdog */
+#define RESETCON_DMT_TIMEOUT BIT(5)
+
+struct pic32_dmt {
+ void __iomem *regs;
+ struct clk *clk;
+};
+
+static inline void dmt_enable(struct pic32_dmt *dmt)
+{
+ writel(DMT_ON, PIC32_SET(dmt->regs + DMTCON_REG));
+}
+
+static inline void dmt_disable(struct pic32_dmt *dmt)
+{
+ writel(DMT_ON, PIC32_CLR(dmt->regs + DMTCON_REG));
+ /*
+ * Cannot touch registers in the CPU cycle following clearing the
+ * ON bit.
+ */
+ nop();
+}
+
+static inline int dmt_bad_status(struct pic32_dmt *dmt)
+{
+ u32 val;
+
+ val = readl(dmt->regs + DMTSTAT_REG);
+ val &= (DMTSTAT_BAD1 | DMTSTAT_BAD2 | DMTSTAT_EVENT);
+ if (val)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static inline int dmt_keepalive(struct pic32_dmt *dmt)
+{
+ u32 v;
+ u32 timeout = 500;
+
+ /* set pre-clear key */
+ writel(DMT_STEP1_KEY << 8, dmt->regs + DMTPRECLR_REG);
+
+ /* wait for DMT window to open */
+ while (--timeout) {
+ v = readl(dmt->regs + DMTSTAT_REG) & DMTSTAT_WINOPN;
+ if (v == DMTSTAT_WINOPN)
+ break;
+ }
+
+ /* apply key2 */
+ writel(DMT_STEP2_KEY, dmt->regs + DMTCLR_REG);
+
+ /* check whether keys are latched correctly */
+ return dmt_bad_status(dmt);
+}
+
+static inline u32 pic32_dmt_get_timeout_secs(struct pic32_dmt *dmt)
+{
+ unsigned long rate;
+
+ rate = clk_get_rate(dmt->clk);
+ if (rate)
+ return readl(dmt->regs + DMTPSCNT_REG) / rate;
+
+ return 0;
+}
+
+static inline u32 pic32_dmt_bootstatus(struct pic32_dmt *dmt)
+{
+ u32 v;
+ void __iomem *rst_base;
+
+ rst_base = ioremap(PIC32_BASE_RESET, 0x10);
+ if (!rst_base)
+ return 0;
+
+ v = readl(rst_base);
+
+ writel(RESETCON_DMT_TIMEOUT, PIC32_CLR(rst_base));
+
+ iounmap(rst_base);
+ return v & RESETCON_DMT_TIMEOUT;
+}
+
+static int pic32_dmt_start(struct watchdog_device *wdd)
+{
+ struct pic32_dmt *dmt = watchdog_get_drvdata(wdd);
+
+ dmt_enable(dmt);
+ return dmt_keepalive(dmt);
+}
+
+static int pic32_dmt_stop(struct watchdog_device *wdd)
+{
+ struct pic32_dmt *dmt = watchdog_get_drvdata(wdd);
+
+ dmt_disable(dmt);
+
+ return 0;
+}
+
+static int pic32_dmt_ping(struct watchdog_device *wdd)
+{
+ struct pic32_dmt *dmt = watchdog_get_drvdata(wdd);
+
+ return dmt_keepalive(dmt);
+}
+
+static const struct watchdog_ops pic32_dmt_fops = {
+ .owner = THIS_MODULE,
+ .start = pic32_dmt_start,
+ .stop = pic32_dmt_stop,
+ .ping = pic32_dmt_ping,
+};
+
+static const struct watchdog_info pic32_dmt_ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .identity = "PIC32 Deadman Timer",
+};
+
+static struct watchdog_device pic32_dmt_wdd = {
+ .info = &pic32_dmt_ident,
+ .ops = &pic32_dmt_fops,
+};
+
+static int pic32_dmt_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct pic32_dmt *dmt;
+ struct resource *mem;
+ struct watchdog_device *wdd = &pic32_dmt_wdd;
+
+ dmt = devm_kzalloc(&pdev->dev, sizeof(*dmt), GFP_KERNEL);
+ if (IS_ERR(dmt))
+ return PTR_ERR(dmt);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dmt->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(dmt->regs))
+ return PTR_ERR(dmt->regs);
+
+ dmt->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dmt->clk)) {
+ dev_err(&pdev->dev, "clk not found\n");
+ return PTR_ERR(dmt->clk);
+ }
+
+ ret = clk_prepare_enable(dmt->clk);
+ if (ret)
+ return ret;
+
+ wdd->timeout = pic32_dmt_get_timeout_secs(dmt);
+ if (!wdd->timeout) {
+ dev_err(&pdev->dev,
+ "failed to read watchdog register timeout\n");
+ ret = -EINVAL;
+ goto out_disable_clk;
+ }
+
+ dev_info(&pdev->dev, "timeout %d\n", wdd->timeout);
+
+ wdd->bootstatus = pic32_dmt_bootstatus(dmt) ? WDIOF_CARDRESET : 0;
+
+ watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
+ watchdog_set_drvdata(wdd, dmt);
+
+ ret = watchdog_register_device(wdd);
+ if (ret) {
+ dev_err(&pdev->dev, "watchdog register failed, err %d\n", ret);
+ goto out_disable_clk;
+ }
+
+ platform_set_drvdata(pdev, wdd);
+ return 0;
+
+out_disable_clk:
+ clk_disable_unprepare(dmt->clk);
+ return ret;
+}
+
+static int pic32_dmt_remove(struct platform_device *pdev)
+{
+ struct watchdog_device *wdd = platform_get_drvdata(pdev);
+ struct pic32_dmt *dmt = watchdog_get_drvdata(wdd);
+
+ watchdog_unregister_device(wdd);
+ clk_disable_unprepare(dmt->clk);
+
+ return 0;
+}
+
+static const struct of_device_id pic32_dmt_of_ids[] = {
+ { .compatible = "microchip,pic32mzda-dmt",},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pic32_dmt_of_ids);
+
+static struct platform_driver pic32_dmt_driver = {
+ .probe = pic32_dmt_probe,
+ .remove = pic32_dmt_remove,
+ .driver = {
+ .name = "pic32-dmt",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(pic32_dmt_of_ids),
+ }
+};
+
+module_platform_driver(pic32_dmt_driver);
+
+MODULE_AUTHOR("Purna Chandra Mandal <purna.mandal@microchip.com>");
+MODULE_DESCRIPTION("Microchip PIC32 DMT Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pic32-wdt.c b/drivers/watchdog/pic32-wdt.c
new file mode 100644
index 000000000000..6047aa89a4d3
--- /dev/null
+++ b/drivers/watchdog/pic32-wdt.c
@@ -0,0 +1,263 @@
+/*
+ * PIC32 watchdog driver
+ *
+ * Joshua Henderson <joshua.henderson@microchip.com>
+ * Copyright (c) 2016, Microchip Technology Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/watchdog.h>
+
+#include <asm/mach-pic32/pic32.h>
+
+/* Watchdog Timer Registers */
+#define WDTCON_REG 0x00
+
+/* Watchdog Timer Control Register fields */
+#define WDTCON_WIN_EN BIT(0)
+#define WDTCON_RMCS_MASK 0x0003
+#define WDTCON_RMCS_SHIFT 0x0006
+#define WDTCON_RMPS_MASK 0x001F
+#define WDTCON_RMPS_SHIFT 0x0008
+#define WDTCON_ON BIT(15)
+#define WDTCON_CLR_KEY 0x5743
+
+/* Reset Control Register fields for watchdog */
+#define RESETCON_TIMEOUT_IDLE BIT(2)
+#define RESETCON_TIMEOUT_SLEEP BIT(3)
+#define RESETCON_WDT_TIMEOUT BIT(4)
+
+struct pic32_wdt {
+ void __iomem *regs;
+ void __iomem *rst_base;
+ struct clk *clk;
+};
+
+static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt)
+{
+ return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN);
+}
+
+static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt)
+{
+ u32 v = readl(wdt->regs + WDTCON_REG);
+
+ return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK;
+}
+
+static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt)
+{
+ u32 v = readl(wdt->regs + WDTCON_REG);
+
+ return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK;
+}
+
+static int pic32_wdt_bootstatus(struct pic32_wdt *wdt)
+{
+ u32 v = readl(wdt->rst_base);
+
+ writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base));
+
+ return v & RESETCON_WDT_TIMEOUT;
+}
+
+static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev)
+{
+ unsigned long rate;
+ u32 period, ps, terminal;
+
+ rate = clk_get_rate(wdt->clk);
+
+ dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n",
+ pic32_wdt_get_clk_id(wdt), rate);
+
+ /* default, prescaler of 32 (i.e. div-by-32) is implicit. */
+ rate >>= 5;
+ if (!rate)
+ return 0;
+
+ /* calculate terminal count from postscaler. */
+ ps = pic32_wdt_get_post_scaler(wdt);
+ terminal = BIT(ps);
+
+ /* find time taken (in secs) to reach terminal count */
+ period = terminal / rate;
+ dev_dbg(dev,
+ "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n",
+ rate, terminal, period);
+
+ return period;
+}
+
+static void pic32_wdt_keepalive(struct pic32_wdt *wdt)
+{
+ /* write key through single half-word */
+ writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2);
+}
+
+static int pic32_wdt_start(struct watchdog_device *wdd)
+{
+ struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG));
+ pic32_wdt_keepalive(wdt);
+
+ return 0;
+}
+
+static int pic32_wdt_stop(struct watchdog_device *wdd)
+{
+ struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG));
+
+ /*
+ * Cannot touch registers in the CPU cycle following clearing the
+ * ON bit.
+ */
+ nop();
+
+ return 0;
+}
+
+static int pic32_wdt_ping(struct watchdog_device *wdd)
+{
+ struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ pic32_wdt_keepalive(wdt);
+
+ return 0;
+}
+
+static const struct watchdog_ops pic32_wdt_fops = {
+ .owner = THIS_MODULE,
+ .start = pic32_wdt_start,
+ .stop = pic32_wdt_stop,
+ .ping = pic32_wdt_ping,
+};
+
+static const struct watchdog_info pic32_wdt_ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
+ .identity = "PIC32 Watchdog",
+};
+
+static struct watchdog_device pic32_wdd = {
+ .info = &pic32_wdt_ident,
+ .ops = &pic32_wdt_fops,
+};
+
+static const struct of_device_id pic32_wdt_dt_ids[] = {
+ { .compatible = "microchip,pic32mzda-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids);
+
+static int pic32_wdt_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct watchdog_device *wdd = &pic32_wdd;
+ struct pic32_wdt *wdt;
+ struct resource *mem;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (IS_ERR(wdt))
+ return PTR_ERR(wdt);
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ wdt->regs = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(wdt->regs))
+ return PTR_ERR(wdt->regs);
+
+ wdt->rst_base = devm_ioremap(&pdev->dev, PIC32_BASE_RESET, 0x10);
+ if (IS_ERR(wdt->rst_base))
+ return PTR_ERR(wdt->rst_base);
+
+ wdt->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(wdt->clk)) {
+ dev_err(&pdev->dev, "clk not found\n");
+ return PTR_ERR(wdt->clk);
+ }
+
+ ret = clk_prepare_enable(wdt->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "clk enable failed\n");
+ return ret;
+ }
+
+ if (pic32_wdt_is_win_enabled(wdt)) {
+ dev_err(&pdev->dev, "windowed-clear mode is not supported.\n");
+ ret = -ENODEV;
+ goto out_disable_clk;
+ }
+
+ wdd->timeout = pic32_wdt_get_timeout_secs(wdt, &pdev->dev);
+ if (!wdd->timeout) {
+ dev_err(&pdev->dev,
+ "failed to read watchdog register timeout\n");
+ ret = -EINVAL;
+ goto out_disable_clk;
+ }
+
+ dev_info(&pdev->dev, "timeout %d\n", wdd->timeout);
+
+ wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0;
+
+ watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
+ watchdog_set_drvdata(wdd, wdt);
+
+ ret = watchdog_register_device(wdd);
+ if (ret) {
+ dev_err(&pdev->dev, "watchdog register failed, err %d\n", ret);
+ goto out_disable_clk;
+ }
+
+ platform_set_drvdata(pdev, wdd);
+
+ return 0;
+
+out_disable_clk:
+ clk_disable_unprepare(wdt->clk);
+
+ return ret;
+}
+
+static int pic32_wdt_drv_remove(struct platform_device *pdev)
+{
+ struct watchdog_device *wdd = platform_get_drvdata(pdev);
+ struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ watchdog_unregister_device(wdd);
+ clk_disable_unprepare(wdt->clk);
+
+ return 0;
+}
+
+static struct platform_driver pic32_wdt_driver = {
+ .probe = pic32_wdt_drv_probe,
+ .remove = pic32_wdt_drv_remove,
+ .driver = {
+ .name = "pic32-wdt",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(pic32_wdt_dt_ids),
+ }
+};
+
+module_platform_driver(pic32_wdt_driver);
+
+MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>");
+MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer");
+MODULE_LICENSE("GPL");