summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-04-17 15:50:54 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2015-04-17 15:50:54 -0400
commitbfaf245022b4b8661af2e35f467cf0e91943c24c (patch)
treeb5a6ee49a047557a791eb897c8c9545a155e36b7 /drivers
parent96d928ed75c4ba4253e82910a697ec7b06ace8b4 (diff)
parent3e20a26b02bd4f24945c87407df51948dd488620 (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 Linux 4.1. Most noteworthy: - Add more Octeon-optimized crypto functions - Octeon crypto preemption and locking fixes - Little endian support for Octeon - Use correct CSR to soft reset Octeons - Support LEDs on the Octeon-based DSR-1000N - Fix PCI interrupt mapping for the Octeon-based DSR-1000N - Mark prom_free_prom_memory() as __init for a number of systems - Support for Imagination's Pistachio SOC. This includes arch and CLK bits. I'd like to merge pinctrl bits later - Improve parallelism of csum_partial for certain pipelines - Organize DTB files in subdirs like other architectures - Implement read_sched_clock for all MIPS platforms other than Octeon - Massive series of 38 fixes and cleanups for the FPU emulator / kernel - Further FPU remulator work to support new features. This sits on a separate branch which also has been pulled into the 4.1 KVM branch - Clean up and fixes for the SEAD3 eval board; remove unused file - Various updates for Netlogic platforms - A number of small updates for Loongson 3 platforms - Increase the memory limit for ATH79 platforms to 256MB - A fair number of fixes and updates for BCM47xx platforms - Finish the implementation of XPA support - MIPS FDC support. No, not floppy controller but Fast Debug Channel :) - Detect the R16000 used in SGI legacy platforms - Fix Kconfig dependencies for the SSB bus support" * 'upstream' of git://git.linux-mips.org/pub/scm/ralf/upstream-linus: (265 commits) MIPS: Makefile: Fix MIPS ASE detection code MIPS: asm: elf: Set O32 default FPU flags MIPS: BCM47XX: Fix detecting Microsoft MN-700 & Asus WL500G MIPS: Kconfig: Disable SMP/CPS for 64-bit MIPS: Hibernate: flush TLB entries earlier MIPS: smp-cps: cpu_set FPU mask if FPU present MIPS: lose_fpu(): Disable FPU when MSA enabled MIPS: ralink: add missing symbol for RALINK_ILL_ACC MIPS: ralink: Fix bad config symbol in PCI makefile. SSB: fix Kconfig dependencies MIPS: Malta: Detect and fix bad memsize values Revert "MIPS: Avoid pipeline stalls on some MIPS32R2 cores." MIPS: Octeon: Delete override of cpu_has_mips_r2_exec_hazard. MIPS: Fix cpu_has_mips_r2_exec_hazard. MIPS: kernel: entry.S: Set correct ISA level for mips_ihb MIPS: asm: spinlock: Fix addiu instruction for R10000_LLSC_WAR case MIPS: r4kcache: Use correct base register for MIPS R6 cache flushes MIPS: Kconfig: Fix typo for the r2-to-r6 emulator kernel parameter MIPS: unaligned: Fix regular load/store instruction emulation for EVA MIPS: unaligned: Surround load/store macros in do {} while statements ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/bcma/driver_mips.c2
-rw-r--r--drivers/bus/Kconfig13
-rw-r--r--drivers/bus/Makefile1
-rw-r--r--drivers/bus/mips_cdmm.c716
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/pistachio/Makefile3
-rw-r--r--drivers/clk/pistachio/clk-pistachio.c329
-rw-r--r--drivers/clk/pistachio/clk-pll.c401
-rw-r--r--drivers/clk/pistachio/clk.c140
-rw-r--r--drivers/clk/pistachio/clk.h174
-rw-r--r--drivers/clocksource/Kconfig2
-rw-r--r--drivers/clocksource/mips-gic-timer.c13
-rw-r--r--drivers/irqchip/Kconfig5
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-bcm7038-l1.c335
-rw-r--r--drivers/irqchip/irq-bcm7120-l2.c193
-rw-r--r--drivers/irqchip/irq-brcmstb-l2.c9
-rw-r--r--drivers/irqchip/irq-mips-gic.c40
-rw-r--r--drivers/net/ethernet/broadcom/b44.c2
-rw-r--r--drivers/net/ethernet/broadcom/bgmac.c2
-rw-r--r--drivers/platform/Kconfig3
-rw-r--r--drivers/platform/Makefile1
-rw-r--r--drivers/platform/mips/Kconfig30
-rw-r--r--drivers/platform/mips/Makefile2
-rw-r--r--drivers/platform/mips/acpi_init.c150
-rw-r--r--drivers/platform/mips/cpu_hwmon.c207
-rw-r--r--drivers/ssb/Kconfig1
-rw-r--r--drivers/ssb/driver_chipcommon_pmu.c2
-rw-r--r--drivers/ssb/driver_mipscore.c2
-rw-r--r--drivers/staging/octeon/ethernet-tx.c5
-rw-r--r--drivers/staging/octeon/ethernet.c10
-rw-r--r--drivers/tty/Kconfig47
-rw-r--r--drivers/tty/Makefile1
-rw-r--r--drivers/tty/mips_ejtag_fdc.c1303
34 files changed, 4070 insertions, 76 deletions
diff --git a/drivers/bcma/driver_mips.c b/drivers/bcma/driver_mips.c
index 04faf6df959f..24424f3fef96 100644
--- a/drivers/bcma/driver_mips.c
+++ b/drivers/bcma/driver_mips.c
@@ -21,7 +21,7 @@
#include <linux/serial_reg.h>
#include <linux/time.h>
#ifdef CONFIG_BCM47XX
-#include <bcm47xx_nvram.h>
+#include <linux/bcm47xx_nvram.h>
#endif
enum bcma_boot_dev {
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
index b99729e36860..cbddbaddb347 100644
--- a/drivers/bus/Kconfig
+++ b/drivers/bus/Kconfig
@@ -20,6 +20,19 @@ config IMX_WEIM
The WEIM(Wireless External Interface Module) works like a bus.
You can attach many different devices on it, such as NOR, onenand.
+config MIPS_CDMM
+ bool "MIPS Common Device Memory Map (CDMM) Driver"
+ depends on CPU_MIPSR2
+ help
+ Driver needed for the MIPS Common Device Memory Map bus in MIPS
+ cores. This bus is for per-CPU tightly coupled devices such as the
+ Fast Debug Channel (FDC).
+
+ For this to work, either your bootloader needs to enable the CDMM
+ region at an unused physical address on the boot CPU, or else your
+ platform code needs to implement mips_cdmm_phys_base() (see
+ asm/cdmm.h).
+
config MVEBU_MBUS
bool
depends on PLAT_ORION
diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile
index 2973c18cbcc2..807dd17ef2f8 100644
--- a/drivers/bus/Makefile
+++ b/drivers/bus/Makefile
@@ -4,6 +4,7 @@
obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o
obj-$(CONFIG_IMX_WEIM) += imx-weim.o
+obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o
obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o
obj-$(CONFIG_OMAP_OCP2SCP) += omap-ocp2scp.o
diff --git a/drivers/bus/mips_cdmm.c b/drivers/bus/mips_cdmm.c
new file mode 100644
index 000000000000..5bd792c68f9b
--- /dev/null
+++ b/drivers/bus/mips_cdmm.c
@@ -0,0 +1,716 @@
+/*
+ * Bus driver for MIPS Common Device Memory Map (CDMM).
+ *
+ * Copyright (C) 2014-2015 Imagination Technologies Ltd.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/atomic.h>
+#include <linux/err.h>
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <asm/cdmm.h>
+#include <asm/hazards.h>
+#include <asm/mipsregs.h>
+
+/* Access control and status register fields */
+#define CDMM_ACSR_DEVTYPE_SHIFT 24
+#define CDMM_ACSR_DEVTYPE (255ul << CDMM_ACSR_DEVTYPE_SHIFT)
+#define CDMM_ACSR_DEVSIZE_SHIFT 16
+#define CDMM_ACSR_DEVSIZE (31ul << CDMM_ACSR_DEVSIZE_SHIFT)
+#define CDMM_ACSR_DEVREV_SHIFT 12
+#define CDMM_ACSR_DEVREV (15ul << CDMM_ACSR_DEVREV_SHIFT)
+#define CDMM_ACSR_UW (1ul << 3)
+#define CDMM_ACSR_UR (1ul << 2)
+#define CDMM_ACSR_SW (1ul << 1)
+#define CDMM_ACSR_SR (1ul << 0)
+
+/* Each block of device registers is 64 bytes */
+#define CDMM_DRB_SIZE 64
+
+#define to_mips_cdmm_driver(d) container_of(d, struct mips_cdmm_driver, drv)
+
+/* Default physical base address */
+static phys_addr_t mips_cdmm_default_base;
+
+/* Bus operations */
+
+static const struct mips_cdmm_device_id *
+mips_cdmm_lookup(const struct mips_cdmm_device_id *table,
+ struct mips_cdmm_device *dev)
+{
+ int ret = 0;
+
+ for (; table->type; ++table) {
+ ret = (dev->type == table->type);
+ if (ret)
+ break;
+ }
+
+ return ret ? table : NULL;
+}
+
+static int mips_cdmm_match(struct device *dev, struct device_driver *drv)
+{
+ struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev);
+ struct mips_cdmm_driver *cdrv = to_mips_cdmm_driver(drv);
+
+ return mips_cdmm_lookup(cdrv->id_table, cdev) != NULL;
+}
+
+static int mips_cdmm_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev);
+ int retval = 0;
+
+ retval = add_uevent_var(env, "CDMM_CPU=%u", cdev->cpu);
+ if (retval)
+ return retval;
+
+ retval = add_uevent_var(env, "CDMM_TYPE=0x%02x", cdev->type);
+ if (retval)
+ return retval;
+
+ retval = add_uevent_var(env, "CDMM_REV=%u", cdev->rev);
+ if (retval)
+ return retval;
+
+ retval = add_uevent_var(env, "MODALIAS=mipscdmm:t%02X", cdev->type);
+ return retval;
+}
+
+/* Device attributes */
+
+#define CDMM_ATTR(name, fmt, arg...) \
+static ssize_t name##_show(struct device *_dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct mips_cdmm_device *dev = to_mips_cdmm_device(_dev); \
+ return sprintf(buf, fmt, arg); \
+} \
+static DEVICE_ATTR_RO(name);
+
+CDMM_ATTR(cpu, "%u\n", dev->cpu);
+CDMM_ATTR(type, "0x%02x\n", dev->type);
+CDMM_ATTR(revision, "%u\n", dev->rev);
+CDMM_ATTR(modalias, "mipscdmm:t%02X\n", dev->type);
+CDMM_ATTR(resource, "\t%016llx\t%016llx\t%016lx\n",
+ (unsigned long long)dev->res.start,
+ (unsigned long long)dev->res.end,
+ dev->res.flags);
+
+static struct attribute *mips_cdmm_dev_attrs[] = {
+ &dev_attr_cpu.attr,
+ &dev_attr_type.attr,
+ &dev_attr_revision.attr,
+ &dev_attr_modalias.attr,
+ &dev_attr_resource.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(mips_cdmm_dev);
+
+struct bus_type mips_cdmm_bustype = {
+ .name = "cdmm",
+ .dev_groups = mips_cdmm_dev_groups,
+ .match = mips_cdmm_match,
+ .uevent = mips_cdmm_uevent,
+};
+EXPORT_SYMBOL_GPL(mips_cdmm_bustype);
+
+/*
+ * Standard driver callback helpers.
+ *
+ * All the CDMM driver callbacks need to be executed on the appropriate CPU from
+ * workqueues. For the standard driver callbacks we need a work function
+ * (mips_cdmm_{void,int}_work()) to do the actual call from the right CPU, and a
+ * wrapper function (generated with BUILD_PERCPU_HELPER) to arrange for the work
+ * function to be called on that CPU.
+ */
+
+/**
+ * struct mips_cdmm_work_dev - Data for per-device call work.
+ * @fn: CDMM driver callback function to call for the device.
+ * @dev: CDMM device to pass to @fn.
+ */
+struct mips_cdmm_work_dev {
+ void *fn;
+ struct mips_cdmm_device *dev;
+};
+
+/**
+ * mips_cdmm_void_work() - Call a void returning CDMM driver callback.
+ * @data: struct mips_cdmm_work_dev pointer.
+ *
+ * A work_on_cpu() callback function to call an arbitrary CDMM driver callback
+ * function which doesn't return a value.
+ */
+static long mips_cdmm_void_work(void *data)
+{
+ struct mips_cdmm_work_dev *work = data;
+ void (*fn)(struct mips_cdmm_device *) = work->fn;
+
+ fn(work->dev);
+ return 0;
+}
+
+/**
+ * mips_cdmm_int_work() - Call an int returning CDMM driver callback.
+ * @data: struct mips_cdmm_work_dev pointer.
+ *
+ * A work_on_cpu() callback function to call an arbitrary CDMM driver callback
+ * function which returns an int.
+ */
+static long mips_cdmm_int_work(void *data)
+{
+ struct mips_cdmm_work_dev *work = data;
+ int (*fn)(struct mips_cdmm_device *) = work->fn;
+
+ return fn(work->dev);
+}
+
+#define _BUILD_RET_void
+#define _BUILD_RET_int return
+
+/**
+ * BUILD_PERCPU_HELPER() - Helper to call a CDMM driver callback on right CPU.
+ * @_ret: Return type (void or int).
+ * @_name: Name of CDMM driver callback function.
+ *
+ * Generates a specific device callback function to call a CDMM driver callback
+ * function on the appropriate CPU for the device, and if applicable return the
+ * result.
+ */
+#define BUILD_PERCPU_HELPER(_ret, _name) \
+static _ret mips_cdmm_##_name(struct device *dev) \
+{ \
+ struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev); \
+ struct mips_cdmm_driver *cdrv = to_mips_cdmm_driver(dev->driver); \
+ struct mips_cdmm_work_dev work = { \
+ .fn = cdrv->_name, \
+ .dev = cdev, \
+ }; \
+ \
+ _BUILD_RET_##_ret work_on_cpu(cdev->cpu, \
+ mips_cdmm_##_ret##_work, &work); \
+}
+
+/* Driver callback functions */
+BUILD_PERCPU_HELPER(int, probe) /* int mips_cdmm_probe(struct device) */
+BUILD_PERCPU_HELPER(int, remove) /* int mips_cdmm_remove(struct device) */
+BUILD_PERCPU_HELPER(void, shutdown) /* void mips_cdmm_shutdown(struct device) */
+
+
+/* Driver registration */
+
+/**
+ * mips_cdmm_driver_register() - Register a CDMM driver.
+ * @drv: CDMM driver information.
+ *
+ * Register a CDMM driver with the CDMM subsystem. The driver will be informed
+ * of matching devices which are discovered.
+ *
+ * Returns: 0 on success.
+ */
+int mips_cdmm_driver_register(struct mips_cdmm_driver *drv)
+{
+ drv->drv.bus = &mips_cdmm_bustype;
+
+ if (drv->probe)
+ drv->drv.probe = mips_cdmm_probe;
+ if (drv->remove)
+ drv->drv.remove = mips_cdmm_remove;
+ if (drv->shutdown)
+ drv->drv.shutdown = mips_cdmm_shutdown;
+
+ return driver_register(&drv->drv);
+}
+EXPORT_SYMBOL_GPL(mips_cdmm_driver_register);
+
+/**
+ * mips_cdmm_driver_unregister() - Unregister a CDMM driver.
+ * @drv: CDMM driver information.
+ *
+ * Unregister a CDMM driver from the CDMM subsystem.
+ */
+void mips_cdmm_driver_unregister(struct mips_cdmm_driver *drv)
+{
+ driver_unregister(&drv->drv);
+}
+EXPORT_SYMBOL_GPL(mips_cdmm_driver_unregister);
+
+
+/* CDMM initialisation and bus discovery */
+
+/**
+ * struct mips_cdmm_bus - Info about CDMM bus.
+ * @phys: Physical address at which it is mapped.
+ * @regs: Virtual address where registers can be accessed.
+ * @drbs: Total number of DRBs.
+ * @drbs_reserved: Number of DRBs reserved.
+ * @discovered: Whether the devices on the bus have been discovered yet.
+ * @offline: Whether the CDMM bus is going offline (or very early
+ * coming back online), in which case it should be
+ * reconfigured each time.
+ */
+struct mips_cdmm_bus {
+ phys_addr_t phys;
+ void __iomem *regs;
+ unsigned int drbs;
+ unsigned int drbs_reserved;
+ bool discovered;
+ bool offline;
+};
+
+static struct mips_cdmm_bus mips_cdmm_boot_bus;
+static DEFINE_PER_CPU(struct mips_cdmm_bus *, mips_cdmm_buses);
+static atomic_t mips_cdmm_next_id = ATOMIC_INIT(-1);
+
+/**
+ * mips_cdmm_get_bus() - Get the per-CPU CDMM bus information.
+ *
+ * Get information about the per-CPU CDMM bus, if the bus is present.
+ *
+ * The caller must prevent migration to another CPU, either by disabling
+ * pre-emption or by running from a pinned kernel thread.
+ *
+ * Returns: Pointer to CDMM bus information for the current CPU.
+ * May return ERR_PTR(-errno) in case of error, so check with
+ * IS_ERR().
+ */
+static struct mips_cdmm_bus *mips_cdmm_get_bus(void)
+{
+ struct mips_cdmm_bus *bus, **bus_p;
+ unsigned long flags;
+ unsigned int cpu;
+
+ if (!cpu_has_cdmm)
+ return ERR_PTR(-ENODEV);
+
+ cpu = smp_processor_id();
+ /* Avoid early use of per-cpu primitives before initialised */
+ if (cpu == 0)
+ return &mips_cdmm_boot_bus;
+
+ /* Get bus pointer */
+ bus_p = per_cpu_ptr(&mips_cdmm_buses, cpu);
+ local_irq_save(flags);
+ bus = *bus_p;
+ /* Attempt allocation if NULL */
+ if (unlikely(!bus)) {
+ bus = kzalloc(sizeof(*bus), GFP_ATOMIC);
+ if (unlikely(!bus))
+ bus = ERR_PTR(-ENOMEM);
+ else
+ *bus_p = bus;
+ }
+ local_irq_restore(flags);
+ return bus;
+}
+
+/**
+ * mips_cdmm_cur_base() - Find current physical base address of CDMM region.
+ *
+ * Returns: Physical base address of CDMM region according to cdmmbase CP0
+ * register, or 0 if the CDMM region is disabled.
+ */
+static phys_addr_t mips_cdmm_cur_base(void)
+{
+ unsigned long cdmmbase = read_c0_cdmmbase();
+
+ if (!(cdmmbase & MIPS_CDMMBASE_EN))
+ return 0;
+
+ return (cdmmbase >> MIPS_CDMMBASE_ADDR_SHIFT)
+ << MIPS_CDMMBASE_ADDR_START;
+}
+
+/**
+ * mips_cdmm_setup() - Ensure the CDMM bus is initialised and usable.
+ * @bus: Pointer to bus information for current CPU.
+ * IS_ERR(bus) is checked, so no need for caller to check.
+ *
+ * The caller must prevent migration to another CPU, either by disabling
+ * pre-emption or by running from a pinned kernel thread.
+ *
+ * Returns 0 on success, -errno on failure.
+ */
+static int mips_cdmm_setup(struct mips_cdmm_bus *bus)
+{
+ unsigned long cdmmbase, flags;
+ int ret = 0;
+
+ if (IS_ERR(bus))
+ return PTR_ERR(bus);
+
+ local_irq_save(flags);
+ /* Don't set up bus a second time unless marked offline */
+ if (bus->offline) {
+ /* If CDMM region is still set up, nothing to do */
+ if (bus->phys == mips_cdmm_cur_base())
+ goto out;
+ /*
+ * The CDMM region isn't set up as expected, so it needs
+ * reconfiguring, but then we can stop checking it.
+ */
+ bus->offline = false;
+ } else if (bus->phys > 1) {
+ goto out;
+ }
+
+ /* If the CDMM region is already configured, inherit that setup */
+ if (!bus->phys)
+ bus->phys = mips_cdmm_cur_base();
+ /* Otherwise, ask platform code for suggestions */
+ if (!bus->phys && mips_cdmm_phys_base)
+ bus->phys = mips_cdmm_phys_base();
+ /* Otherwise, copy what other CPUs have done */
+ if (!bus->phys)
+ bus->phys = mips_cdmm_default_base;
+ /* Otherwise, complain once */
+ if (!bus->phys) {
+ bus->phys = 1;
+ /*
+ * If you hit this, either your bootloader needs to set up the
+ * CDMM on the boot CPU, or else you need to implement
+ * mips_cdmm_phys_base() for your platform (see asm/cdmm.h).
+ */
+ pr_err("cdmm%u: Failed to choose a physical base\n",
+ smp_processor_id());
+ }
+ /* Already complained? */
+ if (bus->phys == 1) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ /* Record our success for other CPUs to copy */
+ mips_cdmm_default_base = bus->phys;
+
+ pr_debug("cdmm%u: Enabling CDMM region at %pa\n",
+ smp_processor_id(), &bus->phys);
+
+ /* Enable CDMM */
+ cdmmbase = read_c0_cdmmbase();
+ cdmmbase &= (1ul << MIPS_CDMMBASE_ADDR_SHIFT) - 1;
+ cdmmbase |= (bus->phys >> MIPS_CDMMBASE_ADDR_START)
+ << MIPS_CDMMBASE_ADDR_SHIFT;
+ cdmmbase |= MIPS_CDMMBASE_EN;
+ write_c0_cdmmbase(cdmmbase);
+ tlbw_use_hazard();
+
+ bus->regs = (void __iomem *)CKSEG1ADDR(bus->phys);
+ bus->drbs = 1 + ((cdmmbase & MIPS_CDMMBASE_SIZE) >>
+ MIPS_CDMMBASE_SIZE_SHIFT);
+ bus->drbs_reserved = !!(cdmmbase & MIPS_CDMMBASE_CI);
+
+out:
+ local_irq_restore(flags);
+ return ret;
+}
+
+/**
+ * mips_cdmm_early_probe() - Minimally probe for a specific device on CDMM.
+ * @dev_type: CDMM type code to look for.
+ *
+ * Minimally configure the in-CPU Common Device Memory Map (CDMM) and look for a
+ * specific device. This can be used to find a device very early in boot for
+ * example to configure an early FDC console device.
+ *
+ * The caller must prevent migration to another CPU, either by disabling
+ * pre-emption or by running from a pinned kernel thread.
+ *
+ * Returns: MMIO pointer to device memory. The caller can read the ACSR
+ * register to find more information about the device (such as the
+ * version number or the number of blocks).
+ * May return IOMEM_ERR_PTR(-errno) in case of error, so check with
+ * IS_ERR().
+ */
+void __iomem *mips_cdmm_early_probe(unsigned int dev_type)
+{
+ struct mips_cdmm_bus *bus;
+ void __iomem *cdmm;
+ u32 acsr;
+ unsigned int drb, type, size;
+ int err;
+
+ if (WARN_ON(!dev_type))
+ return IOMEM_ERR_PTR(-ENODEV);
+
+ bus = mips_cdmm_get_bus();
+ err = mips_cdmm_setup(bus);
+ if (err)
+ return IOMEM_ERR_PTR(err);
+
+ /* Skip the first block if it's reserved for more registers */
+ drb = bus->drbs_reserved;
+ cdmm = bus->regs;
+
+ /* Look for a specific device type */
+ for (; drb < bus->drbs; drb += size + 1) {
+ acsr = readl(cdmm + drb * CDMM_DRB_SIZE);
+ type = (acsr & CDMM_ACSR_DEVTYPE) >> CDMM_ACSR_DEVTYPE_SHIFT;
+ if (type == dev_type)
+ return cdmm + drb * CDMM_DRB_SIZE;
+ size = (acsr & CDMM_ACSR_DEVSIZE) >> CDMM_ACSR_DEVSIZE_SHIFT;
+ }
+
+ return IOMEM_ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL_GPL(mips_cdmm_early_probe);
+
+/**
+ * mips_cdmm_release() - Release a removed CDMM device.
+ * @dev: Device object
+ *
+ * Clean up the struct mips_cdmm_device for an unused CDMM device. This is
+ * called automatically by the driver core when a device is removed.
+ */
+static void mips_cdmm_release(struct device *dev)
+{
+ struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev);
+
+ kfree(cdev);
+}
+
+/**
+ * mips_cdmm_bus_discover() - Discover the devices on the CDMM bus.
+ * @bus: CDMM bus information, must already be set up.
+ */
+static void mips_cdmm_bus_discover(struct mips_cdmm_bus *bus)
+{
+ void __iomem *cdmm;
+ u32 acsr;
+ unsigned int drb, type, size, rev;
+ struct mips_cdmm_device *dev;
+ unsigned int cpu = smp_processor_id();
+ int ret = 0;
+ int id = 0;
+
+ /* Skip the first block if it's reserved for more registers */
+ drb = bus->drbs_reserved;
+ cdmm = bus->regs;
+
+ /* Discover devices */
+ bus->discovered = true;
+ pr_info("cdmm%u discovery (%u blocks)\n", cpu, bus->drbs);
+ for (; drb < bus->drbs; drb += size + 1) {
+ acsr = readl(cdmm + drb * CDMM_DRB_SIZE);
+ type = (acsr & CDMM_ACSR_DEVTYPE) >> CDMM_ACSR_DEVTYPE_SHIFT;
+ size = (acsr & CDMM_ACSR_DEVSIZE) >> CDMM_ACSR_DEVSIZE_SHIFT;
+ rev = (acsr & CDMM_ACSR_DEVREV) >> CDMM_ACSR_DEVREV_SHIFT;
+
+ if (!type)
+ continue;
+
+ pr_info("cdmm%u-%u: @%u (%#x..%#x), type 0x%02x, rev %u\n",
+ cpu, id, drb, drb * CDMM_DRB_SIZE,
+ (drb + size + 1) * CDMM_DRB_SIZE - 1,
+ type, rev);
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ break;
+
+ dev->cpu = cpu;
+ dev->res.start = bus->phys + drb * CDMM_DRB_SIZE;
+ dev->res.end = bus->phys +
+ (drb + size + 1) * CDMM_DRB_SIZE - 1;
+ dev->res.flags = IORESOURCE_MEM;
+ dev->type = type;
+ dev->rev = rev;
+ dev->dev.parent = get_cpu_device(cpu);
+ dev->dev.bus = &mips_cdmm_bustype;
+ dev->dev.id = atomic_inc_return(&mips_cdmm_next_id);
+ dev->dev.release = mips_cdmm_release;
+
+ dev_set_name(&dev->dev, "cdmm%u-%u", cpu, id);
+ ++id;
+ ret = device_register(&dev->dev);
+ if (ret) {
+ put_device(&dev->dev);
+ kfree(dev);
+ }
+ }
+}
+
+
+/*
+ * CPU hotplug and initialisation
+ *
+ * All the CDMM driver callbacks need to be executed on the appropriate CPU from
+ * workqueues. For the CPU callbacks, they need to be called for all devices on
+ * that CPU, so the work function calls bus_for_each_dev, using a helper
+ * (generated with BUILD_PERDEV_HELPER) to call the driver callback if the
+ * device's CPU matches.
+ */
+
+/**
+ * BUILD_PERDEV_HELPER() - Helper to call a CDMM driver callback if CPU matches.
+ * @_name: Name of CDMM driver callback function.
+ *
+ * Generates a bus_for_each_dev callback function to call a specific CDMM driver
+ * callback function for the device if the device's CPU matches that pointed to
+ * by the data argument.
+ *
+ * This is used for informing drivers for all devices on a given CPU of some
+ * event (such as the CPU going online/offline).
+ *
+ * It is expected to already be called from the appropriate CPU.
+ */
+#define BUILD_PERDEV_HELPER(_name) \
+static int mips_cdmm_##_name##_helper(struct device *dev, void *data) \
+{ \
+ struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev); \
+ struct mips_cdmm_driver *cdrv; \
+ unsigned int cpu = *(unsigned int *)data; \
+ \
+ if (cdev->cpu != cpu || !dev->driver) \
+ return 0; \
+ \
+ cdrv = to_mips_cdmm_driver(dev->driver); \
+ if (!cdrv->_name) \
+ return 0; \
+ return cdrv->_name(cdev); \
+}
+
+/* bus_for_each_dev callback helper functions */
+BUILD_PERDEV_HELPER(cpu_down) /* int mips_cdmm_cpu_down_helper(...) */
+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.
+ */
+static long mips_cdmm_bus_down(void *data)
+{
+ struct mips_cdmm_bus *bus;
+ long ret;
+
+ /* Inform all the devices on the bus */
+ ret = bus_for_each_dev(&mips_cdmm_bustype, NULL, data,
+ mips_cdmm_cpu_down_helper);
+
+ /*
+ * While bus is offline, each use of it should reconfigure it just in
+ * case it is first use when coming back online again.
+ */
+ bus = mips_cdmm_get_bus();
+ if (!IS_ERR(bus))
+ bus->offline = true;
+
+ return ret;
+}
+
+/**
+ * mips_cdmm_bus_up() - Bring up the CDMM bus.
+ * @data: Pointer to unsigned int CPU number.
+ *
+ * This work_on_cpu callback function is executed on a given CPU to discover
+ * 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.
+ */
+static long mips_cdmm_bus_up(void *data)
+{
+ struct mips_cdmm_bus *bus;
+ long ret;
+
+ bus = mips_cdmm_get_bus();
+ ret = mips_cdmm_setup(bus);
+ if (ret)
+ return ret;
+
+ /* Bus now set up, so we can drop the offline flag if still set */
+ bus->offline = false;
+
+ if (!bus->discovered)
+ mips_cdmm_bus_discover(bus);
+ else
+ /* Inform all the devices on the bus */
+ ret = bus_for_each_dev(&mips_cdmm_bustype, NULL, data,
+ mips_cdmm_cpu_up_helper);
+
+ return ret;
+}
+
+/**
+ * mips_cdmm_cpu_notify() - Take action when a CPU is going online or offline.
+ * @nb: CPU notifier block .
+ * @action: Event that has taken place (CPU_*).
+ * @data: CPU number.
+ *
+ * This notifier is used to keep the CDMM buses updated as CPUs are offlined and
+ * onlined. When CPUs go offline or come back online, so does their CDMM bus, so
+ * devices must be informed. Also when CPUs come online for the first time the
+ * devices on the CDMM bus need discovering.
+ *
+ * Returns: NOTIFY_OK if event was used.
+ * NOTIFY_DONE if we didn't care.
+ */
+static int mips_cdmm_cpu_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ unsigned int cpu = (unsigned int)data;
+
+ switch (action & ~CPU_TASKS_FROZEN) {
+ case CPU_ONLINE:
+ case CPU_DOWN_FAILED:
+ work_on_cpu(cpu, mips_cdmm_bus_up, &cpu);
+ break;
+ case CPU_DOWN_PREPARE:
+ work_on_cpu(cpu, mips_cdmm_bus_down, &cpu);
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block mips_cdmm_cpu_nb = {
+ .notifier_call = mips_cdmm_cpu_notify,
+};
+
+/**
+ * mips_cdmm_init() - Initialise CDMM bus.
+ *
+ * Initialise CDMM bus, discover CDMM devices for online CPUs, and arrange for
+ * hotplug notifications so the CDMM drivers can be kept up to date.
+ */
+static int __init mips_cdmm_init(void)
+{
+ unsigned int cpu;
+ int ret;
+
+ /* Register the bus */
+ ret = bus_register(&mips_cdmm_bustype);
+ if (ret)
+ return ret;
+
+ /* We want to be notified about new CPUs */
+ ret = register_cpu_notifier(&mips_cdmm_cpu_nb);
+ if (ret) {
+ pr_warn("cdmm: Failed to register CPU notifier\n");
+ goto out;
+ }
+
+ /* Discover devices on CDMM of online CPUs */
+ for_each_online_cpu(cpu)
+ work_on_cpu(cpu, mips_cdmm_bus_up, &cpu);
+
+ return 0;
+out:
+ bus_unregister(&mips_cdmm_bustype);
+ return ret;
+}
+subsys_initcall(mips_cdmm_init);
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d478ceb69c5f..e43ff53f85a6 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_ARCH_MMP) += mmp/
endif
obj-$(CONFIG_PLAT_ORION) += mvebu/
obj-$(CONFIG_ARCH_MXS) += mxs/
+obj-$(CONFIG_MACH_PISTACHIO) += pistachio/
obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
diff --git a/drivers/clk/pistachio/Makefile b/drivers/clk/pistachio/Makefile
new file mode 100644
index 000000000000..f1e151fbef65
--- /dev/null
+++ b/drivers/clk/pistachio/Makefile
@@ -0,0 +1,3 @@
+obj-y += clk.o
+obj-y += clk-pll.o
+obj-y += clk-pistachio.o
diff --git a/drivers/clk/pistachio/clk-pistachio.c b/drivers/clk/pistachio/clk-pistachio.c
new file mode 100644
index 000000000000..8c0fe8828f99
--- /dev/null
+++ b/drivers/clk/pistachio/clk-pistachio.c
@@ -0,0 +1,329 @@
+/*
+ * Pistachio SoC clock controllers
+ *
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+
+#include <dt-bindings/clock/pistachio-clk.h>
+
+#include "clk.h"
+
+static struct pistachio_gate pistachio_gates[] __initdata = {
+ GATE(CLK_MIPS, "mips", "mips_div", 0x104, 0),
+ GATE(CLK_AUDIO_IN, "audio_in", "audio_clk_in_gate", 0x104, 1),
+ GATE(CLK_AUDIO, "audio", "audio_div", 0x104, 2),
+ GATE(CLK_I2S, "i2s", "i2s_div", 0x104, 3),
+ GATE(CLK_SPDIF, "spdif", "spdif_div", 0x104, 4),
+ GATE(CLK_AUDIO_DAC, "audio_dac", "audio_dac_div", 0x104, 5),
+ GATE(CLK_RPU_V, "rpu_v", "rpu_v_div", 0x104, 6),
+ GATE(CLK_RPU_L, "rpu_l", "rpu_l_div", 0x104, 7),
+ GATE(CLK_RPU_SLEEP, "rpu_sleep", "rpu_sleep_div", 0x104, 8),
+ GATE(CLK_WIFI_PLL_GATE, "wifi_pll_gate", "wifi_pll_mux", 0x104, 9),
+ GATE(CLK_RPU_CORE, "rpu_core", "rpu_core_div", 0x104, 10),
+ GATE(CLK_WIFI_ADC, "wifi_adc", "wifi_div8_mux", 0x104, 11),
+ GATE(CLK_WIFI_DAC, "wifi_dac", "wifi_div4_mux", 0x104, 12),
+ GATE(CLK_USB_PHY, "usb_phy", "usb_phy_div", 0x104, 13),
+ GATE(CLK_ENET_IN, "enet_in", "enet_clk_in_gate", 0x104, 14),
+ GATE(CLK_ENET, "enet", "enet_div", 0x104, 15),
+ GATE(CLK_UART0, "uart0", "uart0_div", 0x104, 16),
+ GATE(CLK_UART1, "uart1", "uart1_div", 0x104, 17),
+ GATE(CLK_PERIPH_SYS, "periph_sys", "sys_internal_div", 0x104, 18),
+ GATE(CLK_SPI0, "spi0", "spi0_div", 0x104, 19),
+ GATE(CLK_SPI1, "spi1", "spi1_div", 0x104, 20),
+ GATE(CLK_EVENT_TIMER, "event_timer", "event_timer_div", 0x104, 21),
+ GATE(CLK_AUX_ADC_INTERNAL, "aux_adc_internal", "sys_internal_div",
+ 0x104, 22),
+ GATE(CLK_AUX_ADC, "aux_adc", "aux_adc_div", 0x104, 23),
+ GATE(CLK_SD_HOST, "sd_host", "sd_host_div", 0x104, 24),
+ GATE(CLK_BT, "bt", "bt_div", 0x104, 25),
+ GATE(CLK_BT_DIV4, "bt_div4", "bt_div4_div", 0x104, 26),
+ GATE(CLK_BT_DIV8, "bt_div8", "bt_div8_div", 0x104, 27),
+ GATE(CLK_BT_1MHZ, "bt_1mhz", "bt_1mhz_div", 0x104, 28),
+};
+
+static struct pistachio_fixed_factor pistachio_ffs[] __initdata = {
+ FIXED_FACTOR(CLK_WIFI_DIV4, "wifi_div4", "wifi_pll", 4),
+ FIXED_FACTOR(CLK_WIFI_DIV8, "wifi_div8", "wifi_pll", 8),
+};
+
+static struct pistachio_div pistachio_divs[] __initdata = {
+ DIV(CLK_MIPS_INTERNAL_DIV, "mips_internal_div", "mips_pll_mux",
+ 0x204, 2),
+ DIV(CLK_MIPS_DIV, "mips_div", "mips_internal_div", 0x208, 8),
+ DIV_F(CLK_AUDIO_DIV, "audio_div", "audio_mux",
+ 0x20c, 8, CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(CLK_I2S_DIV, "i2s_div", "audio_pll_mux",
+ 0x210, 8, CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(CLK_SPDIF_DIV, "spdif_div", "audio_pll_mux",
+ 0x214, 8, CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(CLK_AUDIO_DAC_DIV, "audio_dac_div", "audio_pll_mux",
+ 0x218, 8, CLK_DIVIDER_ROUND_CLOSEST),
+ DIV(CLK_RPU_V_DIV, "rpu_v_div", "rpu_v_pll_mux", 0x21c, 2),
+ DIV(CLK_RPU_L_DIV, "rpu_l_div", "rpu_l_mux", 0x220, 2),
+ DIV(CLK_RPU_SLEEP_DIV, "rpu_sleep_div", "xtal", 0x224, 10),
+ DIV(CLK_RPU_CORE_DIV, "rpu_core_div", "rpu_core_mux", 0x228, 3),
+ DIV(CLK_USB_PHY_DIV, "usb_phy_div", "sys_internal_div", 0x22c, 6),
+ DIV(CLK_ENET_DIV, "enet_div", "enet_mux", 0x230, 6),
+ DIV_F(CLK_UART0_INTERNAL_DIV, "uart0_internal_div", "sys_pll_mux",
+ 0x234, 3, CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(CLK_UART0_DIV, "uart0_div", "uart0_internal_div", 0x238, 10,
+ CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(CLK_UART1_INTERNAL_DIV, "uart1_internal_div", "sys_pll_mux",
+ 0x23c, 3, CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(CLK_UART1_DIV, "uart1_div", "uart1_internal_div", 0x240, 10,
+ CLK_DIVIDER_ROUND_CLOSEST),
+ DIV(CLK_SYS_INTERNAL_DIV, "sys_internal_div", "sys_pll_mux", 0x244, 3),
+ DIV(CLK_SPI0_INTERNAL_DIV, "spi0_internal_div", "sys_pll_mux",
+ 0x248, 3),
+ DIV(CLK_SPI0_DIV, "spi0_div", "spi0_internal_div", 0x24c, 7),
+ DIV(CLK_SPI1_INTERNAL_DIV, "spi1_internal_div", "sys_pll_mux",
+ 0x250, 3),
+ DIV(CLK_SPI1_DIV, "spi1_div", "spi1_internal_div", 0x254, 7),
+ DIV(CLK_EVENT_TIMER_INTERNAL_DIV, "event_timer_internal_div",
+ "event_timer_mux", 0x258, 3),
+ DIV(CLK_EVENT_TIMER_DIV, "event_timer_div", "event_timer_internal_div",
+ 0x25c, 12),
+ DIV(CLK_AUX_ADC_INTERNAL_DIV, "aux_adc_internal_div",
+ "aux_adc_internal", 0x260, 3),
+ DIV(CLK_AUX_ADC_DIV, "aux_adc_div", "aux_adc_internal_div", 0x264, 10),
+ DIV(CLK_SD_HOST_DIV, "sd_host_div", "sd_host_mux", 0x268, 6),
+ DIV(CLK_BT_DIV, "bt_div", "bt_pll_mux", 0x26c, 6),
+ DIV(CLK_BT_DIV4_DIV, "bt_div4_div", "bt_pll_mux", 0x270, 6),
+ DIV(CLK_BT_DIV8_DIV, "bt_div8_div", "bt_pll_mux", 0x274, 6),
+ DIV(CLK_BT_1MHZ_INTERNAL_DIV, "bt_1mhz_internal_div", "bt_pll_mux",
+ 0x278, 3),
+ DIV(CLK_BT_1MHZ_DIV, "bt_1mhz_div", "bt_1mhz_internal_div", 0x27c, 10),
+};
+
+PNAME(mux_xtal_audio_refclk) = { "xtal", "audio_clk_in_gate" };
+PNAME(mux_xtal_mips) = { "xtal", "mips_pll" };
+PNAME(mux_xtal_audio) = { "xtal", "audio_pll", "audio_in" };
+PNAME(mux_audio_debug) = { "audio_pll_mux", "debug_mux" };
+PNAME(mux_xtal_rpu_v) = { "xtal", "rpu_v_pll" };
+PNAME(mux_xtal_rpu_l) = { "xtal", "rpu_l_pll" };
+PNAME(mux_rpu_l_mips) = { "rpu_l_pll_mux", "mips_pll_mux" };
+PNAME(mux_xtal_wifi) = { "xtal", "wifi_pll" };
+PNAME(mux_xtal_wifi_div4) = { "xtal", "wifi_div4" };
+PNAME(mux_xtal_wifi_div8) = { "xtal", "wifi_div8" };
+PNAME(mux_wifi_div4_rpu_l) = { "wifi_pll_gate", "wifi_div4_mux",
+ "rpu_l_pll_mux" };
+PNAME(mux_xtal_sys) = { "xtal", "sys_pll" };
+PNAME(mux_sys_enet) = { "sys_internal_div", "enet_in" };
+PNAME(mux_audio_sys) = { "audio_pll_mux", "sys_internal_div" };
+PNAME(mux_sys_bt) = { "sys_internal_div", "bt_pll_mux" };
+PNAME(mux_xtal_bt) = { "xtal", "bt_pll" };
+
+static struct pistachio_mux pistachio_muxes[] __initdata = {
+ MUX(CLK_AUDIO_REF_MUX, "audio_refclk_mux", mux_xtal_audio_refclk,
+ 0x200, 0),
+ MUX(CLK_MIPS_PLL_MUX, "mips_pll_mux", mux_xtal_mips, 0x200, 1),
+ MUX(CLK_AUDIO_PLL_MUX, "audio_pll_mux", mux_xtal_audio, 0x200, 2),
+ MUX(CLK_AUDIO_MUX, "audio_mux", mux_audio_debug, 0x200, 4),
+ MUX(CLK_RPU_V_PLL_MUX, "rpu_v_pll_mux", mux_xtal_rpu_v, 0x200, 5),
+ MUX(CLK_RPU_L_PLL_MUX, "rpu_l_pll_mux", mux_xtal_rpu_l, 0x200, 6),
+ MUX(CLK_RPU_L_MUX, "rpu_l_mux", mux_rpu_l_mips, 0x200, 7),
+ MUX(CLK_WIFI_PLL_MUX, "wifi_pll_mux", mux_xtal_wifi, 0x200, 8),
+ MUX(CLK_WIFI_DIV4_MUX, "wifi_div4_mux", mux_xtal_wifi_div4, 0x200, 9),
+ MUX(CLK_WIFI_DIV8_MUX, "wifi_div8_mux", mux_xtal_wifi_div8, 0x200, 10),
+ MUX(CLK_RPU_CORE_MUX, "rpu_core_mux", mux_wifi_div4_rpu_l, 0x200, 11),
+ MUX(CLK_SYS_PLL_MUX, "sys_pll_mux", mux_xtal_sys, 0x200, 13),
+ MUX(CLK_ENET_MUX, "enet_mux", mux_sys_enet, 0x200, 14),
+ MUX(CLK_EVENT_TIMER_MUX, "event_timer_mux", mux_audio_sys, 0x200, 15),
+ MUX(CLK_SD_HOST_MUX, "sd_host_mux", mux_sys_bt, 0x200, 16),
+ MUX(CLK_BT_PLL_MUX, "bt_pll_mux", mux_xtal_bt, 0x200, 17),
+};
+
+static struct pistachio_pll pistachio_plls[] __initdata = {
+ PLL_FIXED(CLK_MIPS_PLL, "mips_pll", "xtal", PLL_GF40LP_LAINT, 0x0),
+ PLL_FIXED(CLK_AUDIO_PLL, "audio_pll", "audio_refclk_mux",
+ PLL_GF40LP_FRAC, 0xc),
+ PLL_FIXED(CLK_RPU_V_PLL, "rpu_v_pll", "xtal", PLL_GF40LP_LAINT, 0x20),
+ PLL_FIXED(CLK_RPU_L_PLL, "rpu_l_pll", "xtal", PLL_GF40LP_LAINT, 0x2c),
+ PLL_FIXED(CLK_SYS_PLL, "sys_pll", "xtal", PLL_GF40LP_FRAC, 0x38),
+ PLL_FIXED(CLK_WIFI_PLL, "wifi_pll", "xtal", PLL_GF40LP_FRAC, 0x4c),
+ PLL_FIXED(CLK_BT_PLL, "bt_pll", "xtal", PLL_GF40LP_LAINT, 0x60),
+};
+
+PNAME(mux_debug) = { "mips_pll_mux", "rpu_v_pll_mux",
+ "rpu_l_pll_mux", "sys_pll_mux",
+ "wifi_pll_mux", "bt_pll_mux" };
+static u32 mux_debug_idx[] = { 0x0, 0x1, 0x2, 0x4, 0x8, 0x10 };
+
+static unsigned int pistachio_critical_clks[] __initdata = {
+ CLK_MIPS,
+ CLK_PERIPH_SYS,
+};
+
+static void __init pistachio_clk_init(struct device_node *np)
+{
+ struct pistachio_clk_provider *p;
+ struct clk *debug_clk;
+
+ p = pistachio_clk_alloc_provider(np, CLK_NR_CLKS);
+ if (!p)
+ return;
+
+ pistachio_clk_register_pll(p, pistachio_plls,
+ ARRAY_SIZE(pistachio_plls));
+ pistachio_clk_register_mux(p, pistachio_muxes,
+ ARRAY_SIZE(pistachio_muxes));
+ pistachio_clk_register_div(p, pistachio_divs,
+ ARRAY_SIZE(pistachio_divs));
+ pistachio_clk_register_fixed_factor(p, pistachio_ffs,
+ ARRAY_SIZE(pistachio_ffs));
+ pistachio_clk_register_gate(p, pistachio_gates,
+ ARRAY_SIZE(pistachio_gates));
+
+ debug_clk = clk_register_mux_table(NULL, "debug_mux", mux_debug,
+ ARRAY_SIZE(mux_debug),
+ CLK_SET_RATE_NO_REPARENT,
+ p->base + 0x200, 18, 0x1f, 0,
+ mux_debug_idx, NULL);
+ p->clk_data.clks[CLK_DEBUG_MUX] = debug_clk;
+
+ pistachio_clk_register_provider(p);
+
+ pistachio_clk_force_enable(p, pistachio_critical_clks,
+ ARRAY_SIZE(pistachio_critical_clks));
+}
+CLK_OF_DECLARE(pistachio_clk, "img,pistachio-clk", pistachio_clk_init);
+
+static struct pistachio_gate pistachio_periph_gates[] __initdata = {
+ GATE(PERIPH_CLK_SYS, "sys", "periph_sys", 0x100, 0),
+ GATE(PERIPH_CLK_SYS_BUS, "bus_sys", "periph_sys", 0x100, 1),
+ GATE(PERIPH_CLK_DDR, "ddr", "periph_sys", 0x100, 2),
+ GATE(PERIPH_CLK_ROM, "rom", "rom_div", 0x100, 3),
+ GATE(PERIPH_CLK_COUNTER_FAST, "counter_fast", "counter_fast_div",
+ 0x100, 4),
+ GATE(PERIPH_CLK_COUNTER_SLOW, "counter_slow", "counter_slow_div",
+ 0x100, 5),
+ GATE(PERIPH_CLK_IR, "ir", "ir_div", 0x100, 6),
+ GATE(PERIPH_CLK_WD, "wd", "wd_div", 0x100, 7),
+ GATE(PERIPH_CLK_PDM, "pdm", "pdm_div", 0x100, 8),
+ GATE(PERIPH_CLK_PWM, "pwm", "pwm_div", 0x100, 9),
+ GATE(PERIPH_CLK_I2C0, "i2c0", "i2c0_div", 0x100, 10),
+ GATE(PERIPH_CLK_I2C1, "i2c1", "i2c1_div", 0x100, 11),
+ GATE(PERIPH_CLK_I2C2, "i2c2", "i2c2_div", 0x100, 12),
+ GATE(PERIPH_CLK_I2C3, "i2c3", "i2c3_div", 0x100, 13),
+};
+
+static struct pistachio_div pistachio_periph_divs[] __initdata = {
+ DIV(PERIPH_CLK_ROM_DIV, "rom_div", "periph_sys", 0x10c, 7),
+ DIV(PERIPH_CLK_COUNTER_FAST_DIV, "counter_fast_div", "periph_sys",
+ 0x110, 7),
+ DIV(PERIPH_CLK_COUNTER_SLOW_PRE_DIV, "counter_slow_pre_div",
+ "periph_sys", 0x114, 7),
+ DIV(PERIPH_CLK_COUNTER_SLOW_DIV, "counter_slow_div",
+ "counter_slow_pre_div", 0x118, 7),
+ DIV_F(PERIPH_CLK_IR_PRE_DIV, "ir_pre_div", "periph_sys", 0x11c, 7,
+ CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(PERIPH_CLK_IR_DIV, "ir_div", "ir_pre_div", 0x120, 7,
+ CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(PERIPH_CLK_WD_PRE_DIV, "wd_pre_div", "periph_sys", 0x124, 7,
+ CLK_DIVIDER_ROUND_CLOSEST),
+ DIV_F(PERIPH_CLK_WD_DIV, "wd_div", "wd_pre_div", 0x128, 7,
+ CLK_DIVIDER_ROUND_CLOSEST),
+ DIV(PERIPH_CLK_PDM_PRE_DIV, "pdm_pre_div", "periph_sys", 0x12c, 7),
+ DIV(PERIPH_CLK_PDM_DIV, "pdm_div", "pdm_pre_div", 0x130, 7),
+ DIV(PERIPH_CLK_PWM_PRE_DIV, "pwm_pre_div", "periph_sys", 0x134, 7),
+ DIV(PERIPH_CLK_PWM_DIV, "pwm_div", "pwm_pre_div", 0x138, 7),
+ DIV(PERIPH_CLK_I2C0_PRE_DIV, "i2c0_pre_div", "periph_sys", 0x13c, 7),
+ DIV(PERIPH_CLK_I2C0_DIV, "i2c0_div", "i2c0_pre_div", 0x140, 7),
+ DIV(PERIPH_CLK_I2C1_PRE_DIV, "i2c1_pre_div", "periph_sys", 0x144, 7),
+ DIV(PERIPH_CLK_I2C1_DIV, "i2c1_div", "i2c1_pre_div", 0x148, 7),
+ DIV(PERIPH_CLK_I2C2_PRE_DIV, "i2c2_pre_div", "periph_sys", 0x14c, 7),
+ DIV(PERIPH_CLK_I2C2_DIV, "i2c2_div", "i2c2_pre_div", 0x150, 7),
+ DIV(PERIPH_CLK_I2C3_PRE_DIV, "i2c3_pre_div", "periph_sys", 0x154, 7),
+ DIV(PERIPH_CLK_I2C3_DIV, "i2c3_div", "i2c3_pre_div", 0x158, 7),
+};
+
+static void __init pistachio_clk_periph_init(struct device_node *np)
+{
+ struct pistachio_clk_provider *p;
+
+ p = pistachio_clk_alloc_provider(np, PERIPH_CLK_NR_CLKS);
+ if (!p)
+ return;
+
+ pistachio_clk_register_div(p, pistachio_periph_divs,
+ ARRAY_SIZE(pistachio_periph_divs));
+ pistachio_clk_register_gate(p, pistachio_periph_gates,
+ ARRAY_SIZE(pistachio_periph_gates));
+
+ pistachio_clk_register_provider(p);
+}
+CLK_OF_DECLARE(pistachio_clk_periph, "img,pistachio-clk-periph",
+ pistachio_clk_periph_init);
+
+static struct pistachio_gate pistachio_sys_gates[] __initdata = {
+ GATE(SYS_CLK_I2C0, "i2c0_sys", "sys", 0x8, 0),
+ GATE(SYS_CLK_I2C1, "i2c1_sys", "sys", 0x8, 1),
+ GATE(SYS_CLK_I2C2, "i2c2_sys", "sys", 0x8, 2),
+ GATE(SYS_CLK_I2C3, "i2c3_sys", "sys", 0x8, 3),
+ GATE(SYS_CLK_I2S_IN, "i2s_in_sys", "sys", 0x8, 4),
+ GATE(SYS_CLK_PAUD_OUT, "paud_out_sys", "sys", 0x8, 5),
+ GATE(SYS_CLK_SPDIF_OUT, "spdif_out_sys", "sys", 0x8, 6),
+ GATE(SYS_CLK_SPI0_MASTER, "spi0_master_sys", "sys", 0x8, 7),
+ GATE(SYS_CLK_SPI0_SLAVE, "spi0_slave_sys", "sys", 0x8, 8),
+ GATE(SYS_CLK_PWM, "pwm_sys", "sys", 0x8, 9),
+ GATE(SYS_CLK_UART0, "uart0_sys", "sys", 0x8, 10),
+ GATE(SYS_CLK_UART1, "uart1_sys", "sys", 0x8, 11),
+ GATE(SYS_CLK_SPI1, "spi1_sys", "sys", 0x8, 12),
+ GATE(SYS_CLK_MDC, "mdc_sys", "sys", 0x8, 13),
+ GATE(SYS_CLK_SD_HOST, "sd_host_sys", "sys", 0x8, 14),
+ GATE(SYS_CLK_ENET, "enet_sys", "sys", 0x8, 15),
+ GATE(SYS_CLK_IR, "ir_sys", "sys", 0x8, 16),
+ GATE(SYS_CLK_WD, "wd_sys", "sys", 0x8, 17),
+ GATE(SYS_CLK_TIMER, "timer_sys", "sys", 0x8, 18),
+ GATE(SYS_CLK_I2S_OUT, "i2s_out_sys", "sys", 0x8, 24),
+ GATE(SYS_CLK_SPDIF_IN, "spdif_in_sys", "sys", 0x8, 25),
+ GATE(SYS_CLK_EVENT_TIMER, "event_timer_sys", "sys", 0x8, 26),
+ GATE(SYS_CLK_HASH, "hash_sys", "sys", 0x8, 27),
+};
+
+static void __init pistachio_cr_periph_init(struct device_node *np)
+{
+ struct pistachio_clk_provider *p;
+
+ p = pistachio_clk_alloc_provider(np, SYS_CLK_NR_CLKS);
+ if (!p)
+ return;
+
+ pistachio_clk_register_gate(p, pistachio_sys_gates,
+ ARRAY_SIZE(pistachio_sys_gates));
+
+ pistachio_clk_register_provider(p);
+}
+CLK_OF_DECLARE(pistachio_cr_periph, "img,pistachio-cr-periph",
+ pistachio_cr_periph_init);
+
+static struct pistachio_gate pistachio_ext_gates[] __initdata = {
+ GATE(EXT_CLK_ENET_IN, "enet_clk_in_gate", "enet_clk_in", 0x58, 5),
+ GATE(EXT_CLK_AUDIO_IN, "audio_clk_in_gate", "audio_clk_in", 0x58, 8)
+};
+
+static void __init pistachio_cr_top_init(struct device_node *np)
+{
+ struct pistachio_clk_provider *p;
+
+ p = pistachio_clk_alloc_provider(np, EXT_CLK_NR_CLKS);
+ if (!p)
+ return;
+
+ pistachio_clk_register_gate(p, pistachio_ext_gates,
+ ARRAY_SIZE(pistachio_ext_gates));
+
+ pistachio_clk_register_provider(p);
+}
+CLK_OF_DECLARE(pistachio_cr_top, "img,pistachio-cr-top",
+ pistachio_cr_top_init);
diff --git a/drivers/clk/pistachio/clk-pll.c b/drivers/clk/pistachio/clk-pll.c
new file mode 100644
index 000000000000..de537560bf70
--- /dev/null
+++ b/drivers/clk/pistachio/clk-pll.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include "clk.h"
+
+#define PLL_STATUS 0x0
+#define PLL_STATUS_LOCK BIT(0)
+
+#define PLL_CTRL1 0x4
+#define PLL_CTRL1_REFDIV_SHIFT 0
+#define PLL_CTRL1_REFDIV_MASK 0x3f
+#define PLL_CTRL1_FBDIV_SHIFT 6
+#define PLL_CTRL1_FBDIV_MASK 0xfff
+#define PLL_INT_CTRL1_POSTDIV1_SHIFT 18
+#define PLL_INT_CTRL1_POSTDIV1_MASK 0x7
+#define PLL_INT_CTRL1_POSTDIV2_SHIFT 21
+#define PLL_INT_CTRL1_POSTDIV2_MASK 0x7
+#define PLL_INT_CTRL1_PD BIT(24)
+#define PLL_INT_CTRL1_DSMPD BIT(25)
+#define PLL_INT_CTRL1_FOUTPOSTDIVPD BIT(26)
+#define PLL_INT_CTRL1_FOUTVCOPD BIT(27)
+
+#define PLL_CTRL2 0x8
+#define PLL_FRAC_CTRL2_FRAC_SHIFT 0
+#define PLL_FRAC_CTRL2_FRAC_MASK 0xffffff
+#define PLL_FRAC_CTRL2_POSTDIV1_SHIFT 24
+#define PLL_FRAC_CTRL2_POSTDIV1_MASK 0x7
+#define PLL_FRAC_CTRL2_POSTDIV2_SHIFT 27
+#define PLL_FRAC_CTRL2_POSTDIV2_MASK 0x7
+#define PLL_INT_CTRL2_BYPASS BIT(28)
+
+#define PLL_CTRL3 0xc
+#define PLL_FRAC_CTRL3_PD BIT(0)
+#define PLL_FRAC_CTRL3_DACPD BIT(1)
+#define PLL_FRAC_CTRL3_DSMPD BIT(2)
+#define PLL_FRAC_CTRL3_FOUTPOSTDIVPD BIT(3)
+#define PLL_FRAC_CTRL3_FOUT4PHASEPD BIT(4)
+#define PLL_FRAC_CTRL3_FOUTVCOPD BIT(5)
+
+#define PLL_CTRL4 0x10
+#define PLL_FRAC_CTRL4_BYPASS BIT(28)
+
+struct pistachio_clk_pll {
+ struct clk_hw hw;
+ void __iomem *base;
+ struct pistachio_pll_rate_table *rates;
+ unsigned int nr_rates;
+};
+
+static inline u32 pll_readl(struct pistachio_clk_pll *pll, u32 reg)
+{
+ return readl(pll->base + reg);
+}
+
+static inline void pll_writel(struct pistachio_clk_pll *pll, u32 val, u32 reg)
+{
+ writel(val, pll->base + reg);
+}
+
+static inline u32 do_div_round_closest(u64 dividend, u32 divisor)
+{
+ dividend += divisor / 2;
+ do_div(dividend, divisor);
+
+ return dividend;
+}
+
+static inline struct pistachio_clk_pll *to_pistachio_pll(struct clk_hw *hw)
+{
+ return container_of(hw, struct pistachio_clk_pll, hw);
+}
+
+static struct pistachio_pll_rate_table *
+pll_get_params(struct pistachio_clk_pll *pll, unsigned long fref,
+ unsigned long fout)
+{
+ unsigned int i;
+
+ for (i = 0; i < pll->nr_rates; i++) {
+ if (pll->rates[i].fref == fref && pll->rates[i].fout == fout)
+ return &pll->rates[i];
+ }
+
+ return NULL;
+}
+
+static long pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ unsigned int i;
+
+ for (i = 0; i < pll->nr_rates; i++) {
+ if (i > 0 && pll->rates[i].fref == *parent_rate &&
+ pll->rates[i].fout <= rate)
+ return pll->rates[i - 1].fout;
+ }
+
+ return pll->rates[0].fout;
+}
+
+static int pll_gf40lp_frac_enable(struct clk_hw *hw)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ u32 val;
+
+ val = pll_readl(pll, PLL_CTRL3);
+ val &= ~(PLL_FRAC_CTRL3_PD | PLL_FRAC_CTRL3_DACPD |
+ PLL_FRAC_CTRL3_DSMPD | PLL_FRAC_CTRL3_FOUTPOSTDIVPD |
+ PLL_FRAC_CTRL3_FOUT4PHASEPD | PLL_FRAC_CTRL3_FOUTVCOPD);
+ pll_writel(pll, val, PLL_CTRL3);
+
+ val = pll_readl(pll, PLL_CTRL4);
+ val &= ~PLL_FRAC_CTRL4_BYPASS;
+ pll_writel(pll, val, PLL_CTRL4);
+
+ return 0;
+}
+
+static void pll_gf40lp_frac_disable(struct clk_hw *hw)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ u32 val;
+
+ val = pll_readl(pll, PLL_CTRL3);
+ val |= PLL_FRAC_CTRL3_PD;
+ pll_writel(pll, val, PLL_CTRL3);
+}
+
+static int pll_gf40lp_frac_is_enabled(struct clk_hw *hw)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+
+ return !(pll_readl(pll, PLL_CTRL3) & PLL_FRAC_CTRL3_PD);
+}
+
+static int pll_gf40lp_frac_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ struct pistachio_pll_rate_table *params;
+ bool was_enabled;
+ u32 val;
+
+ params = pll_get_params(pll, parent_rate, rate);
+ if (!params)
+ return -EINVAL;
+
+ was_enabled = pll_gf40lp_frac_is_enabled(hw);
+ if (!was_enabled)
+ pll_gf40lp_frac_enable(hw);
+
+ val = pll_readl(pll, PLL_CTRL1);
+ val &= ~((PLL_CTRL1_REFDIV_MASK << PLL_CTRL1_REFDIV_SHIFT) |
+ (PLL_CTRL1_FBDIV_MASK << PLL_CTRL1_FBDIV_SHIFT));
+ val |= (params->refdiv << PLL_CTRL1_REFDIV_SHIFT) |
+ (params->fbdiv << PLL_CTRL1_FBDIV_SHIFT);
+ pll_writel(pll, val, PLL_CTRL1);
+
+ val = pll_readl(pll, PLL_CTRL2);
+ val &= ~((PLL_FRAC_CTRL2_FRAC_MASK << PLL_FRAC_CTRL2_FRAC_SHIFT) |
+ (PLL_FRAC_CTRL2_POSTDIV1_MASK <<
+ PLL_FRAC_CTRL2_POSTDIV1_SHIFT) |
+ (PLL_FRAC_CTRL2_POSTDIV2_MASK <<
+ PLL_FRAC_CTRL2_POSTDIV2_SHIFT));
+ val |= (params->frac << PLL_FRAC_CTRL2_FRAC_SHIFT) |
+ (params->postdiv1 << PLL_FRAC_CTRL2_POSTDIV1_SHIFT) |
+ (params->postdiv2 << PLL_FRAC_CTRL2_POSTDIV2_SHIFT);
+ pll_writel(pll, val, PLL_CTRL2);
+
+ while (!(pll_readl(pll, PLL_STATUS) & PLL_STATUS_LOCK))
+ cpu_relax();
+
+ if (!was_enabled)
+ pll_gf40lp_frac_disable(hw);
+
+ return 0;
+}
+
+static unsigned long pll_gf40lp_frac_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ u32 val, prediv, fbdiv, frac, postdiv1, postdiv2;
+ u64 rate = parent_rate;
+
+ val = pll_readl(pll, PLL_CTRL1);
+ prediv = (val >> PLL_CTRL1_REFDIV_SHIFT) & PLL_CTRL1_REFDIV_MASK;
+ fbdiv = (val >> PLL_CTRL1_FBDIV_SHIFT) & PLL_CTRL1_FBDIV_MASK;
+
+ val = pll_readl(pll, PLL_CTRL2);
+ postdiv1 = (val >> PLL_FRAC_CTRL2_POSTDIV1_SHIFT) &
+ PLL_FRAC_CTRL2_POSTDIV1_MASK;
+ postdiv2 = (val >> PLL_FRAC_CTRL2_POSTDIV2_SHIFT) &
+ PLL_FRAC_CTRL2_POSTDIV2_MASK;
+ frac = (val >> PLL_FRAC_CTRL2_FRAC_SHIFT) & PLL_FRAC_CTRL2_FRAC_MASK;
+
+ rate *= (fbdiv << 24) + frac;
+ rate = do_div_round_closest(rate, (prediv * postdiv1 * postdiv2) << 24);
+
+ return rate;
+}
+
+static struct clk_ops pll_gf40lp_frac_ops = {
+ .enable = pll_gf40lp_frac_enable,
+ .disable = pll_gf40lp_frac_disable,
+ .is_enabled = pll_gf40lp_frac_is_enabled,
+ .recalc_rate = pll_gf40lp_frac_recalc_rate,
+ .round_rate = pll_round_rate,
+ .set_rate = pll_gf40lp_frac_set_rate,
+};
+
+static struct clk_ops pll_gf40lp_frac_fixed_ops = {
+ .enable = pll_gf40lp_frac_enable,
+ .disable = pll_gf40lp_frac_disable,
+ .is_enabled = pll_gf40lp_frac_is_enabled,
+ .recalc_rate = pll_gf40lp_frac_recalc_rate,
+};
+
+static int pll_gf40lp_laint_enable(struct clk_hw *hw)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ u32 val;
+
+ val = pll_readl(pll, PLL_CTRL1);
+ val &= ~(PLL_INT_CTRL1_PD | PLL_INT_CTRL1_DSMPD |
+ PLL_INT_CTRL1_FOUTPOSTDIVPD | PLL_INT_CTRL1_FOUTVCOPD);
+ pll_writel(pll, val, PLL_CTRL1);
+
+ val = pll_readl(pll, PLL_CTRL2);
+ val &= ~PLL_INT_CTRL2_BYPASS;
+ pll_writel(pll, val, PLL_CTRL2);
+
+ return 0;
+}
+
+static void pll_gf40lp_laint_disable(struct clk_hw *hw)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ u32 val;
+
+ val = pll_readl(pll, PLL_CTRL1);
+ val |= PLL_INT_CTRL1_PD;
+ pll_writel(pll, val, PLL_CTRL1);
+}
+
+static int pll_gf40lp_laint_is_enabled(struct clk_hw *hw)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+
+ return !(pll_readl(pll, PLL_CTRL1) & PLL_INT_CTRL1_PD);
+}
+
+static int pll_gf40lp_laint_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ struct pistachio_pll_rate_table *params;
+ bool was_enabled;
+ u32 val;
+
+ params = pll_get_params(pll, parent_rate, rate);
+ if (!params)
+ return -EINVAL;
+
+ was_enabled = pll_gf40lp_laint_is_enabled(hw);
+ if (!was_enabled)
+ pll_gf40lp_laint_enable(hw);
+
+ val = pll_readl(pll, PLL_CTRL1);
+ val &= ~((PLL_CTRL1_REFDIV_MASK << PLL_CTRL1_REFDIV_SHIFT) |
+ (PLL_CTRL1_FBDIV_MASK << PLL_CTRL1_FBDIV_SHIFT) |
+ (PLL_INT_CTRL1_POSTDIV1_MASK << PLL_INT_CTRL1_POSTDIV1_SHIFT) |
+ (PLL_INT_CTRL1_POSTDIV2_MASK << PLL_INT_CTRL1_POSTDIV2_SHIFT));
+ val |= (params->refdiv << PLL_CTRL1_REFDIV_SHIFT) |
+ (params->fbdiv << PLL_CTRL1_FBDIV_SHIFT) |
+ (params->postdiv1 << PLL_INT_CTRL1_POSTDIV1_SHIFT) |
+ (params->postdiv2 << PLL_INT_CTRL1_POSTDIV2_SHIFT);
+ pll_writel(pll, val, PLL_CTRL1);
+
+ while (!(pll_readl(pll, PLL_STATUS) & PLL_STATUS_LOCK))
+ cpu_relax();
+
+ if (!was_enabled)
+ pll_gf40lp_laint_disable(hw);
+
+ return 0;
+}
+
+static unsigned long pll_gf40lp_laint_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct pistachio_clk_pll *pll = to_pistachio_pll(hw);
+ u32 val, prediv, fbdiv, postdiv1, postdiv2;
+ u64 rate = parent_rate;
+
+ val = pll_readl(pll, PLL_CTRL1);
+ prediv = (val >> PLL_CTRL1_REFDIV_SHIFT) & PLL_CTRL1_REFDIV_MASK;
+ fbdiv = (val >> PLL_CTRL1_FBDIV_SHIFT) & PLL_CTRL1_FBDIV_MASK;
+ postdiv1 = (val >> PLL_INT_CTRL1_POSTDIV1_SHIFT) &
+ PLL_INT_CTRL1_POSTDIV1_MASK;
+ postdiv2 = (val >> PLL_INT_CTRL1_POSTDIV2_SHIFT) &
+ PLL_INT_CTRL1_POSTDIV2_MASK;
+
+ rate *= fbdiv;
+ rate = do_div_round_closest(rate, prediv * postdiv1 * postdiv2);
+
+ return rate;
+}
+
+static struct clk_ops pll_gf40lp_laint_ops = {
+ .enable = pll_gf40lp_laint_enable,
+ .disable = pll_gf40lp_laint_disable,
+ .is_enabled = pll_gf40lp_laint_is_enabled,
+ .recalc_rate = pll_gf40lp_laint_recalc_rate,
+ .round_rate = pll_round_rate,
+ .set_rate = pll_gf40lp_laint_set_rate,
+};
+
+static struct clk_ops pll_gf40lp_laint_fixed_ops = {
+ .enable = pll_gf40lp_laint_enable,
+ .disable = pll_gf40lp_laint_disable,
+ .is_enabled = pll_gf40lp_laint_is_enabled,
+ .recalc_rate = pll_gf40lp_laint_recalc_rate,
+};
+
+static struct clk *pll_register(const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *base,
+ enum pistachio_pll_type type,
+ struct pistachio_pll_rate_table *rates,
+ unsigned int nr_rates)
+{
+ struct pistachio_clk_pll *pll;
+ struct clk_init_data init;
+ struct clk *clk;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.flags = flags | CLK_GET_RATE_NOCACHE;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+
+ switch (type) {
+ case PLL_GF40LP_FRAC:
+ if (rates)
+ init.ops = &pll_gf40lp_frac_ops;
+ else
+ init.ops = &pll_gf40lp_frac_fixed_ops;
+ break;
+ case PLL_GF40LP_LAINT:
+ if (rates)
+ init.ops = &pll_gf40lp_laint_ops;
+ else
+ init.ops = &pll_gf40lp_laint_fixed_ops;
+ break;
+ default:
+ pr_err("Unrecognized PLL type %u\n", type);
+ kfree(pll);
+ return ERR_PTR(-EINVAL);
+ }
+
+ pll->hw.init = &init;
+ pll->base = base;
+ pll->rates = rates;
+ pll->nr_rates = nr_rates;
+
+ clk = clk_register(NULL, &pll->hw);
+ if (IS_ERR(clk))
+ kfree(pll);
+
+ return clk;
+}
+
+void pistachio_clk_register_pll(struct pistachio_clk_provider *p,
+ struct pistachio_pll *pll,
+ unsigned int num)
+{
+ struct clk *clk;
+ unsigned int i;
+
+ for (i = 0; i < num; i++) {
+ clk = pll_register(pll[i].name, pll[i].parent,
+ 0, p->base + pll[i].reg_base,
+ pll[i].type, pll[i].rates,
+ pll[i].nr_rates);
+ p->clk_data.clks[pll[i].id] = clk;
+ }
+}
diff --git a/drivers/clk/pistachio/clk.c b/drivers/clk/pistachio/clk.c
new file mode 100644
index 000000000000..85faa83e1bd7
--- /dev/null
+++ b/drivers/clk/pistachio/clk.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+#include "clk.h"
+
+struct pistachio_clk_provider *
+pistachio_clk_alloc_provider(struct device_node *node, unsigned int num_clks)
+{
+ struct pistachio_clk_provider *p;
+
+ p = kzalloc(sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return p;
+
+ p->clk_data.clks = kcalloc(num_clks, sizeof(struct clk *), GFP_KERNEL);
+ if (!p->clk_data.clks)
+ goto free_provider;
+ p->clk_data.clk_num = num_clks;
+ p->node = node;
+ p->base = of_iomap(node, 0);
+ if (!p->base) {
+ pr_err("Failed to map clock provider registers\n");
+ goto free_clks;
+ }
+
+ return p;
+
+free_clks:
+ kfree(p->clk_data.clks);
+free_provider:
+ kfree(p);
+ return NULL;
+}
+
+void pistachio_clk_register_provider(struct pistachio_clk_provider *p)
+{
+ unsigned int i;
+
+ for (i = 0; i < p->clk_data.clk_num; i++) {
+ if (IS_ERR(p->clk_data.clks[i]))
+ pr_warn("Failed to register clock %d: %ld\n", i,
+ PTR_ERR(p->clk_data.clks[i]));
+ }
+
+ of_clk_add_provider(p->node, of_clk_src_onecell_get, &p->clk_data);
+}
+
+void pistachio_clk_register_gate(struct pistachio_clk_provider *p,
+ struct pistachio_gate *gate,
+ unsigned int num)
+{
+ struct clk *clk;
+ unsigned int i;
+
+ for (i = 0; i < num; i++) {
+ clk = clk_register_gate(NULL, gate[i].name, gate[i].parent,
+ CLK_SET_RATE_PARENT,
+ p->base + gate[i].reg, gate[i].shift,
+ 0, NULL);
+ p->clk_data.clks[gate[i].id] = clk;
+ }
+}
+
+void pistachio_clk_register_mux(struct pistachio_clk_provider *p,
+ struct pistachio_mux *mux,
+ unsigned int num)
+{
+ struct clk *clk;
+ unsigned int i;
+
+ for (i = 0; i < num; i++) {
+ clk = clk_register_mux(NULL, mux[i].name, mux[i].parents,
+ mux[i].num_parents,
+ CLK_SET_RATE_NO_REPARENT,
+ p->base + mux[i].reg, mux[i].shift,
+ get_count_order(mux[i].num_parents),
+ 0, NULL);
+ p->clk_data.clks[mux[i].id] = clk;
+ }
+}
+
+void pistachio_clk_register_div(struct pistachio_clk_provider *p,
+ struct pistachio_div *div,
+ unsigned int num)
+{
+ struct clk *clk;
+ unsigned int i;
+
+ for (i = 0; i < num; i++) {
+ clk = clk_register_divider(NULL, div[i].name, div[i].parent,
+ 0, p->base + div[i].reg, 0,
+ div[i].width, div[i].div_flags,
+ NULL);
+ p->clk_data.clks[div[i].id] = clk;
+ }
+}
+
+void pistachio_clk_register_fixed_factor(struct pistachio_clk_provider *p,
+ struct pistachio_fixed_factor *ff,
+ unsigned int num)
+{
+ struct clk *clk;
+ unsigned int i;
+
+ for (i = 0; i < num; i++) {
+ clk = clk_register_fixed_factor(NULL, ff[i].name, ff[i].parent,
+ 0, 1, ff[i].div);
+ p->clk_data.clks[ff[i].id] = clk;
+ }
+}
+
+void pistachio_clk_force_enable(struct pistachio_clk_provider *p,
+ unsigned int *clk_ids, unsigned int num)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < num; i++) {
+ struct clk *clk = p->clk_data.clks[clk_ids[i]];
+
+ if (IS_ERR(clk))
+ continue;
+
+ err = clk_prepare_enable(clk);
+ if (err)
+ pr_err("Failed to enable clock %s: %d\n",
+ __clk_get_name(clk), err);
+ }
+}
diff --git a/drivers/clk/pistachio/clk.h b/drivers/clk/pistachio/clk.h
new file mode 100644
index 000000000000..52fabbc24624
--- /dev/null
+++ b/drivers/clk/pistachio/clk.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#ifndef __PISTACHIO_CLK_H
+#define __PISTACHIO_CLK_H
+
+#include <linux/clk-provider.h>
+
+struct pistachio_gate {
+ unsigned int id;
+ unsigned long reg;
+ unsigned int shift;
+ const char *name;
+ const char *parent;
+};
+
+#define GATE(_id, _name, _pname, _reg, _shift) \
+ { \
+ .id = _id, \
+ .reg = _reg, \
+ .shift = _shift, \
+ .name = _name, \
+ .parent = _pname, \
+ }
+
+struct pistachio_mux {
+ unsigned int id;
+ unsigned long reg;
+ unsigned int shift;
+ unsigned int num_parents;
+ const char *name;
+ const char **parents;
+};
+
+#define PNAME(x) static const char *x[] __initconst
+
+#define MUX(_id, _name, _pnames, _reg, _shift) \
+ { \
+ .id = _id, \
+ .reg = _reg, \
+ .shift = _shift, \
+ .name = _name, \
+ .parents = _pnames, \
+ .num_parents = ARRAY_SIZE(_pnames) \
+ }
+
+
+struct pistachio_div {
+ unsigned int id;
+ unsigned long reg;
+ unsigned int width;
+ unsigned int div_flags;
+ const char *name;
+ const char *parent;
+};
+
+#define DIV(_id, _name, _pname, _reg, _width) \
+ { \
+ .id = _id, \
+ .reg = _reg, \
+ .width = _width, \
+ .div_flags = 0, \
+ .name = _name, \
+ .parent = _pname, \
+ }
+
+#define DIV_F(_id, _name, _pname, _reg, _width, _div_flags) \
+ { \
+ .id = _id, \
+ .reg = _reg, \
+ .width = _width, \
+ .div_flags = _div_flags, \
+ .name = _name, \
+ .parent = _pname, \
+ }
+
+struct pistachio_fixed_factor {
+ unsigned int id;
+ unsigned int div;
+ const char *name;
+ const char *parent;
+};
+
+#define FIXED_FACTOR(_id, _name, _pname, _div) \
+ { \
+ .id = _id, \
+ .div = _div, \
+ .name = _name, \
+ .parent = _pname, \
+ }
+
+struct pistachio_pll_rate_table {
+ unsigned long fref;
+ unsigned long fout;
+ unsigned int refdiv;
+ unsigned int fbdiv;
+ unsigned int postdiv1;
+ unsigned int postdiv2;
+ unsigned int frac;
+};
+
+enum pistachio_pll_type {
+ PLL_GF40LP_LAINT,
+ PLL_GF40LP_FRAC,
+};
+
+struct pistachio_pll {
+ unsigned int id;
+ unsigned long reg_base;
+ enum pistachio_pll_type type;
+ struct pistachio_pll_rate_table *rates;
+ unsigned int nr_rates;
+ const char *name;
+ const char *parent;
+};
+
+#define PLL(_id, _name, _pname, _type, _reg, _rates) \
+ { \
+ .id = _id, \
+ .reg_base = _reg, \
+ .type = _type, \
+ .rates = _rates, \
+ .nr_rates = ARRAY_SIZE(_rates), \
+ .name = _name, \
+ .parent = _pname, \
+ }
+
+#define PLL_FIXED(_id, _name, _pname, _type, _reg) \
+ { \
+ .id = _id, \
+ .reg_base = _reg, \
+ .type = _type, \
+ .rates = NULL, \
+ .nr_rates = 0, \
+ .name = _name, \
+ .parent = _pname, \
+ }
+
+struct pistachio_clk_provider {
+ struct device_node *node;
+ void __iomem *base;
+ struct clk_onecell_data clk_data;
+};
+
+extern struct pistachio_clk_provider *
+pistachio_clk_alloc_provider(struct device_node *node, unsigned int num_clks);
+extern void pistachio_clk_register_provider(struct pistachio_clk_provider *p);
+
+extern void pistachio_clk_register_gate(struct pistachio_clk_provider *p,
+ struct pistachio_gate *gate,
+ unsigned int num);
+extern void pistachio_clk_register_mux(struct pistachio_clk_provider *p,
+ struct pistachio_mux *mux,
+ unsigned int num);
+extern void pistachio_clk_register_div(struct pistachio_clk_provider *p,
+ struct pistachio_div *div,
+ unsigned int num);
+extern void
+pistachio_clk_register_fixed_factor(struct pistachio_clk_provider *p,
+ struct pistachio_fixed_factor *ff,
+ unsigned int num);
+extern void pistachio_clk_register_pll(struct pistachio_clk_provider *p,
+ struct pistachio_pll *pll,
+ unsigned int num);
+
+extern void pistachio_clk_force_enable(struct pistachio_clk_provider *p,
+ unsigned int *clk_ids, unsigned int num);
+
+#endif
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index a0b036ccb118..b4ac7cfae441 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -233,7 +233,7 @@ config CLKSRC_QCOM
config CLKSRC_VERSATILE
bool "ARM Versatile (Express) reference platforms clock source"
- depends on GENERIC_SCHED_CLOCK && !ARCH_USES_GETTIMEOFFSET
+ depends on PLAT_VERSATILE && GENERIC_SCHED_CLOCK && !ARCH_USES_GETTIMEOFFSET
select CLKSRC_OF
default y if MFD_VEXPRESS_SYSREG
help
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index 3bd31b1321f6..b81ed1a5342d 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -5,6 +5,7 @@
*
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
*/
+#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/cpu.h>
#include <linux/init.h>
@@ -133,6 +134,9 @@ static void __init __gic_clocksource_init(void)
clocksource_register_hz(&gic_clocksource, gic_frequency);
gic_clockevent_init();
+
+ /* And finally start the counter */
+ gic_start_count();
}
void __init gic_clocksource_init(unsigned int frequency)
@@ -146,11 +150,18 @@ void __init gic_clocksource_init(unsigned int frequency)
static void __init gic_clocksource_of_init(struct device_node *node)
{
+ struct clk *clk;
+
if (WARN_ON(!gic_present || !node->parent ||
!of_device_is_compatible(node->parent, "mti,gic")))
return;
- if (of_property_read_u32(node, "clock-frequency", &gic_frequency)) {
+ clk = of_clk_get(node, 0);
+ if (!IS_ERR(clk)) {
+ gic_frequency = clk_get_rate(clk);
+ clk_put(clk);
+ } else if (of_property_read_u32(node, "clock-frequency",
+ &gic_frequency)) {
pr_err("GIC frequency not specified.\n");
return;
}
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index c8d260e33a90..6de62a96e79c 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -60,6 +60,11 @@ config ATMEL_AIC5_IRQ
select MULTI_IRQ_HANDLER
select SPARSE_IRQ
+config BCM7038_L1_IRQ
+ bool
+ select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN
+
config BCM7120_L2_IRQ
bool
select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 552a74027601..dda4927e47a6 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
+obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o
obj-$(CONFIG_KEYSTONE_IRQ) += irq-keystone.o
diff --git a/drivers/irqchip/irq-bcm7038-l1.c b/drivers/irqchip/irq-bcm7038-l1.c
new file mode 100644
index 000000000000..d3b8c8be15f6
--- /dev/null
+++ b/drivers/irqchip/irq-bcm7038-l1.c
@@ -0,0 +1,335 @@
+/*
+ * Broadcom BCM7038 style Level 1 interrupt controller driver
+ *
+ * Copyright (C) 2014 Broadcom Corporation
+ * Author: Kevin Cernekee
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/kconfig.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/types.h>
+#include <linux/irqchip/chained_irq.h>
+
+#include "irqchip.h"
+
+#define IRQS_PER_WORD 32
+#define REG_BYTES_PER_IRQ_WORD (sizeof(u32) * 4)
+#define MAX_WORDS 8
+
+struct bcm7038_l1_cpu;
+
+struct bcm7038_l1_chip {
+ raw_spinlock_t lock;
+ unsigned int n_words;
+ struct irq_domain *domain;
+ struct bcm7038_l1_cpu *cpus[NR_CPUS];
+ u8 affinity[MAX_WORDS * IRQS_PER_WORD];
+};
+
+struct bcm7038_l1_cpu {
+ void __iomem *map_base;
+ u32 mask_cache[0];
+};
+
+/*
+ * STATUS/MASK_STATUS/MASK_SET/MASK_CLEAR are packed one right after another:
+ *
+ * 7038:
+ * 0x1000_1400: W0_STATUS
+ * 0x1000_1404: W1_STATUS
+ * 0x1000_1408: W0_MASK_STATUS
+ * 0x1000_140c: W1_MASK_STATUS
+ * 0x1000_1410: W0_MASK_SET
+ * 0x1000_1414: W1_MASK_SET
+ * 0x1000_1418: W0_MASK_CLEAR
+ * 0x1000_141c: W1_MASK_CLEAR
+ *
+ * 7445:
+ * 0xf03e_1500: W0_STATUS
+ * 0xf03e_1504: W1_STATUS
+ * 0xf03e_1508: W2_STATUS
+ * 0xf03e_150c: W3_STATUS
+ * 0xf03e_1510: W4_STATUS
+ * 0xf03e_1514: W0_MASK_STATUS
+ * 0xf03e_1518: W1_MASK_STATUS
+ * [...]
+ */
+
+static inline unsigned int reg_status(struct bcm7038_l1_chip *intc,
+ unsigned int word)
+{
+ return (0 * intc->n_words + word) * sizeof(u32);
+}
+
+static inline unsigned int reg_mask_status(struct bcm7038_l1_chip *intc,
+ unsigned int word)
+{
+ return (1 * intc->n_words + word) * sizeof(u32);
+}
+
+static inline unsigned int reg_mask_set(struct bcm7038_l1_chip *intc,
+ unsigned int word)
+{
+ return (2 * intc->n_words + word) * sizeof(u32);
+}
+
+static inline unsigned int reg_mask_clr(struct bcm7038_l1_chip *intc,
+ unsigned int word)
+{
+ return (3 * intc->n_words + word) * sizeof(u32);
+}
+
+static inline u32 l1_readl(void __iomem *reg)
+{
+ if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+ return ioread32be(reg);
+ else
+ return readl(reg);
+}
+
+static inline void l1_writel(u32 val, void __iomem *reg)
+{
+ if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+ iowrite32be(val, reg);
+ else
+ writel(val, reg);
+}
+
+static void bcm7038_l1_irq_handle(unsigned int irq, struct irq_desc *desc)
+{
+ struct bcm7038_l1_chip *intc = irq_desc_get_handler_data(desc);
+ struct bcm7038_l1_cpu *cpu;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int idx;
+
+#ifdef CONFIG_SMP
+ cpu = intc->cpus[cpu_logical_map(smp_processor_id())];
+#else
+ cpu = intc->cpus[0];
+#endif
+
+ chained_irq_enter(chip, desc);
+
+ for (idx = 0; idx < intc->n_words; idx++) {
+ int base = idx * IRQS_PER_WORD;
+ unsigned long pending, flags;
+ int hwirq;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ pending = l1_readl(cpu->map_base + reg_status(intc, idx)) &
+ ~cpu->mask_cache[idx];
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+
+ for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
+ generic_handle_irq(irq_find_mapping(intc->domain,
+ base + hwirq));
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void __bcm7038_l1_unmask(struct irq_data *d, unsigned int cpu_idx)
+{
+ struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+
+ intc->cpus[cpu_idx]->mask_cache[word] &= ~mask;
+ l1_writel(mask, intc->cpus[cpu_idx]->map_base +
+ reg_mask_clr(intc, word));
+}
+
+static void __bcm7038_l1_mask(struct irq_data *d, unsigned int cpu_idx)
+{
+ struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ u32 word = d->hwirq / IRQS_PER_WORD;
+ u32 mask = BIT(d->hwirq % IRQS_PER_WORD);
+
+ intc->cpus[cpu_idx]->mask_cache[word] |= mask;
+ l1_writel(mask, intc->cpus[cpu_idx]->map_base +
+ reg_mask_set(intc, word));
+}
+
+static void bcm7038_l1_unmask(struct irq_data *d)
+{
+ struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ __bcm7038_l1_unmask(d, intc->affinity[d->hwirq]);
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static void bcm7038_l1_mask(struct irq_data *d)
+{
+ struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+ __bcm7038_l1_mask(d, intc->affinity[d->hwirq]);
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+}
+
+static int bcm7038_l1_set_affinity(struct irq_data *d,
+ const struct cpumask *dest,
+ bool force)
+{
+ struct bcm7038_l1_chip *intc = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ irq_hw_number_t hw = d->hwirq;
+ u32 word = hw / IRQS_PER_WORD;
+ u32 mask = BIT(hw % IRQS_PER_WORD);
+ unsigned int first_cpu = cpumask_any_and(dest, cpu_online_mask);
+ bool was_disabled;
+
+ raw_spin_lock_irqsave(&intc->lock, flags);
+
+ was_disabled = !!(intc->cpus[intc->affinity[hw]]->mask_cache[word] &
+ mask);
+ __bcm7038_l1_mask(d, intc->affinity[hw]);
+ intc->affinity[hw] = first_cpu;
+ if (!was_disabled)
+ __bcm7038_l1_unmask(d, first_cpu);
+
+ raw_spin_unlock_irqrestore(&intc->lock, flags);
+ return 0;
+}
+
+static int __init bcm7038_l1_init_one(struct device_node *dn,
+ unsigned int idx,
+ struct bcm7038_l1_chip *intc)
+{
+ struct resource res;
+ resource_size_t sz;
+ struct bcm7038_l1_cpu *cpu;
+ unsigned int i, n_words, parent_irq;
+
+ if (of_address_to_resource(dn, idx, &res))
+ return -EINVAL;
+ sz = resource_size(&res);
+ n_words = sz / REG_BYTES_PER_IRQ_WORD;
+
+ if (n_words > MAX_WORDS)
+ return -EINVAL;
+ else if (!intc->n_words)
+ intc->n_words = n_words;
+ else if (intc->n_words != n_words)
+ return -EINVAL;
+
+ cpu = intc->cpus[idx] = kzalloc(sizeof(*cpu) + n_words * sizeof(u32),
+ GFP_KERNEL);
+ if (!cpu)
+ return -ENOMEM;
+
+ cpu->map_base = ioremap(res.start, sz);
+ if (!cpu->map_base)
+ return -ENOMEM;
+
+ for (i = 0; i < n_words; i++) {
+ l1_writel(0xffffffff, cpu->map_base + reg_mask_set(intc, i));
+ cpu->mask_cache[i] = 0xffffffff;
+ }
+
+ parent_irq = irq_of_parse_and_map(dn, idx);
+ if (!parent_irq) {
+ pr_err("failed to map parent interrupt %d\n", parent_irq);
+ return -EINVAL;
+ }
+ irq_set_handler_data(parent_irq, intc);
+ irq_set_chained_handler(parent_irq, bcm7038_l1_irq_handle);
+
+ return 0;
+}
+
+static struct irq_chip bcm7038_l1_irq_chip = {
+ .name = "bcm7038-l1",
+ .irq_mask = bcm7038_l1_mask,
+ .irq_unmask = bcm7038_l1_unmask,
+ .irq_set_affinity = bcm7038_l1_set_affinity,
+};
+
+static int bcm7038_l1_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hw_irq)
+{
+ irq_set_chip_and_handler(virq, &bcm7038_l1_irq_chip, handle_level_irq);
+ irq_set_chip_data(virq, d->host_data);
+ return 0;
+}
+
+static const struct irq_domain_ops bcm7038_l1_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = bcm7038_l1_map,
+};
+
+int __init bcm7038_l1_of_init(struct device_node *dn,
+ struct device_node *parent)
+{
+ struct bcm7038_l1_chip *intc;
+ int idx, ret;
+
+ intc = kzalloc(sizeof(*intc), GFP_KERNEL);
+ if (!intc)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&intc->lock);
+ for_each_possible_cpu(idx) {
+ ret = bcm7038_l1_init_one(dn, idx, intc);
+ if (ret < 0) {
+ if (idx)
+ break;
+ pr_err("failed to remap intc L1 registers\n");
+ goto out_free;
+ }
+ }
+
+ intc->domain = irq_domain_add_linear(dn, IRQS_PER_WORD * intc->n_words,
+ &bcm7038_l1_domain_ops,
+ intc);
+ if (!intc->domain) {
+ ret = -ENOMEM;
+ goto out_unmap;
+ }
+
+ pr_info("registered BCM7038 L1 intc (mem: 0x%p, IRQs: %d)\n",
+ intc->cpus[0]->map_base, IRQS_PER_WORD * intc->n_words);
+
+ return 0;
+
+out_unmap:
+ for_each_possible_cpu(idx) {
+ struct bcm7038_l1_cpu *cpu = intc->cpus[idx];
+
+ if (cpu) {
+ if (cpu->map_base)
+ iounmap(cpu->map_base);
+ kfree(cpu);
+ }
+ }
+out_free:
+ kfree(intc);
+ return ret;
+}
+
+IRQCHIP_DECLARE(bcm7038_l1, "brcm,bcm7038-l1-intc", bcm7038_l1_of_init);
diff --git a/drivers/irqchip/irq-bcm7120-l2.c b/drivers/irqchip/irq-bcm7120-l2.c
index 8eec8e1201d9..3ba5cc780fcb 100644
--- a/drivers/irqchip/irq-bcm7120-l2.c
+++ b/drivers/irqchip/irq-bcm7120-l2.c
@@ -14,6 +14,7 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kconfig.h>
+#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_irq.h>
@@ -34,15 +35,21 @@
#define IRQSTAT 0x04
#define MAX_WORDS 4
+#define MAX_MAPPINGS (MAX_WORDS * 2)
#define IRQS_PER_WORD 32
struct bcm7120_l2_intc_data {
unsigned int n_words;
- void __iomem *base[MAX_WORDS];
+ void __iomem *map_base[MAX_MAPPINGS];
+ void __iomem *pair_base[MAX_WORDS];
+ int en_offset[MAX_WORDS];
+ int stat_offset[MAX_WORDS];
struct irq_domain *domain;
bool can_wake;
u32 irq_fwd_mask[MAX_WORDS];
u32 irq_map_mask[MAX_WORDS];
+ int num_parent_irqs;
+ const __be32 *map_mask_prop;
};
static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc)
@@ -61,7 +68,8 @@ static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc)
int hwirq;
irq_gc_lock(gc);
- pending = irq_reg_readl(gc, IRQSTAT) & gc->mask_cache;
+ pending = irq_reg_readl(gc, b->stat_offset[idx]) &
+ gc->mask_cache;
irq_gc_unlock(gc);
for_each_set_bit(hwirq, &pending, IRQS_PER_WORD) {
@@ -76,27 +84,30 @@ static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc)
static void bcm7120_l2_intc_suspend(struct irq_data *d)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
struct bcm7120_l2_intc_data *b = gc->private;
irq_gc_lock(gc);
if (b->can_wake)
- irq_reg_writel(gc, gc->mask_cache | gc->wake_active, IRQEN);
+ irq_reg_writel(gc, gc->mask_cache | gc->wake_active,
+ ct->regs.mask);
irq_gc_unlock(gc);
}
static void bcm7120_l2_intc_resume(struct irq_data *d)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
/* Restore the saved mask */
irq_gc_lock(gc);
- irq_reg_writel(gc, gc->mask_cache, IRQEN);
+ irq_reg_writel(gc, gc->mask_cache, ct->regs.mask);
irq_gc_unlock(gc);
}
static int bcm7120_l2_intc_init_one(struct device_node *dn,
struct bcm7120_l2_intc_data *data,
- int irq, const __be32 *map_mask)
+ int irq)
{
int parent_irq;
unsigned int idx;
@@ -110,9 +121,15 @@ static int bcm7120_l2_intc_init_one(struct device_node *dn,
/* For multiple parent IRQs with multiple words, this looks like:
* <irq0_w0 irq0_w1 irq1_w0 irq1_w1 ...>
*/
- for (idx = 0; idx < data->n_words; idx++)
- data->irq_map_mask[idx] |=
- be32_to_cpup(map_mask + irq * data->n_words + idx);
+ for (idx = 0; idx < data->n_words; idx++) {
+ if (data->map_mask_prop) {
+ data->irq_map_mask[idx] |=
+ be32_to_cpup(data->map_mask_prop +
+ irq * data->n_words + idx);
+ } else {
+ data->irq_map_mask[idx] = 0xffffffff;
+ }
+ }
irq_set_handler_data(parent_irq, data);
irq_set_chained_handler(parent_irq, bcm7120_l2_intc_irq_handle);
@@ -120,68 +137,107 @@ static int bcm7120_l2_intc_init_one(struct device_node *dn,
return 0;
}
-int __init bcm7120_l2_intc_of_init(struct device_node *dn,
- struct device_node *parent)
+static int __init bcm7120_l2_intc_iomap_7120(struct device_node *dn,
+ struct bcm7120_l2_intc_data *data)
+{
+ int ret;
+
+ data->map_base[0] = of_iomap(dn, 0);
+ if (!data->map_base[0]) {
+ pr_err("unable to map registers\n");
+ return -ENOMEM;
+ }
+
+ data->pair_base[0] = data->map_base[0];
+ data->en_offset[0] = IRQEN;
+ data->stat_offset[0] = IRQSTAT;
+ data->n_words = 1;
+
+ ret = of_property_read_u32_array(dn, "brcm,int-fwd-mask",
+ data->irq_fwd_mask, data->n_words);
+ if (ret != 0 && ret != -EINVAL) {
+ /* property exists but has the wrong number of words */
+ pr_err("invalid brcm,int-fwd-mask property\n");
+ return -EINVAL;
+ }
+
+ data->map_mask_prop = of_get_property(dn, "brcm,int-map-mask", &ret);
+ if (!data->map_mask_prop ||
+ (ret != (sizeof(__be32) * data->num_parent_irqs * data->n_words))) {
+ pr_err("invalid brcm,int-map-mask property\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __init bcm7120_l2_intc_iomap_3380(struct device_node *dn,
+ struct bcm7120_l2_intc_data *data)
+{
+ unsigned int gc_idx;
+
+ for (gc_idx = 0; gc_idx < MAX_WORDS; gc_idx++) {
+ unsigned int map_idx = gc_idx * 2;
+ void __iomem *en = of_iomap(dn, map_idx + 0);
+ void __iomem *stat = of_iomap(dn, map_idx + 1);
+ void __iomem *base = min(en, stat);
+
+ data->map_base[map_idx + 0] = en;
+ data->map_base[map_idx + 1] = stat;
+
+ if (!base)
+ break;
+
+ data->pair_base[gc_idx] = base;
+ data->en_offset[gc_idx] = en - base;
+ data->stat_offset[gc_idx] = stat - base;
+ }
+
+ if (!gc_idx) {
+ pr_err("unable to map registers\n");
+ return -EINVAL;
+ }
+
+ data->n_words = gc_idx;
+ return 0;
+}
+
+int __init bcm7120_l2_intc_probe(struct device_node *dn,
+ struct device_node *parent,
+ int (*iomap_regs_fn)(struct device_node *,
+ struct bcm7120_l2_intc_data *),
+ const char *intc_name)
{
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
struct bcm7120_l2_intc_data *data;
struct irq_chip_generic *gc;
struct irq_chip_type *ct;
- const __be32 *map_mask;
- int num_parent_irqs;
- int ret = 0, len;
+ int ret = 0;
unsigned int idx, irq, flags;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
- for (idx = 0; idx < MAX_WORDS; idx++) {
- data->base[idx] = of_iomap(dn, idx);
- if (!data->base[idx])
- break;
- data->n_words = idx + 1;
- }
- if (!data->n_words) {
- pr_err("failed to remap intc L2 registers\n");
- ret = -ENOMEM;
- goto out_unmap;
- }
-
- /* Enable all interrupts specified in the interrupt forward mask;
- * disable all others. If the property doesn't exist (-EINVAL),
- * assume all zeroes.
- */
- ret = of_property_read_u32_array(dn, "brcm,int-fwd-mask",
- data->irq_fwd_mask, data->n_words);
- if (ret == 0 || ret == -EINVAL) {
- for (idx = 0; idx < data->n_words; idx++)
- __raw_writel(data->irq_fwd_mask[idx],
- data->base[idx] + IRQEN);
- } else {
- /* property exists but has the wrong number of words */
- pr_err("invalid int-fwd-mask property\n");
- ret = -EINVAL;
- goto out_unmap;
- }
-
- num_parent_irqs = of_irq_count(dn);
- if (num_parent_irqs <= 0) {
+ data->num_parent_irqs = of_irq_count(dn);
+ if (data->num_parent_irqs <= 0) {
pr_err("invalid number of parent interrupts\n");
ret = -ENOMEM;
goto out_unmap;
}
- map_mask = of_get_property(dn, "brcm,int-map-mask", &len);
- if (!map_mask ||
- (len != (sizeof(*map_mask) * num_parent_irqs * data->n_words))) {
- pr_err("invalid brcm,int-map-mask property\n");
- ret = -EINVAL;
+ ret = iomap_regs_fn(dn, data);
+ if (ret < 0)
goto out_unmap;
+
+ for (idx = 0; idx < data->n_words; idx++) {
+ __raw_writel(data->irq_fwd_mask[idx],
+ data->pair_base[idx] +
+ data->en_offset[idx]);
}
- for (irq = 0; irq < num_parent_irqs; irq++) {
- ret = bcm7120_l2_intc_init_one(dn, data, irq, map_mask);
+ for (irq = 0; irq < data->num_parent_irqs; irq++) {
+ ret = bcm7120_l2_intc_init_one(dn, data, irq);
if (ret)
goto out_unmap;
}
@@ -215,11 +271,12 @@ int __init bcm7120_l2_intc_of_init(struct device_node *dn,
gc = irq_get_domain_generic_chip(data->domain, irq);
gc->unused = 0xffffffff & ~data->irq_map_mask[idx];
- gc->reg_base = data->base[idx];
gc->private = data;
ct = gc->chip_types;
- ct->regs.mask = IRQEN;
+ gc->reg_base = data->pair_base[idx];
+ ct->regs.mask = data->en_offset[idx];
+
ct->chip.irq_mask = irq_gc_mask_clr_bit;
ct->chip.irq_unmask = irq_gc_mask_set_bit;
ct->chip.irq_ack = irq_gc_noop;
@@ -236,20 +293,38 @@ int __init bcm7120_l2_intc_of_init(struct device_node *dn,
}
}
- pr_info("registered BCM7120 L2 intc (mem: 0x%p, parent IRQ(s): %d)\n",
- data->base[0], num_parent_irqs);
+ pr_info("registered %s intc (mem: 0x%p, parent IRQ(s): %d)\n",
+ intc_name, data->map_base[0], data->num_parent_irqs);
return 0;
out_free_domain:
irq_domain_remove(data->domain);
out_unmap:
- for (idx = 0; idx < MAX_WORDS; idx++) {
- if (data->base[idx])
- iounmap(data->base[idx]);
+ for (idx = 0; idx < MAX_MAPPINGS; idx++) {
+ if (data->map_base[idx])
+ iounmap(data->map_base[idx]);
}
kfree(data);
return ret;
}
+
+int __init bcm7120_l2_intc_probe_7120(struct device_node *dn,
+ struct device_node *parent)
+{
+ return bcm7120_l2_intc_probe(dn, parent, bcm7120_l2_intc_iomap_7120,
+ "BCM7120 L2");
+}
+
+int __init bcm7120_l2_intc_probe_3380(struct device_node *dn,
+ struct device_node *parent)
+{
+ return bcm7120_l2_intc_probe(dn, parent, bcm7120_l2_intc_iomap_3380,
+ "BCM3380 L2");
+}
+
IRQCHIP_DECLARE(bcm7120_l2_intc, "brcm,bcm7120-l2-intc",
- bcm7120_l2_intc_of_init);
+ bcm7120_l2_intc_probe_7120);
+
+IRQCHIP_DECLARE(bcm3380_l2_intc, "brcm,bcm3380-l2-intc",
+ bcm7120_l2_intc_probe_3380);
diff --git a/drivers/irqchip/irq-brcmstb-l2.c b/drivers/irqchip/irq-brcmstb-l2.c
index 313c2c64498a..d6bcc6be0777 100644
--- a/drivers/irqchip/irq-brcmstb-l2.c
+++ b/drivers/irqchip/irq-brcmstb-l2.c
@@ -136,7 +136,11 @@ int __init brcmstb_l2_intc_of_init(struct device_node *np,
/* Disable all interrupts by default */
writel(0xffffffff, data->base + CPU_MASK_SET);
- writel(0xffffffff, data->base + CPU_CLEAR);
+
+ /* Wakeup interrupts may be retained from S5 (cold boot) */
+ data->can_wake = of_property_read_bool(np, "brcm,irq-can-wake");
+ if (!data->can_wake)
+ writel(0xffffffff, data->base + CPU_CLEAR);
data->parent_irq = irq_of_parse_and_map(np, 0);
if (!data->parent_irq) {
@@ -188,8 +192,7 @@ int __init brcmstb_l2_intc_of_init(struct device_node *np,
ct->chip.irq_suspend = brcmstb_l2_intc_suspend;
ct->chip.irq_resume = brcmstb_l2_intc_resume;
- if (of_property_read_bool(np, "brcm,irq-can-wake")) {
- data->can_wake = true;
+ if (data->can_wake) {
/* This IRQ chip can wake the system, set all child interrupts
* in wake_enabled mask
*/
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index f2d269bca789..bc48b7dc89ec 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -239,7 +239,7 @@ int gic_get_c0_compare_int(void)
int gic_get_c0_perfcount_int(void)
{
if (!gic_local_irq_is_routable(GIC_LOCAL_INT_PERFCTR)) {
- /* Is the erformance counter shared with the timer? */
+ /* Is the performance counter shared with the timer? */
if (cp0_perfcount_irq < 0)
return -1;
return MIPS_CPU_IRQ_BASE + cp0_perfcount_irq;
@@ -248,6 +248,29 @@ int gic_get_c0_perfcount_int(void)
GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_PERFCTR));
}
+int gic_get_c0_fdc_int(void)
+{
+ if (!gic_local_irq_is_routable(GIC_LOCAL_INT_FDC)) {
+ /* Is the FDC IRQ even present? */
+ if (cp0_fdc_irq < 0)
+ return -1;
+ return MIPS_CPU_IRQ_BASE + cp0_fdc_irq;
+ }
+
+ /*
+ * Some cores claim the FDC is routable but it doesn't actually seem to
+ * be connected.
+ */
+ switch (current_cpu_type()) {
+ case CPU_INTERAPTIV:
+ case CPU_PROAPTIV:
+ return -1;
+ }
+
+ return irq_create_mapping(gic_irq_domain,
+ GIC_LOCAL_TO_HWIRQ(GIC_LOCAL_INT_FDC));
+}
+
static void gic_handle_shared_int(void)
{
unsigned int i, intr, virq;
@@ -613,15 +636,20 @@ static int gic_local_irq_domain_map(struct irq_domain *d, unsigned int virq,
* of the MIPS kernel code does not use the percpu IRQ API for
* the CP0 timer and performance counter interrupts.
*/
- if (intr != GIC_LOCAL_INT_TIMER && intr != GIC_LOCAL_INT_PERFCTR) {
+ switch (intr) {
+ case GIC_LOCAL_INT_TIMER:
+ case GIC_LOCAL_INT_PERFCTR:
+ case GIC_LOCAL_INT_FDC:
+ irq_set_chip_and_handler(virq,
+ &gic_all_vpes_local_irq_controller,
+ handle_percpu_irq);
+ break;
+ default:
irq_set_chip_and_handler(virq,
&gic_local_irq_controller,
handle_percpu_devid_irq);
irq_set_percpu_devid(virq);
- } else {
- irq_set_chip_and_handler(virq,
- &gic_all_vpes_local_irq_controller,
- handle_percpu_irq);
+ break;
}
spin_lock_irqsave(&gic_lock, flags);
diff --git a/drivers/net/ethernet/broadcom/b44.c b/drivers/net/ethernet/broadcom/b44.c
index bd5916a60cb5..77363d680532 100644
--- a/drivers/net/ethernet/broadcom/b44.c
+++ b/drivers/net/ethernet/broadcom/b44.c
@@ -400,7 +400,7 @@ static void b44_set_flow_ctrl(struct b44 *bp, u32 local, u32 remote)
}
#ifdef CONFIG_BCM47XX
-#include <bcm47xx_nvram.h>
+#include <linux/bcm47xx_nvram.h>
static void b44_wap54g10_workaround(struct b44 *bp)
{
char buf[20];
diff --git a/drivers/net/ethernet/broadcom/bgmac.c b/drivers/net/ethernet/broadcom/bgmac.c
index 5cb93d1f50a4..de77d3a74abc 100644
--- a/drivers/net/ethernet/broadcom/bgmac.c
+++ b/drivers/net/ethernet/broadcom/bgmac.c
@@ -17,7 +17,7 @@
#include <linux/phy_fixed.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
-#include <bcm47xx_nvram.h>
+#include <linux/bcm47xx_nvram.h>
static const struct bcma_device_id bgmac_bcma_tbl[] = {
BCMA_CORE(BCMA_MANUF_BCM, BCMA_CORE_4706_MAC_GBIT, BCMA_ANY_REV, BCMA_ANY_CLASS),
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 09fde58b12e0..0adccbf5c83f 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -1,6 +1,9 @@
if X86
source "drivers/platform/x86/Kconfig"
endif
+if MIPS
+source "drivers/platform/mips/Kconfig"
+endif
if GOLDFISH
source "drivers/platform/goldfish/Kconfig"
endif
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index 3656b7b17b99..ca2692510733 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -3,6 +3,7 @@
#
obj-$(CONFIG_X86) += x86/
+obj-$(CONFIG_MIPS) += mips/
obj-$(CONFIG_OLPC) += olpc/
obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig
new file mode 100644
index 000000000000..125e569017be
--- /dev/null
+++ b/drivers/platform/mips/Kconfig
@@ -0,0 +1,30 @@
+#
+# MIPS Platform Specific Drivers
+#
+
+menuconfig MIPS_PLATFORM_DEVICES
+ bool "MIPS Platform Specific Device Drivers"
+ default y
+ help
+ Say Y here to get to see options for device drivers of various
+ MIPS platforms, including vendor-specific netbook/laptop/desktop
+ extension and hardware monitor drivers. This option itself does
+ not add any kernel code.
+
+ If you say N, all options in this submenu will be skipped and disabled.
+
+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
+ select HWMON
+ default y
+ help
+ Loongson-3A/3B CPU Hwmon (temperature sensor) driver.
+
+endif # MIPS_PLATFORM_DEVICES
diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile
new file mode 100644
index 000000000000..43412849b195
--- /dev/null
+++ b/drivers/platform/mips/Makefile
@@ -0,0 +1,2 @@
+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
new file mode 100644
index 000000000000..dbdad79ead8f
--- /dev/null
+++ b/drivers/platform/mips/acpi_init.c
@@ -0,0 +1,150 @@
+#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
new file mode 100644
index 000000000000..0f6c63e17049
--- /dev/null
+++ b/drivers/platform/mips/cpu_hwmon.c
@@ -0,0 +1,207 @@
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/jiffies.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+#include <loongson.h>
+#include <boot_param.h>
+#include <loongson_hwmon.h>
+
+/*
+ * Loongson-3 series cpu has two sensors inside,
+ * each of them from 0 to 255,
+ * if more than 127, that is dangerous.
+ * here only provide sensor1 data, because it always hot than sensor0
+ */
+int loongson3_cpu_temp(int cpu)
+{
+ u32 reg;
+
+ reg = LOONGSON_CHIPTEMP(cpu);
+ if (loongson_sysconf.cputype == Loongson_3A)
+ reg = (reg >> 8) & 0xff;
+ else if (loongson_sysconf.cputype == Loongson_3B)
+ reg = ((reg >> 8) & 0xff) - 100;
+
+ return (int)reg * 1000;
+}
+
+static struct device *cpu_hwmon_dev;
+
+static ssize_t get_hwmon_name(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0);
+
+static struct attribute *cpu_hwmon_attributes[] = {
+ &sensor_dev_attr_name.dev_attr.attr,
+ NULL
+};
+
+/* Hwmon device attribute group */
+static struct attribute_group cpu_hwmon_attribute_group = {
+ .attrs = cpu_hwmon_attributes,
+};
+
+/* Hwmon device get name */
+static ssize_t get_hwmon_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "cpu-hwmon\n");
+}
+
+static ssize_t get_cpu0_temp(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t get_cpu1_temp(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t cpu0_temp_label(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t cpu1_temp_label(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_cpu0_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, cpu0_temp_label, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, get_cpu1_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, cpu1_temp_label, NULL, 2);
+
+static const struct attribute *hwmon_cputemp1[] = {
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute *hwmon_cputemp2[] = {
+ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ &sensor_dev_attr_temp2_label.dev_attr.attr,
+ NULL
+};
+
+static ssize_t cpu0_temp_label(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "CPU 0 Temprature\n");
+}
+
+static ssize_t cpu1_temp_label(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "CPU 1 Temprature\n");
+}
+
+static ssize_t get_cpu0_temp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int value = loongson3_cpu_temp(0);
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t get_cpu1_temp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int value = loongson3_cpu_temp(1);
+ return sprintf(buf, "%d\n", value);
+}
+
+static int create_sysfs_cputemp_files(struct kobject *kobj)
+{
+ int ret;
+
+ ret = sysfs_create_files(kobj, hwmon_cputemp1);
+ if (ret)
+ goto sysfs_create_temp1_fail;
+
+ if (loongson_sysconf.nr_cpus <= loongson_sysconf.cores_per_package)
+ return 0;
+
+ ret = sysfs_create_files(kobj, hwmon_cputemp2);
+ if (ret)
+ goto sysfs_create_temp2_fail;
+
+ return 0;
+
+sysfs_create_temp2_fail:
+ sysfs_remove_files(kobj, hwmon_cputemp1);
+
+sysfs_create_temp1_fail:
+ return -1;
+}
+
+static void remove_sysfs_cputemp_files(struct kobject *kobj)
+{
+ sysfs_remove_files(&cpu_hwmon_dev->kobj, hwmon_cputemp1);
+
+ if (loongson_sysconf.nr_cpus > loongson_sysconf.cores_per_package)
+ sysfs_remove_files(&cpu_hwmon_dev->kobj, hwmon_cputemp2);
+}
+
+#define CPU_THERMAL_THRESHOLD 90000
+static struct delayed_work thermal_work;
+
+static void do_thermal_timer(struct work_struct *work)
+{
+ int value = loongson3_cpu_temp(0);
+ if (value <= CPU_THERMAL_THRESHOLD)
+ schedule_delayed_work(&thermal_work, msecs_to_jiffies(5000));
+ else
+ orderly_poweroff(true);
+}
+
+static int __init loongson_hwmon_init(void)
+{
+ int ret;
+
+ pr_info("Loongson Hwmon Enter...\n");
+
+ cpu_hwmon_dev = hwmon_device_register(NULL);
+ if (IS_ERR(cpu_hwmon_dev)) {
+ ret = -ENOMEM;
+ pr_err("hwmon_device_register fail!\n");
+ goto fail_hwmon_device_register;
+ }
+
+ ret = sysfs_create_group(&cpu_hwmon_dev->kobj,
+ &cpu_hwmon_attribute_group);
+ if (ret) {
+ pr_err("fail to create loongson hwmon!\n");
+ goto fail_sysfs_create_group_hwmon;
+ }
+
+ ret = create_sysfs_cputemp_files(&cpu_hwmon_dev->kobj);
+ if (ret) {
+ pr_err("fail to create cpu temprature interface!\n");
+ goto fail_create_sysfs_cputemp_files;
+ }
+
+ INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer);
+ schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000));
+
+ return ret;
+
+fail_create_sysfs_cputemp_files:
+ sysfs_remove_group(&cpu_hwmon_dev->kobj,
+ &cpu_hwmon_attribute_group);
+
+fail_sysfs_create_group_hwmon:
+ hwmon_device_unregister(cpu_hwmon_dev);
+
+fail_hwmon_device_register:
+ return ret;
+}
+
+static void __exit loongson_hwmon_exit(void)
+{
+ cancel_delayed_work_sync(&thermal_work);
+ remove_sysfs_cputemp_files(&cpu_hwmon_dev->kobj);
+ sysfs_remove_group(&cpu_hwmon_dev->kobj,
+ &cpu_hwmon_attribute_group);
+ hwmon_device_unregister(cpu_hwmon_dev);
+}
+
+module_init(loongson_hwmon_init);
+module_exit(loongson_hwmon_exit);
+
+MODULE_AUTHOR("Yu Xiang <xiangy@lemote.com>");
+MODULE_AUTHOR("Huacai Chen <chenhc@lemote.com>");
+MODULE_DESCRIPTION("Loongson CPU Hwmon driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ssb/Kconfig b/drivers/ssb/Kconfig
index 75b3603906c1..f0d22cdb51cd 100644
--- a/drivers/ssb/Kconfig
+++ b/drivers/ssb/Kconfig
@@ -130,6 +130,7 @@ config SSB_DRIVER_MIPS
bool "SSB Broadcom MIPS core driver"
depends on SSB && MIPS
select SSB_SERIAL
+ select SSB_SFLASH
help
Driver for the Sonics Silicon Backplane attached
Broadcom MIPS core.
diff --git a/drivers/ssb/driver_chipcommon_pmu.c b/drivers/ssb/driver_chipcommon_pmu.c
index 1173a091b402..09428412139e 100644
--- a/drivers/ssb/driver_chipcommon_pmu.c
+++ b/drivers/ssb/driver_chipcommon_pmu.c
@@ -14,7 +14,7 @@
#include <linux/delay.h>
#include <linux/export.h>
#ifdef CONFIG_BCM47XX
-#include <bcm47xx_nvram.h>
+#include <linux/bcm47xx_nvram.h>
#endif
#include "ssb_private.h"
diff --git a/drivers/ssb/driver_mipscore.c b/drivers/ssb/driver_mipscore.c
index 7b986f9f213f..f87efef42252 100644
--- a/drivers/ssb/driver_mipscore.c
+++ b/drivers/ssb/driver_mipscore.c
@@ -16,7 +16,7 @@
#include <linux/serial_reg.h>
#include <linux/time.h>
#ifdef CONFIG_BCM47XX
-#include <bcm47xx_nvram.h>
+#include <linux/bcm47xx_nvram.h>
#endif
#include "ssb_private.h"
diff --git a/drivers/staging/octeon/ethernet-tx.c b/drivers/staging/octeon/ethernet-tx.c
index b7a7854d3f7e..5b9ac1f6d6f0 100644
--- a/drivers/staging/octeon/ethernet-tx.c
+++ b/drivers/staging/octeon/ethernet-tx.c
@@ -274,6 +274,9 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev)
/* Build the PKO command */
pko_command.u64 = 0;
+#ifdef __LITTLE_ENDIAN
+ pko_command.s.le = 1;
+#endif
pko_command.s.n2 = 1; /* Don't pollute L2 with the outgoing packet */
pko_command.s.segs = 1;
pko_command.s.total_bytes = skb->len;
@@ -410,7 +413,7 @@ dont_put_skbuff_in_hw:
/* Check if we can use the hardware checksumming */
if (USE_HW_TCPUDP_CHECKSUM && (skb->protocol == htons(ETH_P_IP)) &&
(ip_hdr(skb)->version == 4) && (ip_hdr(skb)->ihl == 5) &&
- ((ip_hdr(skb)->frag_off == 0) || (ip_hdr(skb)->frag_off == 1 << 14))
+ ((ip_hdr(skb)->frag_off == 0) || (ip_hdr(skb)->frag_off == htons(1 << 14)))
&& ((ip_hdr(skb)->protocol == IPPROTO_TCP)
|| (ip_hdr(skb)->protocol == IPPROTO_UDP))) {
/* Use hardware checksum calc */
diff --git a/drivers/staging/octeon/ethernet.c b/drivers/staging/octeon/ethernet.c
index f539d82f2f11..fbbe866485c7 100644
--- a/drivers/staging/octeon/ethernet.c
+++ b/drivers/staging/octeon/ethernet.c
@@ -170,6 +170,16 @@ static void cvm_oct_configure_common_hw(void)
cvm_oct_mem_fill_fpa(CVMX_FPA_OUTPUT_BUFFER_POOL,
CVMX_FPA_OUTPUT_BUFFER_POOL_SIZE, 128);
+#ifdef __LITTLE_ENDIAN
+ {
+ union cvmx_ipd_ctl_status ipd_ctl_status;
+ ipd_ctl_status.u64 = cvmx_read_csr(CVMX_IPD_CTL_STATUS);
+ ipd_ctl_status.s.pkt_lend = 1;
+ ipd_ctl_status.s.wqe_lend = 1;
+ cvmx_write_csr(CVMX_IPD_CTL_STATUS, ipd_ctl_status.u64);
+ }
+#endif
+
if (USE_RED)
cvmx_helper_setup_red(num_packet_buffers / 4,
num_packet_buffers / 8);
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index b24aa010f68c..c01f45095877 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -419,4 +419,51 @@ config DA_CONSOLE
help
This enables a console on a Dash channel.
+config MIPS_EJTAG_FDC_TTY
+ bool "MIPS EJTAG Fast Debug Channel TTY"
+ depends on MIPS_CDMM
+ help
+ This enables a TTY and console on the MIPS EJTAG Fast Debug Channels,
+ if they are present. This can be useful when working with an EJTAG
+ probe which supports it, to get console output and a login prompt via
+ EJTAG without needing to connect a serial cable.
+
+ TTY devices are named e.g. ttyFDC3c2 (for FDC channel 2 of the FDC on
+ CPU3).
+
+ The console can be enabled with console=fdc1 (for FDC channel 1 on all
+ CPUs). Do not use the console unless there is a debug probe attached
+ to drain the FDC TX FIFO.
+
+ If unsure, say N.
+
+config MIPS_EJTAG_FDC_EARLYCON
+ bool "Early FDC console"
+ depends on MIPS_EJTAG_FDC_TTY
+ help
+ This registers a console on FDC channel 1 very early during boot (from
+ MIPS arch code). This is useful for bring-up and debugging early boot
+ issues.
+
+ Do not enable unless there is a debug probe attached to drain the FDC
+ TX FIFO.
+
+ If unsure, say N.
+
+config MIPS_EJTAG_FDC_KGDB
+ bool "Use KGDB over an FDC channel"
+ depends on MIPS_EJTAG_FDC_TTY && KGDB
+ default y
+ help
+ This enables the use of KGDB over an FDC channel, allowing KGDB to be
+ used remotely or when a serial port isn't available.
+
+config MIPS_EJTAG_FDC_KGDB_CHAN
+ int "KGDB FDC channel"
+ depends on MIPS_EJTAG_FDC_KGDB
+ range 2 15
+ default 3
+ help
+ FDC channel number to use for KGDB.
+
endif # TTY
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 58ad1c05b7f8..5817e2397463 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK) += synclink.o
obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o
obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o
obj-$(CONFIG_DA_TTY) += metag_da.o
+obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o
obj-y += ipwireless/
diff --git a/drivers/tty/mips_ejtag_fdc.c b/drivers/tty/mips_ejtag_fdc.c
new file mode 100644
index 000000000000..04d9e23d1ee1
--- /dev/null
+++ b/drivers/tty/mips_ejtag_fdc.c
@@ -0,0 +1,1303 @@
+/*
+ * TTY driver for MIPS EJTAG Fast Debug Channels.
+ *
+ * Copyright (C) 2007-2015 Imagination Technologies Ltd
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for more
+ * details.
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kgdb.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/uaccess.h>
+
+#include <asm/cdmm.h>
+#include <asm/irq.h>
+
+/* Register offsets */
+#define REG_FDACSR 0x00 /* FDC Access Control and Status Register */
+#define REG_FDCFG 0x08 /* FDC Configuration Register */
+#define REG_FDSTAT 0x10 /* FDC Status Register */
+#define REG_FDRX 0x18 /* FDC Receive Register */
+#define REG_FDTX(N) (0x20+0x8*(N)) /* FDC Transmit Register n (0..15) */
+
+/* Register fields */
+
+#define REG_FDCFG_TXINTTHRES_SHIFT 18
+#define REG_FDCFG_TXINTTHRES (0x3 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_DISABLED (0x0 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_EMPTY (0x1 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_NOTFULL (0x2 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_TXINTTHRES_NEAREMPTY (0x3 << REG_FDCFG_TXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_SHIFT 16
+#define REG_FDCFG_RXINTTHRES (0x3 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_DISABLED (0x0 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_FULL (0x1 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_NOTEMPTY (0x2 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_RXINTTHRES_NEARFULL (0x3 << REG_FDCFG_RXINTTHRES_SHIFT)
+#define REG_FDCFG_TXFIFOSIZE_SHIFT 8
+#define REG_FDCFG_TXFIFOSIZE (0xff << REG_FDCFG_TXFIFOSIZE_SHIFT)
+#define REG_FDCFG_RXFIFOSIZE_SHIFT 0
+#define REG_FDCFG_RXFIFOSIZE (0xff << REG_FDCFG_RXFIFOSIZE_SHIFT)
+
+#define REG_FDSTAT_TXCOUNT_SHIFT 24
+#define REG_FDSTAT_TXCOUNT (0xff << REG_FDSTAT_TXCOUNT_SHIFT)
+#define REG_FDSTAT_RXCOUNT_SHIFT 16
+#define REG_FDSTAT_RXCOUNT (0xff << REG_FDSTAT_RXCOUNT_SHIFT)
+#define REG_FDSTAT_RXCHAN_SHIFT 4
+#define REG_FDSTAT_RXCHAN (0xf << REG_FDSTAT_RXCHAN_SHIFT)
+#define REG_FDSTAT_RXE BIT(3) /* Rx Empty */
+#define REG_FDSTAT_RXF BIT(2) /* Rx Full */
+#define REG_FDSTAT_TXE BIT(1) /* Tx Empty */
+#define REG_FDSTAT_TXF BIT(0) /* Tx Full */
+
+/* Default channel for the early console */
+#define CONSOLE_CHANNEL 1
+
+#define NUM_TTY_CHANNELS 16
+
+#define RX_BUF_SIZE 1024
+
+/*
+ * When the IRQ is unavailable, the FDC state must be polled for incoming data
+ * and space becoming available in TX FIFO.
+ */
+#define FDC_TTY_POLL (HZ / 50)
+
+struct mips_ejtag_fdc_tty;
+
+/**
+ * struct mips_ejtag_fdc_tty_port - Wrapper struct for FDC tty_port.
+ * @port: TTY port data
+ * @driver: TTY driver.
+ * @rx_lock: Lock for rx_buf.
+ * This protects between the hard interrupt and user
+ * context. It's also held during read SWITCH operations.
+ * @rx_buf: Read buffer.
+ * @xmit_lock: Lock for xmit_*, and port.xmit_buf.
+ * This protects between user context and kernel thread.
+ * It is used from chars_in_buffer()/write_room() TTY
+ * callbacks which are used during wait operations, so a
+ * mutex is unsuitable.
+ * @xmit_cnt: Size of xmit buffer contents.
+ * @xmit_head: Head of xmit buffer where data is written.
+ * @xmit_tail: Tail of xmit buffer where data is read.
+ * @xmit_empty: Completion for xmit buffer being empty.
+ */
+struct mips_ejtag_fdc_tty_port {
+ struct tty_port port;
+ struct mips_ejtag_fdc_tty *driver;
+ raw_spinlock_t rx_lock;
+ void *rx_buf;
+ spinlock_t xmit_lock;
+ unsigned int xmit_cnt;
+ unsigned int xmit_head;
+ unsigned int xmit_tail;
+ struct completion xmit_empty;
+};
+
+/**
+ * struct mips_ejtag_fdc_tty - Driver data for FDC as a whole.
+ * @dev: FDC device (for dev_*() logging).
+ * @driver: TTY driver.
+ * @cpu: CPU number for this FDC.
+ * @fdc_name: FDC name (not for base of channel names).
+ * @driver_name: Base of driver name.
+ * @ports: Per-channel data.
+ * @waitqueue: Wait queue for waiting for TX data, or for space in TX
+ * FIFO.
+ * @lock: Lock to protect FDCFG (interrupt enable).
+ * @thread: KThread for writing out data to FDC.
+ * @reg: FDC registers.
+ * @tx_fifo: TX FIFO size.
+ * @xmit_size: Size of each port's xmit buffer.
+ * @xmit_total: Total number of bytes (from all ports) to transmit.
+ * @xmit_next: Next port number to transmit from (round robin).
+ * @xmit_full: Indicates TX FIFO is full, we're waiting for space.
+ * @irq: IRQ number (negative if no IRQ).
+ * @removing: Indicates the device is being removed and @poll_timer
+ * should not be restarted.
+ * @poll_timer: Timer for polling for interrupt events when @irq < 0.
+ * @sysrq_pressed: Whether the magic sysrq key combination has been
+ * detected. See mips_ejtag_fdc_handle().
+ */
+struct mips_ejtag_fdc_tty {
+ struct device *dev;
+ struct tty_driver *driver;
+ unsigned int cpu;
+ char fdc_name[16];
+ char driver_name[16];
+ struct mips_ejtag_fdc_tty_port ports[NUM_TTY_CHANNELS];
+ wait_queue_head_t waitqueue;
+ raw_spinlock_t lock;
+ struct task_struct *thread;
+
+ void __iomem *reg;
+ u8 tx_fifo;
+
+ unsigned int xmit_size;
+ atomic_t xmit_total;
+ unsigned int xmit_next;
+ bool xmit_full;
+
+ int irq;
+ bool removing;
+ struct timer_list poll_timer;
+
+#ifdef CONFIG_MAGIC_SYSRQ
+ bool sysrq_pressed;
+#endif
+};
+
+/* Hardware access */
+
+static inline void mips_ejtag_fdc_write(struct mips_ejtag_fdc_tty *priv,
+ unsigned int offs, unsigned int data)
+{
+ iowrite32(data, priv->reg + offs);
+}
+
+static inline unsigned int mips_ejtag_fdc_read(struct mips_ejtag_fdc_tty *priv,
+ unsigned int offs)
+{
+ return ioread32(priv->reg + offs);
+}
+
+/* Encoding of byte stream in FDC words */
+
+/**
+ * struct fdc_word - FDC word encoding some number of bytes of data.
+ * @word: Raw FDC word.
+ * @bytes: Number of bytes encoded by @word.
+ */
+struct fdc_word {
+ u32 word;
+ unsigned int bytes;
+};
+
+/*
+ * This is a compact encoding which allows every 1 byte, 2 byte, and 3 byte
+ * sequence to be encoded in a single word, while allowing the majority of 4
+ * byte sequences (including all ASCII and common binary data) to be encoded in
+ * a single word too.
+ * _______________________ _____________
+ * | FDC Word | |
+ * |31-24|23-16|15-8 | 7-0 | Bytes |
+ * |_____|_____|_____|_____|_____________|
+ * | | | | | |
+ * |0x80 |0x80 |0x80 | WW | WW |
+ * |0x81 |0x81 | XX | WW | WW XX |
+ * |0x82 | YY | XX | WW | WW XX YY |
+ * | ZZ | YY | XX | WW | WW XX YY ZZ |
+ * |_____|_____|_____|_____|_____________|
+ *
+ * Note that the 4-byte encoding can only be used where none of the other 3
+ * encodings match, otherwise it must fall back to the 3 byte encoding.
+ */
+
+/* ranges >= 1 && sizes[0] >= 1 */
+static struct fdc_word mips_ejtag_fdc_encode(const char **ptrs,
+ unsigned int *sizes,
+ unsigned int ranges)
+{
+ struct fdc_word word = { 0, 0 };
+ const char **ptrs_end = ptrs + ranges;
+
+ for (; ptrs < ptrs_end; ++ptrs) {
+ const char *ptr = *(ptrs++);
+ const char *end = ptr + *(sizes++);
+
+ for (; ptr < end; ++ptr) {
+ word.word |= (u8)*ptr << (8*word.bytes);
+ ++word.bytes;
+ if (word.bytes == 4)
+ goto done;
+ }
+ }
+done:
+ /* Choose the appropriate encoding */
+ switch (word.bytes) {
+ case 4:
+ /* 4 byte encoding, but don't match the 1-3 byte encodings */
+ if ((word.word >> 8) != 0x808080 &&
+ (word.word >> 16) != 0x8181 &&
+ (word.word >> 24) != 0x82)
+ break;
+ /* Fall back to a 3 byte encoding */
+ word.bytes = 3;
+ word.word &= 0x00ffffff;
+ case 3:
+ /* 3 byte encoding */
+ word.word |= 0x82000000;
+ break;
+ case 2:
+ /* 2 byte encoding */
+ word.word |= 0x81810000;
+ break;
+ case 1:
+ /* 1 byte encoding */
+ word.word |= 0x80808000;
+ break;
+ }
+ return word;
+}
+
+static unsigned int mips_ejtag_fdc_decode(u32 word, char *buf)
+{
+ buf[0] = (u8)word;
+ word >>= 8;
+ if (word == 0x808080)
+ return 1;
+ buf[1] = (u8)word;
+ word >>= 8;
+ if (word == 0x8181)
+ return 2;
+ buf[2] = (u8)word;
+ word >>= 8;
+ if (word == 0x82)
+ return 3;
+ buf[3] = (u8)word;
+ return 4;
+}
+
+/* Console operations */
+
+/**
+ * struct mips_ejtag_fdc_console - Wrapper struct for FDC consoles.
+ * @cons: Console object.
+ * @tty_drv: TTY driver associated with this console.
+ * @lock: Lock to protect concurrent access to other fields.
+ * This is raw because it may be used very early.
+ * @initialised: Whether the console is initialised.
+ * @regs: Registers base address for each CPU.
+ */
+struct mips_ejtag_fdc_console {
+ struct console cons;
+ struct tty_driver *tty_drv;
+ raw_spinlock_t lock;
+ bool initialised;
+ void __iomem *regs[NR_CPUS];
+};
+
+/* Low level console write shared by early console and normal console */
+static void mips_ejtag_fdc_console_write(struct console *c, const char *s,
+ unsigned int count)
+{
+ struct mips_ejtag_fdc_console *cons =
+ container_of(c, struct mips_ejtag_fdc_console, cons);
+ void __iomem *regs;
+ struct fdc_word word;
+ unsigned long flags;
+ unsigned int i, buf_len, cpu;
+ bool done_cr = false;
+ char buf[4];
+ const char *buf_ptr = buf;
+ /* Number of bytes of input data encoded up to each byte in buf */
+ u8 inc[4];
+
+ local_irq_save(flags);
+ cpu = smp_processor_id();
+ regs = cons->regs[cpu];
+ /* First console output on this CPU? */
+ if (!regs) {
+ regs = mips_cdmm_early_probe(0xfd);
+ cons->regs[cpu] = regs;
+ }
+ /* Already tried and failed to find FDC on this CPU? */
+ if (IS_ERR(regs))
+ goto out;
+ while (count) {
+ /*
+ * Copy the next few characters to a buffer so we can inject
+ * carriage returns before newlines.
+ */
+ for (buf_len = 0, i = 0; buf_len < 4 && i < count; ++buf_len) {
+ if (s[i] == '\n' && !done_cr) {
+ buf[buf_len] = '\r';
+ done_cr = true;
+ } else {
+ buf[buf_len] = s[i];
+ done_cr = false;
+ ++i;
+ }
+ inc[buf_len] = i;
+ }
+ word = mips_ejtag_fdc_encode(&buf_ptr, &buf_len, 1);
+ count -= inc[word.bytes - 1];
+ s += inc[word.bytes - 1];
+
+ /* Busy wait until there's space in fifo */
+ while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF)
+ ;
+ iowrite32(word.word, regs + REG_FDTX(c->index));
+ }
+out:
+ local_irq_restore(flags);
+}
+
+static struct tty_driver *mips_ejtag_fdc_console_device(struct console *c,
+ int *index)
+{
+ struct mips_ejtag_fdc_console *cons =
+ container_of(c, struct mips_ejtag_fdc_console, cons);
+
+ *index = c->index;
+ return cons->tty_drv;
+}
+
+/* Initialise an FDC console (early or normal */
+static int __init mips_ejtag_fdc_console_init(struct mips_ejtag_fdc_console *c)
+{
+ void __iomem *regs;
+ unsigned long flags;
+ int ret = 0;
+
+ raw_spin_lock_irqsave(&c->lock, flags);
+ /* Don't init twice */
+ if (c->initialised)
+ goto out;
+ /* Look for the FDC device */
+ regs = mips_cdmm_early_probe(0xfd);
+ if (IS_ERR(regs)) {
+ ret = PTR_ERR(regs);
+ goto out;
+ }
+
+ c->initialised = true;
+ c->regs[smp_processor_id()] = regs;
+ register_console(&c->cons);
+out:
+ raw_spin_unlock_irqrestore(&c->lock, flags);
+ return ret;
+}
+
+static struct mips_ejtag_fdc_console mips_ejtag_fdc_con = {
+ .cons = {
+ .name = "fdc",
+ .write = mips_ejtag_fdc_console_write,
+ .device = mips_ejtag_fdc_console_device,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+ },
+ .lock = __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_con.lock),
+};
+
+/* TTY RX/TX operations */
+
+/**
+ * mips_ejtag_fdc_put_chan() - Write out a block of channel data.
+ * @priv: Pointer to driver private data.
+ * @chan: Channel number.
+ *
+ * Write a single block of data out to the debug adapter. If the circular buffer
+ * is wrapped then only the first block is written.
+ *
+ * Returns: The number of bytes that were written.
+ */
+static unsigned int mips_ejtag_fdc_put_chan(struct mips_ejtag_fdc_tty *priv,
+ unsigned int chan)
+{
+ struct mips_ejtag_fdc_tty_port *dport;
+ struct tty_struct *tty;
+ const char *ptrs[2];
+ unsigned int sizes[2] = { 0 };
+ struct fdc_word word = { .bytes = 0 };
+ unsigned long flags;
+
+ dport = &priv->ports[chan];
+ spin_lock(&dport->xmit_lock);
+ if (dport->xmit_cnt) {
+ ptrs[0] = dport->port.xmit_buf + dport->xmit_tail;
+ sizes[0] = min_t(unsigned int,
+ priv->xmit_size - dport->xmit_tail,
+ dport->xmit_cnt);
+ ptrs[1] = dport->port.xmit_buf;
+ sizes[1] = dport->xmit_cnt - sizes[0];
+ word = mips_ejtag_fdc_encode(ptrs, sizes, 1 + !!sizes[1]);
+
+ dev_dbg(priv->dev, "%s%u: out %08x: \"%*pE%*pE\"\n",
+ priv->driver_name, chan, word.word,
+ min_t(int, word.bytes, sizes[0]), ptrs[0],
+ max_t(int, 0, word.bytes - sizes[0]), ptrs[1]);
+
+ local_irq_save(flags);
+ /* Maybe we raced with the console and TX FIFO is full */
+ if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF)
+ word.bytes = 0;
+ else
+ mips_ejtag_fdc_write(priv, REG_FDTX(chan), word.word);
+ local_irq_restore(flags);
+
+ dport->xmit_cnt -= word.bytes;
+ if (!dport->xmit_cnt) {
+ /* Reset pointers to avoid wraps */
+ dport->xmit_head = 0;
+ dport->xmit_tail = 0;
+ complete(&dport->xmit_empty);
+ } else {
+ dport->xmit_tail += word.bytes;
+ if (dport->xmit_tail >= priv->xmit_size)
+ dport->xmit_tail -= priv->xmit_size;
+ }
+ atomic_sub(word.bytes, &priv->xmit_total);
+ }
+ spin_unlock(&dport->xmit_lock);
+
+ /* If we've made more data available, wake up tty */
+ if (sizes[0] && word.bytes) {
+ tty = tty_port_tty_get(&dport->port);
+ if (tty) {
+ tty_wakeup(tty);
+ tty_kref_put(tty);
+ }
+ }
+
+ return word.bytes;
+}
+
+/**
+ * mips_ejtag_fdc_put() - Kernel thread to write out channel data to FDC.
+ * @arg: Driver pointer.
+ *
+ * This kernel thread runs while @priv->xmit_total != 0, and round robins the
+ * channels writing out blocks of buffered data to the FDC TX FIFO.
+ */
+static int mips_ejtag_fdc_put(void *arg)
+{
+ struct mips_ejtag_fdc_tty *priv = arg;
+ struct mips_ejtag_fdc_tty_port *dport;
+ unsigned int ret;
+ u32 cfg;
+
+ __set_current_state(TASK_RUNNING);
+ while (!kthread_should_stop()) {
+ /* Wait for data to actually write */
+ wait_event_interruptible(priv->waitqueue,
+ atomic_read(&priv->xmit_total) ||
+ kthread_should_stop());
+ if (kthread_should_stop())
+ break;
+
+ /* Wait for TX FIFO space to write data */
+ raw_spin_lock_irq(&priv->lock);
+ if (mips_ejtag_fdc_read(priv, REG_FDSTAT) & REG_FDSTAT_TXF) {
+ priv->xmit_full = true;
+ if (priv->irq >= 0) {
+ /* Enable TX interrupt */
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ cfg &= ~REG_FDCFG_TXINTTHRES;
+ cfg |= REG_FDCFG_TXINTTHRES_NOTFULL;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+ }
+ }
+ raw_spin_unlock_irq(&priv->lock);
+ wait_event_interruptible(priv->waitqueue,
+ !(mips_ejtag_fdc_read(priv, REG_FDSTAT)
+ & REG_FDSTAT_TXF) ||
+ kthread_should_stop());
+ if (kthread_should_stop())
+ break;
+
+ /* Find next channel with data to output */
+ for (;;) {
+ dport = &priv->ports[priv->xmit_next];
+ spin_lock(&dport->xmit_lock);
+ ret = dport->xmit_cnt;
+ spin_unlock(&dport->xmit_lock);
+ if (ret)
+ break;
+ /* Round robin */
+ ++priv->xmit_next;
+ if (priv->xmit_next >= NUM_TTY_CHANNELS)
+ priv->xmit_next = 0;
+ }
+
+ /* Try writing data to the chosen channel */
+ ret = mips_ejtag_fdc_put_chan(priv, priv->xmit_next);
+
+ /*
+ * If anything was output, move on to the next channel so as not
+ * to starve other channels.
+ */
+ if (ret) {
+ ++priv->xmit_next;
+ if (priv->xmit_next >= NUM_TTY_CHANNELS)
+ priv->xmit_next = 0;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * mips_ejtag_fdc_handle() - Handle FDC events.
+ * @priv: Pointer to driver private data.
+ *
+ * Handle FDC events, such as new incoming data which needs draining out of the
+ * RX FIFO and feeding into the appropriate TTY ports, and space becoming
+ * available in the TX FIFO which would allow more data to be written out.
+ */
+static void mips_ejtag_fdc_handle(struct mips_ejtag_fdc_tty *priv)
+{
+ struct mips_ejtag_fdc_tty_port *dport;
+ unsigned int stat, channel, data, cfg, i, flipped;
+ int len;
+ char buf[4];
+
+ for (;;) {
+ /* Find which channel the next FDC word is destined for */
+ stat = mips_ejtag_fdc_read(priv, REG_FDSTAT);
+ if (stat & REG_FDSTAT_RXE)
+ break;
+ channel = (stat & REG_FDSTAT_RXCHAN) >> REG_FDSTAT_RXCHAN_SHIFT;
+ dport = &priv->ports[channel];
+
+ /* Read out the FDC word, decode it, and pass to tty layer */
+ raw_spin_lock(&dport->rx_lock);
+ data = mips_ejtag_fdc_read(priv, REG_FDRX);
+
+ len = mips_ejtag_fdc_decode(data, buf);
+ dev_dbg(priv->dev, "%s%u: in %08x: \"%*pE\"\n",
+ priv->driver_name, channel, data, len, buf);
+
+ flipped = 0;
+ for (i = 0; i < len; ++i) {
+#ifdef CONFIG_MAGIC_SYSRQ
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+ /* Support just Ctrl+C with KGDB channel */
+ if (channel == CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN) {
+ if (buf[i] == '\x03') { /* ^C */
+ handle_sysrq('g');
+ continue;
+ }
+ }
+#endif
+ /* Support Ctrl+O for console channel */
+ if (channel == mips_ejtag_fdc_con.cons.index) {
+ if (buf[i] == '\x0f') { /* ^O */
+ priv->sysrq_pressed =
+ !priv->sysrq_pressed;
+ if (priv->sysrq_pressed)
+ continue;
+ } else if (priv->sysrq_pressed) {
+ handle_sysrq(buf[i]);
+ priv->sysrq_pressed = false;
+ continue;
+ }
+ }
+#endif /* CONFIG_MAGIC_SYSRQ */
+
+ /* Check the port isn't being shut down */
+ if (!dport->rx_buf)
+ continue;
+
+ flipped += tty_insert_flip_char(&dport->port, buf[i],
+ TTY_NORMAL);
+ }
+ if (flipped)
+ tty_flip_buffer_push(&dport->port);
+
+ raw_spin_unlock(&dport->rx_lock);
+ }
+
+ /* If TX FIFO no longer full we may be able to write more data */
+ raw_spin_lock(&priv->lock);
+ if (priv->xmit_full && !(stat & REG_FDSTAT_TXF)) {
+ priv->xmit_full = false;
+
+ /* Disable TX interrupt */
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ cfg &= ~REG_FDCFG_TXINTTHRES;
+ cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+
+ /* Wait the kthread so it can try writing more data */
+ wake_up_interruptible(&priv->waitqueue);
+ }
+ raw_spin_unlock(&priv->lock);
+}
+
+/**
+ * mips_ejtag_fdc_isr() - Interrupt handler.
+ * @irq: IRQ number.
+ * @dev_id: Pointer to driver private data.
+ *
+ * This is the interrupt handler, used when interrupts are enabled.
+ *
+ * It simply triggers the common FDC handler code.
+ *
+ * Returns: IRQ_HANDLED if an FDC interrupt was pending.
+ * IRQ_NONE otherwise.
+ */
+static irqreturn_t mips_ejtag_fdc_isr(int irq, void *dev_id)
+{
+ struct mips_ejtag_fdc_tty *priv = dev_id;
+
+ /*
+ * We're not using proper per-cpu IRQs, so we must be careful not to
+ * handle IRQs on CPUs we're not interested in.
+ *
+ * Ideally proper per-cpu IRQ handlers could be used, but that doesn't
+ * fit well with the whole sharing of the main CPU IRQ lines. When we
+ * have something with a GIC that routes the FDC IRQs (i.e. no sharing
+ * between handlers) then support could be added more easily.
+ */
+ if (smp_processor_id() != priv->cpu)
+ return IRQ_NONE;
+
+ /* If no FDC interrupt pending, it wasn't for us */
+ if (!(read_c0_cause() & CAUSEF_FDCI))
+ return IRQ_NONE;
+
+ mips_ejtag_fdc_handle(priv);
+ return IRQ_HANDLED;
+}
+
+/**
+ * mips_ejtag_fdc_tty_timer() - Poll FDC for incoming data.
+ * @opaque: Pointer to driver private data.
+ *
+ * This is the timer handler for when interrupts are disabled and polling the
+ * FDC state is required.
+ *
+ * It simply triggers the common FDC handler code and arranges for further
+ * polling.
+ */
+static void mips_ejtag_fdc_tty_timer(unsigned long opaque)
+{
+ struct mips_ejtag_fdc_tty *priv = (void *)opaque;
+
+ mips_ejtag_fdc_handle(priv);
+ if (!priv->removing)
+ mod_timer_pinned(&priv->poll_timer, jiffies + FDC_TTY_POLL);
+}
+
+/* TTY Port operations */
+
+static int mips_ejtag_fdc_tty_port_activate(struct tty_port *port,
+ struct tty_struct *tty)
+{
+ struct mips_ejtag_fdc_tty_port *dport =
+ container_of(port, struct mips_ejtag_fdc_tty_port, port);
+ void *rx_buf;
+
+ /* Allocate the buffer we use for writing data */
+ if (tty_port_alloc_xmit_buf(port) < 0)
+ goto err;
+
+ /* Allocate the buffer we use for reading data */
+ rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL);
+ if (!rx_buf)
+ goto err_free_xmit;
+
+ raw_spin_lock_irq(&dport->rx_lock);
+ dport->rx_buf = rx_buf;
+ raw_spin_unlock_irq(&dport->rx_lock);
+
+ return 0;
+err_free_xmit:
+ tty_port_free_xmit_buf(port);
+err:
+ return -ENOMEM;
+}
+
+static void mips_ejtag_fdc_tty_port_shutdown(struct tty_port *port)
+{
+ struct mips_ejtag_fdc_tty_port *dport =
+ container_of(port, struct mips_ejtag_fdc_tty_port, port);
+ struct mips_ejtag_fdc_tty *priv = dport->driver;
+ void *rx_buf;
+ unsigned int count;
+
+ spin_lock(&dport->xmit_lock);
+ count = dport->xmit_cnt;
+ spin_unlock(&dport->xmit_lock);
+ if (count) {
+ /*
+ * There's still data to write out, so wake and wait for the
+ * writer thread to drain the buffer.
+ */
+ wake_up_interruptible(&priv->waitqueue);
+ wait_for_completion(&dport->xmit_empty);
+ }
+
+ /* Null the read buffer (timer could still be running!) */
+ raw_spin_lock_irq(&dport->rx_lock);
+ rx_buf = dport->rx_buf;
+ dport->rx_buf = NULL;
+ raw_spin_unlock_irq(&dport->rx_lock);
+ /* Free the read buffer */
+ kfree(rx_buf);
+
+ /* Free the write buffer */
+ tty_port_free_xmit_buf(port);
+}
+
+static const struct tty_port_operations mips_ejtag_fdc_tty_port_ops = {
+ .activate = mips_ejtag_fdc_tty_port_activate,
+ .shutdown = mips_ejtag_fdc_tty_port_shutdown,
+};
+
+/* TTY operations */
+
+static int mips_ejtag_fdc_tty_install(struct tty_driver *driver,
+ struct tty_struct *tty)
+{
+ struct mips_ejtag_fdc_tty *priv = driver->driver_state;
+
+ tty->driver_data = &priv->ports[tty->index];
+ return tty_port_install(&priv->ports[tty->index].port, driver, tty);
+}
+
+static int mips_ejtag_fdc_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ return tty_port_open(tty->port, tty, filp);
+}
+
+static void mips_ejtag_fdc_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ return tty_port_close(tty->port, tty, filp);
+}
+
+static void mips_ejtag_fdc_tty_hangup(struct tty_struct *tty)
+{
+ struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+ struct mips_ejtag_fdc_tty *priv = dport->driver;
+
+ /* Drop any data in the xmit buffer */
+ spin_lock(&dport->xmit_lock);
+ if (dport->xmit_cnt) {
+ atomic_sub(dport->xmit_cnt, &priv->xmit_total);
+ dport->xmit_cnt = 0;
+ dport->xmit_head = 0;
+ dport->xmit_tail = 0;
+ complete(&dport->xmit_empty);
+ }
+ spin_unlock(&dport->xmit_lock);
+
+ tty_port_hangup(tty->port);
+}
+
+static int mips_ejtag_fdc_tty_write(struct tty_struct *tty,
+ const unsigned char *buf, int total)
+{
+ int count, block;
+ struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+ struct mips_ejtag_fdc_tty *priv = dport->driver;
+
+ /*
+ * Write to output buffer.
+ *
+ * The reason that we asynchronously write the buffer is because if we
+ * were to write the buffer synchronously then because the channels are
+ * per-CPU the buffer would be written to the channel of whatever CPU
+ * we're running on.
+ *
+ * What we actually want to happen is have all input and output done on
+ * one CPU.
+ */
+ spin_lock(&dport->xmit_lock);
+ /* Work out how many bytes we can write to the xmit buffer */
+ total = min(total, (int)(priv->xmit_size - dport->xmit_cnt));
+ atomic_add(total, &priv->xmit_total);
+ dport->xmit_cnt += total;
+ /* Write the actual bytes (may need splitting if it wraps) */
+ for (count = total; count; count -= block) {
+ block = min(count, (int)(priv->xmit_size - dport->xmit_head));
+ memcpy(dport->port.xmit_buf + dport->xmit_head, buf, block);
+ dport->xmit_head += block;
+ if (dport->xmit_head >= priv->xmit_size)
+ dport->xmit_head -= priv->xmit_size;
+ buf += block;
+ }
+ count = dport->xmit_cnt;
+ /* Xmit buffer no longer empty? */
+ if (count)
+ reinit_completion(&dport->xmit_empty);
+ spin_unlock(&dport->xmit_lock);
+
+ /* Wake up the kthread */
+ if (total)
+ wake_up_interruptible(&priv->waitqueue);
+ return total;
+}
+
+static int mips_ejtag_fdc_tty_write_room(struct tty_struct *tty)
+{
+ struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+ struct mips_ejtag_fdc_tty *priv = dport->driver;
+ int room;
+
+ /* Report the space in the xmit buffer */
+ spin_lock(&dport->xmit_lock);
+ room = priv->xmit_size - dport->xmit_cnt;
+ spin_unlock(&dport->xmit_lock);
+
+ return room;
+}
+
+static int mips_ejtag_fdc_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct mips_ejtag_fdc_tty_port *dport = tty->driver_data;
+ int chars;
+
+ /* Report the number of bytes in the xmit buffer */
+ spin_lock(&dport->xmit_lock);
+ chars = dport->xmit_cnt;
+ spin_unlock(&dport->xmit_lock);
+
+ return chars;
+}
+
+static const struct tty_operations mips_ejtag_fdc_tty_ops = {
+ .install = mips_ejtag_fdc_tty_install,
+ .open = mips_ejtag_fdc_tty_open,
+ .close = mips_ejtag_fdc_tty_close,
+ .hangup = mips_ejtag_fdc_tty_hangup,
+ .write = mips_ejtag_fdc_tty_write,
+ .write_room = mips_ejtag_fdc_tty_write_room,
+ .chars_in_buffer = mips_ejtag_fdc_tty_chars_in_buffer,
+};
+
+static int mips_ejtag_fdc_tty_probe(struct mips_cdmm_device *dev)
+{
+ int ret, nport;
+ struct mips_ejtag_fdc_tty_port *dport;
+ struct mips_ejtag_fdc_tty *priv;
+ struct tty_driver *driver;
+ unsigned int cfg, tx_fifo;
+
+ priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->cpu = dev->cpu;
+ priv->dev = &dev->dev;
+ mips_cdmm_set_drvdata(dev, priv);
+ atomic_set(&priv->xmit_total, 0);
+ raw_spin_lock_init(&priv->lock);
+
+ priv->reg = devm_ioremap_nocache(priv->dev, dev->res.start,
+ resource_size(&dev->res));
+ if (!priv->reg) {
+ dev_err(priv->dev, "ioremap failed for resource %pR\n",
+ &dev->res);
+ return -ENOMEM;
+ }
+
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ tx_fifo = (cfg & REG_FDCFG_TXFIFOSIZE) >> REG_FDCFG_TXFIFOSIZE_SHIFT;
+ /* Disable interrupts */
+ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+ cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+ cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+
+ /* Make each port's xmit FIFO big enough to fill FDC TX FIFO */
+ priv->xmit_size = min(tx_fifo * 4, (unsigned int)SERIAL_XMIT_SIZE);
+
+ driver = tty_alloc_driver(NUM_TTY_CHANNELS, TTY_DRIVER_REAL_RAW);
+ if (IS_ERR(driver))
+ return PTR_ERR(driver);
+ priv->driver = driver;
+
+ driver->driver_name = "ejtag_fdc";
+ snprintf(priv->fdc_name, sizeof(priv->fdc_name), "ttyFDC%u", dev->cpu);
+ snprintf(priv->driver_name, sizeof(priv->driver_name), "%sc",
+ priv->fdc_name);
+ driver->name = priv->driver_name;
+ driver->major = 0; /* Auto-allocate */
+ driver->minor_start = 0;
+ driver->type = TTY_DRIVER_TYPE_SERIAL;
+ driver->subtype = SERIAL_TYPE_NORMAL;
+ driver->init_termios = tty_std_termios;
+ driver->init_termios.c_cflag |= CLOCAL;
+ driver->driver_state = priv;
+
+ tty_set_operations(driver, &mips_ejtag_fdc_tty_ops);
+ for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
+ dport = &priv->ports[nport];
+ dport->driver = priv;
+ tty_port_init(&dport->port);
+ dport->port.ops = &mips_ejtag_fdc_tty_port_ops;
+ raw_spin_lock_init(&dport->rx_lock);
+ spin_lock_init(&dport->xmit_lock);
+ /* The xmit buffer starts empty, i.e. completely written */
+ init_completion(&dport->xmit_empty);
+ complete(&dport->xmit_empty);
+ }
+
+ /* Set up the console */
+ mips_ejtag_fdc_con.regs[dev->cpu] = priv->reg;
+ if (dev->cpu == 0)
+ mips_ejtag_fdc_con.tty_drv = driver;
+
+ init_waitqueue_head(&priv->waitqueue);
+ priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name);
+ if (IS_ERR(priv->thread)) {
+ ret = PTR_ERR(priv->thread);
+ dev_err(priv->dev, "Couldn't create kthread (%d)\n", ret);
+ goto err_destroy_ports;
+ }
+ /*
+ * Bind the writer thread to the right CPU so it can't migrate.
+ * The channels are per-CPU and we want all channel I/O to be on a
+ * single predictable CPU.
+ */
+ kthread_bind(priv->thread, dev->cpu);
+ wake_up_process(priv->thread);
+
+ /* Look for an FDC IRQ */
+ priv->irq = -1;
+ if (get_c0_fdc_int)
+ priv->irq = get_c0_fdc_int();
+
+ /* Try requesting the IRQ */
+ if (priv->irq >= 0) {
+ /*
+ * IRQF_SHARED, IRQF_NO_SUSPEND: The FDC IRQ may be shared with
+ * other local interrupts such as the timer which sets
+ * IRQF_TIMER (including IRQF_NO_SUSPEND).
+ *
+ * IRQF_NO_THREAD: The FDC IRQ isn't individually maskable so it
+ * cannot be deferred and handled by a thread on RT kernels. For
+ * this reason any spinlocks used from the ISR are raw.
+ */
+ ret = devm_request_irq(priv->dev, priv->irq, mips_ejtag_fdc_isr,
+ IRQF_PERCPU | IRQF_SHARED |
+ IRQF_NO_THREAD | IRQF_NO_SUSPEND,
+ priv->fdc_name, priv);
+ if (ret)
+ priv->irq = -1;
+ }
+ if (priv->irq >= 0) {
+ /* IRQ is usable, enable RX interrupt */
+ raw_spin_lock_irq(&priv->lock);
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ cfg &= ~REG_FDCFG_RXINTTHRES;
+ cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+ raw_spin_unlock_irq(&priv->lock);
+ } else {
+ /* If we didn't get an usable IRQ, poll instead */
+ setup_timer(&priv->poll_timer, mips_ejtag_fdc_tty_timer,
+ (unsigned long)priv);
+ priv->poll_timer.expires = jiffies + FDC_TTY_POLL;
+ /*
+ * Always attach the timer to the right CPU. The channels are
+ * per-CPU so all polling should be from a single CPU.
+ */
+ add_timer_on(&priv->poll_timer, dev->cpu);
+
+ dev_info(priv->dev, "No usable IRQ, polling enabled\n");
+ }
+
+ ret = tty_register_driver(driver);
+ if (ret < 0) {
+ dev_err(priv->dev, "Couldn't install tty driver (%d)\n", ret);
+ goto err_stop_irq;
+ }
+
+ return 0;
+
+err_stop_irq:
+ if (priv->irq >= 0) {
+ raw_spin_lock_irq(&priv->lock);
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ /* Disable interrupts */
+ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+ cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+ cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+ raw_spin_unlock_irq(&priv->lock);
+ } else {
+ priv->removing = true;
+ del_timer_sync(&priv->poll_timer);
+ }
+ kthread_stop(priv->thread);
+err_destroy_ports:
+ if (dev->cpu == 0)
+ mips_ejtag_fdc_con.tty_drv = NULL;
+ for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
+ dport = &priv->ports[nport];
+ tty_port_destroy(&dport->port);
+ }
+ put_tty_driver(priv->driver);
+ return ret;
+}
+
+static int mips_ejtag_fdc_tty_remove(struct mips_cdmm_device *dev)
+{
+ struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev);
+ struct mips_ejtag_fdc_tty_port *dport;
+ int nport;
+ unsigned int cfg;
+
+ if (priv->irq >= 0) {
+ raw_spin_lock_irq(&priv->lock);
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ /* Disable interrupts */
+ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+ cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+ cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+ raw_spin_unlock_irq(&priv->lock);
+ } else {
+ priv->removing = true;
+ del_timer_sync(&priv->poll_timer);
+ }
+ kthread_stop(priv->thread);
+ if (dev->cpu == 0)
+ mips_ejtag_fdc_con.tty_drv = NULL;
+ tty_unregister_driver(priv->driver);
+ for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
+ dport = &priv->ports[nport];
+ tty_port_destroy(&dport->port);
+ }
+ put_tty_driver(priv->driver);
+ return 0;
+}
+
+static int mips_ejtag_fdc_tty_cpu_down(struct mips_cdmm_device *dev)
+{
+ struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev);
+ unsigned int cfg;
+
+ if (priv->irq >= 0) {
+ raw_spin_lock_irq(&priv->lock);
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ /* Disable interrupts */
+ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+ cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+ cfg |= REG_FDCFG_RXINTTHRES_DISABLED;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+ raw_spin_unlock_irq(&priv->lock);
+ } else {
+ priv->removing = true;
+ del_timer_sync(&priv->poll_timer);
+ }
+ kthread_stop(priv->thread);
+
+ return 0;
+}
+
+static int mips_ejtag_fdc_tty_cpu_up(struct mips_cdmm_device *dev)
+{
+ struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev);
+ unsigned int cfg;
+ int ret = 0;
+
+ if (priv->irq >= 0) {
+ /*
+ * IRQ is usable, enable RX interrupt
+ * This must be before kthread is restarted, as kthread may
+ * enable TX interrupt.
+ */
+ raw_spin_lock_irq(&priv->lock);
+ cfg = mips_ejtag_fdc_read(priv, REG_FDCFG);
+ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES);
+ cfg |= REG_FDCFG_TXINTTHRES_DISABLED;
+ cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY;
+ mips_ejtag_fdc_write(priv, REG_FDCFG, cfg);
+ raw_spin_unlock_irq(&priv->lock);
+ } else {
+ /* Restart poll timer */
+ priv->removing = false;
+ add_timer_on(&priv->poll_timer, dev->cpu);
+ }
+
+ /* Restart the kthread */
+ priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name);
+ if (IS_ERR(priv->thread)) {
+ ret = PTR_ERR(priv->thread);
+ dev_err(priv->dev, "Couldn't re-create kthread (%d)\n", ret);
+ goto out;
+ }
+ /* Bind it back to the right CPU and set it off */
+ kthread_bind(priv->thread, dev->cpu);
+ wake_up_process(priv->thread);
+out:
+ return ret;
+}
+
+static struct mips_cdmm_device_id mips_ejtag_fdc_tty_ids[] = {
+ { .type = 0xfd },
+ { }
+};
+
+static struct mips_cdmm_driver mips_ejtag_fdc_tty_driver = {
+ .drv = {
+ .name = "mips_ejtag_fdc",
+ },
+ .probe = mips_ejtag_fdc_tty_probe,
+ .remove = mips_ejtag_fdc_tty_remove,
+ .cpu_down = mips_ejtag_fdc_tty_cpu_down,
+ .cpu_up = mips_ejtag_fdc_tty_cpu_up,
+ .id_table = mips_ejtag_fdc_tty_ids,
+};
+module_mips_cdmm_driver(mips_ejtag_fdc_tty_driver);
+
+static int __init mips_ejtag_fdc_init_console(void)
+{
+ return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_con);
+}
+console_initcall(mips_ejtag_fdc_init_console);
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON
+static struct mips_ejtag_fdc_console mips_ejtag_fdc_earlycon = {
+ .cons = {
+ .name = "early_fdc",
+ .write = mips_ejtag_fdc_console_write,
+ .flags = CON_PRINTBUFFER | CON_BOOT,
+ .index = CONSOLE_CHANNEL,
+ },
+ .lock = __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_earlycon.lock),
+};
+
+int __init setup_early_fdc_console(void)
+{
+ return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon);
+}
+#endif
+
+#ifdef CONFIG_MIPS_EJTAG_FDC_KGDB
+
+/* read buffer to allow decompaction */
+static unsigned int kgdbfdc_rbuflen;
+static unsigned int kgdbfdc_rpos;
+static char kgdbfdc_rbuf[4];
+
+/* write buffer to allow compaction */
+static unsigned int kgdbfdc_wbuflen;
+static char kgdbfdc_wbuf[4];
+
+static void __iomem *kgdbfdc_setup(void)
+{
+ void __iomem *regs;
+ unsigned int cpu;
+
+ /* Find address, piggy backing off console percpu regs */
+ cpu = smp_processor_id();
+ regs = mips_ejtag_fdc_con.regs[cpu];
+ /* First console output on this CPU? */
+ if (!regs) {
+ regs = mips_cdmm_early_probe(0xfd);
+ mips_ejtag_fdc_con.regs[cpu] = regs;
+ }
+ /* Already tried and failed to find FDC on this CPU? */
+ if (IS_ERR(regs))
+ return regs;
+
+ return regs;
+}
+
+/* read a character from the read buffer, filling from FDC RX FIFO */
+static int kgdbfdc_read_char(void)
+{
+ unsigned int stat, channel, data;
+ void __iomem *regs;
+
+ /* No more data, try and read another FDC word from RX FIFO */
+ if (kgdbfdc_rpos >= kgdbfdc_rbuflen) {
+ kgdbfdc_rpos = 0;
+ kgdbfdc_rbuflen = 0;
+
+ regs = kgdbfdc_setup();
+ if (IS_ERR(regs))
+ return NO_POLL_CHAR;
+
+ /* Read next word from KGDB channel */
+ do {
+ stat = ioread32(regs + REG_FDSTAT);
+
+ /* No data waiting? */
+ if (stat & REG_FDSTAT_RXE)
+ return NO_POLL_CHAR;
+
+ /* Read next word */
+ channel = (stat & REG_FDSTAT_RXCHAN) >>
+ REG_FDSTAT_RXCHAN_SHIFT;
+ data = ioread32(regs + REG_FDRX);
+ } while (channel != CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN);
+
+ /* Decode into rbuf */
+ kgdbfdc_rbuflen = mips_ejtag_fdc_decode(data, kgdbfdc_rbuf);
+ }
+ pr_devel("kgdbfdc r %c\n", kgdbfdc_rbuf[kgdbfdc_rpos]);
+ return kgdbfdc_rbuf[kgdbfdc_rpos++];
+}
+
+/* push an FDC word from write buffer to TX FIFO */
+static void kgdbfdc_push_one(void)
+{
+ const char *bufs[1] = { kgdbfdc_wbuf };
+ struct fdc_word word;
+ void __iomem *regs;
+ unsigned int i;
+
+ /* Construct a word from any data in buffer */
+ word = mips_ejtag_fdc_encode(bufs, &kgdbfdc_wbuflen, 1);
+ /* Relocate any remaining data to beginnning of buffer */
+ kgdbfdc_wbuflen -= word.bytes;
+ for (i = 0; i < kgdbfdc_wbuflen; ++i)
+ kgdbfdc_wbuf[i] = kgdbfdc_wbuf[i + word.bytes];
+
+ regs = kgdbfdc_setup();
+ if (IS_ERR(regs))
+ return;
+
+ /* Busy wait until there's space in fifo */
+ while (ioread32(regs + REG_FDSTAT) & REG_FDSTAT_TXF)
+ ;
+ iowrite32(word.word, regs + REG_FDTX(CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN));
+}
+
+/* flush the whole write buffer to the TX FIFO */
+static void kgdbfdc_flush(void)
+{
+ while (kgdbfdc_wbuflen)
+ kgdbfdc_push_one();
+}
+
+/* write a character into the write buffer, writing out if full */
+static void kgdbfdc_write_char(u8 chr)
+{
+ pr_devel("kgdbfdc w %c\n", chr);
+ kgdbfdc_wbuf[kgdbfdc_wbuflen++] = chr;
+ if (kgdbfdc_wbuflen >= sizeof(kgdbfdc_wbuf))
+ kgdbfdc_push_one();
+}
+
+static struct kgdb_io kgdbfdc_io_ops = {
+ .name = "kgdbfdc",
+ .read_char = kgdbfdc_read_char,
+ .write_char = kgdbfdc_write_char,
+ .flush = kgdbfdc_flush,
+};
+
+static int __init kgdbfdc_init(void)
+{
+ kgdb_register_io_module(&kgdbfdc_io_ops);
+ return 0;
+}
+early_initcall(kgdbfdc_init);
+#endif