mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			332 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
From 78997e9a5e4d8a4df561e083a92c91ae23010e07 Mon Sep 17 00:00:00 2001
 | 
						|
From: Daniel Golle <daniel@makrotopia.org>
 | 
						|
Date: Tue, 1 Oct 2024 01:17:18 +0100
 | 
						|
Subject: [PATCH] net: phy: mxl-gpy: add basic LED support
 | 
						|
 | 
						|
Add basic support for LEDs connected to MaxLinear GPY2xx and GPY115 PHYs.
 | 
						|
The PHYs allow up to 4 LEDs to be connected.
 | 
						|
Implement controlling LEDs in software as well as netdev trigger offloading
 | 
						|
and LED polarity setup.
 | 
						|
 | 
						|
The hardware claims to support 16 PWM brightness levels but there is no
 | 
						|
documentation on how to use that feature, hence this is not supported.
 | 
						|
 | 
						|
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
 | 
						|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
 | 
						|
Link: https://patch.msgid.link/b6ec9050339f8244ff898898a1cecc33b13a48fc.1727741563.git.daniel@makrotopia.org
 | 
						|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 | 
						|
---
 | 
						|
 drivers/net/phy/mxl-gpy.c | 218 ++++++++++++++++++++++++++++++++++++++
 | 
						|
 1 file changed, 218 insertions(+)
 | 
						|
 | 
						|
--- a/drivers/net/phy/mxl-gpy.c
 | 
						|
+++ b/drivers/net/phy/mxl-gpy.c
 | 
						|
@@ -38,6 +38,7 @@
 | 
						|
 #define PHY_MIISTAT		0x18	/* MII state */
 | 
						|
 #define PHY_IMASK		0x19	/* interrupt mask */
 | 
						|
 #define PHY_ISTAT		0x1A	/* interrupt status */
 | 
						|
+#define PHY_LED			0x1B	/* LEDs */
 | 
						|
 #define PHY_FWV			0x1E	/* firmware version */
 | 
						|
 
 | 
						|
 #define PHY_MIISTAT_SPD_MASK	GENMASK(2, 0)
 | 
						|
@@ -61,6 +62,11 @@
 | 
						|
 				 PHY_IMASK_ADSC | \
 | 
						|
 				 PHY_IMASK_ANC)
 | 
						|
 
 | 
						|
+#define GPY_MAX_LEDS		4
 | 
						|
+#define PHY_LED_POLARITY(idx)	BIT(12 + (idx))
 | 
						|
+#define PHY_LED_HWCONTROL(idx)	BIT(8 + (idx))
 | 
						|
+#define PHY_LED_ON(idx)		BIT(idx)
 | 
						|
+
 | 
						|
 #define PHY_FWV_REL_MASK	BIT(15)
 | 
						|
 #define PHY_FWV_MAJOR_MASK	GENMASK(11, 8)
 | 
						|
 #define PHY_FWV_MINOR_MASK	GENMASK(7, 0)
 | 
						|
@@ -72,6 +78,23 @@
 | 
						|
 #define PHY_MDI_MDI_X_CD	0x1
 | 
						|
 #define PHY_MDI_MDI_X_CROSS	0x0
 | 
						|
 
 | 
						|
+/* LED */
 | 
						|
+#define VSPEC1_LED(idx)		(1 + (idx))
 | 
						|
+#define VSPEC1_LED_BLINKS	GENMASK(15, 12)
 | 
						|
+#define VSPEC1_LED_PULSE	GENMASK(11, 8)
 | 
						|
+#define VSPEC1_LED_CON		GENMASK(7, 4)
 | 
						|
+#define VSPEC1_LED_BLINKF	GENMASK(3, 0)
 | 
						|
+
 | 
						|
+#define VSPEC1_LED_LINK10	BIT(0)
 | 
						|
+#define VSPEC1_LED_LINK100	BIT(1)
 | 
						|
+#define VSPEC1_LED_LINK1000	BIT(2)
 | 
						|
+#define VSPEC1_LED_LINK2500	BIT(3)
 | 
						|
+
 | 
						|
+#define VSPEC1_LED_TXACT	BIT(0)
 | 
						|
+#define VSPEC1_LED_RXACT	BIT(1)
 | 
						|
