162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// Copyright (c) 2018-2019 MediaTek Inc. 362306a36Sopenharmony_ci/* A library for MediaTek SGMII circuit 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Sean Wang <sean.wang@mediatek.com> 662306a36Sopenharmony_ci * Author: Alexander Couzens <lynxis@fe80.eu> 762306a36Sopenharmony_ci * Author: Daniel Golle <daniel@makrotopia.org> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/mdio.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/pcs/pcs-mtk-lynxi.h> 1462306a36Sopenharmony_ci#include <linux/phylink.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/* SGMII subsystem config registers */ 1862306a36Sopenharmony_ci/* BMCR (low 16) BMSR (high 16) */ 1962306a36Sopenharmony_ci#define SGMSYS_PCS_CONTROL_1 0x0 2062306a36Sopenharmony_ci#define SGMII_BMCR GENMASK(15, 0) 2162306a36Sopenharmony_ci#define SGMII_BMSR GENMASK(31, 16) 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define SGMSYS_PCS_DEVICE_ID 0x4 2462306a36Sopenharmony_ci#define SGMII_LYNXI_DEV_ID 0x4d544950 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define SGMSYS_PCS_ADVERTISE 0x8 2762306a36Sopenharmony_ci#define SGMII_ADVERTISE GENMASK(15, 0) 2862306a36Sopenharmony_ci#define SGMII_LPA GENMASK(31, 16) 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define SGMSYS_PCS_SCRATCH 0x14 3162306a36Sopenharmony_ci#define SGMII_DEV_VERSION GENMASK(31, 16) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* Register to programmable link timer, the unit in 2 * 8ns */ 3462306a36Sopenharmony_ci#define SGMSYS_PCS_LINK_TIMER 0x18 3562306a36Sopenharmony_ci#define SGMII_LINK_TIMER_MASK GENMASK(19, 0) 3662306a36Sopenharmony_ci#define SGMII_LINK_TIMER_VAL(ns) FIELD_PREP(SGMII_LINK_TIMER_MASK, \ 3762306a36Sopenharmony_ci ((ns) / 2 / 8)) 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* Register to control remote fault */ 4062306a36Sopenharmony_ci#define SGMSYS_SGMII_MODE 0x20 4162306a36Sopenharmony_ci#define SGMII_IF_MODE_SGMII BIT(0) 4262306a36Sopenharmony_ci#define SGMII_SPEED_DUPLEX_AN BIT(1) 4362306a36Sopenharmony_ci#define SGMII_SPEED_MASK GENMASK(3, 2) 4462306a36Sopenharmony_ci#define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0) 4562306a36Sopenharmony_ci#define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1) 4662306a36Sopenharmony_ci#define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2) 4762306a36Sopenharmony_ci#define SGMII_DUPLEX_HALF BIT(4) 4862306a36Sopenharmony_ci#define SGMII_REMOTE_FAULT_DIS BIT(8) 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* Register to reset SGMII design */ 5162306a36Sopenharmony_ci#define SGMSYS_RESERVED_0 0x34 5262306a36Sopenharmony_ci#define SGMII_SW_RESET BIT(0) 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* Register to set SGMII speed, ANA RG_ Control Signals III */ 5562306a36Sopenharmony_ci#define SGMII_PHY_SPEED_MASK GENMASK(3, 2) 5662306a36Sopenharmony_ci#define SGMII_PHY_SPEED_1_25G FIELD_PREP(SGMII_PHY_SPEED_MASK, 0) 5762306a36Sopenharmony_ci#define SGMII_PHY_SPEED_3_125G FIELD_PREP(SGMII_PHY_SPEED_MASK, 1) 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* Register to power up QPHY */ 6062306a36Sopenharmony_ci#define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8 6162306a36Sopenharmony_ci#define SGMII_PHYA_PWD BIT(4) 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/* Register to QPHY wrapper control */ 6462306a36Sopenharmony_ci#define SGMSYS_QPHY_WRAP_CTRL 0xec 6562306a36Sopenharmony_ci#define SGMII_PN_SWAP_MASK GENMASK(1, 0) 6662306a36Sopenharmony_ci#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1)) 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated 6962306a36Sopenharmony_ci * data 7062306a36Sopenharmony_ci * @regmap: The register map pointing at the range used to setup 7162306a36Sopenharmony_ci * SGMII modes 7262306a36Sopenharmony_ci * @dev: Pointer to device owning the PCS 7362306a36Sopenharmony_ci * @ana_rgc3: The offset of register ANA_RGC3 relative to regmap 7462306a36Sopenharmony_ci * @interface: Currently configured interface mode 7562306a36Sopenharmony_ci * @pcs: Phylink PCS structure 7662306a36Sopenharmony_ci * @flags: Flags indicating hardware properties 7762306a36Sopenharmony_ci */ 7862306a36Sopenharmony_cistruct mtk_pcs_lynxi { 7962306a36Sopenharmony_ci struct regmap *regmap; 8062306a36Sopenharmony_ci u32 ana_rgc3; 8162306a36Sopenharmony_ci phy_interface_t interface; 8262306a36Sopenharmony_ci struct phylink_pcs pcs; 8362306a36Sopenharmony_ci u32 flags; 8462306a36Sopenharmony_ci}; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci return container_of(pcs, struct mtk_pcs_lynxi, pcs); 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs, 9262306a36Sopenharmony_ci struct phylink_link_state *state) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); 9562306a36Sopenharmony_ci unsigned int bm, adv; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci /* Read the BMSR and LPA */ 9862306a36Sopenharmony_ci regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm); 9962306a36Sopenharmony_ci regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm), 10262306a36Sopenharmony_ci FIELD_GET(SGMII_LPA, adv)); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode, 10662306a36Sopenharmony_ci phy_interface_t interface, 10762306a36Sopenharmony_ci const unsigned long *advertising, 10862306a36Sopenharmony_ci bool permit_pause_to_mac) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); 11162306a36Sopenharmony_ci bool mode_changed = false, changed; 11262306a36Sopenharmony_ci unsigned int rgc3, sgm_mode, bmcr; 11362306a36Sopenharmony_ci int advertise, link_timer; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci advertise = phylink_mii_c22_pcs_encode_advertisement(interface, 11662306a36Sopenharmony_ci advertising); 11762306a36Sopenharmony_ci if (advertise < 0) 11862306a36Sopenharmony_ci return advertise; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and 12162306a36Sopenharmony_ci * we assume that fixes it's speed at bitrate = line rate (in 12262306a36Sopenharmony_ci * other words, 1000Mbps or 2500Mbps). 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci if (interface == PHY_INTERFACE_MODE_SGMII) 12562306a36Sopenharmony_ci sgm_mode = SGMII_IF_MODE_SGMII; 12662306a36Sopenharmony_ci else 12762306a36Sopenharmony_ci sgm_mode = 0; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci if (neg_mode & PHYLINK_PCS_NEG_INBAND) 13062306a36Sopenharmony_ci sgm_mode |= SGMII_REMOTE_FAULT_DIS; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { 13362306a36Sopenharmony_ci if (interface == PHY_INTERFACE_MODE_SGMII) 13462306a36Sopenharmony_ci sgm_mode |= SGMII_SPEED_DUPLEX_AN; 13562306a36Sopenharmony_ci bmcr = BMCR_ANENABLE; 13662306a36Sopenharmony_ci } else { 13762306a36Sopenharmony_ci bmcr = 0; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (mpcs->interface != interface) { 14162306a36Sopenharmony_ci link_timer = phylink_get_link_timer_ns(interface); 14262306a36Sopenharmony_ci if (link_timer < 0) 14362306a36Sopenharmony_ci return link_timer; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci /* PHYA power down */ 14662306a36Sopenharmony_ci regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 14762306a36Sopenharmony_ci SGMII_PHYA_PWD); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci /* Reset SGMII PCS state */ 15062306a36Sopenharmony_ci regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0, 15162306a36Sopenharmony_ci SGMII_SW_RESET); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP) 15462306a36Sopenharmony_ci regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL, 15562306a36Sopenharmony_ci SGMII_PN_SWAP_MASK, 15662306a36Sopenharmony_ci SGMII_PN_SWAP_TX_RX); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (interface == PHY_INTERFACE_MODE_2500BASEX) 15962306a36Sopenharmony_ci rgc3 = SGMII_PHY_SPEED_3_125G; 16062306a36Sopenharmony_ci else 16162306a36Sopenharmony_ci rgc3 = SGMII_PHY_SPEED_1_25G; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* Configure the underlying interface speed */ 16462306a36Sopenharmony_ci regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3, 16562306a36Sopenharmony_ci SGMII_PHY_SPEED_MASK, rgc3); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci /* Setup the link timer */ 16862306a36Sopenharmony_ci regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER, 16962306a36Sopenharmony_ci SGMII_LINK_TIMER_VAL(link_timer)); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci mpcs->interface = interface; 17262306a36Sopenharmony_ci mode_changed = true; 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* Update the advertisement, noting whether it has changed */ 17662306a36Sopenharmony_ci regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE, 17762306a36Sopenharmony_ci SGMII_ADVERTISE, advertise, &changed); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* Update the sgmsys mode register */ 18062306a36Sopenharmony_ci regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE, 18162306a36Sopenharmony_ci SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN | 18262306a36Sopenharmony_ci SGMII_IF_MODE_SGMII, sgm_mode); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci /* Update the BMCR */ 18562306a36Sopenharmony_ci regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, 18662306a36Sopenharmony_ci BMCR_ANENABLE, bmcr); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* Release PHYA power down state 18962306a36Sopenharmony_ci * Only removing bit SGMII_PHYA_PWD isn't enough. 19062306a36Sopenharmony_ci * There are cases when the SGMII_PHYA_PWD register contains 0x9 which 19162306a36Sopenharmony_ci * prevents SGMII from working. The SGMII still shows link but no traffic 19262306a36Sopenharmony_ci * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was 19362306a36Sopenharmony_ci * taken from a good working state of the SGMII interface. 19462306a36Sopenharmony_ci * Unknown how much the QPHY needs but it is racy without a sleep. 19562306a36Sopenharmony_ci * Tested on mt7622 & mt7986. 19662306a36Sopenharmony_ci */ 19762306a36Sopenharmony_ci usleep_range(50, 100); 19862306a36Sopenharmony_ci regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci return changed || mode_changed; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART); 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, 21162306a36Sopenharmony_ci unsigned int neg_mode, 21262306a36Sopenharmony_ci phy_interface_t interface, int speed, 21362306a36Sopenharmony_ci int duplex) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); 21662306a36Sopenharmony_ci unsigned int sgm_mode; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) { 21962306a36Sopenharmony_ci /* Force the speed and duplex setting */ 22062306a36Sopenharmony_ci if (speed == SPEED_10) 22162306a36Sopenharmony_ci sgm_mode = SGMII_SPEED_10; 22262306a36Sopenharmony_ci else if (speed == SPEED_100) 22362306a36Sopenharmony_ci sgm_mode = SGMII_SPEED_100; 22462306a36Sopenharmony_ci else 22562306a36Sopenharmony_ci sgm_mode = SGMII_SPEED_1000; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if (duplex != DUPLEX_FULL) 22862306a36Sopenharmony_ci sgm_mode |= SGMII_DUPLEX_HALF; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE, 23162306a36Sopenharmony_ci SGMII_DUPLEX_HALF | SGMII_SPEED_MASK, 23262306a36Sopenharmony_ci sgm_mode); 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci mpcs->interface = PHY_INTERFACE_MODE_NA; 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic const struct phylink_pcs_ops mtk_pcs_lynxi_ops = { 24462306a36Sopenharmony_ci .pcs_get_state = mtk_pcs_lynxi_get_state, 24562306a36Sopenharmony_ci .pcs_config = mtk_pcs_lynxi_config, 24662306a36Sopenharmony_ci .pcs_an_restart = mtk_pcs_lynxi_restart_an, 24762306a36Sopenharmony_ci .pcs_link_up = mtk_pcs_lynxi_link_up, 24862306a36Sopenharmony_ci .pcs_disable = mtk_pcs_lynxi_disable, 24962306a36Sopenharmony_ci}; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_cistruct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev, 25262306a36Sopenharmony_ci struct regmap *regmap, u32 ana_rgc3, 25362306a36Sopenharmony_ci u32 flags) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci struct mtk_pcs_lynxi *mpcs; 25662306a36Sopenharmony_ci u32 id, ver; 25762306a36Sopenharmony_ci int ret; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id); 26062306a36Sopenharmony_ci if (ret < 0) 26162306a36Sopenharmony_ci return NULL; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci if (id != SGMII_LYNXI_DEV_ID) { 26462306a36Sopenharmony_ci dev_err(dev, "unknown PCS device id %08x\n", id); 26562306a36Sopenharmony_ci return NULL; 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver); 26962306a36Sopenharmony_ci if (ret < 0) 27062306a36Sopenharmony_ci return NULL; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci ver = FIELD_GET(SGMII_DEV_VERSION, ver); 27362306a36Sopenharmony_ci if (ver != 0x1) { 27462306a36Sopenharmony_ci dev_err(dev, "unknown PCS device version %04x\n", ver); 27562306a36Sopenharmony_ci return NULL; 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id, 27962306a36Sopenharmony_ci ver); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci mpcs = kzalloc(sizeof(*mpcs), GFP_KERNEL); 28262306a36Sopenharmony_ci if (!mpcs) 28362306a36Sopenharmony_ci return NULL; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci mpcs->ana_rgc3 = ana_rgc3; 28662306a36Sopenharmony_ci mpcs->regmap = regmap; 28762306a36Sopenharmony_ci mpcs->flags = flags; 28862306a36Sopenharmony_ci mpcs->pcs.ops = &mtk_pcs_lynxi_ops; 28962306a36Sopenharmony_ci mpcs->pcs.neg_mode = true; 29062306a36Sopenharmony_ci mpcs->pcs.poll = true; 29162306a36Sopenharmony_ci mpcs->interface = PHY_INTERFACE_MODE_NA; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci return &mpcs->pcs; 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ciEXPORT_SYMBOL(mtk_pcs_lynxi_create); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_civoid mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci if (!pcs) 30062306a36Sopenharmony_ci return; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci kfree(pcs_to_mtk_pcs_lynxi(pcs)); 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ciEXPORT_SYMBOL(mtk_pcs_lynxi_destroy); 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 307