Suman Anna | e88bb8f | 2017-06-13 18:45:12 -0500 | [diff] [blame] | 1 | /* |
| 2 | * TI Keystone DSP remoteproc driver |
| 3 | * |
| 4 | * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/ |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU General Public License |
| 8 | * version 2 as published by the Free Software Foundation. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, but |
| 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | * General Public License for more details. |
| 14 | */ |
| 15 | |
| 16 | #include <linux/module.h> |
| 17 | #include <linux/slab.h> |
| 18 | #include <linux/io.h> |
| 19 | #include <linux/interrupt.h> |
| 20 | #include <linux/platform_device.h> |
| 21 | #include <linux/pm_runtime.h> |
| 22 | #include <linux/workqueue.h> |
| 23 | #include <linux/of_address.h> |
| 24 | #include <linux/of_reserved_mem.h> |
| 25 | #include <linux/of_gpio.h> |
| 26 | #include <linux/regmap.h> |
| 27 | #include <linux/mfd/syscon.h> |
| 28 | #include <linux/remoteproc.h> |
| 29 | #include <linux/reset.h> |
| 30 | |
| 31 | #include "remoteproc_internal.h" |
| 32 | |
| 33 | #define KEYSTONE_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1) |
| 34 | |
| 35 | /** |
| 36 | * struct keystone_rproc_mem - internal memory structure |
| 37 | * @cpu_addr: MPU virtual address of the memory region |
| 38 | * @bus_addr: Bus address used to access the memory region |
| 39 | * @dev_addr: Device address of the memory region from DSP view |
| 40 | * @size: Size of the memory region |
| 41 | */ |
| 42 | struct keystone_rproc_mem { |
| 43 | void __iomem *cpu_addr; |
| 44 | phys_addr_t bus_addr; |
| 45 | u32 dev_addr; |
| 46 | size_t size; |
| 47 | }; |
| 48 | |
| 49 | /** |
| 50 | * struct keystone_rproc - keystone remote processor driver structure |
| 51 | * @dev: cached device pointer |
| 52 | * @rproc: remoteproc device handle |
| 53 | * @mem: internal memory regions data |
| 54 | * @num_mems: number of internal memory regions |
| 55 | * @dev_ctrl: device control regmap handle |
| 56 | * @reset: reset control handle |
| 57 | * @boot_offset: boot register offset in @dev_ctrl regmap |
| 58 | * @irq_ring: irq entry for vring |
| 59 | * @irq_fault: irq entry for exception |
| 60 | * @kick_gpio: gpio used for virtio kicks |
| 61 | * @workqueue: workqueue for processing virtio interrupts |
| 62 | */ |
| 63 | struct keystone_rproc { |
| 64 | struct device *dev; |
| 65 | struct rproc *rproc; |
| 66 | struct keystone_rproc_mem *mem; |
| 67 | int num_mems; |
| 68 | struct regmap *dev_ctrl; |
| 69 | struct reset_control *reset; |
| 70 | u32 boot_offset; |
| 71 | int irq_ring; |
| 72 | int irq_fault; |
| 73 | int kick_gpio; |
| 74 | struct work_struct workqueue; |
| 75 | }; |
| 76 | |
| 77 | /* Put the DSP processor into reset */ |
| 78 | static void keystone_rproc_dsp_reset(struct keystone_rproc *ksproc) |
| 79 | { |
| 80 | reset_control_assert(ksproc->reset); |
| 81 | } |
| 82 | |
| 83 | /* Configure the boot address and boot the DSP processor */ |
| 84 | static int keystone_rproc_dsp_boot(struct keystone_rproc *ksproc, u32 boot_addr) |
| 85 | { |
| 86 | int ret; |
| 87 | |
| 88 | if (boot_addr & (SZ_1K - 1)) { |
| 89 | dev_err(ksproc->dev, "invalid boot address 0x%x, must be aligned on a 1KB boundary\n", |
| 90 | boot_addr); |
| 91 | return -EINVAL; |
| 92 | } |
| 93 | |
| 94 | ret = regmap_write(ksproc->dev_ctrl, ksproc->boot_offset, boot_addr); |
| 95 | if (ret) { |
| 96 | dev_err(ksproc->dev, "regmap_write of boot address failed, status = %d\n", |
| 97 | ret); |
| 98 | return ret; |
| 99 | } |
| 100 | |
| 101 | reset_control_deassert(ksproc->reset); |
| 102 | |
| 103 | return 0; |
| 104 | } |
| 105 | |
| 106 | /* |
| 107 | * Process the remoteproc exceptions |
| 108 | * |
| 109 | * The exception reporting on Keystone DSP remote processors is very simple |
| 110 | * compared to the equivalent processors on the OMAP family, it is notified |
| 111 | * through a software-designed specific interrupt source in the IPC interrupt |
| 112 | * generation register. |
| 113 | * |
| 114 | * This function just invokes the rproc_report_crash to report the exception |
| 115 | * to the remoteproc driver core, to trigger a recovery. |
| 116 | */ |
| 117 | static irqreturn_t keystone_rproc_exception_interrupt(int irq, void *dev_id) |
| 118 | { |
| 119 | struct keystone_rproc *ksproc = dev_id; |
| 120 | |
| 121 | rproc_report_crash(ksproc->rproc, RPROC_FATAL_ERROR); |
| 122 | |
| 123 | return IRQ_HANDLED; |
| 124 | } |
| 125 | |
| 126 | /* |
| 127 | * Main virtqueue message workqueue function |
| 128 | * |
| 129 | * This function is executed upon scheduling of the keystone remoteproc |
| 130 | * driver's workqueue. The workqueue is scheduled by the vring ISR handler. |
| 131 | * |
| 132 | * There is no payload message indicating the virtqueue index as is the |
| 133 | * case with mailbox-based implementations on OMAP family. As such, this |
| 134 | * handler processes both the Tx and Rx virtqueue indices on every invocation. |
| 135 | * The rproc_vq_interrupt function can detect if there are new unprocessed |
| 136 | * messages or not (returns IRQ_NONE vs IRQ_HANDLED), but there is no need |
| 137 | * to check for these return values. The index 0 triggering will process all |
| 138 | * pending Rx buffers, and the index 1 triggering will process all newly |
| 139 | * available Tx buffers and will wakeup any potentially blocked senders. |
| 140 | * |
| 141 | * NOTE: |
| 142 | * 1. A payload could be added by using some of the source bits in the |
| 143 | * IPC interrupt generation registers, but this would need additional |
| 144 | * changes to the overall IPC stack, and currently there are no benefits |
| 145 | * of adapting that approach. |
| 146 | * 2. The current logic is based on an inherent design assumption of supporting |
| 147 | * only 2 vrings, but this can be changed if needed. |
| 148 | */ |
| 149 | static void handle_event(struct work_struct *work) |
| 150 | { |
| 151 | struct keystone_rproc *ksproc = |
| 152 | container_of(work, struct keystone_rproc, workqueue); |
| 153 | |
| 154 | rproc_vq_interrupt(ksproc->rproc, 0); |
| 155 | rproc_vq_interrupt(ksproc->rproc, 1); |
| 156 | } |
| 157 | |
| 158 | /* |
| 159 | * Interrupt handler for processing vring kicks from remote processor |
| 160 | */ |
| 161 | static irqreturn_t keystone_rproc_vring_interrupt(int irq, void *dev_id) |
| 162 | { |
| 163 | struct keystone_rproc *ksproc = dev_id; |
| 164 | |
| 165 | schedule_work(&ksproc->workqueue); |
| 166 | |
| 167 | return IRQ_HANDLED; |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | * Power up the DSP remote processor. |
| 172 | * |
| 173 | * This function will be invoked only after the firmware for this rproc |
| 174 | * was loaded, parsed successfully, and all of its resource requirements |
| 175 | * were met. |
| 176 | */ |
| 177 | static int keystone_rproc_start(struct rproc *rproc) |
| 178 | { |
| 179 | struct keystone_rproc *ksproc = rproc->priv; |
| 180 | int ret; |
| 181 | |
| 182 | INIT_WORK(&ksproc->workqueue, handle_event); |
| 183 | |
| 184 | ret = request_irq(ksproc->irq_ring, keystone_rproc_vring_interrupt, 0, |
| 185 | dev_name(ksproc->dev), ksproc); |
| 186 | if (ret) { |
| 187 | dev_err(ksproc->dev, "failed to enable vring interrupt, ret = %d\n", |
| 188 | ret); |
| 189 | goto out; |
| 190 | } |
| 191 | |
| 192 | ret = request_irq(ksproc->irq_fault, keystone_rproc_exception_interrupt, |
| 193 | 0, dev_name(ksproc->dev), ksproc); |
| 194 | if (ret) { |
| 195 | dev_err(ksproc->dev, "failed to enable exception interrupt, ret = %d\n", |
| 196 | ret); |
| 197 | goto free_vring_irq; |
| 198 | } |
| 199 | |
| 200 | ret = keystone_rproc_dsp_boot(ksproc, rproc->bootaddr); |
| 201 | if (ret) |
| 202 | goto free_exc_irq; |
| 203 | |
| 204 | return 0; |
| 205 | |
| 206 | free_exc_irq: |
| 207 | free_irq(ksproc->irq_fault, ksproc); |
| 208 | free_vring_irq: |
| 209 | free_irq(ksproc->irq_ring, ksproc); |
| 210 | flush_work(&ksproc->workqueue); |
| 211 | out: |
| 212 | return ret; |
| 213 | } |
| 214 | |
| 215 | /* |
| 216 | * Stop the DSP remote processor. |
| 217 | * |
| 218 | * This function puts the DSP processor into reset, and finishes processing |
| 219 | * of any pending messages. |
| 220 | */ |
| 221 | static int keystone_rproc_stop(struct rproc *rproc) |
| 222 | { |
| 223 | struct keystone_rproc *ksproc = rproc->priv; |
| 224 | |
| 225 | keystone_rproc_dsp_reset(ksproc); |
| 226 | free_irq(ksproc->irq_fault, ksproc); |
| 227 | free_irq(ksproc->irq_ring, ksproc); |
| 228 | flush_work(&ksproc->workqueue); |
| 229 | |
| 230 | return 0; |
| 231 | } |
| 232 | |
| 233 | /* |
| 234 | * Kick the remote processor to notify about pending unprocessed messages. |
| 235 | * The vqid usage is not used and is inconsequential, as the kick is performed |
| 236 | * through a simulated GPIO (a bit in an IPC interrupt-triggering register), |
| 237 | * the remote processor is expected to process both its Tx and Rx virtqueues. |
| 238 | */ |
| 239 | static void keystone_rproc_kick(struct rproc *rproc, int vqid) |
| 240 | { |
| 241 | struct keystone_rproc *ksproc = rproc->priv; |
| 242 | |
| 243 | if (WARN_ON(ksproc->kick_gpio < 0)) |
| 244 | return; |
| 245 | |
| 246 | gpio_set_value(ksproc->kick_gpio, 1); |
| 247 | } |
| 248 | |
| 249 | /* |
| 250 | * Custom function to translate a DSP device address (internal RAMs only) to a |
| 251 | * kernel virtual address. The DSPs can access their RAMs at either an internal |
| 252 | * address visible only from a DSP, or at the SoC-level bus address. Both these |
| 253 | * addresses need to be looked through for translation. The translated addresses |
| 254 | * can be used either by the remoteproc core for loading (when using kernel |
| 255 | * remoteproc loader), or by any rpmsg bus drivers. |
| 256 | */ |
| 257 | static void *keystone_rproc_da_to_va(struct rproc *rproc, u64 da, int len) |
| 258 | { |
| 259 | struct keystone_rproc *ksproc = rproc->priv; |
| 260 | void __iomem *va = NULL; |
| 261 | phys_addr_t bus_addr; |
| 262 | u32 dev_addr, offset; |
| 263 | size_t size; |
| 264 | int i; |
| 265 | |
| 266 | if (len <= 0) |
| 267 | return NULL; |
| 268 | |
| 269 | for (i = 0; i < ksproc->num_mems; i++) { |
| 270 | bus_addr = ksproc->mem[i].bus_addr; |
| 271 | dev_addr = ksproc->mem[i].dev_addr; |
| 272 | size = ksproc->mem[i].size; |
| 273 | |
| 274 | if (da < KEYSTONE_RPROC_LOCAL_ADDRESS_MASK) { |
| 275 | /* handle DSP-view addresses */ |
| 276 | if ((da >= dev_addr) && |
| 277 | ((da + len) <= (dev_addr + size))) { |
| 278 | offset = da - dev_addr; |
| 279 | va = ksproc->mem[i].cpu_addr + offset; |
| 280 | break; |
| 281 | } |
| 282 | } else { |
| 283 | /* handle SoC-view addresses */ |
| 284 | if ((da >= bus_addr) && |
| 285 | (da + len) <= (bus_addr + size)) { |
| 286 | offset = da - bus_addr; |
| 287 | va = ksproc->mem[i].cpu_addr + offset; |
| 288 | break; |
| 289 | } |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | return (__force void *)va; |
| 294 | } |
| 295 | |
| 296 | static const struct rproc_ops keystone_rproc_ops = { |
| 297 | .start = keystone_rproc_start, |
| 298 | .stop = keystone_rproc_stop, |
| 299 | .kick = keystone_rproc_kick, |
| 300 | .da_to_va = keystone_rproc_da_to_va, |
| 301 | }; |
| 302 | |
| 303 | static int keystone_rproc_of_get_memories(struct platform_device *pdev, |
| 304 | struct keystone_rproc *ksproc) |
| 305 | { |
| 306 | static const char * const mem_names[] = {"l2sram", "l1pram", "l1dram"}; |
| 307 | struct device *dev = &pdev->dev; |
| 308 | struct resource *res; |
| 309 | int num_mems = 0; |
| 310 | int i; |
| 311 | |
| 312 | num_mems = ARRAY_SIZE(mem_names); |
| 313 | ksproc->mem = devm_kcalloc(ksproc->dev, num_mems, |
| 314 | sizeof(*ksproc->mem), GFP_KERNEL); |
| 315 | if (!ksproc->mem) |
| 316 | return -ENOMEM; |
| 317 | |
| 318 | for (i = 0; i < num_mems; i++) { |
| 319 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| 320 | mem_names[i]); |
| 321 | ksproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res); |
| 322 | if (IS_ERR(ksproc->mem[i].cpu_addr)) { |
| 323 | dev_err(dev, "failed to parse and map %s memory\n", |
| 324 | mem_names[i]); |
| 325 | return PTR_ERR(ksproc->mem[i].cpu_addr); |
| 326 | } |
| 327 | ksproc->mem[i].bus_addr = res->start; |
| 328 | ksproc->mem[i].dev_addr = |
| 329 | res->start & KEYSTONE_RPROC_LOCAL_ADDRESS_MASK; |
| 330 | ksproc->mem[i].size = resource_size(res); |
| 331 | |
| 332 | /* zero out memories to start in a pristine state */ |
| 333 | memset((__force void *)ksproc->mem[i].cpu_addr, 0, |
| 334 | ksproc->mem[i].size); |
| 335 | } |
| 336 | ksproc->num_mems = num_mems; |
| 337 | |
| 338 | return 0; |
| 339 | } |
| 340 | |
| 341 | static int keystone_rproc_of_get_dev_syscon(struct platform_device *pdev, |
| 342 | struct keystone_rproc *ksproc) |
| 343 | { |
| 344 | struct device_node *np = pdev->dev.of_node; |
| 345 | struct device *dev = &pdev->dev; |
| 346 | int ret; |
| 347 | |
| 348 | if (!of_property_read_bool(np, "ti,syscon-dev")) { |
| 349 | dev_err(dev, "ti,syscon-dev property is absent\n"); |
| 350 | return -EINVAL; |
| 351 | } |
| 352 | |
| 353 | ksproc->dev_ctrl = |
| 354 | syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev"); |
| 355 | if (IS_ERR(ksproc->dev_ctrl)) { |
| 356 | ret = PTR_ERR(ksproc->dev_ctrl); |
| 357 | return ret; |
| 358 | } |
| 359 | |
| 360 | if (of_property_read_u32_index(np, "ti,syscon-dev", 1, |
| 361 | &ksproc->boot_offset)) { |
| 362 | dev_err(dev, "couldn't read the boot register offset\n"); |
| 363 | return -EINVAL; |
| 364 | } |
| 365 | |
| 366 | return 0; |
| 367 | } |
| 368 | |
| 369 | static int keystone_rproc_probe(struct platform_device *pdev) |
| 370 | { |
| 371 | struct device *dev = &pdev->dev; |
| 372 | struct device_node *np = dev->of_node; |
| 373 | struct keystone_rproc *ksproc; |
| 374 | struct rproc *rproc; |
| 375 | int dsp_id; |
| 376 | char *fw_name = NULL; |
| 377 | char *template = "keystone-dsp%d-fw"; |
| 378 | int name_len = 0; |
| 379 | int ret = 0; |
| 380 | |
| 381 | if (!np) { |
| 382 | dev_err(dev, "only DT-based devices are supported\n"); |
| 383 | return -ENODEV; |
| 384 | } |
| 385 | |
| 386 | dsp_id = of_alias_get_id(np, "rproc"); |
| 387 | if (dsp_id < 0) { |
| 388 | dev_warn(dev, "device does not have an alias id\n"); |
| 389 | return dsp_id; |
| 390 | } |
| 391 | |
| 392 | /* construct a custom default fw name - subject to change in future */ |
| 393 | name_len = strlen(template); /* assuming a single digit alias */ |
| 394 | fw_name = devm_kzalloc(dev, name_len, GFP_KERNEL); |
| 395 | if (!fw_name) |
| 396 | return -ENOMEM; |
| 397 | snprintf(fw_name, name_len, template, dsp_id); |
| 398 | |
| 399 | rproc = rproc_alloc(dev, dev_name(dev), &keystone_rproc_ops, fw_name, |
| 400 | sizeof(*ksproc)); |
| 401 | if (!rproc) |
| 402 | return -ENOMEM; |
| 403 | |
| 404 | rproc->has_iommu = false; |
| 405 | ksproc = rproc->priv; |
| 406 | ksproc->rproc = rproc; |
| 407 | ksproc->dev = dev; |
| 408 | |
| 409 | ret = keystone_rproc_of_get_dev_syscon(pdev, ksproc); |
| 410 | if (ret) |
| 411 | goto free_rproc; |
| 412 | |
Philipp Zabel | f5f9865 | 2017-07-19 17:26:15 +0200 | [diff] [blame] | 413 | ksproc->reset = devm_reset_control_get_exclusive(dev, NULL); |
Suman Anna | e88bb8f | 2017-06-13 18:45:12 -0500 | [diff] [blame] | 414 | if (IS_ERR(ksproc->reset)) { |
| 415 | ret = PTR_ERR(ksproc->reset); |
| 416 | goto free_rproc; |
| 417 | } |
| 418 | |
| 419 | /* enable clock for accessing DSP internal memories */ |
| 420 | pm_runtime_enable(dev); |
| 421 | ret = pm_runtime_get_sync(dev); |
| 422 | if (ret < 0) { |
| 423 | dev_err(dev, "failed to enable clock, status = %d\n", ret); |
| 424 | pm_runtime_put_noidle(dev); |
| 425 | goto disable_rpm; |
| 426 | } |
| 427 | |
| 428 | ret = keystone_rproc_of_get_memories(pdev, ksproc); |
| 429 | if (ret) |
| 430 | goto disable_clk; |
| 431 | |
| 432 | ksproc->irq_ring = platform_get_irq_byname(pdev, "vring"); |
| 433 | if (ksproc->irq_ring < 0) { |
| 434 | ret = ksproc->irq_ring; |
| 435 | dev_err(dev, "failed to get vring interrupt, status = %d\n", |
| 436 | ret); |
| 437 | goto disable_clk; |
| 438 | } |
| 439 | |
| 440 | ksproc->irq_fault = platform_get_irq_byname(pdev, "exception"); |
| 441 | if (ksproc->irq_fault < 0) { |
| 442 | ret = ksproc->irq_fault; |
| 443 | dev_err(dev, "failed to get exception interrupt, status = %d\n", |
| 444 | ret); |
| 445 | goto disable_clk; |
| 446 | } |
| 447 | |
| 448 | ksproc->kick_gpio = of_get_named_gpio_flags(np, "kick-gpios", 0, NULL); |
| 449 | if (ksproc->kick_gpio < 0) { |
| 450 | ret = ksproc->kick_gpio; |
| 451 | dev_err(dev, "failed to get gpio for virtio kicks, status = %d\n", |
| 452 | ret); |
| 453 | goto disable_clk; |
| 454 | } |
| 455 | |
| 456 | if (of_reserved_mem_device_init(dev)) |
| 457 | dev_warn(dev, "device does not have specific CMA pool\n"); |
| 458 | |
Andrew F. Davis | 11d2a2f | 2017-06-13 18:45:13 -0500 | [diff] [blame] | 459 | /* ensure the DSP is in reset before loading firmware */ |
| 460 | ret = reset_control_status(ksproc->reset); |
| 461 | if (ret < 0) { |
| 462 | dev_err(dev, "failed to get reset status, status = %d\n", ret); |
| 463 | goto release_mem; |
| 464 | } else if (ret == 0) { |
| 465 | WARN(1, "device is not in reset\n"); |
| 466 | keystone_rproc_dsp_reset(ksproc); |
| 467 | } |
| 468 | |
Suman Anna | e88bb8f | 2017-06-13 18:45:12 -0500 | [diff] [blame] | 469 | ret = rproc_add(rproc); |
| 470 | if (ret) { |
| 471 | dev_err(dev, "failed to add register device with remoteproc core, status = %d\n", |
| 472 | ret); |
| 473 | goto release_mem; |
| 474 | } |
| 475 | |
| 476 | platform_set_drvdata(pdev, ksproc); |
| 477 | |
| 478 | return 0; |
| 479 | |
| 480 | release_mem: |
| 481 | of_reserved_mem_device_release(dev); |
| 482 | disable_clk: |
| 483 | pm_runtime_put_sync(dev); |
| 484 | disable_rpm: |
| 485 | pm_runtime_disable(dev); |
| 486 | free_rproc: |
| 487 | rproc_free(rproc); |
| 488 | return ret; |
| 489 | } |
| 490 | |
| 491 | static int keystone_rproc_remove(struct platform_device *pdev) |
| 492 | { |
| 493 | struct keystone_rproc *ksproc = platform_get_drvdata(pdev); |
| 494 | |
| 495 | rproc_del(ksproc->rproc); |
| 496 | pm_runtime_put_sync(&pdev->dev); |
| 497 | pm_runtime_disable(&pdev->dev); |
| 498 | rproc_free(ksproc->rproc); |
| 499 | of_reserved_mem_device_release(&pdev->dev); |
| 500 | |
| 501 | return 0; |
| 502 | } |
| 503 | |
| 504 | static const struct of_device_id keystone_rproc_of_match[] = { |
| 505 | { .compatible = "ti,k2hk-dsp", }, |
| 506 | { .compatible = "ti,k2l-dsp", }, |
| 507 | { .compatible = "ti,k2e-dsp", }, |
Suman Anna | b4daf89 | 2017-07-31 15:05:02 -0500 | [diff] [blame] | 508 | { .compatible = "ti,k2g-dsp", }, |
Suman Anna | e88bb8f | 2017-06-13 18:45:12 -0500 | [diff] [blame] | 509 | { /* sentinel */ }, |
| 510 | }; |
| 511 | MODULE_DEVICE_TABLE(of, keystone_rproc_of_match); |
| 512 | |
| 513 | static struct platform_driver keystone_rproc_driver = { |
| 514 | .probe = keystone_rproc_probe, |
| 515 | .remove = keystone_rproc_remove, |
| 516 | .driver = { |
| 517 | .name = "keystone-rproc", |
| 518 | .of_match_table = keystone_rproc_of_match, |
| 519 | }, |
| 520 | }; |
| 521 | |
| 522 | module_platform_driver(keystone_rproc_driver); |
| 523 | |
| 524 | MODULE_AUTHOR("Suman Anna <s-anna@ti.com>"); |
| 525 | MODULE_LICENSE("GPL v2"); |
| 526 | MODULE_DESCRIPTION("TI Keystone DSP Remoteproc driver"); |