diff mbox series

[net-next] net: phy: dp83822: Add support for PHY LEDs on DP83822

Message ID 20241217-dp83822-leds-v1-1-800b24461013@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [net-next] net: phy: dp83822: Add support for PHY LEDs on DP83822 | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 7 of 7 maintainers
netdev/build_clang fail Errors and warnings before: 5 this patch: 6
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 95 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Dimitri Fedrau Dec. 17, 2024, 9:16 a.m. UTC
The DP83822 supports up to three configurable Light Emitting Diode (LED)
pins: LED_0, LED_1 (GPIO1), COL (GPIO2) and RX_D3 (GPIO3). Several
functions can be multiplexed onto the LEDs for different modes of
operation. LED_0 and COL (GPIO2) use the MLED function. MLED can be routed
to only one of these two pins at a time. Add minimal LED controller driver
supporting the most common uses with the 'netdev' trigger.

Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
---
 drivers/net/phy/dp83822.c | 271 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 269 insertions(+), 2 deletions(-)


---
base-commit: a14a429069bb1a18eb9fe63d68fcaa77dffe0e23
change-id: 20241215-dp83822-leds-bbdea724d726

Best regards,

Comments

Simon Horman Dec. 17, 2024, 4:32 p.m. UTC | #1
On Tue, Dec 17, 2024 at 10:16:03AM +0100, Dimitri Fedrau wrote:
> The DP83822 supports up to three configurable Light Emitting Diode (LED)
> pins: LED_0, LED_1 (GPIO1), COL (GPIO2) and RX_D3 (GPIO3). Several
> functions can be multiplexed onto the LEDs for different modes of
> operation. LED_0 and COL (GPIO2) use the MLED function. MLED can be routed
> to only one of these two pins at a time. Add minimal LED controller driver
> supporting the most common uses with the 'netdev' trigger.
> 
> Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
> ---
>  drivers/net/phy/dp83822.c | 271 +++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 269 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c

...

> +static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index,
> +				      unsigned long rules)
> +{
> +	int mode;
> +
> +	mode = dp83822_led_mode(index, rules);
> +	if (mode < 0)
> +		return mode;
> +
> +	if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2)
> +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> +				      MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG,
> +				      FIELD_PREP(DP83822_MLEDCR_CFG, mode));

...

> +}
> +
> +static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index,
> +				      unsigned long *rules)
> +{
> +	int val;
> +
> +	if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {

Hi Dimitri,

As per the condition near the top of dp83822_led_hw_control_set(), should
this be:

	if (index == DP83822_LED_INDEX_LED_0 ||
	    index == DP83822_LED_INDEX_COL_GPIO2) {

Flagged by W=1 + -Wno-error build with clang-19.

 drivers/net/phy/dp83822.c:1029:39: note: use '|' for a bitwise operation
  1029 |         if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {
       |                                              ^~
       |

...
Andrew Lunn Dec. 17, 2024, 5:13 p.m. UTC | #2
> +static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index,
> +				      unsigned long rules)
> +{
> +	int mode;
> +
> +	mode = dp83822_led_mode(index, rules);
> +	if (mode < 0)
> +		return mode;
> +
> +	if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2)
> +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> +				      MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG,
> +				      FIELD_PREP(DP83822_MLEDCR_CFG, mode));
> +	else if (index == DP83822_LED_INDEX_LED_1_GPIO1)
> +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> +				      MII_DP83822_LEDCFG1,
> +				      DP83822_LEDCFG1_LED1_CTRL,
> +				      FIELD_PREP(DP83822_LEDCFG1_LED1_CTRL,
> +						 mode));
> +	else
> +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> +				      MII_DP83822_LEDCFG1,
> +				      DP83822_LEDCFG1_LED3_CTRL,
> +				      FIELD_PREP(DP83822_LEDCFG1_LED3_CTRL,
> +						 mode));

index is taken direct from DT. Somebody might have:

           leds {
                #address-cells = <1>;
                #size-cells = <0>;

                led@42 {
                    reg = <42>;
                    color = <LED_COLOR_ID_WHITE>;
                    function = LED_FUNCTION_LAN;
                    default-state = "keep";
                };
            };

so you should not assume if it is not 0, 1 or 2, then it must be
3. Please always validate index.


    Andrew

