phy/rockchip: typec: improved handling of typec_dp_phy_config()

Change-Id: I61ca43ffe7fe85e041fb3ed66ba1d59f515770d9
Signed-off-by: Wyon Bi <bivvy.bi@rock-chips.com>
This commit is contained in:
Wyon Bi 2018-08-06 11:48:25 +08:00 committed by Tao Huang
parent 08aea10ffd
commit 363e47c6aa
2 changed files with 345 additions and 30 deletions

View File

@ -295,13 +295,37 @@
#define RX_DIAG_SIGDET_TUNE(n) ((0x81dc | ((n) << 9)) << 2)
#define RX_DIAG_SC2C_DELAY (0x81e1 << 2)
#define PMA_LANE_CFG (0xc000 << 2)
#define PHY_PMA_LANE_CFG (0xc000 << 2)
#define PMA_LANE3_DP_LANE_SEL(x) (((x) & 0x3) << 14)
#define PMA_LANE3_INTERFACE_SEL(x) (((x) & 0x1) << 12)
#define PMA_LANE2_DP_LANE_SEL(x) (((x) & 0x3) << 10)
#define PMA_LANE2_INTERFACE_SEL(x) (((x) & 0x1) << 8)
#define PMA_LANE1_DP_LANE_SEL(x) (((x) & 0x3) << 6)
#define PMA_LANE1_INTERFACE_SEL(x) (((x) & 0x1) << 4)
#define PMA_LANE0_DP_LANE_SEL(x) (((x) & 0x3) << 2)
#define PMA_LANE0_INTERFACE_SEL(x) (((x) & 0x1) << 0)
#define PIPE_CMN_CTRL1 (0xc001 << 2)
#define PIPE_CMN_CTRL2 (0xc002 << 2)
#define PIPE_COM_LOCK_CFG1 (0xc003 << 2)
#define PIPE_COM_LOCK_CFG2 (0xc004 << 2)
#define PIPE_RCV_DET_INH (0xc005 << 2)
#define DP_MODE_CTL (0xc008 << 2)
#define PHY_DP_MODE_CTL (0xc008 << 2)
#define PHY_DP_LANE_DISABLE GENMASK(15, 12)
#define PHY_DP_LANE_3_DISABLE BIT(15)
#define PHY_DP_LANE_2_DISABLE BIT(14)
#define PHY_DP_LANE_1_DISABLE BIT(13)
#define PHY_DP_LANE_0_DISABLE BIT(12)
#define PHY_DP_POWER_STATE_ACK_MASK GENMASK(7, 4)
#define PHY_DP_POWER_STATE_ACK_SHIFT 4
#define PHY_DP_POWER_STATE_MASK GENMASK(3, 0)
#define PHY_DP_CLK_CTL (0xc009 << 2)
#define DP_PLL_CLOCK_ENABLE_ACK BIT(3)
#define DP_PLL_CLOCK_ENABLE_MASK BIT(2)
#define DP_PLL_CLOCK_DISABLE 0
#define DP_PLL_READY BIT(1)
#define DP_PLL_ENABLE_MASK BIT(0)
#define DP_PLL_ENABLE BIT(0)
#define DP_PLL_DISABLE 0
#define DP_CLK_CTL (0xc009 << 2)
#define STS (0xc00F << 2)
#define PHY_ISO_CMN_CTRL (0xc010 << 2)
@ -468,6 +492,283 @@ static const struct phy_config tcphy_default_config[3][4] = {
{ .swing = 0, .pe = 0 } },
};
enum phy_dp_power_state {
PHY_DP_POWER_STATE_DISABLED = -1,
PHY_DP_POWER_STATE_A0,
PHY_DP_POWER_STATE_A1,
PHY_DP_POWER_STATE_A2,
PHY_DP_POWER_STATE_A3,
};
static int tcphy_dp_set_power_state(struct rockchip_typec_phy *tcphy,
enum phy_dp_power_state state)
{
u32 ack, reg, sts = BIT(state);
int ret;
/*
* Power state changes must not be requested until after the cmn_ready
* signal has gone active.
*/
reg = readl(tcphy->base + PMA_CMN_CTRL1);
if (!(reg & CMN_READY)) {
dev_err(tcphy->dev, "cmn_ready in the inactive state\n");
return -EINVAL;
}
reg = readl(tcphy->base + PHY_DP_MODE_CTL);
reg &= ~PHY_DP_POWER_STATE_MASK;
reg |= sts;
writel(reg, tcphy->base + PHY_DP_MODE_CTL);
ret = readl_poll_timeout(tcphy->base + PHY_DP_MODE_CTL,
ack, (((ack & PHY_DP_POWER_STATE_ACK_MASK) >>
PHY_DP_POWER_STATE_ACK_SHIFT) == sts), 10,
PHY_MODE_SET_TIMEOUT);
if (ret < 0) {
dev_err(tcphy->dev, "failed to enter power state %d\n", state);
return ret;
}
return 0;
}
enum {
PHY_DP_LANE_0,
PHY_DP_LANE_1,
PHY_DP_LANE_2,
PHY_DP_LANE_3,
};
enum {
PMA_IF_PIPE_PCS,
PMA_IF_PHY_DP,
};
/*
* For the TypeC PHY, the 4 lanes are mapping to the USB TypeC receptacle pins
* as follows:
* -------------------------------------------------------------------
* PHY Lanes/Module Pins TypeC Receptacle Pins
* -------------------------------------------------------------------
* Lane0 (tx_p/m_ln_0) TX1+/TX1- (pins A2/A3)
* Lane1 (tx_rx_p/m_ln_1) RX1+/RX1- (pins B11/B10)
* Lane2 (tx_rx_p/m_ln_2) RX2+/RX2- (pins A11/A10)
* Lane3 (tx_p/m_ln_3) TX2+/TX2- (pins B2/B3)
* -------------------------------------------------------------------
*
* USB and DP lanes mapping to TypeC PHY lanes for each of pin assignment
* options (normal connector orientation) described in the VESA DisplayPort
* Alt Mode on USB TypeC Standard as follows:
*
* ----------------------------------------------------------------------
* PHY Lanes A B C D E F
* ----------------------------------------------------------------------
* 0 ML1 SSTX ML2 SSTX ML2 SSTX
* 1 ML3 SSRX ML3 SSRX ML3 SSRX
* 2 ML2 ML1 ML0 ML0 ML0 ML0
* 3 ML0 ML0 ML1 ML1 ML1 ML1
* ----------------------------------------------------------------------
*/
static void tcphy_set_lane_mapping(struct rockchip_typec_phy *tcphy, u8 mode)
{
/*
* The PHY_PMA_LANE_CFG register is used to select whether a PMA lane
* is mapped for USB or PHY DP. The PHY_PMA_LANE_CFG register is
* configured based on a normal connector orientation. Logic in the
* PHY automatically handles the flipped connector case based on the
* setting of orientation of TypeC PHY.
*/
if (mode == MODE_DFP_DP) {
/* This maps to VESA DP Alt Mode pin assignments C and E. */
writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) |
PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) |
PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) |
PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) |
PMA_LANE1_DP_LANE_SEL(PHY_DP_LANE_3) |
PMA_LANE1_INTERFACE_SEL(PMA_IF_PHY_DP) |
PMA_LANE0_DP_LANE_SEL(PHY_DP_LANE_2) |
PMA_LANE0_INTERFACE_SEL(PMA_IF_PHY_DP),
tcphy->base + PHY_PMA_LANE_CFG);
} else {
/* This maps to VESA DP Alt Mode pin assignments D and F. */
writel(PMA_LANE3_DP_LANE_SEL(PHY_DP_LANE_1) |
PMA_LANE3_INTERFACE_SEL(PMA_IF_PHY_DP) |
PMA_LANE2_DP_LANE_SEL(PHY_DP_LANE_0) |
PMA_LANE2_INTERFACE_SEL(PMA_IF_PHY_DP) |
PMA_LANE1_INTERFACE_SEL(PMA_IF_PIPE_PCS) |
PMA_LANE0_INTERFACE_SEL(PMA_IF_PIPE_PCS),
tcphy->base + PHY_PMA_LANE_CFG);
}
}
static int tcphy_dp_set_lane_count(struct rockchip_typec_phy *tcphy,
u8 lane_count)
{
u32 reg;
/*
* In cases where fewer than the configured number of DP lanes are
* being used. PHY_DP_MODE_CTL[15:12] must be set to disable and
* power-down the unused PHY DP lanes (and their mapped PMA lanes).
* Set the bit ([15:12]) associated with each DP PHY lane(s) to be
* disabled.
*/
reg = readl(tcphy->base + PHY_DP_MODE_CTL);
reg |= PHY_DP_LANE_DISABLE;
switch (lane_count) {
case 4:
reg &= ~(PHY_DP_LANE_3_DISABLE | PHY_DP_LANE_2_DISABLE |
PHY_DP_LANE_1_DISABLE | PHY_DP_LANE_0_DISABLE);
break;
case 2:
reg &= ~(PHY_DP_LANE_1_DISABLE | PHY_DP_LANE_0_DISABLE);
break;
case 1:
reg &= ~PHY_DP_LANE_0_DISABLE;
break;
default:
return -EINVAL;
}
writel(reg, tcphy->base + PHY_DP_MODE_CTL);
tcphy->dp.lane_count = lane_count;
return 0;
}
static int tcphy_dp_set_link_rate(struct rockchip_typec_phy *tcphy,
int link_rate)
{
const struct phy_reg *phy_cfg;
u32 cmn_diag_hsclk_sel, phy_dp_clk_ctl, reg;
u32 i, cfg_size;
int ret;
/* Place the PHY lanes in the A3 power state. */
ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A3);
if (ret) {
dev_err(tcphy->dev, "failed to enter A3 state: %d\n", ret);
return ret;
}
/* Gate the PLL clocks from PMA */
reg = readl(tcphy->base + PHY_DP_CLK_CTL);
reg &= ~DP_PLL_CLOCK_ENABLE_MASK;
reg |= DP_PLL_CLOCK_DISABLE;
writel(reg, tcphy->base + PHY_DP_CLK_CTL);
ret = readl_poll_timeout(tcphy->base + PHY_DP_CLK_CTL, reg,
!(reg & DP_PLL_CLOCK_ENABLE_ACK),
10, PHY_MODE_SET_TIMEOUT);
if (ret) {
dev_err(tcphy->dev, "wait DP PLL clock disabled timeout\n");
return ret;
}
/* Disable the PLL */
reg = readl(tcphy->base + PHY_DP_CLK_CTL);
reg &= ~DP_PLL_ENABLE_MASK;
reg |= DP_PLL_DISABLE;
writel(reg, tcphy->base + PHY_DP_CLK_CTL);
ret = readl_poll_timeout(tcphy->base + PHY_DP_CLK_CTL, reg,
!(reg & DP_PLL_READY),
10, PHY_MODE_SET_TIMEOUT);
if (ret) {
dev_err(tcphy->dev, "wait DP PLL not ready timeout\n");
return ret;
}
/* Re-configure PHY registers for the new data rate */
cmn_diag_hsclk_sel = readl(tcphy->base + CMN_DIAG_HSCLK_SEL);
cmn_diag_hsclk_sel &= ~(GENMASK(5, 4) | GENMASK(1, 0));
phy_dp_clk_ctl = readl(tcphy->base + PHY_DP_CLK_CTL);
phy_dp_clk_ctl &= ~(GENMASK(15, 12) | GENMASK(11, 8));
switch (link_rate) {
case 162000:
cmn_diag_hsclk_sel |= (3 << 4) | (0 << 0);
phy_dp_clk_ctl |= (2 << 12) | (4 << 8);
phy_cfg = dp_pll_rbr_cfg;
cfg_size = ARRAY_SIZE(dp_pll_rbr_cfg);
break;
case 270000:
cmn_diag_hsclk_sel |= (3 << 4) | (0 << 0);
phy_dp_clk_ctl |= (2 << 12) | (4 << 8);
phy_cfg = dp_pll_hbr_cfg;
cfg_size = ARRAY_SIZE(dp_pll_hbr_cfg);
break;
case 540000:
cmn_diag_hsclk_sel |= (2 << 4) | (0 << 0);
phy_dp_clk_ctl |= (1 << 12) | (2 << 8);
phy_cfg = dp_pll_hbr2_cfg;
cfg_size = ARRAY_SIZE(dp_pll_hbr2_cfg);
break;
default:
return -EINVAL;
}
writel(cmn_diag_hsclk_sel, tcphy->base + CMN_DIAG_HSCLK_SEL);
writel(phy_dp_clk_ctl, tcphy->base + PHY_DP_CLK_CTL);
/* load the configuration of PLL1 */
for (i = 0; i < cfg_size; i++)
writel(phy_cfg[i].value, tcphy->base + phy_cfg[i].addr);
/* Enable the PLL */
reg = readl(tcphy->base + PHY_DP_CLK_CTL);
reg &= ~DP_PLL_ENABLE_MASK;
reg |= DP_PLL_ENABLE;
writel(reg, tcphy->base + PHY_DP_CLK_CTL);
ret = readl_poll_timeout(tcphy->base + PHY_DP_CLK_CTL, reg,
reg & DP_PLL_READY,
10, PHY_MODE_SET_TIMEOUT);
if (ret < 0) {
dev_err(tcphy->dev, "wait DP PLL ready timeout\n");
return ret;
}
/* Enable PMA PLL clocks */
reg = readl(tcphy->base + PHY_DP_CLK_CTL);
reg &= ~DP_PLL_CLOCK_ENABLE_MASK;
reg |= DP_PLL_CLOCK_ENABLE;
writel(reg, tcphy->base + PHY_DP_CLK_CTL);
ret = readl_poll_timeout(tcphy->base + PHY_DP_CLK_CTL, reg,
reg & DP_PLL_CLOCK_ENABLE_ACK,
10, PHY_MODE_SET_TIMEOUT);
if (ret) {
dev_err(tcphy->dev, "wait DP PLL clock enabled timeout\n");
return ret;
}
/* The PMA must go through the A2 power state upon a data rate change */
ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A2);
if (ret) {
dev_err(tcphy->dev, "failed to enter A2 state: %d\n", ret);
return ret;
}
/* change the PHY power state to A0 */
ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A0);
if (ret) {
dev_err(tcphy->dev, "failed to enter A0 state: %d\n", ret);
return ret;
}
tcphy->dp.link_rate = link_rate;
return 0;
}
static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy)
{
u32 i, rdata;
@ -535,12 +836,14 @@ static void tcphy_cfg_dp_pll(struct rockchip_typec_phy *tcphy, int link_rate)
}
clk_ctrl |= DP_PLL_CLOCK_ENABLE | DP_PLL_ENABLE;
writel(clk_ctrl, tcphy->base + DP_CLK_CTL);
writel(clk_ctrl, tcphy->base + PHY_DP_CLK_CTL);
writel(hsclk_sel, tcphy->base + CMN_DIAG_HSCLK_SEL);
/* load the configuration of PLL1 */
for (i = 0; i < cfg_size; i++)
writel(phy_cfg[i].value, tcphy->base + phy_cfg[i].addr);
tcphy->dp.link_rate = link_rate;
}
static void tcphy_tx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane)
@ -792,13 +1095,12 @@ static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
tcphy_dp_aux_set_flip(tcphy);
tcphy_cfg_24m(tcphy);
tcphy_set_lane_mapping(tcphy, mode);
if (mode == MODE_DFP_DP) {
tcphy_cfg_dp_pll(tcphy, DP_DEFAULT_RATE);
for (i = 0; i < 4; i++)
tcphy_dp_cfg_lane(tcphy, DP_DEFAULT_RATE, 0, 0, i);
writel(PIN_ASSIGN_C_E, tcphy->base + PMA_LANE_CFG);
} else {
tcphy_cfg_usb3_pll(tcphy);
tcphy_cfg_dp_pll(tcphy, DP_DEFAULT_RATE);
@ -813,14 +1115,12 @@ static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode)
tcphy_dp_cfg_lane(tcphy, DP_DEFAULT_RATE, 0, 0, 2);
tcphy_dp_cfg_lane(tcphy, DP_DEFAULT_RATE, 0, 0, 3);
}
writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG);
}
val = readl(tcphy->base + DP_MODE_CTL);
val = readl(tcphy->base + PHY_DP_MODE_CTL);
val &= ~DP_MODE_MASK;
val |= DP_MODE_ENTER_A2 | DP_LINK_RESET_DEASSERTED;
writel(val, tcphy->base + DP_MODE_CTL);
writel(val, tcphy->base + PHY_DP_MODE_CTL);
reset_control_deassert(tcphy->uphy_rst);
@ -1055,7 +1355,7 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
property_enable(tcphy, &cfg->uphy_dp_sel, 1);
ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
ret = readx_poll_timeout(readl, tcphy->base + PHY_DP_MODE_CTL,
val, val & DP_MODE_A2_ACK, 1000,
PHY_MODE_SET_TIMEOUT);
if (ret < 0) {
@ -1066,19 +1366,9 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
tcphy_dp_aux_calibration(tcphy);
/* enter A0 mode */
val = readl(tcphy->base + DP_MODE_CTL);
val &= ~DP_MODE_MASK;
val |= DP_MODE_ENTER_A0;
writel(val, tcphy->base + DP_MODE_CTL);
ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL,
val, val & DP_MODE_A0_ACK, 1000,
PHY_MODE_SET_TIMEOUT);
if (ret < 0) {
val &= ~DP_MODE_MASK;
val |= DP_MODE_ENTER_A2;
writel(val, tcphy->base + DP_MODE_CTL);
dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n");
ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A0);
if (ret) {
dev_err(tcphy->dev, "failed to enter A0 power state\n");
goto power_on_finish;
}
@ -1095,7 +1385,7 @@ static int rockchip_dp_phy_power_on(struct phy *phy)
static int rockchip_dp_phy_power_off(struct phy *phy)
{
struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
u32 val;
int ret;
mutex_lock(&tcphy->lock);
@ -1104,10 +1394,11 @@ static int rockchip_dp_phy_power_off(struct phy *phy)
tcphy->mode &= ~MODE_DFP_DP;
val = readl(tcphy->base + DP_MODE_CTL);
val &= ~DP_MODE_MASK;
val |= DP_MODE_ENTER_A2;
writel(val, tcphy->base + DP_MODE_CTL);
ret = tcphy_dp_set_power_state(tcphy, PHY_DP_POWER_STATE_A2);
if (ret) {
dev_err(tcphy->dev, "failed to enter A2 power state\n");
goto unlock;
}
if (tcphy->mode == MODE_DISCONNECT)
tcphy_phy_deinit(tcphy);
@ -1124,12 +1415,32 @@ static const struct phy_ops rockchip_dp_phy_ops = {
};
static int typec_dp_phy_config(struct phy *phy, int link_rate,
int lanes, u8 swing, u8 pre_emp)
int lane_count, u8 swing, u8 pre_emp)
{
struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy);
u8 i;
int ret;
tcphy_cfg_dp_pll(tcphy, link_rate);
dev_dbg(tcphy->dev, "link_rate=%d, lane_count=%d\n",
link_rate, lane_count);
dev_dbg(tcphy->dev, "voltage_swing=%d, pre_emphasis=%d\n",
swing, pre_emp);
if (lane_count != tcphy->dp.lane_count) {
ret = tcphy_dp_set_lane_count(tcphy, lane_count);
if (ret) {
dev_err(tcphy->dev, "failed to set lane count\n");
return ret;
}
}
if (link_rate != tcphy->dp.link_rate) {
ret = tcphy_dp_set_link_rate(tcphy, link_rate);
if (ret) {
dev_err(tcphy->dev, "failed to set lane rate\n");
return ret;
}
}
if (tcphy->mode == MODE_DFP_DP) {
for (i = 0; i < 4; i++)

View File

@ -45,6 +45,10 @@ struct rockchip_typec_phy {
bool flip;
u8 mode;
struct phy_config config[3][4];
struct {
int link_rate;
u8 lane_count;
} dp;
int (*typec_phy_config)(struct phy *phy, int link_rate,
int lanes, u8 swing, u8 pre_emp);
};