840 lines
22 KiB
C
840 lines
22 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
#include <linux/bitfield.h>
|
||
|
#include <linux/leds.h>
|
||
|
#include <linux/property.h>
|
||
|
|
||
|
#include "chip.h"
|
||
|
#include "global2.h"
|
||
|
#include "port.h"
|
||
|
|
||
|
/* Offset 0x16: LED control */
|
||
|
|
||
|
static int mv88e6xxx_port_led_write(struct mv88e6xxx_chip *chip, int port, u16 reg)
|
||
|
{
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_UPDATE;
|
||
|
|
||
|
return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, reg);
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_port_led_read(struct mv88e6xxx_chip *chip, int port,
|
||
|
u16 ptr, u16 *val)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, ptr);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_LED_CONTROL, val);
|
||
|
*val &= 0x3ff;
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_led_brightness_set(struct mv88e6xxx_port *p, int led,
|
||
|
int brightness)
|
||
|
{
|
||
|
u16 reg;
|
||
|
int err;
|
||
|
|
||
|
err = mv88e6xxx_port_led_read(p->chip, p->port,
|
||
|
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
|
||
|
®);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
if (led == 1)
|
||
|
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
|
||
|
else
|
||
|
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
|
||
|
|
||
|
if (brightness) {
|
||
|
/* Selector 0x0f == Force LED ON */
|
||
|
if (led == 1)
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELF;
|
||
|
else
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELF;
|
||
|
} else {
|
||
|
/* Selector 0x0e == Force LED OFF */
|
||
|
if (led == 1)
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE;
|
||
|
else
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE;
|
||
|
}
|
||
|
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
|
||
|
|
||
|
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_led0_brightness_set_blocking(struct led_classdev *ldev,
|
||
|
enum led_brightness brightness)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
|
||
|
int err;
|
||
|
|
||
|
mv88e6xxx_reg_lock(p->chip);
|
||
|
err = mv88e6xxx_led_brightness_set(p, 0, brightness);
|
||
|
mv88e6xxx_reg_unlock(p->chip);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_led1_brightness_set_blocking(struct led_classdev *ldev,
|
||
|
enum led_brightness brightness)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
|
||
|
int err;
|
||
|
|
||
|
mv88e6xxx_reg_lock(p->chip);
|
||
|
err = mv88e6xxx_led_brightness_set(p, 1, brightness);
|
||
|
mv88e6xxx_reg_unlock(p->chip);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
struct mv88e6xxx_led_hwconfig {
|
||
|
int led;
|
||
|
u8 portmask;
|
||
|
unsigned long rules;
|
||
|
bool fiber;
|
||
|
bool blink_activity;
|
||
|
u16 selector;
|
||
|
};
|
||
|
|
||
|
/* The following is a lookup table to check what rules we can support on a
|
||
|
* certain LED given restrictions such as that some rules only work with fiber
|
||
|
* (SFP) connections and some blink on activity by default.
|
||
|
*/
|
||
|
#define MV88E6XXX_PORTS_0_3 (BIT(0) | BIT(1) | BIT(2) | BIT(3))
|
||
|
#define MV88E6XXX_PORTS_4_5 (BIT(4) | BIT(5))
|
||
|
#define MV88E6XXX_PORT_4 BIT(4)
|
||
|
#define MV88E6XXX_PORT_5 BIT(5)
|
||
|
|
||
|
/* Entries are listed in selector order.
|
||
|
*
|
||
|
* These configurations vary across different switch families, list
|
||
|
* different tables per-family here.
|
||
|
*/
|
||
|
static const struct mv88e6xxx_led_hwconfig mv88e6352_led_hwconfigs[] = {
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORT_4,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL0,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORT_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL0,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_4_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_100),
|
||
|
.blink_activity = true,
|
||
|
.fiber = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_4_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.fiber = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_4_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.fiber = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_4_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_100),
|
||
|
.blink_activity = true,
|
||
|
.fiber = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL3,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_4_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.fiber = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORT_4,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL4,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORT_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL5,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORT_4,
|
||
|
.rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORT_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL7,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL7,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL8,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORT_5,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_10),
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL9,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_100),
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL9,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_10),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELA,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_100),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELA,
|
||
|
},
|
||
|
{
|
||
|
.led = 0,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELB,
|
||
|
},
|
||
|
{
|
||
|
.led = 1,
|
||
|
.portmask = MV88E6XXX_PORTS_0_3,
|
||
|
.rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
|
||
|
.blink_activity = true,
|
||
|
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELB,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
/* mv88e6xxx_led_match_selector() - look up the appropriate LED mode selector
|
||
|
* @p: port state container
|
||
|
* @led: LED number, 0 or 1
|
||
|
* @blink_activity: blink the LED (usually blink on indicated activity)
|
||
|
* @fiber: the link is connected to fiber such as SFP
|
||
|
* @rules: LED status flags from the LED classdev core
|
||
|
* @selector: fill in the selector in this parameter with an OR operation
|
||
|
*/
|
||
|
static int mv88e6xxx_led_match_selector(struct mv88e6xxx_port *p, int led, bool blink_activity,
|
||
|
bool fiber, unsigned long rules, u16 *selector)
|
||
|
{
|
||
|
const struct mv88e6xxx_led_hwconfig *conf;
|
||
|
int i;
|
||
|
|
||
|
/* No rules means we turn the LED off */
|
||
|
if (!rules) {
|
||
|
if (led == 1)
|
||
|
*selector |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE;
|
||
|
else
|
||
|
*selector |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* TODO: these rules are for MV88E6352, when adding other families,
|
||
|
* think about making sure you select the table that match the
|
||
|
* specific switch family.
|
||
|
*/
|
||
|
for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) {
|
||
|
conf = &mv88e6352_led_hwconfigs[i];
|
||
|
|
||
|
if (conf->led != led)
|
||
|
continue;
|
||
|
|
||
|
if (!(conf->portmask & BIT(p->port)))
|
||
|
continue;
|
||
|
|
||
|
if (conf->blink_activity != blink_activity)
|
||
|
continue;
|
||
|
|
||
|
if (conf->fiber != fiber)
|
||
|
continue;
|
||
|
|
||
|
if (conf->rules == rules) {
|
||
|
dev_dbg(p->chip->dev, "port%d LED %d set selector %04x for rules %08lx\n",
|
||
|
p->port, led, conf->selector, rules);
|
||
|
*selector |= conf->selector;
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
/* mv88e6xxx_led_match_selector() - find Linux netdev rules from a selector value
|
||
|
* @p: port state container
|
||
|
* @selector: the selector value from the LED actity register
|
||
|
* @led: LED number, 0 or 1
|
||
|
* @rules: Linux netdev activity rules found from selector
|
||
|
*/
|
||
|
static int
|
||
|
mv88e6xxx_led_match_rule(struct mv88e6xxx_port *p, u16 selector, int led, unsigned long *rules)
|
||
|
{
|
||
|
const struct mv88e6xxx_led_hwconfig *conf;
|
||
|
int i;
|
||
|
|
||
|
/* Find the selector in the table, we just look for the right selector
|
||
|
* and ignore if the activity has special properties such as blinking
|
||
|
* or is fiber-only.
|
||
|
*/
|
||
|
for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) {
|
||
|
conf = &mv88e6352_led_hwconfigs[i];
|
||
|
|
||
|
if (conf->led != led)
|
||
|
continue;
|
||
|
|
||
|
if (!(conf->portmask & BIT(p->port)))
|
||
|
continue;
|
||
|
|
||
|
if (conf->selector == selector) {
|
||
|
dev_dbg(p->chip->dev, "port%d LED %d has selector %04x, rules %08lx\n",
|
||
|
p->port, led, selector, conf->rules);
|
||
|
*rules = conf->rules;
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* mv88e6xxx_led_get_selector() - get the appropriate LED mode selector
|
||
|
* @p: port state container
|
||
|
* @led: LED number, 0 or 1
|
||
|
* @fiber: the link is connected to fiber such as SFP
|
||
|
* @rules: LED status flags from the LED classdev core
|
||
|
* @selector: fill in the selector in this parameter with an OR operation
|
||
|
*/
|
||
|
static int mv88e6xxx_led_get_selector(struct mv88e6xxx_port *p, int led,
|
||
|
bool fiber, unsigned long rules, u16 *selector)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
/* What happens here is that we first try to locate a trigger with solid
|
||
|
* indicator (such as LED is on for a 1000 link) else we try a second
|
||
|
* sweep to find something suitable with a trigger that will blink on
|
||
|
* activity.
|
||
|
*/
|
||
|
err = mv88e6xxx_led_match_selector(p, led, false, fiber, rules, selector);
|
||
|
if (err)
|
||
|
return mv88e6xxx_led_match_selector(p, led, true, fiber, rules, selector);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Sets up the hardware blinking period */
|
||
|
static int mv88e6xxx_led_set_blinking_period(struct mv88e6xxx_port *p, int led,
|
||
|
unsigned long delay_on, unsigned long delay_off)
|
||
|
{
|
||
|
unsigned long period;
|
||
|
u16 reg;
|
||
|
|
||
|
period = delay_on + delay_off;
|
||
|
|
||
|
reg = 0;
|
||
|
|
||
|
switch (period) {
|
||
|
case 21:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_21MS;
|
||
|
break;
|
||
|
case 42:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_42MS;
|
||
|
break;
|
||
|
case 84:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_84MS;
|
||
|
break;
|
||
|
case 168:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_168MS;
|
||
|
break;
|
||
|
case 336:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_336MS;
|
||
|
break;
|
||
|
case 672:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_672MS;
|
||
|
break;
|
||
|
default:
|
||
|
/* Fall back to software blinking */
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* This is essentially PWM duty cycle: how long time of the period
|
||
|
* will the LED be on. Zero isn't great in most cases.
|
||
|
*/
|
||
|
switch (delay_on) {
|
||
|
case 0:
|
||
|
/* This is usually pretty useless and will make the LED look OFF */
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_NONE;
|
||
|
break;
|
||
|
case 21:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS;
|
||
|
break;
|
||
|
case 42:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_42MS;
|
||
|
break;
|
||
|
case 84:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_84MS;
|
||
|
break;
|
||
|
case 168:
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_168MS;
|
||
|
break;
|
||
|
default:
|
||
|
/* Just use something non-zero */
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Set up blink rate */
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_STRETCH_BLINK;
|
||
|
|
||
|
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_led_blink_set(struct mv88e6xxx_port *p, int led,
|
||
|
unsigned long *delay_on, unsigned long *delay_off)
|
||
|
{
|
||
|
u16 reg;
|
||
|
int err;
|
||
|
|
||
|
/* Choose a sensible default 336 ms (~3 Hz) */
|
||
|
if ((*delay_on == 0) && (*delay_off == 0)) {
|
||
|
*delay_on = 168;
|
||
|
*delay_off = 168;
|
||
|
}
|
||
|
|
||
|
/* No off delay is just on */
|
||
|
if (*delay_off == 0)
|
||
|
return mv88e6xxx_led_brightness_set(p, led, 1);
|
||
|
|
||
|
err = mv88e6xxx_led_set_blinking_period(p, led, *delay_on, *delay_off);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
err = mv88e6xxx_port_led_read(p->chip, p->port,
|
||
|
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
|
||
|
®);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
if (led == 1)
|
||
|
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
|
||
|
else
|
||
|
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
|
||
|
|
||
|
/* This will select the forced blinking status */
|
||
|
if (led == 1)
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELD;
|
||
|
else
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELD;
|
||
|
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
|
||
|
|
||
|
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_led0_blink_set(struct led_classdev *ldev,
|
||
|
unsigned long *delay_on,
|
||
|
unsigned long *delay_off)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
|
||
|
int err;
|
||
|
|
||
|
mv88e6xxx_reg_lock(p->chip);
|
||
|
err = mv88e6xxx_led_blink_set(p, 0, delay_on, delay_off);
|
||
|
mv88e6xxx_reg_unlock(p->chip);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_led1_blink_set(struct led_classdev *ldev,
|
||
|
unsigned long *delay_on,
|
||
|
unsigned long *delay_off)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
|
||
|
int err;
|
||
|
|
||
|
mv88e6xxx_reg_lock(p->chip);
|
||
|
err = mv88e6xxx_led_blink_set(p, 1, delay_on, delay_off);
|
||
|
mv88e6xxx_reg_unlock(p->chip);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
mv88e6xxx_led0_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
|
||
|
u16 selector = 0;
|
||
|
|
||
|
return mv88e6xxx_led_get_selector(p, 0, p->fiber, rules, &selector);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
mv88e6xxx_led1_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
|
||
|
u16 selector = 0;
|
||
|
|
||
|
return mv88e6xxx_led_get_selector(p, 1, p->fiber, rules, &selector);
|
||
|
}
|
||
|
|
||
|
static int mv88e6xxx_led_hw_control_set(struct mv88e6xxx_port *p,
|
||
|
int led, unsigned long rules)
|
||
|
{
|
||
|
u16 reg;
|
||
|
int err;
|
||
|
|
||
|
err = mv88e6xxx_port_led_read(p->chip, p->port,
|
||
|
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
|
||
|
®);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
if (led == 1)
|
||
|
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
|
||
|
else
|
||
|
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
|
||
|
|
||
|
err = mv88e6xxx_led_get_selector(p, led, p->fiber, rules, ®);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
|
||
|
|
||
|
if (led == 0)
|
||
|
dev_dbg(p->chip->dev, "LED 0 hw control on port %d trigger selector 0x%02x\n",
|
||
|
p->port,
|
||
|
(unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK));
|
||
|
else
|
||
|
dev_dbg(p->chip->dev, "LED 1 hw control on port %d trigger selector 0x%02x\n",
|
||
|
p->port,
|
||
|
(unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK) >> 4);
|
||
|
|
||
|
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
mv88e6xxx_led_hw_control_get(struct mv88e6xxx_port *p, int led, unsigned long *rules)
|
||
|
{
|
||
|
u16 val;
|
||
|
int err;
|
||
|
|
||
|
mv88e6xxx_reg_lock(p->chip);
|
||
|
err = mv88e6xxx_port_led_read(p->chip, p->port,
|
||
|
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL, &val);
|
||
|
mv88e6xxx_reg_unlock(p->chip);
|
||
|
if (err)
|
||
|
return err;
|
||
|
|
||
|
/* Mask out the selector bits for this port */
|
||
|
if (led == 1) {
|
||
|
val &= MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
|
||
|
/* It's forced blinking/OFF/ON */
|
||
|
if (val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELD ||
|
||
|
val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELE ||
|
||
|
val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELF) {
|
||
|
*rules = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
} else {
|
||
|
val &= MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
|
||
|
/* It's forced blinking/OFF/ON */
|
||
|
if (val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELD ||
|
||
|
val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELE ||
|
||
|
val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELF) {
|
||
|
*rules = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
err = mv88e6xxx_led_match_rule(p, val, led, rules);
|
||
|
if (!err)
|
||
|
return 0;
|
||
|
|
||
|
dev_dbg(p->chip->dev, "couldn't find matching selector for %04x\n", val);
|
||
|
*rules = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
mv88e6xxx_led0_hw_control_set(struct led_classdev *ldev, unsigned long rules)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
|
||
|
int err;
|
||
|
|
||
|
mv88e6xxx_reg_lock(p->chip);
|
||
|
err = mv88e6xxx_led_hw_control_set(p, 0, rules);
|
||
|
mv88e6xxx_reg_unlock(p->chip);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
mv88e6xxx_led1_hw_control_set(struct led_classdev *ldev, unsigned long rules)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
|
||
|
int err;
|
||
|
|
||
|
mv88e6xxx_reg_lock(p->chip);
|
||
|
err = mv88e6xxx_led_hw_control_set(p, 1, rules);
|
||
|
mv88e6xxx_reg_unlock(p->chip);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
mv88e6xxx_led0_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
|
||
|
|
||
|
return mv88e6xxx_led_hw_control_get(p, 0, rules);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
mv88e6xxx_led1_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
|
||
|
|
||
|
return mv88e6xxx_led_hw_control_get(p, 1, rules);
|
||
|
}
|
||
|
|
||
|
static struct device *mv88e6xxx_led_hw_control_get_device(struct mv88e6xxx_port *p)
|
||
|
{
|
||
|
struct dsa_port *dp;
|
||
|
|
||
|
dp = dsa_to_port(p->chip->ds, p->port);
|
||
|
if (!dp)
|
||
|
return NULL;
|
||
|
if (dp->user)
|
||
|
return &dp->user->dev;
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct device *
|
||
|
mv88e6xxx_led0_hw_control_get_device(struct led_classdev *ldev)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
|
||
|
|
||
|
return mv88e6xxx_led_hw_control_get_device(p);
|
||
|
}
|
||
|
|
||
|
static struct device *
|
||
|
mv88e6xxx_led1_hw_control_get_device(struct led_classdev *ldev)
|
||
|
{
|
||
|
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
|
||
|
|
||
|
return mv88e6xxx_led_hw_control_get_device(p);
|
||
|
}
|
||
|
|
||
|
int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port)
|
||
|
{
|
||
|
struct fwnode_handle *led = NULL, *leds = NULL;
|
||
|
struct led_init_data init_data = { };
|
||
|
enum led_default_state state;
|
||
|
struct mv88e6xxx_port *p;
|
||
|
struct led_classdev *l;
|
||
|
struct device *dev;
|
||
|
u32 led_num;
|
||
|
int ret;
|
||
|
|
||
|
/* LEDs are on ports 1,2,3,4, 5 and 6 (index 0..5), no more */
|
||
|
if (port > 5)
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
p = &chip->ports[port];
|
||
|
if (!p->fwnode)
|
||
|
return 0;
|
||
|
|
||
|
dev = chip->dev;
|
||
|
|
||
|
leds = fwnode_get_named_child_node(p->fwnode, "leds");
|
||
|
if (!leds) {
|
||
|
dev_dbg(dev, "No Leds node specified in device tree for port %d!\n",
|
||
|
port);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
fwnode_for_each_child_node(leds, led) {
|
||
|
/* Reg represent the led number of the port, max 2
|
||
|
* LEDs can be connected to each port, in some designs
|
||
|
* only one LED is connected.
|
||
|
*/
|
||
|
if (fwnode_property_read_u32(led, "reg", &led_num))
|
||
|
continue;
|
||
|
if (led_num > 1) {
|
||
|
dev_err(dev, "invalid LED specified port %d\n", port);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (led_num == 0)
|
||
|
l = &p->led0;
|
||
|
else
|
||
|
l = &p->led1;
|
||
|
|
||
|
state = led_init_default_state_get(led);
|
||
|
switch (state) {
|
||
|
case LEDS_DEFSTATE_ON:
|
||
|
l->brightness = 1;
|
||
|
mv88e6xxx_led_brightness_set(p, led_num, 1);
|
||
|
break;
|
||
|
case LEDS_DEFSTATE_KEEP:
|
||
|
break;
|
||
|
default:
|
||
|
l->brightness = 0;
|
||
|
mv88e6xxx_led_brightness_set(p, led_num, 0);
|
||
|
}
|
||
|
|
||
|
l->max_brightness = 1;
|
||
|
if (led_num == 0) {
|
||
|
l->brightness_set_blocking = mv88e6xxx_led0_brightness_set_blocking;
|
||
|
l->blink_set = mv88e6xxx_led0_blink_set;
|
||
|
l->hw_control_is_supported = mv88e6xxx_led0_hw_control_is_supported;
|
||
|
l->hw_control_set = mv88e6xxx_led0_hw_control_set;
|
||
|
l->hw_control_get = mv88e6xxx_led0_hw_control_get;
|
||
|
l->hw_control_get_device = mv88e6xxx_led0_hw_control_get_device;
|
||
|
} else {
|
||
|
l->brightness_set_blocking = mv88e6xxx_led1_brightness_set_blocking;
|
||
|
l->blink_set = mv88e6xxx_led1_blink_set;
|
||
|
l->hw_control_is_supported = mv88e6xxx_led1_hw_control_is_supported;
|
||
|
l->hw_control_set = mv88e6xxx_led1_hw_control_set;
|
||
|
l->hw_control_get = mv88e6xxx_led1_hw_control_get;
|
||
|
l->hw_control_get_device = mv88e6xxx_led1_hw_control_get_device;
|
||
|
}
|
||
|
l->hw_control_trigger = "netdev";
|
||
|
|
||
|
init_data.default_label = ":port";
|
||
|
init_data.fwnode = led;
|
||
|
init_data.devname_mandatory = true;
|
||
|
init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d:0%d", chip->info->name,
|
||
|
port, led_num);
|
||
|
if (!init_data.devicename)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
ret = devm_led_classdev_register_ext(dev, l, &init_data);
|
||
|
kfree(init_data.devicename);
|
||
|
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Failed to init LED %d for port %d", led_num, port);
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|