generic: ar8216: add custom LED support for the AR8327 switch

Signed-off-by: Gabor Juhos <juhosg@openwrt.org>

SVN-Revision: 39338
owl
Gabor Juhos 2014-01-20 10:22:56 +00:00
parent 15d41de1eb
commit 0056ac55af
3 changed files with 408 additions and 0 deletions

View File

@ -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,

View File

@ -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

View File

@ -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 */