blob: 78b66b5f74c1dd344568447c0d0d537f0a7a3f64 [file] [log] [blame]
/*
* Samsung EXYNOS SoC series USB DRD PHY DebugFS file
*
* Phy provider for USB 3.0 DRD controller on Exynos SoC series
*
* Copyright (C) 2016 Samsung Electronics Co., Ltd.
* Author: Kyounghye Yun <k-hye.yun@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/ptrace.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/usb/ch9.h>
#include "phy-exynos-usbdrd.h"
#include "phy-exynos-usbdp-reg.h"
#include "phy-exynos-debug.h"
static struct exynos_debugfs_prvdata *prvdata_dp;
/* PHY Combo DP register set */
static const struct debugfs_reg32 exynos_usb3drd_dp_regs[] = {
dump_register_dp(TRSV_R01),
dump_register_dp(TRSV_R02),
dump_register_dp(TRSV_R03),
dump_register_dp(TRSV_R04),
dump_register_dp(TRSV_R0C),
};
/* PHY Combo DP register set */
static const struct debugfs_regmap32 exynos_usb3drd_dp_regmap[] = {
dump_regmap_dp_mask(TRSV_R01, USBDP_TRSV01, RXAFE_LEQ_ISEL_GEN2, 6),
dump_regmap_dp_mask(TRSV_R01, USBDP_TRSV01, RXAFE_LEQ_ISEL_GEN1, 4),
dump_regmap_dp_mask(TRSV_R01, USBDP_TRSV01, RXAFE_CTLE_SEL, 2),
dump_regmap_dp_mask(TRSV_R01, USBDP_TRSV01, RXAFE_SCLBUF_EN, 0),
dump_regmap_dp_mask(TRSV_R02, USBDP_TRSV02, RXAFE_LEQ_CSEL_GEN2, 4),
dump_regmap_dp_mask(TRSV_R02, USBDP_TRSV02, RXAFE_LEQ_CSEL_GEN1, 0),
dump_regmap_dp_mask(TRSV_R03, USBDP_TRSV03, RXAFE_LEQ_RSEL_GEN2, 3),
dump_regmap_dp_mask(TRSV_R03, USBDP_TRSV03, RXAFE_LEQ_RSEL_GEN1, 0),
dump_regmap_dp_mask(TRSV_R04, USBDP_TRSV04, RXAFE_SQ_VFFSET_CTRL, 0),
dump_regmap_dp_mask(TRSV_R0C, USBDP_TRSV0C, MAN_TX_DE_EMP_LVL, 4),
dump_regmap_dp_mask(TRSV_R0C, USBDP_TRSV0C, MAN_TX_DRVR_LVL, 0),
};
static int debugfs_phy_power_state(struct exynos_usbdrd_phy *phy_drd, int phy_index)
{
struct regmap *reg_pmu;
u32 pmu_offset;
int phy_on;
int ret;
reg_pmu = phy_drd->phys[phy_index].reg_pmu;
pmu_offset = phy_drd->phys[phy_index].pmu_offset;
ret = regmap_read(reg_pmu, pmu_offset, &phy_on);
if (ret) {
dev_err(phy_drd->dev, "Can't read 0x%x\n", pmu_offset);
return ret;
}
phy_on &= phy_drd->phys[phy_index].pmu_mask;
return phy_on;
}
static int debugfs_print_regmap(struct seq_file *s, const struct debugfs_regmap32 *regs,
int nregs, void __iomem *base,
const struct debugfs_reg32 *parent)
{
int i, j = 0;
int bit = 0;
unsigned int bitmask;
int max_string = 24;
int calc_tab;
u32 bit_value, reg_value;
reg_value = readl(base + parent->offset);
seq_printf(s, "%s (0x%04lx) : 0x%08x\n", parent->name,
parent->offset, reg_value);
for (i = 0; i < nregs; i++, regs++) {
if (!strcmp(regs->name, parent->name)) {
bit_value = (reg_value & regs->bitmask) >> regs->bitoffset;
seq_printf(s, "\t%s", regs->bitname);
calc_tab = max_string/8 - strlen(regs->bitname)/8;
for (j = 0 ; j < calc_tab; j++)
seq_puts(s, "\t");
if (regs->mask) {
bitmask = regs->bitmask;
bitmask = bitmask >> regs->bitoffset;
while (bitmask) {
bitmask = bitmask >> 1;
bit++;
}
seq_printf(s, "[%d:%d]\t: 0x%x\n", (int)regs->bitoffset,
((int)regs->bitoffset + bit - 1), bit_value);
bit = 0;
} else {
seq_printf(s, "[%d]\t: 0x%x\n", (int)regs->bitoffset,
bit_value);
}
}
}
return 0;
}
static int debugfs_show_regmap(struct seq_file *s, void *data)
{
struct exynos_debugfs_prvdata *prvdata = s->private;
struct debugfs_regset_map *regmap = prvdata->regmap;
struct debugfs_regset32 *regset = prvdata->regset;
const struct debugfs_reg32 *regs = regset->regs;
int phy_on, i = 0;
phy_on = debugfs_phy_power_state(prvdata->phy_drd, 0);
if (phy_on < 0) {
seq_printf(s, "can't read PHY register, error : %d\n", phy_on);
return -EIO;
}
if (!phy_on) {
seq_puts(s, "can't get PHY register, PHY: Power OFF\n");
return 0;
}
for (i = 0; i < regset->nregs; i++, regs++) {
debugfs_print_regmap(s, regmap->regs, regmap->nregs,
regset->base, regs);
}
return 0;
}
static int debugfs_open_regmap(struct inode *inode, struct file *file)
{
return single_open(file, debugfs_show_regmap, inode->i_private);
}
static const struct file_operations fops_regmap = {
.open = debugfs_open_regmap,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int debugfs_print_regdump(struct seq_file *s, struct exynos_usbdrd_phy *phy_drd,
const struct debugfs_reg32 *regs, int nregs,
void __iomem *base)
{
int phy_on;
int i;
for (i = 0; i < EXYNOS_DRDPHYS_NUM; i++) {
phy_on = debugfs_phy_power_state(phy_drd, i);
if (phy_on < 0) {
seq_printf(s, "can't read PHY register, error : %d\n", phy_on);
return phy_on;
}
if (!phy_on) {
seq_printf(s, "can't get PHY register, PHY%d : Power OFF\n", i);
continue;
}
for (i = 0; i < nregs; i++, regs++) {
seq_printf(s, "%s", regs->name);
if (strlen(regs->name) < 8)
seq_puts(s, "\t\t");
else
seq_puts(s, "\t");
seq_printf(s, "= 0x%08x\n", readl(base + regs->offset));
}
}
return 0;
}
static int debugfs_show_regdump(struct seq_file *s, void *data)
{
struct exynos_debugfs_prvdata *prvdata = s->private;
struct debugfs_regset32 *regset = prvdata->regset;
int ret;
ret = debugfs_print_regdump(s, prvdata->phy_drd, regset->regs,
regset->nregs, regset->base);
if (ret < 0)
return ret;
return 0;
}
static int debugfs_open_regdump(struct inode *inode, struct file *file)
{
return single_open(file, debugfs_show_regdump, inode->i_private);
}
static const struct file_operations fops_regdump = {
.open = debugfs_open_regdump,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int debugfs_show_bitset(struct seq_file *s, void *data)
{
char *b_name = s->private;
struct debugfs_regset_map *regmap = prvdata_dp->regmap;
const struct debugfs_regmap32 *cmp = regmap->regs;
const struct debugfs_regmap32 *regs;
unsigned int bitmask;
int i, bit = 0;
u32 reg_value, bit_value;
u32 detect_regs = 0;
for (i = 0; i < regmap->nregs; i++, cmp++) {
if (!strcmp(cmp->bitname, b_name)) {
regs = cmp;
detect_regs = 1;
break;
}
}
if (!detect_regs)
return -EINVAL;
reg_value = readl(prvdata_dp->regset->base + regs->offset);
bit_value = (reg_value & regs->bitmask) >> regs->bitoffset;
if (regs->mask) {
bitmask = regs->bitmask;
bitmask = bitmask >> regs->bitoffset;
while (bitmask) {
bitmask = bitmask >> 1;
bit++;
}
seq_printf(s, "%s [%d:%d] = 0x%x\n", regs->name,
(int)regs->bitoffset,
((int)regs->bitoffset + bit - 1), bit_value);
} else {
seq_printf(s, "%s [%d] = 0x%x\n", regs->name,
(int)regs->bitoffset, bit_value);
}
return 0;
}
static ssize_t debugfs_write_regset(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
char *reg_name = s->private;
struct debugfs_regset32 *regset = prvdata_dp->regset;
const struct debugfs_reg32 *regs = regset->regs;
unsigned long value;
char buf[8];
int i;
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
return -EFAULT;
value = simple_strtol(buf, NULL, 16);
for (i = 0; i < regset->nregs; i++, regs++) {
if (!strcmp(regs->name, reg_name))
break;
}
writel(value, regset->base + regs->offset);
return count;
}
static ssize_t debugfs_write_bitset(struct file *file,
const char __user *ubuf, size_t count, loff_t *ppos)
{
struct seq_file *s = file->private_data;
char *b_name = s->private;
struct debugfs_regset_map *regmap = prvdata_dp->regmap;
const struct debugfs_regmap32 *regs = regmap->regs;
unsigned long value;
char buf[32];
int i;
u32 reg_value;
if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) {
seq_printf(s, "%s, write error\n", __func__);
return -EFAULT;
}
value = simple_strtol(buf, NULL, 2);
for (i = 0; i < regmap->nregs; i++, regs++) {
if (!strcmp(regs->bitname, b_name))
break;
}
value = value << regs->bitoffset;
reg_value = readl(prvdata_dp->regset->base + regs->offset);
reg_value &= ~(regs->bitmask);
reg_value |= (u32)value;
writel(reg_value, prvdata_dp->regset->base + regs->offset);
return count;
}
static int debugfs_open_bitset(struct inode *inode, struct file *file)
{
return single_open(file, debugfs_show_bitset, inode->i_private);
}
static int debugfs_show_regset(struct seq_file *s, void *data)
{
char *p_name = s->private;
struct debugfs_regset32 *regset = prvdata_dp->regset;
struct debugfs_regset_map *regmap = prvdata_dp->regmap;
const struct debugfs_reg32 *regs = regset->regs;
const struct debugfs_reg32 *parents;
u32 detect_regs = 0;
int i;
for (i = 0; i < regset->nregs; i++, regs++) {
if (!strcmp(regs->name, p_name)) {
parents = regs;
detect_regs = 1;
break;
}
}
if (!detect_regs)
return -EINVAL;
debugfs_print_regmap(s, prvdata_dp->regmap->regs, regmap->nregs,
regset->base, parents);
return 0;
}
static int debugfs_open_regset(struct inode *inode, struct file *file)
{
return single_open(file, debugfs_show_regset, inode->i_private);
}
static const struct file_operations fops_regset = {
.open = debugfs_open_regset,
.write = debugfs_write_regset,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations fops_bitset = {
.open = debugfs_open_bitset,
.write = debugfs_write_bitset,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int debugfs_create_regfile(struct exynos_debugfs_prvdata *prvdata,
const struct debugfs_reg32 *parents,
struct dentry *root)
{
struct debugfs_regset_map *regmap = prvdata->regmap;
const struct debugfs_regmap32 *regs = regmap->regs;
struct dentry *file;
int i, ret;
file = debugfs_create_file(parents->name, 0644, root,
parents->name, &fops_regset);
if (!file) {
ret = -ENOMEM;
return ret;
}
for (i = 0; i < regmap->nregs; i++, regs++) {
if (!strcmp(regs->name, parents->name)) {
file = debugfs_create_file(regs->bitname, 0644,
root, regs->bitname, &fops_bitset);
if (!file) {
ret = -ENOMEM;
return ret;
}
}
}
return 0;
}
static int debugfs_create_regdir(struct exynos_debugfs_prvdata *prvdata,
struct dentry *root)
{
struct exynos_usbdrd_phy *phy_drd = prvdata->phy_drd;
struct debugfs_regset32 *regset = prvdata->regset;
const struct debugfs_reg32 *regs = regset->regs;
struct dentry *dir;
int ret, i;
for (i = 0; i < regset->nregs; i++, regs++) {
dir = debugfs_create_dir(regs->name, root);
if (!dir) {
dev_err(phy_drd->dev, "failed to create '%s' reg dir",
regs->name);
return -ENOMEM;
}
ret = debugfs_create_regfile(prvdata, regs, dir);
if (ret < 0) {
dev_err(phy_drd->dev, "failed to create bitfile for %s, error : %d\n",
regs->name, ret);
return ret;
}
}
return 0;
}
int exynos_usbdrd_dp_debugfs_init(struct exynos_usbdrd_phy *phy_drd)
{
struct device *dev = phy_drd->dev;
struct dentry *root;
struct dentry *dir;
struct dentry *file;
u32 version = phy_drd->usbphy_sub_info.version;
int ret;
root = debugfs_create_dir("110a0000.usbdp", NULL);
if (!root) {
dev_err(dev, "failed to create root directory for USBPHY debugfs");
ret = -ENOMEM;
goto err0;
}
prvdata_dp = devm_kmalloc(dev, sizeof(struct exynos_debugfs_prvdata), GFP_KERNEL);
if (!prvdata_dp) {
dev_err(dev, "failed to alloc private data for debugfs");
ret = -ENOMEM;
goto err1;
}
prvdata_dp->root = root;
prvdata_dp->phy_drd = phy_drd;
prvdata_dp->regset = devm_kmalloc(dev, sizeof(*prvdata_dp->regset), GFP_KERNEL);
if (!prvdata_dp->regset) {
dev_err(dev, "failed to alloc regmap");
ret = -ENOMEM;
goto err1;
}
if (phy_drd->usbphy_sub_info.version == EXYNOS_USBCON_VER_04_0_0) {
/* for USB3PHY Lhotse */
prvdata_dp->regset->regs = exynos_usb3drd_dp_regs;
prvdata_dp->regset->nregs = ARRAY_SIZE(exynos_usb3drd_dp_regs);
}
prvdata_dp->regset->base = phy_drd->reg_phy2;
prvdata_dp->regmap = devm_kmalloc(dev, sizeof(*prvdata_dp->regmap), GFP_KERNEL);
if (!prvdata_dp->regmap) {
dev_err(dev, "failed to alloc regmap");
ret = -ENOMEM;
goto err1;
}
if (version == EXYNOS_USBCON_VER_04_0_0) {
/* for USB3PHY Lhotse */
prvdata_dp->regmap->regs = exynos_usb3drd_dp_regmap;
prvdata_dp->regmap->nregs = ARRAY_SIZE(exynos_usb3drd_dp_regmap);
}
file = debugfs_create_file("regdump", 0444, root, prvdata_dp, &fops_regdump);
if (!file) {
dev_err(dev, "failed to create file for register dump");
ret = -ENOMEM;
goto err1;
}
file = debugfs_create_file("regmap", 0444, root, prvdata_dp, &fops_regmap);
if (!file) {
dev_err(dev, "failed to create file for register dump");
ret = -ENOMEM;
goto err1;
}
dir = debugfs_create_dir("regset", root);
if (!dir) {
ret = -ENOMEM;
goto err1;
}
ret = debugfs_create_regdir(prvdata_dp, dir);
if (ret < 0) {
dev_err(dev, "failed to create regfile, error = %d\n", ret);
goto err1;
}
return 0;
err1:
debugfs_remove_recursive(root);
err0:
return ret;
}