---
pw-bot: cr
Dimitri Fedrau Dec. 17, 2024, 5:54 p.m. UTC | #3
Am Tue, Dec 17, 2024 at 04:32:08PM +0000 schrieb Simon Horman:
> On Tue, Dec 17, 2024 at 10:16:03AM +0100, Dimitri Fedrau wrote:
> > The DP83822 supports up to three configurable Light Emitting Diode (LED)
> > pins: LED_0, LED_1 (GPIO1), COL (GPIO2) and RX_D3 (GPIO3). Several
> > functions can be multiplexed onto the LEDs for different modes of
> > operation. LED_0 and COL (GPIO2) use the MLED function. MLED can be routed
> > to only one of these two pins at a time. Add minimal LED controller driver
> > supporting the most common uses with the 'netdev' trigger.
> > 
> > Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
> > ---
> >  drivers/net/phy/dp83822.c | 271 +++++++++++++++++++++++++++++++++++++++++++++-
> >  1 file changed, 269 insertions(+), 2 deletions(-)
> > 
> > diff --git a/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c
> 
> ...
> 
> > +static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index,
> > +				      unsigned long rules)
> > +{
> > +	int mode;
> > +
> > +	mode = dp83822_led_mode(index, rules);
> > +	if (mode < 0)
> > +		return mode;
> > +
> > +	if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2)
> > +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> > +				      MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG,
> > +				      FIELD_PREP(DP83822_MLEDCR_CFG, mode));
> 
> ...
> 
> > +}
> > +
> > +static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index,
> > +				      unsigned long *rules)
> > +{
> > +	int val;
> > +
> > +	if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {
> 
> Hi Dimitri,
> 
> As per the condition near the top of dp83822_led_hw_control_set(), should
> this be:
> 
> 	if (index == DP83822_LED_INDEX_LED_0 ||
> 	    index == DP83822_LED_INDEX_COL_GPIO2) {
> 
> Flagged by W=1 + -Wno-error build with clang-19.
> 
>  drivers/net/phy/dp83822.c:1029:39: note: use '|' for a bitwise operation
>   1029 |         if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {
>        |                                              ^~
>        |
> 
> ...
Thanks, will fix it.

Best regards,
Dimitri
Dimitri Fedrau Dec. 17, 2024, 5:59 p.m. UTC | #4
Am Tue, Dec 17, 2024 at 06:13:25PM +0100 schrieb Andrew Lunn:
> > +static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index,
> > +				      unsigned long rules)
> > +{
> > +	int mode;
> > +
> > +	mode = dp83822_led_mode(index, rules);
> > +	if (mode < 0)
> > +		return mode;
> > +
> > +	if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2)
> > +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> > +				      MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG,
> > +				      FIELD_PREP(DP83822_MLEDCR_CFG, mode));
> > +	else if (index == DP83822_LED_INDEX_LED_1_GPIO1)
> > +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> > +				      MII_DP83822_LEDCFG1,
> > +				      DP83822_LEDCFG1_LED1_CTRL,
> > +				      FIELD_PREP(DP83822_LEDCFG1_LED1_CTRL,
> > +						 mode));
> > +	else
> > +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> > +				      MII_DP83822_LEDCFG1,
> > +				      DP83822_LEDCFG1_LED3_CTRL,
> > +				      FIELD_PREP(DP83822_LEDCFG1_LED3_CTRL,
> > +						 mode));
> 
> index is taken direct from DT. Somebody might have:
> 
>            leds {
>                 #address-cells = <1>;
>                 #size-cells = <0>;
> 
>                 led@42 {
>                     reg = <42>;
>                     color = <LED_COLOR_ID_WHITE>;
>                     function = LED_FUNCTION_LAN;
>                     default-state = "keep";
>                 };
>             };
> 
> so you should not assume if it is not 0, 1 or 2, then it must be
> 3. Please always validate index.
>
dp83822_of_init_leds does a check that index is 0, 1, 2 or 3. Is this
sufficient ? Otherwise I would validate the index.

Best regards,
Dimitri
kernel test robot Dec. 18, 2024, 8:32 a.m. UTC | #5
Hi Dimitri,

kernel test robot noticed the following build warnings:

[auto build test WARNING on a14a429069bb1a18eb9fe63d68fcaa77dffe0e23]

