#include <linux/mdio.h>
#include <linux/of.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
#include <linux/phy/phy-common-props.h>
#include <linux/phylink.h>
#include <linux/regmap.h>
#define SGMSYS_PCS_CONTROL_1 0x0
#define SGMII_BMCR GENMASK(15, 0)
#define SGMII_BMSR GENMASK(31, 16)
#define SGMSYS_PCS_DEVICE_ID 0x4
#define SGMII_LYNXI_DEV_ID 0x4d544950
#define SGMSYS_PCS_ADVERTISE 0x8
#define SGMII_ADVERTISE GENMASK(15, 0)
#define SGMII_LPA GENMASK(31, 16)
#define SGMSYS_PCS_SCRATCH 0x14
#define SGMII_DEV_VERSION GENMASK(31, 16)
#define SGMSYS_PCS_LINK_TIMER 0x18
#define SGMII_LINK_TIMER_MASK GENMASK(19, 0)
#define SGMII_LINK_TIMER_VAL(ns) FIELD_PREP(SGMII_LINK_TIMER_MASK, \
((ns) / 2 / 8))
#define SGMSYS_SGMII_MODE 0x20
#define SGMII_IF_MODE_SGMII BIT(0)
#define SGMII_SPEED_DUPLEX_AN BIT(1)
#define SGMII_SPEED_MASK GENMASK(3, 2)
#define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0)
#define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1)
#define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2)
#define SGMII_DUPLEX_HALF BIT(4)
#define SGMII_REMOTE_FAULT_DIS BIT(8)
#define SGMSYS_RESERVED_0 0x34
#define SGMII_SW_RESET BIT(0)
#define SGMII_PHY_SPEED_MASK GENMASK(3, 2)
#define SGMII_PHY_SPEED_1_25G FIELD_PREP(SGMII_PHY_SPEED_MASK, 0)
#define SGMII_PHY_SPEED_3_125G FIELD_PREP(SGMII_PHY_SPEED_MASK, 1)
#define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8
#define SGMII_PHYA_PWD BIT(4)
#define SGMSYS_QPHY_WRAP_CTRL 0xec
#define SGMII_PN_SWAP_RX BIT(1)
#define SGMII_PN_SWAP_TX BIT(0)
struct mtk_pcs_lynxi {
struct regmap *regmap;
u32 ana_rgc3;
phy_interface_t interface;
struct phylink_pcs pcs;
u32 flags;
struct fwnode_handle *fwnode;
};
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mtk_pcs_lynxi, pcs);
}
static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
switch (interface) {
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_SGMII:
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
default:
return 0;
}
}
static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
unsigned int bm, adv;
regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
phylink_mii_c22_pcs_decode_state(state, neg_mode,
FIELD_GET(SGMII_BMSR, bm),
FIELD_GET(SGMII_LPA, adv));
}
static int mtk_pcs_config_polarity(struct mtk_pcs_lynxi *mpcs,
phy_interface_t interface)
{
struct fwnode_handle *fwnode = mpcs->fwnode, *pcs_fwnode;
unsigned int pol, default_pol = PHY_POL_NORMAL;
unsigned int val = 0;
int ret;
if (fwnode_property_read_bool(fwnode, "mediatek,pnswap"))
default_pol = PHY_POL_INVERT;
pcs_fwnode = fwnode_get_named_child_node(fwnode, "pcs");
ret = phy_get_rx_polarity(pcs_fwnode, phy_modes(interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
if (ret) {
fwnode_handle_put(pcs_fwnode);
return ret;
}
if (pol == PHY_POL_INVERT)
val |= SGMII_PN_SWAP_RX;
ret = phy_get_tx_polarity(pcs_fwnode, phy_modes(interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
fwnode_handle_put(pcs_fwnode);
if (ret)
return ret;
if (pol == PHY_POL_INVERT)
val |= SGMII_PN_SWAP_TX;
return regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
SGMII_PN_SWAP_RX | SGMII_PN_SWAP_TX, val);
}
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
bool mode_changed = false, changed;
unsigned int rgc3, sgm_mode, bmcr;
int advertise, link_timer;
int ret;
advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
advertising);
if (advertise < 0)
return advertise;
if (interface == PHY_INTERFACE_MODE_SGMII)
sgm_mode = SGMII_IF_MODE_SGMII;
else
sgm_mode = 0;
if (neg_mode & PHYLINK_PCS_NEG_INBAND)
sgm_mode |= SGMII_REMOTE_FAULT_DIS;
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
if (interface == PHY_INTERFACE_MODE_SGMII)
sgm_mode |= SGMII_SPEED_DUPLEX_AN;
bmcr = BMCR_ANENABLE;
} else {
bmcr = 0;
}
if (mpcs->interface != interface) {
link_timer = phylink_get_link_timer_ns(interface);
if (link_timer < 0)
return link_timer;
regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
SGMII_PHYA_PWD);
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
ret = mtk_pcs_config_polarity(mpcs, interface);
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
rgc3 = SGMII_PHY_SPEED_3_125G;
else
rgc3 = SGMII_PHY_SPEED_1_25G;
regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
SGMII_PHY_SPEED_MASK, rgc3);
regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
SGMII_LINK_TIMER_VAL(link_timer));
mpcs->interface = interface;
mode_changed = true;
}
regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
SGMII_ADVERTISE, advertise, &changed);
regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
SGMII_IF_MODE_SGMII, sgm_mode);
regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
BMCR_ANENABLE, bmcr);
usleep_range(50, 100);
regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
return changed || mode_changed;
}
static void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART);
}
static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs,
unsigned int neg_mode,
phy_interface_t interface, int speed,
int duplex)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
unsigned int sgm_mode;
if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
if (speed == SPEED_10)
sgm_mode = SGMII_SPEED_10;
else if (speed == SPEED_100)
sgm_mode = SGMII_SPEED_100;
else
sgm_mode = SGMII_SPEED_1000;
if (duplex != DUPLEX_FULL)
sgm_mode |= SGMII_DUPLEX_HALF;
regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
SGMII_DUPLEX_HALF | SGMII_SPEED_MASK,
sgm_mode);
}
}
static void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
mpcs->interface = PHY_INTERFACE_MODE_NA;
}
static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
.pcs_inband_caps = mtk_pcs_lynxi_inband_caps,
.pcs_get_state = mtk_pcs_lynxi_get_state,
.pcs_config = mtk_pcs_lynxi_config,
.pcs_an_restart = mtk_pcs_lynxi_restart_an,
.pcs_link_up = mtk_pcs_lynxi_link_up,
.pcs_disable = mtk_pcs_lynxi_disable,
};
struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
struct fwnode_handle *fwnode,
struct regmap *regmap, u32 ana_rgc3)
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
int ret;
ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
if (ret < 0)
return NULL;
if (id != SGMII_LYNXI_DEV_ID) {
dev_err(dev, "unknown PCS device id %08x\n", id);
return NULL;
}
ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
if (ret < 0)
return NULL;
ver = FIELD_GET(SGMII_DEV_VERSION, ver);
if (ver != 0x1) {
dev_err(dev, "unknown PCS device version %04x\n", ver);
return NULL;
}
dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id,
ver);
mpcs = kzalloc_obj(*mpcs);
if (!mpcs)
return NULL;
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
mpcs->pcs.poll = true;
mpcs->interface = PHY_INTERFACE_MODE_NA;
mpcs->fwnode = fwnode_handle_get(fwnode);
__set_bit(PHY_INTERFACE_MODE_SGMII, mpcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, mpcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_2500BASEX, mpcs->pcs.supported_interfaces);
return &mpcs->pcs;
}
EXPORT_SYMBOL(mtk_pcs_lynxi_create);
void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs;
if (!pcs)
return;
mpcs = pcs_to_mtk_pcs_lynxi(pcs);
fwnode_handle_put(mpcs->fwnode);
kfree(mpcs);
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
MODULE_DESCRIPTION("MediaTek SGMII library for LynxI");
MODULE_LICENSE("GPL");