@@ -662,6 +669,244 @@ static int m88e1510_config_aneg(struct phy_device *phydev)
return err;
}
+#if IS_ENABLED(CONFIG_PHY_LEDS)
+
+enum {
+ COMMON = BIT(0),
+ L1V0_RECV = BIT(1),
+ L1V0_COPPER = BIT(2),
+ L1V5_100_FIBER = BIT(3),
+ L1V5_100_10 = BIT(4),
+ L2V2_INIT = BIT(5),
+ L2V2_PTP = BIT(6),
+ L2V2_DUPLEX = BIT(7),
+ L3V0_FIBER = BIT(8),
+ L3V0_LOS = BIT(9),
+ L3V5_TRANS = BIT(10),
+ L3V7_FIBER = BIT(11),
+ L3V7_DUPLEX = BIT(12),
+};
+
+struct marvell_led_mode_info {
+ const char *name;
+ s8 regval[MARVELL_PHY_MAX_LEDS];
+ u32 flags;
+};
+
+static const struct marvell_led_mode_info marvell_led_mode_info[] = {
+ { "link", { 0x0, -1, 0x0, -1, -1, -1, }, COMMON },
+ { "link/act", { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, }, COMMON },
+ { "1Gbps/100Mbps/10Mbps", { 0x2, -1, -1, -1, -1, -1, }, COMMON },
+ { "act", { 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, }, COMMON },
+ { "blink-act", { 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, }, COMMON },
+ { "tx", { 0x5, -1, 0x5, -1, 0x5, 0x5, }, COMMON },
+ { "tx", { -1, -1, -1, 0x5, -1, -1, }, L3V5_TRANS },
+ { "rx", { -1, -1, -1, -1, 0x0, 0x0, }, COMMON },
+ { "rx", { -1, 0x0, -1, -1, -1, -1, }, L1V0_RECV },
+ { "copper", { 0x6, -1, -1, -1, -1, -1, }, COMMON },
+ { "copper", { -1, 0x0, -1, -1, -1, -1, }, L1V0_COPPER },
+ { "1Gbps", { 0x7, -1, -1, -1, -1, -1, }, COMMON },
+ { "link/rx", { -1, 0x2, -1, 0x2, 0x2, 0x2, }, COMMON },
+ { "100Mbps-fiber", { -1, 0x5, -1, -1, -1, -1, }, L1V5_100_FIBER },
+ { "100Mbps-10Mbps", { -1, 0x5, -1, -1, -1, -1, }, L1V5_100_10 },
+ { "1Gbps-100Mbps", { -1, 0x6, -1, -1, -1, -1, }, COMMON },
+ { "1Gbps-10Mbps", { -1, -1, 0x6, 0x6, -1, -1, }, COMMON },
+ { "100Mbps", { -1, 0x7, -1, -1, -1, -1, }, COMMON },
+ { "10Mbps", { -1, -1, 0x7, -1, -1, -1, }, COMMON },
+ { "fiber", { -1, -1, -1, 0x0, -1, -1, }, L3V0_FIBER },
+ { "fiber", { -1, -1, -1, 0x7, -1, -1, }, L3V7_FIBER },
+ { "FullDuplex", { -1, -1, -1, 0x7, -1, -1, }, L3V7_DUPLEX },
+ { "FullDuplex", { -1, -1, -1, -1, 0x6, 0x6, }, COMMON },
+ { "FullDuplex/collision", { -1, -1, -1, -1, 0x7, 0x7, }, COMMON },
+ { "FullDuplex/collision", { -1, -1, 0x2, -1, -1, -1, }, L2V2_DUPLEX },
+ { "ptp", { -1, -1, 0x2, -1, -1, -1, }, L2V2_PTP },
+ { "init", { -1, -1, 0x2, -1, -1, -1, }, L2V2_INIT },
+ { "los", { -1, -1, -1, 0x0, -1, -1, }, L3V0_LOS },
+ { "blink", { 0xb, 0xb, 0xb, 0xb, 0xb, 0xb, }, COMMON },
+};
+
+struct marvell_leds_info {
+ u32 family;
+ int nleds;
+ u32 flags;
+};
+
+#define LED(fam,n,flg) \
+ { \
+ .family = MARVELL_PHY_FAMILY_ID(MARVELL_PHY_ID_88E##fam), \
+ .nleds = (n), \
+ .flags = (flg), \
+ } \
+
+static const struct marvell_leds_info marvell_leds_info[] = {
+ LED(1112, 4, COMMON | L1V0_COPPER | L1V5_100_FIBER | L2V2_INIT | L3V0_LOS | L3V5_TRANS |
+ L3V7_FIBER),
+ LED(1121R, 3, COMMON | L1V5_100_10),
+ LED(1240, 6, COMMON | L3V5_TRANS),
+ LED(1340S, 6, COMMON | L1V0_COPPER | L1V5_100_FIBER | L2V2_PTP | L3V0_FIBER | L3V7_DUPLEX),
+ LED(1510, 3, COMMON | L1V0_RECV | L1V5_100_FIBER | L2V2_DUPLEX),
+ LED(1545, 6, COMMON | L1V0_COPPER | L1V5_100_FIBER | L3V0_FIBER | L3V7_DUPLEX),
+};
+
+static inline int marvell_led_reg(int led)
+{
+ switch (led) {
+ case 0 ... 3:
+ return MII_PHY_LED_CTRL;
+ case 4 ... 5:
+ return MII_PHY_LED45_CTRL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int marvell_led_set_regval(struct phy_device *phydev, int led, u16 val)
+{
+ u16 mask;
+ int reg;
+
+ reg = marvell_led_reg(led);
+ if (reg < 0)
+ return reg;
+
+ val <<= (led % 4) * 4;
+ mask = 0xf << ((led % 4) * 4);
+
+ return phy_modify_paged(phydev, MII_MARVELL_LED_PAGE, reg, mask, val);
+}
+
+static int marvell_led_get_regval(struct phy_device *phydev, int led)
+{
+ int reg, val;
+
+ reg = marvell_led_reg(led);
+ if (reg < 0)
+ return reg;
+
+ val = phy_read_paged(phydev, MII_MARVELL_LED_PAGE, reg);
+ if (val < 0)
+ return val;
+
+ val >>= (led % 4) * 4;
+ val &= 0xf;
+
+ return val;
+}
+
+static int marvell_led_brightness_set(struct phy_device *phydev, struct phy_device_led *led,
+ enum led_brightness brightness)
+{
+ u8 val;
+
+ /* don't do anything if HW control is enabled */
+ if (led->cdev.trigger == &phy_hw_led_trig)
+ return 0;
+
+ val = brightness ? MII_PHY_LED_CTRL_FORCE_ON : MII_PHY_LED_CTRL_FORCE_OFF;
+
+ return marvell_led_set_regval(phydev, led->addr, val);
+}
+
+static inline bool is_valid_led_mode(struct phy_device_led *led,
+ const struct marvell_led_mode_info *mode)
+{
+ return mode->regval[led->addr] != -1 && (led->flags & mode->flags);
+}
+
+static const char *marvell_led_iter_hw_mode(struct phy_device *phydev, struct phy_device_led *led,
+ void **iter)
+{
+ const struct marvell_led_mode_info *mode = *iter;
+
+ if (!mode)
+ mode = marvell_led_mode_info;
+
+ if (mode - marvell_led_mode_info == ARRAY_SIZE(marvell_led_mode_info))
+ goto end;
+
+ while (!is_valid_led_mode(led, mode)) {
+ ++mode;
+ if (mode - marvell_led_mode_info == ARRAY_SIZE(marvell_led_mode_info))
+ goto end;
+ }
+
+ *iter = (void *)(mode + 1);
+ return mode->name;
+end:
+ *iter = NULL;
+ return NULL;
+}
+
+static int marvell_led_set_hw_mode(struct phy_device *phydev, struct phy_device_led *led,
+ const char *name)
+{
+ const struct marvell_led_mode_info *mode;
+ int i;
+
+ if (!name)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(marvell_led_mode_info); ++i) {
+ mode = &marvell_led_mode_info[i];
+
+ if (!is_valid_led_mode(led, mode))
+ continue;
+
+ if (sysfs_streq(name, mode->name))
+ return marvell_led_set_regval(phydev, led->addr, mode->regval[led->addr]);
+ }
+
+ return -EINVAL;
+}
+
+static const char *marvell_led_get_hw_mode(struct phy_device *phydev, struct phy_device_led *led)
+{
+ const struct marvell_led_mode_info *mode;
+ int i, regval;
+
+ regval = marvell_led_get_regval(phydev, led->addr);
+ if (regval < 0)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(marvell_led_mode_info); ++i) {
+ mode = &marvell_led_mode_info[i];
+
+ if (!is_valid_led_mode(led, mode))
+ continue;
+
+ if (mode->regval[led->addr] == regval)
+ return mode->name;
+ }
+
+ return NULL;
+}
+
+static int marvell_led_init(struct phy_device *phydev, struct phy_device_led *led)
+{
+ const struct marvell_leds_info *info = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(marvell_leds_info); ++i) {
+ if (MARVELL_PHY_FAMILY_ID(phydev->phy_id) == marvell_leds_info[i].family) {
+ info = &marvell_leds_info[i];
+ break;
+ }
+ }
+
+ if (!info)
+ return -ENOTSUPP;
+
+ if (led->addr >= info->nleds)
+ return -EINVAL;
+
+ led->flags = info->flags;
+ led->cdev.max_brightness = 1;
+
+ return 0;
+}
+
+#endif /* IS_ENABLED(CONFIG_PHY_LEDS) */
+
static void marvell_config_led(struct phy_device *phydev)
{
u16 def_config;