+#define VSPEC1_LED_COL		BIT(2)
 | 
						|
+#define VSPEC1_LED_NO_CON	BIT(3)
 | 
						|
+
 | 
						|
 /* SGMII */
 | 
						|
 #define VSPEC1_SGMII_CTRL	0x08
 | 
						|
 #define VSPEC1_SGMII_CTRL_ANEN	BIT(12)		/* Aneg enable */
 | 
						|
@@ -835,6 +858,156 @@ static int gpy115_loopback(struct phy_de
 | 
						|
 	return genphy_soft_reset(phydev);
 | 
						|
 }
 | 
						|
 
 | 
						|
+static int gpy_led_brightness_set(struct phy_device *phydev,
 | 
						|
+				  u8 index, enum led_brightness value)
 | 
						|
+{
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (index >= GPY_MAX_LEDS)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	/* clear HWCONTROL and set manual LED state */
 | 
						|
+	ret = phy_modify(phydev, PHY_LED,
 | 
						|
+			 ((value == LED_OFF) ? PHY_LED_HWCONTROL(index) : 0) |
 | 
						|
+			 PHY_LED_ON(index),
 | 
						|
+			 (value == LED_OFF) ? 0 : PHY_LED_ON(index));
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	/* ToDo: set PWM brightness */
 | 
						|
+
 | 
						|
+	/* clear HW LED setup */
 | 
						|
+	if (value == LED_OFF)
 | 
						|
+		return phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), 0);
 | 
						|
+	else
 | 
						|
+		return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
 | 
						|
+						 BIT(TRIGGER_NETDEV_LINK_100) |
 | 
						|
+						 BIT(TRIGGER_NETDEV_LINK_1000) |
 | 
						|
+						 BIT(TRIGGER_NETDEV_LINK_2500) |
 | 
						|
+						 BIT(TRIGGER_NETDEV_RX) |
 | 
						|
+						 BIT(TRIGGER_NETDEV_TX));
 | 
						|
+
 | 
						|
+static int gpy_led_hw_is_supported(struct phy_device *phydev, u8 index,
 | 
						|
+				   unsigned long rules)
 | 
						|
+{
 | 
						|
+	if (index >= GPY_MAX_LEDS)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	/* All combinations of the supported triggers are allowed */
 | 
						|
+	if (rules & ~supported_triggers)
 | 
						|
+		return -EOPNOTSUPP;
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int gpy_led_hw_control_get(struct phy_device *phydev, u8 index,
 | 
						|
+				  unsigned long *rules)
 | 
						|
+{
 | 
						|
+	int val;
 | 
						|
+
 | 
						|
+	if (index >= GPY_MAX_LEDS)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index));
 | 
						|
+	if (val < 0)
 | 
						|
+		return val;
 | 
						|
+
 | 
						|
+	if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK10)
 | 
						|
+		*rules |= BIT(TRIGGER_NETDEV_LINK_10);
 | 
						|
+
 | 
						|
+	if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK100)
 | 
						|
+		*rules |= BIT(TRIGGER_NETDEV_LINK_100);
 | 
						|
+
 | 
						|
+	if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK1000)
 | 
						|
+		*rules |= BIT(TRIGGER_NETDEV_LINK_1000);
 | 
						|
+
 | 
						|
+	if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK2500)
 | 
						|
+		*rules |= BIT(TRIGGER_NETDEV_LINK_2500);
 | 
						|
+
 | 
						|
+	if (FIELD_GET(VSPEC1_LED_CON, val) == (VSPEC1_LED_LINK10 |
 | 
						|
+					       VSPEC1_LED_LINK100 |
 | 
						|
+					       VSPEC1_LED_LINK1000 |
 | 
						|
+					       VSPEC1_LED_LINK2500))
 | 
						|
+		*rules |= BIT(TRIGGER_NETDEV_LINK);
 | 
						|
+
 | 
						|
+	if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_TXACT)
 | 
						|
+		*rules |= BIT(TRIGGER_NETDEV_TX);
 | 
						|
+
 | 
						|
+	if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_RXACT)
 | 
						|
+		*rules |= BIT(TRIGGER_NETDEV_RX);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int gpy_led_hw_control_set(struct phy_device *phydev, u8 index,
 | 
						|
+				  unsigned long rules)
 | 
						|
