Commit c042502c authored by Jakub Kicinski's avatar Jakub Kicinski

Merge branch 'support-offload-led-blinking-to-phy'

Andrew Lunn says:

====================
Support offload LED blinking to PHY.

Allow offloading of the LED trigger netdev to PHY drivers and
implement it for the Marvell PHY driver. Additionally, correct the
handling of when the initial state of the LED cannot be represented by
the trigger, and so an error is returned. As with ledtrig-timer,
disable offload when the trigger is deactivate, or replaced by another
trigger.
====================

Link: https://lore.kernel.org/r/20230808210436.838995-1-andrew@lunn.chSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 215c44fa e8fbcc47
......@@ -564,15 +564,17 @@ static int netdev_trig_activate(struct led_classdev *led_cdev)
/* Check if hw control is active by default on the LED.
* Init already enabled mode in hw control.
*/
if (supports_hw_control(led_cdev) &&
!led_cdev->hw_control_get(led_cdev, &mode)) {
if (supports_hw_control(led_cdev)) {
dev = led_cdev->hw_control_get_device(led_cdev);
if (dev) {
const char *name = dev_name(dev);
set_device_name(trigger_data, name, strlen(name));
trigger_data->hw_control = true;
trigger_data->mode = mode;
rc = led_cdev->hw_control_get(led_cdev, &mode);
if (!rc)
trigger_data->mode = mode;
}
}
......@@ -593,6 +595,8 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
cancel_delayed_work_sync(&trigger_data->work);
led_set_brightness(led_cdev, LED_OFF);
dev_put(trigger_data->net_dev);
kfree(trigger_data);
......
......@@ -2893,6 +2893,272 @@ static int m88e1318_led_blink_set(struct phy_device *phydev, u8 index,
MII_88E1318S_PHY_LED_FUNC, reg);
}
struct marvell_led_rules {
int mode;
unsigned long rules;
};
static const struct marvell_led_rules marvell_led0[] = {
{
.mode = 0,
.rules = BIT(TRIGGER_NETDEV_LINK),
},
{
.mode = 1,
.rules = (BIT(TRIGGER_NETDEV_LINK) |
BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 3,
.rules = (BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 4,
.rules = (BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 5,
.rules = BIT(TRIGGER_NETDEV_TX),
},
{
.mode = 6,
.rules = BIT(TRIGGER_NETDEV_LINK),
},
{
.mode = 7,
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
},
{
.mode = 8,
.rules = 0,
},
};
static const struct marvell_led_rules marvell_led1[] = {
{
.mode = 1,
.rules = (BIT(TRIGGER_NETDEV_LINK) |
BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 2,
.rules = (BIT(TRIGGER_NETDEV_LINK) |
BIT(TRIGGER_NETDEV_RX)),
},
{
.mode = 3,
.rules = (BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 4,
.rules = (BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 6,
.rules = (BIT(TRIGGER_NETDEV_LINK_100) |
BIT(TRIGGER_NETDEV_LINK_1000)),
},
{
.mode = 7,
.rules = BIT(TRIGGER_NETDEV_LINK_100),
},
{
.mode = 8,
.rules = 0,
},
};
static const struct marvell_led_rules marvell_led2[] = {
{
.mode = 0,
.rules = BIT(TRIGGER_NETDEV_LINK),
},
{
.mode = 1,
.rules = (BIT(TRIGGER_NETDEV_LINK) |
BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 3,
.rules = (BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 4,
.rules = (BIT(TRIGGER_NETDEV_RX) |
BIT(TRIGGER_NETDEV_TX)),
},
{
.mode = 5,
.rules = BIT(TRIGGER_NETDEV_TX),
},
{
.mode = 6,
.rules = (BIT(TRIGGER_NETDEV_LINK_10) |
BIT(TRIGGER_NETDEV_LINK_1000)),
},
{
.mode = 7,
.rules = BIT(TRIGGER_NETDEV_LINK_10),
},
{
.mode = 8,
.rules = 0,
},
};
static int marvell_find_led_mode(unsigned long rules,
const struct marvell_led_rules *marvell_rules,
int count,
int *mode)
{
int i;
for (i = 0; i < count; i++) {
if (marvell_rules[i].rules == rules) {
*mode = marvell_rules[i].mode;
return 0;
}
}
return -EOPNOTSUPP;
}
static int marvell_get_led_mode(u8 index, unsigned long rules, int *mode)
{
int ret;
switch (index) {
case 0:
ret = marvell_find_led_mode(rules, marvell_led0,
ARRAY_SIZE(marvell_led0), mode);
break;
case 1:
ret = marvell_find_led_mode(rules, marvell_led1,
ARRAY_SIZE(marvell_led1), mode);
break;
case 2:
ret = marvell_find_led_mode(rules, marvell_led2,
ARRAY_SIZE(marvell_led2), mode);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int marvell_find_led_rules(unsigned long *rules,
const struct marvell_led_rules *marvell_rules,
int count,
int mode)
{
int i;
for (i = 0; i < count; i++) {
if (marvell_rules[i].mode == mode) {
*rules = marvell_rules[i].rules;
return 0;
}
}
return -EOPNOTSUPP;
}
static int marvell_get_led_rules(u8 index, unsigned long *rules, int mode)
{
int ret;
switch (index) {
case 0:
ret = marvell_find_led_rules(rules, marvell_led0,
ARRAY_SIZE(marvell_led0), mode);
break;
case 1:
ret = marvell_find_led_rules(rules, marvell_led1,
ARRAY_SIZE(marvell_led1), mode);
break;
case 2:
ret = marvell_find_led_rules(rules, marvell_led2,
ARRAY_SIZE(marvell_led2), mode);
break;
default:
ret = -EOPNOTSUPP;
}
return ret;
}
static int m88e1318_led_hw_is_supported(struct phy_device *phydev, u8 index,
unsigned long rules)
{
int mode, ret;
switch (index) {
case 0:
case 1:
case 2:
ret = marvell_get_led_mode(index, rules, &mode);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int m88e1318_led_hw_control_set(struct phy_device *phydev, u8 index,
unsigned long rules)
{
int mode, ret, reg;
switch (index) {
case 0:
case 1:
case 2:
ret = marvell_get_led_mode(index, rules, &mode);
break;
default:
ret = -EINVAL;
}
if (ret < 0)
return ret;
reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
MII_88E1318S_PHY_LED_FUNC);
if (reg < 0)
return reg;
reg &= ~(0xf << (4 * index));
reg |= mode << (4 * index);
return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
MII_88E1318S_PHY_LED_FUNC, reg);
}
static int m88e1318_led_hw_control_get(struct phy_device *phydev, u8 index,
unsigned long *rules)
{
int mode, reg;
if (index > 2)
return -EINVAL;
reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
MII_88E1318S_PHY_LED_FUNC);
if (reg < 0)
return reg;
mode = (reg >> (4 * index)) & 0xf;
return marvell_get_led_rules(index, rules, mode);
}
static int marvell_probe(struct phy_device *phydev)
{
struct marvell_priv *priv;
......@@ -3144,6 +3410,9 @@ static struct phy_driver marvell_drivers[] = {
.get_stats = marvell_get_stats,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
.led_hw_is_supported = m88e1318_led_hw_is_supported,
.led_hw_control_set = m88e1318_led_hw_control_set,
.led_hw_control_get = m88e1318_led_hw_control_get,
},
{
.phy_id = MARVELL_PHY_ID_88E1145,
......@@ -3252,6 +3521,9 @@ static struct phy_driver marvell_drivers[] = {
.cable_test_get_status = marvell_vct7_cable_test_get_status,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
.led_hw_is_supported = m88e1318_led_hw_is_supported,
.led_hw_control_set = m88e1318_led_hw_control_set,
.led_hw_control_get = m88e1318_led_hw_control_get,
},
{
.phy_id = MARVELL_PHY_ID_88E1540,
......@@ -3280,6 +3552,9 @@ static struct phy_driver marvell_drivers[] = {
.cable_test_get_status = marvell_vct7_cable_test_get_status,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
.led_hw_is_supported = m88e1318_led_hw_is_supported,
.led_hw_control_set = m88e1318_led_hw_control_set,
.led_hw_control_get = m88e1318_led_hw_control_get,
},
{
.phy_id = MARVELL_PHY_ID_88E1545,
......@@ -3308,6 +3583,9 @@ static struct phy_driver marvell_drivers[] = {
.cable_test_get_status = marvell_vct7_cable_test_get_status,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
.led_hw_is_supported = m88e1318_led_hw_is_supported,
.led_hw_control_set = m88e1318_led_hw_control_set,
.led_hw_control_get = m88e1318_led_hw_control_get,
},
{
.phy_id = MARVELL_PHY_ID_88E3016,
......@@ -3451,6 +3729,9 @@ static struct phy_driver marvell_drivers[] = {
.set_tunable = m88e1540_set_tunable,
.led_brightness_set = m88e1318_led_brightness_set,
.led_blink_set = m88e1318_led_blink_set,
.led_hw_is_supported = m88e1318_led_hw_is_supported,
.led_hw_control_set = m88e1318_led_hw_control_set,
.led_hw_control_get = m88e1318_led_hw_control_get,
},
};
......
......@@ -3020,6 +3020,61 @@ static int phy_led_blink_set(struct led_classdev *led_cdev,
return err;
}
static __maybe_unused struct device *
phy_led_hw_control_get_device(struct led_classdev *led_cdev)
{
struct phy_led *phyled = to_phy_led(led_cdev);
struct phy_device *phydev = phyled->phydev;
if (phydev->attached_dev)
return &phydev->attached_dev->dev;
return NULL;
}
static int __maybe_unused
phy_led_hw_control_get(struct led_classdev *led_cdev,
unsigned long *rules)
{
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_hw_control_get(phydev, phyled->index, rules);
mutex_unlock(&phydev->lock);
return err;
}
static int __maybe_unused
phy_led_hw_control_set(struct led_classdev *led_cdev,
unsigned long rules)
{
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_hw_control_set(phydev, phyled->index, rules);
mutex_unlock(&phydev->lock);
return err;
}
static __maybe_unused int phy_led_hw_is_supported(struct led_classdev *led_cdev,
unsigned long rules)
{
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_hw_is_supported(phydev, phyled->index, rules);
mutex_unlock(&phydev->lock);
return err;
}
static void phy_leds_unregister(struct phy_device *phydev)
{
struct phy_led *phyled;
......@@ -3057,6 +3112,19 @@ static int of_phy_led(struct phy_device *phydev,
cdev->brightness_set_blocking = phy_led_set_brightness;
if (phydev->drv->led_blink_set)
cdev->blink_set = phy_led_blink_set;
#ifdef CONFIG_LEDS_TRIGGERS
if (phydev->drv->led_hw_is_supported &&
phydev->drv->led_hw_control_set &&
phydev->drv->led_hw_control_get) {
cdev->hw_control_is_supported = phy_led_hw_is_supported;
cdev->hw_control_set = phy_led_hw_control_set;
cdev->hw_control_get = phy_led_hw_control_get;
cdev->hw_control_trigger = "netdev";
}
cdev->hw_control_get_device = phy_led_hw_control_get_device;
#endif
cdev->max_brightness = 1;
init_data.devicename = dev_name(&phydev->mdio.dev);
init_data.fwnode = of_fwnode_handle(led);
......
......@@ -1105,6 +1105,39 @@ struct phy_driver {
int (*led_blink_set)(struct phy_device *dev, u8 index,
unsigned long *delay_on,
unsigned long *delay_off);
/**
* @led_hw_is_supported: Can the HW support the given rules.
* @dev: PHY device which has the LED
* @index: Which LED of the PHY device
* @rules The core is interested in these rules
*
* Return 0 if yes, -EOPNOTSUPP if not, or an error code.
*/
int (*led_hw_is_supported)(struct phy_device *dev, u8 index,
unsigned long rules);
/**
* @led_hw_control_set: Set the HW to control the LED
* @dev: PHY device which has the LED
* @index: Which LED of the PHY device
* @rules The rules used to control the LED
*
* Returns 0, or a an error code.
*/
int (*led_hw_control_set)(struct phy_device *dev, u8 index,
unsigned long rules);
/**
* @led_hw_control_get: Get how the HW is controlling the LED
* @dev: PHY device which has the LED
* @index: Which LED of the PHY device
* @rules Pointer to the rules used to control the LED
*
* Set *@rules to how the HW is currently blinking. Returns 0
* on success, or a error code if the current blinking cannot
* be represented in rules, or some other error happens.
*/
int (*led_hw_control_get)(struct phy_device *dev, u8 index,
unsigned long *rules);
};
#define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
struct phy_driver, mdiodrv)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment