1/* 2 * swconfig_led.c: LED trigger support for the switch configuration API 3 * 4 * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 2 9 * of the License, or (at your option) any later version. 10 * 11 */ 12 13#ifdef CONFIG_SWCONFIG_LEDS 14 15#include <linux/leds.h> 16#include <linux/ctype.h> 17#include <linux/device.h> 18#include <linux/workqueue.h> 19 20#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) 21#define SWCONFIG_LED_NUM_PORTS 32 22 23struct switch_led_trigger { 24 struct led_trigger trig; 25 struct switch_dev *swdev; 26 27 struct delayed_work sw_led_work; 28 u32 port_mask; 29 u32 port_link; 30 unsigned long port_traffic[SWCONFIG_LED_NUM_PORTS]; 31}; 32 33struct swconfig_trig_data { 34 struct led_classdev *led_cdev; 35 struct switch_dev *swdev; 36 37 rwlock_t lock; 38 u32 port_mask; 39 40 bool prev_link; 41 unsigned long prev_traffic; 42 enum led_brightness prev_brightness; 43}; 44 45static void 46swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, 47 enum led_brightness brightness) 48{ 49 led_brightness_set(trig_data->led_cdev, brightness); 50 trig_data->prev_brightness = brightness; 51} 52 53static void 54swconfig_trig_update_port_mask(struct led_trigger *trigger) 55{ 56 struct list_head *entry; 57 struct switch_led_trigger *sw_trig; 58 u32 port_mask; 59 60 if (!trigger) 61 return; 62 63 sw_trig = (void *) trigger; 64 65 port_mask = 0; 66 read_lock(&trigger->leddev_list_lock); 67 list_for_each(entry, &trigger->led_cdevs) { 68 struct led_classdev *led_cdev; 69 struct swconfig_trig_data *trig_data; 70 71 led_cdev = list_entry(entry, struct led_classdev, trig_list); 72 trig_data = led_cdev->trigger_data; 73 if (trig_data) { 74 read_lock(&trig_data->lock); 75 port_mask |= trig_data->port_mask; 76 read_unlock(&trig_data->lock); 77 } 78 } 79 read_unlock(&trigger->leddev_list_lock); 80 81 sw_trig->port_mask = port_mask; 82 83 if (port_mask) 84 schedule_delayed_work(&sw_trig->sw_led_work, 85 SWCONFIG_LED_TIMER_INTERVAL); 86 else 87 cancel_delayed_work_sync(&sw_trig->sw_led_work); 88} 89 90static ssize_t 91swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, 92 const char *buf, size_t size) 93{ 94 struct led_classdev *led_cdev = dev_get_drvdata(dev); 95 struct swconfig_trig_data *trig_data = led_cdev->trigger_data; 96 unsigned long port_mask; 97 ssize_t ret = -EINVAL; 98 char *after; 99 size_t count; 100 101 port_mask = simple_strtoul(buf, &after, 16); 102 count = after - buf; 103 104 if (*after && isspace(*after)) 105 count++; 106 107 if (count == size) { 108 bool changed; 109 110 write_lock(&trig_data->lock); 111 112 changed = (trig_data->port_mask != port_mask); 113 if (changed) { 114 trig_data->port_mask = port_mask; 115 if (port_mask == 0) 116 swconfig_trig_set_brightness(trig_data, LED_OFF); 117 } 118 119 write_unlock(&trig_data->lock); 120 121 if (changed) 122 swconfig_trig_update_port_mask(led_cdev->trigger); 123 124 ret = count; 125 } 126 127 return ret; 128} 129 130static ssize_t 131swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, 132 char *buf) 133{ 134 struct led_classdev *led_cdev = dev_get_drvdata(dev); 135 struct swconfig_trig_data *trig_data = led_cdev->trigger_data; 136 137 read_lock(&trig_data->lock); 138 sprintf(buf, "%#x\n", trig_data->port_mask); 139 read_unlock(&trig_data->lock); 140 141 return strlen(buf) + 1; 142} 143 144static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, 145 swconfig_trig_port_mask_store); 146 147static void 148swconfig_trig_activate(struct led_classdev *led_cdev) 149{ 150 struct switch_led_trigger *sw_trig; 151 struct swconfig_trig_data *trig_data; 152 int err; 153 154 if (led_cdev->trigger->activate != swconfig_trig_activate) 155 return; 156 157 trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); 158 if (!trig_data) 159 return; 160 161 sw_trig = (void *) led_cdev->trigger; 162 163 rwlock_init(&trig_data->lock); 164 trig_data->led_cdev = led_cdev; 165 trig_data->swdev = sw_trig->swdev; 166 led_cdev->trigger_data = trig_data; 167 168 err = device_create_file(led_cdev->dev, &dev_attr_port_mask); 169 if (err) 170 goto err_free; 171 172 return; 173 174err_free: 175 led_cdev->trigger_data = NULL; 176 kfree(trig_data); 177} 178 179static void 180swconfig_trig_deactivate(struct led_classdev *led_cdev) 181{ 182 struct swconfig_trig_data *trig_data; 183 184 swconfig_trig_update_port_mask(led_cdev->trigger); 185 186 trig_data = (void *) led_cdev->trigger_data; 187 if (trig_data) { 188 device_remove_file(led_cdev->dev, &dev_attr_port_mask); 189 kfree(trig_data); 190 } 191} 192 193static void 194swconfig_trig_led_event(struct switch_led_trigger *sw_trig, 195 struct led_classdev *led_cdev) 196{ 197 struct swconfig_trig_data *trig_data; 198 u32 port_mask; 199 bool link; 200 201 trig_data = led_cdev->trigger_data; 202 if (!trig_data) 203 return; 204 205 read_lock(&trig_data->lock); 206 port_mask = trig_data->port_mask; 207 read_unlock(&trig_data->lock); 208 209 link = !!(sw_trig->port_link & port_mask); 210 if (!link) { 211 if (link != trig_data->prev_link) 212 led_brightness_set(trig_data->led_cdev, LED_OFF); 213 } else { 214 unsigned long traffic; 215 int i; 216 217 traffic = 0; 218 for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { 219 if (port_mask & (1 << i)) 220 traffic += sw_trig->port_traffic[i]; 221 } 222 223 if (trig_data->prev_brightness != LED_FULL) 224 swconfig_trig_set_brightness(trig_data, LED_FULL); 225 else if (traffic != trig_data->prev_traffic) 226 swconfig_trig_set_brightness(trig_data, LED_OFF); 227 228 trig_data->prev_traffic = traffic; 229 } 230 231 trig_data->prev_link = link; 232} 233 234static void 235swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) 236{ 237 struct list_head *entry; 238 struct led_trigger *trigger; 239 240 trigger = &sw_trig->trig; 241 read_lock(&trigger->leddev_list_lock); 242 list_for_each(entry, &trigger->led_cdevs) { 243 struct led_classdev *led_cdev; 244 245 led_cdev = list_entry(entry, struct led_classdev, trig_list); 246 swconfig_trig_led_event(sw_trig, led_cdev); 247 } 248 read_unlock(&trigger->leddev_list_lock); 249} 250 251static void 252swconfig_led_work_func(struct work_struct *work) 253{ 254 struct switch_led_trigger *sw_trig; 255 struct switch_dev *swdev; 256 u32 port_mask; 257 u32 link; 258 int i; 259 260 sw_trig = container_of(work, struct switch_led_trigger, 261 sw_led_work.work); 262 263 port_mask = sw_trig->port_mask; 264 swdev = sw_trig->swdev; 265 266 link = 0; 267 for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { 268 u32 port_bit; 269 270 port_bit = BIT(i); 271 if ((port_mask & port_bit) == 0) 272 continue; 273 274 if (swdev->ops->get_port_link) { 275 struct switch_port_link port_link; 276 277 memset(&port_link, '\0', sizeof(port_link)); 278 swdev->ops->get_port_link(swdev, i, &port_link); 279 280 if (port_link.link) 281 link |= port_bit; 282 } 283 284 if (swdev->ops->get_port_stats) { 285 struct switch_port_stats port_stats; 286 287 memset(&port_stats, '\0', sizeof(port_stats)); 288 swdev->ops->get_port_stats(swdev, i, &port_stats); 289 sw_trig->port_traffic[i] = port_stats.tx_bytes + 290 port_stats.rx_bytes; 291 } 292 } 293 294 sw_trig->port_link = link; 295 296 swconfig_trig_update_leds(sw_trig); 297 298 schedule_delayed_work(&sw_trig->sw_led_work, 299 SWCONFIG_LED_TIMER_INTERVAL); 300} 301 302static int 303swconfig_create_led_trigger(struct switch_dev *swdev) 304{ 305 struct switch_led_trigger *sw_trig; 306 int err; 307 308 if (!swdev->ops->get_port_link) 309 return 0; 310 311 sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); 312 if (!sw_trig) 313 return -ENOMEM; 314 315 sw_trig->swdev = swdev; 316 sw_trig->trig.name = swdev->devname; 317 sw_trig->trig.activate = swconfig_trig_activate; 318 sw_trig->trig.deactivate = swconfig_trig_deactivate; 319 320 INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); 321 322 err = led_trigger_register(&sw_trig->trig); 323 if (err) 324 goto err_free; 325 326 swdev->led_trigger = sw_trig; 327 328 return 0; 329 330err_free: 331 kfree(sw_trig); 332 return err; 333} 334 335static void 336swconfig_destroy_led_trigger(struct switch_dev *swdev) 337{ 338 struct switch_led_trigger *sw_trig; 339 340 sw_trig = swdev->led_trigger; 341 if (sw_trig) { 342 cancel_delayed_work_sync(&sw_trig->sw_led_work); 343 led_trigger_unregister(&sw_trig->trig); 344 kfree(sw_trig); 345 } 346} 347 348#else /* SWCONFIG_LEDS */ 349static inline int 350swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } 351 352static inline void 353swconfig_destroy_led_trigger(struct switch_dev *swdev) { } 354#endif /* CONFIG_SWCONFIG_LEDS */ 355