mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-26 17:01:14 +00:00
2c39269b6e
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>
409 lines
13 KiB
Diff
409 lines
13 KiB
Diff
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);
|