506 lines
13 KiB
C
Executable File
506 lines
13 KiB
C
Executable File
/*
|
|
* 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;
|
|
}
|