@@ -80,17 +80,60 @@
#define QCA807X_MMD7_1000BASE_T_POWER_SAVE_PER_CABLE_LENGTH 0x801a
#define QCA807X_CONTROL_DAC_MASK GENMASK(2, 0)
+#define QCA807X_MMD7_LED_GLOBAL 0x8073
+#define QCA807X_LED_BLINK_1 GENMASK(11, 6)
+#define QCA807X_LED_BLINK_2 GENMASK(5, 0)
+/* Values are the same for both BLINK_1 and BLINK_2 */
+#define QCA807X_LED_BLINK_FREQ_MASK GENMASK(5, 3)
+#define QCA807X_LED_BLINK_FREQ_2HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x0)
+#define QCA807X_LED_BLINK_FREQ_4HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x1)
+#define QCA807X_LED_BLINK_FREQ_8HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x2)
+#define QCA807X_LED_BLINK_FREQ_16HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x3)
+#define QCA807X_LED_BLINK_FREQ_32HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x4)
+#define QCA807X_LED_BLINK_FREQ_64HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x5)
+#define QCA807X_LED_BLINK_FREQ_128HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x6)
+#define QCA807X_LED_BLINK_FREQ_256HZ FIELD_PREP(QCA807X_LED_BLINK_FREQ_MASK, 0x7)
+#define QCA807X_LED_BLINK_DUTY_MASK GENMASK(2, 0)
+#define QCA807X_LED_BLINK_DUTY_50_50 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x0)
+#define QCA807X_LED_BLINK_DUTY_75_25 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x1)
+#define QCA807X_LED_BLINK_DUTY_25_75 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x2)
+#define QCA807X_LED_BLINK_DUTY_33_67 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x3)
+#define QCA807X_LED_BLINK_DUTY_67_33 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x4)
+#define QCA807X_LED_BLINK_DUTY_17_83 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x5)
+#define QCA807X_LED_BLINK_DUTY_83_17 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x6)
+#define QCA807X_LED_BLINK_DUTY_8_92 FIELD_PREP(QCA807X_LED_BLINK_DUTY_MASK, 0x7)
#define QCA807X_MMD7_LED_100N_1 0x8074
#define QCA807X_MMD7_LED_100N_2 0x8075
#define QCA807X_MMD7_LED_1000N_1 0x8076
#define QCA807X_MMD7_LED_1000N_2 0x8077
-#define QCA807X_LED_TXACT_BLK_EN_2 BIT(10)
-#define QCA807X_LED_RXACT_BLK_EN_2 BIT(9)
-#define QCA807X_LED_GT_ON_EN_2 BIT(6)
-#define QCA807X_LED_HT_ON_EN_2 BIT(5)
-#define QCA807X_LED_BT_ON_EN_2 BIT(4)
-#define QCA807X_GPIO_FORCE_EN BIT(15)
-#define QCA807X_GPIO_FORCE_MODE_MASK GENMASK(14, 13)
+/* Values are the same for LED1 and LED2 */
+/* Values for control 1 */
+#define QCA807X_LED_COPPER_ON_BLINK_MASK GENMASK(12, 0)
+#define QCA807X_LED_FDX_ON_EN BIT(12)
+#define QCA807X_LED_HDX_ON_EN BIT(11)
+#define QCA807X_LED_TXACT_BLK_EN BIT(10)
+#define QCA807X_LED_RXACT_BLK_EN BIT(9)
+#define QCA807X_LED_GT_ON_EN BIT(6)
+#define QCA807X_LED_HT_ON_EN BIT(5)
+#define QCA807X_LED_BT_ON_EN BIT(4)
+/* Values for control 2 */
+#define QCA807X_LED_FORCE_EN BIT(15)
+#define QCA807X_LED_FORCE_MODE_MASK GENMASK(14, 13)
+#define QCA807X_LED_FORCE_BLINK_1 FIELD_PREP(QCA807X_LED_FORCE_MODE_MASK, 0x3)
+#define QCA807X_LED_FORCE_BLINK_2 FIELD_PREP(QCA807X_LED_FORCE_MODE_MASK, 0x2)
+#define QCA807X_LED_FORCE_ON FIELD_PREP(QCA807X_LED_FORCE_MODE_MASK, 0x1)
+#define QCA807X_LED_FORCE_OFF FIELD_PREP(QCA807X_LED_FORCE_MODE_MASK, 0x0)
+#define QCA807X_LED_FIBER_ON_BLINK_MASK GENMASK(11, 1)
+#define QCA807X_LED_FIBER_TXACT_BLK_EN BIT(10)
+#define QCA807X_LED_FIBER_RXACT_BLK_EN BIT(9)
+#define QCA807X_LED_FIBER_FDX_ON_EN BIT(6)
+#define QCA807X_LED_FIBER_HDX_ON_EN BIT(5)
+#define QCA807X_LED_FIBER_1000BX_ON_EN BIT(2)
+#define QCA807X_LED_FIBER_100FX_ON_EN BIT(1)
+
+/* Some device repurpose the LED as GPIO out */
+#define QCA807X_GPIO_FORCE_EN QCA807X_LED_FORCE_EN
+#define QCA807X_GPIO_FORCE_MODE_MASK QCA807X_LED_FORCE_MODE_MASK
#define QCA807X_INTR_ENABLE 0x12
#define QCA807X_INTR_STATUS 0x13
@@ -338,6 +381,320 @@ static int qca807x_cable_test_start(struct phy_device *phydev)
return ret;
}
+static int qca807x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
+ u16 *offload_trigger)
+{
+ /* Parsing specific to netdev trigger */
+ switch (phydev->port) {
+ case PORT_TP:
+ if (test_bit(TRIGGER_NETDEV_TX, &rules))
+ *offload_trigger |= QCA807X_LED_TXACT_BLK_EN;
+ if (test_bit(TRIGGER_NETDEV_RX, &rules))
+ *offload_trigger |= QCA807X_LED_RXACT_BLK_EN;
+ if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
+ *offload_trigger |= QCA807X_LED_BT_ON_EN;
+ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+ *offload_trigger |= QCA807X_LED_HT_ON_EN;
+ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+ *offload_trigger |= QCA807X_LED_GT_ON_EN;
+ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
+ *offload_trigger |= QCA807X_LED_HDX_ON_EN;
+ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
+ *offload_trigger |= QCA807X_LED_FDX_ON_EN;
+ break;
+ case PORT_FIBRE:
+ if (test_bit(TRIGGER_NETDEV_TX, &rules))
+ *offload_trigger |= QCA807X_LED_FIBER_TXACT_BLK_EN;
+ if (test_bit(TRIGGER_NETDEV_RX, &rules))
+ *offload_trigger |= QCA807X_LED_FIBER_RXACT_BLK_EN;
+ if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
+ *offload_trigger |= QCA807X_LED_FIBER_100FX_ON_EN;
+ if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
+ *offload_trigger |= QCA807X_LED_FIBER_1000BX_ON_EN;
+ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
+ *offload_trigger |= QCA807X_LED_FIBER_HDX_ON_EN;
+ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
+ *offload_trigger |= QCA807X_LED_FIBER_FDX_ON_EN;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (rules && !*offload_trigger)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int qca807x_led_hw_control_enable(struct phy_device *phydev, u8 index)
+{
+ int val, reg, ret;
+
+ switch (index) {
+ case 0:
+ reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ case 1:
+ reg = QCA807X_MMD7_LED_1000N_2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+ val &= ~QCA807X_LED_FORCE_EN;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, reg, val);
+
+ return ret;
+}
+
+static int qca807x_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ u16 offload_trigger = 0;
+
+ if (index > 1)
+ return -EINVAL;
+
+ return qca807x_led_parse_netdev(phydev, rules, &offload_trigger);
+}
+
+static int qca807x_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ int val, ret, copper_reg, fibre_reg;
+ u16 offload_trigger = 0;
+
+ switch (index) {
+ case 0:
+ copper_reg = QCA807X_MMD7_LED_100N_1;
+ fibre_reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ case 1:
+ copper_reg = QCA807X_MMD7_LED_1000N_1;
+ fibre_reg = QCA807X_MMD7_LED_1000N_2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = qca807x_led_parse_netdev(phydev, rules, &offload_trigger);
+ if (ret)
+ return ret;
+
+ ret = qca807x_led_hw_control_enable(phydev, index);
+ if (ret)
+ return ret;
+
+ switch (phydev->port) {
+ case PORT_TP:
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, copper_reg);
+ val &= ~QCA807X_LED_COPPER_ON_BLINK_MASK;
+ val |= offload_trigger;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, copper_reg, val);
+ break;
+ case PORT_FIBRE:
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, fibre_reg);
+ val &= ~QCA807X_LED_FIBER_ON_BLINK_MASK;
+ val |= offload_trigger;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, fibre_reg, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static bool qca807x_led_hw_control_status(struct phy_device *phydev, u8 index)
+{
+ int val, reg;
+
+ switch (index) {
+ case 0:
+ reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ case 1:
+ reg = QCA807X_MMD7_LED_1000N_2;
+ break;
+ default:
+ return false;
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+
+ return !(val & QCA807X_LED_FORCE_EN);
+}
+
+static int qca807x_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ int val, copper_reg, fibre_reg;
+
+ switch (index) {
+ case 0:
+ copper_reg = QCA807X_MMD7_LED_100N_1;
+ fibre_reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ case 1:
+ copper_reg = QCA807X_MMD7_LED_1000N_1;
+ fibre_reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if we have hw control enabled */
+ if (qca807x_led_hw_control_status(phydev, index))
+ return -EINVAL;
+
+ /* Parsing specific to netdev trigger */
+ switch (phydev->port) {
+ case PORT_TP:
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, copper_reg);
+ if (val & QCA807X_LED_TXACT_BLK_EN)
+ set_bit(TRIGGER_NETDEV_TX, rules);
+ if (val & QCA807X_LED_RXACT_BLK_EN)
+ set_bit(TRIGGER_NETDEV_RX, rules);
+ if (val & QCA807X_LED_BT_ON_EN)
+ set_bit(TRIGGER_NETDEV_LINK_10, rules);
+ if (val & QCA807X_LED_HT_ON_EN)
+ set_bit(TRIGGER_NETDEV_LINK_100, rules);
+ if (val & QCA807X_LED_GT_ON_EN)
+ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+ if (val & QCA807X_LED_HDX_ON_EN)
+ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
+ if (val & QCA807X_LED_FDX_ON_EN)
+ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
+ break;
+ case PORT_FIBRE:
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, fibre_reg);
+ if (val & QCA807X_LED_FIBER_TXACT_BLK_EN)
+ set_bit(TRIGGER_NETDEV_TX, rules);
+ if (val & QCA807X_LED_FIBER_RXACT_BLK_EN)
+ set_bit(TRIGGER_NETDEV_RX, rules);
+ if (val & QCA807X_LED_FIBER_100FX_ON_EN)
+ set_bit(TRIGGER_NETDEV_LINK_100, rules);
+ if (val & QCA807X_LED_FIBER_1000BX_ON_EN)
+ set_bit(TRIGGER_NETDEV_LINK_1000, rules);
+ if (val & QCA807X_LED_FIBER_HDX_ON_EN)
+ set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
+ if (val & QCA807X_LED_FIBER_FDX_ON_EN)
+ set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qca807x_led_hw_control_reset(struct phy_device *phydev, u8 index)
+{
+ int val, copper_reg, fibre_reg, ret;
+
+ switch (index) {
+ case 0:
+ copper_reg = QCA807X_MMD7_LED_100N_1;
+ fibre_reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ case 1:
+ copper_reg = QCA807X_MMD7_LED_1000N_1;
+ fibre_reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (phydev->port) {
+ case PORT_TP:
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, copper_reg);
+ val &= ~QCA807X_LED_COPPER_ON_BLINK_MASK;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, copper_reg, val);
+ break;
+ case PORT_FIBRE:
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, fibre_reg);
+ val &= ~QCA807X_LED_FIBER_ON_BLINK_MASK;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, fibre_reg, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int qca807x_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value)
+{
+ int val, ret;
+ u16 reg;
+
+ switch (index) {
+ case 0:
+ reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ case 1:
+ reg = QCA807X_MMD7_LED_1000N_2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* If we are setting off the LED reset any hw control rule */
+ if (!value) {
+ ret = qca807x_led_hw_control_reset(phydev, index);
+ if (ret)
+ return ret;
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+ val &= ~(QCA807X_LED_FORCE_EN | QCA807X_LED_FORCE_MODE_MASK);
+ val |= QCA807X_LED_FORCE_EN;
+ if (value)
+ val |= QCA807X_LED_FORCE_ON;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, reg, val);
+
+ return ret;
+}
+
+static int qca807x_led_blink_set(struct phy_device *phydev, u8 index,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ int val, ret;
+ u16 reg;
+
+ switch (index) {
+ case 0:
+ reg = QCA807X_MMD7_LED_100N_2;
+ break;
+ case 1:
+ reg = QCA807X_MMD7_LED_1000N_2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Set blink to 50% off, 50% on at 4Hz by default */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_LED_GLOBAL);
+ val &= ~(QCA807X_LED_BLINK_FREQ_MASK | QCA807X_LED_BLINK_DUTY_MASK);
+ val |= QCA807X_LED_BLINK_FREQ_4HZ | QCA807X_LED_BLINK_DUTY_50_50;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_LED_GLOBAL, val);
+
+ /* We use BLINK_1 for normal blinking */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
+ val &= ~(QCA807X_LED_FORCE_EN | QCA807X_LED_FORCE_MODE_MASK);
+ val |= QCA807X_LED_FORCE_EN | QCA807X_LED_FORCE_BLINK_1;
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, reg, val);
+
+ /* We set blink to 4Hz, aka 250ms */
+ *delay_on = 250 / 2;
+ *delay_off = 250 / 2;
+
+ return ret;
+}
+
#ifdef CONFIG_GPIOLIB
static int qca807x_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
{
@@ -716,6 +1073,12 @@ static int qca807x_probe(struct phy_device *phydev)
ret = qca807x_gpio(phydev);
if (ret)
return ret;
+
+ phydev->drv->led_brightness_set = NULL;
+ phydev->drv->led_blink_set = NULL;
+ phydev->drv->led_hw_is_supported = NULL;
+ phydev->drv->led_hw_control_set = NULL;
+ phydev->drv->led_hw_control_get = NULL;
}
}
@@ -843,6 +1206,11 @@ static struct phy_driver qca807x_drivers[] = {
.suspend = genphy_suspend,
.cable_test_start = qca807x_cable_test_start,
.cable_test_get_status = qca807x_cable_test_get_status,
+ .led_brightness_set = qca807x_led_brightness_set,
+ .led_blink_set = qca807x_led_blink_set,
+ .led_hw_is_supported = qca807x_led_hw_is_supported,
+ .led_hw_control_set = qca807x_led_hw_control_set,
+ .led_hw_control_get = qca807x_led_hw_control_get,
/* PHY package define */
.phy_package_global_phy_num = ARRAY_SIZE(qca807x_global_phy_names),
.phy_package_global_phy_names = qca807x_global_phy_names,