u-boot-2016/drivers/net/ipq_common/ipq_qca8084_interface_ctrl.c
Selvam Sathappan Periakaruppan 18d2b93ab3 drivers: net: Add support for QCA8084 PHY
This patch adds initial support for qca8084 PHY
which is based on qca8081 PHY.

qca8084 PHY has support for 4x2.5G.

Change-Id: Ic767c19fad050e5ee9a97ad7fa50c1b6b27893dd
Signed-off-by: Selvam Sathappan Periakaruppan <quic_speriaka@quicinc.com>
2022-05-25 04:08:31 -07:00

504 lines
15 KiB
C

/*
* Copyright (c) 2022, 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.
*/
#include "ipq_phy.h"
#include "ipq_qca8084.h"
#include "ipq_qca8084_clk.h"
#include "ipq_qca8084_interface_ctrl.h"
#include <malloc.h>
#ifdef DEBUG
#define pr_debug(fmt, args...) printf(fmt, ##args);
#else
#define pr_debug(fmt, args...)
#endif
extern void qca8084_phy_reset(u32 phy_id);
extern u16 qca8084_phy_reg_read(u32 phy_id, u32 reg_id);
extern u16 qca8084_phy_reg_write(u32 phy_id, u32 reg_id, u16 value);
extern u16 qca8084_phy_mmd_read(u32 phy_id, u16 mmd_num, u16 reg_id);
extern u16 qca8084_phy_mmd_write(u32 phy_id, u16 mmd_num, u16 reg_id,
u16 value);
extern void qca8084_phy_modify_mmd(uint32_t phy_addr, uint32_t mmd_num,
uint32_t mmd_reg, uint32_t mask, uint32_t value);
extern void qca8084_phy_modify_mii(uint32_t phy_addr, uint32_t mii_reg,
uint32_t mask, uint32_t value);
extern uint32_t ipq_mii_read(uint32_t reg);
extern void ipq_mii_write(uint32_t reg, uint32_t val);
extern void ipq_mii_update(uint32_t reg, uint32_t mask, uint32_t val);
extern void qca8084_port_clk_rate_set(uint32_t qca8084_port_id, uint32_t rate);
extern void qca8084_port_clk_en_set(uint32_t qca8084_port_id, uint8_t mask,
uint8_t enable);
extern void qca8084_clk_assert(const char *clock_id);
extern void qca8084_clk_deassert(const char *clock_id);
extern void qca8084_port_clk_reset(uint32_t qca8084_port_id, uint8_t mask);
void qca8084_serdes_addr_get(uint32_t serdes_id, uint32_t *address)
{
uint32_t data = 0;
data = ipq_mii_read(SERDES_CFG_OFFSET);
switch(serdes_id)
{
case QCA8084_UNIPHY_SGMII_0:
*address = (data >> SERDES_CFG_S0_ADDR_BOFFSET) & 0x1f;
break;
case QCA8084_UNIPHY_SGMII_1:
*address = (data >> SERDES_CFG_S1_ADDR_BOFFSET) & 0x1f;
break;
case QCA8084_UNIPHY_XPCS:
*address = (data >> SERDES_CFG_S1_XPCS_ADDR_BOFFSET) & 0x1f;
break;
default:
pr_debug("Serdes id not matching\n");
break;
}
}
void qca8084_ephy_addr_get(uint32_t qca8084_port_id, uint32_t *phy_addr)
{
uint32_t data = 0;
data = ipq_mii_read(EPHY_CFG_OFFSET);
switch(qca8084_port_id)
{
case PORT1:
*phy_addr = (data >> EPHY_CFG_EPHY0_ADDR_BOFFSET) & 0x1f;
break;
case PORT2:
*phy_addr = (data >> EPHY_CFG_EPHY1_ADDR_BOFFSET) & 0x1f;
break;
case PORT3:
*phy_addr = (data >> EPHY_CFG_EPHY2_ADDR_BOFFSET) & 0x1f;
break;
case PORT4:
*phy_addr = (data >> EPHY_CFG_EPHY3_ADDR_BOFFSET) & 0x1f;
break;
default:
pr_debug("qca8084_port_id not matching\n");
break;
}
}
static uint16_t qca8084_uniphy_xpcs_mmd_read(uint16_t mmd_num, uint16_t mmd_reg)
{
uint32_t uniphy_xpcs_addr = 0;
qca8084_serdes_addr_get(QCA8084_UNIPHY_XPCS, &uniphy_xpcs_addr);
return qca8084_phy_mmd_read(uniphy_xpcs_addr, mmd_num, mmd_reg);
}
static void qca8084_uniphy_xpcs_mmd_write(uint16_t mmd_num, uint16_t mmd_reg,
uint16_t reg_val)
{
uint32_t uniphy_xpcs_addr = 0;
#ifdef DEBUG
uint16_t phy_data = 0;
#endif
qca8084_serdes_addr_get(QCA8084_UNIPHY_XPCS, &uniphy_xpcs_addr);
qca8084_phy_mmd_write(uniphy_xpcs_addr, mmd_num, mmd_reg, reg_val);
/*check the mmd register value*/
#ifdef DEBUG
phy_data =
#endif
qca8084_uniphy_xpcs_mmd_read(mmd_num, mmd_reg);
pr_debug("phy_addr:0x%x, mmd_num:0x%x, mmd_reg:0x%x, phy_data:0x%x\n",
uniphy_xpcs_addr, mmd_num, mmd_reg, phy_data);
}
static void qca8084_uniphy_xpcs_modify_mmd(uint32_t mmd_num, uint32_t mmd_reg,
uint32_t mask, uint32_t value)
{
uint16_t phy_data = 0, new_phy_data = 0;
phy_data = qca8084_uniphy_xpcs_mmd_read(mmd_num, mmd_reg);
new_phy_data = (phy_data & ~mask) | value;
qca8084_uniphy_xpcs_mmd_write(mmd_num, mmd_reg, new_phy_data);
}
uint8_t qca8084_uniphy_mode_check(uint32_t uniphy_index,
qca8084_uniphy_mode_t uniphy_mode)
{
uint32_t uniphy_addr = 0;
uint16_t uniphy_mode_ctrl_data = 0;
qca8084_serdes_addr_get(uniphy_index, &uniphy_addr);
uniphy_mode_ctrl_data = qca8084_phy_mmd_read(uniphy_addr,
QCA8084_UNIPHY_MMD1, QCA8084_UNIPHY_MMD1_MODE_CTRL);
if(uniphy_mode_ctrl_data == PHY_INVALID_DATA)
return 0;
if(!(uniphy_mode & uniphy_mode_ctrl_data))
return 0;
return 1;
}
static uint32_t qca8084_uniphy_xpcs_port_to_mmd(uint32_t qca8084_port_id)
{
uint32_t mmd_id = 0;
switch(qca8084_port_id)
{
case PORT1:
mmd_id = QCA8084_UNIPHY_MMD31;
break;
case PORT2:
mmd_id = QCA8084_UNIPHY_MMD26;
break;
case PORT3:
mmd_id = QCA8084_UNIPHY_MMD27;
break;
case PORT4:
mmd_id = QCA8084_UNIPHY_MMD28;
break;
default:
pr_debug("Port not matching qca8084 ports\n");
}
return mmd_id;
}
static void qca8084_uniphy_xpcs_modify_port_mmd(uint32_t qca8084_port_id,
uint32_t mmd_reg, uint32_t mask,
uint32_t value)
{
uint32_t mmd_id = 0;
mmd_id = qca8084_uniphy_xpcs_port_to_mmd(qca8084_port_id);
qca8084_uniphy_xpcs_modify_mmd(mmd_id, mmd_reg, mask, value);
}
void qca8084_port_speed_clock_set(uint32_t qca8084_port_id,
fal_port_speed_t speed)
{
uint32_t clk_rate = 0;
switch(speed)
{
case FAL_SPEED_2500:
clk_rate = UQXGMII_SPEED_2500M_CLK;
break;
case FAL_SPEED_1000:
clk_rate = UQXGMII_SPEED_1000M_CLK;
break;
case FAL_SPEED_100:
clk_rate = UQXGMII_SPEED_100M_CLK;
break;
case FAL_SPEED_10:
clk_rate = UQXGMII_SPEED_10M_CLK;
break;
default:
pr_debug("Unknown speed\n");
return;
}
qca8084_port_clk_rate_set(qca8084_port_id, clk_rate);
}
static void qca8084_uniphy_xpcs_8023az_enable(void)
{
uint16_t uniphy_data = 0;
uniphy_data = qca8084_uniphy_xpcs_mmd_read(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_AN_LP_BASE_ABL2);
if(!(uniphy_data & QCA8084_UNIPHY_MMD3_XPCS_EEE_CAP))
return;
/*Configure the EEE related timer*/
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_EEE_MODE_CTRL,
0x0f40, QCA8084_UNIPHY_MMD3_EEE_RES_REGS |
QCA8084_UNIPHY_MMD3_EEE_SIGN_BIT_REGS);
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_EEE_TX_TIMER,
0x1fff, QCA8084_UNIPHY_MMD3_EEE_TSL_REGS|
QCA8084_UNIPHY_MMD3_EEE_TLU_REGS |
QCA8084_UNIPHY_MMD3_EEE_TWL_REGS);
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_EEE_RX_TIMER,
0x1fff, QCA8084_UNIPHY_MMD3_EEE_100US_REG_REGS|
QCA8084_UNIPHY_MMD3_EEE_RWR_REG_REGS);
/*enable TRN_LPI*/
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_EEE_MODE_CTRL1,
0x101, QCA8084_UNIPHY_MMD3_EEE_TRANS_LPI_MODE|
QCA8084_UNIPHY_MMD3_EEE_TRANS_RX_LPI_MODE);
/*enable TX/RX LPI pattern*/
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_EEE_MODE_CTRL,
0x3, QCA8084_UNIPHY_MMD3_EEE_EN);
}
static void qca8084_uniphy_calibration(uint32_t uniphy_addr)
{
uint16_t uniphy_data = 0;
uint32_t retries = 100, calibration_done = 0;
/* wait calibration done to uniphy*/
while (calibration_done != QCA8084_UNIPHY_MMD1_CALIBRATION_DONE) {
mdelay(1);
if (retries-- == 0)
pr_debug("uniphy callibration time out!\n");
uniphy_data = qca8084_phy_mmd_read(uniphy_addr, QCA8084_UNIPHY_MMD1,
QCA8084_UNIPHY_MMD1_CALIBRATION4);
calibration_done = (uniphy_data & QCA8084_UNIPHY_MMD1_CALIBRATION_DONE);
}
}
static void qca8084_uniphy_xpcs_10g_r_linkup(void)
{
uint16_t uniphy_data = 0;
uint32_t retries = 100, linkup = 0;
/* wait 10G_R link up */
while (linkup != QCA8084_UNIPHY_MMD3_10GBASE_R_UP) {
mdelay(1);
if (retries-- == 0)
pr_debug("10g_r link up timeout\n");
uniphy_data = qca8084_uniphy_xpcs_mmd_read(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_10GBASE_R_PCS_STATUS1);
linkup = (uniphy_data & QCA8084_UNIPHY_MMD3_10GBASE_R_UP);
}
}
static void qca8084_uniphy_xpcs_soft_reset(void)
{
uint16_t uniphy_data = 0;
uint32_t retries = 100, reset_done = QCA8084_UNIPHY_MMD3_XPCS_SOFT_RESET;
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_DIG_CTRL1, 0x8000,
QCA8084_UNIPHY_MMD3_XPCS_SOFT_RESET);
while (reset_done) {
mdelay(1);
if (retries-- == 0)
pr_debug("xpcs soft reset timeout\n");
uniphy_data = qca8084_uniphy_xpcs_mmd_read(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_DIG_CTRL1);
reset_done = (uniphy_data & QCA8084_UNIPHY_MMD3_XPCS_SOFT_RESET);
}
}
void qca8084_uniphy_xpcs_speed_set(uint32_t qca8084_port_id,
fal_port_speed_t speed)
{
uint32_t xpcs_speed = 0;
switch(speed)
{
case FAL_SPEED_2500:
xpcs_speed = QCA8084_UNIPHY_MMD_XPC_SPEED_2500;
break;
case FAL_SPEED_1000:
xpcs_speed = QCA8084_UNIPHY_MMD_XPC_SPEED_1000;
break;
case FAL_SPEED_100:
xpcs_speed = QCA8084_UNIPHY_MMD_XPC_SPEED_100;
break;
case FAL_SPEED_10:
xpcs_speed = QCA8084_UNIPHY_MMD_XPC_SPEED_10;
break;
default:
pr_debug("Unknown speed\n");
return;
}
qca8084_uniphy_xpcs_modify_port_mmd(qca8084_port_id,
QCA8084_UNIPHY_MMD_MII_CTRL,
QCA8084_UNIPHY_MMD_XPC_SPEED_MASK,
xpcs_speed);
}
void qca8084_uniphy_uqxgmii_function_reset(uint32_t qca8084_port_id)
{
uint32_t uniphy_addr = 0;
qca8084_serdes_addr_get(QCA8084_UNIPHY_SGMII_1, &uniphy_addr);
qca8084_phy_modify_mmd(uniphy_addr, QCA8084_UNIPHY_MMD1,
QCA8084_UNIPHY_MMD1_USXGMII_RESET, BIT(qca8084_port_id-1), 0);
mdelay(1);
qca8084_phy_modify_mmd(uniphy_addr, QCA8084_UNIPHY_MMD1,
QCA8084_UNIPHY_MMD1_USXGMII_RESET, BIT(qca8084_port_id-1),
BIT(qca8084_port_id-1));
if(qca8084_port_id == PORT1)
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD_MII_DIG_CTRL,
0x400, QCA8084_UNIPHY_MMD3_USXG_FIFO_RESET);
else
qca8084_uniphy_xpcs_modify_port_mmd(qca8084_port_id,
QCA8084_UNIPHY_MMD_MII_DIG_CTRL,
0x20, QCA8084_UNIPHY_MMD_USXG_FIFO_RESET);
}
void qca8084_uniphy_xpcs_autoneg_restart(uint32_t qca8084_port_id)
{
uint32_t retries = 500, uniphy_data = 0, mmd_id = 0;
mmd_id = qca8084_uniphy_xpcs_port_to_mmd(qca8084_port_id);
qca8084_uniphy_xpcs_modify_mmd(mmd_id, QCA8084_UNIPHY_MMD_MII_CTRL,
QCA8084_UNIPHY_MMD_MII_AN_RESTART, QCA8084_UNIPHY_MMD_MII_AN_RESTART);
mdelay(1);
uniphy_data = qca8084_uniphy_xpcs_mmd_read(mmd_id,
QCA8084_UNIPHY_MMD_MII_ERR_SEL);
while(!(uniphy_data & QCA8084_UNIPHY_MMD_MII_AN_COMPLETE_INT))
{
mdelay(1);
if (retries-- == 0)
{
pr_debug("xpcs uniphy autoneg restart timeout\n");
}
uniphy_data = qca8084_uniphy_xpcs_mmd_read(mmd_id,
QCA8084_UNIPHY_MMD_MII_ERR_SEL);
}
}
static void _qca8084_interface_uqxgmii_mode_set(uint32_t uniphy_addr)
{
uint32_t qca8084_port_id = 0, phy_addr = 0;
/*reset xpcs*/
pr_debug("reset xpcs\n");
qca8084_clk_assert(QCA8084_UNIPHY_XPCS_RST);
/*select xpcs mode*/
pr_debug("select xpcs mode\n");
qca8084_phy_modify_mmd(uniphy_addr, QCA8084_UNIPHY_MMD1,
QCA8084_UNIPHY_MMD1_MODE_CTRL, 0x1f00, QCA8084_UNIPHY_MMD1_XPCS_MODE);
/*config dapa pass as usxgmii*/
pr_debug("config dapa pass as usxgmii\n");
qca8084_phy_modify_mmd(uniphy_addr, QCA8084_UNIPHY_MMD1,
QCA8084_UNIPHY_MMD1_GMII_DATAPASS_SEL, QCA8084_UNIPHY_MMD1_DATAPASS_MASK,
QCA8084_UNIPHY_MMD1_DATAPASS_USXGMII);
/*reset and release uniphy GMII/XGMII and ethphy GMII*/
pr_debug("reset and release uniphy GMII/XGMII and ethphy GMII\n");
for(qca8084_port_id = PORT1; qca8084_port_id <= PORT4;
qca8084_port_id++)
{
qca8084_port_clk_reset(qca8084_port_id,
QCA8084_CLK_TYPE_UNIPHY|QCA8084_CLK_TYPE_EPHY);
}
/*ana sw reset and release*/
pr_debug("ana sw reset and release\n");
qca8084_phy_modify_mii(uniphy_addr,
QCA8084_UNIPHY_PLL_POWER_ON_AND_RESET, 0x40, QCA8084_UNIPHY_ANA_SOFT_RESET);
mdelay(10);
qca8084_phy_modify_mii(uniphy_addr,
QCA8084_UNIPHY_PLL_POWER_ON_AND_RESET, 0x40, QCA8084_UNIPHY_ANA_SOFT_RELEASE);
/*Wait calibration done*/
pr_debug("Wait calibration done\n");
qca8084_uniphy_calibration(uniphy_addr);
/*Enable SSCG(Spread Spectrum Clock Generator)*/
pr_debug("enable uniphy sscg\n");
qca8084_phy_modify_mmd(uniphy_addr, QCA8084_UNIPHY_MMD1,
QCA8084_UNIPHY_MMD1_CDA_CONTROL1, 0x8, QCA8084_UNIPHY_MMD1_SSCG_ENABLE);
/*release XPCS*/
pr_debug("release XPCS\n");
qca8084_clk_deassert(QCA8084_UNIPHY_XPCS_RST);
/*ethphy software reset*/
pr_debug("ethphy software reset\n");
for(qca8084_port_id = PORT1; qca8084_port_id <= PORT4;
qca8084_port_id++)
{
qca8084_ephy_addr_get(qca8084_port_id, &phy_addr);
qca8084_phy_reset(phy_addr);
}
/*Set BaseR mode*/
pr_debug("Set BaseR mode\n");
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_PCS_CTRL2, 0xf, QCA8084_UNIPHY_MMD3_PCS_TYPE_10GBASE_R);
/*wait 10G base_r link up*/
pr_debug("wait 10G base_r link up\n");
qca8084_uniphy_xpcs_10g_r_linkup();
/*enable UQXGMII mode*/
pr_debug("enable UQSXGMII mode\n");
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_DIG_CTRL1, 0x200, QCA8084_UNIPHY_MMD3_USXGMII_EN);
/*set UQXGMII mode*/
pr_debug("set QXGMII mode\n");
qca8084_uniphy_xpcs_modify_mmd(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_VR_RPCS_TPC, 0x1c00, QCA8084_UNIPHY_MMD3_QXGMII_EN);
/*set AM interval*/
pr_debug("set AM interval\n");
qca8084_uniphy_xpcs_mmd_write(QCA8084_UNIPHY_MMD3,
QCA8084_UNIPHY_MMD3_MII_AM_INTERVAL, QCA8084_UNIPHY_MMD3_MII_AM_INTERVAL_VAL);
/*xpcs software reset*/
pr_debug("xpcs software reset\n");
qca8084_uniphy_xpcs_soft_reset();
}
void qca8084_interface_uqxgmii_mode_set(void)
{
uint32_t uniphy_addr = 0, qca8084_port_id = 0;
qca8084_serdes_addr_get(QCA8084_UNIPHY_SGMII_1, &uniphy_addr);
/*disable IPG_tuning bypass*/
pr_debug("disable IPG_tuning bypass\n");
qca8084_phy_modify_mmd(uniphy_addr, QCA8084_UNIPHY_MMD1,
QCA8084_UNIPHY_MMD1_BYPASS_TUNING_IPG,
QCA8084_UNIPHY_MMD1_BYPASS_TUNING_IPG_EN, 0);
/*disable uniphy GMII/XGMII clock and disable ethphy GMII clock*/
pr_debug("disable uniphy GMII/XGMII clock and ethphy GMII clock\n");
for(qca8084_port_id = PORT1; qca8084_port_id <= PORT4;
qca8084_port_id++)
{
qca8084_port_clk_en_set(qca8084_port_id,
QCA8084_CLK_TYPE_UNIPHY|QCA8084_CLK_TYPE_EPHY, 0);
}
/*configure uqxgmii mode*/
pr_debug("configure uqxgmii mode\n");
_qca8084_interface_uqxgmii_mode_set(uniphy_addr);
/*enable auto-neg complete interrupt,Mii using mii-4bits,
configure as PHY mode, enable autoneg ability*/
pr_debug("enable auto-neg complete interrupt, Mii using mii-4bits,"
" configure as PHY mode, enable autoneg ability, disable TICD\n");
for (qca8084_port_id = PORT1; qca8084_port_id <= PORT4;
qca8084_port_id++)
{
/*enable auto-neg complete interrupt,Mii using mii-4bits,configure as PHY mode*/
qca8084_uniphy_xpcs_modify_port_mmd(qca8084_port_id,
QCA8084_UNIPHY_MMD_MII_AN_INT_MSK, 0x109,
QCA8084_UNIPHY_MMD_AN_COMPLETE_INT |
QCA8084_UNIPHY_MMD_MII_4BITS_CTRL |
QCA8084_UNIPHY_MMD_TX_CONFIG_CTRL);
/*enable autoneg ability*/
qca8084_uniphy_xpcs_modify_port_mmd(qca8084_port_id,
QCA8084_UNIPHY_MMD_MII_CTRL, 0x3060, QCA8084_UNIPHY_MMD_MII_AN_ENABLE |
QCA8084_UNIPHY_MMD_XPC_SPEED_1000);
/*disable TICD*/
qca8084_uniphy_xpcs_modify_port_mmd(qca8084_port_id,
QCA8084_UNIPHY_MMD_MII_XAUI_MODE_CTRL, 0x1,
QCA8084_UNIPHY_MMD_TX_IPG_CHECK_DISABLE);
}
/*enable EEE for xpcs*/
pr_debug("enable EEE for xpcs\n");
qca8084_uniphy_xpcs_8023az_enable();
}