Message ID | 2127127.LLR1zRQNRy@debian64 |
---|---|
State | Changes Requested |
Headers | show |
Hi, 1 comment inline On 22/09/2015 19:07, Chris R Blake wrote: > From: Chris R Blake <chrisrblake93@gmail.com> > > The MR18 uses a 3-channel 16-bit PWM Constant Current Driver > for its status LED. > > Signed-off-by: Chris R Blake <chrisrblake93@gmail.com> > --- > package/base-files/files/etc/init.d/led | 2 +- > .../linux/ar71xx/files/drivers/leds/leds-nu801.c | 396 +++++++++++++++++++++ > .../linux/ar71xx/files/include/linux/leds-nu801.h | 38 ++ > target/linux/ar71xx/modules.mk | 16 + > .../818-MIPS-ath79-add-nu801-led-driver.patch | 26 ++ > 5 files changed, 477 insertions(+), 1 deletion(-) > create mode 100644 target/linux/ar71xx/files/drivers/leds/leds-nu801.c > create mode 100644 target/linux/ar71xx/files/include/linux/leds-nu801.h > create mode 100644 target/linux/ar71xx/patches-4.1/818-MIPS-ath79-add-nu801-led-driver.patch > > diff --git a/package/base-files/files/etc/init.d/led b/package/base-files/files/etc/init.d/led > index 3f45732..84cd028 100755 > --- a/package/base-files/files/etc/init.d/led > +++ b/package/base-files/files/etc/init.d/led > @@ -44,7 +44,7 @@ load_led() { > ret="$?" > > [ $default = 1 ] && > - echo 1 >/sys/class/leds/${sysfs}/brightness > + cat /sys/class/leds/${sysfs}/max_brightness > /sys/class/leds/${sysfs}/brightness > > [ $ret = 0 ] || { > echo >&2 "Skipping trigger '$trigger' for led '$name' due to missing kernel module" this is a nice corner case i never considered before. however this part of the patch need to go into its own patch file as it modifies a different part of openwrt. John > diff --git a/target/linux/ar71xx/files/drivers/leds/leds-nu801.c b/target/linux/ar71xx/files/drivers/leds/leds-nu801.c > new file mode 100644 > index 0000000..0dfc015 > --- /dev/null > +++ b/target/linux/ar71xx/files/drivers/leds/leds-nu801.c > @@ -0,0 +1,396 @@ > +/* > + * LED driver for NU801 > + * > + * Kevin Paul Herbert > + * Copyright (c) 2012, Meraki, Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + */ > + > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/slab.h> > +#include <linux/platform_device.h> > +#include <linux/leds.h> > +#include <linux/workqueue.h> > +#include <linux/delay.h> > +#include <linux/leds-nu801.h> > + > +#include <asm/gpio.h> > +#include <linux/of_gpio.h> > + > +#define MAX_NAME_LENGTH 24 > +#define NUM_COLORS 3 > + > +static const char * const led_nu801_colors[] = { "blue", "green", "red" }; > + > +struct led_nu801_led_data { > + struct led_classdev cdev; > + struct led_nu801_data *controller; > + enum led_brightness level; > + char name[MAX_NAME_LENGTH]; > +}; > + > +struct led_nu801_data { > + unsigned cki; > + unsigned sdi; > + int lei; > + struct delayed_work work; > + struct led_nu801_led_data *led_chain; > + int num_leds; > + const char *device_name; > + const char *name; > + u32 ndelay; > + atomic_t pending; > +}; > + > +static void led_nu801_work(struct work_struct *work) > +{ > + struct led_nu801_data *controller = > + container_of(work, struct led_nu801_data, work.work); > + struct led_nu801_led_data *led; > + u16 bit; > + u16 brightness; > + int index; > + > + for (index = 0; index < controller->num_leds; index++) { > + led = &controller->led_chain[index]; > + brightness = led->level << 8; /* To do: gamma correction */ > + for (bit = 0x8000; bit; bit = bit >> 1) { > + gpio_set_value(controller->sdi, > + (brightness & bit) != 0); > + gpio_set_value(controller->cki, 1); > + if (unlikely(((index == (controller->num_leds - 1)) && > + (bit == 1) && > + (controller->lei < 0)))) { > + udelay(600); > + } else { > + ndelay(controller->ndelay); > + } > + gpio_set_value(controller->cki, 0); > + ndelay(controller->ndelay); > + } > + } > + if (controller->lei >= 0) { > + gpio_set_value(controller->lei, 1); > + ndelay(controller->ndelay); > + gpio_set_value(controller->lei, 0); > + } > + atomic_set(&controller->pending, 1); > +} > + > +static void led_nu801_set(struct led_classdev *led_cdev, > + enum led_brightness value) > +{ > + struct led_nu801_led_data *led_dat = > + container_of(led_cdev, struct led_nu801_led_data, cdev); > + struct led_nu801_data *controller = led_dat->controller; > + > + if (led_dat->level != value) { > + led_dat->level = value; > + if (atomic_dec_and_test(&controller->pending)) > + schedule_delayed_work(&led_dat->controller->work, > + (HZ/1000) + 1); > + } > +} > + > +static int __init led_nu801_create(struct led_nu801_data *controller, > + struct device *parent, > + int index, > + enum led_brightness brightness, > +#ifdef CONFIG_LEDS_TRIGGERS > + const char *default_trigger, > +#endif > + const char *color) > +{ > + struct led_nu801_led_data *led = &controller->led_chain[index]; > + int ret; > + > + scnprintf(led->name, sizeof(led->name), "%s:%s:%s%d", > + controller->device_name, color, controller->name, > + (controller->num_leds - (index + 1)) / NUM_COLORS); > + led->cdev.name = led->name; > + led->cdev.brightness_set = led_nu801_set; > +#ifdef CONFIG_LEDS_TRIGGERS > + led->cdev.default_trigger = default_trigger; > +#endif > + led->level = brightness; > + led->controller = controller; > + ret = led_classdev_register(parent, &led->cdev); > + if (ret < 0) > + goto err; > + > + return 0; > + > +err: > + kfree(led); > + return ret; > +} > + > +static int __init > +led_nu801_create_chain(const struct led_nu801_template *template, > + struct led_nu801_data *controller, > + struct device *parent) > +{ > + int ret; > + int index; > + > + controller->cki = template->cki; > + controller->sdi = template->sdi; > + controller->lei = template->lei; > + controller->num_leds = template->num_leds * 3; > + controller->device_name = template->device_name; > + controller->name = template->name; > + controller->ndelay = template->ndelay; > + atomic_set(&controller->pending, 1); > + > + controller->led_chain = kzalloc(sizeof(struct led_nu801_led_data) * > + controller->num_leds, GFP_KERNEL); > + > + if (!controller->led_chain) > + return -ENOMEM; > + > + ret = gpio_request(controller->cki, template->name); > + if (ret < 0) > + goto err_free_chain; > + > + ret = gpio_request(controller->sdi, template->name); > + if (ret < 0) > + goto err_ret_cki; > + > + if (controller->lei >= 0) { > + ret = gpio_request(controller->lei, template->name); > + if (ret < 0) > + goto err_ret_sdi; > + ret = gpio_direction_output(controller->lei, 0); > + if (ret < 0) > + goto err_ret_lei; > + } > + > + ret = gpio_direction_output(controller->cki, 0); > + if (ret < 0) > + goto err_ret_lei; > + > + ret = gpio_direction_output(controller->sdi, 0); > + if (ret < 0) > + goto err_ret_lei; > + > + for (index = 0; index < controller->num_leds; index++) { > + ret = led_nu801_create(controller, parent, index, > + template->init_brightness > + [index % NUM_COLORS], > +#ifdef CONFIG_LEDS_TRIGGERS > + template->default_trigger, > +#endif > + template->led_colors[index % NUM_COLORS] ? > + template->led_colors[index % NUM_COLORS] : > + led_nu801_colors[index % NUM_COLORS]); > + if (ret < 0) > + goto err_ret_sdi; > + } > + > + INIT_DELAYED_WORK(&controller->work, led_nu801_work); > + schedule_delayed_work(&controller->work, 0); > + > + return 0; > + > +err_ret_lei: > + if (controller->lei >= 0) > + gpio_free(controller->lei); > +err_ret_sdi: > + gpio_free(controller->sdi); > +err_ret_cki: > + gpio_free(controller->cki); > +err_free_chain: > + kfree(controller->led_chain); > + > + return ret; > +} > + > +static void led_nu801_delete_chain(struct led_nu801_data *controller) > +{ > + struct led_nu801_led_data *led_chain; > + struct led_nu801_led_data *led; > + int index; > + int num_leds; > + > + led_chain = controller->led_chain; > + controller->led_chain = 0; > + num_leds = controller->num_leds; > + controller->num_leds = 0; > + cancel_delayed_work_sync(&controller->work); > + > + for (index = 0; index < num_leds; index++) { > + led = &led_chain[index]; > + led_classdev_unregister(&led->cdev); > + } > + > + gpio_free(controller->cki); > + gpio_free(controller->sdi); > + if (controller->lei >= 0) > + gpio_free(controller->lei); > + > + kfree(led_chain); > +} > + > +static struct led_nu801_data * __init > +leds_nu801_create_of(struct platform_device *pdev) > +{ > + struct device_node *np = pdev->dev.of_node, *child; > + struct led_nu801_data *controllers; > + int count = 0, ret; > + int i = 0; > + > + for_each_child_of_node(np, child) > + count++; > + if (!count) > + return NULL; > + > + controllers = kzalloc(sizeof(struct led_nu801_data) * count, > + GFP_KERNEL); > + if (!controllers) > + return NULL; > + > + for_each_child_of_node(np, child) { > + const char *state; > + struct led_nu801_template template = {}; > + struct device_node *colors; > + int jj; > + > + template.cki = of_get_named_gpio_flags(child, "cki", 0, NULL); > + template.sdi = of_get_named_gpio_flags(child, "sdi", 0, NULL); > + if (of_find_property(child, "lei", NULL)) { > + template.lei = of_get_named_gpio_flags(child, "lei", > + 0, NULL); > + } else { > + template.lei = -1; > + } > + of_property_read_u32(child, "ndelay", &template.ndelay); > + of_property_read_u32(child, "num_leds", &template.num_leds); > + template.name = of_get_property(child, "label", NULL) ? : > + child->name; > + template.default_trigger = of_get_property(child, > + "default-trigger", NULL); > + > + jj = 0; > + for_each_child_of_node(child, colors) { > + template.led_colors[jj] = of_get_property(colors, > + "label", NULL); > + state = of_get_property(colors, "state", NULL); > + if (!strncmp(state, "off", 3)) > + template.init_brightness[jj] = LED_OFF; > + else if (!strncmp(state, "half", 4)) > + template.init_brightness[jj] = LED_HALF; > + else if (!strncmp(state, "full", 4)) > + template.init_brightness[jj] = LED_FULL; > + jj++; > + } > + > + ret = led_nu801_create_chain(&template, > + &controllers[i], > + &pdev->dev); > + if (ret < 0) > + goto err; > + i++; > + } > + > + return controllers; > + > +err: > + for (i = i - 1; i >= 0; i--) > + led_nu801_delete_chain(&controllers[i]); > + kfree(controllers); > + return NULL; > +} > + > +static int __init led_nu801_probe(struct platform_device *pdev) > +{ > + struct led_nu801_platform_data *pdata = pdev->dev.platform_data; > + struct led_nu801_data *controllers; > + int i, ret = 0; > + > + if (!(pdata && pdata->num_controllers)) { > + controllers = leds_nu801_create_of(pdev); > + if (!controllers) > + return -ENODEV; > + } > + > + controllers = kzalloc(sizeof(struct led_nu801_data) * > + pdata->num_controllers, GFP_KERNEL); > + if (!controllers) > + return -ENOMEM; > + > + for (i = 0; i < pdata->num_controllers; i++) { > + ret = led_nu801_create_chain(&pdata->template[i], > + &controllers[i], > + &pdev->dev); > + if (ret < 0) > + goto err; > + } > + > + platform_set_drvdata(pdev, controllers); > + > + return 0; > + > +err: > + for (i = i - 1; i >= 0; i--) > + led_nu801_delete_chain(&controllers[i]); > + > + kfree(controllers); > + > + return ret; > +} > + > +static int led_nu801_remove(struct platform_device *pdev) > +{ > + int i; > + struct led_nu801_platform_data *pdata = pdev->dev.platform_data; > + struct led_nu801_data *controllers; > + > + controllers = platform_get_drvdata(pdev); > + > + for (i = 0; i < pdata->num_controllers; i++) > + led_nu801_delete_chain(&controllers[i]); > + > + kfree(controllers); > + > + return 0; > +} > + > +static const struct of_device_id of_numen_leds_match[] = { > + { .compatible = "numen,leds-nu801", }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, of_pwm_leds_match); > + > +static struct platform_driver led_nu801_driver = { > + .probe = led_nu801_probe, > + .remove = led_nu801_remove, > + .driver = { > + .name = "leds-nu801", > + .owner = THIS_MODULE, > + .of_match_table = of_numen_leds_match, > + }, > +}; > + > +static int __init led_nu801_init(void) > +{ > + return platform_driver_register(&led_nu801_driver); > +} > + > +static void __exit led_nu801_exit(void) > +{ > + platform_driver_unregister(&led_nu801_driver); > +} > + > +module_init(led_nu801_init); > +module_exit(led_nu801_exit); > + > +MODULE_AUTHOR("Kevin Paul Herbert <kph@meraki.net>"); > +MODULE_DESCRIPTION("NU801 LED driver"); > +MODULE_LICENSE("GPL v2"); > +MODULE_ALIAS("platform:leds-nu801"); > diff --git a/target/linux/ar71xx/files/include/linux/leds-nu801.h b/target/linux/ar71xx/files/include/linux/leds-nu801.h > new file mode 100644 > index 0000000..0fc310d > --- /dev/null > +++ b/target/linux/ar71xx/files/include/linux/leds-nu801.h > @@ -0,0 +1,38 @@ > +#ifndef __LEDS_NU801_H__ > +#define __LEDS_NU801_H__ > + > +/* > + * Definitions for LED driver for NU801 > + * > + * Kevin Paul Herbert > + * Copyright (c) 2012, Meraki, Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License version 2 as > + * published by the Free Software Foundation. > + * > + */ > + > +#include <linux/leds.h> > + > +struct led_nu801_template { > + const char *device_name; /* Name of the platform device */ > + const char *name; /* Name of this LED chain */ > + int num_leds; /* Number of LEDs in the chain */ > + unsigned cki; /* GPIO pin for CKI */ > + unsigned sdi; /* GPIO pin for SDI */ > + int lei; /* GPIO pin for LEI; < 0 if none */ > + u32 ndelay; /* Delay in nanoseconds */ > + enum led_brightness init_brightness[3]; /* Default rgb state */ > +#ifdef CONFIG_LEDS_TRIGGERS > + const char *default_trigger; /* default trigger */ > +#endif > + const char *led_colors[3]; /* rgb color order */ > +}; > + > +struct led_nu801_platform_data { > + int num_controllers; /* Numnber of controllers */ > + struct led_nu801_template *template; /* Template per controller */ > +}; > + > +#endif /* __LEDS_NU801_H__ */ > diff --git a/target/linux/ar71xx/modules.mk b/target/linux/ar71xx/modules.mk > index a6a13d2..9ead8b4 100644 > --- a/target/linux/ar71xx/modules.mk > +++ b/target/linux/ar71xx/modules.mk > @@ -5,6 +5,22 @@ > # See /LICENSE for more information. > # > > +define KernelPackage/leds-nu801 > + SUBMENU:=$(LEDS_MENU) > + TITLE:=Meraki MR18 LED support > + DEPENDS:=@TARGET_ar71xx > + KCONFIG:=CONFIG_LEDS_NU801 > + FILES:=$(LINUX_DIR)/drivers/leds/leds-nu801.ko > + AUTOLOAD:=$(call AutoLoad,60,leds-nu801) > +endef > + > +define KernelPackage/leds-nu801/description > + Kernel module for the nu801 LED driver used on the Meraki MR18. > +endef > + > +$(eval $(call KernelPackage,leds-nu801)) > + > + > define KernelPackage/leds-rb750 > SUBMENU:=$(LEDS_MENU) > TITLE:=RouterBOARD 750 LED support > diff --git a/target/linux/ar71xx/patches-4.1/818-MIPS-ath79-add-nu801-led-driver.patch b/target/linux/ar71xx/patches-4.1/818-MIPS-ath79-add-nu801-led-driver.patch > new file mode 100644 > index 0000000..562388d > --- /dev/null > +++ b/target/linux/ar71xx/patches-4.1/818-MIPS-ath79-add-nu801-led-driver.patch > @@ -0,0 +1,26 @@ > +--- a/drivers/leds/Kconfig > ++++ b/drivers/leds/Kconfig > +@@ -500,6 +500,13 @@ config LEDS_MENF21BMC > + > + comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" > + > ++config LEDS_NU801 > ++ tristate "LED driver for NU801 RGB LED" > ++ depends on LEDS_CLASS && ATH79_MACH_MR18 > ++ help > ++ This option enables support for NU801 RGB LED driver chips > ++ accessed via GPIO. > ++ > + config LEDS_BLINKM > + tristate "LED support for the BlinkM I2C RGB LED" > + depends on LEDS_CLASS > +--- a/drivers/leds/Makefile > ++++ b/drivers/leds/Makefile > +@@ -51,6 +51,7 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5 > + obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o > + obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o > + obj-$(CONFIG_LEDS_RB750) += leds-rb750.o > ++obj-$(CONFIG_LEDS_NU801) += leds-nu801.o > + obj-$(CONFIG_LEDS_NS2) += leds-ns2.o > + obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o > + obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o >
diff --git a/package/base-files/files/etc/init.d/led b/package/base-files/files/etc/init.d/led index 3f45732..84cd028 100755 --- a/package/base-files/files/etc/init.d/led +++ b/package/base-files/files/etc/init.d/led @@ -44,7 +44,7 @@ load_led() { ret="$?" [ $default = 1 ] && - echo 1 >/sys/class/leds/${sysfs}/brightness + cat /sys/class/leds/${sysfs}/max_brightness > /sys/class/leds/${sysfs}/brightness [ $ret = 0 ] || { echo >&2 "Skipping trigger '$trigger' for led '$name' due to missing kernel module" diff --git a/target/linux/ar71xx/files/drivers/leds/leds-nu801.c b/target/linux/ar71xx/files/drivers/leds/leds-nu801.c new file mode 100644 index 0000000..0dfc015 --- /dev/null +++ b/target/linux/ar71xx/files/drivers/leds/leds-nu801.c @@ -0,0 +1,396 @@ +/* + * LED driver for NU801 + * + * Kevin Paul Herbert + * Copyright (c) 2012, Meraki, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/leds-nu801.h> + +#include <asm/gpio.h> +#include <linux/of_gpio.h> + +#define MAX_NAME_LENGTH 24 +#define NUM_COLORS 3 + +static const char * const led_nu801_colors[] = { "blue", "green", "red" }; + +struct led_nu801_led_data { + struct led_classdev cdev; + struct led_nu801_data *controller; + enum led_brightness level; + char name[MAX_NAME_LENGTH]; +}; + +struct led_nu801_data { + unsigned cki; + unsigned sdi; + int lei; + struct delayed_work work; + struct led_nu801_led_data *led_chain; + int num_leds; + const char *device_name; + const char *name; + u32 ndelay; + atomic_t pending; +}; + +static void led_nu801_work(struct work_struct *work) +{ + struct led_nu801_data *controller = + container_of(work, struct led_nu801_data, work.work); + struct led_nu801_led_data *led; + u16 bit; + u16 brightness; + int index; + + for (index = 0; index < controller->num_leds; index++) { + led = &controller->led_chain[index]; + brightness = led->level << 8; /* To do: gamma correction */ + for (bit = 0x8000; bit; bit = bit >> 1) { + gpio_set_value(controller->sdi, + (brightness & bit) != 0); + gpio_set_value(controller->cki, 1); + if (unlikely(((index == (controller->num_leds - 1)) && + (bit == 1) && + (controller->lei < 0)))) { + udelay(600); + } else { + ndelay(controller->ndelay); + } + gpio_set_value(controller->cki, 0); + ndelay(controller->ndelay); + } + } + if (controller->lei >= 0) { + gpio_set_value(controller->lei, 1); + ndelay(controller->ndelay); + gpio_set_value(controller->lei, 0); + } + atomic_set(&controller->pending, 1); +} + +static void led_nu801_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct led_nu801_led_data *led_dat = + container_of(led_cdev, struct led_nu801_led_data, cdev); + struct led_nu801_data *controller = led_dat->controller; + + if (led_dat->level != value) { + led_dat->level = value; + if (atomic_dec_and_test(&controller->pending)) + schedule_delayed_work(&led_dat->controller->work, + (HZ/1000) + 1); + } +} + +static int __init led_nu801_create(struct led_nu801_data *controller, + struct device *parent, + int index, + enum led_brightness brightness, +#ifdef CONFIG_LEDS_TRIGGERS + const char *default_trigger, +#endif + const char *color) +{ + struct led_nu801_led_data *led = &controller->led_chain[index]; + int ret; + + scnprintf(led->name, sizeof(led->name), "%s:%s:%s%d", + controller->device_name, color, controller->name, + (controller->num_leds - (index + 1)) / NUM_COLORS); + led->cdev.name = led->name; + led->cdev.brightness_set = led_nu801_set; +#ifdef CONFIG_LEDS_TRIGGERS + led->cdev.default_trigger = default_trigger; +#endif + led->level = brightness; + led->controller = controller; + ret = led_classdev_register(parent, &led->cdev); + if (ret < 0) + goto err; + + return 0; + +err: + kfree(led); + return ret; +} + +static int __init +led_nu801_create_chain(const struct led_nu801_template *template, + struct led_nu801_data *controller, + struct device *parent) +{ + int ret; + int index; + + controller->cki = template->cki; + controller->sdi = template->sdi; + controller->lei = template->lei; + controller->num_leds = template->num_leds * 3; + controller->device_name = template->device_name; + controller->name = template->name; + controller->ndelay = template->ndelay; + atomic_set(&controller->pending, 1); + + controller->led_chain = kzalloc(sizeof(struct led_nu801_led_data) * + controller->num_leds, GFP_KERNEL); + + if (!controller->led_chain) + return -ENOMEM; + + ret = gpio_request(controller->cki, template->name); + if (ret < 0) + goto err_free_chain; + + ret = gpio_request(controller->sdi, template->name); + if (ret < 0) + goto err_ret_cki; + + if (controller->lei >= 0) { + ret = gpio_request(controller->lei, template->name); + if (ret < 0) + goto err_ret_sdi; + ret = gpio_direction_output(controller->lei, 0); + if (ret < 0) + goto err_ret_lei; + } + + ret = gpio_direction_output(controller->cki, 0); + if (ret < 0) + goto err_ret_lei; + + ret = gpio_direction_output(controller->sdi, 0); + if (ret < 0) + goto err_ret_lei; + + for (index = 0; index < controller->num_leds; index++) { + ret = led_nu801_create(controller, parent, index, + template->init_brightness + [index % NUM_COLORS], +#ifdef CONFIG_LEDS_TRIGGERS + template->default_trigger, +#endif + template->led_colors[index % NUM_COLORS] ? + template->led_colors[index % NUM_COLORS] : + led_nu801_colors[index % NUM_COLORS]); + if (ret < 0) + goto err_ret_sdi; + } + + INIT_DELAYED_WORK(&controller->work, led_nu801_work); + schedule_delayed_work(&controller->work, 0); + + return 0; + +err_ret_lei: + if (controller->lei >= 0) + gpio_free(controller->lei); +err_ret_sdi: + gpio_free(controller->sdi); +err_ret_cki: + gpio_free(controller->cki); +err_free_chain: + kfree(controller->led_chain); + + return ret; +} + +static void led_nu801_delete_chain(struct led_nu801_data *controller) +{ + struct led_nu801_led_data *led_chain; + struct led_nu801_led_data *led; + int index; + int num_leds; + + led_chain = controller->led_chain; + controller->led_chain = 0; + num_leds = controller->num_leds; + controller->num_leds = 0; + cancel_delayed_work_sync(&controller->work); + + for (index = 0; index < num_leds; index++) { + led = &led_chain[index]; + led_classdev_unregister(&led->cdev); + } + + gpio_free(controller->cki); + gpio_free(controller->sdi); + if (controller->lei >= 0) + gpio_free(controller->lei); + + kfree(led_chain); +} + +static struct led_nu801_data * __init +leds_nu801_create_of(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node, *child; + struct led_nu801_data *controllers; + int count = 0, ret; + int i = 0; + + for_each_child_of_node(np, child) + count++; + if (!count) + return NULL; + + controllers = kzalloc(sizeof(struct led_nu801_data) * count, + GFP_KERNEL); + if (!controllers) + return NULL; + + for_each_child_of_node(np, child) { + const char *state; + struct led_nu801_template template = {}; + struct device_node *colors; + int jj; + + template.cki = of_get_named_gpio_flags(child, "cki", 0, NULL); + template.sdi = of_get_named_gpio_flags(child, "sdi", 0, NULL); + if (of_find_property(child, "lei", NULL)) { + template.lei = of_get_named_gpio_flags(child, "lei", + 0, NULL); + } else { + template.lei = -1; + } + of_property_read_u32(child, "ndelay", &template.ndelay); + of_property_read_u32(child, "num_leds", &template.num_leds); + template.name = of_get_property(child, "label", NULL) ? : + child->name; + template.default_trigger = of_get_property(child, + "default-trigger", NULL); + + jj = 0; + for_each_child_of_node(child, colors) { + template.led_colors[jj] = of_get_property(colors, + "label", NULL); + state = of_get_property(colors, "state", NULL); + if (!strncmp(state, "off", 3)) + template.init_brightness[jj] = LED_OFF; + else if (!strncmp(state, "half", 4)) + template.init_brightness[jj] = LED_HALF; + else if (!strncmp(state, "full", 4)) + template.init_brightness[jj] = LED_FULL; + jj++; + } + + ret = led_nu801_create_chain(&template, + &controllers[i], + &pdev->dev); + if (ret < 0) + goto err; + i++; + } + + return controllers; + +err: + for (i = i - 1; i >= 0; i--) + led_nu801_delete_chain(&controllers[i]); + kfree(controllers); + return NULL; +} + +static int __init led_nu801_probe(struct platform_device *pdev) +{ + struct led_nu801_platform_data *pdata = pdev->dev.platform_data; + struct led_nu801_data *controllers; + int i, ret = 0; + + if (!(pdata && pdata->num_controllers)) { + controllers = leds_nu801_create_of(pdev); + if (!controllers) + return -ENODEV; + } + + controllers = kzalloc(sizeof(struct led_nu801_data) * + pdata->num_controllers, GFP_KERNEL); + if (!controllers) + return -ENOMEM; + + for (i = 0; i < pdata->num_controllers; i++) { + ret = led_nu801_create_chain(&pdata->template[i], + &controllers[i], + &pdev->dev); + if (ret < 0) + goto err; + } + + platform_set_drvdata(pdev, controllers); + + return 0; + +err: + for (i = i - 1; i >= 0; i--) + led_nu801_delete_chain(&controllers[i]); + + kfree(controllers); + + return ret; +} + +static int led_nu801_remove(struct platform_device *pdev) +{ + int i; + struct led_nu801_platform_data *pdata = pdev->dev.platform_data; + struct led_nu801_data *controllers; + + controllers = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_controllers; i++) + led_nu801_delete_chain(&controllers[i]); + + kfree(controllers); + + return 0; +} + +static const struct of_device_id of_numen_leds_match[] = { + { .compatible = "numen,leds-nu801", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_pwm_leds_match); + +static struct platform_driver led_nu801_driver = { + .probe = led_nu801_probe, + .remove = led_nu801_remove, + .driver = { + .name = "leds-nu801", + .owner = THIS_MODULE, + .of_match_table = of_numen_leds_match, + }, +}; + +static int __init led_nu801_init(void) +{ + return platform_driver_register(&led_nu801_driver); +} + +static void __exit led_nu801_exit(void) +{ + platform_driver_unregister(&led_nu801_driver); +} + +module_init(led_nu801_init); +module_exit(led_nu801_exit); + +MODULE_AUTHOR("Kevin Paul Herbert <kph@meraki.net>"); +MODULE_DESCRIPTION("NU801 LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds-nu801"); diff --git a/target/linux/ar71xx/files/include/linux/leds-nu801.h b/target/linux/ar71xx/files/include/linux/leds-nu801.h new file mode 100644 index 0000000..0fc310d --- /dev/null +++ b/target/linux/ar71xx/files/include/linux/leds-nu801.h @@ -0,0 +1,38 @@ +#ifndef __LEDS_NU801_H__ +#define __LEDS_NU801_H__ + +/* + * Definitions for LED driver for NU801 + * + * Kevin Paul Herbert + * Copyright (c) 2012, Meraki, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/leds.h> + +struct led_nu801_template { + const char *device_name; /* Name of the platform device */ + const char *name; /* Name of this LED chain */ + int num_leds; /* Number of LEDs in the chain */ + unsigned cki; /* GPIO pin for CKI */ + unsigned sdi; /* GPIO pin for SDI */ + int lei; /* GPIO pin for LEI; < 0 if none */ + u32 ndelay; /* Delay in nanoseconds */ + enum led_brightness init_brightness[3]; /* Default rgb state */ +#ifdef CONFIG_LEDS_TRIGGERS + const char *default_trigger; /* default trigger */ +#endif + const char *led_colors[3]; /* rgb color order */ +}; + +struct led_nu801_platform_data { + int num_controllers; /* Numnber of controllers */ + struct led_nu801_template *template; /* Template per controller */ +}; + +#endif /* __LEDS_NU801_H__ */ diff --git a/target/linux/ar71xx/modules.mk b/target/linux/ar71xx/modules.mk index a6a13d2..9ead8b4 100644 --- a/target/linux/ar71xx/modules.mk +++ b/target/linux/ar71xx/modules.mk @@ -5,6 +5,22 @@ # See /LICENSE for more information. # +define KernelPackage/leds-nu801 + SUBMENU:=$(LEDS_MENU) + TITLE:=Meraki MR18 LED support + DEPENDS:=@TARGET_ar71xx + KCONFIG:=CONFIG_LEDS_NU801 + FILES:=$(LINUX_DIR)/drivers/leds/leds-nu801.ko + AUTOLOAD:=$(call AutoLoad,60,leds-nu801) +endef + +define KernelPackage/leds-nu801/description + Kernel module for the nu801 LED driver used on the Meraki MR18. +endef + +$(eval $(call KernelPackage,leds-nu801)) + + define KernelPackage/leds-rb750 SUBMENU:=$(LEDS_MENU) TITLE:=RouterBOARD 750 LED support diff --git a/target/linux/ar71xx/patches-4.1/818-MIPS-ath79-add-nu801-led-driver.patch b/target/linux/ar71xx/patches-4.1/818-MIPS-ath79-add-nu801-led-driver.patch new file mode 100644 index 0000000..562388d --- /dev/null +++ b/target/linux/ar71xx/patches-4.1/818-MIPS-ath79-add-nu801-led-driver.patch @@ -0,0 +1,26 @@ +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -500,6 +500,13 @@ config LEDS_MENF21BMC + + comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" + ++config LEDS_NU801 ++ tristate "LED driver for NU801 RGB LED" ++ depends on LEDS_CLASS && ATH79_MACH_MR18 ++ help ++ This option enables support for NU801 RGB LED driver chips ++ accessed via GPIO. ++ + config LEDS_BLINKM + tristate "LED support for the BlinkM I2C RGB LED" + depends on LEDS_CLASS +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -51,6 +51,7 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5 + obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o + obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o + obj-$(CONFIG_LEDS_RB750) += leds-rb750.o ++obj-$(CONFIG_LEDS_NU801) += leds-nu801.o + obj-$(CONFIG_LEDS_NS2) += leds-ns2.o + obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o + obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o