Merge branch 'switch-phy-leds'

Christian Marangi says:

====================
net: Add basic LED support for switch/phy

This is a continue of [1]. It was decided to take a more gradual
approach to implement LEDs support for switch and phy starting with
basic support and then implementing the hw control part when we have all
the prereq done.

This series implements only the brightness_set() and blink_set() ops.
An example of switch implementation is done with qca8k.

For PHY a more generic approach is used with implementing the LED
support in PHY core and with the user (in this case marvell) adding all
the required functions.

Currently we set the default-state as "keep" to not change the default
configuration of the declared LEDs since almost every switch have a
default configuration.

[1] https://lore.kernel.org/lkml/20230216013230.22978-1-ansuelsmth@gmail.com/

Changes in new series v7:
- Drop ethernet-leds schema and add unevaluatedProperties to
  ethernet-controller and ethernet-phy schema
- Drop function-enumerator binding from schema example and DT
- Set devname_mandatory for qca8k leds and assign better name to LEDs
  using the format {slave_mii_bus id}:0{port number}:{color}:{function}
- Add Documentation patch for Correct LEDs naming from Andrew
- Changes in Andrew patch:
  - net: phy: Add a binding for PHY LEDs
    - Convert index from u32 to u8
  - net: phy: phy_device: Call into the PHY driver to set LED brightness
    - Fixup kernel doc
    - Convert index from u32 to u8
  - net: phy: marvell: Add software control of the LEDs
    - Convert index from u32 to u8
  - net: phy: phy_device: Call into the PHY driver to set LED blinking
    - Kernel doc fix
    - Convert index from u32 to u8
  - net: phy: marvell: Implement led_blink_set()
    - Convert index from u32 to u8
Changes in new series v6:
- Add leds-ethernet.yaml to document reg in led node
- Update ethernet-controller and ethernet-phy to follow new leds-ethernet schema
- Fix comments in qca8k-leds.c (at least -> at most)
  (wrong GENMASK for led phy 0 and 4)
- Add review and ack tag from Pavel Machek
- Changes in Andrew patch:
  - leds: Provide stubs for when CLASS_LED & NEW_LEDS are disabled
    - Change LED_CLASS to NEW_LEDS for led_init_default_state_get()
  - net: phy: Add a binding for PHY LEDs
    - Add dependency on LED_CLASS
    - Drop review tag from Michal Kubiak (patch modified)
Changes in new series v5:
- Rebase everything on top of net-next/main
- Add more info on LED probe fail for qca8k
- Drop some additional raw number and move to define in qca8k header
- Add additional info on LED mapping on qca8k regs
- Checks port number in qca8k switch port parse
- Changes in Andrew patch:
  - Add additional patch for stubs when CLASS_LED disabled
  - Drop CLASS_LED dependency for PHYLIB (to fix kbot errors reported)
Changes in new series v4:
- Changes in Andrew patch:
  - net: phy: Add a binding for PHY LEDs:
    - Rename phy_led: led_list to list
    - Rename phy_device: led_list to leds
    - Remove phy_leds_remove() since devm_ should do what is needed
    - Fixup documentation for struct phy_led
    - Fail probe on LED errors
  - net: phy: phy_device: Call into the PHY driver to set LED brightness
    - Moved phy_led::phydev from previous patch to here since it is first
      used here.
  - net: phy: marvell: Implement led_blink_set()
    - Use int instead of unsigned
  - net: phy: marvell: Add software control of the LEDs
    - Use int instead of unsigned
- Add depends on LED_CLASS for qca8k Kconfig
- Fix Makefile for qca8k as suggested
- Move qca8k_setup_led_ctrl to separate header
- Move Documentation from dsa-port to ethernet-controller
- Drop trailing . from Andrew patch fro consistency
Changes in new series v3:
- Move QCA8K_LEDS Kconfig option from tristate to bool
- Use new helper led_init_default_state_get for default-state in qca8k
- Drop cled_qca8k_brightness_get() as there isn't a good way to describe
  the mode the led is currently in
- Rework qca8k_led_brightness_get() to return true only when LED is set
  to always ON
Changes in new series v2:
- Add LEDs node for rb3011
- Fix rb3011 switch node unevaluated properties while running
  make dtbs_check
- Fix a copypaste error in qca8k-leds.c for port 4 required shift
- Drop phy-handle usage for qca8k and use qca8k_port_to_phy()
- Add review tag from Andrew
- Add Christian Marangi SOB in each Andrew patch
- Add extra description for dsa-port stressing that PHY have no access
  and LED are controlled by the related MAC
- Add missing additionalProperties for dsa-port.yaml and ethernet-phy.yaml

Changes from the old v8 series:
- Drop linux,default-trigger set to netdev.
- Dropped every hw control related patch and implement only
  blink_set and brightness_set
- Add default-state to "keep" for each LED node example
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2023-04-19 12:59:17 +01:00
commit 4edd97fbdc
17 changed files with 884 additions and 24 deletions

View File

