From 1dada3fc3c08b9f7fe12219af238d408409c5fa1 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Fri, 11 Dec 2015 15:04:57 +0000 Subject: [PATCH] ar71xx: add LED driver NU801 The MR18 uses a 3-channel 16-bit PWM Constant Current Driver for its status LED. Signed-off-by: Chris R Blake SVN-Revision: 47848 --- .../ar71xx/files/drivers/leds/leds-nu801.c | 396 ++++++++++++++++++ .../ar71xx/files/include/linux/leds-nu801.h | 38 ++ target/linux/ar71xx/modules.mk | 16 + .../818-MIPS-ath79-add-nu801-led-driver.patch | 26 ++ 4 files changed, 476 insertions(+) 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/target/linux/ar71xx/files/drivers/leds/leds-nu801.c b/target/linux/ar71xx/files/drivers/leds/leds-nu801.c new file mode 100644 index 0000000000..0dfc015653 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 "); +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 0000000000..0fc310d277 --- /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 + +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 a6a13d2613..9ead8b451c 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 0000000000..562388d104 --- /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