developer | abd06d7 | 2022-03-03 16:13:41 +0800 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0
|
| 2 | /*
|
| 3 | * xHCI host controller toolkit driver for intr-en
|
| 4 | *
|
| 5 | * Copyright (C) 2021 MediaTek Inc.
|
| 6 | *
|
| 7 | * Author: Zhanyong Wang <zhanyong.wang@mediatek.com>
|
| 8 | */
|
| 9 |
|
| 10 |
|
| 11 | #include <linux/platform_device.h>
|
| 12 | #include <linux/module.h>
|
| 13 | #include <linux/slab.h>
|
| 14 | #include <linux/usb.h>
|
| 15 | #include "xhci-mtk.h"
|
| 16 | #include "xhci-mtk-test.h"
|
| 17 | #include "xhci-mtk-unusual.h"
|
| 18 |
|
| 19 | #define REGS_LIMIT_XHCI 0x1000
|
| 20 | #define REGS_LIMIT_MU3D 0x2e00
|
| 21 | static ssize_t reg_show(struct device *dev,
|
| 22 | struct device_attribute *attr, char *buf)
|
| 23 | {
|
| 24 | struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
|
| 25 | ssize_t cnt = 0;
|
| 26 |
|
| 27 | cnt += sprintf(buf + cnt,
|
| 28 | "SSUSB register operation interface help info.\n"
|
| 29 | " rx - read xhci reg: offset [len]\n"
|
| 30 | " rm - read mu3d reg: offset [len]\n"
|
| 31 | " ri - read ippc reg: offset [len]\n"
|
| 32 | " rp - read phy reg: offset [len]\n"
|
| 33 | " wx - write xhci reg: offset value\n"
|
| 34 | " wm - write mu3d reg: offset value\n"
|
| 35 | " wi - write ippc reg: offset value\n"
|
| 36 | " wp - write phy reg: offset value\n"
|
| 37 | " sx - set xhci mac reg bits: offset bit_start mask value\n"
|
| 38 | " sm - set mu3d mac reg bits: offset bit_start mask value\n"
|
| 39 | " si - set ippc reg bits: offset bit_start mask value\n"
|
| 40 | " sp - set phy reg bits: offset bit_start mask value\n"
|
| 41 | " px - print xhci mac reg bits: offset bit_start mask\n"
|
| 42 | " pm - print mu3d mac reg bits: offset bit_start mask\n"
|
| 43 | " pi - print ippc reg bits: offset bit_start mask\n"
|
| 44 | " pp - print phy reg bits: offset bit_start mask\n"
|
| 45 | " NOTE: numbers should be HEX, except bit_star(DEC)\n");
|
| 46 |
|
| 47 | if (mtk->hqa_pos) {
|
| 48 | cnt += sprintf(buf + cnt, "%s", mtk->hqa_buf);
|
| 49 | mtk->hqa_pos = 0;
|
| 50 | }
|
| 51 |
|
| 52 | return cnt;
|
| 53 | }
|
| 54 |
|
| 55 | /* base address: return value; limit is put into @limit */
|
| 56 | static void __iomem *get_reg_base_limit(struct xhci_hcd_mtk *mtk,
|
| 57 | const char *buf, u32 *limit)
|
| 58 | {
|
| 59 | struct usb_hcd *hcd = mtk->hcd;
|
| 60 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 61 | struct platform_device *device = to_platform_device(mtk->dev);
|
| 62 | void __iomem *base = NULL;
|
| 63 | struct device_node *node = mtk->dev->of_node;
|
| 64 | u32 io = 0;
|
| 65 | u32 range = 0;
|
| 66 | u32 len = 0;
|
| 67 | int index = 0;
|
| 68 | int ret = 0;
|
| 69 |
|
| 70 | switch (buf[1]) {
|
| 71 | case 'x':
|
| 72 | ret = query_reg_addr(device, &io, &range, "mac");
|
| 73 | if (ret) break;
|
| 74 |
|
| 75 | base = ioremap(io, range);
|
| 76 |
|
| 77 | xhci_info(xhci, "xhci's reg: [0x%08X ~ 0x%08X]\n",
|
| 78 | io, io + range);
|
| 79 | hqa_info (mtk, "xhci's reg: [0x%08X ~ 0x%08X]\n",
|
| 80 | io, io + range);
|
| 81 | break;
|
| 82 | case 'm':
|
| 83 | if (!mtk->has_ippc)
|
| 84 | device = to_platform_device(device->dev.parent);
|
| 85 |
|
| 86 | ret = query_reg_addr(device, &io, &range, "mac");
|
| 87 | if (ret) break;
|
| 88 |
|
| 89 | if (mtk->has_ippc) {
|
| 90 | io += REGS_LIMIT_XHCI;
|
| 91 | range = REGS_LIMIT_MU3D;
|
| 92 | }
|
| 93 |
|
| 94 | base = ioremap(io, range);
|
| 95 | xhci_info(xhci, "mu3d's reg: [0x%08X ~ 0x%08X]\n",
|
| 96 | io, io + range);
|
| 97 | hqa_info (mtk, "mu3d's reg: [0x%08X ~ 0x%08X]\n",
|
| 98 | io, io + range);
|
| 99 | break;
|
| 100 | case 'i':
|
| 101 | ret = query_reg_addr(device, &io, &range, "ippc");
|
| 102 | if (ret) break;
|
| 103 |
|
| 104 | base = ioremap(io, range);
|
| 105 | xhci_info(xhci, "ippc's reg: [0x%08X ~ 0x%08X]\n",
|
| 106 | io, io + range);
|
| 107 | hqa_info (mtk, "ippc's reg: [0x%08X ~ 0x%08X]\n",
|
| 108 | io, io + range);
|
| 109 | break;
|
| 110 | case 'p':
|
| 111 | ret = query_phy_addr(node, &index, &io, &len, PHY_TYPE_USB3);
|
| 112 | if (ret && ret != -EACCES) break;
|
| 113 |
|
| 114 | range = io & 0x0000FFFF;
|
| 115 | range += len;
|
| 116 |
|
| 117 | io &= 0xFFFF0000;
|
| 118 |
|
| 119 | base = ioremap(io, range);
|
| 120 | xhci_info(xhci, "phy's reg: [0x%08X ~ 0x%08X]\n",
|
| 121 | io, io + range);
|
| 122 | hqa_info (mtk, "phy's reg: [0x%08X ~ 0x%08X]\n",
|
| 123 | io, io + range);
|
| 124 | break;
|
| 125 | default:
|
| 126 | base = NULL;
|
| 127 | }
|
| 128 |
|
| 129 | *limit = range;
|
| 130 |
|
| 131 | return base;
|
| 132 | }
|
| 133 |
|
| 134 | static void ssusb_write_reg(struct xhci_hcd_mtk *mtk, const char *buf)
|
| 135 | {
|
| 136 | struct usb_hcd *hcd = mtk->hcd;
|
| 137 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 138 | void __iomem *base;
|
| 139 | u32 offset = 0;
|
| 140 | u32 value = 0;
|
| 141 | u32 old_val = 0;
|
| 142 | u32 limit = 0;
|
| 143 | u32 param;
|
| 144 |
|
| 145 | param = sscanf(buf, "%*s 0x%x 0x%x", &offset, &value);
|
| 146 | xhci_info(xhci, "params-%d (offset: %#x, value: %#x)\n",
|
| 147 | param, offset, value);
|
| 148 | hqa_info (mtk, "params-%d (offset: %#x, value: %#x)\n",
|
| 149 | param, offset, value);
|
| 150 |
|
| 151 | base = get_reg_base_limit(mtk, buf, &limit);
|
| 152 | if (!base || (param != 2)) {
|
| 153 | xhci_err(xhci, "params are invalid!\n");
|
| 154 | hqa_info(mtk, "params are invalid since %p, %u!\n",
|
| 155 | base, param);
|
| 156 | return;
|
| 157 | }
|
| 158 |
|
| 159 | offset &= ~0x3; /* 4-bytes align */
|
| 160 | if (offset >= limit) {
|
| 161 | xhci_err(xhci, "reg's offset overrun!\n");
|
| 162 | hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
|
| 163 | offset, limit);
|
| 164 | return;
|
| 165 | }
|
| 166 | old_val = readl(base + offset);
|
| 167 | writel(value, base + offset);
|
| 168 | xhci_info(xhci, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
|
| 169 | readl(base + offset));
|
| 170 | hqa_info (mtk, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
|
| 171 | readl(base + offset));
|
| 172 |
|
| 173 | base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
|
| 174 | iounmap(base);
|
| 175 | }
|
| 176 |
|
| 177 | static void read_single_reg(struct xhci_hcd_mtk *mtk,
|
| 178 | void __iomem *base, u32 offset, u32 limit)
|
| 179 | {
|
| 180 | struct usb_hcd *hcd = mtk->hcd;
|
| 181 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 182 | u32 value;
|
| 183 |
|
| 184 | offset &= ~0x3; /* 4-bytes align */
|
| 185 | if (offset >= limit) {
|
| 186 | xhci_err(xhci, "reg's offset overrun!\n");
|
| 187 | hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
|
| 188 | offset, limit);
|
| 189 | return;
|
| 190 | }
|
| 191 | value = readl(base + offset);
|
| 192 | xhci_err(xhci, "0x%8.8x : 0x%8.8x\n", offset, value);
|
| 193 | hqa_info(mtk, "0x%8.8x : 0x%8.8x\n", offset, value);
|
| 194 | }
|
| 195 |
|
| 196 | static void read_multi_regs(struct xhci_hcd_mtk *mtk,
|
| 197 | void __iomem *base, u32 offset, u32 len, u32 limit)
|
| 198 | {
|
| 199 | struct usb_hcd *hcd = mtk->hcd;
|
| 200 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 201 | int i;
|
| 202 |
|
| 203 | /* at least 4 ints */
|
| 204 | offset &= ~0xF;
|
| 205 | len = (len + 0x3) & ~0x3;
|
| 206 |
|
| 207 | if (offset + len > limit) {
|
| 208 | xhci_err(xhci, "reg's offset overrun!\n");
|
| 209 | hqa_info(mtk, "reg's offset overrun since %u > %u!\n",
|
| 210 | offset + len, limit);
|
| 211 | return;
|
| 212 | }
|
| 213 |
|
| 214 | len >>= 2;
|
| 215 | xhci_info(xhci, "read regs [%#x, %#x)\n", offset, offset + (len << 4));
|
| 216 | hqa_info (mtk, "read regs [%#x, %#x)\n", offset, offset + (len << 4));
|
| 217 | for (i = 0; i < len; i++) {
|
| 218 | xhci_err(xhci, "0x%8.8x : 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n",
|
| 219 | offset, readl(base + offset),
|
| 220 | readl(base + offset + 0x4),
|
| 221 | readl(base + offset + 0x8),
|
| 222 | readl(base + offset + 0xc));
|
| 223 | hqa_info(mtk, "0x%8.8x : 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n",
|
| 224 | offset, readl(base + offset),
|
| 225 | readl(base + offset + 0x4),
|
| 226 | readl(base + offset + 0x8),
|
| 227 | readl(base + offset + 0xc));
|
| 228 | offset += 0x10;
|
| 229 | }
|
| 230 | }
|
| 231 |
|
| 232 | static void ssusb_read_regs(struct xhci_hcd_mtk *mtk, const char *buf)
|
| 233 | {
|
| 234 | struct usb_hcd *hcd = mtk->hcd;
|
| 235 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 236 | void __iomem *base;
|
| 237 | u32 offset = 0;
|
| 238 | u32 len = 0;
|
| 239 | u32 limit = 0;
|
| 240 | u32 param;
|
| 241 |
|
| 242 | param = sscanf(buf, "%*s 0x%x 0x%x", &offset, &len);
|
| 243 | xhci_info(xhci, "params-%d (offset: %#x, len: %#x)\n",
|
| 244 | param, offset, len);
|
| 245 | hqa_info (mtk, "params-%d (offset: %#x, len: %#x)\n",
|
| 246 | param, offset, len);
|
| 247 |
|
| 248 | base = get_reg_base_limit(mtk, buf, &limit);
|
| 249 | if (!base || !param) {
|
| 250 | xhci_err(xhci, "params are invalid!\n");
|
| 251 | hqa_info(mtk, "params are invalid since %p, %u!\n",
|
| 252 | base, param);
|
| 253 | return;
|
| 254 | }
|
| 255 |
|
| 256 | if (param == 1)
|
| 257 | read_single_reg(mtk, base, offset, limit);
|
| 258 | else
|
| 259 | read_multi_regs(mtk, base, offset, len, limit);
|
| 260 |
|
| 261 | base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
|
| 262 | iounmap(base);
|
| 263 | }
|
| 264 |
|
| 265 | static void ssusb_set_reg_bits(struct xhci_hcd_mtk *mtk, const char *buf)
|
| 266 | {
|
| 267 | struct usb_hcd *hcd = mtk->hcd;
|
| 268 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 269 | void __iomem *base;
|
| 270 | u32 offset = 0;
|
| 271 | u32 bit_start = 0;
|
| 272 | u32 mask = 0;
|
| 273 | u32 value = 0;
|
| 274 | u32 old_val = 0;
|
| 275 | u32 new_val = 0;
|
| 276 | u32 limit = 0;
|
| 277 | u32 param;
|
| 278 |
|
| 279 | param = sscanf(buf, "%*s 0x%x %d 0x%x 0x%x",
|
| 280 | &offset, &bit_start, &mask, &value);
|
| 281 | xhci_info(xhci, "params-%d (offset:%#x,bit_start:%d,mask:%#x,value:%#x)\n",
|
| 282 | param, offset, bit_start, mask, value);
|
| 283 | hqa_info(mtk, "params-%d (offset:%#x,bit_start:%d,mask:%#x,value:%#x)\n",
|
| 284 | param, offset, bit_start, mask, value);
|
| 285 |
|
| 286 | base = get_reg_base_limit(mtk, buf, &limit);
|
| 287 | if (!base || (param != 4) || (bit_start > 31)) {
|
| 288 | xhci_err(xhci, "params are invalid!\n");
|
| 289 | hqa_info(mtk, "params are invalid since %p, %u, %u\n",
|
| 290 | base, param, bit_start);
|
| 291 | return;
|
| 292 | }
|
| 293 |
|
| 294 | offset &= ~0x3; /* 4-bytes align */
|
| 295 | if (offset >= limit) {
|
| 296 | xhci_err(xhci, "reg's offset overrun!\n");
|
| 297 | hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
|
| 298 | offset, limit);
|
| 299 | return;
|
| 300 | }
|
| 301 | old_val = readl(base + offset);
|
| 302 | new_val = old_val;
|
| 303 | new_val &= ~(mask << bit_start);
|
| 304 | new_val |= (value << bit_start);
|
| 305 | writel(new_val, base + offset);
|
| 306 | xhci_info(xhci, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
|
| 307 | readl(base + offset));
|
| 308 | hqa_info (mtk, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
|
| 309 | readl(base + offset));
|
| 310 |
|
| 311 | base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
|
| 312 | iounmap(base);
|
| 313 | }
|
| 314 |
|
| 315 | static void ssusb_print_reg_bits(struct xhci_hcd_mtk *mtk, const char *buf)
|
| 316 | {
|
| 317 | struct usb_hcd *hcd = mtk->hcd;
|
| 318 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 319 | void __iomem *base;
|
| 320 | u32 offset = 0;
|
| 321 | u32 bit_start = 0;
|
| 322 | u32 mask = 0;
|
| 323 | u32 old_val = 0;
|
| 324 | u32 new_val = 0;
|
| 325 | u32 limit = 0;
|
| 326 | u32 param;
|
| 327 |
|
| 328 | param = sscanf(buf, "%*s 0x%x %d 0x%x", &offset, &bit_start, &mask);
|
| 329 | xhci_info(xhci, "params-%d (offset: %#x, bit_start: %d, mask: %#x)\n",
|
| 330 | param, offset, bit_start, mask);
|
| 331 | hqa_info (mtk, "params-%d (offset: %#x, bit_start: %d, mask: %#x)\n",
|
| 332 | param, offset, bit_start, mask);
|
| 333 |
|
| 334 | base = get_reg_base_limit(mtk, buf, &limit);
|
| 335 | if (!base || (param != 3) || (bit_start > 31)) {
|
| 336 | xhci_err(xhci, "params are invalid!\n");
|
| 337 | hqa_info(mtk, "params are invalid since %p, %u, %u\n",
|
| 338 | base, param, bit_start);
|
| 339 | return;
|
| 340 | }
|
| 341 |
|
| 342 | offset &= ~0x3; /* 4-bytes align */
|
| 343 | if (offset >= limit) {
|
| 344 | xhci_err(xhci, "reg's offset overrun!\n");
|
| 345 | hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
|
| 346 | offset, limit);
|
| 347 | return;
|
| 348 | }
|
| 349 |
|
| 350 | old_val = readl(base + offset);
|
| 351 | new_val = old_val;
|
| 352 | new_val >>= bit_start;
|
| 353 | new_val &= mask;
|
| 354 | xhci_info(xhci, "0x%8.8x : 0x%8.8x (0x%x)\n", offset, old_val, new_val);
|
| 355 | hqa_info (mtk, "0x%8.8x : 0x%8.8x (0x%x)\n", offset, old_val, new_val);
|
| 356 |
|
| 357 | base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
|
| 358 | iounmap(base);
|
| 359 | }
|
| 360 |
|
| 361 | static ssize_t
|
| 362 | reg_store(struct device *dev, struct device_attribute *attr,
|
| 363 | const char *buf, size_t n)
|
| 364 | {
|
| 365 | struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
|
| 366 | struct usb_hcd *hcd = mtk->hcd;
|
| 367 | struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
| 368 |
|
| 369 | xhci_info(xhci, "cmd:%s\n", buf);
|
| 370 | hqa_info (mtk, "cmd:%s\n", buf);
|
| 371 |
|
| 372 | switch (buf[0]) {
|
| 373 | case 'w':
|
| 374 | ssusb_write_reg(mtk, buf);
|
| 375 | break;
|
| 376 | case 'r':
|
| 377 | ssusb_read_regs(mtk, buf);
|
| 378 | break;
|
| 379 | case 's':
|
| 380 | ssusb_set_reg_bits(mtk, buf);
|
| 381 | break;
|
| 382 | case 'p':
|
| 383 | ssusb_print_reg_bits(mtk, buf);
|
| 384 | break;
|
| 385 | default:
|
| 386 | xhci_err(xhci, "No such cmd\n");
|
| 387 | hqa_info(mtk, "No such cmd\n");
|
| 388 | }
|
| 389 |
|
| 390 | return n;
|
| 391 | }
|
| 392 | DEVICE_ATTR_RW(reg);
|