Edit

IABSD.fr/src/sys/dev/mii/ytphy.c

Branch :

  • Show log

    Commit

  • Author : kettenis
    Date : 2024-03-12 16:26:46
    Hash : 9d7a05f9
    Message : Configure the signal on the CLKOUT pin of the YT8531 PHY when we're instructed to do so by the device tree. ok patrick@

  • sys/dev/mii/ytphy.c
  • /*	$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 <kettenis@openbsd.org>
     *
     * 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 <sys/param.h>
    #include <sys/systm.h>
    #include <sys/device.h>
    #include <sys/socket.h>
    #include <sys/errno.h>
    
    #include <net/if.h>
    #include <net/if_var.h>
    #include <net/if_media.h>
    
    #include <dev/mii/mii.h>
    #include <dev/mii/miivar.h>
    #include <dev/mii/miidevs.h>
    
    #ifdef __HAVE_FDT
    #include <machine/fdt.h>
    #include <dev/ofw/openfirm.h>
    #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);
    }