1025 lines
27 KiB
C
1025 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Rockchip USB3.0 and PCIE COMBPHY with Innosilicon IP block driver
|
|
*
|
|
* Copyright (C) 2018 Fuzhou Rockchip Electronics Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/phy/pcie.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/reset.h>
|
|
#include <dt-bindings/phy/phy.h>
|
|
|
|
#define BIT_WRITEABLE_SHIFT 16
|
|
|
|
struct rockchip_combphy_priv;
|
|
|
|
enum rockchip_combphy_rst {
|
|
OTG_RSTN = 0,
|
|
PHY_POR_RSTN = 1,
|
|
PHY_APB_RSTN = 2,
|
|
PHY_PIPE_RSTN = 3,
|
|
PHY_GRF_P_RSTN = 4,
|
|
PHY_RESET_MAX = 5,
|
|
};
|
|
|
|
struct combphy_reg {
|
|
u32 offset;
|
|
u32 bitend;
|
|
u32 bitstart;
|
|
u32 disable;
|
|
u32 enable;
|
|
};
|
|
|
|
struct rockchip_combphy_grfcfg {
|
|
struct combphy_reg pipe_l1_sel;
|
|
struct combphy_reg pipe_l1_set;
|
|
struct combphy_reg pipe_l1pd_sel;
|
|
struct combphy_reg pipe_l1pd_p3;
|
|
struct combphy_reg pipe_l0pd_sel;
|
|
struct combphy_reg pipe_l0pd_p3;
|
|
struct combphy_reg pipe_clk_sel;
|
|
struct combphy_reg pipe_clk_set;
|
|
struct combphy_reg pipe_rate_sel;
|
|
struct combphy_reg pipe_rate_set;
|
|
struct combphy_reg pipe_mode_sel;
|
|
struct combphy_reg pipe_mode_set;
|
|
struct combphy_reg pipe_txrx_sel;
|
|
struct combphy_reg pipe_txrx_set;
|
|
struct combphy_reg pipe_width_sel;
|
|
struct combphy_reg pipe_width_set;
|
|
struct combphy_reg pipe_usb3_sel;
|
|
struct combphy_reg pipe_pll_lock;
|
|
struct combphy_reg pipe_status_l0;
|
|
struct combphy_reg pipe_l0rxterm_sel;
|
|
struct combphy_reg pipe_l1rxterm_sel;
|
|
struct combphy_reg pipe_l0rxterm_set;
|
|
struct combphy_reg pipe_l1rxterm_set;
|
|
struct combphy_reg pipe_l0rxelec_set;
|
|
struct combphy_reg u3_port_disable;
|
|
struct combphy_reg u3_port_num;
|
|
};
|
|
|
|
struct rockchip_combphy_cfg {
|
|
const struct rockchip_combphy_grfcfg grfcfg;
|
|
int (*combphy_cfg)(struct rockchip_combphy_priv *priv);
|
|
int (*combphy_low_power_ctrl)(struct rockchip_combphy_priv *priv,
|
|
bool en);
|
|
};
|
|
|
|
struct rockchip_combphy_priv {
|
|
bool phy_initialized;
|
|
bool phy_suspended;
|
|
u8 phy_type;
|
|
void __iomem *mmio;
|
|
struct device *dev;
|
|
struct clk *ref_clk;
|
|
struct phy *phy;
|
|
struct regmap *combphy_grf;
|
|
struct regmap *usb_pcie_grf;
|
|
struct reset_control *rsts[PHY_RESET_MAX];
|
|
const struct rockchip_combphy_cfg *cfg;
|
|
};
|
|
|
|
static const char *get_reset_name(enum rockchip_combphy_rst rst)
|
|
{
|
|
switch (rst) {
|
|
case OTG_RSTN:
|
|
return "otg-rst";
|
|
case PHY_POR_RSTN:
|
|
return "combphy-por";
|
|
case PHY_APB_RSTN:
|
|
return "combphy-apb";
|
|
case PHY_PIPE_RSTN:
|
|
return "combphy-pipe";
|
|
case PHY_GRF_P_RSTN:
|
|
return "usb3phy_grf_p";
|
|
default:
|
|
return "invalid";
|
|
}
|
|
}
|
|
|
|
static inline bool param_read(struct regmap *base,
|
|
const struct combphy_reg *reg, u32 val)
|
|
{
|
|
int ret;
|
|
u32 mask, orig, tmp;
|
|
|
|
ret = regmap_read(base, reg->offset, &orig);
|
|
if (ret)
|
|
return false;
|
|
|
|
mask = GENMASK(reg->bitend, reg->bitstart);
|
|
tmp = (orig & mask) >> reg->bitstart;
|
|
|
|
return tmp == val;
|
|
}
|
|
|
|
static inline int param_write(struct regmap *base,
|
|
const struct combphy_reg *reg, bool en)
|
|
{
|
|
u32 val, mask, tmp;
|
|
|
|
tmp = en ? reg->enable : reg->disable;
|
|
mask = GENMASK(reg->bitend, reg->bitstart);
|
|
val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT);
|
|
|
|
return regmap_write(base, reg->offset, val);
|
|
}
|
|
|
|
static ssize_t u3phy_mode_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct rockchip_combphy_priv *priv = dev_get_drvdata(device);
|
|
|
|
if (param_read(priv->usb_pcie_grf, &priv->cfg->grfcfg.u3_port_num, 0))
|
|
return sprintf(buf, "u2\n");
|
|
else
|
|
return sprintf(buf, "u3\n");
|
|
}
|
|
|
|
static ssize_t u3phy_mode_store(struct device *device,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct rockchip_combphy_priv *priv = dev_get_drvdata(device);
|
|
|
|
if (!strncmp(buf, "u3", 2) &&
|
|
param_read(priv->usb_pcie_grf, &priv->cfg->grfcfg.u3_port_num, 0)) {
|
|
/*
|
|
* Enable USB 3.0 rx termination, need to select
|
|
* pipe_l0_rxtermination from USB 3.0 controller.
|
|
*/
|
|
param_write(priv->combphy_grf,
|
|
&priv->cfg->grfcfg.pipe_l0rxterm_sel, false);
|
|
/* Set xHCI USB 3.0 port number to 1 */
|
|
param_write(priv->usb_pcie_grf,
|
|
&priv->cfg->grfcfg.u3_port_num, true);
|
|
/* Enable xHCI USB 3.0 port */
|
|
param_write(priv->usb_pcie_grf,
|
|
&priv->cfg->grfcfg.u3_port_disable, false);
|
|
dev_info(priv->dev, "Set usb3.0 and usb2.0 mode successfully\n");
|
|
} else if (!strncmp(buf, "u2", 2) &&
|
|
param_read(priv->usb_pcie_grf,
|
|
&priv->cfg->grfcfg.u3_port_num, 1)) {
|
|
/*
|
|
* Disable USB 3.0 rx termination, need to select
|
|
* pipe_l0_rxtermination from grf and remove rx
|
|
* termimation by grf.
|
|
*/
|
|
param_write(priv->combphy_grf,
|
|
&priv->cfg->grfcfg.pipe_l0rxterm_set, false);
|
|
param_write(priv->combphy_grf,
|
|
&priv->cfg->grfcfg.pipe_l0rxterm_sel, true);
|
|
/* Set xHCI USB 3.0 port number to 0 */
|
|
param_write(priv->usb_pcie_grf,
|
|
&priv->cfg->grfcfg.u3_port_num, false);
|
|
/* Disable xHCI USB 3.0 port */
|
|
param_write(priv->usb_pcie_grf,
|
|
&priv->cfg->grfcfg.u3_port_disable, true);
|
|
/*
|
|
* Note:
|
|
* Don't disable the USB 3.0 PIPE pclk here(set reg
|
|
* pipe_usb3_sel to false), because USB 3.0 PHY depend
|
|
* on this clk, if we disable it, we need to reinit
|
|
* the USB 3.0 PHY when use USB 3.0 mode, in order to
|
|
* simplify the process, don't disable this PIPE pclk.
|
|
*/
|
|
dev_info(priv->dev, "Set usb2.0 only mode successfully\n");
|
|
} else {
|
|
dev_info(priv->dev, "Same or illegal mode\n");
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(u3phy_mode);
|
|
|
|
static struct attribute *rockchip_combphy_u3phy_mode_attrs[] = {
|
|
&dev_attr_u3phy_mode.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group rockchip_combphy_u3phy_mode_attr_group = {
|
|
.name = NULL, /* we want them in the same directory */
|
|
.attrs = rockchip_combphy_u3phy_mode_attrs,
|
|
};
|
|
|
|
static u32 rockchip_combphy_pll_lock(struct rockchip_combphy_priv *priv)
|
|
{
|
|
const struct rockchip_combphy_grfcfg *grfcfg;
|
|
u32 mask, val;
|
|
|
|
grfcfg = &priv->cfg->grfcfg;
|
|
mask = GENMASK(grfcfg->pipe_pll_lock.bitend,
|
|
grfcfg->pipe_pll_lock.bitstart);
|
|
|
|
regmap_read(priv->combphy_grf, grfcfg->pipe_pll_lock.offset, &val);
|
|
val = (val & mask) >> grfcfg->pipe_pll_lock.bitstart;
|
|
|
|
return val;
|
|
}
|
|
|
|
static u32 rockchip_combphy_is_ready(struct rockchip_combphy_priv *priv)
|
|
{
|
|
const struct rockchip_combphy_grfcfg *grfcfg;
|
|
u32 mask, val;
|
|
|
|
grfcfg = &priv->cfg->grfcfg;
|
|
mask = GENMASK(grfcfg->pipe_status_l0.bitend,
|
|
grfcfg->pipe_status_l0.bitstart);
|
|
|
|
regmap_read(priv->combphy_grf, grfcfg->pipe_status_l0.offset, &val);
|
|
val = (val & mask) >> grfcfg->pipe_status_l0.bitstart;
|
|
|
|
return val;
|
|
}
|
|
|
|
static int phy_pcie_init(struct rockchip_combphy_priv *priv)
|
|
{
|
|
const struct rockchip_combphy_grfcfg *grfcfg;
|
|
u32 val;
|
|
int ret = 0;
|
|
|
|
grfcfg = &priv->cfg->grfcfg;
|
|
|
|
/* reset PCIe phy to default configuration */
|
|
reset_control_assert(priv->rsts[PHY_POR_RSTN]);
|
|
reset_control_assert(priv->rsts[PHY_APB_RSTN]);
|
|
reset_control_assert(priv->rsts[PHY_PIPE_RSTN]);
|
|
|
|
reset_control_deassert(priv->rsts[PHY_POR_RSTN]);
|
|
/* Wait PHY power on stable */
|
|
udelay(5);
|
|
reset_control_deassert(priv->rsts[PHY_APB_RSTN]);
|
|
udelay(5);
|
|
|
|
/* Set rxtermination for lane0 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0rxterm_set, true);
|
|
/* Set rxtermination for lane1 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l1rxterm_set, true);
|
|
/* Select pipe_l0_rxtermination from grf */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0rxterm_sel, true);
|
|
/* Select pipe_l1_rxtermination from grf */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l1rxterm_sel, true);
|
|
/* Select rxelecidle_disable and txcommonmode from PCIe controller */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_txrx_sel, false);
|
|
|
|
/* Start to configurate PHY registers for PCIE. */
|
|
if (priv->cfg->combphy_cfg) {
|
|
ret = priv->cfg->combphy_cfg(priv);
|
|
if (ret)
|
|
goto error;
|
|
}
|
|
|
|
/* Wait Tx PLL lock */
|
|
usleep_range(300, 350);
|
|
ret = readx_poll_timeout_atomic(rockchip_combphy_pll_lock, priv, val,
|
|
val == grfcfg->pipe_pll_lock.enable,
|
|
10, 1000);
|
|
if (ret) {
|
|
dev_err(priv->dev, "wait phy PLL lock timeout\n");
|
|
goto error;
|
|
}
|
|
|
|
reset_control_deassert(priv->rsts[PHY_PIPE_RSTN]);
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
static int phy_u3_init(struct rockchip_combphy_priv *priv)
|
|
{
|
|
const struct rockchip_combphy_grfcfg *grfcfg;
|
|
u32 val;
|
|
int ret = 0;
|
|
|
|
grfcfg = &priv->cfg->grfcfg;
|
|
|
|
/* Reset the USB3 controller first. */
|
|
reset_control_assert(priv->rsts[OTG_RSTN]);
|
|
|
|
reset_control_deassert(priv->rsts[PHY_POR_RSTN]);
|
|
/* Wait PHY power on stable. */
|
|
udelay(5);
|
|
|
|
reset_control_deassert(priv->rsts[PHY_APB_RSTN]);
|
|
udelay(5);
|
|
|
|
/*
|
|
* Start to configurate PHY registers for USB3.
|
|
* Note: set operation must be done before corresponding
|
|
* sel operation, otherwise, the PIPE PHY status lane0
|
|
* may be unable to get ready.
|
|
*/
|
|
|
|
/* Disable PHY lane1 which isn't needed for USB3 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l1_set, true);
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l1_sel, true);
|
|
|
|
/* Set PHY Tx and Rx for USB3 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_txrx_set, true);
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_txrx_sel, true);
|
|
|
|
/* Set PHY PIPE MAC pclk request */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_clk_set, true);
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_clk_sel, true);
|
|
|
|
/* Set PHY PIPE rate for USB3 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_rate_set, true);
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_rate_sel, true);
|
|
|
|
/* Set PHY mode for USB3 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_mode_set, true);
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_mode_sel, true);
|
|
|
|
/* Set PHY data bus width for USB3 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_width_set, true);
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_width_sel, true);
|
|
|
|
/* Select PIPE for USB3 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_usb3_sel, true);
|
|
|
|
if (priv->cfg->combphy_cfg) {
|
|
ret = priv->cfg->combphy_cfg(priv);
|
|
if (ret)
|
|
goto error;
|
|
}
|
|
|
|
/* Wait Tx PLL lock */
|
|
usleep_range(300, 350);
|
|
ret = readx_poll_timeout_atomic(rockchip_combphy_pll_lock, priv, val,
|
|
val == grfcfg->pipe_pll_lock.enable,
|
|
10, 1000);
|
|
if (ret) {
|
|
dev_err(priv->dev, "wait phy PLL lock timeout\n");
|
|
goto error;
|
|
}
|
|
|
|
reset_control_deassert(priv->rsts[PHY_PIPE_RSTN]);
|
|
|
|
/* Wait PIPE PHY status lane0 ready */
|
|
ret = readx_poll_timeout_atomic(rockchip_combphy_is_ready, priv, val,
|
|
val == grfcfg->pipe_status_l0.enable,
|
|
10, 1000);
|
|
if (ret) {
|
|
dev_err(priv->dev, "wait phy status lane0 ready timeout\n");
|
|
goto error;
|
|
}
|
|
|
|
reset_control_deassert(priv->rsts[OTG_RSTN]);
|
|
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_combphy_set_phy_type(struct rockchip_combphy_priv *priv)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (priv->phy_initialized)
|
|
return ret;
|
|
|
|
switch (priv->phy_type) {
|
|
case PHY_TYPE_PCIE:
|
|
ret = phy_pcie_init(priv);
|
|
break;
|
|
case PHY_TYPE_USB3:
|
|
ret = phy_u3_init(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Attributes */
|
|
ret = sysfs_create_group(&priv->dev->kobj,
|
|
&rockchip_combphy_u3phy_mode_attr_group);
|
|
break;
|
|
default:
|
|
dev_err(priv->dev, "incompatible PHY type\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rockchip_combphy_init(struct phy *phy)
|
|
{
|
|
struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(priv->ref_clk);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed to enable ref_clk\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = rockchip_combphy_set_phy_type(priv);
|
|
if (ret) {
|
|
dev_err(priv->dev, "failed to set phy type\n");
|
|
return ret;
|
|
}
|
|
|
|
priv->phy_initialized = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_combphy_exit(struct phy *phy)
|
|
{
|
|
struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
|
|
|
|
/*
|
|
* Note: don't assert PHY reset here, because
|
|
* we set many phy configurations during phy
|
|
* init to reduce PHY power consumption, if we
|
|
* assert PHY reset here, these configurations
|
|
* will be lost, and increase power consumption.
|
|
*/
|
|
clk_disable_unprepare(priv->ref_clk);
|
|
|
|
/* in case of waiting phy PLL lock timeout */
|
|
if (priv->phy_type == PHY_TYPE_PCIE) {
|
|
reset_control_assert(priv->rsts[PHY_GRF_P_RSTN]);
|
|
udelay(5);
|
|
reset_control_deassert(priv->rsts[PHY_GRF_P_RSTN]);
|
|
priv->phy_initialized = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_combphy_power_on(struct phy *phy)
|
|
{
|
|
struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
|
|
const struct rockchip_combphy_grfcfg *grfcfg;
|
|
|
|
if (!priv->phy_suspended)
|
|
return 0;
|
|
|
|
grfcfg = &priv->cfg->grfcfg;
|
|
|
|
if (priv->phy_type == PHY_TYPE_USB3) {
|
|
if (priv->cfg->combphy_low_power_ctrl)
|
|
priv->cfg->combphy_low_power_ctrl(priv, false);
|
|
|
|
/* Enable lane 0 squelch detection */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0rxelec_set,
|
|
false);
|
|
|
|
/*
|
|
* Check if lane 0 powerdown is already
|
|
* controlled by USB 3.0 controller.
|
|
*/
|
|
if (param_read(priv->combphy_grf,
|
|
&grfcfg->pipe_l0pd_sel, 0))
|
|
goto done;
|
|
|
|
/* Exit to P0 from P3 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_p3, false);
|
|
usleep_range(250, 300);
|
|
|
|
/*
|
|
* Set lane 0 powerdown to be controlled
|
|
* by USB 3.0 controller.
|
|
*/
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_sel, false);
|
|
}
|
|
|
|
done:
|
|
priv->phy_suspended = false;
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_combphy_power_off(struct phy *phy)
|
|
{
|
|
struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
|
|
const struct rockchip_combphy_grfcfg *grfcfg;
|
|
|
|
if (priv->phy_suspended)
|
|
return 0;
|
|
|
|
grfcfg = &priv->cfg->grfcfg;
|
|
|
|
if (priv->phy_type == PHY_TYPE_USB3 ||
|
|
priv->phy_type == PHY_TYPE_PCIE) {
|
|
/*
|
|
* Check if lane 0 powerdown is already
|
|
* controlled by grf and in P3 state.
|
|
*/
|
|
if (param_read(priv->combphy_grf,
|
|
&grfcfg->pipe_l0pd_sel, 1) &&
|
|
param_read(priv->combphy_grf,
|
|
&grfcfg->pipe_l0pd_p3, 3))
|
|
goto done;
|
|
|
|
/* Exit to P0 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_p3, false);
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_sel, true);
|
|
udelay(1);
|
|
|
|
/* Enter to P3 from P0 */
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0pd_p3, true);
|
|
udelay(2);
|
|
|
|
/*
|
|
* Disable lane 0 squelch detection.
|
|
* Note: if squelch detection is disabled,
|
|
* the PHY can't detect LFPS.
|
|
*/
|
|
param_write(priv->combphy_grf, &grfcfg->pipe_l0rxelec_set,
|
|
true);
|
|
|
|
if (priv->cfg->combphy_low_power_ctrl)
|
|
priv->cfg->combphy_low_power_ctrl(priv, true);
|
|
}
|
|
|
|
done:
|
|
priv->phy_suspended = true;
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_combphy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
|
|
{
|
|
struct rockchip_combphy_priv *priv = phy_get_drvdata(phy);
|
|
u32 reg;
|
|
|
|
if (priv->phy_type != PHY_TYPE_PCIE)
|
|
return -EINVAL;
|
|
|
|
reg = readl(priv->mmio + 0x21a8);
|
|
|
|
if (PHY_MODE_PCIE_EP == submode)
|
|
reg |= (0x1 << 2);
|
|
else if (PHY_MODE_PCIE_RC == submode)
|
|
reg &= ~(0x1 << 2);
|
|
else
|
|
return -EINVAL;
|
|
|
|
writel(reg, priv->mmio + 0x21a8);
|
|
return 0;
|
|
}
|
|
|
|
static const struct phy_ops rockchip_combphy_ops = {
|
|
.init = rockchip_combphy_init,
|
|
.exit = rockchip_combphy_exit,
|
|
.power_on = rockchip_combphy_power_on,
|
|
.power_off = rockchip_combphy_power_off,
|
|
.set_mode = rockchip_combphy_set_mode,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static struct phy *rockchip_combphy_xlate(struct device *dev,
|
|
struct of_phandle_args *args)
|
|
{
|
|
struct rockchip_combphy_priv *priv = dev_get_drvdata(dev);
|
|
|
|
if (args->args_count < 1) {
|
|
dev_err(dev, "invalid number of arguments\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (priv->phy_type != PHY_NONE && priv->phy_type != args->args[0]) {
|
|
dev_err(dev, "type select %d overwriting phy type %d\n",
|
|
args->args[0], priv->phy_type);
|
|
return ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
priv->phy_type = args->args[0];
|
|
|
|
if (priv->phy_type < PHY_TYPE_SATA || priv->phy_type > PHY_TYPE_USB3) {
|
|
dev_err(dev, "invalid phy type select argument\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
return priv->phy;
|
|
}
|
|
|
|
static int rockchip_combphy_parse_dt(struct device *dev,
|
|
struct rockchip_combphy_priv *priv)
|
|
{
|
|
u32 i;
|
|
|
|
priv->combphy_grf = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
"rockchip,combphygrf");
|
|
if (IS_ERR(priv->combphy_grf)) {
|
|
dev_err(dev, "failed to find combphy grf regmap\n");
|
|
return PTR_ERR(priv->combphy_grf);
|
|
}
|
|
|
|
priv->usb_pcie_grf = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
"rockchip,usbpciegrf");
|
|
if (IS_ERR(priv->usb_pcie_grf)) {
|
|
dev_err(dev, "failed to find usb_pcie_grf regmap\n");
|
|
return PTR_ERR(priv->usb_pcie_grf);
|
|
}
|
|
|
|
priv->ref_clk = devm_clk_get(dev, "refclk");
|
|
if (IS_ERR(priv->ref_clk)) {
|
|
dev_err(dev, "failed to find ref clock\n");
|
|
return PTR_ERR(priv->ref_clk);
|
|
}
|
|
|
|
for (i = 0; i < PHY_RESET_MAX; i++) {
|
|
priv->rsts[i] = devm_reset_control_get(dev, get_reset_name(i));
|
|
if (IS_ERR(priv->rsts[i])) {
|
|
dev_warn(dev, "no %s reset control specified\n",
|
|
get_reset_name(i));
|
|
priv->rsts[i] = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rockchip_combphy_probe(struct platform_device *pdev)
|
|
{
|
|
struct phy_provider *phy_provider;
|
|
struct device *dev = &pdev->dev;
|
|
struct rockchip_combphy_priv *priv;
|
|
struct resource *res;
|
|
const struct rockchip_combphy_cfg *phy_cfg;
|
|
int ret;
|
|
|
|
phy_cfg = of_device_get_match_data(dev);
|
|
if (!phy_cfg) {
|
|
dev_err(dev, "No OF match data provided\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
priv->mmio = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(priv->mmio)) {
|
|
ret = PTR_ERR(priv->mmio);
|
|
return ret;
|
|
}
|
|
|
|
ret = rockchip_combphy_parse_dt(dev, priv);
|
|
if (ret) {
|
|
dev_err(dev, "parse dt failed, ret(%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
reset_control_assert(priv->rsts[PHY_POR_RSTN]);
|
|
reset_control_assert(priv->rsts[PHY_APB_RSTN]);
|
|
reset_control_assert(priv->rsts[PHY_PIPE_RSTN]);
|
|
|
|
priv->phy_type = PHY_NONE;
|
|
priv->dev = dev;
|
|
priv->cfg = phy_cfg;
|
|
priv->phy = devm_phy_create(dev, NULL, &rockchip_combphy_ops);
|
|
if (IS_ERR(priv->phy)) {
|
|
dev_err(dev, "failed to create combphy\n");
|
|
return PTR_ERR(priv->phy);
|
|
}
|
|
|
|
dev_set_drvdata(dev, priv);
|
|
phy_set_drvdata(priv->phy, priv);
|
|
|
|
phy_provider = devm_of_phy_provider_register(dev,
|
|
rockchip_combphy_xlate);
|
|
return PTR_ERR_OR_ZERO(phy_provider);
|
|
}
|
|
|
|
static int rockchip_combphy_remove(struct platform_device *pdev)
|
|
{
|
|
struct rockchip_combphy_priv *priv = platform_get_drvdata(pdev);
|
|
|
|
if (priv->phy_type == PHY_TYPE_USB3 && priv->phy_initialized)
|
|
sysfs_remove_group(&priv->dev->kobj,
|
|
&rockchip_combphy_u3phy_mode_attr_group);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk1808_combphy_cfg(struct rockchip_combphy_priv *priv)
|
|
{
|
|
unsigned long rate;
|
|
u32 reg;
|
|
bool ssc_en = false;
|
|
|
|
rate = clk_get_rate(priv->ref_clk);
|
|
|
|
/* Configure PHY reference clock frequency */
|
|
switch (rate) {
|
|
case 24000000:
|
|
/*
|
|
* The default PHY refclk frequency
|
|
* configuration is 24MHz.
|
|
*/
|
|
break;
|
|
case 25000000:
|
|
writel(0x00, priv->mmio + 0x2118);
|
|
writel(0x64, priv->mmio + 0x211c);
|
|
writel(0x01, priv->mmio + 0x2020);
|
|
writel(0x64, priv->mmio + 0x2028);
|
|
writel(0x21, priv->mmio + 0x2030);
|
|
|
|
if (priv->phy_type == PHY_TYPE_PCIE) {
|
|
writel(0x1, priv->mmio + 0x3020);
|
|
writel(0x64, priv->mmio + 0x3028);
|
|
writel(0x21, priv->mmio + 0x3030);
|
|
}
|
|
|
|
break;
|
|
case 50000000:
|
|
writel(0x00, priv->mmio + 0x2118);
|
|
writel(0x32, priv->mmio + 0x211c);
|
|
writel(0x01, priv->mmio + 0x2020);
|
|
writel(0x32, priv->mmio + 0x2028);
|
|
writel(0x21, priv->mmio + 0x2030);
|
|
break;
|
|
default:
|
|
dev_err(priv->dev, "Unsupported rate: %lu\n", rate);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (priv->phy_type == PHY_TYPE_PCIE) {
|
|
/* turn on pcie phy pd */
|
|
writel(0x08400000, priv->mmio + 0x0);
|
|
writel(0x03030000, priv->mmio + 0x8);
|
|
|
|
/* Adjust Lane 0 Rx interface timing */
|
|
writel(0x20, priv->mmio + 0x20ac);
|
|
writel(0x12, priv->mmio + 0x20c8);
|
|
writel(0x76, priv->mmio + 0x2150);
|
|
|
|
/* Adjust Lane 1 Rx interface timing */
|
|
writel(0x20, priv->mmio + 0x30ac);
|
|
writel(0x12, priv->mmio + 0x30c8);
|
|
writel(0x76, priv->mmio + 0x3150);
|
|
/* Set PHY output refclk path */
|
|
writel(0x0, priv->mmio + 0x21a4);
|
|
writel(0x0, priv->mmio + 0x21a8);
|
|
writel(0xb, priv->mmio + 0x21ec);
|
|
|
|
/* Physical ordered set for PCIe */
|
|
writel(0x02, priv->mmio + 0x45c0);
|
|
writel(0x83, priv->mmio + 0x45c4);
|
|
writel(0x03, priv->mmio + 0x45c8);
|
|
writel(0x43, priv->mmio + 0x45cc);
|
|
writel(0x00, priv->mmio + 0x45d0);
|
|
writel(0xbc, priv->mmio + 0x45d4);
|
|
|
|
/* Boost pre-emphasis */
|
|
writel(0xaa, priv->mmio + 0x21b8);
|
|
writel(0xaa, priv->mmio + 0x31b8);
|
|
} else if (priv->phy_type == PHY_TYPE_USB3) {
|
|
/*
|
|
* Disable PHY Lane 1 which isn't needed
|
|
* for USB3 to reduce power consumption.
|
|
*/
|
|
/* Lane 1 cdr power down */
|
|
writel(0x09, priv->mmio + 0x3148);
|
|
|
|
/* Lane 1 rx bias disable */
|
|
writel(0x01, priv->mmio + 0x21cc);
|
|
|
|
/* Lane 1 cdr disable */
|
|
writel(0x08, priv->mmio + 0x30c4);
|
|
writel(0x08, priv->mmio + 0x20f4);
|
|
|
|
/* Lane 1 rx lock disable and tx bias disable */
|
|
writel(0x12, priv->mmio + 0x3150);
|
|
|
|
/* Lane 1 rx termination disable, and tx_cmenb disable */
|
|
writel(0x04, priv->mmio + 0x3080);
|
|
|
|
/* Lane 1 tx termination disable */
|
|
writel(0x1d, priv->mmio + 0x3090);
|
|
|
|
/* Lane 1 tx driver disable */
|
|
writel(0x50, priv->mmio + 0x21c4);
|
|
writel(0x10, priv->mmio + 0x2050);
|
|
|
|
/* Lane 1 txldo_refsel disable */
|
|
writel(0x81, priv->mmio + 0x31a8);
|
|
|
|
/* Lane 1 txdetrx_en disable */
|
|
writel(0x00, priv->mmio + 0x31e8);
|
|
|
|
/* Lane 1 rxcm_en disable */
|
|
writel(0x08, priv->mmio + 0x30c0);
|
|
|
|
/* Adjust Lane 0 Rx interface timing */
|
|
writel(0x20, priv->mmio + 0x20ac);
|
|
|
|
/* Set and enable SSC */
|
|
switch (rate) {
|
|
case 24000000:
|
|
/* Set SSC rate to 31.25KHz */
|
|
reg = readl(priv->mmio + 0x2108);
|
|
reg = (reg & ~0xf) | 0x1;
|
|
writel(reg, priv->mmio + 0x2108);
|
|
ssc_en = true;
|
|
break;
|
|
case 25000000:
|
|
/* Set SSC rate to 32.55KHz */
|
|
reg = readl(priv->mmio + 0x2108);
|
|
reg = (reg & ~0xf) | 0x6;
|
|
writel(reg, priv->mmio + 0x2108);
|
|
ssc_en = true;
|
|
break;
|
|
default:
|
|
dev_warn(priv->dev,
|
|
"failed to set SSC on rate: %lu\n", rate);
|
|
break;
|
|
}
|
|
|
|
if (ssc_en) {
|
|
/* Enable SSC */
|
|
reg = readl(priv->mmio + 0x2120);
|
|
reg &= ~BIT(4);
|
|
writel(reg, priv->mmio + 0x2120);
|
|
|
|
reg = readl(priv->mmio + 0x2000);
|
|
reg &= ~0x6;
|
|
writel(reg, priv->mmio + 0x2000);
|
|
}
|
|
|
|
/*
|
|
* Tuning Tx:
|
|
* offset 0x21b8 bit[7:4]: lane 0 TX driver swing
|
|
* tuning bits with weight, "1111" represents the
|
|
* largest swing and "0000" the smallest.
|
|
*/
|
|
reg = readl(priv->mmio + 0x21b8);
|
|
reg = (reg & ~0xf0) | 0xe0;
|
|
writel(reg, priv->mmio + 0x21b8);
|
|
|
|
/*
|
|
* Tuning Rx for RJTL:
|
|
* Decrease CDR Chump Bump current.
|
|
*/
|
|
reg = readl(priv->mmio + 0x20c8);
|
|
reg = (reg & ~0x6) | BIT(1);
|
|
writel(reg, priv->mmio + 0x20c8);
|
|
reg = readl(priv->mmio + 0x2150);
|
|
reg |= BIT(2);
|
|
writel(reg, priv->mmio + 0x2150);
|
|
} else {
|
|
dev_err(priv->dev, "failed to cfg incompatible PHY type\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk1808_combphy_low_power_control(struct rockchip_combphy_priv *priv,
|
|
bool en)
|
|
{
|
|
if (priv->phy_type != PHY_TYPE_USB3) {
|
|
/* turn off pcie phy pd */
|
|
writel(0x08400840, priv->mmio + 0x0);
|
|
writel(0x03030303, priv->mmio + 0x8);
|
|
|
|
/* enter PCIe phy low power mode */
|
|
writel(0x36, priv->mmio + 0x2150);
|
|
writel(0x36, priv->mmio + 0x3150);
|
|
writel(0x02, priv->mmio + 0x21e8);
|
|
writel(0x02, priv->mmio + 0x31e8);
|
|
writel(0x0c, priv->mmio + 0x2080);
|
|
writel(0x0c, priv->mmio + 0x3080);
|
|
writel(0x08, priv->mmio + 0x20c0);
|
|
writel(0x08, priv->mmio + 0x30c0);
|
|
writel(0x08, priv->mmio + 0x2058);
|
|
|
|
writel(0x10, priv->mmio + 0x2044);
|
|
writel(0x10, priv->mmio + 0x21a8);
|
|
writel(0x10, priv->mmio + 0x31a8);
|
|
writel(0x08, priv->mmio + 0x2058);
|
|
writel(0x08, priv->mmio + 0x3058);
|
|
writel(0x40, priv->mmio + 0x205c);
|
|
writel(0x40, priv->mmio + 0x305c);
|
|
writel(0x08, priv->mmio + 0x2184);
|
|
writel(0x08, priv->mmio + 0x3184);
|
|
writel(0x00, priv->mmio + 0x2150);
|
|
writel(0x00, priv->mmio + 0x3150);
|
|
writel(0x10, priv->mmio + 0x20e0);
|
|
writel(0x00, priv->mmio + 0x21e8);
|
|
writel(0x00, priv->mmio + 0x31e8);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (en) {
|
|
/* Lane 0 tx_biasen disable */
|
|
writel(0x36, priv->mmio + 0x2150);
|
|
|
|
/* Lane 0 txdetrx_en disable */
|
|
writel(0x02, priv->mmio + 0x21e8);
|
|
|
|
/* Lane 0 tx_cmenb disable */
|
|
writel(0x0c, priv->mmio + 0x2080);
|
|
|
|
/* Lane 0 rxcm_en disable */
|
|
writel(0x08, priv->mmio + 0x20c0);
|
|
|
|
/* Lane 0 and Lane 1 bg_pwrdn */
|
|
writel(0x10, priv->mmio + 0x2044);
|
|
|
|
/* Lane 0 and Lane 1 rcomp_osenseampen disable */
|
|
writel(0x08, priv->mmio + 0x2058);
|
|
|
|
/* Lane 0 txldo_refsel disable and LDO disable */
|
|
writel(0x91, priv->mmio + 0x21a8);
|
|
|
|
/* Lane 1 LDO disable */
|
|
writel(0x91, priv->mmio + 0x31a8);
|
|
} else {
|
|
/* Lane 0 tx_biasen enable */
|
|
writel(0x76, priv->mmio + 0x2150);
|
|
|
|
/* Lane 0 txdetrx_en enable */
|
|
writel(0x02, priv->mmio + 0x21e8);
|
|
|
|
/* Lane 0 tx_cmenb enable */
|
|
writel(0x08, priv->mmio + 0x2080);
|
|
|
|
/* Lane 0 rxcm_en enable */
|
|
writel(0x18, priv->mmio + 0x20c0);
|
|
|
|
/* Lane 0 and Lane 1 bg_pwrdn */
|
|
writel(0x00, priv->mmio + 0x2044);
|
|
|
|
/* Lane 0 and Lane 1 rcomp_osenseampen enable */
|
|
writel(0x28, priv->mmio + 0x2058);
|
|
|
|
/* Lane 0 txldo_refsel enable and LDO enable */
|
|
writel(0x01, priv->mmio + 0x21a8);
|
|
|
|
/* Lane 1 LDO enable */
|
|
writel(0x81, priv->mmio + 0x31a8);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct rockchip_combphy_cfg rk1808_combphy_cfgs = {
|
|
.grfcfg = {
|
|
.pipe_l1_sel = { 0x0000, 15, 11, 0x00, 0x1f },
|
|
.pipe_l1_set = { 0x0008, 13, 8, 0x00, 0x13 },
|
|
.pipe_l1rxterm_sel = { 0x0000, 12, 12, 0x0, 0x1 },
|
|
.pipe_l1pd_sel = { 0x0000, 11, 11, 0x0, 0x1},
|
|
.pipe_l1pd_p3 = { 0x0008, 9, 8, 0x0, 0x3 },
|
|
.pipe_l0rxterm_sel = { 0x0000, 7, 7, 0x0, 0x1 },
|
|
.pipe_l0pd_sel = { 0x0000, 6, 6, 0x0, 0x1 },
|
|
.pipe_l0pd_p3 = { 0x0008, 1, 0, 0x0, 0x3 },
|
|
.pipe_clk_sel = { 0x0000, 3, 3, 0x0, 0x1 },
|
|
.pipe_clk_set = { 0x0004, 7, 6, 0x1, 0x0 },
|
|
.pipe_rate_sel = { 0x0000, 2, 2, 0x0, 0x1 },
|
|
.pipe_rate_set = { 0x0004, 5, 4, 0x0, 0x1 },
|
|
.pipe_mode_sel = { 0x0000, 1, 1, 0x0, 0x1 },
|
|
.pipe_mode_set = { 0x0004, 3, 2, 0x0, 0x1 },
|
|
.pipe_txrx_sel = { 0x0004, 15, 8, 0x10, 0x2f },
|
|
.pipe_txrx_set = { 0x0008, 15, 14, 0x0, 0x3 },
|
|
.pipe_l1rxterm_set = { 0x0008, 10, 10, 0x0, 0x1 },
|
|
.pipe_l0rxterm_set = { 0x0008, 2, 2, 0x0, 0x1 },
|
|
.pipe_l0rxelec_set = { 0x0008, 6, 6, 0x0, 0x1 },
|
|
.pipe_width_sel = { 0x0000, 0, 0, 0x0, 0x1 },
|
|
.pipe_width_set = { 0x0004, 1, 0, 0x2, 0x0 },
|
|
.pipe_usb3_sel = { 0x000c, 0, 0, 0x0, 0x1 },
|
|
.pipe_pll_lock = { 0x0034, 14, 14, 0x0, 0x1 },
|
|
.pipe_status_l0 = { 0x0034, 7, 7, 0x1, 0x0 },
|
|
.u3_port_disable = { 0x0434, 0, 0, 0, 1},
|
|
.u3_port_num = { 0x0434, 15, 12, 0, 1},
|
|
},
|
|
.combphy_cfg = rk1808_combphy_cfg,
|
|
.combphy_low_power_ctrl = rk1808_combphy_low_power_control,
|
|
};
|
|
|
|
static const struct of_device_id rockchip_combphy_of_match[] = {
|
|
{
|
|
.compatible = "rockchip,rk1808-combphy",
|
|
.data = &rk1808_combphy_cfgs,
|
|
},
|
|
{ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, rockchip_combphy_of_match);
|
|
|
|
static struct platform_driver rockchip_combphy_driver = {
|
|
.probe = rockchip_combphy_probe,
|
|
.remove = rockchip_combphy_remove,
|
|
.driver = {
|
|
.name = "rockchip-combphy",
|
|
.of_match_table = rockchip_combphy_of_match,
|
|
},
|
|
};
|
|
module_platform_driver(rockchip_combphy_driver);
|
|
|
|
MODULE_AUTHOR("William Wu <william.wu@rock-chips.com>");
|
|
MODULE_DESCRIPTION("Rockchip USB3.0 and PCIE COMBPHY driver");
|
|
MODULE_LICENSE("GPL v2");
|