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.

DP83TC812R-Q1: how to use dp83tc812 driver code in petalinux

Part Number: DP83TC812R-Q1

Tool/software:

Dear sir.

  Our team has purchased phy chip for dp83tc812 from ti, but I have some problems when I am using the driver file for dp83tc812 under linux written by you. The details are as follows

We are under linux system based on linux4.19 kernel install petalinux software for driver module add but when using the driver code you pro

 // SPDX-License-Identifier: GPL-2.0
/*
 * Driver for the Texas Instruments DP83TC812 PHY
 *
 * Copyright (C) 2021 Texas Instruments Incorporated - http://www.ti.com/
 *
 */

#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 DP83TC812_CS1_0_PHY_ID	0x2000a270
#define DP83TC812_CS2_0_PHY_ID	0x2000a271
#define DP83TC813_CS2_0_PHY_ID	0x2000a211
#define DP83TC814_CS2_0_PHY_ID	0x2000a261

#define DP83812_DEVADDR		0x1f
#define DP83812_DEVADDR_MMD1	0x1

#define DP83812_STRAP		0x45d
#define MII_DP83812_SGMII_CTRL	0x608
#define MII_DP83812_RGMII_CTRL	0x600
#define MII_DP83812_INT_STAT1	0x12
#define MII_DP83812_INT_STAT2	0x13
#define MII_DP83812_INT_STAT3	0x18
#define MII_DP83812_RESET_CTRL	0x1f

#define DP83812_HW_RESET	BIT(15)
#define DP83812_SW_RESET	BIT(14)

/* INT_STAT1 bits */
#define DP83812_RX_ERR_CNT_HALF_FULL_INT_EN	BIT(0)
#define DP83812_TX_ERR_CNT_HALF_FULL_INT_EN	BIT(1)
#define DP83812_MS_TRAIN_DONE_INT_EN		BIT(2)
#define DP83812_ESD_EVENT_INT_EN		BIT(3)
#define DP83812_LINK_STAT_INT_EN		BIT(5)
#define DP83812_ENERGY_DET_INT_EN		BIT(6)
#define DP83812_LINK_QUAL_INT_EN		BIT(7)

/* INT_STAT2 bits */
#define DP83812_JABBER_INT_EN		BIT(0)
#define DP83812_POL_INT_EN		BIT(1)
#define DP83812_SLEEP_MODE_INT_EN	BIT(2)
#define DP83812_OVERTEMP_INT_EN		BIT(3)
#define DP83812_FIFO_INT_EN		BIT(4)
#define DP83812_PAGE_RXD_INT_EN		BIT(5)
#define DP83812_OVERVOLTAGE_INT_EN	BIT(6)
#define DP83812_UNDERVOLTAGE_INT_EN	BIT(7)

/* INT_STAT3 bits */
#define DP83812_LPS_INT_EN		BIT(0)
#define DP83812_WUP_INT_EN		BIT(1)
#define DP83812_WAKE_REQ_INT_EN		BIT(2)
#define DP83811_NO_FRAME_INT_EN		BIT(3)
#define DP83811_POR_DONE_INT_EN		BIT(4)
#define DP83812_SLEEP_FAIL_INT_EN	BIT(5)

/* RGMII_CTRL bits */
#define DP83812_RGMII_EN		BIT(3)

/* SGMII CTRL bits */
#define DP83812_SGMII_AUTO_NEG_EN	BIT(0)
#define DP83812_SGMII_EN		BIT(9)

/* Strap bits */
#define DP83812_MASTER_MODE	BIT(9)
#define DP83812_RGMII_IS_EN	BIT(7)

enum dp83812_chip_type {
	DP83812_CS1 = 0,
	DP83812_CS2,
	DP83813_CS2,
	DP83814_CS2,
};

struct dp83812_init_reg {
	int	reg;
	int	val;
};

static const struct dp83812_init_reg dp83812_master_cs1_0_init[] = {
	{0x523, 0x0001},
	{0x800, 0xf864},
	{0x803, 0x1552},
	{0x804, 0x1a66},
	{0x805, 0x1f7b},
	{0x81f, 0x2a88},
	{0x825, 0x40e5},
	{0x82b, 0x7f3f},
	{0x830, 0x0543},
	{0x836, 0x5008},
	{0x83a, 0x08e0},
	{0x83b, 0x0845},
	{0x83e, 0x0445},
	{0x855, 0x9b9a},
	{0x85f, 0x2010},
	{0x860, 0x6040},
	{0x86c, 0x1333},
	{0x86b, 0x3e10},
	{0x872, 0x88c0},
	{0x873, 0x0003},
	{0x879, 0x000f},
	{0x87b, 0x0070},
	{0x87c, 0x003f},
	{0x89e, 0x00aa},
	{0x523, 0x0000},
};

static const struct dp83812_init_reg dp83812_master_cs2_0_init[] = {
	{0x523, 0x0001},
	{0x81C, 0x0fe2},
	{0x872, 0x0300},
	{0x879, 0x0f00},
	{0x806, 0x2952},
	{0x807, 0x3361},
	{0x808, 0x3D7B},
	{0x83E, 0x045F},
	{0x834, 0x8000},
	{0x862, 0x00E8},
	{0x896, 0x32CB},
	{0x03E, 0x0009},
	{0x01f, 0x4000},
	{0x523, 0x0000},
};

static const struct dp83812_init_reg dp83812_slave_cs1_0_init[] = {
	{0x523, 0x0001},
	{0x803, 0x1b52},
	{0x804, 0x216c},
	{0x805, 0x277b},
	{0x827, 0x3000},
	{0x830, 0x0543},
	{0x83a, 0x0020},
	{0x83c, 0x0001},
	{0x855, 0x9b9a},
	{0x85f, 0x2010},
	{0x860, 0x6040},
	{0x86c, 0x0333},
	{0x872, 0x88c0},
	{0x873, 0x0021},
	{0x879, 0x000f},
	{0x87b, 0x0070},
	{0x87c, 0x0002},
	{0x897, 0x003f},
	{0x89e, 0x00a2},
	{0x510, 0x000f},
	{0x523, 0x0000},
};

