#include <linux/ethtool.h>
 #include <linux/phy.h>
 #include <linux/marvell_phy.h>
+#include <linux/bitfield.h>
 #include <linux/of.h>
 
 #include <linux/io.h>
 #define MII_88E1510_TEMP_SENSOR                0x1b
 #define MII_88E1510_TEMP_SENSOR_MASK   0xff
 
+#define MII_88E1540_COPPER_CTRL3       0x1a
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK  GENMASK(11, 10)
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS  0
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS  1
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS  2
+#define MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS  3
+#define MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN                BIT(9)
+
 #define MII_88E6390_MISC_TEST          0x1b
 #define MII_88E6390_MISC_TEST_SAMPLE_1S                0
 #define MII_88E6390_MISC_TEST_SAMPLE_10MS      BIT(14)
        return 0;
 }
 
+static int m88e1540_get_fld(struct phy_device *phydev, u8 *msecs)
+{
+       int val;
+
+       val = phy_read(phydev, MII_88E1540_COPPER_CTRL3);
+       if (val < 0)
+               return val;
+
+       if (!(val & MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN)) {
+               *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF;
+               return 0;
+       }
+
+       val = FIELD_GET(MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);
+
+       switch (val) {
+       case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS:
+               *msecs = 0;
+               break;
+       case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS:
+               *msecs = 10;
+               break;
+       case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS:
+               *msecs = 20;
+               break;
+       case MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS:
+               *msecs = 40;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int m88e1540_set_fld(struct phy_device *phydev, const u8 *msecs)
+{
+       struct ethtool_eee eee;
+       int val, ret;
+
+       if (*msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
+               return phy_clear_bits(phydev, MII_88E1540_COPPER_CTRL3,
+                                     MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN);
+
+       /* According to the Marvell data sheet EEE must be disabled for
+        * Fast Link Down detection to work properly
+        */
+       ret = phy_ethtool_get_eee(phydev, &eee);
+       if (!ret && eee.eee_enabled) {
+               phydev_warn(phydev, "Fast Link Down detection requires EEE to be disabled!\n");
+               return -EBUSY;
+       }
+
+       if (*msecs <= 5)
+               val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_00MS;
+       else if (*msecs <= 15)
+               val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_10MS;
+       else if (*msecs <= 30)
+               val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_20MS;
+       else
+               val = MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_40MS;
+
+       val = FIELD_PREP(MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);
+
+       ret = phy_modify(phydev, MII_88E1540_COPPER_CTRL3,
+                        MII_88E1540_COPPER_CTRL3_LINK_DOWN_DELAY_MASK, val);
+       if (ret)
+               return ret;
+
+       return phy_set_bits(phydev, MII_88E1540_COPPER_CTRL3,
+                           MII_88E1540_COPPER_CTRL3_FAST_LINK_DOWN);
+}
+
+static int m88e1540_get_tunable(struct phy_device *phydev,
+                               struct ethtool_tunable *tuna, void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_FAST_LINK_DOWN:
+               return m88e1540_get_fld(phydev, data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int m88e1540_set_tunable(struct phy_device *phydev,
+                               struct ethtool_tunable *tuna, const void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_FAST_LINK_DOWN:
+               return m88e1540_set_fld(phydev, data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
 /* The VOD can be out of specification on link up. Poke an
  * undocumented register, in an undocumented page, with a magic value
  * to fix this.
                .get_sset_count = marvell_get_sset_count,
                .get_strings = marvell_get_strings,
                .get_stats = marvell_get_stats,
+               .get_tunable = m88e1540_get_tunable,
+               .set_tunable = m88e1540_set_tunable,
        },
        {
                .phy_id = MARVELL_PHY_ID_88E1545,
                .get_sset_count = marvell_get_sset_count,
                .get_strings = marvell_get_strings,
                .get_stats = marvell_get_stats,
+               .get_tunable = m88e1540_get_tunable,
+               .set_tunable = m88e1540_set_tunable,
        },
 };