| 1 | |
| 2 | * Bus driver for MIPS Common Device Memory Map (CDMM).
|
| 3 | *
|
| 4 | * Copyright (C) 2014-2015 Imagination Technologies Ltd.
|
| 5 | *
|
| 6 | * This file is subject to the terms and conditions of the GNU General Public
|
| 7 | * License. See the file "COPYING" in the main directory of this archive
|
| 8 | * for more details.
|
| 9 |
|
| 10 |
|
| 11 | #include <linux/atomic.h>
|
| 12 | #include <linux/err.h>
|
| 13 | #include <linux/cpu.h>
|
| 14 | #include <linux/cpumask.h>
|
| 15 | #include <linux/io.h>
|
| 16 | #include <linux/of_address.h>
|
| 17 | #include <linux/of.h>
|
| 18 | #include <linux/platform_device.h>
|
| 19 | #include <linux/slab.h>
|
| 20 | #include <linux/smp.h>
|
| 21 | #include <asm/cdmm.h>
|
| 22 | #include <asm/hazards.h>
|
| 23 | #include <asm/mipsregs.h>
|
| 24 |
|
| 25 |
|
| 26 | #define CDMM_ACSR_DEVTYPE_SHIFT 24
|
| 27 | #define CDMM_ACSR_DEVTYPE (255ul << CDMM_ACSR_DEVTYPE_SHIFT)
|
| 28 | #define CDMM_ACSR_DEVSIZE_SHIFT 16
|
| 29 | #define CDMM_ACSR_DEVSIZE (31ul << CDMM_ACSR_DEVSIZE_SHIFT)
|
| 30 | #define CDMM_ACSR_DEVREV_SHIFT 12
|
| 31 | #define CDMM_ACSR_DEVREV (15ul << CDMM_ACSR_DEVREV_SHIFT)
|
| 32 | #define CDMM_ACSR_UW (1ul << 3)
|
| 33 | #define CDMM_ACSR_UR (1ul << 2)
|
| 34 | #define CDMM_ACSR_SW (1ul << 1)
|
| 35 | #define CDMM_ACSR_SR (1ul << 0)
|
| 36 |
|
| 37 |
|
| 38 | #define CDMM_DRB_SIZE 64
|
| 39 |
|
| 40 | #define to_mips_cdmm_driver(d) container_of_const(d, struct mips_cdmm_driver, drv)
|
| 41 |
|
| 42 |
|
| 43 | static phys_addr_t mips_cdmm_default_base;
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 | static const struct mips_cdmm_device_id *
|
| 48 | mips_cdmm_lookup(const struct mips_cdmm_device_id *table,
|
| 49 | struct mips_cdmm_device *dev)
|
| 50 | {
|
| 51 | int ret = 0;
|
| 52 |
|
| 53 | for (; table->type; ++table) {
|
| 54 | ret = (dev->type == table->type);
|
| 55 | if (ret)
|
| 56 | break;
|
| 57 | }
|
| 58 |
|
| 59 | return ret ? table : NULL;
|
| 60 | }
|
| 61 |
|
| 62 | static int mips_cdmm_match(struct device *dev, const struct device_driver *drv)
|
| 63 | {
|
| 64 | struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev);
|
| 65 | const struct mips_cdmm_driver *cdrv = to_mips_cdmm_driver(drv);
|
| 66 |
|
| 67 | return mips_cdmm_lookup(cdrv->id_table, cdev) != NULL;
|
| 68 | }
|
| 69 |
|
| 70 | static int mips_cdmm_uevent(const struct device *dev, struct kobj_uevent_env *env)
|
| 71 | {
|
| 72 | const struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev);
|
| 73 | int retval = 0;
|
| 74 |
|
| 75 | retval = add_uevent_var(env, "CDMM_CPU=%u", cdev->cpu);
|
| 76 | if (retval)
|
| 77 | return retval;
|
| 78 |
|
| 79 | retval = add_uevent_var(env, "CDMM_TYPE=0x%02x", cdev->type);
|
| 80 | if (retval)
|
| 81 | return retval;
|
| 82 |
|
| 83 | retval = add_uevent_var(env, "CDMM_REV=%u", cdev->rev);
|
| 84 | if (retval)
|
| 85 | return retval;
|
| 86 |
|
| 87 | retval = add_uevent_var(env, "MODALIAS=mipscdmm:t%02X", cdev->type);
|
| 88 | return retval;
|
| 89 | }
|
| 90 |
|
| 91 |
|
| 92 |
|
| 93 | #define CDMM_ATTR(name, fmt, arg...) \
|
| 94 | static ssize_t name##_show(struct device *_dev, \
|
| 95 | struct device_attribute *attr, char *buf) \
|
| 96 | { \
|
| 97 | struct mips_cdmm_device *dev = to_mips_cdmm_device(_dev); \
|
| 98 | return sprintf(buf, fmt, arg); \
|
| 99 | } \
|
| 100 | static DEVICE_ATTR_RO(name);
|
| 101 |
|
| 102 | CDMM_ATTR(cpu, "%u\n", dev->cpu);
|
| 103 | CDMM_ATTR(type, "0x%02x\n", dev->type);
|
| 104 | CDMM_ATTR(revision, "%u\n", dev->rev);
|
| 105 | CDMM_ATTR(modalias, "mipscdmm:t%02X\n", dev->type);
|
| 106 | CDMM_ATTR(resource, "\t%016llx\t%016llx\t%016lx\n",
|
| 107 | (unsigned long long)dev->res.start,
|
| 108 | (unsigned long long)dev->res.end,
|
| 109 | dev->res.flags);
|
| 110 |
|
| 111 | static struct attribute *mips_cdmm_dev_attrs[] = {
|
| 112 | &dev_attr_cpu.attr,
|
| 113 | &dev_attr_type.attr,
|
| 114 | &dev_attr_revision.attr,
|
| 115 | &dev_attr_modalias.attr,
|
| 116 | &dev_attr_resource.attr,
|
| 117 | NULL,
|
| 118 | };
|
| 119 | ATTRIBUTE_GROUPS(mips_cdmm_dev);
|
| 120 |
|
| 121 | const struct bus_type mips_cdmm_bustype = {
|
| 122 | .name = "cdmm",
|
| 123 | .dev_groups = mips_cdmm_dev_groups,
|
| 124 | .match = mips_cdmm_match,
|
| 125 | .uevent = mips_cdmm_uevent,
|
| 126 | };
|
| 127 | EXPORT_SYMBOL_GPL(mips_cdmm_bustype);
|
| 128 |
|
| 129 | |
| 130 | * Standard driver callback helpers.
|
| 131 | *
|
| 132 | * All the CDMM driver callbacks need to be executed on the appropriate CPU from
|
| 133 | * workqueues. For the standard driver callbacks we need a work function
|
| 134 | * (mips_cdmm_{void,int}_work()) to do the actual call from the right CPU, and a
|
| 135 | * wrapper function (generated with BUILD_PERCPU_HELPER) to arrange for the work
|
| 136 | * function to be called on that CPU.
|
| 137 |
|
| 138 |
|
| 139 | |
| 140 | * struct mips_cdmm_work_dev - Data for per-device call work.
|
| 141 | * @fn: CDMM driver callback function to call for the device.
|
| 142 | * @dev: CDMM device to pass to @fn.
|
| 143 |
|
| 144 | struct mips_cdmm_work_dev {
|
| 145 | void *fn;
|
| 146 | struct mips_cdmm_device *dev;
|
| 147 | };
|
| 148 |
|
| 149 | |
| 150 | * mips_cdmm_void_work() - Call a void returning CDMM driver callback.
|
| 151 | * @data: struct mips_cdmm_work_dev pointer.
|
| 152 | *
|
| 153 | * A work_on_cpu() callback function to call an arbitrary CDMM driver callback
|
| 154 | * function which doesn't return a value.
|
| 155 |
|
| 156 | static long mips_cdmm_void_work(void *data)
|
| 157 | {
|
| 158 | struct mips_cdmm_work_dev *work = data;
|
| 159 | void (*fn)(struct mips_cdmm_device *) = work->fn;
|
| 160 |
|
| 161 | fn(work->dev);
|
| 162 | return 0;
|
| 163 | }
|
| 164 |
|
| 165 | |
| 166 | * mips_cdmm_int_work() - Call an int returning CDMM driver callback.
|
| 167 | * @data: struct mips_cdmm_work_dev pointer.
|
| 168 | *
|
| 169 | * A work_on_cpu() callback function to call an arbitrary CDMM driver callback
|
| 170 | * function which returns an int.
|
| 171 |
|
| 172 | static long mips_cdmm_int_work(void *data)
|
| 173 | {
|
| 174 | struct mips_cdmm_work_dev *work = data;
|
| 175 | int (*fn)(struct mips_cdmm_device *) = work->fn;
|
| 176 |
|
| 177 | return fn(work->dev);
|
| 178 | }
|
| 179 |
|
| 180 | #define _BUILD_RET_void
|
| 181 | #define _BUILD_RET_int return
|
| 182 |
|
| 183 | |
| 184 | * BUILD_PERCPU_HELPER() - Helper to call a CDMM driver callback on right CPU.
|
| 185 | * @_ret: Return type (void or int).
|
| 186 | * @_name: Name of CDMM driver callback function.
|
| 187 | *
|
| 188 | * Generates a specific device callback function to call a CDMM driver callback
|
| 189 | * function on the appropriate CPU for the device, and if applicable return the
|
| 190 | * result.
|
| 191 |
|
| 192 | #define BUILD_PERCPU_HELPER(_ret, _name) \
|
| 193 | static _ret mips_cdmm_##_name(struct device *dev) \
|
| 194 | { \
|
| 195 | struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev); \
|
| 196 | struct mips_cdmm_driver *cdrv = to_mips_cdmm_driver(dev->driver); \
|
| 197 | struct mips_cdmm_work_dev work = { \
|
| 198 | .fn = cdrv->_name, \
|
| 199 | .dev = cdev, \
|
| 200 | }; \
|
| 201 | \
|
| 202 | _BUILD_RET_##_ret work_on_cpu(cdev->cpu, \
|
| 203 | mips_cdmm_##_ret##_work, &work); \
|
| 204 | }
|
| 205 |
|
| 206 |
|
| 207 | BUILD_PERCPU_HELPER(int, probe)
|
| 208 | BUILD_PERCPU_HELPER(int, remove)
|
| 209 | BUILD_PERCPU_HELPER(void, shutdown)
|
| 210 |
|
| 211 |
|
| 212 |
|
| 213 |
|
| 214 | |
| 215 | * mips_cdmm_driver_register() - Register a CDMM driver.
|
| 216 | * @drv: CDMM driver information.
|
| 217 | *
|
| 218 | * Register a CDMM driver with the CDMM subsystem. The driver will be informed
|
| 219 | * of matching devices which are discovered.
|
| 220 | *
|
| 221 | * Returns: 0 on success.
|
| 222 |
|
| 223 | int mips_cdmm_driver_register(struct mips_cdmm_driver *drv)
|
| 224 | {
|
| 225 | drv->drv.bus = &mips_cdmm_bustype;
|
| 226 |
|
| 227 | if (drv->probe)
|
| 228 | drv->drv.probe = mips_cdmm_probe;
|
| 229 | if (drv->remove)
|
| 230 | drv->drv.remove = mips_cdmm_remove;
|
| 231 | if (drv->shutdown)
|
| 232 | drv->drv.shutdown = mips_cdmm_shutdown;
|
| 233 |
|
| 234 | return driver_register(&drv->drv);
|
| 235 | }
|
| 236 | EXPORT_SYMBOL_GPL(mips_cdmm_driver_register);
|
| 237 |
|
| 238 | |
| 239 | * mips_cdmm_driver_unregister() - Unregister a CDMM driver.
|
| 240 | * @drv: CDMM driver information.
|
| 241 | *
|
| 242 | * Unregister a CDMM driver from the CDMM subsystem.
|
| 243 |
|
| 244 | void mips_cdmm_driver_unregister(struct mips_cdmm_driver *drv)
|
| 245 | {
|
| 246 | driver_unregister(&drv->drv);
|
| 247 | }
|
| 248 | EXPORT_SYMBOL_GPL(mips_cdmm_driver_unregister);
|
| 249 |
|
| 250 |
|
| 251 |
|
| 252 |
|
| 253 | |
| 254 | * struct mips_cdmm_bus - Info about CDMM bus.
|
| 255 | * @phys: Physical address at which it is mapped.
|
| 256 | * @regs: Virtual address where registers can be accessed.
|
| 257 | * @drbs: Total number of DRBs.
|
| 258 | * @drbs_reserved: Number of DRBs reserved.
|
| 259 | * @discovered: Whether the devices on the bus have been discovered yet.
|
| 260 | * @offline: Whether the CDMM bus is going offline (or very early
|
| 261 | * coming back online), in which case it should be
|
| 262 | * reconfigured each time.
|
| 263 |
|
| 264 | struct mips_cdmm_bus {
|
| 265 | phys_addr_t phys;
|
| 266 | void __iomem *regs;
|
| 267 | unsigned int drbs;
|
| 268 | unsigned int drbs_reserved;
|
| 269 | bool discovered;
|
| 270 | bool offline;
|
| 271 | };
|
| 272 |
|
| 273 | static struct mips_cdmm_bus mips_cdmm_boot_bus;
|
| 274 | static DEFINE_PER_CPU(struct mips_cdmm_bus *, mips_cdmm_buses);
|
| 275 | static atomic_t mips_cdmm_next_id = ATOMIC_INIT(-1);
|
| 276 |
|
| 277 | |
| 278 | * mips_cdmm_get_bus() - Get the per-CPU CDMM bus information.
|
| 279 | *
|
| 280 | * Get information about the per-CPU CDMM bus, if the bus is present.
|
| 281 | *
|
| 282 | * The caller must prevent migration to another CPU, either by disabling
|
| 283 | * pre-emption or by running from a pinned kernel thread.
|
| 284 | *
|
| 285 | * Returns: Pointer to CDMM bus information for the current CPU.
|
| 286 | * May return ERR_PTR(-errno) in case of error, so check with
|
| 287 | * IS_ERR().
|
| 288 |
|
| 289 | static struct mips_cdmm_bus *mips_cdmm_get_bus(void)
|
| 290 | {
|
| 291 | struct mips_cdmm_bus *bus, **bus_p;
|
| 292 | unsigned long flags;
|
| 293 | unsigned int cpu;
|
| 294 |
|
| 295 | if (!cpu_has_cdmm)
|
| 296 | return ERR_PTR(-ENODEV);
|
| 297 |
|
| 298 | cpu = smp_processor_id();
|
| 299 |
|
| 300 | if (cpu == 0)
|
| 301 | return &mips_cdmm_boot_bus;
|
| 302 |
|
| 303 |
|
| 304 | bus_p = per_cpu_ptr(&mips_cdmm_buses, cpu);
|
| 305 | local_irq_save(flags);
|
| 306 | bus = *bus_p;
|
| 307 |
|
| 308 | if (unlikely(!bus)) {
|
| 309 | bus = kzalloc_obj(*bus, GFP_ATOMIC);
|
| 310 | if (unlikely(!bus))
|
| 311 | bus = ERR_PTR(-ENOMEM);
|
| 312 | else
|
| 313 | *bus_p = bus;
|
| 314 | }
|
| 315 | local_irq_restore(flags);
|
| 316 | return bus;
|
| 317 | }
|
| 318 |
|
| 319 | |
| 320 | * mips_cdmm_cur_base() - Find current physical base address of CDMM region.
|
| 321 | *
|
| 322 | * Returns: Physical base address of CDMM region according to cdmmbase CP0
|
| 323 | * register, or 0 if the CDMM region is disabled.
|
| 324 |
|
| 325 | static phys_addr_t mips_cdmm_cur_base(void)
|
| 326 | {
|
| 327 | unsigned long cdmmbase = read_c0_cdmmbase();
|
| 328 |
|
| 329 | if (!(cdmmbase & MIPS_CDMMBASE_EN))
|
| 330 | return 0;
|
| 331 |
|
| 332 | return (cdmmbase >> MIPS_CDMMBASE_ADDR_SHIFT)
|
| 333 | << MIPS_CDMMBASE_ADDR_START;
|
| 334 | }
|
| 335 |
|
| 336 | |
| 337 | * mips_cdmm_phys_base() - Choose a physical base address for CDMM region.
|
| 338 | *
|
| 339 | * Picking a suitable physical address at which to map the CDMM region is
|
| 340 | * platform specific, so this weak function can be overridden by platform
|
| 341 | * code to pick a suitable value if none is configured by the bootloader.
|
| 342 | * By default this method tries to find a CDMM-specific node in the system
|
| 343 | * dtb. Note that this won't work for early serial console.
|
| 344 |
|
| 345 | phys_addr_t __weak mips_cdmm_phys_base(void)
|
| 346 | {
|
| 347 | struct device_node *np;
|
| 348 | struct resource res;
|
| 349 | int err;
|
| 350 |
|
| 351 | np = of_find_compatible_node(NULL, NULL, "mti,mips-cdmm");
|
| 352 | if (np) {
|
| 353 | err = of_address_to_resource(np, 0, &res);
|
| 354 | of_node_put(np);
|
| 355 | if (!err)
|
| 356 | return res.start;
|
| 357 | }
|
| 358 |
|
| 359 | return 0;
|
| 360 | }
|
| 361 |
|
| 362 | |
| 363 | * mips_cdmm_setup() - Ensure the CDMM bus is initialised and usable.
|
| 364 | * @bus: Pointer to bus information for current CPU.
|
| 365 | * IS_ERR(bus) is checked, so no need for caller to check.
|
| 366 | *
|
| 367 | * The caller must prevent migration to another CPU, either by disabling
|
| 368 | * pre-emption or by running from a pinned kernel thread.
|
| 369 | *
|
| 370 | * Returns 0 on success, -errno on failure.
|
| 371 |
|
| 372 | static int mips_cdmm_setup(struct mips_cdmm_bus *bus)
|
| 373 | {
|
| 374 | unsigned long cdmmbase, flags;
|
| 375 | int ret = 0;
|
| 376 |
|
| 377 | if (IS_ERR(bus))
|
| 378 | return PTR_ERR(bus);
|
| 379 |
|
| 380 | local_irq_save(flags);
|
| 381 |
|
| 382 | if (bus->offline) {
|
| 383 |
|
| 384 | if (bus->phys == mips_cdmm_cur_base())
|
| 385 | goto out;
|
| 386 | |
| 387 | * The CDMM region isn't set up as expected, so it needs
|
| 388 | * reconfiguring, but then we can stop checking it.
|
| 389 |
|
| 390 | bus->offline = false;
|
| 391 | } else if (bus->phys > 1) {
|
| 392 | goto out;
|
| 393 | }
|
| 394 |
|
| 395 |
|
| 396 | if (!bus->phys)
|
| 397 | bus->phys = mips_cdmm_cur_base();
|
| 398 |
|
| 399 | if (!bus->phys)
|
| 400 | bus->phys = mips_cdmm_phys_base();
|
| 401 |
|
| 402 | if (!bus->phys)
|
| 403 | bus->phys = mips_cdmm_default_base;
|
| 404 |
|
| 405 | if (!bus->phys) {
|
| 406 | bus->phys = 1;
|
| 407 | |
| 408 | * If you hit this, either your bootloader needs to set up the
|
| 409 | * CDMM on the boot CPU, or else you need to implement
|
| 410 | * mips_cdmm_phys_base() for your platform (see asm/cdmm.h).
|
| 411 |
|
| 412 | pr_err("cdmm%u: Failed to choose a physical base\n",
|
| 413 | smp_processor_id());
|
| 414 | }
|
| 415 |
|
| 416 | if (bus->phys == 1) {
|
| 417 | ret = -ENOMEM;
|
| 418 | goto out;
|
| 419 | }
|
| 420 |
|
| 421 | mips_cdmm_default_base = bus->phys;
|
| 422 |
|
| 423 | pr_debug("cdmm%u: Enabling CDMM region at %pa\n",
|
| 424 | smp_processor_id(), &bus->phys);
|
| 425 |
|
| 426 |
|
| 427 | cdmmbase = read_c0_cdmmbase();
|
| 428 | cdmmbase &= (1ul << MIPS_CDMMBASE_ADDR_SHIFT) - 1;
|
| 429 | cdmmbase |= (bus->phys >> MIPS_CDMMBASE_ADDR_START)
|
| 430 | << MIPS_CDMMBASE_ADDR_SHIFT;
|
| 431 | cdmmbase |= MIPS_CDMMBASE_EN;
|
| 432 | write_c0_cdmmbase(cdmmbase);
|
| 433 | tlbw_use_hazard();
|
| 434 |
|
| 435 | bus->regs = (void __iomem *)CKSEG1ADDR(bus->phys);
|
| 436 | bus->drbs = 1 + ((cdmmbase & MIPS_CDMMBASE_SIZE) >>
|
| 437 | MIPS_CDMMBASE_SIZE_SHIFT);
|
| 438 | bus->drbs_reserved = !!(cdmmbase & MIPS_CDMMBASE_CI);
|
| 439 |
|
| 440 | out:
|
| 441 | local_irq_restore(flags);
|
| 442 | return ret;
|
| 443 | }
|
| 444 |
|
| 445 | |
| 446 | * mips_cdmm_early_probe() - Minimally probe for a specific device on CDMM.
|
| 447 | * @dev_type: CDMM type code to look for.
|
| 448 | *
|
| 449 | * Minimally configure the in-CPU Common Device Memory Map (CDMM) and look for a
|
| 450 | * specific device. This can be used to find a device very early in boot for
|
| 451 | * example to configure an early FDC console device.
|
| 452 | *
|
| 453 | * The caller must prevent migration to another CPU, either by disabling
|
| 454 | * pre-emption or by running from a pinned kernel thread.
|
| 455 | *
|
| 456 | * Returns: MMIO pointer to device memory. The caller can read the ACSR
|
| 457 | * register to find more information about the device (such as the
|
| 458 | * version number or the number of blocks).
|
| 459 | * May return IOMEM_ERR_PTR(-errno) in case of error, so check with
|
| 460 | * IS_ERR().
|
| 461 |
|
| 462 | void __iomem *mips_cdmm_early_probe(unsigned int dev_type)
|
| 463 | {
|
| 464 | struct mips_cdmm_bus *bus;
|
| 465 | void __iomem *cdmm;
|
| 466 | u32 acsr;
|
| 467 | unsigned int drb, type, size;
|
| 468 | int err;
|
| 469 |
|
| 470 | if (WARN_ON(!dev_type))
|
| 471 | return IOMEM_ERR_PTR(-ENODEV);
|
| 472 |
|
| 473 | bus = mips_cdmm_get_bus();
|
| 474 | err = mips_cdmm_setup(bus);
|
| 475 | if (err)
|
| 476 | return IOMEM_ERR_PTR(err);
|
| 477 |
|
| 478 |
|
| 479 | drb = bus->drbs_reserved;
|
| 480 | cdmm = bus->regs;
|
| 481 |
|
| 482 |
|
| 483 | for (; drb < bus->drbs; drb += size + 1) {
|
| 484 | acsr = __raw_readl(cdmm + drb * CDMM_DRB_SIZE);
|
| 485 | type = (acsr & CDMM_ACSR_DEVTYPE) >> CDMM_ACSR_DEVTYPE_SHIFT;
|
| 486 | if (type == dev_type)
|
| 487 | return cdmm + drb * CDMM_DRB_SIZE;
|
| 488 | size = (acsr & CDMM_ACSR_DEVSIZE) >> CDMM_ACSR_DEVSIZE_SHIFT;
|
| 489 | }
|
| 490 |
|
| 491 | return IOMEM_ERR_PTR(-ENODEV);
|
| 492 | }
|
| 493 | EXPORT_SYMBOL_GPL(mips_cdmm_early_probe);
|
| 494 |
|
| 495 | |
| 496 | * mips_cdmm_release() - Release a removed CDMM device.
|
| 497 | * @dev: Device object
|
| 498 | *
|
| 499 | * Clean up the struct mips_cdmm_device for an unused CDMM device. This is
|
| 500 | * called automatically by the driver core when a device is removed.
|
| 501 |
|
| 502 | static void mips_cdmm_release(struct device *dev)
|
| 503 | {
|
| 504 | struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev);
|
| 505 |
|
| 506 | kfree(cdev);
|
| 507 | }
|
| 508 |
|
| 509 | |
| 510 | * mips_cdmm_bus_discover() - Discover the devices on the CDMM bus.
|
| 511 | * @bus: CDMM bus information, must already be set up.
|
| 512 |
|
| 513 | static void mips_cdmm_bus_discover(struct mips_cdmm_bus *bus)
|
| 514 | {
|
| 515 | void __iomem *cdmm;
|
| 516 | u32 acsr;
|
| 517 | unsigned int drb, type, size, rev;
|
| 518 | struct mips_cdmm_device *dev;
|
| 519 | unsigned int cpu = smp_processor_id();
|
| 520 | int ret = 0;
|
| 521 | int id = 0;
|
| 522 |
|
| 523 |
|
| 524 | drb = bus->drbs_reserved;
|
| 525 | cdmm = bus->regs;
|
| 526 |
|
| 527 |
|
| 528 | bus->discovered = true;
|
| 529 | pr_info("cdmm%u discovery (%u blocks)\n", cpu, bus->drbs);
|
| 530 | for (; drb < bus->drbs; drb += size + 1) {
|
| 531 | acsr = __raw_readl(cdmm + drb * CDMM_DRB_SIZE);
|
| 532 | type = (acsr & CDMM_ACSR_DEVTYPE) >> CDMM_ACSR_DEVTYPE_SHIFT;
|
| 533 | size = (acsr & CDMM_ACSR_DEVSIZE) >> CDMM_ACSR_DEVSIZE_SHIFT;
|
| 534 | rev = (acsr & CDMM_ACSR_DEVREV) >> CDMM_ACSR_DEVREV_SHIFT;
|
| 535 |
|
| 536 | if (!type)
|
| 537 | continue;
|
| 538 |
|
| 539 | pr_info("cdmm%u-%u: @%u (%#x..%#x), type 0x%02x, rev %u\n",
|
| 540 | cpu, id, drb, drb * CDMM_DRB_SIZE,
|
| 541 | (drb + size + 1) * CDMM_DRB_SIZE - 1,
|
| 542 | type, rev);
|
| 543 |
|
| 544 | dev = kzalloc_obj(*dev);
|
| 545 | if (!dev)
|
| 546 | break;
|
| 547 |
|
| 548 | dev->cpu = cpu;
|
| 549 | dev->res.start = bus->phys + drb * CDMM_DRB_SIZE;
|
| 550 | dev->res.end = bus->phys +
|
| 551 | (drb + size + 1) * CDMM_DRB_SIZE - 1;
|
| 552 | dev->res.flags = IORESOURCE_MEM;
|
| 553 | dev->type = type;
|
| 554 | dev->rev = rev;
|
| 555 | dev->dev.parent = get_cpu_device(cpu);
|
| 556 | dev->dev.bus = &mips_cdmm_bustype;
|
| 557 | dev->dev.id = atomic_inc_return(&mips_cdmm_next_id);
|
| 558 | dev->dev.release = mips_cdmm_release;
|
| 559 |
|
| 560 | dev_set_name(&dev->dev, "cdmm%u-%u", cpu, id);
|
| 561 | ++id;
|
| 562 | ret = device_register(&dev->dev);
|
| 563 | if (ret)
|
| 564 | put_device(&dev->dev);
|
| 565 | }
|
| 566 | }
|
| 567 |
|
| 568 |
|
| 569 | |
| 570 | * CPU hotplug and initialisation
|
| 571 | *
|
| 572 | * All the CDMM driver callbacks need to be executed on the appropriate CPU from
|
| 573 | * workqueues. For the CPU callbacks, they need to be called for all devices on
|
| 574 | * that CPU, so the work function calls bus_for_each_dev, using a helper
|
| 575 | * (generated with BUILD_PERDEV_HELPER) to call the driver callback if the
|
| 576 | * device's CPU matches.
|
| 577 |
|
| 578 |
|
| 579 | |
| 580 | * BUILD_PERDEV_HELPER() - Helper to call a CDMM driver callback if CPU matches.
|
| 581 | * @_name: Name of CDMM driver callback function.
|
| 582 | *
|
| 583 | * Generates a bus_for_each_dev callback function to call a specific CDMM driver
|
| 584 | * callback function for the device if the device's CPU matches that pointed to
|
| 585 | * by the data argument.
|
| 586 | *
|
| 587 | * This is used for informing drivers for all devices on a given CPU of some
|
| 588 | * event (such as the CPU going online/offline).
|
| 589 | *
|
| 590 | * It is expected to already be called from the appropriate CPU.
|
| 591 |
|
| 592 | #define BUILD_PERDEV_HELPER(_name) \
|
| 593 | static int mips_cdmm_##_name##_helper(struct device *dev, void *data) \
|
| 594 | { \
|
| 595 | struct mips_cdmm_device *cdev = to_mips_cdmm_device(dev); \
|
| 596 | struct mips_cdmm_driver *cdrv; \
|
| 597 | unsigned int cpu = *(unsigned int *)data; \
|
| 598 | \
|
| 599 | if (cdev->cpu != cpu || !dev->driver) \
|
| 600 | return 0; \
|
| 601 | \
|
| 602 | cdrv = to_mips_cdmm_driver(dev->driver); \
|
| 603 | if (!cdrv->_name) \
|
| 604 | return 0; \
|
| 605 | return cdrv->_name(cdev); \
|
| 606 | }
|
| 607 |
|
| 608 |
|
| 609 | BUILD_PERDEV_HELPER(cpu_down)
|
| 610 | BUILD_PERDEV_HELPER(cpu_up)
|
| 611 |
|
| 612 | |
| 613 | * mips_cdmm_cpu_down_prep() - Callback for CPUHP DOWN_PREP:
|
| 614 | * Tear down the CDMM bus.
|
| 615 | * @cpu: unsigned int CPU number.
|
| 616 | *
|
| 617 | * This function is executed on the hotplugged CPU and calls the CDMM
|
| 618 | * driver cpu_down callback for all devices on that CPU.
|
| 619 |
|
| 620 | static int mips_cdmm_cpu_down_prep(unsigned int cpu)
|
| 621 | {
|
| 622 | struct mips_cdmm_bus *bus;
|
| 623 | long ret;
|
| 624 |
|
| 625 |
|
| 626 | ret = bus_for_each_dev(&mips_cdmm_bustype, NULL, &cpu,
|
| 627 | mips_cdmm_cpu_down_helper);
|
| 628 |
|
| 629 | |
| 630 | * While bus is offline, each use of it should reconfigure it just in
|
| 631 | * case it is first use when coming back online again.
|
| 632 |
|
| 633 | bus = mips_cdmm_get_bus();
|
| 634 | if (!IS_ERR(bus))
|
| 635 | bus->offline = true;
|
| 636 |
|
| 637 | return ret;
|
| 638 | }
|
| 639 |
|
| 640 | |
| 641 | * mips_cdmm_cpu_online() - Callback for CPUHP ONLINE: Bring up the CDMM bus.
|
| 642 | * @cpu: unsigned int CPU number.
|
| 643 | *
|
| 644 | * This work_on_cpu callback function is executed on a given CPU to discover
|
| 645 | * CDMM devices on that CPU, or to call the CDMM driver cpu_up callback for all
|
| 646 | * devices already discovered on that CPU.
|
| 647 | *
|
| 648 | * It is used as work_on_cpu callback function during
|
| 649 | * initialisation. When CPUs are brought online the function is
|
| 650 | * invoked directly on the hotplugged CPU.
|
| 651 |
|
| 652 | static int mips_cdmm_cpu_online(unsigned int cpu)
|
| 653 | {
|
| 654 | struct mips_cdmm_bus *bus;
|
| 655 | long ret;
|
| 656 |
|
| 657 | bus = mips_cdmm_get_bus();
|
| 658 | ret = mips_cdmm_setup(bus);
|
| 659 | if (ret)
|
| 660 | return ret;
|
| 661 |
|
| 662 |
|
| 663 | bus->offline = false;
|
| 664 |
|
| 665 | if (!bus->discovered)
|
| 666 | mips_cdmm_bus_discover(bus);
|
| 667 | else
|
| 668 |
|
| 669 | ret = bus_for_each_dev(&mips_cdmm_bustype, NULL, &cpu,
|
| 670 | mips_cdmm_cpu_up_helper);
|
| 671 |
|
| 672 | return ret;
|
| 673 | }
|
| 674 |
|
| 675 | |
| 676 | * mips_cdmm_init() - Initialise CDMM bus.
|
| 677 | *
|
| 678 | * Initialise CDMM bus, discover CDMM devices for online CPUs, and arrange for
|
| 679 | * hotplug notifications so the CDMM drivers can be kept up to date.
|
| 680 |
|
| 681 | static int __init mips_cdmm_init(void)
|
| 682 | {
|
| 683 | int ret;
|
| 684 |
|
| 685 |
|
| 686 | ret = bus_register(&mips_cdmm_bustype);
|
| 687 | if (ret)
|
| 688 | return ret;
|
| 689 |
|
| 690 |
|
| 691 | ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "bus/cdmm:online",
|
| 692 | mips_cdmm_cpu_online, mips_cdmm_cpu_down_prep);
|
| 693 | if (ret < 0)
|
| 694 | pr_warn("cdmm: Failed to register CPU notifier\n");
|
| 695 |
|
| 696 | return ret;
|
| 697 | }
|
| 698 | subsys_initcall(mips_cdmm_init);
|