aboutsummaryrefslogtreecommitdiff
path: root/drivers/leds/leds-apollo3g.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/leds-apollo3g.c')
-rw-r--r--drivers/leds/leds-apollo3g.c365
1 files changed, 365 insertions, 0 deletions
diff --git a/drivers/leds/leds-apollo3g.c b/drivers/leds/leds-apollo3g.c
new file mode 100644
index 00000000000..6e796dc7c24
--- /dev/null
+++ b/drivers/leds/leds-apollo3g.c
@@ -0,0 +1,365 @@
+/*
+ * LED Platform driver for Apollo3G board.
+ *
+ * © 2010 Western Digital Technologies, Inc. All rights reserved.
+ *
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+#include <linux/leds.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include <linux/suspend.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/signal.h>
+#include <linux/freezer.h>
+
+
+
+#define _3G_BIT_LED_RED 0x10
+#define _3G_BIT_LED_GREEN 0x20
+#define _3G_BIT_LED_BLUE 0x40
+#define _3G_BIT_LED_YELLOW (_3G_BIT_LED_GREEN | _3G_BIT_LED_RED)
+#define _3G_BIT_LED_ALL 0x70
+#define _3G_BIT_LED_OFF 0x00
+
+#define _3G_LED_OFF 0
+#define _3G_LED_RED 1
+#define _3G_LED_GREEN 2
+#define _3G_LED_BLUE 3
+#define _3G_LED_YELLOW 4
+#define _3G_LED_ALL 5
+#define _3G_LED_WHITE 5 /* save as _ALL */
+
+#define _BLINK_YES 1
+#define _BLINK_NO 0
+
+#define HDD_BLINK_RATE 250
+
+static DEFINE_SPINLOCK(led_lock);
+struct task_struct * threadptr;
+
+wait_queue_head_t ts_wait;
+int blink_flag = 0;
+
+
+void __iomem *led_port = NULL;
+
+typedef struct led_state_s {
+ int cur_color;
+ int cur_action;
+} led_state_t;
+
+led_state_t led_state = { .cur_color = _3G_LED_YELLOW,
+ .cur_action = _BLINK_NO
+ };
+
+EXPORT_SYMBOL(led_port);
+
+/****************************************************/
+/* read value from 3g led_port */
+ u8 read_3gled( void ) {
+ return readb(led_port);
+}
+
+/****************************************************/
+/* read-modify-write 3g led port */
+u8 write_3gled( u8 mask ,u8 value ) {
+ u8 regval;
+
+ regval = read_3gled();
+ regval &= ~mask;
+ regval |= (value & mask);
+ writeb( regval, led_port);
+ return regval;
+}
+
+/****************************************************/
+/* return 3g led color */
+static enum led_brightness a3g_led_get( struct led_classdev * led_cdev ) {
+ unsigned char readval;
+
+ readval = readb(led_port);
+ if( (readval & _3G_BIT_LED_ALL) == _3G_BIT_LED_RED ) {
+ readval = _3G_LED_RED;
+ }
+ else if( (readval & _3G_BIT_LED_ALL) == _3G_BIT_LED_GREEN ) {
+ readval = _3G_LED_GREEN;
+ }
+ else if( (readval & _3G_BIT_LED_ALL) == _3G_BIT_LED_BLUE ) {
+ readval = _3G_LED_BLUE;
+ }
+ else if( (readval & _3G_BIT_LED_ALL) == _3G_BIT_LED_YELLOW ) {
+ readval = _3G_LED_YELLOW;
+ }
+ else if( (readval & _3G_BIT_LED_ALL) == _3G_BIT_LED_ALL ) {
+ readval = _3G_LED_ALL;
+ }
+ else if( (readval & _3G_BIT_LED_ALL) == _3G_BIT_LED_OFF ) {
+ readval = _3G_LED_OFF;
+ }
+
+ return readval;
+}
+
+/****************************************************/
+/* set 3g led color */
+static void a3g_led_set(struct led_classdev *led_cdev, enum led_brightness value) {
+
+ unsigned long flags;
+
+ spin_lock_irqsave(&led_lock, flags);
+
+ switch (value) {
+ case _3G_LED_RED:
+ write_3gled( _3G_BIT_LED_ALL, _3G_BIT_LED_RED);
+ break;
+ case _3G_LED_GREEN:
+ write_3gled( _3G_BIT_LED_ALL, _3G_BIT_LED_GREEN);
+ break;
+ case _3G_LED_BLUE:
+ write_3gled( _3G_BIT_LED_ALL, _3G_BIT_LED_BLUE);
+ break;
+ case _3G_LED_OFF:
+ write_3gled( _3G_BIT_LED_ALL, _3G_BIT_LED_OFF);
+ break;
+ case _3G_LED_ALL:
+ write_3gled( _3G_BIT_LED_ALL, _3G_BIT_LED_ALL);
+ break;
+ case _3G_LED_YELLOW:
+ write_3gled(_3G_BIT_LED_ALL, _3G_BIT_LED_YELLOW);
+ break;
+ default:
+ break; /* should never be here */
+ }
+ led_state.cur_color = value;
+
+ spin_unlock_irqrestore(&led_lock, flags);
+
+}
+
+/****************************************************/
+/* set 3g led blinking */
+static int a3g_led_blink(struct led_classdev *led_cdev, int value) {
+
+ /*
+ * if forced blink, don't set blink_flag
+ */
+ if( blink_flag == 2 ) {
+ return 0;
+ }
+
+ /*spin_lock_irqsave(&led_lock, flags);*/
+ /* user wants to blink led */
+ if( value == 1 ) {
+ wake_up(&ts_wait);
+ blink_flag = 1;
+
+ }
+ else if( value == 0) {
+ blink_flag = 0;
+ }
+ else if( value == 2 ) {
+ wake_up(&ts_wait);
+ blink_flag = 2;
+ }
+ /* spin_unlock_irqrestore(&led_lock, flags);*/
+
+ return 0;
+}
+
+/****************************************************/
+/*
+ * flag = blink or not
+ * color = blinking color
+ */
+void signal_hdd_led(int flag, int color) {
+
+ /*
+ * if forced blinking was set, keep it blinking forever
+ */
+ if( blink_flag == 2 ) {
+ return;
+ }
+
+ if( flag && /* blink or not */
+ (led_state.cur_color == _3G_LED_GREEN)
+#if 0
+ (led_state.cur_color != _3G_LED_WHITE) && /* don't touch fw update led */
+ (led_state.cur_color != _3G_LED_RED) && /* don't touch system error led */
+ !((led_state.cur_color == _3G_LED_BLUE) && (led_state.cur_action == _BLINK_YES)) && /* leave identity alone */
+ (color != _3G_LED_RED)
+#endif
+ ) {
+ if( color == _3G_LED_RED ) {
+ a3g_led_set( NULL, _3G_LED_RED);
+ }
+ blink_flag = 1;
+ wake_up(&ts_wait);
+ }
+ else {
+ blink_flag = 0;
+ }
+}
+
+static struct led_classdev a3g_led_dev = {
+ .name = "a3g_led",
+ .color_set = a3g_led_set,
+ .color_get = a3g_led_get,
+ .blink_set_3g = a3g_led_blink,
+};
+
+/****************************************************/
+static int __init a3g_led_probe(struct platform_device *pdev ) {
+
+ /* Not used */
+ return 0;
+}
+
+/****************************************************/
+static int __devexit a3g_led_remove(struct platform_device *pdev){
+
+ led_classdev_unregister(&a3g_led_dev);
+ if( led_port ){
+ iounmap(led_port);
+ led_port = NULL;
+ }
+ return 0;
+}
+static struct platform_driver a3g_led_driver = {
+ .probe = a3g_led_probe,
+ .remove = __devexit_p(a3g_led_remove),
+ .driver = {
+ .name = "a3g-leds",
+ .owner = THIS_MODULE,
+ },
+};
+
+#if 0
+struct platform_device {
+ const char * name;
+ int id;
+ struct device dev;
+ u32 num_resources;
+ struct resource * resource;
+
+ struct platform_device_id *id_entry;
+
+ /* arch specific additions */
+ struct pdev_archdata archdata;
+};
+#endif
+
+static struct resource a3g_res = {
+ .name = "cs1",
+ .start = 0x4e0000000ULL,
+ .end = 0x4e0000300ULL,
+ /*.flags = IORESOURCE_IO,*/
+ .flags = IORESOURCE_MEM,
+ };
+
+
+/****************************************************/
+static int a3g_led_blink_thread( void * data ) {
+ unsigned char readval, color;
+
+ struct task_struct * tsk = current;
+ struct sched_param param = { .sched_priority = 1};
+
+ init_waitqueue_head(&ts_wait);
+
+ sched_setscheduler(tsk, SCHED_FIFO, &param);
+ set_freezable();
+
+ while( !kthread_should_stop() ) {
+
+ led_state.cur_action = _BLINK_NO;
+ /* always set current color before blinking */
+ a3g_led_set( NULL, led_state.cur_color);
+ wait_event_freezable_timeout(ts_wait, blink_flag || kthread_should_stop(), MAX_SCHEDULE_TIMEOUT);
+ if( led_port ) {
+ readval = readb(led_port);
+ color = readval & _3G_BIT_LED_ALL;
+ write_3gled( _3G_BIT_LED_ALL, _3G_BIT_LED_OFF);
+ msleep(HDD_BLINK_RATE);
+ write_3gled( _3G_BIT_LED_ALL, color);
+ msleep(HDD_BLINK_RATE);
+ led_state.cur_action = _BLINK_YES;
+ }
+ }
+
+ return 0;
+}
+
+
+/****************************************************/
+static int __init a3g_led_init(void) {
+
+ resource_size_t res_size;
+ struct resource *phys_res = &a3g_res;
+ int retval;
+
+ res_size = resource_size(phys_res);
+
+ if( !request_mem_region(phys_res->start, res_size, phys_res->name) ) {
+ printk(KERN_DEBUG "**** error request_mem_region()\n");
+ return -1;
+ }
+
+ led_port = ioremap(phys_res->start, res_size);
+ if (led_port == NULL) {
+ release_mem_region(phys_res->start, res_size);
+ printk(KERN_DEBUG "*** Error ioremap()");
+ return -1;
+ }
+ else {
+ retval = led_classdev_register(NULL, &a3g_led_dev);
+ if (retval) {
+ led_classdev_unregister(&a3g_led_dev);
+ iounmap(led_port);
+ led_port = NULL;
+ release_mem_region(phys_res->start, res_size);
+ return -1;
+ }
+
+ threadptr = kthread_run( a3g_led_blink_thread, NULL, "a3gblink_t");
+
+
+ return platform_driver_register(&a3g_led_driver);
+ }
+}
+
+/****************************************************/
+static void __exit a3g_led_exit(void) {
+
+ platform_driver_unregister(&a3g_led_driver);
+ if( led_port ){
+ led_classdev_unregister(&a3g_led_dev);
+ iounmap(led_port);
+ led_port = NULL;
+ if( threadptr ){
+ kthread_stop(threadptr);
+ }
+ release_mem_region(a3g_res.start, (a3g_res.end - a3g_res.start + 1));
+ }
+}
+
+
+module_init(a3g_led_init);
+module_exit(a3g_led_exit);
+
+MODULE_AUTHOR("Hai Le <hai.le@wdc.com>");
+MODULE_DESCRIPTION("Apollo3G LED driver");
+MODULE_LICENSE("GPL");