1 // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) 2 /* 3 * Driver for the onsemi 10BASE-T1S NCN26000 PHYs family. 4 * 5 * Copyright 2022 onsemi 6 */ 7 #include <linux/kernel.h> 8 #include <linux/bitfield.h> 9 #include <linux/errno.h> 10 #include <linux/init.h> 11 #include <linux/module.h> 12 #include <linux/mii.h> 13 #include <linux/phy.h> 14 15 #include "mdio-open-alliance.h" 16 17 #define PHY_ID_NCN26000 0x180FF5A1 18 19 #define NCN26000_REG_IRQ_CTL 16 20 #define NCN26000_REG_IRQ_STATUS 17 21 22 // the NCN26000 maps link_ctrl to BMCR_ANENABLE 23 #define NCN26000_BCMR_LINK_CTRL_BIT BMCR_ANENABLE 24 25 // the NCN26000 maps link_status to BMSR_ANEGCOMPLETE 26 #define NCN26000_BMSR_LINK_STATUS_BIT BMSR_ANEGCOMPLETE 27 28 #define NCN26000_IRQ_LINKST_BIT BIT(0) 29 #define NCN26000_IRQ_PLCAST_BIT BIT(1) 30 #define NCN26000_IRQ_LJABBER_BIT BIT(2) 31 #define NCN26000_IRQ_RJABBER_BIT BIT(3) 32 #define NCN26000_IRQ_PLCAREC_BIT BIT(4) 33 #define NCN26000_IRQ_PHYSCOL_BIT BIT(5) 34 #define NCN26000_IRQ_RESET_BIT BIT(15) 35 36 #define TO_TMR_DEFAULT 32 37 38 static int ncn26000_config_init(struct phy_device *phydev) 39 { 40 /* HW bug workaround: the default value of the PLCA TO_TIMER should be 41 * 32, where the current version of NCN26000 reports 24. This will be 42 * fixed in future PHY versions. For the time being, we force the 43 * correct default here. 44 */ 45 return phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_OATC14_PLCA_TOTMR, 46 TO_TMR_DEFAULT); 47 } 48 49 static int ncn26000_config_aneg(struct phy_device *phydev) 50 { 51 /* Note: the NCN26000 supports only P2MP link mode. Therefore, AN is not 52 * supported. However, this function is invoked by phylib to enable the 53 * PHY, regardless of the AN support. 54 */ 55 phydev->mdix_ctrl = ETH_TP_MDI_AUTO; 56 phydev->mdix = ETH_TP_MDI; 57 58 // bring up the link 59 return phy_write(phydev, MII_BMCR, NCN26000_BCMR_LINK_CTRL_BIT); 60 } 61 62 static int ncn26000_read_status(struct phy_device *phydev) 63 { 64 /* The NCN26000 reports NCN26000_LINK_STATUS_BIT if the link status of 65 * the PHY is up. It further reports the logical AND of the link status 66 * and the PLCA status in the BMSR_LSTATUS bit. 67 */ 68 int ret; 69 70 /* The link state is latched low so that momentary link 71 * drops can be detected. Do not double-read the status 72 * in polling mode to detect such short link drops except 73 * the link was already down. 74 */ 75 if (!phy_polling_mode(phydev) || !phydev->link) { 76 ret = phy_read(phydev, MII_BMSR); 77 if (ret < 0) 78 return ret; 79 else if (ret & NCN26000_BMSR_LINK_STATUS_BIT) 80 goto upd_link; 81 } 82 83 ret = phy_read(phydev, MII_BMSR); 84 if (ret < 0) 85 return ret; 86 87 upd_link: 88 // update link status 89 if (ret & NCN26000_BMSR_LINK_STATUS_BIT) { 90 phydev->link = 1; 91 phydev->pause = 0; 92 phydev->duplex = DUPLEX_HALF; 93 phydev->speed = SPEED_10; 94 } else { 95 phydev->link = 0; 96 phydev->duplex = DUPLEX_UNKNOWN; 97 phydev->speed = SPEED_UNKNOWN; 98 } 99 100 return 0; 101 } 102 103 static irqreturn_t ncn26000_handle_interrupt(struct phy_device *phydev) 104 { 105 int ret; 106 107 // read and aknowledge the IRQ status register 108 ret = phy_read(phydev, NCN26000_REG_IRQ_STATUS); 109 110 // check only link status changes 111 if (ret < 0 || (ret & NCN26000_REG_IRQ_STATUS) == 0) 112 return IRQ_NONE; 113 114 phy_trigger_machine(phydev); 115 return IRQ_HANDLED; 116 } 117 118 static int ncn26000_config_intr(struct phy_device *phydev) 119 { 120 int ret; 121 u16 irqe; 122 123 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 124 // acknowledge IRQs 125 ret = phy_read(phydev, NCN26000_REG_IRQ_STATUS); 126 if (ret < 0) 127 return ret; 128 129 // get link status notifications 130 irqe = NCN26000_IRQ_LINKST_BIT; 131 } else { 132 // disable all IRQs 133 irqe = 0; 134 } 135 136 ret = phy_write(phydev, NCN26000_REG_IRQ_CTL, irqe); 137 if (ret != 0) 138 return ret; 139 140 return 0; 141 } 142 143 static struct phy_driver ncn26000_driver[] = { 144 { 145 PHY_ID_MATCH_MODEL(PHY_ID_NCN26000), 146 .name = "NCN26000", 147 .features = PHY_BASIC_T1S_P2MP_FEATURES, 148 .config_init = ncn26000_config_init, 149 .config_intr = ncn26000_config_intr, 150 .config_aneg = ncn26000_config_aneg, 151 .read_status = ncn26000_read_status, 152 .handle_interrupt = ncn26000_handle_interrupt, 153 .get_plca_cfg = genphy_c45_plca_get_cfg, 154 .set_plca_cfg = genphy_c45_plca_set_cfg, 155 .get_plca_status = genphy_c45_plca_get_status, 156 .soft_reset = genphy_soft_reset, 157 }, 158 }; 159 160 module_phy_driver(ncn26000_driver); 161 162 static struct mdio_device_id __maybe_unused ncn26000_tbl[] = { 163 { PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) }, 164 { } 165 }; 166 167 MODULE_DEVICE_TABLE(mdio, ncn26000_tbl); 168 169 MODULE_AUTHOR("Piergiorgio Beruto"); 170 MODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver"); 171 MODULE_LICENSE("Dual BSD/GPL"); 172