diff mbox series

[net-next,2/2] net: phy: mxl-gpy: add LED dimming support

Message ID 20250222183814.3315-2-olek2@wp.pl (mailing list archive)
State New
Delegated to: Netdev Maintainers
Headers show
Series [net-next,1/2] net: phy: add LED dimming support | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 8 of 8 maintainers
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 209 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Aleksander Jan Bajkowski Feb. 22, 2025, 6:38 p.m. UTC
Some PHYs support LED dimming. The use case is a router that dims LEDs
at night. In the GPY2xx series, the PWM control register is common to
all LEDs. To avoid confusing users, only the first LED used has
brightness control enabled. In many routers only one LED is connected
to PHY. When the minimum or maximum value is set, the PWM is turned off.

As of now, only Maxlinear PHYs support dimming. In the future, support
may be extended to other PHYs such as the Realtek RTL8221B.

Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
Reviewed-by: Daniel Golle <daniel@makrotopia.org>
---
 drivers/net/phy/mxl-gpy.c | 100 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 95 insertions(+), 5 deletions(-)
diff mbox series

Patch

diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c
index 94d9cb727121..321f2b5c260c 100644
--- a/drivers/net/phy/mxl-gpy.c
+++ b/drivers/net/phy/mxl-gpy.c
@@ -110,9 +110,11 @@ 
 #define VSPEC1_MBOX_DATA	0x5
 #define VSPEC1_MBOX_ADDRLO	0x6
 #define VSPEC1_MBOX_CMD		0x7
-#define VSPEC1_MBOX_CMD_ADDRHI	GENMASK(7, 0)
-#define VSPEC1_MBOX_CMD_RD	(0 << 8)
 #define VSPEC1_MBOX_CMD_READY	BIT(15)
+#define VSPEC1_MBOX_CMD_MASK	GENMASK(11, 8)
+#define VSPEC1_MBOX_CMD_WR	0x1
+#define VSPEC1_MBOX_CMD_RD	0x0
+#define VSPEC1_MBOX_CMD_ADDRHI	GENMASK(7, 0)
 
 /* WoL */
 #define VPSPEC2_WOL_CTL		0x0E06
@@ -124,6 +126,15 @@ 
 /* Internal registers, access via mbox */
 #define REG_GPIO0_OUT		0xd3ce00
 
+/* LED Brightness registers, access via mbox */
+#define LED_BRT_CTRL		0xd3cf84
+#define LED_BRT_CTRL_LVLMAX	GENMASK(15, 12)
+#define LED_BRT_CTRL_LVLMIN	GENMASK(11, 8)
+#define LED_BRT_CTRL_EN		BIT(5)
+#define LED_BRT_CTRL_TSEN	BIT(4)
+
+#define GPY_MAX_BRIGHTNESS	15
+
 struct gpy_priv {
 	/* serialize mailbox acesses */
 	struct mutex mbox_lock;
@@ -138,6 +149,9 @@  struct gpy_priv {
 	 * is enabled.
 	 */
 	u64 lb_dis_to;
+
+	/* LED index with brightness control enabled */
+	int br_led_index;
 };
 
 static const struct {
@@ -267,7 +281,7 @@  static int gpy_mbox_read(struct phy_device *phydev, u32 addr)
 	if (ret)
 		goto out;
 
-	cmd = VSPEC1_MBOX_CMD_RD;
+	cmd = FIELD_PREP(VSPEC1_MBOX_CMD_MASK, VSPEC1_MBOX_CMD_RD);
 	cmd |= FIELD_PREP(VSPEC1_MBOX_CMD_ADDRHI, addr >> 16);
 
 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_CMD, cmd);
@@ -293,6 +307,46 @@  static int gpy_mbox_read(struct phy_device *phydev, u32 addr)
 	return ret;
 }
 
