/* $OpenBSD: ytphy.c,v 1.6 2024/03/12 16:26:46 kettenis Exp $ */ /* * Copyright (c) 2001 Theo de Raadt * Copyright (c) 2023 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef __HAVE_FDT #include #include #endif #define MII_MODEL_MOTORCOMM_YT8511 0x10 #define MII_MODEL_MOTORCOMM_YT8521 0x11 #define YT8511_REG_ADDR 0x1e #define YT8511_REG_DATA 0x1f #define YT8511_EXT_CLK_GATE 0x0c #define YT8511_TX_CLK_DELAY_SEL_MASK (0xf << 4) #define YT8511_TX_CLK_DELAY_SEL_EN (0xf << 4) #define YT8511_TX_CLK_DELAY_SEL_DIS (0x2 << 4) #define YT8511_CLK_25M_SEL_MASK (0x3 << 1) #define YT8511_CLK_25M_SEL_125M (0x3 << 1) #define YT8511_RX_CLK_DELAY_EN (1 << 0) #define YT8511_EXT_DELAY_DRIVE 0x0d #define YT8511_TXC_DELAY_SEL_FE_MASK (0xf << 12) #define YT8511_TXC_DELAY_SEL_FE_EN (0xf << 12) #define YT8511_TXC_DELAY_SEL_FE_DIS (0x2 << 12) #define YT8511_EXT_SLEEP_CTRL 0x27 #define YT8511_PLL_ON_IN_SLEEP (1 << 14) #define YT8521_EXT_CHIP_CONFIG 0xa001 #define YT8521_RXC_DLY_EN (1 << 8) #define YT8521_CFG_LDO_MASK (0x3 << 4) #define YT8521_CFG_LDO_3V3 (0x0 << 4) #define YT8521_CFG_LDO_2V5 (0x1 << 4) #define YT8521_CFG_LDO_1V8 (0x2 << 4) /* or 0x3 */ #define YT8521_EXT_RGMII_CONFIG1 0xa003 #define YT8521_TX_CLK_SEL (1 << 14) #define YT8521_RX_DELAY_SEL_MASK (0xf << 10) #define YT8521_RX_DELAY_SEL_SHIFT 10 #define YT8521_TX_DELAY_SEL_MASK (0xf << 0) #define YT8521_TX_DELAY_SEL_SHIFT 0 #define YT8521_EXT_PAD_DRIVE_STRENGTH 0xa010 #define YT8531_RGMII_RXC_DS_MASK (0x7 << 13) #define YT8531_RGMII_RXC_DS_SHIFT 13 #define YT8531_RGMII_RXD_DS_MASK ((0x1 << 12) | (0x3 << 4)) #define YT8531_RGMII_RXD_DS_LOW(x) (((x) & 0x3) << 4) #define YT8531_RGMII_RXD_DS_HIGH(x) (((x) >> 2) << 12) #define YT8521_EXT_SYNCE_CFG 0xa012 #define YT8531_EN_SYNC_E (1 << 6) #define YT8531_CLK_FRE_SEL_125M (1 << 4) #define YT8531_CLK_SRC_SEL_MASK (0x7 << 1) #define YT8531_CLK_SRC_SEL_PLL_125M (0x0 << 1) #define YT8531_CLK_SRC_SEL_REF_25M (0x4 << 1) int ytphy_match(struct device *, void *, void *); void ytphy_attach(struct device *, struct device *, void *); const struct cfattach ytphy_ca = { sizeof(struct mii_softc), ytphy_match, ytphy_attach, mii_phy_detach }; struct cfdriver ytphy_cd = { NULL, "ytphy", DV_DULL }; int ytphy_service(struct mii_softc *, struct mii_data *, int); void ytphy_yt8511_init(struct mii_softc *); void ytphy_yt8521_init(struct mii_softc *); void ytphy_yt8521_update(struct mii_softc *); void ytphy_yt8531_init(struct mii_softc *); const struct mii_phy_funcs ytphy_funcs = { ytphy_service, ukphy_status, mii_phy_reset, }; static const struct mii_phydesc ytphys[] = { { MII_OUI_MOTORCOMM, MII_MODEL_MOTORCOMM_YT8531, MII_STR_MOTORCOMM_YT8531 }, { 0, 0, NULL }, }; int ytphy_match(struct device *parent, void *match, void *aux) { struct mii_attach_args *ma = aux; /* * The MotorComm YT8511 and TY8521 have bogus MII OUIs, so * match the complete ID including the rev. */ if (ma->mii_id1 == 0x0000 && ma->mii_id2 == 0x010a) return (10); if (ma->mii_id1 == 0x0000 && ma->mii_id2 == 0x011a) return (10); if (mii_phy_match(ma, ytphys) != NULL) return (10); return (0); } void ytphy_attach(struct device *parent, struct device *self, void *aux) { struct mii_softc *sc = (struct mii_softc *)self; struct mii_attach_args *ma = aux; struct mii_data *mii = ma->mii_data; const struct mii_phydesc *mpd; mpd = mii_phy_match(ma, ytphys); if (mpd) printf(": %s, rev. %d\n", mpd->mpd_name, MII_REV(ma->mii_id2)); else if (ma->mii_id1 == 0x0000 && ma->mii_id2 == 0x010a) printf(": YT8511 10/100/1000 PHY\n"); else printf(": YT8521 10/100/1000 PHY\n"); sc->mii_inst = mii->mii_instance; sc->mii_phy = ma->mii_phyno; sc->mii_funcs = &ytphy_funcs; sc->mii_oui = MII_OUI(ma->mii_id1, ma->mii_id2); sc->mii_model = MII_MODEL(ma->mii_id2); sc->mii_pdata = mii; sc->mii_flags = ma->mii_flags; if (ma->mii_id1 == 0x0000 && ma->mii_id2 == 0x010a) ytphy_yt8511_init(sc); else if (ma->mii_id1 == 0x0000 && ma->mii_id2 == 0x011a) ytphy_yt8521_init(sc); else ytphy_yt8531_init(sc); sc->mii_capabilities = PHY_READ(sc, MII_BMSR) & ma->mii_capmask; if (sc->mii_capabilities & BMSR_EXTSTAT) sc->mii_extcapabilities = PHY_READ(sc, MII_EXTSR); if ((sc->mii_capabilities & BMSR_MEDIAMASK) || (sc->mii_extcapabilities & EXTSR_MEDIAMASK)) mii_phy_add_media(sc); PHY_RESET(sc); } int ytphy_service(struct mii_softc *sc, struct mii_data *mii, int cmd) { struct ifmedia_entry *ife = mii->mii_media.ifm_cur; int reg; if ((sc->mii_dev.dv_flags & DVF_ACTIVE) == 0) return (ENXIO); switch (cmd) { case MII_POLLSTAT: /* * If we're not polling our PHY instance, just return. */ if (IFM_INST(ife->ifm_media) != sc->mii_inst) return (0); break; case MII_MEDIACHG: /* * If the media indicates a different PHY instance, * isolate ourselves. */ if (IFM_INST(ife->ifm_media) != sc->mii_inst) { reg = PHY_READ(sc, MII_BMCR); PHY_WRITE(sc, MII_BMCR, reg | BMCR_ISO); return (0); } /* * If the interface is not up, don't do anything. */ if ((mii->mii_ifp->if_flags & IFF_UP) == 0) break; mii_phy_setmedia(sc); break; case MII_TICK: /* * If we're not currently selected, just return. */ if (IFM_INST(ife->ifm_media) != sc->mii_inst) return (0); if (mii_phy_tick(sc) == EJUSTRETURN) return (0); break; case MII_DOWN: mii_phy_down(sc); return (0); } /* Update the media status. */ mii_phy_status(sc); /* Callback if something changed. */ if (sc->mii_model != MII_MODEL_MOTORCOMM_YT8511) ytphy_yt8521_update(sc); mii_phy_update(sc, cmd); return (0); } void ytphy_yt8511_init(struct mii_softc *sc) { uint16_t tx_clk_delay_sel; uint16_t rx_clk_delay_en; uint16_t txc_delay_sel_fe; uint16_t addr, data; if (sc->mii_flags & MIIF_RXID) rx_clk_delay_en = YT8511_RX_CLK_DELAY_EN; else rx_clk_delay_en = 0; if (sc->mii_flags & MIIF_TXID) { tx_clk_delay_sel = YT8511_TX_CLK_DELAY_SEL_EN; txc_delay_sel_fe = YT8511_TXC_DELAY_SEL_FE_EN; } else { tx_clk_delay_sel = YT8511_TX_CLK_DELAY_SEL_DIS; txc_delay_sel_fe = YT8511_TXC_DELAY_SEL_FE_DIS; } /* Save address register. */ addr = PHY_READ(sc, YT8511_REG_ADDR); PHY_WRITE(sc, YT8511_REG_ADDR, YT8511_EXT_CLK_GATE); data = PHY_READ(sc, YT8511_REG_DATA); data &= ~YT8511_TX_CLK_DELAY_SEL_MASK; data &= ~YT8511_RX_CLK_DELAY_EN; data &= ~YT8511_CLK_25M_SEL_MASK; data |= tx_clk_delay_sel; data |= rx_clk_delay_en; data |= YT8511_CLK_25M_SEL_125M; PHY_WRITE(sc, YT8511_REG_DATA, data); PHY_WRITE(sc, YT8511_REG_ADDR, YT8511_EXT_DELAY_DRIVE); data = PHY_READ(sc, YT8511_REG_DATA); data &= ~YT8511_TXC_DELAY_SEL_FE_MASK; data |= txc_delay_sel_fe; PHY_WRITE(sc, YT8511_REG_DATA, data); PHY_WRITE(sc, YT8511_REG_ADDR, YT8511_EXT_SLEEP_CTRL); data = PHY_READ(sc, YT8511_REG_DATA); data |= YT8511_PLL_ON_IN_SLEEP; PHY_WRITE(sc, YT8511_REG_DATA, data); /* Restore address register. */ PHY_WRITE(sc, YT8511_REG_ADDR, addr); } void ytphy_yt8521_init(struct mii_softc *sc) { uint32_t rx_delay = 1950; uint32_t tx_delay = 1950; int rx_delay_en = 0; uint16_t addr, data; #ifdef __HAVE_FDT if (sc->mii_pdata->mii_node) { rx_delay = OF_getpropint(sc->mii_pdata->mii_node, "rx-internal-delay-ps", rx_delay); tx_delay = OF_getpropint(sc->mii_pdata->mii_node, "tx-internal-delay-ps", tx_delay); } #endif /* Save address register. */ addr = PHY_READ(sc, YT8511_REG_ADDR); if ((sc->mii_flags & MIIF_RXID) == 0) rx_delay = 0; if ((sc->mii_flags & MIIF_TXID) == 0) tx_delay = 0; if (rx_delay >= 1900 && ((rx_delay - 1900) % 150) == 0) { rx_delay -= 1900; rx_delay_en = 1; } PHY_WRITE(sc, YT8511_REG_ADDR, YT8521_EXT_CHIP_CONFIG); data = PHY_READ(sc, YT8511_REG_DATA); if (rx_delay_en) data |= YT8521_RXC_DLY_EN; else data &= ~YT8521_RXC_DLY_EN; PHY_WRITE(sc, YT8511_REG_DATA, data); PHY_WRITE(sc, YT8511_REG_ADDR, YT8521_EXT_RGMII_CONFIG1); data = PHY_READ(sc, YT8511_REG_DATA); data &= ~YT8521_RX_DELAY_SEL_MASK; data |= (((rx_delay + 75) / 150) << YT8521_RX_DELAY_SEL_SHIFT); data &= ~YT8521_TX_DELAY_SEL_MASK; data |= (((tx_delay + 75) / 150) << YT8521_TX_DELAY_SEL_SHIFT); PHY_WRITE(sc, YT8511_REG_DATA, data); /* Restore address register. */ PHY_WRITE(sc, YT8511_REG_ADDR, addr); } void ytphy_yt8521_update(struct mii_softc *sc) { #ifdef __HAVE_FDT struct mii_data *mii = sc->mii_pdata; int tx_clk_adj_en; int tx_clk_inv = 0; uint16_t addr, data; if (sc->mii_media_active == mii->mii_media_active) return; tx_clk_adj_en = OF_getpropbool(mii->mii_node, "motorcomm,tx-clk-adj-enabled"); if (!tx_clk_adj_en) return; switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: tx_clk_inv = OF_getpropbool(mii->mii_node, "motorcomm,tx-clk-1000-inverted"); break; case IFM_100_TX: tx_clk_inv = OF_getpropbool(mii->mii_node, "motorcomm,tx-clk-100-inverted"); break; case IFM_10_T: tx_clk_inv = OF_getpropbool(mii->mii_node, "motorcomm,tx-clk-10-inverted"); break; } /* Save address register. */ addr = PHY_READ(sc, YT8511_REG_ADDR); PHY_WRITE(sc, YT8511_REG_ADDR, YT8521_EXT_RGMII_CONFIG1); data = PHY_READ(sc, YT8511_REG_DATA); if (tx_clk_inv) data |= YT8521_TX_CLK_SEL; else data &= ~YT8521_TX_CLK_SEL; PHY_WRITE(sc, YT8511_REG_DATA, data); /* Restore address register. */ PHY_WRITE(sc, YT8511_REG_ADDR, addr); #endif } /* The RGMII drive strength depends on the voltage level. */ struct ytphy_ds_map { uint32_t volt; /* mV */ uint16_t amp; /* uA */ uint16_t ds; }; struct ytphy_ds_map ytphy_yt8531_ds_map[] = { { 1800, 1200, 0 }, { 1800, 2100, 1 }, { 1800, 2700, 2 }, { 1800, 2910, 3 }, { 1800, 3110, 4 }, { 1800, 3600, 5 }, { 1800, 3970, 6 }, { 1800, 4350, 7 }, { 3300, 3070, 0 }, { 3300, 4080, 1 }, { 3300, 4370, 2 }, { 3300, 4680, 3 }, { 3300, 5020, 4 }, { 3300, 5450, 5 }, { 3300, 5740, 6 }, { 3300, 6140, 7 }, }; uint32_t ytphy_yt8531_ds(struct mii_softc *sc, uint32_t volt, uint32_t amp) { int i; for (i = 0; i < nitems(ytphy_yt8531_ds_map); i++) { if (ytphy_yt8531_ds_map[i].volt == volt && ytphy_yt8531_ds_map[i].amp == amp) return ytphy_yt8531_ds_map[i].ds; } if (amp) { printf("%s: unknown drive strength (%d uA at %d mV)\n", sc->mii_dev.dv_xname, amp, volt); } /* Default drive strength. */ return 3; } void ytphy_yt8531_init(struct mii_softc *sc) { uint32_t rx_clk_drv = 0; uint32_t rx_data_drv = 0; uint32_t clk_out_freq = 0; uint16_t addr, data; uint16_t volt; ytphy_yt8521_init(sc); #ifdef __HAVE_FDT if (sc->mii_pdata->mii_node) { rx_clk_drv = OF_getpropint(sc->mii_pdata->mii_node, "motorcomm,rx-clk-drv-microamp", 0); rx_data_drv = OF_getpropint(sc->mii_pdata->mii_node, "motorcomm,rx-data-drv-microamp", 0); clk_out_freq = OF_getpropint(sc->mii_pdata->mii_node, "motorcomm,clk-out-frequency-hz", 0); } #endif /* Save address register. */ addr = PHY_READ(sc, YT8511_REG_ADDR); PHY_WRITE(sc, YT8511_REG_ADDR, YT8521_EXT_CHIP_CONFIG); data = PHY_READ(sc, YT8511_REG_DATA); if ((data & YT8521_CFG_LDO_MASK) == YT8521_CFG_LDO_3V3) volt = 3300; else if ((data & YT8521_CFG_LDO_MASK) == YT8521_CFG_LDO_2V5) volt = 2500; else volt = 1800; rx_clk_drv = ytphy_yt8531_ds(sc, volt, rx_clk_drv); rx_data_drv = ytphy_yt8531_ds(sc, volt, rx_data_drv); PHY_WRITE(sc, YT8511_REG_ADDR, YT8521_EXT_PAD_DRIVE_STRENGTH); data = PHY_READ(sc, YT8511_REG_DATA); data &= ~YT8531_RGMII_RXC_DS_MASK; data |= rx_clk_drv << YT8531_RGMII_RXC_DS_SHIFT; data &= ~YT8531_RGMII_RXD_DS_MASK; data |= YT8531_RGMII_RXD_DS_LOW(rx_data_drv); data |= YT8531_RGMII_RXD_DS_HIGH(rx_data_drv); PHY_WRITE(sc, YT8511_REG_DATA, data); PHY_WRITE(sc, YT8511_REG_ADDR, YT8521_EXT_SYNCE_CFG); data = PHY_READ(sc, YT8511_REG_DATA); switch (clk_out_freq) { case 125000000: data |= YT8531_EN_SYNC_E; data |= YT8531_CLK_FRE_SEL_125M; data &= ~YT8531_CLK_SRC_SEL_MASK; data |= YT8531_CLK_SRC_SEL_PLL_125M; break; case 25000000: data |= YT8531_EN_SYNC_E; data &= ~YT8531_CLK_FRE_SEL_125M; data &= ~YT8531_CLK_SRC_SEL_MASK; data |= YT8531_CLK_SRC_SEL_REF_25M; break; default: data &= ~YT8531_EN_SYNC_E; break; } PHY_WRITE(sc, YT8511_REG_DATA, data); /* Restore address register. */ PHY_WRITE(sc, YT8511_REG_ADDR, addr); }