linux/drivers/remoteproc/qcom_q6v5_wcss.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (C) 2016-2018 Linaro Ltd.
   4 * Copyright (C) 2014 Sony Mobile Communications AB
   5 * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
   6 */
   7#include <linux/iopoll.h>
   8#include <linux/kernel.h>
   9#include <linux/mfd/syscon.h>
  10#include <linux/module.h>
  11#include <linux/of_reserved_mem.h>
  12#include <linux/platform_device.h>
  13#include <linux/regmap.h>
  14#include <linux/reset.h>
  15#include <linux/soc/qcom/mdt_loader.h>
  16#include "qcom_common.h"
  17#include "qcom_q6v5.h"
  18
  19#define WCSS_CRASH_REASON               421
  20
  21/* Q6SS Register Offsets */
  22#define Q6SS_RESET_REG          0x014
  23#define Q6SS_GFMUX_CTL_REG              0x020
  24#define Q6SS_PWR_CTL_REG                0x030
  25#define Q6SS_MEM_PWR_CTL                0x0B0
  26
  27/* AXI Halt Register Offsets */
  28#define AXI_HALTREQ_REG                 0x0
  29#define AXI_HALTACK_REG                 0x4
  30#define AXI_IDLE_REG                    0x8
  31
  32#define HALT_ACK_TIMEOUT_MS             100
  33
  34/* Q6SS_RESET */
  35#define Q6SS_STOP_CORE                  BIT(0)
  36#define Q6SS_CORE_ARES                  BIT(1)
  37#define Q6SS_BUS_ARES_ENABLE            BIT(2)
  38
  39/* Q6SS_GFMUX_CTL */
  40#define Q6SS_CLK_ENABLE                 BIT(1)
  41
  42/* Q6SS_PWR_CTL */
  43#define Q6SS_L2DATA_STBY_N              BIT(18)
  44#define Q6SS_SLP_RET_N                  BIT(19)
  45#define Q6SS_CLAMP_IO                   BIT(20)
  46#define QDSS_BHS_ON                     BIT(21)
  47
  48/* Q6SS parameters */
  49#define Q6SS_LDO_BYP            BIT(25)
  50#define Q6SS_BHS_ON             BIT(24)
  51#define Q6SS_CLAMP_WL           BIT(21)
  52#define Q6SS_CLAMP_QMC_MEM              BIT(22)
  53#define HALT_CHECK_MAX_LOOPS            200
  54#define Q6SS_XO_CBCR            GENMASK(5, 3)
  55
  56/* Q6SS config/status registers */
  57#define TCSR_GLOBAL_CFG0        0x0
  58#define TCSR_GLOBAL_CFG1        0x4
  59#define SSCAON_CONFIG           0x8
  60#define SSCAON_STATUS           0xc
  61#define Q6SS_BHS_STATUS         0x78
  62#define Q6SS_RST_EVB            0x10
  63
  64#define BHS_EN_REST_ACK         BIT(0)
  65#define SSCAON_ENABLE           BIT(13)
  66#define SSCAON_BUS_EN           BIT(15)
  67#define SSCAON_BUS_MUX_MASK     GENMASK(18, 16)
  68
  69#define MEM_BANKS               19
  70#define TCSR_WCSS_CLK_MASK      0x1F
  71#define TCSR_WCSS_CLK_ENABLE    0x14
  72
  73struct q6v5_wcss {
  74        struct device *dev;
  75
  76        void __iomem *reg_base;
  77        void __iomem *rmb_base;
  78
  79        struct regmap *halt_map;
  80        u32 halt_q6;
  81        u32 halt_wcss;
  82        u32 halt_nc;
  83
  84        struct reset_control *wcss_aon_reset;
  85        struct reset_control *wcss_reset;
  86        struct reset_control *wcss_q6_reset;
  87
  88        struct qcom_q6v5 q6v5;
  89
  90        phys_addr_t mem_phys;
  91        phys_addr_t mem_reloc;
  92        void *mem_region;
  93        size_t mem_size;
  94};
  95
  96static int q6v5_wcss_reset(struct q6v5_wcss *wcss)
  97{
  98        int ret;
  99        u32 val;
 100        int i;
 101
 102        /* Assert resets, stop core */
 103        val = readl(wcss->reg_base + Q6SS_RESET_REG);
 104        val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE;
 105        writel(val, wcss->reg_base + Q6SS_RESET_REG);
 106
 107        /* BHS require xo cbcr to be enabled */
 108        val = readl(wcss->reg_base + Q6SS_XO_CBCR);
 109        val |= 0x1;
 110        writel(val, wcss->reg_base + Q6SS_XO_CBCR);
 111
 112        /* Read CLKOFF bit to go low indicating CLK is enabled */
 113        ret = readl_poll_timeout(wcss->reg_base + Q6SS_XO_CBCR,
 114                                 val, !(val & BIT(31)), 1,
 115                                 HALT_CHECK_MAX_LOOPS);
 116        if (ret) {
 117                dev_err(wcss->dev,
 118                        "xo cbcr enabling timed out (rc:%d)\n", ret);
 119                return ret;
 120        }
 121        /* Enable power block headswitch and wait for it to stabilize */
 122        val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
 123        val |= Q6SS_BHS_ON;
 124        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 125        udelay(1);
 126
 127        /* Put LDO in bypass mode */
 128        val |= Q6SS_LDO_BYP;
 129        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 130
 131        /* Deassert Q6 compiler memory clamp */
 132        val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
 133        val &= ~Q6SS_CLAMP_QMC_MEM;
 134        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 135
 136        /* Deassert memory peripheral sleep and L2 memory standby */
 137        val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N;
 138        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 139
 140        /* Turn on L1, L2, ETB and JU memories 1 at a time */
 141        val = readl(wcss->reg_base + Q6SS_MEM_PWR_CTL);
 142        for (i = MEM_BANKS; i >= 0; i--) {
 143                val |= BIT(i);
 144                writel(val, wcss->reg_base + Q6SS_MEM_PWR_CTL);
 145                /*
 146                 * Read back value to ensure the write is done then
 147                 * wait for 1us for both memory peripheral and data
 148                 * array to turn on.
 149                 */
 150                val |= readl(wcss->reg_base + Q6SS_MEM_PWR_CTL);
 151                udelay(1);
 152        }
 153        /* Remove word line clamp */
 154        val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
 155        val &= ~Q6SS_CLAMP_WL;
 156        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 157
 158        /* Remove IO clamp */
 159        val &= ~Q6SS_CLAMP_IO;
 160        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 161
 162        /* Bring core out of reset */
 163        val = readl(wcss->reg_base + Q6SS_RESET_REG);
 164        val &= ~Q6SS_CORE_ARES;
 165        writel(val, wcss->reg_base + Q6SS_RESET_REG);
 166
 167        /* Turn on core clock */
 168        val = readl(wcss->reg_base + Q6SS_GFMUX_CTL_REG);
 169        val |= Q6SS_CLK_ENABLE;
 170        writel(val, wcss->reg_base + Q6SS_GFMUX_CTL_REG);
 171
 172        /* Start core execution */
 173        val = readl(wcss->reg_base + Q6SS_RESET_REG);
 174        val &= ~Q6SS_STOP_CORE;
 175        writel(val, wcss->reg_base + Q6SS_RESET_REG);
 176
 177        return 0;
 178}
 179
 180static int q6v5_wcss_start(struct rproc *rproc)
 181{
 182        struct q6v5_wcss *wcss = rproc->priv;
 183        int ret;
 184
 185        qcom_q6v5_prepare(&wcss->q6v5);
 186
 187        /* Release Q6 and WCSS reset */
 188        ret = reset_control_deassert(wcss->wcss_reset);
 189        if (ret) {
 190                dev_err(wcss->dev, "wcss_reset failed\n");
 191                return ret;
 192        }
 193
 194        ret = reset_control_deassert(wcss->wcss_q6_reset);
 195        if (ret) {
 196                dev_err(wcss->dev, "wcss_q6_reset failed\n");
 197                goto wcss_reset;
 198        }
 199
 200        /* Lithium configuration - clock gating and bus arbitration */
 201        ret = regmap_update_bits(wcss->halt_map,
 202                                 wcss->halt_nc + TCSR_GLOBAL_CFG0,
 203                                 TCSR_WCSS_CLK_MASK,
 204                                 TCSR_WCSS_CLK_ENABLE);
 205        if (ret)
 206                goto wcss_q6_reset;
 207
 208        ret = regmap_update_bits(wcss->halt_map,
 209                                 wcss->halt_nc + TCSR_GLOBAL_CFG1,
 210                                 1, 0);
 211        if (ret)
 212                goto wcss_q6_reset;
 213
 214        /* Write bootaddr to EVB so that Q6WCSS will jump there after reset */
 215        writel(rproc->bootaddr >> 4, wcss->reg_base + Q6SS_RST_EVB);
 216
 217        ret = q6v5_wcss_reset(wcss);
 218        if (ret)
 219                goto wcss_q6_reset;
 220
 221        ret = qcom_q6v5_wait_for_start(&wcss->q6v5, 5 * HZ);
 222        if (ret == -ETIMEDOUT)
 223                dev_err(wcss->dev, "start timed out\n");
 224
 225        return ret;
 226
 227wcss_q6_reset:
 228        reset_control_assert(wcss->wcss_q6_reset);
 229
 230wcss_reset:
 231        reset_control_assert(wcss->wcss_reset);
 232
 233        return ret;
 234}
 235
 236static void q6v5_wcss_halt_axi_port(struct q6v5_wcss *wcss,
 237                                    struct regmap *halt_map,
 238                                    u32 offset)
 239{
 240        unsigned long timeout;
 241        unsigned int val;
 242        int ret;
 243
 244        /* Check if we're already idle */
 245        ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val);
 246        if (!ret && val)
 247                return;
 248
 249        /* Assert halt request */
 250        regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1);
 251
 252        /* Wait for halt */
 253        timeout = jiffies + msecs_to_jiffies(HALT_ACK_TIMEOUT_MS);
 254        for (;;) {
 255                ret = regmap_read(halt_map, offset + AXI_HALTACK_REG, &val);
 256                if (ret || val || time_after(jiffies, timeout))
 257                        break;
 258
 259                msleep(1);
 260        }
 261
 262        ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val);
 263        if (ret || !val)
 264                dev_err(wcss->dev, "port failed halt\n");
 265
 266        /* Clear halt request (port will remain halted until reset) */
 267        regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0);
 268}
 269
 270static int q6v5_wcss_powerdown(struct q6v5_wcss *wcss)
 271{
 272        int ret;
 273        u32 val;
 274
 275        /* 1 - Assert WCSS/Q6 HALTREQ */
 276        q6v5_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_wcss);
 277
 278        /* 2 - Enable WCSSAON_CONFIG */
 279        val = readl(wcss->rmb_base + SSCAON_CONFIG);
 280        val |= SSCAON_ENABLE;
 281        writel(val, wcss->rmb_base + SSCAON_CONFIG);
 282
 283        /* 3 - Set SSCAON_CONFIG */
 284        val |= SSCAON_BUS_EN;
 285        val &= ~SSCAON_BUS_MUX_MASK;
 286        writel(val, wcss->rmb_base + SSCAON_CONFIG);
 287
 288        /* 4 - SSCAON_CONFIG 1 */
 289        val |= BIT(1);
 290        writel(val, wcss->rmb_base + SSCAON_CONFIG);
 291
 292        /* 5 - wait for SSCAON_STATUS */
 293        ret = readl_poll_timeout(wcss->rmb_base + SSCAON_STATUS,
 294                                 val, (val & 0xffff) == 0x400, 1000,
 295                                 HALT_CHECK_MAX_LOOPS);
 296        if (ret) {
 297                dev_err(wcss->dev,
 298                        "can't get SSCAON_STATUS rc:%d)\n", ret);
 299                return ret;
 300        }
 301
 302        /* 6 - De-assert WCSS_AON reset */
 303        reset_control_assert(wcss->wcss_aon_reset);
 304
 305        /* 7 - Disable WCSSAON_CONFIG 13 */
 306        val = readl(wcss->rmb_base + SSCAON_CONFIG);
 307        val &= ~SSCAON_ENABLE;
 308        writel(val, wcss->rmb_base + SSCAON_CONFIG);
 309
 310        /* 8 - De-assert WCSS/Q6 HALTREQ */
 311        reset_control_assert(wcss->wcss_reset);
 312
 313        return 0;
 314}
 315
 316static int q6v5_q6_powerdown(struct q6v5_wcss *wcss)
 317{
 318        int ret;
 319        u32 val;
 320        int i;
 321
 322        /* 1 - Halt Q6 bus interface */
 323        q6v5_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_q6);
 324
 325        /* 2 - Disable Q6 Core clock */
 326        val = readl(wcss->reg_base + Q6SS_GFMUX_CTL_REG);
 327        val &= ~Q6SS_CLK_ENABLE;
 328        writel(val, wcss->reg_base + Q6SS_GFMUX_CTL_REG);
 329
 330        /* 3 - Clamp I/O */
 331        val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
 332        val |= Q6SS_CLAMP_IO;
 333        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 334
 335        /* 4 - Clamp WL */
 336        val |= QDSS_BHS_ON;
 337        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 338
 339        /* 5 - Clear Erase standby */
 340        val &= ~Q6SS_L2DATA_STBY_N;
 341        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 342
 343        /* 6 - Clear Sleep RTN */
 344        val &= ~Q6SS_SLP_RET_N;
 345        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 346
 347        /* 7 - turn off Q6 memory foot/head switch one bank at a time */
 348        for (i = 0; i < 20; i++) {
 349                val = readl(wcss->reg_base + Q6SS_MEM_PWR_CTL);
 350                val &= ~BIT(i);
 351                writel(val, wcss->reg_base + Q6SS_MEM_PWR_CTL);
 352                mdelay(1);
 353        }
 354
 355        /* 8 - Assert QMC memory RTN */
 356        val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG);
 357        val |= Q6SS_CLAMP_QMC_MEM;
 358        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 359
 360        /* 9 - Turn off BHS */
 361        val &= ~Q6SS_BHS_ON;
 362        writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG);
 363        udelay(1);
 364
 365        /* 10 - Wait till BHS Reset is done */
 366        ret = readl_poll_timeout(wcss->reg_base + Q6SS_BHS_STATUS,
 367                                 val, !(val & BHS_EN_REST_ACK), 1000,
 368                                 HALT_CHECK_MAX_LOOPS);
 369        if (ret) {
 370                dev_err(wcss->dev, "BHS_STATUS not OFF (rc:%d)\n", ret);
 371                return ret;
 372        }
 373
 374        /* 11 -  Assert WCSS reset */
 375        reset_control_assert(wcss->wcss_reset);
 376
 377        /* 12 - Assert Q6 reset */
 378        reset_control_assert(wcss->wcss_q6_reset);
 379
 380        return 0;
 381}
 382
 383static int q6v5_wcss_stop(struct rproc *rproc)
 384{
 385        struct q6v5_wcss *wcss = rproc->priv;
 386        int ret;
 387
 388        /* WCSS powerdown */
 389        ret = qcom_q6v5_request_stop(&wcss->q6v5);
 390        if (ret == -ETIMEDOUT) {
 391                dev_err(wcss->dev, "timed out on wait\n");
 392                return ret;
 393        }
 394
 395        ret = q6v5_wcss_powerdown(wcss);
 396        if (ret)
 397                return ret;
 398
 399        /* Q6 Power down */
 400        ret = q6v5_q6_powerdown(wcss);
 401        if (ret)
 402                return ret;
 403
 404        qcom_q6v5_unprepare(&wcss->q6v5);
 405
 406        return 0;
 407}
 408
 409static void *q6v5_wcss_da_to_va(struct rproc *rproc, u64 da, int len)
 410{
 411        struct q6v5_wcss *wcss = rproc->priv;
 412        int offset;
 413
 414        offset = da - wcss->mem_reloc;
 415        if (offset < 0 || offset + len > wcss->mem_size)
 416                return NULL;
 417
 418        return wcss->mem_region + offset;
 419}
 420
 421static int q6v5_wcss_load(struct rproc *rproc, const struct firmware *fw)
 422{
 423        struct q6v5_wcss *wcss = rproc->priv;
 424
 425        return qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware,
 426                                     0, wcss->mem_region, wcss->mem_phys,
 427                                     wcss->mem_size, &wcss->mem_reloc);
 428}
 429
 430static const struct rproc_ops q6v5_wcss_ops = {
 431        .start = q6v5_wcss_start,
 432        .stop = q6v5_wcss_stop,
 433        .da_to_va = q6v5_wcss_da_to_va,
 434        .load = q6v5_wcss_load,
 435        .get_boot_addr = rproc_elf_get_boot_addr,
 436};
 437
 438static int q6v5_wcss_init_reset(struct q6v5_wcss *wcss)
 439{
 440        struct device *dev = wcss->dev;
 441
 442        wcss->wcss_aon_reset = devm_reset_control_get(dev, "wcss_aon_reset");
 443        if (IS_ERR(wcss->wcss_aon_reset)) {
 444                dev_err(wcss->dev, "unable to acquire wcss_aon_reset\n");
 445                return PTR_ERR(wcss->wcss_aon_reset);
 446        }
 447
 448        wcss->wcss_reset = devm_reset_control_get(dev, "wcss_reset");
 449        if (IS_ERR(wcss->wcss_reset)) {
 450                dev_err(wcss->dev, "unable to acquire wcss_reset\n");
 451                return PTR_ERR(wcss->wcss_reset);
 452        }
 453
 454        wcss->wcss_q6_reset = devm_reset_control_get(dev, "wcss_q6_reset");
 455        if (IS_ERR(wcss->wcss_q6_reset)) {
 456                dev_err(wcss->dev, "unable to acquire wcss_q6_reset\n");
 457                return PTR_ERR(wcss->wcss_q6_reset);
 458        }
 459
 460        return 0;
 461}
 462
 463static int q6v5_wcss_init_mmio(struct q6v5_wcss *wcss,
 464                               struct platform_device *pdev)
 465{
 466        struct of_phandle_args args;
 467        struct resource *res;
 468        int ret;
 469
 470        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6");
 471        wcss->reg_base = devm_ioremap_resource(&pdev->dev, res);
 472        if (IS_ERR(wcss->reg_base))
 473                return PTR_ERR(wcss->reg_base);
 474
 475        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb");
 476        wcss->rmb_base = devm_ioremap_resource(&pdev->dev, res);
 477        if (IS_ERR(wcss->rmb_base))
 478                return PTR_ERR(wcss->rmb_base);
 479
 480        ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
 481                                               "qcom,halt-regs", 3, 0, &args);
 482        if (ret < 0) {
 483                dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n");
 484                return -EINVAL;
 485        }
 486
 487        wcss->halt_map = syscon_node_to_regmap(args.np);
 488        of_node_put(args.np);
 489        if (IS_ERR(wcss->halt_map))
 490                return PTR_ERR(wcss->halt_map);
 491
 492        wcss->halt_q6 = args.args[0];
 493        wcss->halt_wcss = args.args[1];
 494        wcss->halt_nc = args.args[2];
 495
 496        return 0;
 497}
 498
 499static int q6v5_alloc_memory_region(struct q6v5_wcss *wcss)
 500{
 501        struct reserved_mem *rmem = NULL;
 502        struct device_node *node;
 503        struct device *dev = wcss->dev;
 504
 505        node = of_parse_phandle(dev->of_node, "memory-region", 0);
 506        if (node)
 507                rmem = of_reserved_mem_lookup(node);
 508        of_node_put(node);
 509
 510        if (!rmem) {
 511                dev_err(dev, "unable to acquire memory-region\n");
 512                return -EINVAL;
 513        }
 514
 515        wcss->mem_phys = rmem->base;
 516        wcss->mem_reloc = rmem->base;
 517        wcss->mem_size = rmem->size;
 518        wcss->mem_region = devm_ioremap_wc(dev, wcss->mem_phys, wcss->mem_size);
 519        if (!wcss->mem_region) {
 520                dev_err(dev, "unable to map memory region: %pa+%pa\n",
 521                        &rmem->base, &rmem->size);
 522                return -EBUSY;
 523        }
 524
 525        return 0;
 526}
 527
 528static int q6v5_wcss_probe(struct platform_device *pdev)
 529{
 530        struct q6v5_wcss *wcss;
 531        struct rproc *rproc;
 532        int ret;
 533
 534        rproc = rproc_alloc(&pdev->dev, pdev->name, &q6v5_wcss_ops,
 535                            "IPQ8074/q6_fw.mdt", sizeof(*wcss));
 536        if (!rproc) {
 537                dev_err(&pdev->dev, "failed to allocate rproc\n");
 538                return -ENOMEM;
 539        }
 540
 541        wcss = rproc->priv;
 542        wcss->dev = &pdev->dev;
 543
 544        ret = q6v5_wcss_init_mmio(wcss, pdev);
 545        if (ret)
 546                goto free_rproc;
 547
 548        ret = q6v5_alloc_memory_region(wcss);
 549        if (ret)
 550                goto free_rproc;
 551
 552        ret = q6v5_wcss_init_reset(wcss);
 553        if (ret)
 554                goto free_rproc;
 555
 556        ret = qcom_q6v5_init(&wcss->q6v5, pdev, rproc, WCSS_CRASH_REASON, NULL);
 557        if (ret)
 558                goto free_rproc;
 559
 560        ret = rproc_add(rproc);
 561        if (ret)
 562                goto free_rproc;
 563
 564        platform_set_drvdata(pdev, rproc);
 565
 566        return 0;
 567
 568free_rproc:
 569        rproc_free(rproc);
 570
 571        return ret;
 572}
 573
 574static int q6v5_wcss_remove(struct platform_device *pdev)
 575{
 576        struct rproc *rproc = platform_get_drvdata(pdev);
 577
 578        rproc_del(rproc);
 579        rproc_free(rproc);
 580
 581        return 0;
 582}
 583
 584static const struct of_device_id q6v5_wcss_of_match[] = {
 585        { .compatible = "qcom,ipq8074-wcss-pil" },
 586        { },
 587};
 588MODULE_DEVICE_TABLE(of, q6v5_wcss_of_match);
 589
 590static struct platform_driver q6v5_wcss_driver = {
 591        .probe = q6v5_wcss_probe,
 592        .remove = q6v5_wcss_remove,
 593        .driver = {
 594                .name = "qcom-q6v5-wcss-pil",
 595                .of_match_table = q6v5_wcss_of_match,
 596        },
 597};
 598module_platform_driver(q6v5_wcss_driver);
 599
 600MODULE_DESCRIPTION("Hexagon WCSS Peripheral Image Loader");
 601MODULE_LICENSE("GPL v2");
 602