+static int gpy_mbox_write(struct phy_device *phydev, u32 addr, u16 data)
+{
+	struct gpy_priv *priv = phydev->priv;
+	int val, ret;
+	u16 cmd;
+
+	mutex_lock(&priv->mbox_lock);
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_DATA,
+			    data);
+	if (ret)
+		goto out;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_ADDRLO,
+			    addr);
+	if (ret)
+		goto out;
+
+	cmd = FIELD_PREP(VSPEC1_MBOX_CMD_MASK, VSPEC1_MBOX_CMD_WR);
+	cmd |= FIELD_PREP(VSPEC1_MBOX_CMD_ADDRHI, addr >> 16);
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_MBOX_CMD, cmd);
+	if (ret)
+		goto out;
+
+	/* The mbox read is used in the interrupt workaround. It was observed
+	 * that a read might take up to 2.5ms. This is also the time for which
+	 * the interrupt line is stuck low. To be on the safe side, poll the
+	 * ready bit for 10ms.
+	 */
+	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+					VSPEC1_MBOX_CMD, val,
+					(val & VSPEC1_MBOX_CMD_READY),
+					500, 10000, false);
+
+out:
+	mutex_unlock(&priv->mbox_lock);
+	return ret;
+}
+
 static int gpy_config_init(struct phy_device *phydev)
 {
 	/* Nothing to configure. Configuration Requirement Placeholder */
@@ -323,6 +377,7 @@  static int gpy_probe(struct phy_device *phydev)
 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
 		return -ENOMEM;
+	priv->br_led_index = -1;
 	phydev->priv = priv;
 	mutex_init(&priv->mbox_lock);
 
@@ -861,7 +916,8 @@  static int gpy115_loopback(struct phy_device *phydev, bool enable)
 static int gpy_led_brightness_set(struct phy_device *phydev,
 				  u8 index, enum led_brightness value)
 {
-	int ret;
+	struct gpy_priv *priv = phydev->priv;
+	int ret, reg;
 
 	if (index >= GPY_MAX_LEDS)
 		return -EINVAL;
@@ -874,7 +930,21 @@  static int gpy_led_brightness_set(struct phy_device *phydev,
 	if (ret)
 		return ret;
 
-	/* ToDo: set PWM brightness */
+	/* Set PWM brightness */
+	if (index == priv->br_led_index) {
+		if (value > LED_OFF && value < GPY_MAX_BRIGHTNESS) {
+			reg = LED_BRT_CTRL_EN;
+
+			reg |= FIELD_PREP(LED_BRT_CTRL_LVLMIN, 0x4);
+			reg |= FIELD_PREP(LED_BRT_CTRL_LVLMAX, value);
+		} else {
+			reg = LED_BRT_CTRL_TSEN;
+		}
+
+		ret = gpy_mbox_write(phydev, LED_BRT_CTRL, reg);
+		if (ret < 0)
+			return ret;
+	}
 
 	/* clear HW LED setup */
 	if (value == LED_OFF)
@@ -883,6 +953,17 @@  static int gpy_led_brightness_set(struct phy_device *phydev,
 		return 0;
 }
 
+static int gpy_led_max_brightness(struct phy_device *phydev, u8 index)
+{
+	struct gpy_priv *priv = phydev->priv;
+
+	if (priv->br_led_index < 0) {
+		priv->br_led_index = index;
+		return GPY_MAX_BRIGHTNESS;
+	}
+
+	return 1;
+}
 static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) |
 						 BIT(TRIGGER_NETDEV_LINK_10) |
 						 BIT(TRIGGER_NETDEV_LINK_100) |
@@ -1035,6 +1116,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1058,6 +1140,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy115_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1080,6 +1163,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy115_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1103,6 +1187,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1125,6 +1210,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1148,6 +1234,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1170,6 +1257,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1193,6 +1281,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,
@@ -1215,6 +1304,7 @@  static struct phy_driver gpy_drivers[] = {
 		.get_wol	= gpy_get_wol,
 		.set_loopback	= gpy_loopback,
 		.led_brightness_set = gpy_led_brightness_set,
+		.led_max_brightness = gpy_led_max_brightness,
 		.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,