static const struct dp83812_init_reg dp83812_slave_cs2_0_init[] = {
	{0x523, 0x0001},
	{0x873, 0x0821},
	{0x896, 0x22ff},
	{0x89E, 0x0000},
	{0x01f, 0x4000},
	{0x523, 0x0000},
};

struct dp83812_private {
	int chip;
	bool is_master;
	bool is_rgmii;
	bool is_sgmii;
};

static int dp83812_read_straps(struct phy_device *phydev)
{
	struct dp83812_private *dp83812 = phydev->priv;
	int strap;

	strap = phy_read_mmd(phydev, DP83812_DEVADDR, DP83812_STRAP);
	if (strap < 0)
		return strap;

	printk("%s: Strap is 0x%X\n", __func__, strap);
	if (strap & DP83812_MASTER_MODE)
		dp83812->is_master = true;

	if (strap & DP83812_RGMII_IS_EN)
		dp83812->is_rgmii = true;
	return 0;
};

static int dp83812_reset(struct phy_device *phydev, bool hw_reset)
{
	int ret;

	if (hw_reset)
		ret = phy_write(phydev, MII_DP83812_RESET_CTRL,
				DP83812_HW_RESET);
	else
		ret = phy_write(phydev, MII_DP83812_RESET_CTRL,
				DP83812_SW_RESET);

	if (ret)
		return ret;

	mdelay(100);

	return 0;
}

static int dp83812_phy_reset(struct phy_device *phydev)
{
	int err;
	int ret;

	err = phy_write(phydev, MII_DP83812_RESET_CTRL, DP83812_HW_RESET);
	if (err < 0)
		return err;

	ret = dp83812_read_straps(phydev);
	if (ret)
		return ret;

	return 0;
}

static int dp83812_write_seq(struct phy_device *phydev, const struct
				dp83812_init_reg *init_data, int size)
{
	int ret;
	int i;

	for (i = 0; i < size; i++) {
		ret = phy_write_mmd(phydev, DP83812_DEVADDR, init_data[i].reg,
					init_data[i].val);
	if (ret)
		return ret;
	}
	return 0;
}

static int dp83812_chip_init(struct phy_device *phydev)
{
	struct dp83812_private *dp83812 = phydev->priv;
	int ret;

	ret = dp83812_reset(phydev, true);
	if (ret)
		return ret;

	if (dp83812->is_master)
		ret = phy_write_mmd(phydev, DP83812_DEVADDR_MMD1, 0x0834, 0xc001);
		// ret = phy_write_mmd(phydev, DP83812_DEVADDR, 0x0834, 0xc001);
	else
		ret = phy_write_mmd(phydev, DP83812_DEVADDR_MMD1, 0x0834, 0x8001);
		// ret = phy_write_mmd(phydev, DP83812_DEVADDR, 0x0834, 0x8001);

	switch (dp83812->chip) {
	case DP83812_CS1:
		if (dp83812->is_master)
			ret = dp83812_write_seq(phydev,
						dp83812_master_cs1_0_init,
						ARRAY_SIZE(dp83812_master_cs1_0_init));
		else
			ret = dp83812_write_seq(phydev,
						dp83812_slave_cs1_0_init,
						ARRAY_SIZE(dp83812_slave_cs1_0_init));
	break;
	case DP83812_CS2:
		if (dp83812->is_master)
			ret = dp83812_write_seq(phydev,
						dp83812_master_cs2_0_init,
						ARRAY_SIZE(dp83812_master_cs2_0_init));
		else
			ret = dp83812_write_seq(phydev,
						dp83812_slave_cs2_0_init,
						ARRAY_SIZE(dp83812_slave_cs2_0_init));
	break;
	case DP83813_CS2:
		if (dp83812->is_master)
			ret = dp83812_write_seq(phydev,
						dp83812_master_cs2_0_init,
						ARRAY_SIZE(dp83812_master_cs2_0_init));
		else
			ret = dp83812_write_seq(phydev,
						dp83812_slave_cs2_0_init,
						ARRAY_SIZE(dp83812_slave_cs2_0_init));
	break;
	case DP83814_CS2:
		if (dp83812->is_master)
			ret = dp83812_write_seq(phydev,
						dp83812_master_cs2_0_init,
						ARRAY_SIZE(dp83812_master_cs2_0_init));
		else
			ret = dp83812_write_seq(phydev,
						dp83812_slave_cs2_0_init,
						ARRAY_SIZE(dp83812_slave_cs2_0_init));
	break;
	default:
		return -EINVAL;
	};

	if (ret)
		return ret;

	mdelay(10);
	// phy_write_mmd(phydev, DP83812_DEVADDR, 0x523, 0x00);
	/* Do a soft reset to restart the PHY with updated values */
	return dp83812_reset(phydev, false);
}

static int dp83812_config_init(struct phy_device *phydev)
{
	int value, err;

	err = dp83812_chip_init(phydev);
	if (err)
		return err;

	value = phy_read_mmd(phydev, DP83812_DEVADDR, MII_DP83812_SGMII_CTRL);
	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
		err = phy_write_mmd(phydev, DP83812_DEVADDR, MII_DP83812_SGMII_CTRL,
					(DP83812_SGMII_EN | value));
	} else {
		err = phy_write_mmd(phydev, DP83812_DEVADDR, MII_DP83812_SGMII_CTRL,
					(~DP83812_SGMII_EN & value));
	}

	if (err < 0)

		return err;

	return 0;
}

