diff options
Diffstat (limited to 'target/linux/generic/files/drivers')
| -rw-r--r-- | target/linux/generic/files/drivers/net/phy/swconfig.c | 8 | ||||
| -rw-r--r-- | target/linux/generic/files/drivers/net/phy/swconfig_leds.c | 354 | 
2 files changed, 362 insertions, 0 deletions
diff --git a/target/linux/generic/files/drivers/net/phy/swconfig.c b/target/linux/generic/files/drivers/net/phy/swconfig.c index 2038330ba..1f4491ac5 100644 --- a/target/linux/generic/files/drivers/net/phy/swconfig.c +++ b/target/linux/generic/files/drivers/net/phy/swconfig.c @@ -33,6 +33,8 @@  #define SWCONFIG_DEVNAME	"switch%d" +#include "swconfig_leds.c" +  MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>");  MODULE_LICENSE("GPL"); @@ -863,6 +865,7 @@ register_switch(struct switch_dev *dev, struct net_device *netdev)  	struct switch_dev *sdev;  	const int max_switches = 8 * sizeof(unsigned long);  	unsigned long in_use = 0; +	int err;  	int i;  	INIT_LIST_HEAD(&dev->dev_list); @@ -905,6 +908,10 @@ register_switch(struct switch_dev *dev, struct net_device *netdev)  	list_add(&dev->dev_list, &swdevs);  	swconfig_unlock(); +	err = swconfig_create_led_trigger(dev); +	if (err) +		return err; +  	return 0;  }  EXPORT_SYMBOL_GPL(register_switch); @@ -912,6 +919,7 @@ EXPORT_SYMBOL_GPL(register_switch);  void  unregister_switch(struct switch_dev *dev)  { +	swconfig_destroy_led_trigger(dev);  	kfree(dev->portbuf);  	spin_lock(&dev->lock);  	swconfig_lock(); diff --git a/target/linux/generic/files/drivers/net/phy/swconfig_leds.c b/target/linux/generic/files/drivers/net/phy/swconfig_leds.c new file mode 100644 index 000000000..6f54cc15b --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/swconfig_leds.c @@ -0,0 +1,354 @@ +/* + * swconfig_led.c: LED trigger support for the switch configuration API + * + * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifdef CONFIG_SWCONFIG_LEDS + +#include <linux/leds.h> +#include <linux/ctype.h> +#include <linux/device.h> +#include <linux/workqueue.h> + +#define SWCONFIG_LED_TIMER_INTERVAL	(HZ / 10) +#define SWCONFIG_LED_NUM_PORTS		32 + +struct switch_led_trigger { +	struct led_trigger trig; +	struct switch_dev *swdev; + +	struct delayed_work sw_led_work; +	u32 port_mask; +	u32 port_link; +	unsigned long port_traffic[SWCONFIG_LED_NUM_PORTS]; +}; + +struct swconfig_trig_data { +	struct led_classdev *led_cdev; +	struct switch_dev *swdev; + +	rwlock_t lock; +	u32 port_mask; + +	bool prev_link; +	unsigned long prev_traffic; +	enum led_brightness prev_brightness; +}; + +static void +swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, +			     enum led_brightness brightness) +{ +	led_brightness_set(trig_data->led_cdev, brightness); +	trig_data->prev_brightness = brightness; +} + +static void +swconfig_trig_update_port_mask(struct led_trigger *trigger) +{ +	struct list_head *entry; +	struct switch_led_trigger *sw_trig; +	u32 port_mask; + +	if (!trigger) +		return; + +	sw_trig = (void *) trigger; + +	port_mask = 0; +	read_lock(&trigger->leddev_list_lock); +	list_for_each(entry, &trigger->led_cdevs) { +		struct led_classdev *led_cdev; +		struct swconfig_trig_data *trig_data; + +		led_cdev = list_entry(entry, struct led_classdev, trig_list); +		trig_data = led_cdev->trigger_data; +		if (trig_data) { +			read_lock(&trig_data->lock); +			port_mask |= trig_data->port_mask; +			read_unlock(&trig_data->lock); +		} +	} +	read_unlock(&trigger->leddev_list_lock); + +	sw_trig->port_mask = port_mask; + +	if (port_mask) +		schedule_delayed_work(&sw_trig->sw_led_work, +				      SWCONFIG_LED_TIMER_INTERVAL); +	else +		cancel_delayed_work_sync(&sw_trig->sw_led_work); +} + +static ssize_t +swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, +			      const char *buf, size_t size) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct swconfig_trig_data *trig_data = led_cdev->trigger_data; +	unsigned long port_mask; +	ssize_t ret = -EINVAL; +	char *after; +	size_t count; + +	port_mask = simple_strtoul(buf, &after, 16); +	count =	after - buf; + +	if (*after && isspace(*after)) +		count++; + +	if (count == size) { +		bool changed; + +		write_lock(&trig_data->lock); + +		changed = (trig_data->port_mask != port_mask); +		if (changed) { +			trig_data->port_mask = port_mask; +			if (port_mask == 0) +				swconfig_trig_set_brightness(trig_data, LED_OFF); +		} + +		write_unlock(&trig_data->lock); + +		if (changed) +			swconfig_trig_update_port_mask(led_cdev->trigger); + +		ret = count; +	} + +	return ret; +} + +static ssize_t +swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, +			     char *buf) +{ +	struct led_classdev *led_cdev = dev_get_drvdata(dev); +	struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + +	read_lock(&trig_data->lock); +	sprintf(buf, "%#x\n", trig_data->port_mask); +	read_unlock(&trig_data->lock); + +	return strlen(buf) + 1; +} + +static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, +		   swconfig_trig_port_mask_store); + +static void +swconfig_trig_activate(struct led_classdev *led_cdev) +{ +	struct switch_led_trigger *sw_trig; +	struct swconfig_trig_data *trig_data; +	int err; + +	if (led_cdev->trigger->activate != swconfig_trig_activate) +		return; + +	trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); +	if (!trig_data) +		return; + +	sw_trig = (void *) led_cdev->trigger; + +	rwlock_init(&trig_data->lock); +	trig_data->led_cdev = led_cdev; +	trig_data->swdev = sw_trig->swdev; +	led_cdev->trigger_data = trig_data; + +	err = device_create_file(led_cdev->dev, &dev_attr_port_mask); +	if (err) +		goto err_free; + +	return; + +err_free: +	led_cdev->trigger_data = NULL; +	kfree(trig_data); +} + +static void +swconfig_trig_deactivate(struct led_classdev *led_cdev) +{ +	struct swconfig_trig_data *trig_data; + +	swconfig_trig_update_port_mask(led_cdev->trigger); + +	trig_data = (void *) led_cdev->trigger_data; +	if (trig_data) { +		device_remove_file(led_cdev->dev, &dev_attr_port_mask); +		kfree(trig_data); +	} +} + +static void +swconfig_trig_led_event(struct switch_led_trigger *sw_trig, +			struct led_classdev *led_cdev) +{ +	struct swconfig_trig_data *trig_data; +	u32 port_mask; +	bool link; + +	trig_data = led_cdev->trigger_data; +	if (!trig_data) +		return; + +	read_lock(&trig_data->lock); +	port_mask = trig_data->port_mask; +	read_unlock(&trig_data->lock); + +	link = !!(sw_trig->port_link & port_mask); +	if (!link) { +		if (link != trig_data->prev_link) +			led_brightness_set(trig_data->led_cdev, LED_OFF); +	} else { +		unsigned long traffic; +		int i; + +		traffic = 0; +		for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { +			if (port_mask & (1 << i)) +				traffic += sw_trig->port_traffic[i]; +		} + +		if (trig_data->prev_brightness != LED_FULL) +			swconfig_trig_set_brightness(trig_data, LED_FULL); +		else if (traffic != trig_data->prev_traffic) +			swconfig_trig_set_brightness(trig_data, LED_OFF); + +		trig_data->prev_traffic = traffic; +	} + +	trig_data->prev_link = link; +} + +static void +swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) +{ +	struct list_head *entry; +	struct led_trigger *trigger; + +	trigger = &sw_trig->trig; +	read_lock(&trigger->leddev_list_lock); +	list_for_each(entry, &trigger->led_cdevs) { +		struct led_classdev *led_cdev; + +		led_cdev = list_entry(entry, struct led_classdev, trig_list); +		swconfig_trig_led_event(sw_trig, led_cdev); +	} +	read_unlock(&trigger->leddev_list_lock); +} + +static void +swconfig_led_work_func(struct work_struct *work) +{ +	struct switch_led_trigger *sw_trig; +	struct switch_dev *swdev; +	u32 port_mask; +	u32 link; +	int i; + +	sw_trig = container_of(work, struct switch_led_trigger, +			       sw_led_work.work); + +	port_mask = sw_trig->port_mask; +	swdev = sw_trig->swdev; + +	link = 0; +	for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { +		u32 port_bit; + +		port_bit = BIT(i); +		if ((port_mask & port_bit) == 0) +			continue; + +		if (swdev->ops->get_port_link) { +			struct switch_port_link port_link; + +			memset(&port_link, '\0', sizeof(port_link)); +			swdev->ops->get_port_link(swdev, i, &port_link); + +			if (port_link.link) +				link |= port_bit; +		} + +		if (swdev->ops->get_port_stats) { +			struct switch_port_stats port_stats; + +			memset(&port_stats, '\0', sizeof(port_stats)); +			swdev->ops->get_port_stats(swdev, i, &port_stats); +			sw_trig->port_traffic[i] = port_stats.tx_bytes + +						   port_stats.rx_bytes; +		} +	} + +	sw_trig->port_link = link; + +	swconfig_trig_update_leds(sw_trig); + +	schedule_delayed_work(&sw_trig->sw_led_work, +			      SWCONFIG_LED_TIMER_INTERVAL); +} + +static int +swconfig_create_led_trigger(struct switch_dev *swdev) +{ +	struct switch_led_trigger *sw_trig; +	int err; + +	if (!swdev->ops->get_port_link) +		return 0; + +	sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); +	if (!sw_trig) +		return -ENOMEM; + +	sw_trig->swdev = swdev; +	sw_trig->trig.name = swdev->devname; +	sw_trig->trig.activate = swconfig_trig_activate; +	sw_trig->trig.deactivate = swconfig_trig_deactivate; + +	INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); + +	err = led_trigger_register(&sw_trig->trig); +	if (err) +		goto err_free; + +	swdev->led_trigger = sw_trig; + +	return 0; + +err_free: +	kfree(sw_trig); +	return err; +} + +static void +swconfig_destroy_led_trigger(struct switch_dev *swdev) +{ +	struct switch_led_trigger *sw_trig; + +	sw_trig = swdev->led_trigger; +	if (sw_trig) { +		cancel_delayed_work_sync(&sw_trig->sw_led_work); +		led_trigger_unregister(&sw_trig->trig); +		kfree(sw_trig); +	} +} + +#else /* SWCONFIG_LEDS */ +static inline int +swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } + +static inline void +swconfig_destroy_led_trigger(struct switch_dev *swdev) { } +#endif /* CONFIG_SWCONFIG_LEDS */  | 
