Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: phy: marvell-88q2xxx: Add support for PHY LEDs on 88q2xxx

Marvell 88Q2XXX devices support up to two configurable Light Emitting
Diode (LED). Add minimal LED controller driver supporting the most common
uses with the 'netdev' trigger.

Reviewed-by: Stefan Eichenberger <eichest@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Dimitri Fedrau <dima.fedrau@gmail.com>
Link: https://patch.msgid.link/20250210-marvell-88q2xxx-leds-v4-1-3a0900dc121f@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

authored by

Dimitri Fedrau and committed by
Paolo Abeni
a3783dbf 8dbf0c75

+185 -4
+185 -4
drivers/net/phy/marvell-88q2xxx.c
··· 8 8 */ 9 9 #include <linux/ethtool_netlink.h> 10 10 #include <linux/marvell_phy.h> 11 + #include <linux/of.h> 11 12 #include <linux/phy.h> 12 13 #include <linux/hwmon.h> 13 14 ··· 28 27 #define MDIO_MMD_AN_MV_STAT2_100BT1 0x2000 29 28 #define MDIO_MMD_AN_MV_STAT2_1000BT1 0x4000 30 29 30 + #define MDIO_MMD_PCS_MV_RESET_CTRL 32768 31 + #define MDIO_MMD_PCS_MV_RESET_CTRL_TX_DISABLE 0x8 32 + 31 33 #define MDIO_MMD_PCS_MV_INT_EN 32784 32 34 #define MDIO_MMD_PCS_MV_INT_EN_LINK_UP 0x0040 33 35 #define MDIO_MMD_PCS_MV_INT_EN_LINK_DOWN 0x0080 ··· 43 39 44 40 #define MDIO_MMD_PCS_MV_GPIO_INT_CTRL 32787 45 41 #define MDIO_MMD_PCS_MV_GPIO_INT_CTRL_TRI_DIS 0x0800 42 + 43 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL 32790 44 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK GENMASK(7, 4) 45 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK GENMASK(3, 0) 46 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK 0x0 /* Link established */ 47 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_RX_TX 0x1 /* Link established, blink for rx or tx activity */ 48 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1 0x2 /* Blink 3x for 1000BT1 link established */ 49 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX_ON 0x3 /* Receive or transmit activity */ 50 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX 0x4 /* Blink on receive or transmit activity */ 51 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_TX 0x5 /* Transmit activity */ 52 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_COPPER 0x6 /* Copper Link established */ 53 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1_ON 0x7 /* 1000BT1 link established */ 54 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_OFF 0x8 /* Force off */ 55 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_ON 0x9 /* Force on */ 56 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_HIGHZ 0xa /* Force Hi-Z */ 57 + #define MDIO_MMD_PCS_MV_LED_FUNC_CTRL_FORCE_BLINK 0xb /* Force blink */ 46 58 47 59 #define MDIO_MMD_PCS_MV_TEMP_SENSOR1 32833 48 60 #define MDIO_MMD_PCS_MV_TEMP_SENSOR1_RAW_INT 0x0001 ··· 115 95 116 96 #define MDIO_MMD_PCS_MV_TDR_OFF_CUTOFF 65246 117 97 98 + #define MV88Q2XXX_LED_INDEX_TX_ENABLE 0 99 + #define MV88Q2XXX_LED_INDEX_GPIO 1 100 + 118 101 struct mv88q2xxx_priv { 119 102 bool enable_temp; 103 + bool enable_led0; 120 104 }; 121 105 122 106 struct mmd_val { ··· 484 460 485 461 static int mv88q2xxx_config_init(struct phy_device *phydev) 486 462 { 463 + struct mv88q2xxx_priv *priv = phydev->priv; 464 + int ret; 465 + 487 466 /* The 88Q2XXX PHYs do have the extended ability register available, but 488 467 * register MDIO_PMA_EXTABLE where they should signalize it does not 489 468 * work according to specification. Therefore, we force it here. ··· 496 469 /* Configure interrupt with default settings, output is driven low for 497 470 * active interrupt and high for inactive. 498 471 */ 499 - if (phy_interrupt_is_valid(phydev)) 500 - return phy_set_bits_mmd(phydev, MDIO_MMD_PCS, 501 - MDIO_MMD_PCS_MV_GPIO_INT_CTRL, 502 - MDIO_MMD_PCS_MV_GPIO_INT_CTRL_TRI_DIS); 472 + if (phy_interrupt_is_valid(phydev)) { 473 + ret = phy_set_bits_mmd(phydev, MDIO_MMD_PCS, 474 + MDIO_MMD_PCS_MV_GPIO_INT_CTRL, 475 + MDIO_MMD_PCS_MV_GPIO_INT_CTRL_TRI_DIS); 476 + if (ret < 0) 477 + return ret; 478 + } 479 + 480 + /* Enable LED function and disable TX disable feature on LED/TX_ENABLE */ 481 + if (priv->enable_led0) { 482 + ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, 483 + MDIO_MMD_PCS_MV_RESET_CTRL, 484 + MDIO_MMD_PCS_MV_RESET_CTRL_TX_DISABLE); 485 + if (ret < 0) 486 + return ret; 487 + } 503 488 504 489 return 0; 505 490 } ··· 779 740 } 780 741 #endif 781 742 743 + #if IS_ENABLED(CONFIG_OF_MDIO) 744 + static int mv88q2xxx_leds_probe(struct phy_device *phydev) 745 + { 746 + struct device_node *node = phydev->mdio.dev.of_node; 747 + struct mv88q2xxx_priv *priv = phydev->priv; 748 + struct device_node *leds; 749 + int ret = 0; 750 + u32 index; 751 + 752 + if (!node) 753 + return 0; 754 + 755 + leds = of_get_child_by_name(node, "leds"); 756 + if (!leds) 757 + return 0; 758 + 759 + for_each_available_child_of_node_scoped(leds, led) { 760 + ret = of_property_read_u32(led, "reg", &index); 761 + if (ret) 762 + goto exit; 763 + 764 + if (index > MV88Q2XXX_LED_INDEX_GPIO) { 765 + ret = -EINVAL; 766 + goto exit; 767 + } 768 + 769 + if (index == MV88Q2XXX_LED_INDEX_TX_ENABLE) 770 + priv->enable_led0 = true; 771 + } 772 + 773 + exit: 774 + of_node_put(leds); 775 + 776 + return ret; 777 + } 778 + 779 + #else 780 + static int mv88q2xxx_leds_probe(struct phy_device *phydev) 781 + { 782 + return 0; 783 + } 784 + #endif 785 + 782 786 static int mv88q2xxx_probe(struct phy_device *phydev) 783 787 { 784 788 struct mv88q2xxx_priv *priv; 789 + int ret; 785 790 786 791 priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL); 787 792 if (!priv) 788 793 return -ENOMEM; 789 794 790 795 phydev->priv = priv; 796 + ret = mv88q2xxx_leds_probe(phydev); 797 + if (ret) 798 + return ret; 791 799 792 800 return mv88q2xxx_hwmon_probe(phydev); 793 801 } ··· 1004 918 return 0; 1005 919 } 1006 920 921 + static int mv88q2xxx_led_mode(u8 index, unsigned long rules) 922 + { 923 + switch (rules) { 924 + case BIT(TRIGGER_NETDEV_LINK): 925 + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK; 926 + case BIT(TRIGGER_NETDEV_LINK_1000): 927 + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1_ON; 928 + case BIT(TRIGGER_NETDEV_TX): 929 + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_TX; 930 + case BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): 931 + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX; 932 + case BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX): 933 + return MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_RX_TX; 934 + default: 935 + return -EOPNOTSUPP; 936 + } 937 + } 938 + 939 + static int mv88q2xxx_led_hw_is_supported(struct phy_device *phydev, u8 index, 940 + unsigned long rules) 941 + { 942 + int mode; 943 + 944 + mode = mv88q2xxx_led_mode(index, rules); 945 + if (mode < 0) 946 + return mode; 947 + 948 + return 0; 949 + } 950 + 951 + static int mv88q2xxx_led_hw_control_set(struct phy_device *phydev, u8 index, 952 + unsigned long rules) 953 + { 954 + int mode; 955 + 956 + mode = mv88q2xxx_led_mode(index, rules); 957 + if (mode < 0) 958 + return mode; 959 + 960 + if (index == MV88Q2XXX_LED_INDEX_TX_ENABLE) 961 + return phy_modify_mmd(phydev, MDIO_MMD_PCS, 962 + MDIO_MMD_PCS_MV_LED_FUNC_CTRL, 963 + MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK, 964 + FIELD_PREP(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK, 965 + mode)); 966 + else 967 + return phy_modify_mmd(phydev, MDIO_MMD_PCS, 968 + MDIO_MMD_PCS_MV_LED_FUNC_CTRL, 969 + MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK, 970 + FIELD_PREP(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK, 971 + mode)); 972 + } 973 + 974 + static int mv88q2xxx_led_hw_control_get(struct phy_device *phydev, u8 index, 975 + unsigned long *rules) 976 + { 977 + int val; 978 + 979 + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_LED_FUNC_CTRL); 980 + if (val < 0) 981 + return val; 982 + 983 + if (index == MV88Q2XXX_LED_INDEX_TX_ENABLE) 984 + val = FIELD_GET(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_0_MASK, val); 985 + else 986 + val = FIELD_GET(MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LED_1_MASK, val); 987 + 988 + switch (val) { 989 + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK: 990 + *rules = BIT(TRIGGER_NETDEV_LINK); 991 + break; 992 + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_1000BT1_ON: 993 + *rules = BIT(TRIGGER_NETDEV_LINK_1000); 994 + break; 995 + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_TX: 996 + *rules = BIT(TRIGGER_NETDEV_TX); 997 + break; 998 + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_RX_TX: 999 + *rules = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX); 1000 + break; 1001 + case MDIO_MMD_PCS_MV_LED_FUNC_CTRL_LINK_RX_TX: 1002 + *rules = BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_TX) | 1003 + BIT(TRIGGER_NETDEV_RX); 1004 + break; 1005 + default: 1006 + *rules = 0; 1007 + break; 1008 + } 1009 + 1010 + return 0; 1011 + } 1012 + 1007 1013 static struct phy_driver mv88q2xxx_driver[] = { 1008 1014 { 1009 1015 .phy_id = MARVELL_PHY_ID_88Q2110, ··· 1131 953 .get_sqi_max = mv88q2xxx_get_sqi_max, 1132 954 .suspend = mv88q2xxx_suspend, 1133 955 .resume = mv88q2xxx_resume, 956 + .led_hw_is_supported = mv88q2xxx_led_hw_is_supported, 957 + .led_hw_control_set = mv88q2xxx_led_hw_control_set, 958 + .led_hw_control_get = mv88q2xxx_led_hw_control_get, 1134 959 }, 1135 960 }; 1136 961