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