static int dp83812_ack_interrupt(struct phy_device *phydev)
{
	int err;

	err = phy_read(phydev, MII_DP83812_INT_STAT1);
	if (err < 0)
		return err;

	err = phy_read(phydev, MII_DP83812_INT_STAT2);
	if (err < 0)
		return err;

	err = phy_read(phydev, MII_DP83812_INT_STAT3);
	if (err < 0)
		return err;

	return 0;
}

static int dp83812_config_intr(struct phy_device *phydev)
{
	int misr_status, err;

	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
		misr_status = phy_read(phydev, MII_DP83812_INT_STAT1);
		if (misr_status < 0)
			return misr_status;

		misr_status |= (DP83812_ESD_EVENT_INT_EN |
				DP83812_LINK_STAT_INT_EN |
				DP83812_ENERGY_DET_INT_EN |
				DP83812_LINK_QUAL_INT_EN);

		err = phy_write(phydev, MII_DP83812_INT_STAT1, misr_status);
		if (err < 0)
			return err;

		misr_status = phy_read(phydev, MII_DP83812_INT_STAT2);
		if (misr_status < 0)
			return misr_status;

		misr_status |= (DP83812_SLEEP_MODE_INT_EN |
				DP83812_OVERTEMP_INT_EN |
				DP83812_OVERVOLTAGE_INT_EN |
				DP83812_UNDERVOLTAGE_INT_EN);

		err = phy_write(phydev, MII_DP83812_INT_STAT2, misr_status);
		if (err < 0)
			return err;

		misr_status = phy_read(phydev, MII_DP83812_INT_STAT3);
		if (misr_status < 0)
			return misr_status;

		misr_status |= (DP83812_LPS_INT_EN |
				DP83812_WAKE_REQ_INT_EN |
				DP83811_NO_FRAME_INT_EN |
				DP83811_POR_DONE_INT_EN);

		err = phy_write(phydev, MII_DP83812_INT_STAT3, misr_status);

	} else {
		err = phy_write(phydev, MII_DP83812_INT_STAT1, 0);
		if (err < 0)
			return err;

		err = phy_write(phydev, MII_DP83812_INT_STAT2, 0);
		if (err < 0)
			return err;

		err = phy_write(phydev, MII_DP83812_INT_STAT3, 0);
	}

	return err;
}

#if 0
static irqreturn_t dp83812_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_DP83812_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_DP83812_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_DP83812_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;
}
#endif

static int dp83812_config_aneg(struct phy_device *phydev)
{
	int value, err;

	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
		value = phy_read_mmd(phydev, DP83812_DEVADDR, MII_DP83812_SGMII_CTRL);
		if (phydev->autoneg == AUTONEG_ENABLE) {
			err = phy_write_mmd(phydev, DP83812_DEVADDR,
					    MII_DP83812_SGMII_CTRL,
					(DP83812_SGMII_AUTO_NEG_EN | value));
			if (err < 0)
				return err;
		} else {
			err = phy_write_mmd(phydev, DP83812_DEVADDR,
					    MII_DP83812_SGMII_CTRL,
					(~DP83812_SGMII_AUTO_NEG_EN & value));
			if (err < 0)
				return err;
		}
	}
	return genphy_config_aneg(phydev);
}



static int dp83812_probe(struct phy_device *phydev)
{
	struct dp83812_private *dp83812;
	int ret;

	dp83812 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83812), GFP_KERNEL);
	if (!dp83812)
		return -ENOMEM;

	ret = dp83812_read_straps(phydev);
	if (ret)
		return ret;

	switch (phydev->phy_id) {
	case DP83TC812_CS1_0_PHY_ID:
		dp83812->chip = DP83812_CS1;
	break;
	case DP83TC812_CS2_0_PHY_ID:
		dp83812->chip = DP83812_CS2;
	break;
	case DP83TC813_CS2_0_PHY_ID:
		dp83812->chip = DP83813_CS2;
		break;
	case DP83TC814_CS2_0_PHY_ID:
		dp83812->chip = DP83814_CS2;
	break;
	default:
	return -EINVAL;
	};
/* vikram : above code added to switch between different phy ids */

	return dp83812_config_init(phydev);
}

#define DP83812_PHY_DRIVER(_id, _name)				\
	{							\
		PHY_ID_MATCH_EXACT(_id),			\
		.name           = (_name),			\
		.probe          = dp83812_probe,		\
		/* PHY_BASIC_FEATURES */			\
		.soft_reset     = dp83812_phy_reset,		\
		.config_init    = dp83812_config_init,		\
		.config_aneg = dp83812_config_aneg,		\
		.ack_interrupt = dp83812_ack_interrupt,		\
/*if 0								\
		.handle_interrupt = dp83812_handle_interrupt,	\
#endif	*/							\
		.config_intr = dp83812_config_intr,		\
		.suspend = genphy_suspend,			\
		.resume = genphy_resume,			\
	}

static struct phy_driver dp83812_driver[] = {
	DP83812_PHY_DRIVER(DP83TC812_CS1_0_PHY_ID, "TI DP83TC812CS1.0"),
	DP83812_PHY_DRIVER(DP83TC812_CS2_0_PHY_ID, "TI DP83TC812CS2.0"),
	DP83812_PHY_DRIVER(DP83TC813_CS2_0_PHY_ID, "TI DP83TC813CS2.0"),
	DP83812_PHY_DRIVER(DP83TC814_CS2_0_PHY_ID, "TI DP83TC814CS2.0"),
	};

module_phy_driver(dp83812_driver);

