162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for the onsemi 10BASE-T1S NCN26000 PHYs family. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2022 onsemi 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/kernel.h> 862306a36Sopenharmony_ci#include <linux/bitfield.h> 962306a36Sopenharmony_ci#include <linux/errno.h> 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/mii.h> 1362306a36Sopenharmony_ci#include <linux/phy.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include "mdio-open-alliance.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define PHY_ID_NCN26000 0x180FF5A1 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define NCN26000_REG_IRQ_CTL 16 2062306a36Sopenharmony_ci#define NCN26000_REG_IRQ_STATUS 17 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci// the NCN26000 maps link_ctrl to BMCR_ANENABLE 2362306a36Sopenharmony_ci#define NCN26000_BCMR_LINK_CTRL_BIT BMCR_ANENABLE 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci// the NCN26000 maps link_status to BMSR_ANEGCOMPLETE 2662306a36Sopenharmony_ci#define NCN26000_BMSR_LINK_STATUS_BIT BMSR_ANEGCOMPLETE 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define NCN26000_IRQ_LINKST_BIT BIT(0) 2962306a36Sopenharmony_ci#define NCN26000_IRQ_PLCAST_BIT BIT(1) 3062306a36Sopenharmony_ci#define NCN26000_IRQ_LJABBER_BIT BIT(2) 3162306a36Sopenharmony_ci#define NCN26000_IRQ_RJABBER_BIT BIT(3) 3262306a36Sopenharmony_ci#define NCN26000_IRQ_PLCAREC_BIT BIT(4) 3362306a36Sopenharmony_ci#define NCN26000_IRQ_PHYSCOL_BIT BIT(5) 3462306a36Sopenharmony_ci#define NCN26000_IRQ_RESET_BIT BIT(15) 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define TO_TMR_DEFAULT 32 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic int ncn26000_config_init(struct phy_device *phydev) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci /* HW bug workaround: the default value of the PLCA TO_TIMER should be 4162306a36Sopenharmony_ci * 32, where the current version of NCN26000 reports 24. This will be 4262306a36Sopenharmony_ci * fixed in future PHY versions. For the time being, we force the 4362306a36Sopenharmony_ci * correct default here. 4462306a36Sopenharmony_ci */ 4562306a36Sopenharmony_ci return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR, 4662306a36Sopenharmony_ci TO_TMR_DEFAULT); 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic int ncn26000_config_aneg(struct phy_device *phydev) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci /* Note: the NCN26000 supports only P2MP link mode. Therefore, AN is not 5262306a36Sopenharmony_ci * supported. However, this function is invoked by phylib to enable the 5362306a36Sopenharmony_ci * PHY, regardless of the AN support. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_ci phydev->mdix_ctrl = ETH_TP_MDI_AUTO; 5662306a36Sopenharmony_ci phydev->mdix = ETH_TP_MDI; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci // bring up the link 5962306a36Sopenharmony_ci return phy_write(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic int ncn26000_read_status(struct phy_device *phydev) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci /* The NCN26000 reports NCN26000_LINK_STATUS_BIT if the link status of 6562306a36Sopenharmony_ci * the PHY is up. It further reports the logical AND of the link status 6662306a36Sopenharmony_ci * and the PLCA status in the BMSR_LSTATUS bit. 6762306a36Sopenharmony_ci */ 6862306a36Sopenharmony_ci int ret; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* The link state is latched low so that momentary link 7162306a36Sopenharmony_ci * drops can be detected. Do not double-read the status 7262306a36Sopenharmony_ci * in polling mode to detect such short link drops except 7362306a36Sopenharmony_ci * the link was already down. 7462306a36Sopenharmony_ci */ 7562306a36Sopenharmony_ci if (!phy_polling_mode(phydev) || !phydev->link) { 7662306a36Sopenharmony_ci ret = phy_read(phydev, MII_BMSR); 7762306a36Sopenharmony_ci if (ret < 0) 7862306a36Sopenharmony_ci return ret; 7962306a36Sopenharmony_ci else if (ret & NCN26000_BMSR_LINK_STATUS_BIT) 8062306a36Sopenharmony_ci goto upd_link; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci ret = phy_read(phydev, MII_BMSR); 8462306a36Sopenharmony_ci if (ret < 0) 8562306a36Sopenharmony_ci return ret; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ciupd_link: 8862306a36Sopenharmony_ci // update link status 8962306a36Sopenharmony_ci if (ret & NCN26000_BMSR_LINK_STATUS_BIT) { 9062306a36Sopenharmony_ci phydev->link = 1; 9162306a36Sopenharmony_ci phydev->pause = 0; 9262306a36Sopenharmony_ci phydev->duplex = DUPLEX_HALF; 9362306a36Sopenharmony_ci phydev->speed = SPEED_10; 9462306a36Sopenharmony_ci } else { 9562306a36Sopenharmony_ci phydev->link = 0; 9662306a36Sopenharmony_ci phydev->duplex = DUPLEX_UNKNOWN; 9762306a36Sopenharmony_ci phydev->speed = SPEED_UNKNOWN; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci return 0; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci int ret; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci // read and aknowledge the IRQ status register 10862306a36Sopenharmony_ci ret = phy_read(phydev, NCN26000_REG_IRQ_STATUS); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci // check only link status changes 11162306a36Sopenharmony_ci if (ret < 0 || (ret & NCN26000_REG_IRQ_STATUS) == 0) 11262306a36Sopenharmony_ci return IRQ_NONE; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci phy_trigger_machine(phydev); 11562306a36Sopenharmony_ci return IRQ_HANDLED; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic int ncn26000_config_intr(struct phy_device *phydev) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci int ret; 12162306a36Sopenharmony_ci u16 irqe; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 12462306a36Sopenharmony_ci // acknowledge IRQs 12562306a36Sopenharmony_ci ret = phy_read(phydev, NCN26000_REG_IRQ_STATUS); 12662306a36Sopenharmony_ci if (ret < 0) 12762306a36Sopenharmony_ci return ret; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci // get link status notifications 13062306a36Sopenharmony_ci irqe = NCN26000_IRQ_LINKST_BIT; 13162306a36Sopenharmony_ci } else { 13262306a36Sopenharmony_ci // disable all IRQs 13362306a36Sopenharmony_ci irqe = 0; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci ret = phy_write(phydev, NCN26000_REG_IRQ_CTL, irqe); 13762306a36Sopenharmony_ci if (ret != 0) 13862306a36Sopenharmony_ci return ret; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic struct phy_driver ncn26000_driver[] = { 14462306a36Sopenharmony_ci { 14562306a36Sopenharmony_ci PHY_ID_MATCH_MODEL(PHY_ID_NCN26000), 14662306a36Sopenharmony_ci .name = "NCN26000", 14762306a36Sopenharmony_ci .features = PHY_BASIC_T1S_P2MP_FEATURES, 14862306a36Sopenharmony_ci .config_init = ncn26000_config_init, 14962306a36Sopenharmony_ci .config_intr = ncn26000_config_intr, 15062306a36Sopenharmony_ci .config_aneg = ncn26000_config_aneg, 15162306a36Sopenharmony_ci .read_status = ncn26000_read_status, 15262306a36Sopenharmony_ci .handle_interrupt = ncn26000_handle_interrupt, 15362306a36Sopenharmony_ci .get_plca_cfg = genphy_c45_plca_get_cfg, 15462306a36Sopenharmony_ci .set_plca_cfg = genphy_c45_plca_set_cfg, 15562306a36Sopenharmony_ci .get_plca_status = genphy_c45_plca_get_status, 15662306a36Sopenharmony_ci .soft_reset = genphy_soft_reset, 15762306a36Sopenharmony_ci }, 15862306a36Sopenharmony_ci}; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cimodule_phy_driver(ncn26000_driver); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused ncn26000_tbl[] = { 16362306a36Sopenharmony_ci { PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) }, 16462306a36Sopenharmony_ci { } 16562306a36Sopenharmony_ci}; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, ncn26000_tbl); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ciMODULE_AUTHOR("Piergiorgio Beruto"); 17062306a36Sopenharmony_ciMODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver"); 17162306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 172