--- /dev/null
+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);
--- /dev/null
+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)