url:    https://github.com/intel-lab-lkp/linux/commits/Dimitri-Fedrau/net-phy-dp83822-Add-support-for-PHY-LEDs-on-DP83822/20241217-172305
base:   a14a429069bb1a18eb9fe63d68fcaa77dffe0e23
patch link:    https://lore.kernel.org/r/20241217-dp83822-leds-v1-1-800b24461013%40gmail.com
patch subject: [PATCH net-next] net: phy: dp83822: Add support for PHY LEDs on DP83822
config: x86_64-buildonly-randconfig-002-20241218 (https://download.01.org/0day-ci/archive/20241218/202412181638.7XsHSwtp-lkp@intel.com/config)
compiler: clang version 19.1.3 (https://github.com/llvm/llvm-project ab51eccf88f5321e7c60591c5546b254b6afab99)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241218/202412181638.7XsHSwtp-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202412181638.7XsHSwtp-lkp@intel.com/

All warnings (new ones prefixed by >>):

   In file included from drivers/net/phy/dp83822.c:7:
   In file included from include/linux/ethtool.h:18:
   In file included from include/linux/if_ether.h:19:
   In file included from include/linux/skbuff.h:17:
   In file included from include/linux/bvec.h:10:
   In file included from include/linux/highmem.h:8:
   In file included from include/linux/cacheflush.h:5:
   In file included from arch/x86/include/asm/cacheflush.h:5:
   In file included from include/linux/mm.h:2223:
   include/linux/vmstat.h:504:43: warning: arithmetic between different enumeration types ('enum zone_stat_item' and 'enum numa_stat_item') [-Wenum-enum-conversion]
     504 |         return vmstat_text[NR_VM_ZONE_STAT_ITEMS +
         |                            ~~~~~~~~~~~~~~~~~~~~~ ^
     505 |                            item];
         |                            ~~~~
   include/linux/vmstat.h:511:43: warning: arithmetic between different enumeration types ('enum zone_stat_item' and 'enum numa_stat_item') [-Wenum-enum-conversion]
     511 |         return vmstat_text[NR_VM_ZONE_STAT_ITEMS +
         |                            ~~~~~~~~~~~~~~~~~~~~~ ^
     512 |                            NR_VM_NUMA_EVENT_ITEMS +
         |                            ~~~~~~~~~~~~~~~~~~~~~~
   include/linux/vmstat.h:518:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion]
     518 |         return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_"
         |                               ~~~~~~~~~~~ ^ ~~~
   include/linux/vmstat.h:524:43: warning: arithmetic between different enumeration types ('enum zone_stat_item' and 'enum numa_stat_item') [-Wenum-enum-conversion]
     524 |         return vmstat_text[NR_VM_ZONE_STAT_ITEMS +
         |                            ~~~~~~~~~~~~~~~~~~~~~ ^
     525 |                            NR_VM_NUMA_EVENT_ITEMS +
         |                            ~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/phy/dp83822.c:1029:39: warning: use of logical '||' with constant operand [-Wconstant-logical-operand]
    1029 |         if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {
         |                                              ^  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/net/phy/dp83822.c:1029:39: note: use '|' for a bitwise operation
    1029 |         if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {
         |                                              ^~
         |                                              |
   5 warnings generated.


vim +1029 drivers/net/phy/dp83822.c

  1023	
  1024	static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index,
  1025					      unsigned long *rules)
  1026	{
  1027		int val;
  1028	
> 1029		if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {
  1030			val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_MLEDCR);
  1031			if (val < 0)
  1032				return val;
  1033	
  1034			val = FIELD_GET(DP83822_MLEDCR_CFG, val);
  1035		} else {
  1036			val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_LEDCFG1);
  1037			if (val < 0)
  1038				return val;
  1039	
  1040			if (index == DP83822_LED_INDEX_LED_1_GPIO1)
  1041				val = FIELD_GET(DP83822_LEDCFG1_LED1_CTRL, val);
  1042			else
  1043				val = FIELD_GET(DP83822_LEDCFG1_LED3_CTRL, val);
  1044		}
  1045	
  1046		switch (val) {
  1047		case DP83822_LED_FN_LINK:
  1048			*rules = BIT(TRIGGER_NETDEV_LINK);
  1049			break;
  1050		case DP83822_LED_FN_LINK_10_BT:
  1051			*rules = BIT(TRIGGER_NETDEV_LINK_10);
  1052			break;
  1053		case DP83822_LED_FN_LINK_100_BTX:
  1054			*rules = BIT(TRIGGER_NETDEV_LINK_100);
  1055			break;
  1056		case DP83822_LED_FN_FULL_DUPLEX:
  1057			*rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX);
  1058			break;
  1059		case DP83822_LED_FN_TX:
  1060			*rules = BIT(TRIGGER_NETDEV_TX);
  1061			break;
  1062		case DP83822_LED_FN_RX:
  1063			*rules = BIT(TRIGGER_NETDEV_RX);
  1064			break;
  1065		case DP83822_LED_FN_RX_TX:
  1066			*rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);
  1067			break;
  1068		case DP83822_LED_FN_RX_TX_ERR:
  1069			*rules = BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR);
  1070			break;
  1071		case DP83822_LED_FN_LINK_RX_TX:
  1072			*rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) |
  1073				 BIT(TRIGGER_NETDEV_RX);
  1074			break;
  1075		default:
  1076			*rules = 0;
  1077			break;
  1078		}
  1079	
  1080		return 0;
  1081	}
  1082
