Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / bus / mips_cdmm.c
diff --git a/kernel/drivers/bus/mips_cdmm.c b/kernel/drivers/bus/mips_cdmm.c
new file mode 100644 (file)
index 0000000..ab3bde1
--- /dev/null
@@ -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 = __raw_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 = __raw_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);