+{
 | 
						|
+	u16 val = 0;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (index >= GPY_MAX_LEDS)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (rules & BIT(TRIGGER_NETDEV_LINK) ||
 | 
						|
+	    rules & BIT(TRIGGER_NETDEV_LINK_10))
 | 
						|
+		val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK10);
 | 
						|
+
 | 
						|
+	if (rules & BIT(TRIGGER_NETDEV_LINK) ||
 | 
						|
+	    rules & BIT(TRIGGER_NETDEV_LINK_100))
 | 
						|
+		val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK100);
 | 
						|
+
 | 
						|
+	if (rules & BIT(TRIGGER_NETDEV_LINK) ||
 | 
						|
+	    rules & BIT(TRIGGER_NETDEV_LINK_1000))
 | 
						|
+		val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK1000);
 | 
						|
+
 | 
						|
+	if (rules & BIT(TRIGGER_NETDEV_LINK) ||
 | 
						|
+	    rules & BIT(TRIGGER_NETDEV_LINK_2500))
 | 
						|
+		val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK2500);
 | 
						|
+
 | 
						|
+	if (rules & BIT(TRIGGER_NETDEV_TX))
 | 
						|
+		val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_TXACT);
 | 
						|
+
 | 
						|
+	if (rules & BIT(TRIGGER_NETDEV_RX))
 | 
						|
+		val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_RXACT);
 | 
						|
+
 | 
						|
+	/* allow RX/TX pulse without link indication */
 | 
						|
+	if ((rules & BIT(TRIGGER_NETDEV_TX) || rules & BIT(TRIGGER_NETDEV_RX)) &&
 | 
						|
+	    !(val & VSPEC1_LED_CON))
 | 
						|
+		val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_NO_CON) | VSPEC1_LED_CON;
 | 
						|
+
 | 
						|
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), val);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	return phy_set_bits(phydev, PHY_LED, PHY_LED_HWCONTROL(index));
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int gpy_led_polarity_set(struct phy_device *phydev, int index,
 | 
						|
+				unsigned long modes)
 | 
						|
+{
 | 
						|
+	bool active_low = false;
 | 
						|
+	u32 mode;
 | 
						|
+
 | 
						|
+	if (index >= GPY_MAX_LEDS)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
 | 
						|
+		switch (mode) {
 | 
						|
+		case PHY_LED_ACTIVE_LOW:
 | 
						|
+			active_low = true;
 | 
						|
+			break;
 | 
						|
+		default:
 | 
						|
+			return -EINVAL;
 | 
						|
+		}
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
 | 
						|
+			  active_low ? 0 : PHY_LED_POLARITY(index));
 | 
						|
+}
 | 
						|
+
 | 
						|
 static struct phy_driver gpy_drivers[] = {
 | 
						|
 	{
 | 
						|
 		PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
 | 
						|
@@ -852,6 +1025,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		.phy_id		= PHY_ID_GPY115B,
 | 
						|
@@ -870,6 +1048,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy115_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
 | 
						|
@@ -887,6 +1070,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy115_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		.phy_id		= PHY_ID_GPY211B,
 | 
						|
@@ -905,6 +1093,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
 | 
						|
@@ -922,6 +1115,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		.phy_id		= PHY_ID_GPY212B,
 | 
						|
@@ -940,6 +1138,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
 | 
						|
@@ -957,6 +1160,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		.phy_id		= PHY_ID_GPY215B,
 | 
						|
@@ -975,6 +1183,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
 | 
						|
@@ -992,6 +1205,11 @@ static struct phy_driver gpy_drivers[] =
 | 
						|
 		.set_wol	= gpy_set_wol,
 | 
						|
 		.get_wol	= gpy_get_wol,
 | 
						|
 		.set_loopback	= gpy_loopback,
 | 
						|
+		.led_brightness_set = gpy_led_brightness_set,
 | 
						|
+		.led_hw_is_supported = gpy_led_hw_is_supported,
 | 
						|
+		.led_hw_control_get = gpy_led_hw_control_get,
 | 
						|
+		.led_hw_control_set = gpy_led_hw_control_set,
 | 
						|
+		.led_polarity_set = gpy_led_polarity_set,
 | 
						|
 	},
 | 
						|
 	{
 | 
						|
 		PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
 |