static struct mdio_device_id __maybe_unused dp83812_tbl[] = {
	{ PHY_ID_MATCH_EXACT(DP83TC812_CS1_0_PHY_ID) },
	{ PHY_ID_MATCH_EXACT(DP83TC812_CS2_0_PHY_ID) },
	{ PHY_ID_MATCH_EXACT(DP83TC813_CS2_0_PHY_ID) },
	{ PHY_ID_MATCH_EXACT(DP83TC814_CS2_0_PHY_ID) },
	{ },
};

MODULE_DESCRIPTION("Texas Instruments DP83TC812 PHY driver");
MODULE_AUTHOR("Hari Nagalla <hnagalla@ti.com");
MODULE_LICENSE("GPL");
vided reported the following error I do not know how to solve,

if you have any suggestions and methods, please do not hesitate to advise, look forward to your reply!

  • Hi,

    You can test the version I attached, but unfortunately our 812 driver only currently works for Linux v5.10 and above at the moment.

    // SPDX-License-Identifier: GPL-2.0
    /* Driver for the Texas Instruments DP83TC812 PHY
     * Copyright (C) 2021 Texas Instruments Incorporated - http://www.ti.com/
     */
    /* 01/17/2024 Alvaro Reyes (a-reyes1@ti.com)
    * Updated Open Alliance script for the 812 in both Master and Slave
    * Added SQI, TDR, and Forced-Master/Slave features
     *
     * 02/08/2024 Alvaro Reyes (a-reyes1@ti.com)
     *
     * New Global Variables
     *
     * Updated Functions:
     * dp83812_reset
     * dp83812_phy_reset
     * dp83812_chip_init
     * dp83812_config_init
     * dp83812_config_aned
     * dp83812_probe
     */
    
    #define DP83TC812_ENABLE_NEW		0
    
    #include <linux/ethtool.h>
    #if DP83TC812_ENABLE_NEW
    #include <linux/ethtool_netlink.h>
    #endif
    #include <linux/etherdevice.h>
    #include <linux/kernel.h>
    #include <linux/mii.h>
    #include <linux/mdio.h>
    #include <linux/module.h>
    #include <linux/of.h>
    #include <linux/phy.h>
    #include <linux/netdevice.h>
    
    #if !DP83TC812_ENABLE_NEW
    static int __phy_modify_mmd_changed(struct phy_device *phydev, int devad,
    				    u32 regnum, u16 mask, u16 set)
    {
    	int new, ret;
    
    	ret = phy_read_mmd(phydev, devad, regnum);
    	if (ret < 0)
    		return ret;
    
    	new = (ret & ~mask) | set;
    	if (new == ret)
    		return 0;
    
    	ret = phy_write_mmd(phydev, devad, regnum, new);
    
    	return ret < 0 ? ret : 1;
    }
    
    static int __phy_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
    			    u16 mask, u16 set)
    {
    	int ret;
    
    	ret = __phy_modify_mmd_changed(phydev, devad, regnum, mask, set);
    
    	return ret < 0 ? ret : 0;
    }
    
    static inline int phy_set_bits_mmd(struct phy_device *phydev, int devad,
    				   u32 regnum, u16 val)
    {
    	return __phy_modify_mmd(phydev, devad, regnum, 0, val);
    }
    #endif
    
    #define DP83TC812_CS2_0_PHY_ID			0x2000a271
    #define DP83TC813_CS2_0_PHY_ID			0x2000a211
    #define DP83TC814_CS2_0_PHY_ID			0x2000a261
    
    #define MMD1F							0x1f
    #define MMD1							0x1
    
    #define DP83TC812_STRAP					0x45d
    #define MII_DP83TC812_SGMII_CTRL		0x608
    #define SGMII_CONFIG_VAL				0x027B
    #define MII_DP83TC812_RGMII_CTRL		0x600
    #define MII_DP83TC812_INT_STAT1			0x12
    #define MII_DP83TC812_INT_STAT2			0x13
    #define MII_DP83TC812_INT_STAT3			0x18
    #define MII_DP83TC812_RESET_CTRL		0x1f
    #define MMD1_PMA_CTRL_2					0x1834
    #define DP83TC812_TDR_CFG5				0x0306
    #define DP83TC812_CDCR					0x1E
    #define TDR_DONE						BIT(1)
    #define TDR_FAIL						BIT(0)
    #define DP83TC812_TDR_TC1				0x310
    #define DP83TC812_TDR_START_BIT			BIT(15)
    #define DP83TC812_TDR_half_open_det_en 	BIT(4)
    #define BRK_MS_CFG						BIT(14)
    #define HALF_OPEN_DETECT				BIT(8)
    #define PEAK_DETECT						BIT(7)
    #define PEAK_SIGN						BIT(6)
    
    #define DP83TC812_HW_RESET				BIT(15)
    #define DP83TC812_SW_RESET				BIT(14)
    
    /* INT_STAT1 bits */
    #define DP83TC812_RX_ERR_CNT_HALF_FULL_INT_EN	BIT(0)
    #define DP83TC812_TX_ERR_CNT_HALF_FULL_INT_EN	BIT(1)
    #define DP83TC812_MS_TRAIN_DONE_INT_EN			BIT(2)
    #define DP83TC812_ESD_EVENT_INT_EN				BIT(3)
    #define DP83TC812_LINK_STAT_INT_EN				BIT(5)
    #define DP83TC812_ENERGY_DET_INT_EN				BIT(6)
    #define DP83TC812_LINK_QUAL_INT_EN				BIT(7)
    
    /* INT_STAT2 bits */
    #define DP83TC812_JABBER_INT_EN			BIT(0)
    #define DP83TC812_POL_INT_EN			BIT(1)
    #define DP83TC812_SLEEP_MODE_INT_EN		BIT(2)
    #define DP83TC812_OVERTEMP_INT_EN		BIT(3)
    #define DP83TC812_FIFO_INT_EN			BIT(4)
    #define DP83TC812_PAGE_RXD_INT_EN		BIT(5)
    #define DP83TC812_OVERVOLTAGE_INT_EN	BIT(6)
    #define DP83TC812_UNDERVOLTAGE_INT_EN	BIT(7)
    
    /* INT_STAT3 bits */
    #define DP83TC812_LPS_INT_EN			BIT(0)
    #define DP83TC812_WUP_INT_EN			BIT(1)
    #define DP83TC812_WAKE_REQ_INT_EN		BIT(2)
    #define DP83TC811_NO_FRAME_INT_EN		BIT(3)
    #define DP83TC811_POR_DONE_INT_EN		BIT(4)
    #define DP83TC812_SLEEP_FAIL_INT_EN		BIT(5)
    
    /* RGMII_CTRL bits */
    #define DP83TC812_RGMII_EN				BIT(3)
    
    /* SGMII CTRL bits */
    #define DP83TC812_SGMII_AUTO_NEG_EN		BIT(0)
    #define DP83TC812_SGMII_EN				BIT(9)
    
    /* Strap bits */
    #define DP83TC812_MASTER_MODE			BIT(9)
    #define DP83TC812_RGMII_IS_EN			BIT(7)
    
    /* RGMII ID CTRL */
    #define DP83TC812_RGMII_ID_CTRL			0x602
    #define DP83TC812_RX_CLK_SHIFT			BIT(1)
    #define DP83TC812_TX_CLK_SHIFT			BIT(0)
    
    /*SQI Status bits*/
    #define DP83TC812_dsp_reg_71			0x871
    #define MAX_SQI_VALUE					0x7
    
    #if !DP83TC812_ENABLE_NEW
    static int rx_int_delay;
    module_param(rx_int_delay, int, 0444);
    static int tx_int_delay;
    module_param(tx_int_delay, int, 0444);
    #endif
    
    enum DP83TC812_chip_type {
    	DP83TC812_CS1 = 0,
    	DP83TC812_CS2,
    	DP83TC813_CS2,
    	DP83TC814_CS2,
    };
    
    struct DP83TC812_init_reg {
    	int	reg;
    	int	val;
    };
    
    static const struct DP83TC812_init_reg DP83TC812_master_cs2_0_init[] = {
    	{0x001F, 0x8000},
    	{0x0523, 0x0001},
    	{0x0834, 0xC001},
    	{0x081C, 0x0FE2},
    	{0x0872, 0x0300},
    	{0x0879, 0x0F00},
    	{0x0806, 0x2952},
    	{0x0807, 0x3361},
    	{0x0808, 0x3D7B},
    	{0x083E, 0x045F},
    	{0x0834, 0x8000},
    	{0x0862, 0x00E8},
    	{0x0896, 0x32CB},
    	{0x003E, 0x0009},
    	{0x001F, 0x4000},
    	{0x0523, 0x0000},
    };
    
    static const struct DP83TC812_init_reg DP83TC812_slave_cs2_0_init[] = {
    	{0x001F, 0x8000},
    	{0x0523, 0x0001},
    	{0x0834, 0x8001},
    	{0x0873, 0x0821},
    	{0x0896, 0x22FF},
    	{0x089E, 0x0000},
    	{0x001F, 0x4000},
    	{0x0523, 0x0000},
    };
    
    static const struct DP83TC812_init_reg DP83TC812_tdr_config_init[] = {
    	{0x523, 0x0001},
    	{0x827, 0x4800},
    	{0x301, 0x1701},
    	{0x303, 0x023D},
    	{0x305, 0x0015},
    	{0x306, 0x001A},
    	{0x01f, 0x4000},
    	{0x523, 0x0000},
    	{0x01f, 0x0000},
    };
    
    struct DP83TC812_private {
    	int chip;
    	bool is_master;
    	bool is_rgmii;
    	bool is_sgmii;
    };
    
    static int DP83TC812_read_straps(struct phy_device *phydev)
    {
    	struct DP83TC812_private *DP83TC812 = phydev->priv;
    	int strap;
    
    	strap = phy_read_mmd(phydev, MMD1F, DP83TC812_STRAP);
    	if (strap < 0)
    		return strap;
    
    	if (strap & DP83TC812_MASTER_MODE)
    		DP83TC812->is_master = true;
    
    	if (strap & DP83TC812_RGMII_IS_EN)
    		DP83TC812->is_rgmii = true;
    	return 0;
    };
    
    static int DP83TC812_reset(struct phy_device *phydev, bool hw_reset)
    {
    	int ret;
    
    	if (hw_reset)
    		ret = phy_write_mmd(phydev, MMD1F, MII_DP83TC812_RESET_CTRL,
    				DP83TC812_HW_RESET);
    	else
    		ret = phy_write_mmd(phydev, MMD1F, MII_DP83TC812_RESET_CTRL,
    				DP83TC812_SW_RESET);
    
    	if (ret)
    		return ret;
    
    	mdelay(100);
    
    	return 0;
    }
    
    static int DP83TC812_phy_reset(struct phy_device *phydev)
    {
    	int err;
    	int ret;
    
    	err = phy_write_mmd(phydev, MMD1F, MII_DP83TC812_RESET_CTRL, DP83TC812_HW_RESET);
    	if (err < 0)
    		return err;
    
    	ret = DP83TC812_read_straps(phydev);
    	if (ret)
    		return ret;
    
    	return 0;
    }
    
    static int DP83TC812_write_seq(struct phy_device *phydev, const struct
    				DP83TC812_init_reg *init_data, int size)
    {
    	int ret;
    	int i;
    
    	for (i = 0; i < size; i++) {
    		ret = phy_write_mmd(phydev, MMD1F, init_data[i].reg,
    					init_data[i].val);
    		if (ret)
    			return ret;
    	}
    	return 0;
    }
    
    static int DP83TC812_setup_master_slave(struct phy_device *phydev)
    {
    #if DP83TC812_ENABLE_NEW
    	switch(phydev->master_slave_set)
    	{
    		case MASTER_SLAVE_CFG_MASTER_FORCE:
    		case MASTER_SLAVE_CFG_MASTER_PREFERRED:
    			return phy_write_mmd(phydev, MMD1, 0x0834, 0xC001);
    		case MASTER_SLAVE_CFG_SLAVE_FORCE:
    		case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
    			return phy_write_mmd(phydev, MMD1, 0x0834,0x8001);
    	case MASTER_SLAVE_CFG_UNKNOWN:
    	case MASTER_SLAVE_CFG_UNSUPPORTED:
    	default:
    		return 0;
    	}
    #else
    	return 0;
    #endif
    }
    
    static int DP83TC812_read_master_slave(struct phy_device *phydev)
    {
    #if DP83TC812_ENABLE_NEW
    	int ctrl2 = phy_read_mmd(phydev, MMD1, MMD1_PMA_CTRL_2);
    
    	if (ctrl2 < 0)
    		return ctrl2;
    
    	if (ctrl2 & BRK_MS_CFG){
    		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;
    	}
    #endif
    
    	return 0;
    }
    
    static int DP83TC812_read_status(struct phy_device *phydev)
    {
    	int ret;
    
    	ret = genphy_read_status(phydev);
    	if (ret)
    		return ret;
    
    	ret = DP83TC812_read_master_slave(phydev);
    
    	return 0;
    }
    
    #if DP83TC812_ENABLE_NEW
    static int DP83TC812_sqi(struct phy_device *phydev){
    	int sqi;
    
    	sqi = phy_read_mmd(phydev,MMD1F,DP83TC812_dsp_reg_71);
    
    	if(sqi < 0){
    		printk("Error: Invalid Value.\n");
    		return sqi;
    	}
    
    	sqi = (sqi >> 1) & 0x7;
    
    	return sqi;
    }
    
    static int DP83TC812_sqi_max(struct phy_device *phydev){
    	return MAX_SQI_VALUE;
    }
    
    static int DP83TC812_cable_test_start(struct phy_device *phydev){
    	if(DP83TC812_read_master_slave(phydev))
    		printk("PHY is set as Master.\n");
    	else
    		printk("PHY is set as Slave.\n");
    
    	DP83TC812_write_seq(phydev, DP83TC812_tdr_config_init, ARRAY_SIZE(DP83TC812_tdr_config_init));
    
    	phy_write_mmd(phydev,MMD1F,DP83TC812_CDCR, DP83TC812_TDR_START_BIT);
    
    	msleep(100);
    
    	return 0;
    }
    
    static int DP83TC812_cable_test_report_trans(u32 result) {
    	int length_of_fault;
    	if (result == 0) {
    		printk("No Fault Detected. \n");
    		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
    	}
    	else if (result & PEAK_DETECT) {
    		length_of_fault = (result & 0x3F);
    
    		// If Cable is Open
    		if(result & PEAK_SIGN){
    
    			/*if(result & HALF_OPEN_DETECT){
    				printk("Half Open Cable Detected\n");
    				printk("Length of Fault: %d Meters\n",length_of_fault);
    				return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
    			} */
    			printk("Open Cable Detected\n");
    			printk("Length of Fault: %d Meters\n",length_of_fault);
    			return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
    		}
    		// Else it is Short
    		printk("Short Detected\n");
    		printk("Length of Fault: %d Meters\n",length_of_fault);
    		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
    
    	}
    
    	return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
    }
    
    
    static int DP83TC812_cable_test_report(struct phy_device *phydev){
    
    	int ret;
    	ret = phy_read_mmd(phydev,MMD1F,DP83TC812_TDR_TC1);
    
    	if (ret < 0)
    		return ret;
    	ethnl_cable_test_result(phydev,ETHTOOL_A_CABLE_PAIR_A,DP83TC812_cable_test_report_trans(ret));
    
    	return 0;
    }
    
    static int DP83TC812_cable_test_get_status(struct phy_device *phydev, bool *finished){
    	int statusReg;
    	int TDR_Done;
    	int TDR_Fail;
    	*finished = false;
    	statusReg = phy_read_mmd(phydev, MMD1F,DP83TC812_CDCR);
    
    	TDR_Done = statusReg & TDR_DONE;
    	TDR_Fail = statusReg & TDR_FAIL;
    
    	if(TDR_Done && !TDR_Fail){
    		*finished = true;
    		printk("\nTDR HAS COMPLETED AND PASSED\n");
    		return DP83TC812_cable_test_report(phydev);
    	}
    	else if (TDR_Fail){
    		printk("\nTDR HAS FAILED\n");
    	}
    
    	return -EINVAL;
    }
    #endif
    
    static int DP83TC812_chip_init(struct phy_device *phydev)
    {
    	struct DP83TC812_private *DP83TC812 = phydev->priv;
    	int ret;
    
    	ret = DP83TC812_reset(phydev, true);
    	if (ret)
    		return ret;
    
    	phydev->autoneg = AUTONEG_DISABLE;
    	phydev->speed = SPEED_100;
    	phydev->duplex = DUPLEX_FULL;
    #if DP83TC812_ENABLE_NEW
    	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
    		 phydev->supported);
    #else
    	phydev->supported = SUPPORTED_100baseT_Full;
    #endif
    
    	if (DP83TC812->is_master)
    		ret = phy_write_mmd(phydev, MMD1, 0x0834, 0xc001);
    	else
    		ret = phy_write_mmd(phydev, MMD1, 0x0834, 0x8001);
    
    	switch (DP83TC812->chip) {
    	case DP83TC812_CS2:
    		if (DP83TC812->is_master)
    			{
    				ret = DP83TC812_write_seq(phydev,
    						DP83TC812_master_cs2_0_init,
    						ARRAY_SIZE(DP83TC812_master_cs2_0_init));
    				phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); //Enables Autonomous Mode
    			}
    		else
    			{
    				ret = DP83TC812_write_seq(phydev,
    						DP83TC812_slave_cs2_0_init,
    						ARRAY_SIZE(DP83TC812_slave_cs2_0_init));
    				phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); //Enables Autonomous Mode
    			}
    	break;
    	case DP83TC813_CS2:
    		if (DP83TC812->is_master)
    			{
    				ret = DP83TC812_write_seq(phydev,
    						DP83TC812_master_cs2_0_init,
    						ARRAY_SIZE(DP83TC812_master_cs2_0_init));
    				phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); //Enables Autonomous Mode
    			}
    		else
    			{
    				ret = DP83TC812_write_seq(phydev,
    						DP83TC812_slave_cs2_0_init,
    						ARRAY_SIZE(DP83TC812_slave_cs2_0_init));
    				phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); //Enables Autonomous Mode
    			}
    	break;
    	case DP83TC814_CS2:
    		if (DP83TC812->is_master)
    			{
    				ret = DP83TC812_write_seq(phydev,
    						DP83TC812_master_cs2_0_init,
    						ARRAY_SIZE(DP83TC812_master_cs2_0_init));
    				phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); //Enables Autonomous Mode
    			}
    		else
    			{
    				ret = DP83TC812_write_seq(phydev,
    						DP83TC812_slave_cs2_0_init,
    						ARRAY_SIZE(DP83TC812_slave_cs2_0_init));
    				phy_set_bits_mmd(phydev, MMD1F, 0x018B, BIT(6)); //Enables Autonomous Mode
    			}
    	break;
    	default:
    		return -EINVAL;
    	};
    
    	if (ret)
    		return ret;
    
    	mdelay(10);
    	// phy_write_mmd(phydev, DP83TC812_DEVADDR, 0x523, 0x00);
    	/* Do a soft reset to restart the PHY with updated values */
    	return DP83TC812_reset(phydev, false);
    }
    
    static int DP83TC812_config_init(struct phy_device *phydev)
    {
    #if DP83TC812_ENABLE_NEW
    	struct device *dev = &phydev->mdio.dev;
    	s32 rx_int_delay;
    	s32 tx_int_delay;
    #endif
    	int rgmii_delay;
    	int value, ret;
    
    	ret = DP83TC812_chip_init(phydev);
    	if (ret)
    		return ret;
    
    	if (phy_interface_is_rgmii(phydev)) {
    #if DP83TC812_ENABLE_NEW
    		rx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0,
    						      true);
    #endif
    
    		if (rx_int_delay <= 0)
    			rgmii_delay = 0;
    		else
    			rgmii_delay = DP83TC812_RX_CLK_SHIFT;
    
    #if DP83TC812_ENABLE_NEW
    		tx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0,
    						      false);
    #endif
    		if (tx_int_delay <= 0)
    			rgmii_delay &= ~DP83TC812_TX_CLK_SHIFT;
    		else
    			rgmii_delay |= DP83TC812_TX_CLK_SHIFT;
    
    		if (rgmii_delay) {
    			ret = phy_set_bits_mmd(phydev, MMD1,
    					       DP83TC812_RGMII_ID_CTRL,
    					       rgmii_delay);
    			if (ret)
    				return ret;
    		}
    	}
    
    	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
    		value = phy_read(phydev, MII_DP83TC812_SGMII_CTRL);
    		ret = phy_write_mmd(phydev, MMD1F, MII_DP83TC812_SGMII_CTRL,
    				SGMII_CONFIG_VAL);
    	if (ret < 0)
    		return ret;
    	}
    
    	return 0;
    }
    
    static int DP83TC812_ack_interrupt(struct phy_device *phydev)
    {
    	int err;
    
    	err = phy_read(phydev, MII_DP83TC812_INT_STAT1);
    	if (err < 0)
    		return err;
    
    	err = phy_read(phydev, MII_DP83TC812_INT_STAT2);
    	if (err < 0)
    		return err;
    
    	err = phy_read(phydev, MII_DP83TC812_INT_STAT3);
    	if (err < 0)
    		return err;
    
    	return 0;
    }
    
    static int DP83TC812_config_intr(struct phy_device *phydev)
    {
    	int misr_status, err;
    
    	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
    		misr_status = phy_read(phydev, MII_DP83TC812_INT_STAT1);
    		if (misr_status < 0)
    			return misr_status;
    
    		misr_status |= (DP83TC812_ESD_EVENT_INT_EN |
    				DP83TC812_LINK_STAT_INT_EN |
    				DP83TC812_ENERGY_DET_INT_EN |
    				DP83TC812_LINK_QUAL_INT_EN);
    
    		err = phy_write(phydev, MII_DP83TC812_INT_STAT1, misr_status);
    		if (err < 0)
    			return err;
    
    		misr_status = phy_read(phydev, MII_DP83TC812_INT_STAT2);
    		if (misr_status < 0)
    			return misr_status;
    
    		misr_status |= (DP83TC812_SLEEP_MODE_INT_EN |
    				DP83TC812_OVERTEMP_INT_EN |
    				DP83TC812_OVERVOLTAGE_INT_EN |
    				DP83TC812_UNDERVOLTAGE_INT_EN);
    
    		err = phy_write(phydev, MII_DP83TC812_INT_STAT2, misr_status);
    		if (err < 0)
    			return err;
    
    		misr_status = phy_read(phydev, MII_DP83TC812_INT_STAT3);
    		if (misr_status < 0)
    			return misr_status;
    
    		misr_status |= (DP83TC812_LPS_INT_EN |
    				DP83TC812_WAKE_REQ_INT_EN |
    				DP83TC811_NO_FRAME_INT_EN |
    				DP83TC811_POR_DONE_INT_EN);
    
    		err = phy_write(phydev, MII_DP83TC812_INT_STAT3, misr_status);
    
    	} else {
    		err = phy_write(phydev, MII_DP83TC812_INT_STAT1, 0);
    		if (err < 0)
    			return err;
    
    		err = phy_write(phydev, MII_DP83TC812_INT_STAT2, 0);
    		if (err < 0)
    			return err;
    
    		err = phy_write(phydev, MII_DP83TC812_INT_STAT3, 0);
    	}
    
    	return err;
    }
    
    #if 0
    static irqreturn_t DP83TC812_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_DP83TC812_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_DP83TC812_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_DP83TC812_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;
    }
    #endif
    
    static int DP83TC812_config_aneg(struct phy_device *phydev)
    {
    	bool changed = false;
    	int err, value, ret;
    
    	if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
    		value = phy_read(phydev, MII_DP83TC812_SGMII_CTRL);
    		ret = phy_write_mmd(phydev, MMD1F, MII_DP83TC812_SGMII_CTRL,
    				SGMII_CONFIG_VAL);
    		if (ret < 0)
    			return ret;
    	}
    
    	err = DP83TC812_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_config_aneg(phydev);
    }
    
    static int DP83TC812_probe(struct phy_device *phydev)
    {
    	struct DP83TC812_private *DP83TC812;
    	int ret;
    
    	DP83TC812 = devm_kzalloc(&phydev->mdio.dev, sizeof(*DP83TC812), GFP_KERNEL);
    	if (!DP83TC812)
    		return -ENOMEM;
    
    	phydev->priv = DP83TC812;
    	ret = DP83TC812_read_straps(phydev);
    	if (ret)
    		return ret;
    
    	switch (phydev->phy_id) {
    	case DP83TC812_CS2_0_PHY_ID:
    		DP83TC812->chip = DP83TC812_CS2;
    	break;
    	case DP83TC813_CS2_0_PHY_ID:
    		DP83TC812->chip = DP83TC813_CS2;
    		break;
    	case DP83TC814_CS2_0_PHY_ID:
    		DP83TC812->chip = DP83TC814_CS2;
    	break;
    	default:
    	return -EINVAL;
    	};
    
    	return DP83TC812_config_init(phydev);
    }
    
    #ifndef PHY_ID_MATCH_EXACT
    #define PHY_ID_MATCH_EXACT(id) .phy_id = (id), .phy_id_mask = GENMASK(31, 0)
    #endif
    
    #define DP83TC812_PHY_DRIVER(_id, _name)				\
    	{							\
    		PHY_ID_MATCH_EXACT(_id),			\
    		.name           = (_name),			\
    		.probe          = DP83TC812_probe,		\
    /*#if !DP83TC812_ENABLE_NEW*/					\
    		.features   = PHY_100BT_FEATURES | PHY_DEFAULT_FEATURES,\
    		.flags      = PHY_HAS_INTERRUPT,            \
    /*#endif*/									\
    		/* PHY_BASIC_FEATURES */			\
    		.soft_reset     = DP83TC812_phy_reset,		\
    		.config_init    = DP83TC812_config_init,		\
    		.config_aneg = DP83TC812_config_aneg,		\
    		.ack_interrupt = DP83TC812_ack_interrupt,		\
    /*if 0								\
    		.handle_interrupt = dp83812_handle_interrupt,	\
    #endif	*/									\
    		.config_intr = DP83TC812_config_intr,		\
    		.suspend = genphy_suspend,			\
    		.resume = genphy_resume,			\
    /*#if DP83TC812_ENABLE_NEW					\
    		.get_sqi = DP83TC812_sqi,				\
    		.get_sqi_max = DP83TC812_sqi_max,		\
    		.cable_test_start = DP83TC812_cable_test_start, \
    		.cable_test_get_status = DP83TC812_cable_test_get_status, \
    #endif*/							\
    		.read_status	= DP83TC812_read_status,		\
    	}
    
    static struct phy_driver DP83TC812_driver[] = {
    	DP83TC812_PHY_DRIVER(DP83TC812_CS2_0_PHY_ID, "TI DP83TC812CS2.0"),
    	DP83TC812_PHY_DRIVER(DP83TC813_CS2_0_PHY_ID, "TI DP83TC813CS2.0"),
    	DP83TC812_PHY_DRIVER(DP83TC814_CS2_0_PHY_ID, "TI DP83TC814CS2.0"),
    	};
    
    module_phy_driver(DP83TC812_driver);
    
    static struct mdio_device_id __maybe_unused DP83TC812_tbl[] = {
    	// { PHY_ID_MATCH_EXACT(DP83TC812_CS1_0_PHY_ID) },
    	{ PHY_ID_MATCH_EXACT(DP83TC812_CS2_0_PHY_ID) },
    	{ PHY_ID_MATCH_EXACT(DP83TC813_CS2_0_PHY_ID) },
    	{ PHY_ID_MATCH_EXACT(DP83TC814_CS2_0_PHY_ID) },
    	{ },
    };
    MODULE_DEVICE_TABLE(mdio, DP83TC812_tbl);
    
    MODULE_DESCRIPTION("Texas Instruments DP83TC812 PHY driver");
    MODULE_AUTHOR("Hari Nagalla <hnagalla@ti.com");
    MODULE_LICENSE("GPL");
    

    Best regards,

    Melissa