blob: 366747dc479a514b980b93f1bb7bac7e6c00f99b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* xHCI host controller toolkit driver for intr-en
*
* Copyright (C) 2021 MediaTek Inc.
*
* Author: Zhanyong Wang <zhanyong.wang@mediatek.com>
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include "xhci-mtk.h"
#include "xhci-mtk-test.h"
#include "xhci-mtk-unusual.h"
#define REGS_LIMIT_XHCI 0x1000
#define REGS_LIMIT_MU3D 0x2e00
static ssize_t reg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
ssize_t cnt = 0;
cnt += sprintf(buf + cnt,
"SSUSB register operation interface help info.\n"
" rx - read xhci reg: offset [len]\n"
" rm - read mu3d reg: offset [len]\n"
" ri - read ippc reg: offset [len]\n"
" rp - read phy reg: offset [len]\n"
" wx - write xhci reg: offset value\n"
" wm - write mu3d reg: offset value\n"
" wi - write ippc reg: offset value\n"
" wp - write phy reg: offset value\n"
" sx - set xhci mac reg bits: offset bit_start mask value\n"
" sm - set mu3d mac reg bits: offset bit_start mask value\n"
" si - set ippc reg bits: offset bit_start mask value\n"
" sp - set phy reg bits: offset bit_start mask value\n"
" px - print xhci mac reg bits: offset bit_start mask\n"
" pm - print mu3d mac reg bits: offset bit_start mask\n"
" pi - print ippc reg bits: offset bit_start mask\n"
" pp - print phy reg bits: offset bit_start mask\n"
" NOTE: numbers should be HEX, except bit_star(DEC)\n");
if (mtk->hqa_pos) {
cnt += sprintf(buf + cnt, "%s", mtk->hqa_buf);
mtk->hqa_pos = 0;
}
return cnt;
}
/* base address: return value; limit is put into @limit */
static void __iomem *get_reg_base_limit(struct xhci_hcd_mtk *mtk,
const char *buf, u32 *limit)
{
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct platform_device *device = to_platform_device(mtk->dev);
void __iomem *base = NULL;
struct device_node *node = mtk->dev->of_node;
u32 io = 0;
u32 range = 0;
u32 len = 0;
int index = 0;
int ret = 0;
switch (buf[1]) {
case 'x':
ret = query_reg_addr(device, &io, &range, "mac");
if (ret) break;
base = ioremap(io, range);
xhci_info(xhci, "xhci's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
hqa_info (mtk, "xhci's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
break;
case 'm':
if (!mtk->has_ippc)
device = to_platform_device(device->dev.parent);
ret = query_reg_addr(device, &io, &range, "mac");
if (ret) break;
if (mtk->has_ippc) {
io += REGS_LIMIT_XHCI;
range = REGS_LIMIT_MU3D;
}
base = ioremap(io, range);
xhci_info(xhci, "mu3d's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
hqa_info (mtk, "mu3d's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
break;
case 'i':
ret = query_reg_addr(device, &io, &range, "ippc");
if (ret) break;
base = ioremap(io, range);
xhci_info(xhci, "ippc's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
hqa_info (mtk, "ippc's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
break;
case 'p':
ret = query_phy_addr(node, &index, &io, &len, PHY_TYPE_USB3);
if (ret && ret != -EACCES) break;
range = io & 0x0000FFFF;
range += len;
io &= 0xFFFF0000;
base = ioremap(io, range);
xhci_info(xhci, "phy's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
hqa_info (mtk, "phy's reg: [0x%08X ~ 0x%08X]\n",
io, io + range);
break;
default:
base = NULL;
}
*limit = range;
return base;
}
static void ssusb_write_reg(struct xhci_hcd_mtk *mtk, const char *buf)
{
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
void __iomem *base;
u32 offset = 0;
u32 value = 0;
u32 old_val = 0;
u32 limit = 0;
u32 param;
param = sscanf(buf, "%*s 0x%x 0x%x", &offset, &value);
xhci_info(xhci, "params-%d (offset: %#x, value: %#x)\n",
param, offset, value);
hqa_info (mtk, "params-%d (offset: %#x, value: %#x)\n",
param, offset, value);
base = get_reg_base_limit(mtk, buf, &limit);
if (!base || (param != 2)) {
xhci_err(xhci, "params are invalid!\n");
hqa_info(mtk, "params are invalid since %p, %u!\n",
base, param);
return;
}
offset &= ~0x3; /* 4-bytes align */
if (offset >= limit) {
xhci_err(xhci, "reg's offset overrun!\n");
hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
offset, limit);
return;
}
old_val = readl(base + offset);
writel(value, base + offset);
xhci_info(xhci, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
readl(base + offset));
hqa_info (mtk, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
readl(base + offset));
base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
iounmap(base);
}
static void read_single_reg(struct xhci_hcd_mtk *mtk,
void __iomem *base, u32 offset, u32 limit)
{
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
u32 value;
offset &= ~0x3; /* 4-bytes align */
if (offset >= limit) {
xhci_err(xhci, "reg's offset overrun!\n");
hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
offset, limit);
return;
}
value = readl(base + offset);
xhci_err(xhci, "0x%8.8x : 0x%8.8x\n", offset, value);
hqa_info(mtk, "0x%8.8x : 0x%8.8x\n", offset, value);
}
static void read_multi_regs(struct xhci_hcd_mtk *mtk,
void __iomem *base, u32 offset, u32 len, u32 limit)
{
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
int i;
/* at least 4 ints */
offset &= ~0xF;
len = (len + 0x3) & ~0x3;
if (offset + len > limit) {
xhci_err(xhci, "reg's offset overrun!\n");
hqa_info(mtk, "reg's offset overrun since %u > %u!\n",
offset + len, limit);
return;
}
len >>= 2;
xhci_info(xhci, "read regs [%#x, %#x)\n", offset, offset + (len << 4));
hqa_info (mtk, "read regs [%#x, %#x)\n", offset, offset + (len << 4));
for (i = 0; i < len; i++) {
xhci_err(xhci, "0x%8.8x : 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n",
offset, readl(base + offset),
readl(base + offset + 0x4),
readl(base + offset + 0x8),
readl(base + offset + 0xc));
hqa_info(mtk, "0x%8.8x : 0x%8.8x 0x%8.8x 0x%8.8x 0x%8.8x\n",
offset, readl(base + offset),
readl(base + offset + 0x4),
readl(base + offset + 0x8),
readl(base + offset + 0xc));
offset += 0x10;
}
}
static void ssusb_read_regs(struct xhci_hcd_mtk *mtk, const char *buf)
{
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
void __iomem *base;
u32 offset = 0;
u32 len = 0;
u32 limit = 0;
u32 param;
param = sscanf(buf, "%*s 0x%x 0x%x", &offset, &len);
xhci_info(xhci, "params-%d (offset: %#x, len: %#x)\n",
param, offset, len);
hqa_info (mtk, "params-%d (offset: %#x, len: %#x)\n",
param, offset, len);
base = get_reg_base_limit(mtk, buf, &limit);
if (!base || !param) {
xhci_err(xhci, "params are invalid!\n");
hqa_info(mtk, "params are invalid since %p, %u!\n",
base, param);
return;
}
if (param == 1)
read_single_reg(mtk, base, offset, limit);
else
read_multi_regs(mtk, base, offset, len, limit);
base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
iounmap(base);
}
static void ssusb_set_reg_bits(struct xhci_hcd_mtk *mtk, const char *buf)
{
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
void __iomem *base;
u32 offset = 0;
u32 bit_start = 0;
u32 mask = 0;
u32 value = 0;
u32 old_val = 0;
u32 new_val = 0;
u32 limit = 0;
u32 param;
param = sscanf(buf, "%*s 0x%x %d 0x%x 0x%x",
&offset, &bit_start, &mask, &value);
xhci_info(xhci, "params-%d (offset:%#x,bit_start:%d,mask:%#x,value:%#x)\n",
param, offset, bit_start, mask, value);
hqa_info(mtk, "params-%d (offset:%#x,bit_start:%d,mask:%#x,value:%#x)\n",
param, offset, bit_start, mask, value);
base = get_reg_base_limit(mtk, buf, &limit);
if (!base || (param != 4) || (bit_start > 31)) {
xhci_err(xhci, "params are invalid!\n");
hqa_info(mtk, "params are invalid since %p, %u, %u\n",
base, param, bit_start);
return;
}
offset &= ~0x3; /* 4-bytes align */
if (offset >= limit) {
xhci_err(xhci, "reg's offset overrun!\n");
hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
offset, limit);
return;
}
old_val = readl(base + offset);
new_val = old_val;
new_val &= ~(mask << bit_start);
new_val |= (value << bit_start);
writel(new_val, base + offset);
xhci_info(xhci, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
readl(base + offset));
hqa_info (mtk, "0x%8.8x : 0x%8.8x --> 0x%8.8x\n", offset, old_val,
readl(base + offset));
base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
iounmap(base);
}
static void ssusb_print_reg_bits(struct xhci_hcd_mtk *mtk, const char *buf)
{
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
void __iomem *base;
u32 offset = 0;
u32 bit_start = 0;
u32 mask = 0;
u32 old_val = 0;
u32 new_val = 0;
u32 limit = 0;
u32 param;
param = sscanf(buf, "%*s 0x%x %d 0x%x", &offset, &bit_start, &mask);
xhci_info(xhci, "params-%d (offset: %#x, bit_start: %d, mask: %#x)\n",
param, offset, bit_start, mask);
hqa_info (mtk, "params-%d (offset: %#x, bit_start: %d, mask: %#x)\n",
param, offset, bit_start, mask);
base = get_reg_base_limit(mtk, buf, &limit);
if (!base || (param != 3) || (bit_start > 31)) {
xhci_err(xhci, "params are invalid!\n");
hqa_info(mtk, "params are invalid since %p, %u, %u\n",
base, param, bit_start);
return;
}
offset &= ~0x3; /* 4-bytes align */
if (offset >= limit) {
xhci_err(xhci, "reg's offset overrun!\n");
hqa_info(mtk, "reg's offset overrun since %u >= %u!\n",
offset, limit);
return;
}
old_val = readl(base + offset);
new_val = old_val;
new_val >>= bit_start;
new_val &= mask;
xhci_info(xhci, "0x%8.8x : 0x%8.8x (0x%x)\n", offset, old_val, new_val);
hqa_info (mtk, "0x%8.8x : 0x%8.8x (0x%x)\n", offset, old_val, new_val);
base = (void __iomem *)((unsigned long)base & 0xFFFF0000);
iounmap(base);
}
static ssize_t
reg_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
struct xhci_hcd_mtk *mtk = dev_get_drvdata(dev);
struct usb_hcd *hcd = mtk->hcd;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
xhci_info(xhci, "cmd:%s\n", buf);
hqa_info (mtk, "cmd:%s\n", buf);
switch (buf[0]) {
case 'w':
ssusb_write_reg(mtk, buf);
break;
case 'r':
ssusb_read_regs(mtk, buf);
break;
case 's':
ssusb_set_reg_bits(mtk, buf);
break;
case 'p':
ssusb_print_reg_bits(mtk, buf);
break;
default:
xhci_err(xhci, "No such cmd\n");
hqa_info(mtk, "No such cmd\n");
}
return n;
}
DEVICE_ATTR_RW(reg);