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