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