mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-22 15:02:32 +00:00
generic: 6.1: backport qca808x LED support patch
Backport qca808x LED support patch merged upstream needed to drop handling of it from the SSDK for ipq807x target. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
This commit is contained in:
parent
0c45d2cfc6
commit
2c39269b6e
@ -0,0 +1,408 @@
|
|||||||
|
From 7196062b64ee470b91015f3d2e82d225948258ea Mon Sep 17 00:00:00 2001
|
||||||
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
||||||
|
Date: Thu, 25 Jan 2024 21:37:01 +0100
|
||||||
|
Subject: [PATCH 5/5] net: phy: at803x: add LED support for qca808x
|
||||||
|
|
||||||
|
Add LED support for QCA8081 PHY.
|
||||||
|
|
||||||
|
Documentation for this LEDs PHY is very scarce even with NDA access
|
||||||
|
to Documentation for OEMs. Only the blink pattern are documented and are
|
||||||
|
very confusing most of the time. No documentation is present about
|
||||||
|
forcing the LED on/off or to always blink.
|
||||||
|
|
||||||
|
Those settings were reversed by poking the regs and trying to find the
|
||||||
|
correct bits to trigger these modes. Some bits mode are not clear and
|
||||||
|
maybe the documentation option are not 100% correct. For the sake of LED
|
||||||
|
support the reversed option are enough to add support for current LED
|
||||||
|
APIs.
|
||||||
|
|
||||||
|
Supported HW control modes are:
|
||||||
|
- tx
|
||||||
|
- rx
|
||||||
|
- link_10
|
||||||
|
- link_100
|
||||||
|
- link_1000
|
||||||
|
- link_2500
|
||||||
|
- half_duplex
|
||||||
|
- full_duplex
|
||||||
|
|
||||||
|
Also add support for LED polarity set to set LED polarity to active
|
||||||
|
high or low. QSDK sets this value to high by default but PHY reset value
|
||||||
|
doesn't have this enabled by default.
|
||||||
|
|
||||||
|
QSDK also sets 2 additional bits but their usage is not clear, info about
|
||||||
|
this is added in the header. It was verified that for correct function
|
||||||
|
of the LED if active high is needed, only BIT 6 is needed.
|
||||||
|
|
||||||
|
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://lore.kernel.org/r/20240125203702.4552-6-ansuelsmth@gmail.com
|
||||||
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||||
|
---
|
||||||
|
drivers/net/phy/at803x.c | 327 +++++++++++++++++++++++++++++++++++++++
|
||||||
|
1 file changed, 327 insertions(+)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/at803x.c
|
||||||
|
+++ b/drivers/net/phy/at803x.c
|
||||||
|
@@ -301,6 +301,87 @@
|
||||||
|
/* Added for reference of existence but should be handled by wait_for_completion already */
|
||||||
|
#define QCA808X_CDT_STATUS_STAT_BUSY (BIT(1) | BIT(3))
|
||||||
|
|
||||||
|
+#define QCA808X_MMD7_LED_GLOBAL 0x8073
|
||||||
|
+#define QCA808X_LED_BLINK_1 GENMASK(11, 6)
|
||||||
|
+#define QCA808X_LED_BLINK_2 GENMASK(5, 0)
|
||||||
|
+/* Values are the same for both BLINK_1 and BLINK_2 */
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
|
||||||
|
+#define QCA808X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
|
||||||
|
+#define QCA808X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
|
||||||
|
+
|
||||||
|
+#define QCA808X_MMD7_LED2_CTRL 0x8074
|
||||||
|
+#define QCA808X_MMD7_LED2_FORCE_CTRL 0x8075
|
||||||
|
+#define QCA808X_MMD7_LED1_CTRL 0x8076
|
||||||
|
+#define QCA808X_MMD7_LED1_FORCE_CTRL 0x8077
|
||||||
|
+#define QCA808X_MMD7_LED0_CTRL 0x8078
|
||||||
|
+#define QCA808X_MMD7_LED_CTRL(x) (0x8078 - ((x) * 2))
|
||||||
|
+
|
||||||
|
+/* LED hw control pattern is the same for every LED */
|
||||||
|
+#define QCA808X_LED_PATTERN_MASK GENMASK(15, 0)
|
||||||
|
+#define QCA808X_LED_SPEED2500_ON BIT(15)
|
||||||
|
+#define QCA808X_LED_SPEED2500_BLINK BIT(14)
|
||||||
|
+/* Follow blink trigger even if duplex or speed condition doesn't match */
|
||||||
|
+#define QCA808X_LED_BLINK_CHECK_BYPASS BIT(13)
|
||||||
|
+#define QCA808X_LED_FULL_DUPLEX_ON BIT(12)
|
||||||
|
+#define QCA808X_LED_HALF_DUPLEX_ON BIT(11)
|
||||||
|
+#define QCA808X_LED_TX_BLINK BIT(10)
|
||||||
|
+#define QCA808X_LED_RX_BLINK BIT(9)
|
||||||
|
+#define QCA808X_LED_TX_ON_10MS BIT(8)
|
||||||
|
+#define QCA808X_LED_RX_ON_10MS BIT(7)
|
||||||
|
+#define QCA808X_LED_SPEED1000_ON BIT(6)
|
||||||
|
+#define QCA808X_LED_SPEED100_ON BIT(5)
|
||||||
|
+#define QCA808X_LED_SPEED10_ON BIT(4)
|
||||||
|
+#define QCA808X_LED_COLLISION_BLINK BIT(3)
|
||||||
|
+#define QCA808X_LED_SPEED1000_BLINK BIT(2)
|
||||||
|
+#define QCA808X_LED_SPEED100_BLINK BIT(1)
|
||||||
|
+#define QCA808X_LED_SPEED10_BLINK BIT(0)
|
||||||
|
+
|
||||||
|
+#define QCA808X_MMD7_LED0_FORCE_CTRL 0x8079
|
||||||
|
+#define QCA808X_MMD7_LED_FORCE_CTRL(x) (0x8079 - ((x) * 2))
|
||||||
|
+
|
||||||
|
+/* LED force ctrl is the same for every LED
|
||||||
|
+ * No documentation exist for this, not even internal one
|
||||||
|
+ * with NDA as QCOM gives only info about configuring
|
||||||
|
+ * hw control pattern rules and doesn't indicate any way
|
||||||
|
+ * to force the LED to specific mode.
|
||||||
|
+ * These define comes from reverse and testing and maybe
|
||||||
|
+ * lack of some info or some info are not entirely correct.
|
||||||
|
+ * For the basic LED control and hw control these finding
|
||||||
|
+ * are enough to support LED control in all the required APIs.
|
||||||
|
+ *
|
||||||
|
+ * On doing some comparison with implementation with qca807x,
|
||||||
|
+ * it was found that it's 1:1 equal to it and confirms all the
|
||||||
|
+ * reverse done. It was also found further specification with the
|
||||||
|
+ * force mode and the blink modes.
|
||||||
|
+ */
|
||||||
|
+#define QCA808X_LED_FORCE_EN BIT(15)
|
||||||
|
+#define QCA808X_LED_FORCE_MODE_MASK GENMASK(14, 13)
|
||||||
|
+#define QCA808X_LED_FORCE_BLINK_1 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
|
||||||
|
+#define QCA808X_LED_FORCE_BLINK_2 FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
|
||||||
|
+#define QCA808X_LED_FORCE_ON FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
|
||||||
|
+#define QCA808X_LED_FORCE_OFF FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
|
||||||
|
+
|
||||||
|
+#define QCA808X_MMD7_LED_POLARITY_CTRL 0x901a
|
||||||
|
+/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
|
||||||
|
+ * LED to active high. It's not clear what BIT 3 and BIT 4 does.
|
||||||
|
+ */
|
||||||
|
+#define QCA808X_LED_ACTIVE_HIGH BIT(6)
|
||||||
|
+
|
||||||
|
/* QCA808X 1G chip type */
|
||||||
|
#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
|
||||||
|
#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
|
||||||
|
@@ -346,6 +427,7 @@ struct at803x_priv {
|
||||||
|
struct regulator_dev *vddio_rdev;
|
||||||
|
struct regulator_dev *vddh_rdev;
|
||||||
|
u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
|
||||||
|
+ int led_polarity_mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct at803x_context {
|
||||||
|
@@ -706,6 +788,9 @@ static int at803x_probe(struct phy_devic
|
||||||
|
if (!priv)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
+ /* Init LED polarity mode to -1 */
|
||||||
|
+ priv->led_polarity_mode = -1;
|
||||||
|
+
|
||||||
|
phydev->priv = priv;
|
||||||
|
|
||||||
|
ret = at803x_parse_dt(phydev);
|
||||||
|
@@ -2235,6 +2320,242 @@ static void qca808x_link_change_notify(s
|
||||||
|
phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
+static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
|
||||||
|
+ u16 *offload_trigger)
|
||||||
|
+{
|
||||||
|
+ /* Parsing specific to netdev trigger */
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_TX, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_TX_BLINK;
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_RX, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_RX_BLINK;
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_SPEED10_ON;
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_SPEED100_ON;
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_SPEED1000_ON;
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_SPEED2500_ON;
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
|
||||||
|
+ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
|
||||||
|
+ *offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
|
||||||
|
+
|
||||||
|
+ if (rules && !*offload_trigger)
|
||||||
|
+ return -EOPNOTSUPP;
|
||||||
|
+
|
||||||
|
+ /* Enable BLINK_CHECK_BYPASS by default to make the LED
|
||||||
|
+ * blink even with duplex or speed mode not enabled.
|
||||||
|
+ */
|
||||||
|
+ *offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
|
||||||
|
+{
|
||||||
|
+ u16 reg;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
|
||||||
|
+
|
||||||
|
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
|
||||||
|
+ QCA808X_LED_FORCE_EN);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long rules)
|
||||||
|
+{
|
||||||
|
+ u16 offload_trigger = 0;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long rules)
|
||||||
|
+{
|
||||||
|
+ u16 reg, offload_trigger = 0;
|
||||||
|
+ int ret;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ reg = QCA808X_MMD7_LED_CTRL(index);
|
||||||
|
+
|
||||||
|
+ ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ ret = qca808x_led_hw_control_enable(phydev, index);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
|
||||||
|
+ QCA808X_LED_PATTERN_MASK,
|
||||||
|
+ offload_trigger);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
|
||||||
|
+{
|
||||||
|
+ u16 reg;
|
||||||
|
+ int val;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return false;
|
||||||
|
+
|
||||||
|
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
|
||||||
|
+
|
||||||
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
|
||||||
|
+
|
||||||
|
+ return !(val & QCA808X_LED_FORCE_EN);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long *rules)
|
||||||
|
+{
|
||||||
|
+ u16 reg;
|
||||||
|
+ int val;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ /* Check if we have hw control enabled */
|
||||||
|
+ if (qca808x_led_hw_control_status(phydev, index))
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ reg = QCA808X_MMD7_LED_CTRL(index);
|
||||||
|
+
|
||||||
|
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
|
||||||
|
+ if (val & QCA808X_LED_TX_BLINK)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_TX, rules);
|
||||||
|
+ if (val & QCA808X_LED_RX_BLINK)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_RX, rules);
|
||||||
|
+ if (val & QCA808X_LED_SPEED10_ON)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_LINK_10, rules);
|
||||||
|
+ if (val & QCA808X_LED_SPEED100_ON)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_LINK_100, rules);
|
||||||
|
+ if (val & QCA808X_LED_SPEED1000_ON)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
|
||||||
|
+ if (val & QCA808X_LED_SPEED2500_ON)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_LINK_2500, rules);
|
||||||
|
+ if (val & QCA808X_LED_HALF_DUPLEX_ON)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
|
||||||
|
+ if (val & QCA808X_LED_FULL_DUPLEX_ON)
|
||||||
|
+ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
|
||||||
|
+{
|
||||||
|
+ u16 reg;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ reg = QCA808X_MMD7_LED_CTRL(index);
|
||||||
|
+
|
||||||
|
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
|
||||||
|
+ QCA808X_LED_PATTERN_MASK);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_brightness_set(struct phy_device *phydev,
|
||||||
|
+ u8 index, enum led_brightness value)
|
||||||
|
+{
|
||||||
|
+ u16 reg;
|
||||||
|
+ int ret;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ if (!value) {
|
||||||
|
+ ret = qca808x_led_hw_control_reset(phydev, index);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
|
||||||
|
+
|
||||||
|
+ return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
|
||||||
|
+ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
|
||||||
|
+ QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
|
||||||
|
+ QCA808X_LED_FORCE_OFF);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
|
||||||
|
+ unsigned long *delay_on,
|
||||||
|
+ unsigned long *delay_off)
|
||||||
|
+{
|
||||||
|
+ int ret;
|
||||||
|
+ u16 reg;
|
||||||
|
+
|
||||||
|
+ if (index > 2)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
|
||||||
|
+
|
||||||
|
+ /* Set blink to 50% off, 50% on at 4Hz by default */
|
||||||
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
|
||||||
|
+ QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
|
||||||
|
+ QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ /* We use BLINK_1 for normal blinking */
|
||||||
|
+ ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
|
||||||
|
+ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
|
||||||
|
+ QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ /* We set blink to 4Hz, aka 250ms */
|
||||||
|
+ *delay_on = 250 / 2;
|
||||||
|
+ *delay_off = 250 / 2;
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
|
||||||
|
+ unsigned long modes)
|
||||||
|
+{
|
||||||
|
+ struct at803x_priv *priv = phydev->priv;
|
||||||
|
+ bool active_low = false;
|
||||||
|
+ u32 mode;
|
||||||
|
+
|
||||||
|
+ for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
|
||||||
|
+ switch (mode) {
|
||||||
|
+ case PHY_LED_ACTIVE_LOW:
|
||||||
|
+ active_low = true;
|
||||||
|
+ break;
|
||||||
|
+ default:
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /* PHY polarity is global and can't be set per LED.
|
||||||
|
+ * To detect this, check if last requested polarity mode
|
||||||
|
+ * match the new one.
|
||||||
|
+ */
|
||||||
|
+ if (priv->led_polarity_mode >= 0 &&
|
||||||
|
+ priv->led_polarity_mode != active_low) {
|
||||||
|
+ phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
|
||||||
|
+ return -EINVAL;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /* Save the last PHY polarity mode */
|
||||||
|
+ priv->led_polarity_mode = active_low;
|
||||||
|
+
|
||||||
|
+ return phy_modify_mmd(phydev, MDIO_MMD_AN,
|
||||||
|
+ QCA808X_MMD7_LED_POLARITY_CTRL,
|
||||||
|
+ QCA808X_LED_ACTIVE_HIGH,
|
||||||
|
+ active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
static struct phy_driver at803x_driver[] = {
|
||||||
|
{
|
||||||
|
/* Qualcomm Atheros AR8035 */
|
||||||
|
@@ -2411,6 +2732,12 @@ static struct phy_driver at803x_driver[]
|
||||||
|
.cable_test_start = qca808x_cable_test_start,
|
||||||
|
.cable_test_get_status = qca808x_cable_test_get_status,
|
||||||
|
.link_change_notify = qca808x_link_change_notify,
|
||||||
|
+ .led_brightness_set = qca808x_led_brightness_set,
|
||||||
|
+ .led_blink_set = qca808x_led_blink_set,
|
||||||
|
+ .led_hw_is_supported = qca808x_led_hw_is_supported,
|
||||||
|
+ .led_hw_control_set = qca808x_led_hw_control_set,
|
||||||
|
+ .led_hw_control_get = qca808x_led_hw_control_get,
|
||||||
|
+ .led_polarity_set = qca808x_led_polarity_set,
|
||||||
|
}, };
|
||||||
|
|
||||||
|
module_phy_driver(at803x_driver);
|
@ -0,0 +1,98 @@
|
|||||||
|
From 7ae215ee7bb855f13c80565470fc7f67db4ba82f Mon Sep 17 00:00:00 2001
|
||||||
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
||||||
|
Date: Thu, 25 Jan 2024 21:36:59 +0100
|
||||||
|
Subject: [PATCH 3/5] net: phy: add support for PHY LEDs polarity modes
|
||||||
|
|
||||||
|
Add support for PHY LEDs polarity modes. Some PHY require LED to be set
|
||||||
|
to active low to be turned ON. Adds support for this by declaring
|
||||||
|
active-low property in DT.
|
||||||
|
|
||||||
|
PHY driver needs to declare .led_polarity_set() to configure LED
|
||||||
|
polarity modes. Function will pass the index with the LED index and a
|
||||||
|
bitmap with all the required modes to set.
|
||||||
|
|
||||||
|
Current supported modes are:
|
||||||
|
- active-low with the flag PHY_LED_ACTIVE_LOW. LED is set to active-low
|
||||||
|
to turn it ON.
|
||||||
|
- inactive-high-impedance with the flag PHY_LED_INACTIVE_HIGH_IMPEDANCE.
|
||||||
|
LED is set to high impedance to turn it OFF.
|
||||||
|
|
||||||
|
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
||||||
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
||||||
|
Link: https://lore.kernel.org/r/20240125203702.4552-4-ansuelsmth@gmail.com
|
||||||
|
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
|
||||||
|
---
|
||||||
|
drivers/net/phy/phy_device.c | 16 ++++++++++++++++
|
||||||
|
include/linux/phy.h | 22 ++++++++++++++++++++++
|
||||||
|
2 files changed, 38 insertions(+)
|
||||||
|
|
||||||
|
--- a/drivers/net/phy/phy_device.c
|
||||||
|
+++ b/drivers/net/phy/phy_device.c
|
||||||
|
@@ -3034,6 +3034,7 @@ static int of_phy_led(struct phy_device
|
||||||
|
struct device *dev = &phydev->mdio.dev;
|
||||||
|
struct led_init_data init_data = {};
|
||||||
|
struct led_classdev *cdev;
|
||||||
|
+ unsigned long modes = 0;
|
||||||
|
struct phy_led *phyled;
|
||||||
|
u32 index;
|
||||||
|
int err;
|
||||||
|
@@ -3051,6 +3052,21 @@ static int of_phy_led(struct phy_device
|
||||||
|
if (index > U8_MAX)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
+ if (of_property_read_bool(led, "active-low"))
|
||||||
|
+ set_bit(PHY_LED_ACTIVE_LOW, &modes);
|
||||||
|
+ if (of_property_read_bool(led, "inactive-high-impedance"))
|
||||||
|
+ set_bit(PHY_LED_INACTIVE_HIGH_IMPEDANCE, &modes);
|
||||||
|
+
|
||||||
|
+ if (modes) {
|
||||||
|
+ /* Return error if asked to set polarity modes but not supported */
|
||||||
|
+ if (!phydev->drv->led_polarity_set)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ err = phydev->drv->led_polarity_set(phydev, index, modes);
|
||||||
|
+ if (err)
|
||||||
|
+ return err;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
phyled->index = index;
|
||||||
|
if (phydev->drv->led_brightness_set)
|
||||||
|
cdev->brightness_set_blocking = phy_led_set_brightness;
|
||||||
|
--- a/include/linux/phy.h
|
||||||
|
+++ b/include/linux/phy.h
|
||||||
|
@@ -787,6 +787,15 @@ struct phy_led {
|
||||||
|
|
||||||
|
#define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
|
||||||
|
|
||||||
|
+/* Modes for PHY LED configuration */
|
||||||
|
+enum phy_led_modes {
|
||||||
|
+ PHY_LED_ACTIVE_LOW = 0,
|
||||||
|
+ PHY_LED_INACTIVE_HIGH_IMPEDANCE = 1,
|
||||||
|
+
|
||||||
|
+ /* keep it last */
|
||||||
|
+ __PHY_LED_MODES_NUM,
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* struct phy_driver - Driver structure for a particular PHY type
|
||||||
|
*
|
||||||
|
@@ -1055,6 +1064,19 @@ struct phy_driver {
|
||||||
|
int (*led_hw_control_get)(struct phy_device *dev, u8 index,
|
||||||
|
unsigned long *rules);
|
||||||
|
|
||||||
|
+ /**
|
||||||
|
+ * @led_polarity_set: Set the LED polarity modes
|
||||||
|
+ * @dev: PHY device which has the LED
|
||||||
|
+ * @index: Which LED of the PHY device
|
||||||
|
+ * @modes: bitmap of LED polarity modes
|
||||||
|
+ *
|
||||||
|
+ * Configure LED with all the required polarity modes in @modes
|
||||||
|
+ * to make it correctly turn ON or OFF.
|
||||||
|
+ *
|
||||||
|
+ * Returns 0, or an error code.
|
||||||
|
+ */
|
||||||
|
+ int (*led_polarity_set)(struct phy_device *dev, int index,
|
||||||
|
+ unsigned long modes);
|
||||||
|
};
|
||||||
|
#define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
|
||||||
|
struct phy_driver, mdiodrv)
|
@ -23,7 +23,7 @@ Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
|
|||||||
sysfs_remove_link(&dev->dev.kobj, "phydev");
|
sysfs_remove_link(&dev->dev.kobj, "phydev");
|
||||||
--- a/include/linux/phy.h
|
--- a/include/linux/phy.h
|
||||||
+++ b/include/linux/phy.h
|
+++ b/include/linux/phy.h
|
||||||
@@ -887,6 +887,12 @@ struct phy_driver {
|
@@ -896,6 +896,12 @@ struct phy_driver {
|
||||||
/** @handle_interrupt: Override default interrupt handling */
|
/** @handle_interrupt: Override default interrupt handling */
|
||||||
irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
|
irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user