Dimitri Fedrau Dec. 18, 2024, 8:54 a.m. UTC | #6
Am Tue, Dec 17, 2024 at 06:13:25PM +0100 schrieb Andrew Lunn:
> > +static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index,
> > +				      unsigned long rules)
> > +{
> > +	int mode;
> > +
> > +	mode = dp83822_led_mode(index, rules);
> > +	if (mode < 0)
> > +		return mode;
> > +
> > +	if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2)
> > +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> > +				      MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG,
> > +				      FIELD_PREP(DP83822_MLEDCR_CFG, mode));
> > +	else if (index == DP83822_LED_INDEX_LED_1_GPIO1)
> > +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> > +				      MII_DP83822_LEDCFG1,
> > +				      DP83822_LEDCFG1_LED1_CTRL,
> > +				      FIELD_PREP(DP83822_LEDCFG1_LED1_CTRL,
> > +						 mode));
> > +	else
> > +		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
> > +				      MII_DP83822_LEDCFG1,
> > +				      DP83822_LEDCFG1_LED3_CTRL,
> > +				      FIELD_PREP(DP83822_LEDCFG1_LED3_CTRL,
> > +						 mode));
> 
> index is taken direct from DT. Somebody might have:
> 
>            leds {
>                 #address-cells = <1>;
>                 #size-cells = <0>;
> 
>                 led@42 {
>                     reg = <42>;
>                     color = <LED_COLOR_ID_WHITE>;
>                     function = LED_FUNCTION_LAN;
>                     default-state = "keep";
>                 };
>             };
> 
> so you should not assume if it is not 0, 1 or 2, then it must be
> 3. Please always validate index.
>

By the way. Wouldn't it be helpful adding a u32 max_leds to
struct phy_driver ? Every driver supporting PHY LEDs validates index at the
moment. With max_leds it should be easy to check it in of_phy_leds and
return with an error if index is not valid.

Best regards,
Dimitri
Andrew Lunn Dec. 18, 2024, 5:13 p.m. UTC | #7
> > index is taken direct from DT. Somebody might have:
> > 
> >            leds {
> >                 #address-cells = <1>;
> >                 #size-cells = <0>;
> > 
> >                 led@42 {
> >                     reg = <42>;
> >                     color = <LED_COLOR_ID_WHITE>;
> >                     function = LED_FUNCTION_LAN;
> >                     default-state = "keep";
> >                 };
> >             };
> > 
> > so you should not assume if it is not 0, 1 or 2, then it must be
> > 3. Please always validate index.
> >
> dp83822_of_init_leds does a check that index is 0, 1, 2 or 3. Is this
> sufficient ? Otherwise I would validate the index.

Ah, i missed that. I'm just used to the usual pattern that every PHY
driver has a check for the MAX LEDs in their callback.

	Andrew
Andrew Lunn Dec. 18, 2024, 5:16 p.m. UTC | #8
> By the way. Wouldn't it be helpful adding a u32 max_leds to
> struct phy_driver ? Every driver supporting PHY LEDs validates index at the
> moment. With max_leds it should be easy to check it in of_phy_leds and
> return with an error if index is not valid.

I have been considering it. However, so far developers have been good
at adding the checks, because the first driver had the checks, cargo
cult at its best.

If we are going to add it, we should do it early, before there are too
many PHY drivers which need updating.

	Andrew
Dimitri Fedrau Dec. 18, 2024, 6:17 p.m. UTC | #9
Am Wed, Dec 18, 2024 at 06:16:20PM +0100 schrieb Andrew Lunn:
> > By the way. Wouldn't it be helpful adding a u32 max_leds to
> > struct phy_driver ? Every driver supporting PHY LEDs validates index at the
> > moment. With max_leds it should be easy to check it in of_phy_leds and
> > return with an error if index is not valid.
> 
> I have been considering it. However, so far developers have been good
> at adding the checks, because the first driver had the checks, cargo
> cult at its best.
> 
> If we are going to add it, we should do it early, before there are too
> many PHY drivers which need updating.
>
Another solution without breaking others driver would be to add a
callback in struct phy_driver:
int (*led_validate_index)(struct phy_device *dev, int index)
It should be called in of_phy_led right after reading in reg property:
if (phydev->drv->led_validate_index)
	ret = phydev->drv->led_validate_index(phydev, index);

This would solve another isssue I have. The LED pins of the DP83822 can
be multiplexed. Not all of them have per default a LED function. So I
need to set them up. In dp83822_of_init_leds I iterate over all DT nodes
in leds to get the information which of the pins should output LED
function. Using the callback would eleminate the need for copying code of
functions of_phy_leds and of_phy_led.

I can come up with a patch if you agree. I would add it to the patch
series and then we would also have a example with the DP83822.

Best regards,
Dimitri
Andrew Lunn Dec. 18, 2024, 8:19 p.m. UTC | #10
On Wed, Dec 18, 2024 at 07:17:52PM +0100, Dimitri Fedrau wrote:
> Am Wed, Dec 18, 2024 at 06:16:20PM +0100 schrieb Andrew Lunn:
> > > By the way. Wouldn't it be helpful adding a u32 max_leds to
> > > struct phy_driver ? Every driver supporting PHY LEDs validates index at the
> > > moment. With max_leds it should be easy to check it in of_phy_leds and
> > > return with an error if index is not valid.
> > 
> > I have been considering it. However, so far developers have been good
> > at adding the checks, because the first driver had the checks, cargo
> > cult at its best.
> > 
> > If we are going to add it, we should do it early, before there are too
> > many PHY drivers which need updating.
> >
> Another solution without breaking others driver would be to add a
> callback in struct phy_driver:

