| // SPDX-License-Identifier: GPL-2.0 |
| /* Driver for the Texas Instruments DP83822, DP83825 and DP83826 PHYs. |
| * |
| * Copyright (C) 2017 Texas Instruments Inc. |
| */ |
| |
| #include <linux/ethtool.h> |
| #include <linux/etherdevice.h> |
| #include <linux/kernel.h> |
| #include <linux/mii.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/phy.h> |
| #include <linux/netdevice.h> |
| #include <linux/bitfield.h> |
| |
| #define DP83822_PHY_ID 0x2000a240 |
| #define DP83825S_PHY_ID 0x2000a140 |
| #define DP83825I_PHY_ID 0x2000a150 |
| #define DP83825CM_PHY_ID 0x2000a160 |
| #define DP83825CS_PHY_ID 0x2000a170 |
| #define DP83826C_PHY_ID 0x2000a130 |
| #define DP83826NC_PHY_ID 0x2000a110 |
| |
| #define DP83822_DEVADDR 0x1f |
| |
| #define MII_DP83822_CTRL_2 0x0a |
| #define MII_DP83822_PHYSTS 0x10 |
| #define MII_DP83822_PHYSCR 0x11 |
| #define MII_DP83822_MISR1 0x12 |
| #define MII_DP83822_MISR2 0x13 |
| #define MII_DP83822_FCSCR 0x14 |
| #define MII_DP83822_RCSR 0x17 |
| #define MII_DP83822_RESET_CTRL 0x1f |
| #define MII_DP83822_GENCFG 0x465 |
| #define MII_DP83822_SOR1 0x467 |
| |
| /* DP83826 specific registers */ |
| #define MII_DP83826_VOD_CFG1 0x30b |
| #define MII_DP83826_VOD_CFG2 0x30c |
| |
| /* GENCFG */ |
| #define DP83822_SIG_DET_LOW BIT(0) |
| |
| /* Control Register 2 bits */ |
| #define DP83822_FX_ENABLE BIT(14) |
| |
| #define DP83822_HW_RESET BIT(15) |
| #define DP83822_SW_RESET BIT(14) |
| |
| /* PHY STS bits */ |
| #define DP83822_PHYSTS_DUPLEX BIT(2) |
| #define DP83822_PHYSTS_10 BIT(1) |
| #define DP83822_PHYSTS_LINK BIT(0) |
| |
| /* PHYSCR Register Fields */ |
| #define DP83822_PHYSCR_INT_OE BIT(0) /* Interrupt Output Enable */ |
| #define DP83822_PHYSCR_INTEN BIT(1) /* Interrupt Enable */ |
| |
| /* MISR1 bits */ |
| #define DP83822_RX_ERR_HF_INT_EN BIT(0) |
| #define DP83822_FALSE_CARRIER_HF_INT_EN BIT(1) |
| #define DP83822_ANEG_COMPLETE_INT_EN BIT(2) |
| #define DP83822_DUP_MODE_CHANGE_INT_EN BIT(3) |
| #define DP83822_SPEED_CHANGED_INT_EN BIT(4) |
| #define DP83822_LINK_STAT_INT_EN BIT(5) |
| #define DP83822_ENERGY_DET_INT_EN BIT(6) |
| #define DP83822_LINK_QUAL_INT_EN BIT(7) |
| |
| /* MISR2 bits */ |
| #define DP83822_JABBER_DET_INT_EN BIT(0) |
| #define DP83822_WOL_PKT_INT_EN BIT(1) |
| #define DP83822_SLEEP_MODE_INT_EN BIT(2) |
| #define DP83822_MDI_XOVER_INT_EN BIT(3) |
| #define DP83822_LB_FIFO_INT_EN BIT(4) |
| #define DP83822_PAGE_RX_INT_EN BIT(5) |
| #define DP83822_ANEG_ERR_INT_EN BIT(6) |
| #define DP83822_EEE_ERROR_CHANGE_INT_EN BIT(7) |
| |
| /* INT_STAT1 bits */ |
| #define DP83822_WOL_INT_EN BIT(4) |
| #define DP83822_WOL_INT_STAT BIT(12) |
| |
| #define MII_DP83822_RXSOP1 0x04a5 |
| #define MII_DP83822_RXSOP2 0x04a6 |
| #define MII_DP83822_RXSOP3 0x04a7 |
| |
| /* WoL Registers */ |
| #define MII_DP83822_WOL_CFG 0x04a0 |
| #define MII_DP83822_WOL_STAT 0x04a1 |
| #define MII_DP83822_WOL_DA1 0x04a2 |
| #define MII_DP83822_WOL_DA2 0x04a3 |
| #define MII_DP83822_WOL_DA3 0x04a4 |
| |
| /* WoL bits */ |
| #define DP83822_WOL_MAGIC_EN BIT(0) |
| #define DP83822_WOL_SECURE_ON BIT(5) |
| #define DP83822_WOL_EN BIT(7) |
| #define DP83822_WOL_INDICATION_SEL BIT(8) |
| #define DP83822_WOL_CLR_INDICATION BIT(11) |
| |
| /* RCSR bits */ |
| #define DP83822_RMII_MODE_EN BIT(5) |
| #define DP83822_RMII_MODE_SEL BIT(7) |
| #define DP83822_RGMII_MODE_EN BIT(9) |
| #define DP83822_RX_CLK_SHIFT BIT(12) |
| #define DP83822_TX_CLK_SHIFT BIT(11) |
| |
| /* SOR1 mode */ |
| #define DP83822_STRAP_MODE1 0 |
| #define DP83822_STRAP_MODE2 BIT(0) |
| #define DP83822_STRAP_MODE3 BIT(1) |
| #define DP83822_STRAP_MODE4 GENMASK(1, 0) |
| |
| #define DP83822_COL_STRAP_MASK GENMASK(11, 10) |
| #define DP83822_COL_SHIFT 10 |
| #define DP83822_RX_ER_STR_MASK GENMASK(9, 8) |
| #define DP83822_RX_ER_SHIFT 8 |
| |
| /* DP83826: VOD_CFG1 & VOD_CFG2 */ |
| #define DP83826_VOD_CFG1_MINUS_MDIX_MASK GENMASK(13, 12) |
| #define DP83826_VOD_CFG1_MINUS_MDI_MASK GENMASK(11, 6) |
| #define DP83826_VOD_CFG2_MINUS_MDIX_MASK GENMASK(15, 12) |
| #define DP83826_VOD_CFG2_PLUS_MDIX_MASK GENMASK(11, 6) |
| #define DP83826_VOD_CFG2_PLUS_MDI_MASK GENMASK(5, 0) |
| #define DP83826_CFG_DAC_MINUS_MDIX_5_TO_4 GENMASK(5, 4) |
| #define DP83826_CFG_DAC_MINUS_MDIX_3_TO_0 GENMASK(3, 0) |
| #define DP83826_CFG_DAC_PERCENT_PER_STEP 625 |
| #define DP83826_CFG_DAC_PERCENT_DEFAULT 10000 |
| #define DP83826_CFG_DAC_MINUS_DEFAULT 0x30 |
| #define DP83826_CFG_DAC_PLUS_DEFAULT 0x10 |
| |
| #define MII_DP83822_FIBER_ADVERTISE (ADVERTISED_TP | ADVERTISED_MII | \ |
| ADVERTISED_FIBRE | \ |
| ADVERTISED_Pause | ADVERTISED_Asym_Pause) |
| |
| struct dp83822_private { |
| bool fx_signal_det_low; |
| int fx_enabled; |
| u16 fx_sd_enable; |
| u8 cfg_dac_minus; |
| u8 cfg_dac_plus; |
| struct ethtool_wolinfo wol; |
| }; |
| |
| static int dp83822_config_wol(struct phy_device *phydev, |
| struct ethtool_wolinfo *wol) |
| { |
| struct net_device *ndev = phydev->attached_dev; |
| u16 value; |
| const u8 *mac; |
| |
| if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) { |
| mac = (const u8 *)ndev->dev_addr; |
| |
| if (!is_valid_ether_addr(mac)) |
| return -EINVAL; |
| |
| /* MAC addresses start with byte 5, but stored in mac[0]. |
| * 822 PHYs store bytes 4|5, 2|3, 0|1 |
| */ |
| phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA1, |
| (mac[1] << 8) | mac[0]); |
| phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA2, |
| (mac[3] << 8) | mac[2]); |
| phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA3, |
| (mac[5] << 8) | mac[4]); |
| |
| value = phy_read_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_WOL_CFG); |
| if (wol->wolopts & WAKE_MAGIC) |
| value |= DP83822_WOL_MAGIC_EN; |
| else |
| value &= ~DP83822_WOL_MAGIC_EN; |
| |
| if (wol->wolopts & WAKE_MAGICSECURE) { |
| phy_write_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RXSOP1, |
| (wol->sopass[1] << 8) | wol->sopass[0]); |
| phy_write_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RXSOP2, |
| (wol->sopass[3] << 8) | wol->sopass[2]); |
| phy_write_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RXSOP3, |
| (wol->sopass[5] << 8) | wol->sopass[4]); |
| value |= DP83822_WOL_SECURE_ON; |
| } else { |
| value &= ~DP83822_WOL_SECURE_ON; |
| } |
| |
| /* Clear any pending WoL interrupt */ |
| phy_read(phydev, MII_DP83822_MISR2); |
| |
| value |= DP83822_WOL_EN | DP83822_WOL_INDICATION_SEL | |
| DP83822_WOL_CLR_INDICATION; |
| |
| return phy_write_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_WOL_CFG, value); |
| } else { |
| return phy_clear_bits_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_WOL_CFG, |
| DP83822_WOL_EN | |
| DP83822_WOL_MAGIC_EN | |
| DP83822_WOL_SECURE_ON); |
| } |
| } |
| |
| static int dp83822_set_wol(struct phy_device *phydev, |
| struct ethtool_wolinfo *wol) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| int ret; |
| |
| ret = dp83822_config_wol(phydev, wol); |
| if (!ret) |
| memcpy(&dp83822->wol, wol, sizeof(*wol)); |
| return ret; |
| } |
| |
| static void dp83822_get_wol(struct phy_device *phydev, |
| struct ethtool_wolinfo *wol) |
| { |
| int value; |
| u16 sopass_val; |
| |
| wol->supported = (WAKE_MAGIC | WAKE_MAGICSECURE); |
| wol->wolopts = 0; |
| |
| value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG); |
| |
| if (value & DP83822_WOL_MAGIC_EN) |
| wol->wolopts |= WAKE_MAGIC; |
| |
| if (value & DP83822_WOL_SECURE_ON) { |
| sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RXSOP1); |
| wol->sopass[0] = (sopass_val & 0xff); |
| wol->sopass[1] = (sopass_val >> 8); |
| |
| sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RXSOP2); |
| wol->sopass[2] = (sopass_val & 0xff); |
| wol->sopass[3] = (sopass_val >> 8); |
| |
| sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RXSOP3); |
| wol->sopass[4] = (sopass_val & 0xff); |
| wol->sopass[5] = (sopass_val >> 8); |
| |
| wol->wolopts |= WAKE_MAGICSECURE; |
| } |
| |
| /* WoL is not enabled so set wolopts to 0 */ |
| if (!(value & DP83822_WOL_EN)) |
| wol->wolopts = 0; |
| } |
| |
| static int dp83822_config_intr(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| int misr_status; |
| int physcr_status; |
| int err; |
| |
| if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { |
| misr_status = phy_read(phydev, MII_DP83822_MISR1); |
| if (misr_status < 0) |
| return misr_status; |
| |
| misr_status |= (DP83822_LINK_STAT_INT_EN | |
| DP83822_ENERGY_DET_INT_EN | |
| DP83822_LINK_QUAL_INT_EN); |
| |
| if (!dp83822->fx_enabled) |
| misr_status |= DP83822_ANEG_COMPLETE_INT_EN | |
| DP83822_DUP_MODE_CHANGE_INT_EN | |
| DP83822_SPEED_CHANGED_INT_EN; |
| |
| |
| err = phy_write(phydev, MII_DP83822_MISR1, misr_status); |
| if (err < 0) |
| return err; |
| |
| misr_status = phy_read(phydev, MII_DP83822_MISR2); |
| if (misr_status < 0) |
| return misr_status; |
| |
| misr_status |= (DP83822_JABBER_DET_INT_EN | |
| DP83822_SLEEP_MODE_INT_EN | |
| DP83822_LB_FIFO_INT_EN | |
| DP83822_PAGE_RX_INT_EN | |
| DP83822_EEE_ERROR_CHANGE_INT_EN); |
| |
| if (!dp83822->fx_enabled) |
| misr_status |= DP83822_ANEG_ERR_INT_EN | |
| DP83822_WOL_PKT_INT_EN; |
| |
| err = phy_write(phydev, MII_DP83822_MISR2, misr_status); |
| if (err < 0) |
| return err; |
| |
| physcr_status = phy_read(phydev, MII_DP83822_PHYSCR); |
| if (physcr_status < 0) |
| return physcr_status; |
| |
| physcr_status |= DP83822_PHYSCR_INT_OE | DP83822_PHYSCR_INTEN; |
| |
| } else { |
| err = phy_write(phydev, MII_DP83822_MISR1, 0); |
| if (err < 0) |
| return err; |
| |
| err = phy_write(phydev, MII_DP83822_MISR2, 0); |
| if (err < 0) |
| return err; |
| |
| physcr_status = phy_read(phydev, MII_DP83822_PHYSCR); |
| if (physcr_status < 0) |
| return physcr_status; |
| |
| physcr_status &= ~DP83822_PHYSCR_INTEN; |
| } |
| |
| return phy_write(phydev, MII_DP83822_PHYSCR, physcr_status); |
| } |
| |
| static irqreturn_t dp83822_handle_interrupt(struct phy_device *phydev) |
| { |
| bool trigger_machine = false; |
| int irq_status; |
| |
| /* The MISR1 and MISR2 registers are holding the interrupt status in |
| * the upper half (15:8), while the lower half (7:0) is used for |
| * controlling the interrupt enable state of those individual interrupt |
| * sources. To determine the possible interrupt sources, just read the |
| * MISR* register and use it directly to know which interrupts have |
| * been enabled previously or not. |
| */ |
| irq_status = phy_read(phydev, MII_DP83822_MISR1); |
| if (irq_status < 0) { |
| phy_error(phydev); |
| return IRQ_NONE; |
| } |
| if (irq_status & ((irq_status & GENMASK(7, 0)) << 8)) |
| trigger_machine = true; |
| |
| irq_status = phy_read(phydev, MII_DP83822_MISR2); |
| if (irq_status < 0) { |
| phy_error(phydev); |
| return IRQ_NONE; |
| } |
| if (irq_status & ((irq_status & GENMASK(7, 0)) << 8)) |
| trigger_machine = true; |
| |
| if (!trigger_machine) |
| return IRQ_NONE; |
| |
| phy_trigger_machine(phydev); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int dp83822_read_status(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| int status = phy_read(phydev, MII_DP83822_PHYSTS); |
| int ctrl2; |
| int ret; |
| |
| if (dp83822->fx_enabled) { |
| if (status & DP83822_PHYSTS_LINK) { |
| phydev->speed = SPEED_UNKNOWN; |
| phydev->duplex = DUPLEX_UNKNOWN; |
| } else { |
| ctrl2 = phy_read(phydev, MII_DP83822_CTRL_2); |
| if (ctrl2 < 0) |
| return ctrl2; |
| |
| if (!(ctrl2 & DP83822_FX_ENABLE)) { |
| ret = phy_write(phydev, MII_DP83822_CTRL_2, |
| DP83822_FX_ENABLE | ctrl2); |
| if (ret < 0) |
| return ret; |
| } |
| } |
| } |
| |
| ret = genphy_read_status(phydev); |
| if (ret) |
| return ret; |
| |
| if (status < 0) |
| return status; |
| |
| if (status & DP83822_PHYSTS_DUPLEX) |
| phydev->duplex = DUPLEX_FULL; |
| else |
| phydev->duplex = DUPLEX_HALF; |
| |
| if (status & DP83822_PHYSTS_10) |
| phydev->speed = SPEED_10; |
| else |
| phydev->speed = SPEED_100; |
| |
| return 0; |
| } |
| |
| static int dp83822_config_init(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| struct device *dev = &phydev->mdio.dev; |
| int rgmii_delay = 0; |
| s32 rx_int_delay; |
| s32 tx_int_delay; |
| int err = 0; |
| int bmcr; |
| |
| if (phy_interface_is_rgmii(phydev)) { |
| rx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0, |
| true); |
| |
| /* Set DP83822_RX_CLK_SHIFT to enable rx clk internal delay */ |
| if (rx_int_delay > 0) |
| rgmii_delay |= DP83822_RX_CLK_SHIFT; |
| |
| tx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0, |
| false); |
| |
| /* Set DP83822_TX_CLK_SHIFT to disable tx clk internal delay */ |
| if (tx_int_delay <= 0) |
| rgmii_delay |= DP83822_TX_CLK_SHIFT; |
| |
| err = phy_modify_mmd(phydev, DP83822_DEVADDR, MII_DP83822_RCSR, |
| DP83822_RX_CLK_SHIFT | DP83822_TX_CLK_SHIFT, rgmii_delay); |
| if (err) |
| return err; |
| |
| err = phy_set_bits_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RCSR, DP83822_RGMII_MODE_EN); |
| |
| if (err) |
| return err; |
| } else { |
| err = phy_clear_bits_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_RCSR, DP83822_RGMII_MODE_EN); |
| |
| if (err) |
| return err; |
| } |
| |
| if (dp83822->fx_enabled) { |
| err = phy_modify(phydev, MII_DP83822_CTRL_2, |
| DP83822_FX_ENABLE, 1); |
| if (err < 0) |
| return err; |
| |
| /* Only allow advertising what this PHY supports */ |
| linkmode_and(phydev->advertising, phydev->advertising, |
| phydev->supported); |
| |
| linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, |
| phydev->supported); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, |
| phydev->advertising); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, |
| phydev->supported); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Half_BIT, |
| phydev->supported); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Full_BIT, |
| phydev->advertising); |
| linkmode_set_bit(ETHTOOL_LINK_MODE_100baseFX_Half_BIT, |
| phydev->advertising); |
| |
| /* Auto neg is not supported in fiber mode */ |
| bmcr = phy_read(phydev, MII_BMCR); |
| if (bmcr < 0) |
| return bmcr; |
| |
| if (bmcr & BMCR_ANENABLE) { |
| err = phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0); |
| if (err < 0) |
| return err; |
| } |
| phydev->autoneg = AUTONEG_DISABLE; |
| linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, |
| phydev->supported); |
| linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, |
| phydev->advertising); |
| |
| /* Setup fiber advertisement */ |
| err = phy_modify_changed(phydev, MII_ADVERTISE, |
| MII_DP83822_FIBER_ADVERTISE, |
| MII_DP83822_FIBER_ADVERTISE); |
| |
| if (err < 0) |
| return err; |
| |
| if (dp83822->fx_signal_det_low) { |
| err = phy_set_bits_mmd(phydev, DP83822_DEVADDR, |
| MII_DP83822_GENCFG, |
| DP83822_SIG_DET_LOW); |
| if (err) |
| return err; |
| } |
| } |
| return dp83822_config_wol(phydev, &dp83822->wol); |
| } |
| |
| static int dp83826_config_rmii_mode(struct phy_device *phydev) |
| { |
| struct device *dev = &phydev->mdio.dev; |
| const char *of_val; |
| int ret; |
| |
| if (!device_property_read_string(dev, "ti,rmii-mode", &of_val)) { |
| if (strcmp(of_val, "master") == 0) { |
| ret = phy_clear_bits_mmd(phydev, DP83822_DEVADDR, MII_DP83822_RCSR, |
| DP83822_RMII_MODE_SEL); |
| } else if (strcmp(of_val, "slave") == 0) { |
| ret = phy_set_bits_mmd(phydev, DP83822_DEVADDR, MII_DP83822_RCSR, |
| DP83822_RMII_MODE_SEL); |
| } else { |
| phydev_err(phydev, "Invalid value for ti,rmii-mode property (%s)\n", |
| of_val); |
| ret = -EINVAL; |
| } |
| |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int dp83826_config_init(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| u16 val, mask; |
| int ret; |
| |
| if (phydev->interface == PHY_INTERFACE_MODE_RMII) { |
| ret = phy_set_bits_mmd(phydev, DP83822_DEVADDR, MII_DP83822_RCSR, |
| DP83822_RMII_MODE_EN); |
| if (ret) |
| return ret; |
| |
| ret = dp83826_config_rmii_mode(phydev); |
| if (ret) |
| return ret; |
| } else { |
| ret = phy_clear_bits_mmd(phydev, DP83822_DEVADDR, MII_DP83822_RCSR, |
| DP83822_RMII_MODE_EN); |
| if (ret) |
| return ret; |
| } |
| |
| if (dp83822->cfg_dac_minus != DP83826_CFG_DAC_MINUS_DEFAULT) { |
| val = FIELD_PREP(DP83826_VOD_CFG1_MINUS_MDI_MASK, dp83822->cfg_dac_minus) | |
| FIELD_PREP(DP83826_VOD_CFG1_MINUS_MDIX_MASK, |
| FIELD_GET(DP83826_CFG_DAC_MINUS_MDIX_5_TO_4, |
| dp83822->cfg_dac_minus)); |
| mask = DP83826_VOD_CFG1_MINUS_MDIX_MASK | DP83826_VOD_CFG1_MINUS_MDI_MASK; |
| ret = phy_modify_mmd(phydev, DP83822_DEVADDR, MII_DP83826_VOD_CFG1, mask, val); |
| if (ret) |
| return ret; |
| |
| val = FIELD_PREP(DP83826_VOD_CFG2_MINUS_MDIX_MASK, |
| FIELD_GET(DP83826_CFG_DAC_MINUS_MDIX_3_TO_0, |
| dp83822->cfg_dac_minus)); |
| mask = DP83826_VOD_CFG2_MINUS_MDIX_MASK; |
| ret = phy_modify_mmd(phydev, DP83822_DEVADDR, MII_DP83826_VOD_CFG2, mask, val); |
| if (ret) |
| return ret; |
| } |
| |
| if (dp83822->cfg_dac_plus != DP83826_CFG_DAC_PLUS_DEFAULT) { |
| val = FIELD_PREP(DP83826_VOD_CFG2_PLUS_MDIX_MASK, dp83822->cfg_dac_plus) | |
| FIELD_PREP(DP83826_VOD_CFG2_PLUS_MDI_MASK, dp83822->cfg_dac_plus); |
| mask = DP83826_VOD_CFG2_PLUS_MDIX_MASK | DP83826_VOD_CFG2_PLUS_MDI_MASK; |
| ret = phy_modify_mmd(phydev, DP83822_DEVADDR, MII_DP83826_VOD_CFG2, mask, val); |
| if (ret) |
| return ret; |
| } |
| |
| return dp83822_config_wol(phydev, &dp83822->wol); |
| } |
| |
| static int dp8382x_config_init(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| |
| return dp83822_config_wol(phydev, &dp83822->wol); |
| } |
| |
| static int dp83822_phy_reset(struct phy_device *phydev) |
| { |
| int err; |
| |
| err = phy_write(phydev, MII_DP83822_RESET_CTRL, DP83822_SW_RESET); |
| if (err < 0) |
| return err; |
| |
| return phydev->drv->config_init(phydev); |
| } |
| |
| #ifdef CONFIG_OF_MDIO |
| static int dp83822_of_init(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| struct device *dev = &phydev->mdio.dev; |
| |
| /* Signal detection for the PHY is only enabled if the FX_EN and the |
| * SD_EN pins are strapped. Signal detection can only enabled if FX_EN |
| * is strapped otherwise signal detection is disabled for the PHY. |
| */ |
| if (dp83822->fx_enabled && dp83822->fx_sd_enable) |
| dp83822->fx_signal_det_low = device_property_present(dev, |
| "ti,link-loss-low"); |
| if (!dp83822->fx_enabled) |
| dp83822->fx_enabled = device_property_present(dev, |
| "ti,fiber-mode"); |
| |
| return 0; |
| } |
| |
| static int dp83826_to_dac_minus_one_regval(int percent) |
| { |
| int tmp = DP83826_CFG_DAC_PERCENT_DEFAULT - percent; |
| |
| return tmp / DP83826_CFG_DAC_PERCENT_PER_STEP; |
| } |
| |
| static int dp83826_to_dac_plus_one_regval(int percent) |
| { |
| int tmp = percent - DP83826_CFG_DAC_PERCENT_DEFAULT; |
| |
| return tmp / DP83826_CFG_DAC_PERCENT_PER_STEP; |
| } |
| |
| static void dp83826_of_init(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| struct device *dev = &phydev->mdio.dev; |
| u32 val; |
| |
| dp83822->cfg_dac_minus = DP83826_CFG_DAC_MINUS_DEFAULT; |
| if (!device_property_read_u32(dev, "ti,cfg-dac-minus-one-bp", &val)) |
| dp83822->cfg_dac_minus += dp83826_to_dac_minus_one_regval(val); |
| |
| dp83822->cfg_dac_plus = DP83826_CFG_DAC_PLUS_DEFAULT; |
| if (!device_property_read_u32(dev, "ti,cfg-dac-plus-one-bp", &val)) |
| dp83822->cfg_dac_plus += dp83826_to_dac_plus_one_regval(val); |
| } |
| #else |
| static int dp83822_of_init(struct phy_device *phydev) |
| { |
| return 0; |
| } |
| |
| static void dp83826_of_init(struct phy_device *phydev) |
| { |
| } |
| #endif /* CONFIG_OF_MDIO */ |
| |
| static int dp83822_read_straps(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822 = phydev->priv; |
| int fx_enabled, fx_sd_enable; |
| int val; |
| |
| val = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_SOR1); |
| if (val < 0) |
| return val; |
| |
| phydev_dbg(phydev, "SOR1 strap register: 0x%04x\n", val); |
| |
| fx_enabled = (val & DP83822_COL_STRAP_MASK) >> DP83822_COL_SHIFT; |
| if (fx_enabled == DP83822_STRAP_MODE2 || |
| fx_enabled == DP83822_STRAP_MODE3) |
| dp83822->fx_enabled = 1; |
| |
| if (dp83822->fx_enabled) { |
| fx_sd_enable = (val & DP83822_RX_ER_STR_MASK) >> DP83822_RX_ER_SHIFT; |
| if (fx_sd_enable == DP83822_STRAP_MODE3 || |
| fx_sd_enable == DP83822_STRAP_MODE4) |
| dp83822->fx_sd_enable = 1; |
| } |
| |
| return 0; |
| } |
| |
| static int dp8382x_probe(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822; |
| |
| dp83822 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83822), |
| GFP_KERNEL); |
| if (!dp83822) |
| return -ENOMEM; |
| |
| phydev->priv = dp83822; |
| |
| return 0; |
| } |
| |
| static int dp83822_probe(struct phy_device *phydev) |
| { |
| struct dp83822_private *dp83822; |
| int ret; |
| |
| ret = dp8382x_probe(phydev); |
| if (ret) |
| return ret; |
| |
| dp83822 = phydev->priv; |
| |
| ret = dp83822_read_straps(phydev); |
| if (ret) |
| return ret; |
| |
| dp83822_of_init(phydev); |
| |
| if (dp83822->fx_enabled) |
| phydev->port = PORT_FIBRE; |
| |
| return 0; |
| } |
| |
| static int dp83826_probe(struct phy_device *phydev) |
| { |
| int ret; |
| |
| ret = dp8382x_probe(phydev); |
| if (ret) |
| return ret; |
| |
| dp83826_of_init(phydev); |
| |
| return 0; |
| } |
| |
| static int dp83822_suspend(struct phy_device *phydev) |
| { |
| int value; |
| |
| value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG); |
| |
| if (!(value & DP83822_WOL_EN)) |
| genphy_suspend(phydev); |
| |
| return 0; |
| } |
| |
| static int dp83822_resume(struct phy_device *phydev) |
| { |
| int value; |
| |
| genphy_resume(phydev); |
| |
| value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG); |
| |
| phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG, value | |
| DP83822_WOL_CLR_INDICATION); |
| |
| return 0; |
| } |
| |
| #define DP83822_PHY_DRIVER(_id, _name) \ |
| { \ |
| PHY_ID_MATCH_MODEL(_id), \ |
| .name = (_name), \ |
| /* PHY_BASIC_FEATURES */ \ |
| .probe = dp83822_probe, \ |
| .soft_reset = dp83822_phy_reset, \ |
| .config_init = dp83822_config_init, \ |
| .read_status = dp83822_read_status, \ |
| .get_wol = dp83822_get_wol, \ |
| .set_wol = dp83822_set_wol, \ |
| .config_intr = dp83822_config_intr, \ |
| .handle_interrupt = dp83822_handle_interrupt, \ |
| .suspend = dp83822_suspend, \ |
| .resume = dp83822_resume, \ |
| } |
| |
| #define DP83826_PHY_DRIVER(_id, _name) \ |
| { \ |
| PHY_ID_MATCH_MODEL(_id), \ |
| .name = (_name), \ |
| /* PHY_BASIC_FEATURES */ \ |
| .probe = dp83826_probe, \ |
| .soft_reset = dp83822_phy_reset, \ |
| .config_init = dp83826_config_init, \ |
| .get_wol = dp83822_get_wol, \ |
| .set_wol = dp83822_set_wol, \ |
| .config_intr = dp83822_config_intr, \ |
| .handle_interrupt = dp83822_handle_interrupt, \ |
| .suspend = dp83822_suspend, \ |
| .resume = dp83822_resume, \ |
| } |
| |
| #define DP8382X_PHY_DRIVER(_id, _name) \ |
| { \ |
| PHY_ID_MATCH_MODEL(_id), \ |
| .name = (_name), \ |
| /* PHY_BASIC_FEATURES */ \ |
| .probe = dp8382x_probe, \ |
| .soft_reset = dp83822_phy_reset, \ |
| .config_init = dp8382x_config_init, \ |
| .get_wol = dp83822_get_wol, \ |
| .set_wol = dp83822_set_wol, \ |
| .config_intr = dp83822_config_intr, \ |
| .handle_interrupt = dp83822_handle_interrupt, \ |
| .suspend = dp83822_suspend, \ |
| .resume = dp83822_resume, \ |
| } |
| |
| static struct phy_driver dp83822_driver[] = { |
| DP83822_PHY_DRIVER(DP83822_PHY_ID, "TI DP83822"), |
| DP8382X_PHY_DRIVER(DP83825I_PHY_ID, "TI DP83825I"), |
| DP83826_PHY_DRIVER(DP83826C_PHY_ID, "TI DP83826C"), |
| DP83826_PHY_DRIVER(DP83826NC_PHY_ID, "TI DP83826NC"), |
| DP8382X_PHY_DRIVER(DP83825S_PHY_ID, "TI DP83825S"), |
| DP8382X_PHY_DRIVER(DP83825CM_PHY_ID, "TI DP83825M"), |
| DP8382X_PHY_DRIVER(DP83825CS_PHY_ID, "TI DP83825CS"), |
| }; |
| module_phy_driver(dp83822_driver); |
| |
| static struct mdio_device_id __maybe_unused dp83822_tbl[] = { |
| { DP83822_PHY_ID, 0xfffffff0 }, |
| { DP83825I_PHY_ID, 0xfffffff0 }, |
| { DP83826C_PHY_ID, 0xfffffff0 }, |
| { DP83826NC_PHY_ID, 0xfffffff0 }, |
| { DP83825S_PHY_ID, 0xfffffff0 }, |
| { DP83825CM_PHY_ID, 0xfffffff0 }, |
| { DP83825CS_PHY_ID, 0xfffffff0 }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(mdio, dp83822_tbl); |
| |
| MODULE_DESCRIPTION("Texas Instruments DP83822 PHY driver"); |
| MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com"); |
| MODULE_LICENSE("GPL v2"); |