Tool/software:
Hi,
We have a DP83TC817 ethernet PHY connected to AM6204 SOC. Does the linux driver support this PHY? Is there anything to consider specifically for bringing up the interface?
This thread has been locked.
If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.
Tool/software:
Hi,
We have a DP83TC817 ethernet PHY connected to AM6204 SOC. Does the linux driver support this PHY? Is there anything to consider specifically for bringing up the interface?
See DP83TC817S-Q1: Linux Driver .
Pekka
For DP83TC817.c, please reach out to your local Texas Instruments field representative to provide it for you as it requires access to our NDA folders.
How to reach out to local Texas Instruments field representative? Is this a proprietary driver?
Yes, for DP83TG721, dp83tg720.c can be used: https://github.com/TexasInstruments/ti-ethernet-software/tree/main/linux_drivers
could this driver be used?
Hi Shibi,
You could modify dp83tc812.c to serve as DP83TC817 driver.
Please see dp83tc817 proprietary driver at link below.
https://www.ti.com/secureresources/DP83TC817-Q1-DESIGN
Thanks,
Drew
Hello,
I got access to the driver from TI. But it doesn't support the PHY version 2. Is there any latest driver available?
Hi Shibi,
Please find the latest driver attached here.
/* * Copyright (c) 2025, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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> #define DP83TC817_CS2_0_PHY_ID 0x2000a2b2 #define MMD1F 0x1f #define MMD1 0x1 #define DP83TC817_STRAP 0x45d #define MII_DP83TC817_SGMII_CTRL 0x0608 #define MII_DP83TC817_INT_STAT1 0x12 #define MII_DP83TC817_INT_STAT2 0x13 #define MII_DP83TC817_INT_STAT3 0x18 #define MII_DP83TC817_RESET_CTRL 0x1f #define DP83TC817_HW_RESET BIT(15) #define DP83TC817_SW_RESET BIT(14) /* INT_STAT1 bits */ #define DP83TC817_RX_ERR_HF_INT_EN BIT(0) #define DP83TC817_MS_TRAINING_INT_EN BIT(1) #define DP83TC817_ANEG_COMPLETE_INT_EN BIT(2) #define DP83TC817_ESD_EVENT_INT_EN BIT(3) #define DP83TC817_LINK_STAT_INT_EN BIT(5) #define DP83TC817_ENERGY_DET_INT_EN BIT(6) #define DP83TC817_LINK_QUAL_INT_EN BIT(7) /* INT_STAT2 bits */ #define DP83TC817_JABBER_DET_INT_EN BIT(0) #define DP83TC817_POLARITY_INT_EN BIT(1) #define DP83TC817_SLEEP_MODE_INT_EN BIT(2) #define DP83TC817_OVERTEMP_INT_EN BIT(3) #define DP83TC817_OVERVOLTAGE_INT_EN BIT(6) #define DP83TC817_UNDERVOLTAGE_INT_EN BIT(7) /* INT_STAT3 bits */ #define DP83TC817_LPS_INT_EN BIT(0) #define DP83TC817_NO_FRAME_INT_EN BIT(3) #define DP83TC817_POR_DONE_INT_EN BIT(4) #define MII_DP83TC817_RXSOP1 0x04a5 #define MII_DP83TC817_RXSOP2 0x04a6 #define MII_DP83TC817_RXSOP3 0x04a7 /* SGMII CTRL bits */ #define DP83TC817_SGMII_EN BIT(9) #define DP83TC817_SGMII_AUTO_NEG_EN BIT(0) #define DP83TC817_SGMII_TX_ERR_DIS BIT(15) /* Strap bits */ #define DP83TC817_MASTER_MODE BIT(9) #define DP83TC817_RGMII_IS_EN BIT(7) /* Master/Slave bits */ #define DP83TC817_MMD1_PMA_CTRL2 0x0834 #define DP83TC817_MMD1_PMA_CTRL2_MASTER BIT(14) const int dp83tc817_feature_array[3] = { //ETHTOOL_LINK_MODE_100baseT1_Half_BIT, ETHTOOL_LINK_MODE_100baseT1_Full_BIT, ETHTOOL_LINK_MODE_TP_BIT, ETHTOOL_LINK_MODE_Autoneg_BIT, }; enum dp83tc817_chip_type{ DP83TC817_CS2_0, }; struct dp83tc817_init_reg { int mmd; int reg; int val; }; static const struct dp83tc817_init_reg dp83tc817_cs2_0_master_init[] = { {MMD1F, 0x001F, 0x8000}, {MMD1F, 0x0523, 0x0001}, {MMD1, 0x0834, 0xC000}, {MMD1F, 0x05A8, 0x1D27}, {MMD1F, 0x0848, 0x0030}, {MMD1F, 0x04DF, 0x0006}, {MMD1F, 0x0154, 0x0220}, {MMD1F, 0x041D, 0x0018}, {MMD1F, 0x031B, 0x2929}, {MMD1F, 0x031C, 0x2929}, {MMD1F, 0x031D, 0x2929}, {MMD1F, 0x031E, 0x7F29}, {MMD1F, 0x0F28, 0x0001}, {MMD1F, 0x01DD, 0x000F}, {MMD1F, 0x0834, 0xC251}, {MMD1F, 0x048E, 0x2315}, {MMD1F, 0x05B0, 0x0003}, {MMD1F, 0x0888, 0x10E0}, {MMD1F, 0x0426, 0x0008}, {MMD1F, 0x0830, 0x0542}, {MMD1F, 0x0831, 0x0705}, {MMD1F, 0x003E, 0x0000}, {MMD1F, 0x0331, 0x017C}, {MMD1F, 0x08FF, 0x81C2}, {MMD1F, 0x0833, 0x008A}, {MMD1F, 0x08E9, 0xC1D8}, {MMD1F, 0x086C, 0x2337}, {MMD1F, 0x082A, 0x1000}, {MMD1F, 0x086B, 0x3C90}, {MMD1F, 0x0836, 0x0008}, {MMD1F, 0x0844, 0x2305}, {MMD1F, 0x0874, 0x6866}, {MMD1F, 0x0875, 0x6868}, {MMD1F, 0x0876, 0x8868}, {MMD1F, 0x0877, 0x6668}, {MMD1F, 0x0872, 0x000C}, {MMD1F, 0x0879, 0x00B0}, {MMD1F, 0x0827, 0x3000}, {MMD1F, 0x0802, 0x3247}, {MMD1F, 0x085D, 0x5BB0}, {MMD1F, 0x0852, 0x4280}, {MMD1F, 0x08DB, 0x0800}, {MMD1F, 0x08D5, 0x0008}, {MMD1F, 0x08D4, 0x1FE4}, {MMD1F, 0x0809, 0x4C85}, {MMD1F, 0x0837, 0x0355}, {MMD1F, 0x0820, 0x01AA}, {MMD1F, 0x0825, 0x40E5}, {MMD1F, 0x08FE, 0xF800}, {MMD1F, 0x0552, 0x0008}, {MMD1F, 0x05A1, 0x960D}, {MMD1F, 0x001F, 0x4000}, {MMD1F, 0x0523, 0x0000}, }; static const struct dp83tc817_init_reg dp83tc817_cs2_0_slave_init[] = { {MMD1F, 0x001F, 0x8000}, {MMD1F, 0x0523, 0x0001}, {MMD1, 0x0834, 0x8000}, {MMD1F, 0x05A8, 0x1E07}, {MMD1F, 0x04DF, 0x0006}, {MMD1F, 0x0827, 0x3000}, {MMD1F, 0x0154, 0x0220}, {MMD1F, 0x041D, 0x0018}, {MMD1F, 0x08D6, 0x3636}, {MMD1F, 0x08D7, 0x3636}, {MMD1F, 0x08D8, 0x3636}, {MMD1F, 0x08D9, 0xCA36}, {MMD1F, 0x0844, 0x2315}, {MMD1F, 0x0870, 0x3333}, {MMD1F, 0x0873, 0x0021}, {MMD1F, 0x089E, 0x0000}, {MMD1F, 0x0F28, 0x0001}, {MMD1F, 0x01DD, 0x000F}, {MMD1F, 0x0834, 0x0251}, {MMD1F, 0x05B0, 0x0003}, {MMD1F, 0x0888, 0x10E0}, {MMD1F, 0x0426, 0x0008}, {MMD1F, 0x0830, 0x0542}, {MMD1F, 0x003E, 0x0001}, {MMD1F, 0x0831, 0x0705}, {MMD1F, 0x0852, 0x5200}, {MMD1F, 0x085D, 0x5BAC}, {MMD1F, 0x048E, 0x2315}, {MMD1F, 0x08EA, 0x5210}, {MMD1F, 0x0317, 0x7F7F}, {MMD1F, 0x086C, 0x130E}, {MMD1F, 0x080A, 0x0015}, {MMD1F, 0x0825, 0x40C1}, {MMD1F, 0x086B, 0x3E90}, {MMD1F, 0x08EC, 0x5300}, {MMD1F, 0x0552, 0x0008}, {MMD1F, 0x05A1, 0x960D}, {MMD1F, 0x001F, 0x4000}, {MMD1F, 0x0523, 0x0000}, }; struct dp83tc817_private{ int chip; bool is_master; bool is_rgmii; bool is_sgmii; }; static int dp83tc817_read_straps(struct phy_device *phydev) { struct dp83tc817_private *DP83TC817 = phydev->priv; int strap; strap = phy_read_mmd(phydev, MMD1F, DP83TC817_STRAP); if (strap < 0) return strap; if (strap & DP83TC817_MASTER_MODE) DP83TC817->is_master = true; if (strap & DP83TC817_RGMII_IS_EN) DP83TC817->is_rgmii = true; return 0; }; static int dp83tc817_reset(struct phy_device *phydev, bool hw_reset) { int ret; if (hw_reset) ret = phy_write_mmd(phydev, MMD1F, MII_DP83TC817_RESET_CTRL, DP83TC817_HW_RESET); else ret = phy_write_mmd(phydev, MMD1F, MII_DP83TC817_RESET_CTRL, DP83TC817_SW_RESET); if (ret) return ret; mdelay(100); return 0; } static int dp83tc817_phy_reset(struct phy_device *phydev) { int err; int ret; err = phy_write_mmd(phydev, MMD1F, MII_DP83TC817_RESET_CTRL, DP83TC817_HW_RESET); if (err < 0) return err; ret = dp83tc817_read_straps(phydev); if (ret) return ret; return 0; } static int dp83tc817_write_sequence(struct phy_device *phydev, const struct dp83tc817_init_reg *init_data, int size) { int ret; int i; for (i = 0; i < size; i++) { ret = phy_write_mmd(phydev, init_data[i].mmd, init_data[i].reg, init_data[i].val); if (ret) return ret; } return 0; } static int dp83tc817_ack_interrupt(struct phy_device *phydev) { int err; err = phy_read(phydev, MII_DP83TC817_INT_STAT1); if (err < 0) return err; err = phy_read(phydev, MII_DP83TC817_INT_STAT2); if (err < 0) return err; err = phy_read(phydev, MII_DP83TC817_INT_STAT3); if (err < 0) return err; return 0; } static int dp83tc817_config_intr(struct phy_device *phydev) { int misr_status, err; if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { err = dp83tc817_ack_interrupt(phydev); if (err) return err; misr_status = phy_read(phydev, MII_DP83TC817_INT_STAT1); if (misr_status < 0) return misr_status; misr_status |= (DP83TC817_RX_ERR_HF_INT_EN | DP83TC817_MS_TRAINING_INT_EN | DP83TC817_ANEG_COMPLETE_INT_EN | DP83TC817_ESD_EVENT_INT_EN | DP83TC817_LINK_STAT_INT_EN | DP83TC817_ENERGY_DET_INT_EN | DP83TC817_LINK_QUAL_INT_EN); err = phy_write(phydev, MII_DP83TC817_INT_STAT1, misr_status); if (err < 0) return err; misr_status = phy_read(phydev, MII_DP83TC817_INT_STAT2); if (misr_status < 0) return misr_status; misr_status |= (DP83TC817_JABBER_DET_INT_EN | DP83TC817_POLARITY_INT_EN | DP83TC817_SLEEP_MODE_INT_EN | DP83TC817_OVERTEMP_INT_EN | DP83TC817_OVERVOLTAGE_INT_EN | DP83TC817_UNDERVOLTAGE_INT_EN); err = phy_write(phydev, MII_DP83TC817_INT_STAT2, misr_status); if (err < 0) return err; misr_status = phy_read(phydev, MII_DP83TC817_INT_STAT3); if (misr_status < 0) return misr_status; misr_status |= (DP83TC817_LPS_INT_EN | DP83TC817_NO_FRAME_INT_EN | DP83TC817_POR_DONE_INT_EN); err = phy_write(phydev, MII_DP83TC817_INT_STAT3, misr_status); } else { err = phy_write(phydev, MII_DP83TC817_INT_STAT1, 0); if (err < 0) return err; err = phy_write(phydev, MII_DP83TC817_INT_STAT2, 0); if (err < 0) return err; err = phy_write(phydev, MII_DP83TC817_INT_STAT3, 0); if (err < 0) return err; err = dp83tc817_ack_interrupt(phydev); } return err; } static irqreturn_t dp83tc817_handle_interrupt(struct phy_device *phydev) { bool trigger_machine = false; int irq_status; /* The INT_STAT registers 1, 2 and 3 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 * INT_STAT* register and use it directly to know which interrupts have * been enabled previously or not. */ irq_status = phy_read(phydev, MII_DP83TC817_INT_STAT1); 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_DP83TC817_INT_STAT2); 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_DP83TC817_INT_STAT3); 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 dp83tc81x_setup_sgmii(struct phy_device *phydev) { int value, err; value = phy_read(phydev, MII_DP83TC817_SGMII_CTRL); if (phydev->autoneg == AUTONEG_ENABLE) { err = phy_write(phydev, MII_DP83TC817_SGMII_CTRL, (DP83TC817_SGMII_AUTO_NEG_EN | value)); if (err < 0) return err; } else { err = phy_write(phydev, MII_DP83TC817_SGMII_CTRL, (~DP83TC817_SGMII_AUTO_NEG_EN & value)); if (err < 0) return err; } return err; } static int dp83tc817_setup_master_slave(struct phy_device *phydev) { switch(phydev->master_slave_set) { case MASTER_SLAVE_CFG_MASTER_FORCE: case MASTER_SLAVE_CFG_MASTER_PREFERRED: return phy_write_mmd(phydev, MDIO_MMD_PMAPMD, DP83TC817_MMD1_PMA_CTRL2, DP83TC817_MMD1_PMA_CTRL2_MASTER); case MASTER_SLAVE_CFG_SLAVE_FORCE: case MASTER_SLAVE_CFG_SLAVE_PREFERRED: return phy_write_mmd(phydev, MDIO_MMD_PMAPMD, DP83TC817_MMD1_PMA_CTRL2, 0); case MASTER_SLAVE_CFG_UNKNOWN: case MASTER_SLAVE_CFG_UNSUPPORTED: default: return 0; } return 0; } static int dp83tc817_read_master_slave(struct phy_device *phydev) { int ctrl2 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, DP83TC817_MMD1_PMA_CTRL2); if (ctrl2 < 0) return ctrl2; if (ctrl2 & DP83TC817_MMD1_PMA_CTRL2_MASTER) { phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE; phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; return 1; } else { phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE; phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; return 0; } return 0; } static int dp83tc817_config_aneg(struct phy_device *phydev) { bool changed = false; int err; if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { err = dp83tc81x_setup_sgmii(phydev); if (err) return err; } err = dp83tc817_setup_master_slave(phydev); if (err < 0) return err; else if (err) changed = true; if (AUTONEG_ENABLE != phydev->autoneg) return genphy_setup_forced(phydev); return genphy_check_and_restart_aneg(phydev, changed); } static int dp83tc817_chip_init(struct phy_device *phydev) { struct dp83tc817_private *DP83TC817 = phydev->priv; int ret; ret = dp83tc817_reset(phydev, true); if (ret) return ret; phydev->autoneg = AUTONEG_DISABLE; phydev->speed = SPEED_100; phydev->duplex = DUPLEX_FULL; linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported); if (DP83TC817->is_master) ret = phy_write_mmd(phydev, MMD1, 0x0834, 0xc001); else ret = phy_write_mmd(phydev, MMD1, 0x0834, 0x8001); switch (DP83TC817->chip){ case DP83TC817_CS2_0: if (DP83TC817->is_master) { ret = dp83tc817_write_sequence(phydev, dp83tc817_cs2_0_master_init, ARRAY_SIZE(dp83tc817_cs2_0_master_init)); phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); } else { ret = dp83tc817_write_sequence(phydev, dp83tc817_cs2_0_slave_init, ARRAY_SIZE(dp83tc817_cs2_0_slave_init)); phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); } break; default: return -EINVAL; }; if (ret) return ret; mdelay(10); return dp83tc817_reset(phydev, false); } static int dp83tc817_config_init(struct phy_device *phydev) { int value, err; err = dp83tc817_chip_init(phydev); if (err) return err; value = phy_read(phydev, MII_DP83TC817_SGMII_CTRL); if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { err = phy_write(phydev, MII_DP83TC817_SGMII_CTRL, (DP83TC817_SGMII_EN | value)); } else { err = phy_write(phydev, MII_DP83TC817_SGMII_CTRL, (~DP83TC817_SGMII_EN & value)); } if (err < 0) return err; return 0; } static int dp83tc817_probe(struct phy_device *phydev) { struct dp83tc817_private *DP83TC817; int ret; DP83TC817 = devm_kzalloc(&phydev->mdio.dev, sizeof(*DP83TC817), GFP_KERNEL); if(!DP83TC817) return -ENOMEM; phydev->priv = DP83TC817; ret = dp83tc817_read_straps(phydev); if (ret) return ret; switch (phydev->phy_id) { case DP83TC817_CS2_0_PHY_ID: DP83TC817->chip = DP83TC817_CS2_0; break; default: return -EINVAL; }; return dp83tc817_config_init(phydev); } static int dp83tc817_get_features(struct phy_device *phydev) { genphy_read_abilities(phydev); linkmode_set_bit_array(dp83tc817_feature_array, ARRAY_SIZE(dp83tc817_feature_array), phydev->supported); linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, phydev->advertising); /* Only allow advertising what this PHY supports */ linkmode_and(phydev->advertising, phydev->advertising, phydev->supported); return 0; } static int dp83tc817_read_status(struct phy_device *phydev) { int ret; ret = genphy_read_status(phydev); if (ret) return ret; ret = dp83tc817_read_master_slave(phydev); return 0; } #define DP83TC817_PHY_DRIVER(_id, _name) \ { \ PHY_ID_MATCH_EXACT(_id), \ .name = (_name), \ .probe = dp83tc817_probe, \ .config_init = dp83tc817_config_init, \ .config_aneg = dp83tc817_config_aneg, \ .soft_reset = dp83tc817_phy_reset, \ .handle_interrupt = dp83tc817_handle_interrupt, \ .config_intr = dp83tc817_config_intr, \ .suspend = genphy_suspend, \ .resume = genphy_resume, \ .get_features = dp83tc817_get_features, \ .read_status = dp83tc817_read_status, \ } static struct phy_driver dp83tc817_driver[] = { DP83TC817_PHY_DRIVER(DP83TC817_CS2_0_PHY_ID, "TI DP83TC817CS2.0"), }; module_phy_driver(dp83tc817_driver); static struct mdio_device_id __maybe_unused dp83tc817_tbl[] = { { PHY_ID_MATCH_EXACT(DP83TC817_CS2_0_PHY_ID) }, { }, }; MODULE_DEVICE_TABLE(mdio, dp83tc817_tbl); MODULE_DESCRIPTION("Texas Instruments DP83TC817 PHY driver"); MODULE_AUTHOR("Alvaro Reyes <a-reyes1@ti.com>"); MODULE_AUTHOR("Drew Miller <drew-miller@ti.com>");
Thanks,
David