Adding the maximum number of LEDs to struct phy_driver will not break
anything. But we would want to remove all the tests for the index
value from the drivers, since they become pointless. That will be
easier to do when there are less drivers which need editing.

> int (*led_validate_index)(struct phy_device *dev, int index)
> It should be called in of_phy_led right after reading in reg property:
> if (phydev->drv->led_validate_index)
> 	ret = phydev->drv->led_validate_index(phydev, index);
> 
> This would solve another isssue I have. The LED pins of the DP83822 can
> be multiplexed. Not all of them have per default a LED function. So I
> need to set them up. In dp83822_of_init_leds I iterate over all DT nodes
> in leds to get the information which of the pins should output LED
> function. Using the callback would eleminate the need for copying code of
> functions of_phy_leds and of_phy_led.

Your hardware is pretty unique. It might be best to keep it in the
driver, until there is a second driver which needs the same. I also
think you need the complete configuration in order to validate it, not
each LED one by one, which your led_validate_index() would provide.

	Andrew
Dimitri Fedrau Dec. 18, 2024, 8:44 p.m. UTC | #11
Am Wed, Dec 18, 2024 at 09:19:47PM +0100 schrieb Andrew Lunn:
> On Wed, Dec 18, 2024 at 07:17:52PM +0100, Dimitri Fedrau wrote:
> > Am Wed, Dec 18, 2024 at 06:16:20PM +0100 schrieb Andrew Lunn:
> > > > By the way. Wouldn't it be helpful adding a u32 max_leds to
> > > > struct phy_driver ? Every driver supporting PHY LEDs validates index at the
> > > > moment. With max_leds it should be easy to check it in of_phy_leds and
> > > > return with an error if index is not valid.
> > > 
> > > I have been considering it. However, so far developers have been good
> > > at adding the checks, because the first driver had the checks, cargo
> > > cult at its best.
> > > 
> > > If we are going to add it, we should do it early, before there are too
> > > many PHY drivers which need updating.
> > >
> > Another solution without breaking others driver would be to add a
> > callback in struct phy_driver:
> 
> Adding the maximum number of LEDs to struct phy_driver will not break
> anything. But we would want to remove all the tests for the index
> value from the drivers, since they become pointless. That will be
> easier to do when there are less drivers which need editing.
>
Just adding the number will not break anything, but probably the test
that should be implemented in of_phy_led. Except the test gets skipped if
maximum number of LEDs is not set(zero). Otherwise we would have to
change all existing drivers to set the maximum numbers of LEDs.

> > int (*led_validate_index)(struct phy_device *dev, int index)
> > It should be called in of_phy_led right after reading in reg property:
> > if (phydev->drv->led_validate_index)
> > 	ret = phydev->drv->led_validate_index(phydev, index);
> > 
> > This would solve another isssue I have. The LED pins of the DP83822 can
> > be multiplexed. Not all of them have per default a LED function. So I
> > need to set them up. In dp83822_of_init_leds I iterate over all DT nodes
> > in leds to get the information which of the pins should output LED
> > function. Using the callback would eleminate the need for copying code of
> > functions of_phy_leds and of_phy_led.
> 
> Your hardware is pretty unique. It might be best to keep it in the
> driver, until there is a second driver which needs the same. I also
> think you need the complete configuration in order to validate it, not
> each LED one by one, which your led_validate_index() would provide.
>
I have implemented LED support for marvell-88q2xxx.c which I wanted to
upstream after LED support for DP83822 is done. There I have the same
issue.
You are right, I need the whole configuration. But at the moment I read
it in the same way as it is done in of_phy_led and save indices to
bool led_pin_enable[DP83822_MAX_LED_PINS] and set them up later in
dp83822_config_init.

How do we proceed ? Implement maximum numbers of LEDs ? Skip the
validation index callback when upstreaming LED support for
marvell-88q2xxx.c ?

Best regards,
Dimitri
diff mbox series

Patch

diff --git a/drivers/net/phy/dp83822.c b/drivers/net/phy/dp83822.c
index 334c17a68edd7c2a0f707b3ccafaa7a870818fbe..66651dbbd944fe1469057e9ef2f2057aedfb9e29 100644
--- a/drivers/net/phy/dp83822.c
+++ b/drivers/net/phy/dp83822.c
@@ -30,6 +30,9 @@ 
 #define MII_DP83822_FCSCR	0x14
 #define MII_DP83822_RCSR	0x17
 #define MII_DP83822_RESET_CTRL	0x1f
+#define MII_DP83822_MLEDCR	0x25
+#define MII_DP83822_LEDCFG1	0x460
+#define MII_DP83822_IOCTRL1	0x462
 #define MII_DP83822_IOCTRL2	0x463
 #define MII_DP83822_GENCFG	0x465
 #define MII_DP83822_SOR1	0x467
@@ -105,10 +108,26 @@ 
 #define DP83822_RX_CLK_SHIFT	BIT(12)
 #define DP83822_TX_CLK_SHIFT	BIT(11)
 
