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 38static 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 49static 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 62static 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 87upd_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 103static 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 118static 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 143static 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 160module_phy_driver(ncn26000_driver); 161 162static struct mdio_device_id __maybe_unused ncn26000_tbl[] = { 163 { PHY_ID_MATCH_MODEL(PHY_ID_NCN26000) }, 164 { } 165}; 166 167MODULE_DEVICE_TABLE(mdio, ncn26000_tbl); 168 169MODULE_AUTHOR("Piergiorgio Beruto"); 170MODULE_DESCRIPTION("onsemi 10BASE-T1S PHY driver"); 171MODULE_LICENSE("Dual BSD/GPL"); 172