@ -18,6 +18,8 @@ description:
PHY it is connected to. In this config, an internal mdio-bus is registered and PHY it is connected to. In this config, an internal mdio-bus is registered and
the MDIO master is used for communication. Mixed external and internal the MDIO master is used for communication. Mixed external and internal
mdio-bus configurations are not supported by the hardware. mdio-bus configurations are not supported by the hardware.
Each phy has at most 3 LEDs connected and can be declared
using the standard LEDs structure.
properties: properties:
compatible: compatible:
@ -117,6 +119,7 @@ unevaluatedProperties: false
examples: examples:
- | - |
#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
mdio { mdio {
#address-cells = <1>; #address-cells = <1>;
@ -226,6 +229,25 @@ examples:
label = "lan1"; label = "lan1";
phy-mode = "internal"; phy-mode = "internal";
phy-handle = <&internal_phy_port1>; phy-handle = <&internal_phy_port1>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_WHITE>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
led@1 {
reg = <1>;
color = <LED_COLOR_ID_AMBER>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@2 { port@2 {

View File

@ -222,6 +222,41 @@ properties:
required: required:
- speed - speed
leds:
description:
Describes the LEDs associated by Ethernet Controller.
These LEDs are not integrated in the PHY and PHY doesn't have any
control on them. Ethernet Controller regs are used to control
these defined LEDs.
type: object
properties:
'#address-cells':
const: 1
'#size-cells':
const: 0
patternProperties:
'^led@[a-f0-9]+$':
$ref: /schemas/leds/common.yaml#
properties:
reg:
maxItems: 1
description:
This define the LED index in the PHY or the MAC. It's really
driver dependent and required for ports that define multiple
LED for the same port.
required:
- reg
unevaluatedProperties: false
additionalProperties: false
dependencies: dependencies:
pcs-handle-names: [pcs-handle] pcs-handle-names: [pcs-handle]

View File

@ -197,6 +197,35 @@ properties:
PHY's that have configurable TX internal delays. If this property is PHY's that have configurable TX internal delays. If this property is
present then the PHY applies the TX delay. present then the PHY applies the TX delay.
leds:
type: object
properties:
'#address-cells':
const: 1
'#size-cells':
const: 0
patternProperties:
'^led@[a-f0-9]+$':
$ref: /schemas/leds/common.yaml#
properties:
reg:
maxItems: 1
description:
This define the LED index in the PHY or the MAC. It's really
driver dependent and required for ports that define multiple
LED for the same port.
required:
- reg
unevaluatedProperties: false
additionalProperties: false
required: required:
- reg - reg
@ -204,6 +233,8 @@ additionalProperties: true
examples: examples:
- | - |
#include <dt-bindings/leds/common.h>
ethernet { ethernet {
#address-cells = <1>; #address-cells = <1>;
#size-cells = <0>; #size-cells = <0>;
@ -219,5 +250,17 @@ examples:
reset-gpios = <&gpio1 4 1>; reset-gpios = <&gpio1 4 1>;
reset-assert-us = <1000>; reset-assert-us = <1000>;
reset-deassert-us = <2000>; reset-deassert-us = <2000>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_WHITE>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
}; };

View File

@ -70,3 +70,33 @@ Good: "platform:*:charging" (allwinner sun50i)
* Screen * Screen
Good: ":backlight" (Motorola Droid 4) Good: ":backlight" (Motorola Droid 4)
* Ethernet LEDs
Currently two types of Network LEDs are support, those controlled by
the PHY and those by the MAC. In theory both can be present at the
same time for one Linux netdev, hence the names need to differ between
MAC and PHY.
Do not use the netdev name, such as eth0, enp1s0. These are not stable
and are not unique. They also don't differentiate between MAC and PHY.
** MAC LEDs
Good: f1070000.ethernet:white:WAN
Good: mdio_mux-0.1:00:green:left
Good: 0000:02:00.0:yellow:top
The first part must uniquely name the MAC controller. Then follows the
colour. WAN/LAN should be used for a single LED. If there are
multiple LEDs, use left/right, or top/bottom to indicate their
position on the RJ45 socket.
** PHY LEDs
Good: f1072004.mdio-mii:00: white:WAN
Good: !mdio-mux!mdio@2!switch@0!mdio:01:green:right
Good: r8169-0-200:00:yellow:bottom
The first part must uniquely name the PHY. This often means uniquely
identifying the MDIO bus controller, and the address on the bus.

View File

@ -20,6 +20,7 @@
/dts-v1/; /dts-v1/;
#include <dt-bindings/input/input.h> #include <dt-bindings/input/input.h>
#include <dt-bindings/interrupt-controller/irq.h> #include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/leds/common.h>
#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/gpio/gpio.h>
#include "armada-370.dtsi" #include "armada-370.dtsi"
@ -135,6 +136,17 @@
pinctrl-names = "default"; pinctrl-names = "default";
phy0: ethernet-phy@0 { phy0: ethernet-phy@0 {
reg = <0>; reg = <0>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_WHITE>;
function = LED_FUNCTION_WAN;
default-state = "keep";
};
};
}; };
switch: switch@10 { switch: switch@10 {

View File

@ -38,8 +38,6 @@
switch0: switch@10 { switch0: switch@10 {
compatible = "qca,qca8337"; compatible = "qca,qca8337";
#address-cells = <1>;
#size-cells = <0>;
dsa,member = <0 0>; dsa,member = <0 0>;
@ -67,26 +65,86 @@
port@1 { port@1 {
reg = <1>; reg = <1>;
label = "sw1"; label = "sw1";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@2 { port@2 {
reg = <2>; reg = <2>;
label = "sw2"; label = "sw2";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@3 { port@3 {
reg = <3>; reg = <3>;
label = "sw3"; label = "sw3";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@4 { port@4 {
reg = <4>; reg = <4>;
label = "sw4"; label = "sw4";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@5 { port@5 {
reg = <5>; reg = <5>;
label = "sw5"; label = "sw5";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
}; };
}; };
@ -105,8 +163,6 @@
switch1: switch@14 { switch1: switch@14 {
compatible = "qca,qca8337"; compatible = "qca,qca8337";
#address-cells = <1>;
#size-cells = <0>;
dsa,member = <1 0>; dsa,member = <1 0>;
@ -134,26 +190,86 @@
port@1 { port@1 {
reg = <1>; reg = <1>;
label = "sw6"; label = "sw6";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@2 { port@2 {
reg = <2>; reg = <2>;
label = "sw7"; label = "sw7";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@3 { port@3 {
reg = <3>; reg = <3>;
label = "sw8"; label = "sw8";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@4 { port@4 {
reg = <4>; reg = <4>;
label = "sw9"; label = "sw9";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
port@5 { port@5 {
reg = <5>; reg = <5>;
label = "sw10"; label = "sw10";
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
}; };
}; };
}; };

View File

@ -15,3 +15,11 @@ config NET_DSA_QCA8K
help help
This enables support for the Qualcomm Atheros QCA8K Ethernet This enables support for the Qualcomm Atheros QCA8K Ethernet
switch chips. switch chips.
config NET_DSA_QCA8K_LEDS_SUPPORT
bool "Qualcomm Atheros QCA8K Ethernet switch family LEDs support"
depends on NET_DSA_QCA8K
depends on LEDS_CLASS
help
This enabled support for LEDs present on the Qualcomm Atheros
QCA8K Ethernet switch chips.

View File

@ -2,3 +2,6 @@
obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o
obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
qca8k-y += qca8k-common.o qca8k-8xxx.o qca8k-y += qca8k-common.o qca8k-8xxx.o
ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
qca8k-y += qca8k-leds.o
endif

View File

@ -22,6 +22,7 @@
#include <linux/dsa/tag_qca.h> #include <linux/dsa/tag_qca.h>
#include "qca8k.h" #include "qca8k.h"
#include "qca8k_leds.h"
static void static void
qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
@ -772,21 +773,6 @@ err_clear_skb:
return ret; return ret;
} }
static u32
qca8k_port_to_phy(int port)
{
/* From Andrew Lunn:
* Port 0 has no internal phy.
* Port 1 has an internal PHY at MDIO address 0.
* Port 2 has an internal PHY at MDIO address 1.
* ...
* Port 5 has an internal PHY at MDIO address 4.
* Port 6 has no internal PHY.
*/
return port - 1;
}
static int static int
qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask) qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
{ {
@ -1797,6 +1783,10 @@ qca8k_setup(struct dsa_switch *ds)
if (ret) if (ret)
return ret; return ret;
ret = qca8k_setup_led_ctrl(priv);
if (ret)
return ret;
qca8k_setup_pcs(priv, &priv->pcs_port_0, 0); qca8k_setup_pcs(priv, &priv->pcs_port_0, 0);
qca8k_setup_pcs(priv, &priv->pcs_port_6, 6); qca8k_setup_pcs(priv, &priv->pcs_port_6, 6);

View File

@ -0,0 +1,277 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/regmap.h>
#include <net/dsa.h>
#include "qca8k.h"
#include "qca8k_leds.h"
static int
qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
{
switch (port_num) {
case 0:
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
break;
case 1:
case 2:
case 3:
/* Port 123 are controlled on a different reg */
reg_info->reg = QCA8K_LED_CTRL3_REG;
reg_info->shift = QCA8K_LED_PHY123_PATTERN_EN_SHIFT(port_num, led_num);
break;
case 4:
reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
break;
default:
return -EINVAL;
}
return 0;
}
static int
qca8k_led_brightness_set(struct qca8k_led *led,
enum led_brightness brightness)
{
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 mask, val;
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
val = QCA8K_LED_ALWAYS_OFF;
if (brightness)
val = QCA8K_LED_ALWAYS_ON;
/* HW regs to control brightness is special and port 1-2-3
* are placed in a different reg.
*
* To control port 0 brightness:
* - the 2 bit (15, 14) of:
* - QCA8K_LED_CTRL0_REG for led1
* - QCA8K_LED_CTRL1_REG for led2
* - QCA8K_LED_CTRL2_REG for led3
*
* To control port 4:
* - the 2 bit (31, 30) of:
* - QCA8K_LED_CTRL0_REG for led1
* - QCA8K_LED_CTRL1_REG for led2
* - QCA8K_LED_CTRL2_REG for led3
*
* To control port 1:
* - the 2 bit at (9, 8) of QCA8K_LED_CTRL3_REG are used for led1
* - the 2 bit at (11, 10) of QCA8K_LED_CTRL3_REG are used for led2
* - the 2 bit at (13, 12) of QCA8K_LED_CTRL3_REG are used for led3
*
* To control port 2:
* - the 2 bit at (15, 14) of QCA8K_LED_CTRL3_REG are used for led1
* - the 2 bit at (17, 16) of QCA8K_LED_CTRL3_REG are used for led2
* - the 2 bit at (19, 18) of QCA8K_LED_CTRL3_REG are used for led3
*
* To control port 3:
* - the 2 bit at (21, 20) of QCA8K_LED_CTRL3_REG are used for led1
* - the 2 bit at (23, 22) of QCA8K_LED_CTRL3_REG are used for led2
* - the 2 bit at (25, 24) of QCA8K_LED_CTRL3_REG are used for led3
*
* To abstract this and have less code, we use the port and led numm
* to calculate the shift and the correct reg due to this problem of
* not having a 1:1 map of LED with the regs.
*/
if (led->port_num == 0 || led->port_num == 4) {
mask = QCA8K_LED_PATTERN_EN_MASK;
val <<= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
return regmap_update_bits(priv->regmap, reg_info.reg,
mask << reg_info.shift,
val << reg_info.shift);
}
static int
qca8k_cled_brightness_set_blocking(struct led_classdev *ldev,
enum led_brightness brightness)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
return qca8k_led_brightness_set(led, brightness);
}
static enum led_brightness
qca8k_led_brightness_get(struct qca8k_led *led)
{
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
u32 val;
int ret;
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
ret = regmap_read(priv->regmap, reg_info.reg, &val);
if (ret)
return 0;
val >>= reg_info.shift;
if (led->port_num == 0 || led->port_num == 4) {
val &= QCA8K_LED_PATTERN_EN_MASK;
val >>= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
/* Assume brightness ON only when the LED is set to always ON */
return val == QCA8K_LED_ALWAYS_ON;
}
static int
qca8k_cled_blink_set(struct led_classdev *ldev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
u32 mask, val = QCA8K_LED_ALWAYS_BLINK_4HZ;
struct qca8k_led_pattern_en reg_info;
struct qca8k_priv *priv = led->priv;
if (*delay_on == 0 && *delay_off == 0) {
*delay_on = 125;
*delay_off = 125;
}
if (*delay_on != 125 || *delay_off != 125) {
/* The hardware only supports blinking at 4Hz. Fall back
* to software implementation in other cases.
*/
return -EINVAL;
}
qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
if (led->port_num == 0 || led->port_num == 4) {
mask = QCA8K_LED_PATTERN_EN_MASK;
val <<= QCA8K_LED_PATTERN_EN_SHIFT;
} else {
mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
}
regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
val << reg_info.shift);
return 0;
}
static int
qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
{
struct fwnode_handle *led = NULL, *leds = NULL;
struct led_init_data init_data = { };
struct dsa_switch *ds = priv->ds;
enum led_default_state state;
struct qca8k_led *port_led;
int led_num, led_index;
int ret;
leds = fwnode_get_named_child_node(port, "leds");
if (!leds) {
dev_dbg(priv->dev, "No Leds node specified in device tree for port %d!\n",
port_num);
return 0;
}
fwnode_for_each_child_node(leds, led) {
/* Reg represent the led number of the port.
* Each port can have at most 3 leds attached
* Commonly:
* 1. is gigabit led
* 2. is mbit led
* 3. additional status led
*/
if (fwnode_property_read_u32(led, "reg", &led_num))
continue;
if (led_num >= QCA8K_LED_PORT_COUNT) {
dev_warn(priv->dev, "Invalid LED reg %d defined for port %d",
led_num, port_num);
continue;
}
led_index = QCA8K_LED_PORT_INDEX(port_num, led_num);
port_led = &priv->ports_led[led_index];
port_led->port_num = port_num;
port_led->led_num = led_num;
port_led->priv = priv;
state = led_init_default_state_get(led);
switch (state) {
case LEDS_DEFSTATE_ON:
port_led->cdev.brightness = 1;
qca8k_led_brightness_set(port_led, 1);
break;
case LEDS_DEFSTATE_KEEP:
port_led->cdev.brightness =
qca8k_led_brightness_get(port_led);
break;
default:
port_led->cdev.brightness = 0;
qca8k_led_brightness_set(port_led, 0);
}
port_led->cdev.max_brightness = 1;
port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
port_led->cdev.blink_set = qca8k_cled_blink_set;
init_data.default_label = ":port";
init_data.fwnode = led;
init_data.devname_mandatory = true;
init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d", ds->slave_mii_bus->id,
port_num);
if (!init_data.devicename)
return -ENOMEM;
ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data);
if (ret)
dev_warn(priv->dev, "Failed to init LED %d for port %d", led_num, port_num);
kfree(init_data.devicename);
}
return 0;
}
int
qca8k_setup_led_ctrl(struct qca8k_priv *priv)
{
struct fwnode_handle *ports, *port;
int port_num;
int ret;
ports = device_get_named_child_node(priv->dev, "ports");
if (!ports) {
dev_info(priv->dev, "No ports node specified in device tree!");
return 0;
}
fwnode_for_each_child_node(ports, port) {
if (fwnode_property_read_u32(port, "reg", &port_num))
continue;
/* Skip checking for CPU port 0 and CPU port 6 as not supported */
if (port_num == 0 || port_num == 6)
continue;
/* Each port can have at most 3 different leds attached.
* Switch port starts from 0 to 6, but port 0 and 6 are CPU
* port. The port index needs to be decreased by one to identify
* the correct port for LED setup.
*/
ret = qca8k_parse_port_leds(priv, port, qca8k_port_to_phy(port_num));
if (ret)
return ret;
}
return 0;
}

View File

@ -11,6 +11,7 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/dsa/tag_qca.h> #include <linux/dsa/tag_qca.h>
#define QCA8K_ETHERNET_MDIO_PRIORITY 7 #define QCA8K_ETHERNET_MDIO_PRIORITY 7
@ -85,6 +86,51 @@
#define QCA8K_MDIO_MASTER_DATA(x) FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x) #define QCA8K_MDIO_MASTER_DATA(x) FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x)
#define QCA8K_MDIO_MASTER_MAX_PORTS 5 #define QCA8K_MDIO_MASTER_MAX_PORTS 5
#define QCA8K_MDIO_MASTER_MAX_REG 32 #define QCA8K_MDIO_MASTER_MAX_REG 32
/* LED control register */
#define QCA8K_LED_PORT_COUNT 3
#define QCA8K_LED_COUNT ((QCA8K_NUM_PORTS - QCA8K_NUM_CPU_PORTS) * QCA8K_LED_PORT_COUNT)
#define QCA8K_LED_RULE_COUNT 6
#define QCA8K_LED_RULE_MAX 11
#define QCA8K_LED_PORT_INDEX(_phy, _led) (((_phy) * QCA8K_LED_PORT_COUNT) + (_led))
#define QCA8K_LED_PHY123_PATTERN_EN_SHIFT(_phy, _led) ((((_phy) - 1) * 6) + 8 + (2 * (_led)))
#define QCA8K_LED_PHY123_PATTERN_EN_MASK GENMASK(1, 0)
#define QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT 0
#define QCA8K_LED_PHY4_CONTROL_RULE_SHIFT 16
#define QCA8K_LED_CTRL_REG(_i) (0x050 + (_i) * 4)
#define QCA8K_LED_CTRL0_REG 0x50
#define QCA8K_LED_CTRL1_REG 0x54
#define QCA8K_LED_CTRL2_REG 0x58
#define QCA8K_LED_CTRL3_REG 0x5C
#define QCA8K_LED_CTRL_SHIFT(_i) (((_i) % 2) * 16)
#define QCA8K_LED_CTRL_MASK GENMASK(15, 0)
#define QCA8K_LED_RULE_MASK GENMASK(13, 0)
#define QCA8K_LED_BLINK_FREQ_MASK GENMASK(1, 0)
#define QCA8K_LED_BLINK_FREQ_SHITF 0
#define QCA8K_LED_BLINK_2HZ 0
#define QCA8K_LED_BLINK_4HZ 1
#define QCA8K_LED_BLINK_8HZ 2
#define QCA8K_LED_BLINK_AUTO 3
#define QCA8K_LED_LINKUP_OVER_MASK BIT(2)
#define QCA8K_LED_TX_BLINK_MASK BIT(4)
#define QCA8K_LED_RX_BLINK_MASK BIT(5)
#define QCA8K_LED_COL_BLINK_MASK BIT(7)
#define QCA8K_LED_LINK_10M_EN_MASK BIT(8)
#define QCA8K_LED_LINK_100M_EN_MASK BIT(9)
#define QCA8K_LED_LINK_1000M_EN_MASK BIT(10)
#define QCA8K_LED_POWER_ON_LIGHT_MASK BIT(11)
#define QCA8K_LED_HALF_DUPLEX_MASK BIT(12)
#define QCA8K_LED_FULL_DUPLEX_MASK BIT(13)
#define QCA8K_LED_PATTERN_EN_MASK GENMASK(15, 14)
#define QCA8K_LED_PATTERN_EN_SHIFT 14
#define QCA8K_LED_ALWAYS_OFF 0
#define QCA8K_LED_ALWAYS_BLINK_4HZ 1
#define QCA8K_LED_ALWAYS_ON 2
#define QCA8K_LED_RULE_CONTROLLED 3
#define QCA8K_GOL_MAC_ADDR0 0x60 #define QCA8K_GOL_MAC_ADDR0 0x60
#define QCA8K_GOL_MAC_ADDR1 0x64 #define QCA8K_GOL_MAC_ADDR1 0x64
#define QCA8K_MAX_FRAME_SIZE 0x78 #define QCA8K_MAX_FRAME_SIZE 0x78
@ -382,6 +428,19 @@ struct qca8k_pcs {
int port; int port;
}; };
struct qca8k_led_pattern_en {
u32 reg;
u8 shift;
};
struct qca8k_led {
u8 port_num;
u8 led_num;
u16 old_rule;
struct qca8k_priv *priv;
struct led_classdev cdev;
};
struct qca8k_priv { struct qca8k_priv {
u8 switch_id; u8 switch_id;
u8 switch_revision; u8 switch_revision;
@ -406,6 +465,7 @@ struct qca8k_priv {
struct qca8k_pcs pcs_port_0; struct qca8k_pcs pcs_port_0;
struct qca8k_pcs pcs_port_6; struct qca8k_pcs pcs_port_6;
const struct qca8k_match_data *info; const struct qca8k_match_data *info;
struct qca8k_led ports_led[QCA8K_LED_COUNT];
}; };
struct qca8k_mib_desc { struct qca8k_mib_desc {
@ -421,6 +481,20 @@ struct qca8k_fdb {
u8 mac[6]; u8 mac[6];
}; };
static inline u32 qca8k_port_to_phy(int port)
{
/* From Andrew Lunn:
* Port 0 has no internal phy.
* Port 1 has an internal PHY at MDIO address 0.
* Port 2 has an internal PHY at MDIO address 1.
* ...
* Port 5 has an internal PHY at MDIO address 4.
* Port 6 has no internal PHY.
*/
return port - 1;
}
/* Common setup function */ /* Common setup function */
extern const struct qca8k_mib_desc ar8327_mib[]; extern const struct qca8k_mib_desc ar8327_mib[];
extern const struct regmap_access_table qca8k_readable_table; extern const struct regmap_access_table qca8k_readable_table;

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __QCA8K_LEDS_H
#define __QCA8K_LEDS_H
/* Leds Support function */
#ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
int qca8k_setup_led_ctrl(struct qca8k_priv *priv);
#else
static inline int qca8k_setup_led_ctrl(struct qca8k_priv *priv)
{
return 0;
}
#endif
#endif /* __QCA8K_LEDS_H */

View File

@ -18,6 +18,7 @@ menuconfig PHYLIB
depends on NETDEVICES depends on NETDEVICES
select MDIO_DEVICE select MDIO_DEVICE
select MDIO_DEVRES select MDIO_DEVRES
depends on LEDS_CLASS || LEDS_CLASS=n
help help
Ethernet controllers are usually attached to PHY Ethernet controllers are usually attached to PHY
devices. This option provides infrastructure for devices. This option provides infrastructure for

View File

@ -144,11 +144,15 @@
/* WOL Event Interrupt Enable */ /* WOL Event Interrupt Enable */
#define MII_88E1318S_PHY_CSIER_WOL_EIE BIT(7) #define MII_88E1318S_PHY_CSIER_WOL_EIE BIT(7)
/* LED Timer Control Register */ #define MII_88E1318S_PHY_LED_FUNC 0x10
#define MII_88E1318S_PHY_LED_TCR 0x12 #define MII_88E1318S_PHY_LED_FUNC_OFF (0x8)
#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15) #define MII_88E1318S_PHY_LED_FUNC_ON (0x9)
#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7) #define MII_88E1318S_PHY_LED_FUNC_HI_Z (0xa)
#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11) #define MII_88E1318S_PHY_LED_FUNC_BLINK (0xb)
#define MII_88E1318S_PHY_LED_TCR 0x12
#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11)
/* Magic Packet MAC address registers */ /* Magic Packet MAC address registers */
#define MII_88E1318S_PHY_MAGIC_PACKET_WORD2 0x17 #define MII_88E1318S_PHY_MAGIC_PACKET_WORD2 0x17
@ -2832,6 +2836,63 @@ static int marvell_hwmon_probe(struct phy_device *phydev)
} }
#endif #endif
static int m88e1318_led_brightness_set(struct phy_device *phydev,
u8 index, enum led_brightness value)
{
int reg;
reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
MII_88E1318S_PHY_LED_FUNC);
if (reg < 0)
return reg;
switch (index) {
case 0:
case 1:
case 2:
reg &= ~(0xf << (4 * index));
if (value == LED_OFF)
reg |= MII_88E1318S_PHY_LED_FUNC_OFF << (4 * index);
else
reg |= MII_88E1318S_PHY_LED_FUNC_ON << (4 * index);
break;
default:
return -EINVAL;
}
return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
MII_88E1318S_PHY_LED_FUNC, reg);
}
static int m88e1318_led_blink_set(struct phy_device *phydev, u8 index,
unsigned long *delay_on,
unsigned long *delay_off)
{
int reg;
reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
MII_88E1318S_PHY_LED_FUNC);
if (reg < 0)
return reg;
switch (index) {
case 0:
case 1:
case 2:
reg &= ~(0xf << (4 * index));
reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
/* Reset default is 84ms */
*delay_on = 84 / 2;
*delay_off = 84 / 2;
break;
default:
return -EINVAL;
}
return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
MII_88E1318S_PHY_LED_FUNC, reg);
}
static int marvell_probe(struct phy_device *phydev) static int marvell_probe(struct phy_device *phydev)
{ {
struct marvell_priv *priv; struct marvell_priv *priv;
@ -3081,6 +3142,8 @@ static struct phy_driver marvell_drivers[] = {
.get_sset_count = marvell_get_sset_count, .get_sset_count = marvell_get_sset_count,
.get_strings = marvell_get_strings, .get_strings = marvell_get_strings,
.get_stats = marvell_get_stats, .get_stats = marvell_get_stats,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
}, },
{ {
.phy_id = MARVELL_PHY_ID_88E1145, .phy_id = MARVELL_PHY_ID_88E1145,
@ -3187,6 +3250,8 @@ static struct phy_driver marvell_drivers[] = {
.cable_test_start = marvell_vct7_cable_test_start, .cable_test_start = marvell_vct7_cable_test_start,
.cable_test_tdr_start = marvell_vct5_cable_test_tdr_start, .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
.cable_test_get_status = marvell_vct7_cable_test_get_status, .cable_test_get_status = marvell_vct7_cable_test_get_status,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
}, },
{ {
.phy_id = MARVELL_PHY_ID_88E1540, .phy_id = MARVELL_PHY_ID_88E1540,
@ -3213,6 +3278,8 @@ static struct phy_driver marvell_drivers[] = {
.cable_test_start = marvell_vct7_cable_test_start, .cable_test_start = marvell_vct7_cable_test_start,
.cable_test_tdr_start = marvell_vct5_cable_test_tdr_start, .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
.cable_test_get_status = marvell_vct7_cable_test_get_status, .cable_test_get_status = marvell_vct7_cable_test_get_status,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
}, },
{ {
.phy_id = MARVELL_PHY_ID_88E1545, .phy_id = MARVELL_PHY_ID_88E1545,
@ -3239,6 +3306,8 @@ static struct phy_driver marvell_drivers[] = {
.cable_test_start = marvell_vct7_cable_test_start, .cable_test_start = marvell_vct7_cable_test_start,
.cable_test_tdr_start = marvell_vct5_cable_test_tdr_start, .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
.cable_test_get_status = marvell_vct7_cable_test_get_status, .cable_test_get_status = marvell_vct7_cable_test_get_status,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
}, },
{ {
.phy_id = MARVELL_PHY_ID_88E3016, .phy_id = MARVELL_PHY_ID_88E3016,
@ -3380,6 +3449,8 @@ static struct phy_driver marvell_drivers[] = {
.get_stats = marvell_get_stats, .get_stats = marvell_get_stats,
.get_tunable = m88e1540_get_tunable, .get_tunable = m88e1540_get_tunable,
.set_tunable = m88e1540_set_tunable, .set_tunable = m88e1540_set_tunable,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
}, },
}; };

View File

@ -19,10 +19,12 @@
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mdio.h> #include <linux/mdio.h>
#include <linux/mii.h> #include <linux/mii.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/phy.h> #include <linux/phy.h>
#include <linux/phy_led_triggers.h> #include <linux/phy_led_triggers.h>
@ -674,6 +676,7 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
device_initialize(&mdiodev->dev); device_initialize(&mdiodev->dev);
dev->state = PHY_DOWN; dev->state = PHY_DOWN;
INIT_LIST_HEAD(&dev->leds);
mutex_init(&dev->lock); mutex_init(&dev->lock);
INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
@ -2988,6 +2991,101 @@ static bool phy_drv_supports_irq(struct phy_driver *phydrv)
return phydrv->config_intr && phydrv->handle_interrupt; return phydrv->config_intr && phydrv->handle_interrupt;
} }
static int phy_led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct phy_led *phyled = to_phy_led(led_cdev);
struct phy_device *phydev = phyled->phydev;
int err;
mutex_lock(&phydev->lock);
err = phydev->drv->led_brightness_set(phydev, phyled->index, value);
mutex_unlock(&phydev->lock);
return err;
}
static int phy_led_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct phy_led *phyled = to_phy_led(led_cdev);
struct phy_device *phydev = phyled->phydev;
int err;
mutex_lock(&phydev->lock);
err = phydev->drv->led_blink_set(phydev, phyled->index,
delay_on, delay_off);
mutex_unlock(&phydev->lock);
return err;
}
static int of_phy_led(struct phy_device *phydev,
struct device_node *led)
{
struct device *dev = &phydev->mdio.dev;
struct led_init_data init_data = {};
struct led_classdev *cdev;
struct phy_led *phyled;
int err;
phyled = devm_kzalloc(dev, sizeof(*phyled), GFP_KERNEL);
if (!phyled)
return -ENOMEM;
cdev = &phyled->led_cdev;
phyled->phydev = phydev;
err = of_property_read_u8(led, "reg", &phyled->index);
if (err)
return err;
if (phydev->drv->led_brightness_set)
cdev->brightness_set_blocking = phy_led_set_brightness;
if (phydev->drv->led_blink_set)
cdev->blink_set = phy_led_blink_set;
cdev->max_brightness = 1;
init_data.devicename = dev_name(&phydev->mdio.dev);
init_data.fwnode = of_fwnode_handle(led);
init_data.devname_mandatory = true;
err = devm_led_classdev_register_ext(dev, cdev, &init_data);
if (err)
return err;
list_add(&phyled->list, &phydev->leds);
return 0;
}
static int of_phy_leds(struct phy_device *phydev)
{
struct device_node *node = phydev->mdio.dev.of_node;
struct device_node *leds, *led;
int err;
if (!IS_ENABLED(CONFIG_OF_MDIO))
return 0;
if (!node)
return 0;
leds = of_get_child_by_name(node, "leds");
if (!leds)
return 0;
for_each_available_child_of_node(leds, led) {
err = of_phy_led(phydev, led);
if (err) {
of_node_put(led);
return err;
}
}
return 0;
}
/** /**
* fwnode_mdio_find_device - Given a fwnode, find the mdio_device * fwnode_mdio_find_device - Given a fwnode, find the mdio_device
* @fwnode: pointer to the mdio_device's fwnode * @fwnode: pointer to the mdio_device's fwnode
@ -3183,6 +3281,11 @@ static int phy_probe(struct device *dev)
/* Set the state to READY by default */ /* Set the state to READY by default */
phydev->state = PHY_READY; phydev->state = PHY_READY;
/* Get the LEDs from the device tree, and instantiate standard
* LEDs for them.
*/
err = of_phy_leds(phydev);
out: out:
/* Re-assert the reset signal on error */ /* Re-assert the reset signal on error */
if (err) if (err)

View File

@ -82,7 +82,15 @@ struct led_init_data {
bool devname_mandatory; bool devname_mandatory;
}; };
#if IS_ENABLED(CONFIG_NEW_LEDS)
enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode); enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode);
#else
static inline enum led_default_state
led_init_default_state_get(struct fwnode_handle *fwnode)
{
return LEDS_DEFSTATE_OFF;
}
#endif
struct led_hw_trigger_type { struct led_hw_trigger_type {
int dummy; int dummy;
@ -217,9 +225,19 @@ static inline int led_classdev_register(struct device *parent,
return led_classdev_register_ext(parent, led_cdev, NULL); return led_classdev_register_ext(parent, led_cdev, NULL);
} }
#if IS_ENABLED(CONFIG_LEDS_CLASS)
int devm_led_classdev_register_ext(struct device *parent, int devm_led_classdev_register_ext(struct device *parent,
struct led_classdev *led_cdev, struct led_classdev *led_cdev,
struct led_init_data *init_data); struct led_init_data *init_data);
#else
static inline int
devm_led_classdev_register_ext(struct device *parent,
struct led_classdev *led_cdev,
struct led_init_data *init_data)
{
return 0;
}
#endif
static inline int devm_led_classdev_register(struct device *parent, static inline int devm_led_classdev_register(struct device *parent,
struct led_classdev *led_cdev) struct led_classdev *led_cdev)

View File

@ -14,6 +14,7 @@
#include <linux/compiler.h> #include <linux/compiler.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/ethtool.h> #include <linux/ethtool.h>
#include <linux/leds.h>
#include <linux/linkmode.h> #include <linux/linkmode.h>
#include <linux/netlink.h> #include <linux/netlink.h>
#include <linux/mdio.h> #include <linux/mdio.h>
@ -600,6 +601,7 @@ struct macsec_ops;
* @phy_num_led_triggers: Number of triggers in @phy_led_triggers * @phy_num_led_triggers: Number of triggers in @phy_led_triggers
* @led_link_trigger: LED trigger for link up/down * @led_link_trigger: LED trigger for link up/down
* @last_triggered: last LED trigger for link speed * @last_triggered: last LED trigger for link speed
* @leds: list of PHY LED structures
* @master_slave_set: User requested master/slave configuration * @master_slave_set: User requested master/slave configuration
* @master_slave_get: Current master/slave advertisement * @master_slave_get: Current master/slave advertisement
* @master_slave_state: Current master/slave configuration * @master_slave_state: Current master/slave configuration
@ -699,6 +701,7 @@ struct phy_device {
struct phy_led_trigger *led_link_trigger; struct phy_led_trigger *led_link_trigger;
#endif #endif
struct list_head leds;
/* /*
* Interrupt number for this PHY * Interrupt number for this PHY
@ -834,6 +837,23 @@ struct phy_plca_status {
bool pst; bool pst;
}; };
/**
* struct phy_led: An LED driven by the PHY
*
* @list: List of LEDs
* @phydev: PHY this LED is attached to
* @led_cdev: Standard LED class structure
* @index: Number of the LED
*/
struct phy_led {
struct list_head list;
struct phy_device *phydev;
struct led_classdev led_cdev;
u8 index;
};
#define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
/** /**
* struct phy_driver - Driver structure for a particular PHY type * struct phy_driver - Driver structure for a particular PHY type
* *
@ -1056,6 +1076,27 @@ struct phy_driver {
/** @get_plca_status: Return the current PLCA status info */ /** @get_plca_status: Return the current PLCA status info */
int (*get_plca_status)(struct phy_device *dev, int (*get_plca_status)(struct phy_device *dev,
struct phy_plca_status *plca_st); struct phy_plca_status *plca_st);
/**
* @led_brightness_set: Set a PHY LED brightness. Index
* indicates which of the PHYs led should be set. Value
* follows the standard LED class meaning, e.g. LED_OFF,
* LED_HALF, LED_FULL.
*/
int (*led_brightness_set)(struct phy_device *dev,
u8 index, enum led_brightness value);
/**
* @led_blink_set: Set a PHY LED brightness. Index indicates
* which of the PHYs led should be configured to blink. Delays
* are in milliseconds and if both are zero then a sensible
* default should be chosen. The call should adjust the
* timings in that case and if it can't match the values
* specified exactly.
*/
int (*led_blink_set)(struct phy_device *dev, u8 index,
unsigned long *delay_on,
unsigned long *delay_off);
}; };
#define to_phy_driver(d) container_of(to_mdio_common_driver(d), \ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
struct phy_driver, mdiodrv) struct phy_driver, mdiodrv)