+/* MLEDCR bits */
+#define DP83822_MLEDCR_CFG		GENMASK(6, 3)
+#define DP83822_MLEDCR_ROUTE		GENMASK(1, 0)
+#define DP83822_MLEDCR_ROUTE_LED_0	DP83822_MLEDCR_ROUTE
+
+/* LEDCFG1 bits */
+#define DP83822_LEDCFG1_LED1_CTRL	GENMASK(11, 8)
+#define DP83822_LEDCFG1_LED3_CTRL	GENMASK(7, 4)
+
+/* IOCTRL1 bits */
+#define DP83822_IOCTRL1_GPIO3_CTRL		GENMASK(10, 8)
+#define DP83822_IOCTRL1_GPIO3_CTRL_LED3		BIT(0)
+#define DP83822_IOCTRL1_GPIO1_CTRL		GENMASK(2, 0)
+#define DP83822_IOCTRL1_GPIO1_CTRL_LED_1	BIT(0)
+
 /* IOCTRL2 bits */
 #define DP83822_IOCTRL2_GPIO2_CLK_SRC		GENMASK(6, 4)
 #define DP83822_IOCTRL2_GPIO2_CTRL		GENMASK(2, 0)
 #define DP83822_IOCTRL2_GPIO2_CTRL_CLK_REF	GENMASK(1, 0)
+#define DP83822_IOCTRL2_GPIO2_CTRL_MLED		BIT(0)
 
 #define DP83822_CLK_SRC_MAC_IF			0x0
 #define DP83822_CLK_SRC_XI			0x1
@@ -117,6 +136,22 @@ 
 #define DP83822_CLK_SRC_FREE_RUNNING		0x6
 #define DP83822_CLK_SRC_RECOVERED		0x7
 
+#define DP83822_LED_FN_LINK		0x0 /* Link established */
+#define DP83822_LED_FN_RX_TX		0x1 /* Receive or Transmit activity */
+#define DP83822_LED_FN_TX		0x2 /* Transmit activity */
+#define DP83822_LED_FN_RX		0x3 /* Receive activity */
+#define DP83822_LED_FN_COLLISION	0x4 /* Collision detected */
+#define DP83822_LED_FN_LINK_100_BTX	0x5 /* 100 BTX link established */
+#define DP83822_LED_FN_LINK_10_BT	0x6 /* 10BT link established */
+#define DP83822_LED_FN_FULL_DUPLEX	0x7 /* Full duplex */
+#define DP83822_LED_FN_LINK_RX_TX	0x8 /* Link established, blink for rx or tx activity */
+#define DP83822_LED_FN_ACTIVE_STRETCH	0x9 /* Active Stretch Signal */
+#define DP83822_LED_FN_MII_LINK		0xa /* MII LINK (100BT+FD) */
+#define DP83822_LED_FN_LPI_MODE		0xb /* LPI Mode (EEE) */
+#define DP83822_LED_FN_RX_TX_ERR	0xc /* TX/RX MII Error */
+#define DP83822_LED_FN_LINK_LOST	0xd /* Link Lost */
+#define DP83822_LED_FN_PRBS_ERR		0xe /* Blink for PRBS error */
+
 /* SOR1 mode */
 #define DP83822_STRAP_MODE1	0
 #define DP83822_STRAP_MODE2	BIT(0)
@@ -145,6 +180,12 @@ 
 					ADVERTISED_FIBRE | \
 					ADVERTISED_Pause | ADVERTISED_Asym_Pause)
 
