Merge branch 'net-phy-generic-polarity-led-support-for-qca808x'

Christian Marangi says:

====================
net: phy: generic polarity + LED support for qca808x

This small series add LEDs support for qca808x.

QCA808x apply on PHY reset a strange polarity settings and require
some tweak to apply a more common configuration found on devices.
On adding support for it, it was pointed out that a similar
feature is also being implemented for a marvell PHY where
LED polarity is set per LED (and not global) and also have
a special mode where the LED is tristated.

The first 3 patch are to generalize this as we expect more PHY
in the future to have a similar configuration.

The implementation is extensible to support additional special
mode in the future with minimal changes and don't create regression
on already implemented PHY drivers.
====================

Link: https://lore.kernel.org/r/20240125203702.4552-1-ansuelsmth@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2024-01-26 21:03:43 -08:00
commit c09f32a859
10 changed files with 431 additions and 19 deletions

View file

@ -200,6 +200,18 @@ properties:
#trigger-source-cells property in the source node.
$ref: /schemas/types.yaml#/definitions/phandle-array
active-low:
type: boolean
description:
Makes LED active low. To turn the LED ON, line needs to be
set to low voltage instead of high.
inactive-high-impedance:
type: boolean
description:
Set LED to high-impedance mode to turn the LED OFF. LED might also
describe this mode as tristate.
# Required properties for flash LED child nodes:
flash-max-microamp:
description:

View file

@ -52,10 +52,6 @@ patternProperties:
maxItems: 1
description: LED pin number
active-low:
type: boolean
description: Makes LED active low
required:
- reg

View file

@ -78,10 +78,6 @@ patternProperties:
- maximum: 23
description: LED pin number (only LEDs 0 to 23 are valid).
active-low:
type: boolean
description: Makes LED active low.
brcm,hardware-controlled:
type: boolean
description: Makes this LED hardware controlled.

View file

@ -25,8 +25,6 @@ LED sub-node required properties:
LED sub-node optional properties:
- label : see Documentation/devicetree/bindings/leds/common.txt
- active-low : Boolean, makes LED active low.
Default : false
- default-state : see
Documentation/devicetree/bindings/leds/common.txt
- linux,default-trigger : see

View file

@ -41,10 +41,6 @@ properties:
pwm-names: true
active-low:
description: For PWMs where the LED is wired to supply rather than ground.
type: boolean
color: true
required:

View file

@ -34,11 +34,6 @@ patternProperties:
Maximum brightness possible for the LED
$ref: /schemas/types.yaml#/definitions/uint32
active-low:
description:
For PWMs where the LED is wired to supply rather than ground.
type: boolean
required:
- pwms
- max-brightness

View file

@ -0,0 +1,54 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/qca,qca808x.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Qualcomm Atheros QCA808X PHY
maintainers:
- Christian Marangi <ansuelsmth@gmail.com>
description:
QCA808X PHYs can have up to 3 LEDs attached.
All 3 LEDs are disabled by default.
2 LEDs have dedicated pins with the 3rd LED having the
double function of Interrupt LEDs/GPIO or additional LED.
By default this special PIN is set to LED function.
allOf:
- $ref: ethernet-phy.yaml#
properties:
compatible:
enum:
- ethernet-phy-id004d.d101
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethernet-phy@0 {
compatible = "ethernet-phy-id004d.d101";
reg = <0>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_WAN;
default-state = "keep";
};
};
};
};

View file

@ -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_device *phydev)
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(struct phy_device *phydev)
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);

View file

@ -3097,6 +3097,7 @@ static int of_phy_led(struct phy_device *phydev,
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;
@ -3114,6 +3115,21 @@ static int of_phy_led(struct phy_device *phydev,
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;

View file

@ -852,6 +852,15 @@ struct phy_plca_status {
bool pst;
};
/* 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_led: An LED driven by the PHY
*
@ -1145,6 +1154,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)