kernel_samsung_a34x-permissive/drivers/misc/mediatek/usb20/musb_debugfs.c
2024-04-28 15:51:13 +02:00

782 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 MediaTek Inc.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#ifdef CONFIG_MTK_MUSB_PHY
#include <usb20_phy.h>
#endif
#define MUSB_OTG_CSR0 0x102
#include <musb.h>
#include <musb_core.h>
#include <musb_debug.h>
#include <musb_dr.h>
#define PROC_DIR_MTK_USB "mtk_usb"
#define PROC_FILE_REGDUMP "mtk_usb/regdump"
#define PROC_FILE_TESTMODE "mtk_usb/testmode"
#define PROC_FILE_REGW "mtk_usb/regw"
#define PROC_FILE_REGR "mtk_usb/regr"
#define PROC_FILE_SPEED "mtk_usb/speed"
#define PROC_FILE_NUM 5
static struct proc_dir_entry *proc_files[PROC_FILE_NUM] = {
NULL, NULL, NULL, NULL, NULL};
#define PROC_FILE_MODE "mtk_usb/mode"
#define PROC_FILE_VBUS "mtk_usb/vbus"
#ifdef CONFIG_MTK_MUSB_DUAL_ROLE
#define PROC_FILE_DR_NUM 2
static struct proc_dir_entry *proc_dr_files[PROC_FILE_DR_NUM] = {
NULL, NULL};
#endif
struct musb_register_map {
char *name;
unsigned int offset;
unsigned int size;
};
static const struct musb_register_map musb_regmap[] = {
{"FAddr", 0x00, 8},
{"Power", 0x01, 8},
{"Frame", 0x0c, 16},
{"Index", 0x0e, 8},
{"Testmode", 0x0f, 8},
{"TxMaxPp", 0x10, 16},
{"TxCSRp", 0x12, 16},
{"RxMaxPp", 0x14, 16},
{"RxCSR", 0x16, 16},
{"RxCount", 0x18, 16},
{"ConfigData", 0x1f, 8},
{"DevCtl", 0x60, 8},
{"MISC", 0x61, 8},
{"TxFIFOsz", 0x62, 8},
{"RxFIFOsz", 0x63, 8},
{"TxFIFOadd", 0x64, 16},
{"RxFIFOadd", 0x66, 16},
{"VControl", 0x68, 32},
{"HWVers", 0x6C, 16},
{"EPInfo", 0x78, 8},
{"RAMInfo", 0x79, 8},
{"LinkInfo", 0x7A, 8},
{"VPLen", 0x7B, 8},
{"HS_EOF1", 0x7C, 8},
{"FS_EOF1", 0x7D, 8},
{"LS_EOF1", 0x7E, 8},
{"SOFT_RST", 0x7F, 8},
{"DMA_CNTLch0", 0x204, 16},
{"DMA_ADDRch0", 0x208, 32},
{"DMA_COUNTch0", 0x20C, 32},
{"DMA_CNTLch1", 0x214, 16},
{"DMA_ADDRch1", 0x218, 32},
{"DMA_COUNTch1", 0x21C, 32},
{"DMA_CNTLch2", 0x224, 16},
{"DMA_ADDRch2", 0x228, 32},
{"DMA_COUNTch2", 0x22C, 32},
{"DMA_CNTLch3", 0x234, 16},
{"DMA_ADDRch3", 0x238, 32},
{"DMA_COUNTch3", 0x23C, 32},
{"DMA_CNTLch4", 0x244, 16},
{"DMA_ADDRch4", 0x248, 32},
{"DMA_COUNTch4", 0x24C, 32},
{"DMA_CNTLch5", 0x254, 16},
{"DMA_ADDRch5", 0x258, 32},
{"DMA_COUNTch5", 0x25C, 32},
{"DMA_CNTLch6", 0x264, 16},
{"DMA_ADDRch6", 0x268, 32},
{"DMA_COUNTch6", 0x26C, 32},
{"DMA_CNTLch7", 0x274, 16},
{"DMA_ADDRch7", 0x278, 32},
{"DMA_COUNTch7", 0x27C, 32},
{} /* Terminating Entry */
};
static int musb_regdump_show(struct seq_file *s, void *unused)
{
struct musb *musb = s->private;
unsigned int i;
seq_puts(s, "MUSB (M)HDRC Register Dump\n");
for (i = 0; i < ARRAY_SIZE(musb_regmap); i++) {
switch (musb_regmap[i].size) {
case 8:
seq_printf(s, "%-12s: %02x\n", musb_regmap[i].name,
musb_readb(musb->mregs, musb_regmap[i].offset));
break;
case 16:
seq_printf(s, "%-12s: %04x\n", musb_regmap[i].name,
musb_readw(musb->mregs, musb_regmap[i].offset));
break;
case 32:
seq_printf(s, "%-12s: %08x\n", musb_regmap[i].name,
musb_readl(musb->mregs, musb_regmap[i].offset));
break;
}
}
return 0;
}
static int musb_regdump_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_regdump_show, PDE_DATA(inode));
}
static int musb_test_mode_show(struct seq_file *s, void *unused)
{
struct musb *musb = s->private;
unsigned int test;
test = musb_readb(musb->mregs, MUSB_TESTMODE);
if (test & MUSB_TEST_FORCE_HOST)
seq_puts(s, "force host\n");
if (test & MUSB_TEST_FIFO_ACCESS)
seq_puts(s, "fifo access\n");
if (test & MUSB_TEST_FORCE_FS)
seq_puts(s, "force full-speed\n");
if (test & MUSB_TEST_FORCE_HS)
seq_puts(s, "force high-speed\n");
if (test & MUSB_TEST_PACKET)
seq_puts(s, "test packet\n");
if (test & MUSB_TEST_K)
seq_puts(s, "test K\n");
if (test & MUSB_TEST_J)
seq_puts(s, "test J\n");
if (test & MUSB_TEST_SE0_NAK)
seq_puts(s, "test SE0 NAK\n");
return 0;
}
static const struct file_operations musb_regdump_fops = {
.open = musb_regdump_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int musb_test_mode_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_test_mode_show, PDE_DATA(inode));
}
void musbdebugfs_otg_write_fifo(u16 len, u8 *buf, struct musb *mtk_musb)
{
int i;
DBG(0, "musb_otg_write_fifo, len=%d\n", len);
for (i = 0; i < len; i++)
musb_writeb(mtk_musb->mregs, 0x20, *(buf + i));
}
void musbdebugfs_h_setup(struct usb_ctrlrequest *setup, struct musb *mtk_musb)
{
unsigned short csr0;
DBG(0, "musb_h_setup++\n");
musbdebugfs_otg_write_fifo
(sizeof(struct usb_ctrlrequest), (u8 *)setup, mtk_musb);
csr0 = musb_readw(mtk_musb->mregs, MUSB_OTG_CSR0);
DBG(0, "musb_h_setup,csr0=0x%x\n", csr0);
csr0 |= MUSB_CSR0_H_SETUPPKT | MUSB_CSR0_TXPKTRDY;
musb_writew(mtk_musb->mregs, MUSB_OTG_CSR0, csr0);
DBG(0, "musb_h_setup--\n");
}
static ssize_t musb_test_mode_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct musb *musb = s->private;
u8 test = 0;
char buf[20];
unsigned char power;
struct usb_ctrlrequest setup_packet;
setup_packet.bRequestType =
USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE;
setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR;
setup_packet.wIndex = 0;
setup_packet.wValue = 0x0100;
setup_packet.wLength = 0x40;
memset(buf, 0x00, sizeof(buf));
if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
if (!strncmp(buf, "force host", 9))
test = MUSB_TEST_FORCE_HOST;
if (!strncmp(buf, "fifo access", 11))
test = MUSB_TEST_FIFO_ACCESS;
if (!strncmp(buf, "force full-speed", 15))
test = MUSB_TEST_FORCE_FS;
if (!strncmp(buf, "force high-speed", 15))
test = MUSB_TEST_FORCE_HS;
if (!strncmp(buf, "test packet", 10)) {
test = MUSB_TEST_PACKET;
musb_load_testpacket(musb);
}
if (!strncmp(buf, "test suspend_resume", 18)) {
DBG(0, "HS_HOST_PORT_SUSPEND_RESUME\n");
msleep(5000); /* the host must continue sending SOFs for 15s */
DBG(0, "please begin to trigger suspend!\n");
msleep(10000);
power = musb_readb(musb->mregs, MUSB_POWER);
power |= MUSB_POWER_SUSPENDM | MUSB_POWER_ENSUSPEND;
musb_writeb(musb->mregs, MUSB_POWER, power);
msleep(5000);
DBG(0, "please begin to trigger resume!\n");
msleep(10000);
power &= ~MUSB_POWER_SUSPENDM;
power |= MUSB_POWER_RESUME;
musb_writeb(musb->mregs, MUSB_POWER, power);
mdelay(25);
power &= ~MUSB_POWER_RESUME;
musb_writeb(musb->mregs, MUSB_POWER, power);
/* SOF continue */
musbdebugfs_h_setup(&setup_packet, musb);
return count;
}
if (!strncmp(buf, "test get_descripter", 18)) {
DBG(0, "SINGLE_STEP_GET_DEVICE_DESCRIPTOR\n");
/* the host issues SOFs for 15s allowing the test engineer
* to raise the scope trigger just above the SOF voltage level.
*/
msleep(15000);
musbdebugfs_h_setup(&setup_packet, musb);
return count;
}
if (!strncmp(buf, "test K", 6))
test = MUSB_TEST_K;
if (!strncmp(buf, "test J", 6))
test = MUSB_TEST_J;
if (!strncmp(buf, "test SE0 NAK", 12))
test = MUSB_TEST_SE0_NAK;
musb_writeb(musb->mregs, MUSB_TESTMODE, test);
return count;
}
static const struct file_operations musb_test_mode_fops = {
.open = musb_test_mode_open,
.write = musb_test_mode_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static inline int my_isspace(char c)
{
return (c == ' ' || c == '\t' || c == '\n' || c == '\12');
}
static inline int my_isupper(char c)
{
return (c >= 'A' && c <= 'Z');
}
static inline int my_isalpha(char c)
{
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
}
static inline int my_isdigit(char c)
{
return (c >= '0' && c <= '9');
}
static unsigned my_strtoul
(const char *nptr, char **endptr, unsigned int base)
{
const char *s = nptr;
unsigned long acc;
int c;
unsigned long cutoff;
int neg = 0, any, cutlim;
do {
c = *s++;
} while (my_isspace(c));
if (c == '-') {
neg = 1;
c = *s++;
} else if (c == '+')
c = *s++;
if ((base == 0 || base == 16) &&
c == '0' && (*s == 'x' || *s == 'X')) {
c = s[1];
s += 2;
base = 16;
} else if ((base == 0 || base == 2) &&
c == '0' && (*s == 'b' || *s == 'B')) {
c = s[1];
s += 2;
base = 2;
}
if (base == 0)
base = c == '0' ? 8 : 10;
cutoff = (unsigned long)ULONG_MAX / (unsigned long)base;
cutlim = (unsigned long)ULONG_MAX % (unsigned long)base;
for (acc = 0, any = 0;; c = *s++) {
if (my_isdigit(c))
c -= '0';
else if (my_isalpha(c))
c -= my_isupper(c) ? 'A' - 10 : 'a' - 10;
else
break;
if (c >= base)
break;
if ((any < 0 || acc > cutoff || acc == cutoff) && c > cutlim)
any = -1;
else {
any = 1;
acc *= base;
acc += c;
}
}
if (any < 0)
acc = ULONG_MAX;
else if (neg)
acc = -acc;
if (endptr != 0)
*endptr = (char *)(any ? s - 1 : nptr);
return acc;
}
static int musb_regw_show(struct seq_file *s, void *unused)
{
DBG(0, "%s -> Called\n", __func__);
pr_notice("Uage:\n");
pr_notice("Mac Write: echo mac:addr:data > regw\n");
pr_notice("Phy Write: echo phy:addr:data > regw\n");
return 0;
}
static int musb_regw_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_regw_show, PDE_DATA(inode));
}
static ssize_t musb_regw_mode_write
(struct file *file, const char __user *ubuf
, size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct musb *musb = s->private;
char buf[20];
u8 is_mac = 0;
char *tmp1 = NULL;
char *tmp2 = NULL;
unsigned int offset = 0;
u8 data = 0;
memset(buf, 0x00, sizeof(buf));
pr_notice("%s -> Called\n", __func__);
if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
buf[19] = '\0';
if ((!strncmp(buf, "MAC", 3)) || (!strncmp(buf, "mac", 3)))
is_mac = 1;
else if ((!strncmp(buf, "PHY", 3)) || (!strncmp(buf, "phy", 3)))
is_mac = 0;
else
return -EFAULT;
tmp1 = strchr(buf, ':');
if (tmp1 == NULL)
return -EFAULT;
tmp1++;
if (strlen(tmp1) == 0)
return -EFAULT;
tmp2 = strrchr(buf, ':');
if (tmp2 == NULL)
return -EFAULT;
tmp2++;
if (strlen(tmp2) == 0)
return -EFAULT;
offset = my_strtoul(tmp1, NULL, 0);
data = my_strtoul(tmp2, NULL, 0);
if (is_mac == 1) {
pr_notice("Mac base adddr 0x%lx, Write 0x%x[0x%x]\n",
(unsigned long)musb->mregs, offset, data);
musb_writeb(musb->mregs, offset, data);
} else {
if ((offset % 4) != 0) {
pr_notice("Must use 32bits alignment address\n");
return count;
}
#ifdef CONFIG_MTK_MUSB_PHY
pr_notice("Phy base adddr 0x%lx, Write 0x%x[0x%x]\n",
(unsigned long)((void __iomem *)
(((unsigned long)musb->xceiv->io_priv)
+ 0x800)), offset, data);
USBPHY_WRITE32(offset, data);
#endif
}
return count;
}
static const struct file_operations musb_regw_fops = {
.open = musb_regw_open,
.write = musb_regw_mode_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int musb_regr_show(struct seq_file *s, void *unused)
{
DBG(0, "%s -> Called\n"
"Uage:\n"
"Mac Read: echo mac:addr > regr\n"
"Phy Read: echo phy:addr > regr\n", __func__);
return 0;
}
static int musb_regr_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_regr_show, PDE_DATA(inode));
}
static ssize_t musb_regr_mode_write(struct file *file,
const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct musb *musb = s->private;
char buf[20];
u8 is_mac = 0;
char *tmp = NULL;
unsigned int offset = 0;
memset(buf, 0x00, sizeof(buf));
pr_notice("%s -> Called\n", __func__);
if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
buf[19] = '\0';
if ((!strncmp(buf, "MAC", 3)) || (!strncmp(buf, "mac", 3)))
is_mac = 1;
else if ((!strncmp(buf, "PHY", 3)) || (!strncmp(buf, "phy", 3)))
is_mac = 0;
else
return -EFAULT;
tmp = strrchr(buf, ':');
if (tmp == NULL)
return -EFAULT;
tmp++;
if (strlen(tmp) == 0)
return -EFAULT;
offset = my_strtoul(tmp, NULL, 0);
if (is_mac == 1)
pr_notice("Read Mac base adddr 0x%lx, Read 0x%x[0x%x]\n",
(unsigned long)musb->mregs, offset
, musb_readb(musb->mregs, offset));
else {
if ((offset % 4) != 0) {
pr_notice("Must use 32bits alignment address\n");
return count;
}
#ifdef CONFIG_MTK_MUSB_PHY
pr_notice("Read Phy base adddr 0x%lx, Read 0x%x[0x%x]\n",
(unsigned long)((void __iomem *)
(((unsigned long)musb->xceiv->io_priv)
+ 0x800)), offset,
USBPHY_READ32(offset));
#endif
}
return count;
}
static const struct file_operations musb_regr_fops = {
.open = musb_regr_open,
.write = musb_regr_mode_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int musb_speed_show(struct seq_file *s, void *unused)
{
seq_printf(s, "musb_speed = %d\n", musb_speed);
return 0;
}
static int musb_speed_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_speed_show, PDE_DATA(inode));
}
static ssize_t musb_speed_write(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
char buf[20];
unsigned int val;
memset(buf, 0x00, sizeof(buf));
if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
if (kstrtouint(buf, 10, &val) == 0 && val >= 0 && val <= 1)
musb_speed = val;
else
return -EINVAL;
return count;
}
static const struct file_operations musb_speed_fops = {
.open = musb_speed_open,
.write = musb_speed_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
int musb_init_debugfs(struct musb *musb)
{
int ret, idx = 0;
proc_mkdir(PROC_DIR_MTK_USB, NULL);
proc_files[idx] = proc_create_data(PROC_FILE_REGDUMP, 0444,
NULL, &musb_regdump_fops, musb);
if (!proc_files[idx]) {
ret = -ENOMEM;
goto err1;
}
idx++;
proc_files[idx] = proc_create_data(PROC_FILE_TESTMODE, 0644,
NULL, &musb_test_mode_fops, musb);
if (!proc_files[idx]) {
ret = -ENOMEM;
goto err1;
}
idx++;
proc_files[idx] = proc_create_data(PROC_FILE_REGW, 0644,
NULL, &musb_regw_fops, musb);
if (!proc_files[idx]) {
ret = -ENOMEM;
goto err1;
}
idx++;
proc_files[idx] = proc_create_data(PROC_FILE_REGR, 0644,
NULL, &musb_regr_fops, musb);
if (!proc_files[idx]) {
ret = -ENOMEM;
goto err1;
}
idx++;
proc_files[idx] = proc_create_data(PROC_FILE_SPEED, 0644,
NULL, &musb_speed_fops, musb);
if (!proc_files[idx]) {
ret = -ENOMEM;
goto err1;
}
return 0;
err1:
for (; idx >= 0; idx--) {
if (proc_files[idx]) {
proc_remove(proc_files[idx]);
proc_files[idx] = NULL;
}
}
return ret;
}
void /* __init_or_exit */ musb_exit_debugfs(struct musb *musb)
{
int idx = 0;
for (; idx < PROC_FILE_NUM; idx++) {
if (proc_files[idx]) {
proc_remove(proc_files[idx]);
proc_files[idx] = NULL;
}
}
}
#ifdef CONFIG_MTK_MUSB_DUAL_ROLE
static int musb_mode_show(struct seq_file *sf, void *unused)
{
struct musb *musb = sf->private;
struct mt_usb_glue *glue =
container_of(&musb, struct mt_usb_glue, mtk_musb);
seq_printf(sf, "current mode: %s(%s drd)\n(echo device/host)\n",
musb->is_host ? "host" : "device",
glue->otg_sx.manual_drd_enabled ? "manual" : "auto");
return 0;
}
static int musb_mode_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_mode_show, PDE_DATA(inode));
}
static ssize_t musb_mode_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct seq_file *sf = file->private_data;
struct musb *musb = sf->private;
char buf[16];
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
if (!strncmp(buf, "host", 4) && !musb->is_host) {
mt_usb_mode_switch(musb, 1);
} else if (!strncmp(buf, "device", 6) && musb->is_host) {
mt_usb_mode_switch(musb, 0);
} else {
dev_err(musb->controller, "wrong or duplicated setting\n");
return -EINVAL;
}
return count;
}
static const struct file_operations musb_mode_fops = {
.open = musb_mode_open,
.write = musb_mode_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int musb_vbus_show(struct seq_file *sf, void *unused)
{
struct musb *musb = sf->private;
struct mt_usb_glue *glue =
container_of(&musb, struct mt_usb_glue, mtk_musb);
struct otg_switch_mtk *otg_sx = &glue->otg_sx;
seq_printf(sf, "vbus state: %s\n(echo on/off)\n",
regulator_is_enabled(otg_sx->vbus) ? "on" : "off");
return 0;
}
static int musb_vbus_open(struct inode *inode, struct file *file)
{
return single_open(file, musb_vbus_show, PDE_DATA(inode));
}
static ssize_t musb_vbus_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct seq_file *sf = file->private_data;
struct musb *musb = sf->private;
struct mt_usb_glue *glue =
container_of(&musb, struct mt_usb_glue, mtk_musb);
struct otg_switch_mtk *otg_sx = &glue->otg_sx;
char buf[16];
bool enable;
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
if (kstrtobool(buf, &enable)) {
dev_err(musb->controller, "wrong setting\n");
return -EINVAL;
}
mt_usb_set_vbus(otg_sx, enable);
return count;
}
static const struct file_operations musb_vbus_fops = {
.open = musb_vbus_open,
.write = musb_vbus_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void musb_dr_debugfs_init(struct musb *musb)
{
int idx = 0;
proc_mkdir(PROC_DIR_MTK_USB, NULL);
proc_dr_files[idx++] = proc_create_data(PROC_FILE_MODE, 0644,
NULL, &musb_mode_fops, musb);
proc_dr_files[idx++] = proc_create_data(PROC_FILE_VBUS, 0644,
NULL, &musb_vbus_fops, musb);
}
#endif