+#define DP83822_MAX_LED_PINS		4
+
+#define DP83822_LED_INDEX_LED_0		0
+#define DP83822_LED_INDEX_LED_1_GPIO1	1
+#define DP83822_LED_INDEX_COL_GPIO2	2
+#define DP83822_LED_INDEX_RX_D3_GPIO3	3
 struct dp83822_private {
 	bool fx_signal_det_low;
 	int fx_enabled;
@@ -154,6 +195,7 @@  struct dp83822_private {
 	struct ethtool_wolinfo wol;
 	bool set_gpio2_clk_out;
 	u32 gpio2_clk_out;
+	bool led_pin_enable[DP83822_MAX_LED_PINS];
 };
 
 static int dp83822_config_wol(struct phy_device *phydev,
@@ -418,6 +460,48 @@  static int dp83822_read_status(struct phy_device *phydev)
 	return 0;
 }
 
+static int dp83822_config_init_leds(struct phy_device *phydev)
+{
+	struct dp83822_private *dp83822 = phydev->priv;
+	int ret;
+
+	if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_0]) {
+		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_MLEDCR,
+				     DP83822_MLEDCR_ROUTE,
+				     FIELD_PREP(DP83822_MLEDCR_ROUTE,
+						DP83822_MLEDCR_ROUTE_LED_0));
+		if (ret)
+			return ret;
+	} else if (dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2]) {
+		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL2,
+				     DP83822_IOCTRL2_GPIO2_CTRL,
+				     FIELD_PREP(DP83822_IOCTRL2_GPIO2_CTRL,
+						DP83822_IOCTRL2_GPIO2_CTRL_MLED));
+		if (ret)
+			return ret;
+	}
+
+	if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_1_GPIO1]) {
+		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL1,
+				     DP83822_IOCTRL1_GPIO1_CTRL,
+				     FIELD_PREP(DP83822_IOCTRL1_GPIO1_CTRL,
+						DP83822_IOCTRL1_GPIO1_CTRL_LED_1));
+		if (ret)
+			return ret;
+	}
+
+	if (dp83822->led_pin_enable[DP83822_LED_INDEX_RX_D3_GPIO3]) {
+		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_IOCTRL1,
+				     DP83822_IOCTRL1_GPIO3_CTRL,
+				     FIELD_PREP(DP83822_IOCTRL1_GPIO3_CTRL,
+						DP83822_IOCTRL1_GPIO3_CTRL_LED3));
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static int dp83822_config_init(struct phy_device *phydev)
 {
 	struct dp83822_private *dp83822 = phydev->priv;
@@ -437,6 +521,10 @@  static int dp83822_config_init(struct phy_device *phydev)
 			       FIELD_PREP(DP83822_IOCTRL2_GPIO2_CLK_SRC,
 					  dp83822->gpio2_clk_out));
 
+	err = dp83822_config_init_leds(phydev);
+	if (err)
+		return err;
+
 	if (phy_interface_is_rgmii(phydev)) {
 		rx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0,
 						      true);
@@ -631,6 +719,56 @@  static int dp83822_phy_reset(struct phy_device *phydev)
 }
 
 #ifdef CONFIG_OF_MDIO
+static int dp83822_of_init_leds(struct phy_device *phydev)
+{
+	struct device_node *node = phydev->mdio.dev.of_node;
+	struct dp83822_private *dp83822 = phydev->priv;
+	struct device_node *leds;
+	u32 index;
+	int err;
+
+	if (!node)
+		return 0;
+
+	leds = of_get_child_by_name(node, "leds");
+	if (!leds)
+		return 0;
+
+	for_each_available_child_of_node_scoped(leds, led) {
+		err = of_property_read_u32(led, "reg", &index);
+		if (err)
+			return err;
+
+		if (index <= DP83822_LED_INDEX_RX_D3_GPIO3)
+			dp83822->led_pin_enable[index] = true;
+		else
+			return -EINVAL;
+	}
+
+	/* LED_0 and COL(GPIO2) use the MLED function. MLED can be routed to
+	 * only one of these two pins at a time.
+	 */
+	if (dp83822->led_pin_enable[DP83822_LED_INDEX_LED_0] &&
+	    dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2]) {
+		phydev_err(phydev, "LED_0 and COL(GPIO2) cannot be used as LED output at the same time\n");
+		return -EINVAL;
+	}
+
+	if (dp83822->led_pin_enable[DP83822_LED_INDEX_COL_GPIO2] &&
+	    dp83822->set_gpio2_clk_out) {
+		phydev_err(phydev, "COL(GPIO2) cannot be used as LED outout, already used as clock output\n");
+		return -EINVAL;
+	}
+
+	if (dp83822->led_pin_enable[DP83822_LED_INDEX_RX_D3_GPIO3] &&
+	    phydev->interface != PHY_INTERFACE_MODE_RMII) {
+		phydev_err(phydev, "RX_D3 can only be used as LED output when in RMII mode\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int dp83822_of_init(struct phy_device *phydev)
 {
 	struct dp83822_private *dp83822 = phydev->priv;
@@ -671,7 +809,7 @@  static int dp83822_of_init(struct phy_device *phydev)
 		dp83822->set_gpio2_clk_out = true;
 	}
 
-	return 0;
+	return dp83822_of_init_leds(phydev);
 }
 
 static int dp83826_to_dac_minus_one_regval(int percent)
@@ -769,7 +907,9 @@  static int dp83822_probe(struct phy_device *phydev)
 	if (ret)
 		return ret;
 
-	dp83822_of_init(phydev);
+	ret = dp83822_of_init(phydev);
+	if (ret)
+		return ret;
 
 	if (dp83822->fx_enabled)
 		phydev->port = PORT_FIBRE;
@@ -816,6 +956,130 @@  static int dp83822_resume(struct phy_device *phydev)
 	return 0;
 }
 
