blob: 0bf770f8ebbb41deabe795376262772c9581be1d [file]
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY.
*
* Specifications:
* - IEEE 802.3 10BASE-Te, 100BASE-TX, 1000BASE-T
* - IEEE 802.3az-2010 Energy Efficient Ethernet
* - IEEE 1588 SyncE support
* - RGMII
*
* Author: Artem Shimko <a.shimko.dev@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/errno.h>
#include <linux/ethtool.h>
#include <linux/kernel.h>
#include <linux/iopoll.h>
#include <linux/mii.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#define DAP8211R_PHY_ID 0x0008011B
#define DAP8211R_PHY_ID_MASK 0xFFFFFFFF
#define DAP8211R_EXT_ADD 0x1E
#define DAP8211R_EXT_DATA 0x1F
#define DAP8211R_PHY_CON 0xA001
#define DAP8211R_PHY_SW_RST BIT(15)
#define DAP8211R_RGMII_CON 0xA003
#define DAP8211R_RGMII_TX_DEL_MASK GENMASK(3, 0)
#define DAP8211R_RGMII_RX_DEL_MASK GENMASK(13, 10)
#define DAP8211R_RGMII_CONFIG_MASK (DAP8211R_RGMII_RX_DEL_MASK | DAP8211R_RGMII_TX_DEL_MASK)
/*
* Hardware reset RX RGMII default delay from the datasheet: 0 * 150ps == 0.00ns
* Used when rx-internal-delay-ps is not specified in DT for RGMII mode and
* in RGMII_TXID to set rx delay.
*/
#define DAP8211R_INITIAL_RX_DEL_VAL 0
/*
* Hardware reset TX RGMII default delay from the datasheet: 1 * 150ps == 0.15ns
* Used when tx-internal-delay-ps is not specified in DT for RGMII mode and
* in RGMII_RXID to set tx delay.
*/
#define DAP8211R_INITIAL_TX_DEL_VAL 1
/*
* Default RGMII delay: 13 * 150 == 1.95ns
* Used when rx-internal-delay-ps or tx-internal-delay-ps are not specified in DT
* for RGMII ID modes to set tx or rx delay.
*/
#define DAP8211R_DEFAULT_DEL_SEL 0xD
static const int dap8211r_internal_delay[] = {0, 150, 300, 450, 600, 750, 900,
1050, 1200, 1350, 1500, 1650, 1800,
1950, 2100, 2250};
#define DAP8211R_DELAY_SIZE ARRAY_SIZE(dap8211r_internal_delay)
/**
* dap8211r_read_ext() - Read extended register
* @phydev: PHY device structure
* @reg: Extended register address
*
* Reads a PHY extended register using the indirect access method.
* The caller must hold the MDIO bus lock.
*
* Return: Register value on success, or negative error code
*/
static int dap8211r_read_ext(struct phy_device *phydev, u16 reg)
{
int ret;
phy_lock_mdio_bus(phydev);
ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg);
if (ret < 0)
goto out;
ret = __phy_read(phydev, DAP8211R_EXT_DATA);
out:
phy_unlock_mdio_bus(phydev);
return ret;
}
/**
* dap8211r_modify_ext() - Modify extended register bits
* @phydev: PHY device structure
* @reg: Extended register address
* @mask: Bit mask of bits to clear
* @set: Bit mask of bits to set
*
* Modifies a PHY extended register using the indirect access method.
* New value = (old value & ~mask) | set.
* The caller must hold the MDIO bus lock.
*
* Return: 0 on success, or negative error code
*/
static int dap8211r_modify_ext(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
{
int ret;
phy_lock_mdio_bus(phydev);
ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg);
if (ret < 0)
goto out;
ret = __phy_modify(phydev, DAP8211R_EXT_DATA, mask, set);
out:
phy_unlock_mdio_bus(phydev);
return ret;
}
/**
* dap8211r_config_init() - Initialize PHY
* @phydev: PHY device structure
*
* Configures the PHY during initialization:
* - RGMII delays based on interface mode
* - Software reset to apply settings (low active, self clear)
*
* Return: 0 on success, or negative error code
*/
static int dap8211r_config_init(struct phy_device *phydev)
{
u16 set = 0;
int ret, val;
s32 rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL;
s32 tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL;
if (!phy_interface_is_rgmii(phydev))
return 0;
if (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID)
rx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay,
DAP8211R_DELAY_SIZE, true);
if (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID)
tx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay,
DAP8211R_DELAY_SIZE, false);
switch (phydev->interface) {
case PHY_INTERFACE_MODE_RGMII:
if (rx_internal_delay < 0)
rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL;
if (tx_internal_delay < 0)
tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL;
break;
case PHY_INTERFACE_MODE_RGMII_RXID:
if (rx_internal_delay < 0)
rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL;
break;
case PHY_INTERFACE_MODE_RGMII_ID:
if (rx_internal_delay < 0)
rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL;
fallthrough;
case PHY_INTERFACE_MODE_RGMII_TXID:
if (tx_internal_delay < 0)
tx_internal_delay = DAP8211R_DEFAULT_DEL_SEL;
break;
default:
phydev_err(phydev, "Unsupported interface: %d\n",
phydev->interface);
return -EINVAL;
}
set |= FIELD_PREP(DAP8211R_RGMII_RX_DEL_MASK, rx_internal_delay);
set |= FIELD_PREP(DAP8211R_RGMII_TX_DEL_MASK, tx_internal_delay);
ret = dap8211r_modify_ext(phydev, DAP8211R_PHY_CON, DAP8211R_PHY_SW_RST, 0);
if (ret)
return ret;
/* Wait for reset self-clear (from low active to high) */
ret = read_poll_timeout(dap8211r_read_ext, val,
(val & DAP8211R_PHY_SW_RST),
20, 200, false, phydev, DAP8211R_PHY_CON);
if (ret)
return ret;
if (val < 0)
return val;
ret = dap8211r_modify_ext(phydev, DAP8211R_RGMII_CON, DAP8211R_RGMII_CONFIG_MASK, set);
if (ret)
return ret;
return 0;
}
static struct phy_driver dap8211r_driver[] = {
{
PHY_ID_MATCH_EXACT(DAP8211R_PHY_ID),
.name = "DAP8211R Gigabit Ethernet",
.soft_reset = genphy_soft_reset,
.config_init = dap8211r_config_init,
.read_status = genphy_read_status,
.set_loopback = genphy_loopback,
.config_aneg = genphy_config_aneg,
.suspend = genphy_suspend,
.resume = genphy_resume,
},
};
module_phy_driver(dap8211r_driver);
MODULE_DESCRIPTION("DAP8211R Gigabit Ethernet PHY driver");
MODULE_AUTHOR("Artem Shimko <a.shimko.dev@gmail.com>");
MODULE_LICENSE("GPL");
static const struct mdio_device_id __maybe_unused dap8211r_tb[] = {
{ DAP8211R_PHY_ID, DAP8211R_PHY_ID_MASK },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(mdio, dap8211r_tb);