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