@@ -434,6 +434,7 @@ struct kszphy_priv {
const struct kszphy_type *type;
struct clk *clk;
int led_mode;
+ unsigned long led_rules[2];
u16 vct_ctrl1000;
bool rmii_ref_clk_sel;
bool rmii_ref_clk_sel_val;
@@ -891,6 +892,112 @@ static int ksz8795_match_phy_device(struct phy_device *phydev)
return ksz8051_ksz8795_match_phy_device(phydev, false);
}
+#define KSZ8795_LED_COUNT 2
+
+static const unsigned long ksz8795_led_rules_map[4][2] = {
+ {
+ /* Control Bits = 2'b00 => LEDx_0=Link/ACT LEDx_1=Speed */
+ BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX),
+ BIT(TRIGGER_NETDEV_LINK_100)
+ }, {
+ /* Control Bits = 2'b01 => LEDx_0=Link LEDx_1=ACT */
+ BIT(TRIGGER_NETDEV_LINK),
+ BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)
+ }, {
+ /* Control Bits = 2'b10 => LEDx_0=Link/ACT LEDx_1=Duplex */
+ BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX),
+ BIT(TRIGGER_NETDEV_FULL_DUPLEX)
+ }, {
+ /* Control Bits = 2'b11 => LEDx_0=Link LEDx_1=Duplex */
+ BIT(TRIGGER_NETDEV_LINK),
+ BIT(TRIGGER_NETDEV_FULL_DUPLEX)
+ }
+};
+
+static int ksz8795_led_brightness_set(struct phy_device *phydev, u8 index,
+ enum led_brightness value)
+{
+ /* Turn all LEDs on this port on or off */
+ /* Emulated rmw of Register 29/45/61 (0x1D/0x2D/0x3D): Port 1/2/3 Control 10 */
+ return phy_modify(phydev, 0x0d00, BIT(7), (value == LED_OFF) ? BIT(7) : 0);
+}
+
+static int ksz8795_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ const unsigned long mask[2] = {
+ BIT(TRIGGER_NETDEV_LINK) | BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX),
+ BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_FULL_DUPLEX)
+ };
+
+ if (index >= KSZ8795_LED_COUNT)
+ return -EINVAL;
+
+ /* Filter out any other unsupported triggers. */
+ if (rules & ~mask[index])
+ return -EOPNOTSUPP;
+
+ /* RX and TX are not differentiated, either both are set or not set. */
+ if (!(rules & BIT(TRIGGER_NETDEV_RX)) ^ !(rules & BIT(TRIGGER_NETDEV_TX)))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int ksz8795_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ int val;
+
+ if (index >= KSZ8795_LED_COUNT)
+ return -EINVAL;
+
+ /* Emulated read of Register 11 (0x0B): Global Control 9 */
+ val = phy_read(phydev, 0x0b00);
+ if (val < 0)
+ return val;
+
+ /* Extract bits [5:4] and look up matching LED configuration */
+ *rules = ksz8795_led_rules_map[(val >> 4) & 0x3][index];
+
+ return 0;
+}
+
+static int ksz8795_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ struct kszphy_priv *priv = phydev->priv;
+ unsigned long other_rules;
+ int i;
+
+ if (index >= KSZ8795_LED_COUNT)
+ return -EINVAL;
+
+ /*
+ * Cache the rules for this LED for future use when setting up the
+ * other LED and looking up compatible configuration of the global
+ * control 9 register bitfield [5:4].
+ */
+ priv->led_rules[index] = rules;
+
+ /* Use cached configuration of the other LED. */
+ other_rules = priv->led_rules[!index];
+
+ /* Update this LED configuration if compatible with the other LED */
+ for (i = 0; i < 4; i++) {
+ if (ksz8795_led_rules_map[i][index] == rules &&
+ ksz8795_led_rules_map[i][!index] == other_rules) {
+ return phy_modify(phydev, 0x0b00, 0x30, i << 4);
+ }
+ }
+
+ return -EINVAL;
+}
+
static int ksz9021_load_values_from_of(struct phy_device *phydev,
const struct device_node *of_node,
u16 reg,
@@ -5666,10 +5773,15 @@ static struct phy_driver ksphy_driver[] = {
}, {
.name = "Micrel KSZ87XX Switch",
/* PHY_BASIC_FEATURES */
+ .probe = kszphy_probe,
.config_init = kszphy_config_init,
.match_phy_device = ksz8795_match_phy_device,
.suspend = genphy_suspend,
.resume = genphy_resume,
+ .led_brightness_set = ksz8795_led_brightness_set,
+ .led_hw_is_supported = ksz8795_led_hw_is_supported,
+ .led_hw_control_get = ksz8795_led_hw_control_get,
+ .led_hw_control_set = ksz8795_led_hw_control_set,
}, {
.phy_id = PHY_ID_KSZ9477,
.phy_id_mask = MICREL_PHY_ID_MASK,
The KSZ87xx switch contains LED control registers. There is one shared global control register bitfield which affects behavior of all LEDs on all ports, the Register 11 (0x0B): Global Control 9 bitfield [5:4]. There is also one per-port Register 29/45/61 (0x1D/0x2D/0x3D): Port 1/2/3 Control 10 bit 7 which controls enablement of both LEDs on each port separately. Expose LED brightness control and HW offload support for both of the two programmable LEDs on this KSZ87XX Switch. Note that on KSZ87xx there are three or more instances of simple KSZ87XX Switch PHY, one for each port, however, the registers which control the LED behavior are mostly shared. Introduce LED brightness control using Register 29/45/61 (0x1D/0x2D/0x3D): Port 1/2/3 Control 10 bit 7. This bit selects between LEDs disabled and LEDs set to Function mode. In case LED brightness is set to 0, both LEDs are turned off, otherwise both LEDs are configured to Function mode which follows the global Register 11 (0x0B): Global Control 9 bitfield [5:4] setting. Note that while two LEDs are registered per port, and each expose a matching sysfs directory which contains a brightness attribute, a write into either brightness attribute does reconfigure the same bit 7 in Register 29/45/61 (0x1D/0x2D/0x3D): Port 1/2/3 Control 10 for that particular port . The two brightness attributes can also be out of sync which is not great. Introduce LED mode configuration using Register 11 (0x0B): Global Control 9 bitfield [5:4]. This bitfield can be set to 1 of 4 non-orthogonal mode settings which affects both LEDs on the port. Use a look up table to find out whether setting one LED on the port is compatible with current setting of the other LED and if not, reject the configuration until both LEDs are configured to one of the four valid modes. Note that while there are two LEDs per port, and there are multiple ports, each with matching sysfs directory which contains netdev trigger attributes, a write into either attribute does reconfigure the same shared Register 11 (0x0B): Global Control 9 bitfield [5:4] and the sysfs attributes can be out of sync which is not great. Signed-off-by: Marek Vasut <marex@denx.de> --- Cc: "David S. Miller" <davem@davemloft.net> Cc: Andrew Lunn <andrew@lunn.ch> Cc: Eric Dumazet <edumazet@google.com> Cc: Heiner Kallweit <hkallweit1@gmail.com> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Paolo Abeni <pabeni@redhat.com> Cc: Russell King <linux@armlinux.org.uk> Cc: Tristram Ha <tristram.ha@microchip.com> Cc: UNGLinuxDriver@microchip.com Cc: Vladimir Oltean <olteanv@gmail.com> Cc: Woojung Huh <woojung.huh@microchip.com> Cc: linux-kernel@vger.kernel.org Cc: netdev@vger.kernel.org --- drivers/net/phy/micrel.c | 112 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)