123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- From 7196062b64ee470b91015f3d2e82d225948258ea Mon Sep 17 00:00:00 2001
- From: Christian Marangi <[email protected]>
- 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 <[email protected]>
- Reviewed-by: Andrew Lunn <[email protected]>
- Link: https://lore.kernel.org/r/[email protected]
- Signed-off-by: Jakub Kicinski <[email protected]>
- ---
- 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);
|