239 lines
8.2 KiB
C
239 lines
8.2 KiB
C
|
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
/* Qualcomm Technologies, Inc. EMAC SGMII Controller driver.
|
||
|
*/
|
||
|
|
||
|
#include "emac_sgmii.h"
|
||
|
#include "emac_hw.h"
|
||
|
|
||
|
#define PCS_MAX_REG_CNT 10
|
||
|
#define PLL_MAX_REG_CNT 18
|
||
|
|
||
|
static const struct emac_reg_write
|
||
|
physical_coding_sublayer_programming[][PCS_MAX_REG_CNT] = {
|
||
|
/* EMAC_PHY_MAP_DEFAULT */
|
||
|
{
|
||
|
{EMAC_SGMII_PHY_CDR_CTRL0, SGMII_CDR_MAX_CNT},
|
||
|
{EMAC_SGMII_PHY_POW_DWN_CTRL0, PWRDN_B},
|
||
|
{EMAC_SGMII_PHY_CMN_PWR_CTRL, BIAS_EN | SYSCLK_EN |
|
||
|
CLKBUF_L_EN | PLL_TXCLK_EN | PLL_RXCLK_EN},
|
||
|
{EMAC_SGMII_PHY_TX_PWR_CTRL, L0_TX_EN | L0_CLKBUF_EN |
|
||
|
L0_TRAN_BIAS_EN},
|
||
|
{EMAC_SGMII_PHY_RX_PWR_CTRL, L0_RX_SIGDET_EN |
|
||
|
(1 << L0_RX_TERM_MODE_SHFT) | L0_RX_I_EN},
|
||
|
{EMAC_SGMII_PHY_CMN_PWR_CTRL, BIAS_EN | PLL_EN | SYSCLK_EN |
|
||
|
CLKBUF_L_EN | PLL_TXCLK_EN | PLL_RXCLK_EN},
|
||
|
{EMAC_SGMII_PHY_LANE_CTRL1, L0_RX_EQ_EN |
|
||
|
L0_RESET_TSYNC_EN | L0_DRV_LVL_BMSK},
|
||
|
{END_MARKER, END_MARKER},
|
||
|
},
|
||
|
/* EMAC_PHY_MAP_MDM9607 */
|
||
|
{
|
||
|
{EMAC_SGMII_PHY_CDR_CTRL0, SGMII_CDR_MAX_CNT},
|
||
|
{EMAC_SGMII_PHY_POW_DWN_CTRL0, PWRDN_B},
|
||
|
{EMAC_SGMII_PHY_CMN_PWR_CTRL, BIAS_EN | SYSCLK_EN |
|
||
|
CLKBUF_L_EN | PLL_TXCLK_EN | PLL_RXCLK_EN},
|
||
|
{EMAC_SGMII_PHY_TX_PWR_CTRL, L0_TX_EN | L0_CLKBUF_EN |
|
||
|
L0_TRAN_BIAS_EN},
|
||
|
{EMAC_SGMII_PHY_RX_PWR_CTRL, L0_RX_SIGDET_EN |
|
||
|
(1 << L0_RX_TERM_MODE_SHFT) | L0_RX_I_EN},
|
||
|
{EMAC_SGMII_PHY_CMN_PWR_CTRL, BIAS_EN | PLL_EN | SYSCLK_EN |
|
||
|
CLKBUF_L_EN | PLL_TXCLK_EN | PLL_RXCLK_EN},
|
||
|
{EMAC_QSERDES_COM_PLL_VCOTAIL_EN, PLL_VCO_TAIL_MUX |
|
||
|
PLL_VCO_TAIL | PLL_EN_VCOTAIL_EN},
|
||
|
{EMAC_QSERDES_COM_PLL_CNTRL, OCP_EN | PLL_DIV_FFEN |
|
||
|
PLL_DIV_ORD},
|
||
|
{EMAC_SGMII_PHY_LANE_CTRL1, L0_RX_EQ_EN |
|
||
|
L0_RESET_TSYNC_EN | L0_DRV_LVL_BMSK},
|
||
|
{END_MARKER, END_MARKER}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static const struct emac_reg_write sysclk_refclk_setting[] = {
|
||
|
{EMAC_QSERDES_COM_SYSCLK_EN_SEL, SYSCLK_SEL_CMOS},
|
||
|
{EMAC_QSERDES_COM_SYS_CLK_CTRL, SYSCLK_CM | SYSCLK_AC_COUPLE},
|
||
|
{END_MARKER, END_MARKER},
|
||
|
};
|
||
|
|
||
|
static const struct emac_reg_write pll_setting[][PLL_MAX_REG_CNT] = {
|
||
|
/* EMAC_PHY_MAP_DEFAULT */
|
||
|
{
|
||
|
{EMAC_QSERDES_COM_PLL_IP_SETI, QSERDES_PLL_IPSETI_DEF},
|
||
|
{EMAC_QSERDES_COM_PLL_CP_SETI, QSERDES_PLL_CP_SETI},
|
||
|
{EMAC_QSERDES_COM_PLL_IP_SETP, QSERDES_PLL_IP_SETP},
|
||
|
{EMAC_QSERDES_COM_PLL_CP_SETP, QSERDES_PLL_CP_SETP},
|
||
|
{EMAC_QSERDES_COM_PLL_CRCTRL, QSERDES_PLL_CRCTRL},
|
||
|
{EMAC_QSERDES_COM_PLL_CNTRL, OCP_EN | PLL_DIV_FFEN |
|
||
|
PLL_DIV_ORD},
|
||
|
{EMAC_QSERDES_COM_DEC_START1, DEC_START1_MUX |
|
||
|
QSERDES_PLL_DEC},
|
||
|
{EMAC_QSERDES_COM_DEC_START2, DEC_START2_MUX |
|
||
|
DEC_START2},
|
||
|
{EMAC_QSERDES_COM_DIV_FRAC_START1, DIV_FRAC_START1_MUX |
|
||
|
QSERDES_PLL_DIV_FRAC_START1},
|
||
|
{EMAC_QSERDES_COM_DIV_FRAC_START2, DIV_FRAC_START2_MUX |
|
||
|
QSERDES_PLL_DIV_FRAC_START2},
|
||
|
{EMAC_QSERDES_COM_DIV_FRAC_START3, DIV_FRAC_START3_MUX |
|
||
|
QSERDES_PLL_DIV_FRAC_START3},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP1, QSERDES_PLL_LOCK_CMP1},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP2, QSERDES_PLL_LOCK_CMP2},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP3, QSERDES_PLL_LOCK_CMP3},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP_EN, PLLLOCK_CMP_EN},
|
||
|
{EMAC_QSERDES_COM_RESETSM_CNTRL, FRQ_TUNE_MODE},
|
||
|
{END_MARKER, END_MARKER}
|
||
|
},
|
||
|
/* EMAC_PHY_MAP_MDM9607 */
|
||
|
{
|
||
|
{EMAC_QSERDES_COM_PLL_IP_SETI, QSERDES_PLL_IPSETI_MDM},
|
||
|
{EMAC_QSERDES_COM_PLL_CP_SETI, QSERDES_PLL_CP_SETI},
|
||
|
{EMAC_QSERDES_COM_PLL_IP_SETP, QSERDES_PLL_IP_SETP},
|
||
|
{EMAC_QSERDES_COM_PLL_CP_SETP, QSERDES_PLL_CP_SETP},
|
||
|
{EMAC_QSERDES_COM_PLL_CRCTRL, QSERDES_PLL_CRCTRL},
|
||
|
{EMAC_QSERDES_COM_DEC_START1, DEC_START1_MUX |
|
||
|
QSERDES_PLL_DEC},
|
||
|
{EMAC_QSERDES_COM_DEC_START2, DEC_START2_MUX |
|
||
|
DEC_START2},
|
||
|
{EMAC_QSERDES_COM_DIV_FRAC_START1, DIV_FRAC_START1_MUX |
|
||
|
QSERDES_PLL_DIV_FRAC_START1},
|
||
|
{EMAC_QSERDES_COM_DIV_FRAC_START2, DIV_FRAC_START2_MUX |
|
||
|
QSERDES_PLL_DIV_FRAC_START2},
|
||
|
{EMAC_QSERDES_COM_DIV_FRAC_START3, DIV_FRAC_START3_MUX |
|
||
|
QSERDES_PLL_DIV_FRAC_START3},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP1, QSERDES_PLL_LOCK_CMP1},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP2, QSERDES_PLL_LOCK_CMP2},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP3, QSERDES_PLL_LOCK_CMP3},
|
||
|
{EMAC_QSERDES_COM_PLLLOCK_CMP_EN, PLLLOCK_CMP_EN},
|
||
|
{EMAC_QSERDES_COM_RESETSM_CNTRL, FRQ_TUNE_MODE},
|
||
|
{EMAC_QSERDES_COM_RES_TRIM_SEARCH, RESTRIM_SEARCH},
|
||
|
{EMAC_QSERDES_COM_BGTC, BGTC},
|
||
|
{END_MARKER, END_MARKER},
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static const struct emac_reg_write cdr_setting[] = {
|
||
|
{EMAC_QSERDES_RX_CDR_CONTROL, SECONDORDERENABLE |
|
||
|
(QSERDES_RX_CDR_CTRL1_THRESH << FIRSTORDER_THRESH_SHFT) |
|
||
|
(QSERDES_RX_CDR_CTRL1_GAIN << SECONDORDERGAIN_SHFT)},
|
||
|
{EMAC_QSERDES_RX_CDR_CONTROL2, SECONDORDERENABLE |
|
||
|
(QSERDES_RX_CDR_CTRL2_THRESH << FIRSTORDER_THRESH_SHFT) |
|
||
|
(QSERDES_RX_CDR_CTRL2_GAIN << SECONDORDERGAIN_SHFT)},
|
||
|
{END_MARKER, END_MARKER},
|
||
|
};
|
||
|
|
||
|
static const struct emac_reg_write tx_rx_setting[] = {
|
||
|
{EMAC_QSERDES_TX_BIST_MODE_LANENO, QSERDES_TX_BIST_MODE_LANENO},
|
||
|
{EMAC_QSERDES_TX_TX_DRV_LVL, TX_DRV_LVL_MUX |
|
||
|
(QSERDES_TX_DRV_LVL << TX_DRV_LVL_SHFT)},
|
||
|
{EMAC_QSERDES_TX_TRAN_DRVR_EMP_EN, EMP_EN_MUX | EMP_EN},
|
||
|
{EMAC_QSERDES_TX_TX_EMP_POST1_LVL, TX_EMP_POST1_LVL_MUX |
|
||
|
(QSERDES_TX_EMP_POST1_LVL << TX_EMP_POST1_LVL_SHFT)},
|
||
|
{EMAC_QSERDES_RX_RX_EQ_GAIN12,
|
||
|
(QSERDES_RX_EQ_GAIN2 << RX_EQ_GAIN2_SHFT) |
|
||
|
(QSERDES_RX_EQ_GAIN1 << RX_EQ_GAIN1_SHFT)},
|
||
|
{EMAC_QSERDES_TX_LANE_MODE,
|
||
|
QSERDES_TX_LANE_MODE},
|
||
|
{END_MARKER, END_MARKER}
|
||
|
};
|
||
|
|
||
|
static int emac_sgmii_v1_init(struct emac_adapter *adpt)
|
||
|
{
|
||
|
int i;
|
||
|
struct emac_phy *phy = &adpt->phy;
|
||
|
struct emac_sgmii *sgmii = phy->private;
|
||
|
|
||
|
emac_sgmii_init_link(adpt, phy->autoneg_advertised, phy->autoneg,
|
||
|
!phy->disable_fc_autoneg);
|
||
|
|
||
|
emac_reg_write_all(
|
||
|
sgmii->base,
|
||
|
(const struct emac_reg_write *)
|
||
|
&physical_coding_sublayer_programming[phy->board_id]);
|
||
|
|
||
|
/* Ensure Rx/Tx lanes power configuration is written to hw before
|
||
|
* configuring the SerDes engine's clocks
|
||
|
*/
|
||
|
wmb();
|
||
|
|
||
|
emac_reg_write_all(sgmii->base, sysclk_refclk_setting);
|
||
|
emac_reg_write_all(
|
||
|
sgmii->base,
|
||
|
(const struct emac_reg_write *)&pll_setting[phy->board_id]);
|
||
|
emac_reg_write_all(sgmii->base, cdr_setting);
|
||
|
emac_reg_write_all(sgmii->base, tx_rx_setting);
|
||
|
|
||
|
/* Ensure SerDes engine configuration is written to hw before powering
|
||
|
* it up
|
||
|
*/
|
||
|
wmb();
|
||
|
|
||
|
writel_relaxed(SERDES_START, sgmii->base + EMAC_SGMII_PHY_SERDES_START);
|
||
|
|
||
|
/* Ensure Rx/Tx SerDes engine power-up command is written to HW */
|
||
|
wmb();
|
||
|
|
||
|
for (i = 0; i < SERDES_START_WAIT_TIMES; i++) {
|
||
|
if (readl_relaxed(sgmii->base + EMAC_QSERDES_COM_RESET_SM) &
|
||
|
QSERDES_READY)
|
||
|
break;
|
||
|
usleep_range(100, 200);
|
||
|
}
|
||
|
|
||
|
if (i == SERDES_START_WAIT_TIMES) {
|
||
|
emac_err(adpt, "serdes failed to start\n");
|
||
|
return -EIO;
|
||
|
}
|
||
|
/* Mask out all the SGMII Interrupt */
|
||
|
writel_relaxed(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
|
||
|
/* Ensure SGMII interrupts are masked out before clearing them */
|
||
|
wmb();
|
||
|
|
||
|
emac_hw_clear_sgmii_intr_status(adpt, SGMII_PHY_INTERRUPT_ERR);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void emac_sgmii_v1_reset(struct emac_adapter *adpt)
|
||
|
{
|
||
|
emac_clk_set_rate(adpt, EMAC_CLK_125M, EMC_CLK_RATE_19_2MHZ);
|
||
|
emac_sgmii_reset_prepare(adpt);
|
||
|
emac_sgmii_v1_init(adpt);
|
||
|
emac_clk_set_rate(adpt, EMAC_CLK_125M, EMC_CLK_RATE_125MHZ);
|
||
|
}
|
||
|
|
||
|
int emac_sgmii_v1_link_setup_no_ephy(struct emac_adapter *adpt, u32 speed,
|
||
|
bool autoneg)
|
||
|
{
|
||
|
struct emac_phy *phy = &adpt->phy;
|
||
|
|
||
|
phy->autoneg = autoneg;
|
||
|
phy->autoneg_advertised = speed;
|
||
|
/* The AN_ENABLE and SPEED_CFG can't change on fly. The SGMII_PHY has
|
||
|
* to be re-initialized.
|
||
|
*/
|
||
|
emac_sgmii_reset_prepare(adpt);
|
||
|
return emac_sgmii_v1_init(adpt);
|
||
|
}
|
||
|
|
||
|
struct emac_phy_ops emac_sgmii_v1_ops = {
|
||
|
.config = emac_sgmii_config,
|
||
|
.up = emac_sgmii_up,
|
||
|
.down = emac_sgmii_down,
|
||
|
.init = emac_sgmii_v1_init,
|
||
|
.reset = emac_sgmii_v1_reset,
|
||
|
.init_ephy = emac_sgmii_init_ephy_nop,
|
||
|
.link_setup_no_ephy = emac_sgmii_v1_link_setup_no_ephy,
|
||
|
.link_check_no_ephy = emac_sgmii_link_check_no_ephy,
|
||
|
.tx_clk_set_rate = emac_sgmii_tx_clk_set_rate_nop,
|
||
|
.periodic_task = emac_sgmii_periodic_check,
|
||
|
};
|