+static int dp83822_led_mode(u8 index, unsigned long rules)
+{
+	switch (rules) {
+	case BIT(TRIGGER_NETDEV_LINK):
+		return DP83822_LED_FN_LINK;
+	case BIT(TRIGGER_NETDEV_LINK_10):
+		return DP83822_LED_FN_LINK_10_BT;
+	case BIT(TRIGGER_NETDEV_LINK_100):
+		return DP83822_LED_FN_LINK_100_BTX;
+	case BIT(TRIGGER_NETDEV_FULL_DUPLEX):
+		return DP83822_LED_FN_FULL_DUPLEX;
+	case BIT(TRIGGER_NETDEV_TX):
+		return DP83822_LED_FN_TX;
+	case BIT(TRIGGER_NETDEV_RX):
+		return DP83822_LED_FN_RX;
+	case BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX):
+		return DP83822_LED_FN_RX_TX;
+	case BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR):
+		return DP83822_LED_FN_RX_TX_ERR;
+	case BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX):
+		return DP83822_LED_FN_LINK_RX_TX;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int dp83822_led_hw_is_supported(struct phy_device *phydev, u8 index,
+				       unsigned long rules)
+{
+	int mode;
+
+	mode = dp83822_led_mode(index, rules);
+	if (mode < 0)
+		return mode;
+
+	return 0;
+}
+
+static int dp83822_led_hw_control_set(struct phy_device *phydev, u8 index,
+				      unsigned long rules)
+{
+	int mode;
+
+	mode = dp83822_led_mode(index, rules);
+	if (mode < 0)
+		return mode;
+
+	if (index == DP83822_LED_INDEX_LED_0 || index == DP83822_LED_INDEX_COL_GPIO2)
+		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+				      MII_DP83822_MLEDCR, DP83822_MLEDCR_CFG,
+				      FIELD_PREP(DP83822_MLEDCR_CFG, mode));
+	else if (index == DP83822_LED_INDEX_LED_1_GPIO1)
+		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+				      MII_DP83822_LEDCFG1,
+				      DP83822_LEDCFG1_LED1_CTRL,
+				      FIELD_PREP(DP83822_LEDCFG1_LED1_CTRL,
+						 mode));
+	else
+		return phy_modify_mmd(phydev, MDIO_MMD_VEND2,
+				      MII_DP83822_LEDCFG1,
+				      DP83822_LEDCFG1_LED3_CTRL,
+				      FIELD_PREP(DP83822_LEDCFG1_LED3_CTRL,
+						 mode));
+}
+
+static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index,
+				      unsigned long *rules)
+{
+	int val;
+
+	if (index == DP83822_LED_INDEX_LED_0 || DP83822_LED_INDEX_COL_GPIO2) {
+		val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_MLEDCR);
+		if (val < 0)
+			return val;
+
+		val = FIELD_GET(DP83822_MLEDCR_CFG, val);
+	} else {
+		val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MII_DP83822_LEDCFG1);
+		if (val < 0)
+			return val;
+
+		if (index == DP83822_LED_INDEX_LED_1_GPIO1)
+			val = FIELD_GET(DP83822_LEDCFG1_LED1_CTRL, val);
+		else
+			val = FIELD_GET(DP83822_LEDCFG1_LED3_CTRL, val);
+	}
+
+	switch (val) {
+	case DP83822_LED_FN_LINK:
+		*rules = BIT(TRIGGER_NETDEV_LINK);
+		break;
+	case DP83822_LED_FN_LINK_10_BT:
+		*rules = BIT(TRIGGER_NETDEV_LINK_10);
+		break;
+	case DP83822_LED_FN_LINK_100_BTX:
+		*rules = BIT(TRIGGER_NETDEV_LINK_100);
+		break;
+	case DP83822_LED_FN_FULL_DUPLEX:
+		*rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX);
+		break;
+	case DP83822_LED_FN_TX:
+		*rules = BIT(TRIGGER_NETDEV_TX);
+		break;
+	case DP83822_LED_FN_RX:
+		*rules = BIT(TRIGGER_NETDEV_RX);
+		break;
+	case DP83822_LED_FN_RX_TX:
+		*rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);
+		break;
+	case DP83822_LED_FN_RX_TX_ERR:
+		*rules = BIT(TRIGGER_NETDEV_TX_ERR) | BIT(TRIGGER_NETDEV_RX_ERR);
+		break;
+	case DP83822_LED_FN_LINK_RX_TX:
+		*rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) |
+			 BIT(TRIGGER_NETDEV_RX);
+		break;
+	default:
+		*rules = 0;
+		break;
+	}
+
+	return 0;
+}
+
 #define DP83822_PHY_DRIVER(_id, _name)				\
 	{							\
 		PHY_ID_MATCH_MODEL(_id),			\
@@ -831,6 +1095,9 @@  static int dp83822_resume(struct phy_device *phydev)
 		.handle_interrupt = dp83822_handle_interrupt,	\
 		.suspend = dp83822_suspend,			\
 		.resume = dp83822_resume,			\
+		.led_hw_is_supported = dp83822_led_hw_is_supported,	\
+		.led_hw_control_set = dp83822_led_hw_control_set,	\
+		.led_hw_control_get = dp83822_led_hw_control_get,	\
 	}
 
 #define DP83825_PHY_DRIVER(_id, _name)				\