generic: ar8216: add custom LED support for the AR8327 switch
Signed-off-by: Gabor Juhos <juhosg@openwrt.org> SVN-Revision: 39338
This commit is contained in:
parent
15d41de1eb
commit
0056ac55af
3 changed files with 408 additions and 0 deletions
|
@ -34,6 +34,8 @@
|
|||
#include <linux/ar8216_platform.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
#include "ar8216.h"
|
||||
|
||||
|
@ -82,9 +84,40 @@ struct ar8xxx_chip {
|
|||
unsigned num_mibs;
|
||||
};
|
||||
|
||||
enum ar8327_led_pattern {
|
||||
AR8327_LED_PATTERN_OFF = 0,
|
||||
AR8327_LED_PATTERN_BLINK,
|
||||
AR8327_LED_PATTERN_ON,
|
||||
AR8327_LED_PATTERN_RULE,
|
||||
};
|
||||
|
||||
struct ar8327_led_entry {
|
||||
unsigned reg;
|
||||
unsigned shift;
|
||||
};
|
||||
|
||||
struct ar8327_led {
|
||||
struct led_classdev cdev;
|
||||
struct ar8xxx_priv *sw_priv;
|
||||
|
||||
char *name;
|
||||
bool active_low;
|
||||
u8 led_num;
|
||||
enum ar8327_led_mode mode;
|
||||
|
||||
struct mutex mutex;
|
||||
spinlock_t lock;
|
||||
struct work_struct led_work;
|
||||
bool enable_hw_mode;
|
||||
enum ar8327_led_pattern pattern;
|
||||
};
|
||||
|
||||
struct ar8327_data {
|
||||
u32 port0_status;
|
||||
u32 port6_status;
|
||||
|
||||
struct ar8327_led **leds;
|
||||
unsigned int num_leds;
|
||||
};
|
||||
|
||||
struct ar8xxx_priv {
|
||||
|
@ -1090,6 +1123,317 @@ ar8327_get_port_init_status(struct ar8327_port_cfg *cfg)
|
|||
return t;
|
||||
}
|
||||
|
||||
#define AR8327_LED_ENTRY(_num, _reg, _shift) \
|
||||
[_num] = { .reg = (_reg), .shift = (_shift) }
|
||||
|
||||
static const struct ar8327_led_entry
|
||||
ar8327_led_map[AR8327_NUM_LEDS] = {
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14),
|
||||
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12),
|
||||
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18),
|
||||
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24),
|
||||
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30),
|
||||
AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30),
|
||||
};
|
||||
|
||||
static void
|
||||
ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num,
|
||||
enum ar8327_led_pattern pattern)
|
||||
{
|
||||
const struct ar8327_led_entry *entry;
|
||||
|
||||
entry = &ar8327_led_map[led_num];
|
||||
ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg),
|
||||
(3 << entry->shift), pattern << entry->shift);
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_led_work_func(struct work_struct *work)
|
||||
{
|
||||
struct ar8327_led *aled;
|
||||
u8 pattern;
|
||||
|
||||
aled = container_of(work, struct ar8327_led, led_work);
|
||||
|
||||
spin_lock(&aled->lock);
|
||||
pattern = aled->pattern;
|
||||
spin_unlock(&aled->lock);
|
||||
|
||||
ar8327_set_led_pattern(aled->sw_priv, aled->led_num,
|
||||
pattern);
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern)
|
||||
{
|
||||
if (aled->pattern == pattern)
|
||||
return;
|
||||
|
||||
aled->pattern = pattern;
|
||||
schedule_work(&aled->led_work);
|
||||
}
|
||||
|
||||
static inline struct ar8327_led *
|
||||
led_cdev_to_ar8327_led(struct led_classdev *led_cdev)
|
||||
{
|
||||
return container_of(led_cdev, struct ar8327_led, cdev);
|
||||
}
|
||||
|
||||
static int
|
||||
ar8327_led_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
|
||||
|
||||
if (*delay_on == 0 && *delay_off == 0) {
|
||||
*delay_on = 125;
|
||||
*delay_off = 125;
|
||||
}
|
||||
|
||||
if (*delay_on != 125 || *delay_off != 125) {
|
||||
/*
|
||||
* The hardware only supports blinking at 4Hz. Fall back
|
||||
* to software implementation in other cases.
|
||||
*/
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
spin_lock(&aled->lock);
|
||||
|
||||
aled->enable_hw_mode = false;
|
||||
ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK);
|
||||
|
||||
spin_unlock(&aled->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_led_set_brightness(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
|
||||
u8 pattern;
|
||||
bool active;
|
||||
|
||||
active = (brightness != LED_OFF);
|
||||
active ^= aled->active_low;
|
||||
|
||||
pattern = (active) ? AR8327_LED_PATTERN_ON :
|
||||
AR8327_LED_PATTERN_OFF;
|
||||
|
||||
spin_lock(&aled->lock);
|
||||
|
||||
aled->enable_hw_mode = false;
|
||||
ar8327_led_schedule_change(aled, pattern);
|
||||
|
||||
spin_unlock(&aled->lock);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
ar8327_led_enable_hw_mode_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
|
||||
ssize_t ret = 0;
|
||||
|
||||
spin_lock(&aled->lock);
|
||||
ret += sprintf(buf, "%d\n", aled->enable_hw_mode);
|
||||
spin_unlock(&aled->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
ar8327_led_enable_hw_mode_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
|
||||
u8 pattern;
|
||||
u8 value;
|
||||
int ret;
|
||||
|
||||
ret = kstrtou8(buf, 10, &value);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&aled->lock);
|
||||
|
||||
aled->enable_hw_mode = !!value;
|
||||
if (aled->enable_hw_mode)
|
||||
pattern = AR8327_LED_PATTERN_RULE;
|
||||
else
|
||||
pattern = AR8327_LED_PATTERN_OFF;
|
||||
|
||||
ar8327_led_schedule_change(aled, pattern);
|
||||
|
||||
spin_unlock(&aled->lock);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(enable_hw_mode, S_IRUGO | S_IWUSR,
|
||||
ar8327_led_enable_hw_mode_show,
|
||||
ar8327_led_enable_hw_mode_store);
|
||||
|
||||
static int
|
||||
ar8327_led_register(struct ar8xxx_priv *priv, struct ar8327_led *aled)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = led_classdev_register(NULL, &aled->cdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (aled->mode == AR8327_LED_MODE_HW) {
|
||||
ret = device_create_file(aled->cdev.dev,
|
||||
&dev_attr_enable_hw_mode);
|
||||
if (ret)
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
led_classdev_unregister(&aled->cdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_led_unregister(struct ar8327_led *aled)
|
||||
{
|
||||
if (aled->mode == AR8327_LED_MODE_HW)
|
||||
device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode);
|
||||
|
||||
led_classdev_unregister(&aled->cdev);
|
||||
cancel_work_sync(&aled->led_work);
|
||||
}
|
||||
|
||||
static int
|
||||
ar8327_led_create(struct ar8xxx_priv *priv,
|
||||
const struct ar8327_led_info *led_info)
|
||||
{
|
||||
struct ar8327_data *data = &priv->chip_data.ar8327;
|
||||
struct ar8327_led *aled;
|
||||
int ret;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS))
|
||||
return 0;
|
||||
|
||||
if (!led_info->name)
|
||||
return -EINVAL;
|
||||
|
||||
if (led_info->led_num >= AR8327_NUM_LEDS)
|
||||
return -EINVAL;
|
||||
|
||||
aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1,
|
||||
GFP_KERNEL);
|
||||
if (!aled)
|
||||
return -ENOMEM;
|
||||
|
||||
aled->sw_priv = priv;
|
||||
aled->led_num = led_info->led_num;
|
||||
aled->active_low = led_info->active_low;
|
||||
aled->mode = led_info->mode;
|
||||
|
||||
if (aled->mode == AR8327_LED_MODE_HW)
|
||||
aled->enable_hw_mode = true;
|
||||
|
||||
aled->name = (char *)(aled + 1);
|
||||
strcpy(aled->name, led_info->name);
|
||||
|
||||
aled->cdev.name = aled->name;
|
||||
aled->cdev.brightness_set = ar8327_led_set_brightness;
|
||||
aled->cdev.blink_set = ar8327_led_blink_set;
|
||||
aled->cdev.default_trigger = led_info->default_trigger;
|
||||
|
||||
spin_lock_init(&aled->lock);
|
||||
mutex_init(&aled->mutex);
|
||||
INIT_WORK(&aled->led_work, ar8327_led_work_func);
|
||||
|
||||
ret = ar8327_led_register(priv, aled);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
data->leds[data->num_leds++] = aled;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
kfree(aled);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_led_destroy(struct ar8327_led *aled)
|
||||
{
|
||||
ar8327_led_unregister(aled);
|
||||
kfree(aled);
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_leds_init(struct ar8xxx_priv *priv)
|
||||
{
|
||||
struct ar8327_data *data;
|
||||
unsigned i;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS))
|
||||
return;
|
||||
|
||||
data = &priv->chip_data.ar8327;
|
||||
|
||||
for (i = 0; i < data->num_leds; i++) {
|
||||
struct ar8327_led *aled;
|
||||
|
||||
aled = data->leds[i];
|
||||
|
||||
if (aled->enable_hw_mode)
|
||||
aled->pattern = AR8327_LED_PATTERN_RULE;
|
||||
else
|
||||
aled->pattern = AR8327_LED_PATTERN_OFF;
|
||||
|
||||
ar8327_set_led_pattern(priv, aled->led_num, aled->pattern);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_leds_cleanup(struct ar8xxx_priv *priv)
|
||||
{
|
||||
struct ar8327_data *data = &priv->chip_data.ar8327;
|
||||
unsigned i;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS))
|
||||
return;
|
||||
|
||||
for (i = 0; i < data->num_leds; i++) {
|
||||
struct ar8327_led *aled;
|
||||
|
||||
aled = data->leds[i];
|
||||
ar8327_led_destroy(aled);
|
||||
}
|
||||
|
||||
kfree(data->leds);
|
||||
}
|
||||
|
||||
static int
|
||||
ar8327_hw_config_pdata(struct ar8xxx_priv *priv,
|
||||
struct ar8327_platform_data *pdata)
|
||||
|
@ -1159,6 +1503,18 @@ ar8327_hw_config_pdata(struct ar8xxx_priv *priv,
|
|||
|
||||
priv->write(priv, AR8327_REG_POWER_ON_STRIP, new_pos);
|
||||
|
||||
if (pdata->leds && pdata->num_leds) {
|
||||
int i;
|
||||
|
||||
data->leds = kzalloc(pdata->num_leds * sizeof(void *),
|
||||
GFP_KERNEL);
|
||||
if (!data->leds)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++)
|
||||
ar8327_led_create(priv, &pdata->leds[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1222,6 +1578,8 @@ ar8327_hw_init(struct ar8xxx_priv *priv)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ar8327_leds_init(priv);
|
||||
|
||||
bus = priv->mii_bus;
|
||||
for (i = 0; i < AR8327_NUM_PHYS; i++) {
|
||||
ar8327_phy_fixup(priv, i);
|
||||
|
@ -1239,6 +1597,12 @@ ar8327_hw_init(struct ar8xxx_priv *priv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_cleanup(struct ar8xxx_priv *priv)
|
||||
{
|
||||
ar8327_leds_cleanup(priv);
|
||||
}
|
||||
|
||||
static void
|
||||
ar8327_init_globals(struct ar8xxx_priv *priv)
|
||||
{
|
||||
|
@ -1395,6 +1759,7 @@ ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 egress, u32 ingress,
|
|||
static const struct ar8xxx_chip ar8327_chip = {
|
||||
.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS,
|
||||
.hw_init = ar8327_hw_init,
|
||||
.cleanup = ar8327_cleanup,
|
||||
.init_globals = ar8327_init_globals,
|
||||
.init_port = ar8327_init_port,
|
||||
.setup_port = ar8327_setup_port,
|
||||
|
|
|
@ -290,8 +290,10 @@
|
|||
#define AR8316_POSTRIP_POWER_ON_SEL BIT(31)
|
||||
|
||||
#define AR8327_NUM_PORTS 7
|
||||
#define AR8327_NUM_LEDS 15
|
||||
#define AR8327_NUM_PHYS 5
|
||||
#define AR8327_PORTS_ALL 0x7f
|
||||
#define AR8327_NUM_LED_CTRL_REGS 4
|
||||
|
||||
#define AR8327_REG_MASK 0x000
|
||||
|
||||
|
@ -343,6 +345,7 @@
|
|||
#define AR8327_MIB_CPU_KEEP BIT(20)
|
||||
|
||||
#define AR8327_REG_SERVICE_TAG 0x048
|
||||
#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4)
|
||||
#define AR8327_REG_LED_CTRL0 0x050
|
||||
#define AR8327_REG_LED_CTRL1 0x054
|
||||
#define AR8327_REG_LED_CTRL2 0x058
|
||||
|
|
|
@ -76,6 +76,43 @@ struct ar8327_led_cfg {
|
|||
bool open_drain;
|
||||
};
|
||||
|
||||
enum ar8327_led_num {
|
||||
AR8327_LED_PHY0_0 = 0,
|
||||
AR8327_LED_PHY0_1,
|
||||
AR8327_LED_PHY0_2,
|
||||
AR8327_LED_PHY1_0,
|
||||
AR8327_LED_PHY1_1,
|
||||
AR8327_LED_PHY1_2,
|
||||
AR8327_LED_PHY2_0,
|
||||
AR8327_LED_PHY2_1,
|
||||
AR8327_LED_PHY2_2,
|
||||
AR8327_LED_PHY3_0,
|
||||
AR8327_LED_PHY3_1,
|
||||
AR8327_LED_PHY3_2,
|
||||
AR8327_LED_PHY4_0,
|
||||
AR8327_LED_PHY4_1,
|
||||
AR8327_LED_PHY4_2,
|
||||
};
|
||||
|
||||
enum ar8327_led_mode {
|
||||
AR8327_LED_MODE_HW = 0,
|
||||
AR8327_LED_MODE_SW,
|
||||
};
|
||||
|
||||
struct ar8327_led_info {
|
||||
const char *name;
|
||||
const char *default_trigger;
|
||||
bool active_low;
|
||||
enum ar8327_led_num led_num;
|
||||
enum ar8327_led_mode mode;
|
||||
};
|
||||
|
||||
#define AR8327_LED_INFO(_led, _mode, _name) { \
|
||||
.name = (_name), \
|
||||
.led_num = AR8327_LED_ ## _led, \
|
||||
.mode = AR8327_LED_MODE_ ## _mode \
|
||||
}
|
||||
|
||||
struct ar8327_platform_data {
|
||||
struct ar8327_pad_cfg *pad0_cfg;
|
||||
struct ar8327_pad_cfg *pad5_cfg;
|
||||
|
@ -86,6 +123,9 @@ struct ar8327_platform_data {
|
|||
struct ar8327_led_cfg *led_cfg;
|
||||
|
||||
int (*get_port_link)(unsigned port);
|
||||
|
||||
unsigned num_leds;
|
||||
const struct ar8327_led_info *leds;
|
||||
};
|
||||
|
||||
#endif /* AR8216_PLATFORM_H */
|
Loading…
Reference in a new issue