370 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* arch/arm/mach-msm/board-htcleo-microp.c
 | |
|  * Copyright (C) 2009 HTC Corporation.
 | |
|  *
 | |
|  * This software is licensed under the terms of the GNU General Public
 | |
|  * License version 2, as published by the Free Software Foundation, and
 | |
|  * may be copied, distributed, and modified under those terms.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
| */
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/gpio.h>
 | |
| #include <linux/leds.h>
 | |
| #include <linux/i2c.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/earlysuspend.h>
 | |
| #include <asm/mach-types.h>
 | |
| #include <mach/board-htcleo-microp.h>
 | |
| #include <linux/capella_cm3602.h>
 | |
| #include <linux/input.h>
 | |
| #include <linux/uaccess.h>
 | |
| #include <linux/miscdevice.h>
 | |
| #include <mach/vreg.h>
 | |
| #include <linux/wakelock.h>
 | |
| #include <linux/workqueue.h>
 | |
| 
 | |
| #include "board-htcleo.h"
 | |
| 
 | |
| static void htcleo_leds_led_brightness_set_work(struct work_struct *work);
 | |
| 
 | |
| enum led_type {
 | |
| 	GREEN_LED,
 | |
| 	AMBER_LED,
 | |
| 	NUM_LEDS,
 | |
| };
 | |
| 
 | |
| struct htcleo_leds_led_data {
 | |
| 	int type;
 | |
| 	struct led_classdev ldev;
 | |
| 	struct mutex led_data_mutex;
 | |
| 	struct work_struct brightness_work;
 | |
| 	spinlock_t brightness_lock;
 | |
| 	enum led_brightness brightness;
 | |
| 	uint8_t mode;
 | |
| 	uint8_t blink;
 | |
| };
 | |
| 
 | |
| static struct htcleo_leds_data {
 | |
| 	struct htcleo_leds_led_data leds[NUM_LEDS];
 | |
| } the_data;
 | |
| 
 | |
| 
 | |
| int htcleo_leds_disable_lights(void)
 | |
| {
 | |
| 	int ret;
 | |
| 	uint8_t data[4];
 | |
| 
 | |
| 	data[0] = 0;
 | |
| 	data[1] = 0;
 | |
| 	data[2] = 0;
 | |
| 	data[3] = 0;
 | |
| 	ret = microp_i2c_write(MICROP_I2C_WCMD_LED_CTRL, data, 1);
 | |
| 	if (ret != 0)
 | |
| 	{
 | |
| 		pr_err("%s: set failed\n", __func__);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int htcleo_leds_write_led_mode(struct led_classdev *led_cdev,
 | |
| 				uint8_t mode)
 | |
| {
 | |
| /*	There are 5 different Led Modi;
 | |
| *	0x0, 0x0: Disabled
 | |
| *	0x0, 0x1: LED Green
 | |
| *	0x0, 0x2: LED Amber 
 | |
| *	0x0, 0x3: LED Green flashing slow ( ca. 6 sek ) 
 | |
| *	0x0, 0x4: LED Green flashing fast ( ca. 2 sek )
 | |
| *	0x0, 0x5: LED Amber flashing fast ( ca. 2 sek ) 
 | |
| *	0x10,0xX: LED Amber and Green flashing alternately
 | |
| */
 | |
| 	struct htcleo_leds_led_data *ldata;
 | |
| 	uint8_t data[2] = { 0, 0 };
 | |
| 	int ret;
 | |
| 	static uint8_t oldvalgr=0, oldvalam=0, alternately=0;
 | |
| 
 | |
| 	ldata = container_of(led_cdev, struct htcleo_leds_led_data, ldev);
 | |
| 
 | |
| 	data[0] = 0x00;
 | |
| 	if (ldata->type == GREEN_LED) {
 | |
| 		switch(mode) {
 | |
| 		  case 0x0:
 | |
| 			if(alternately) {
 | |
| 				data[1]=oldvalam; 
 | |
| 				alternately=0;
 | |
| 			} else
 | |
| 				data[1] = 0x0;  // Disable Light
 | |
| 			break;
 | |
| 		  case 0x1:
 | |
| 			data[1] = 0x1;  // Enable Light
 | |
| 			break;
 | |
| 		  case 0x2:
 | |
| 			if(oldvalam==0x5) { // alternately blinking
 | |
| 				data[0] = 0x10;	
 | |
| 				alternately=1;
 | |
| 			} else
 | |
| 				alternately=0;
 | |
| 			data[1] = 0x3;  // Slow blinking
 | |
| 			break;
 | |
| 		  case 0x3:
 | |
| 			if(oldvalam==0x5) { // alternately blinking
 | |
| 				data[0] = 0x10;	
 | |
| 				alternately=1;
 | |
| 			} else
 | |
| 				alternately=0;
 | |
| 			data[1] = 0x4;  // Fast blinking
 | |
| 			break;
 | |
| 		}
 | |
| 		oldvalgr=data[1];
 | |
| 	} else if (ldata->type == AMBER_LED) {
 | |
| 		switch(mode) {
 | |
| 		  case 0x0:
 | |
| 			if(alternately) {
 | |
| 				data[1]=oldvalgr; 
 | |
| 				alternately=0;
 | |
| 			} else
 | |
| 				data[1] = 0x0;  // Disable Light
 | |
| 			break;
 | |
| 		  case 0x1:
 | |
| 			data[1] = 0x2;  // Enable Light
 | |
| 			break;
 | |
| 		  case 0x2:
 | |
| 		  case 0x3:
 | |
| 			if(oldvalgr==0x3 || oldvalgr==0x4) { // alternately blinking
 | |
| 				data[0] = 0x10;	
 | |
| 				alternately=1;
 | |
| 			} else
 | |
| 				alternately=0;
 | |
| 			data[1] = 0x5;  // Fast blinking
 | |
| 			break;
 | |
| 		}
 | |
| 		oldvalam=data[1];
 | |
| 	}
 | |
| 
 | |
| 	ret = microp_i2c_write(MICROP_I2C_WCMD_LED_CTRL, data, 2);
 | |
| 	if (ret == 0) {
 | |
| 		mutex_lock(&ldata->led_data_mutex);
 | |
| 		if (mode > 1)
 | |
| 			ldata->blink = mode;
 | |
| 		else
 | |
| 			ldata->mode = mode;
 | |
| 		mutex_unlock(&ldata->led_data_mutex);
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t htcleo_leds_led_blink_show(struct device *dev,
 | |
| 				  struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	struct led_classdev *led_cdev;
 | |
| 	struct htcleo_leds_led_data *ldata;
 | |
| 	int ret;
 | |
| 
 | |
| 	led_cdev = (struct led_classdev *)dev_get_drvdata(dev);
 | |
| 	ldata = container_of(led_cdev, struct htcleo_leds_led_data, ldev);
 | |
| 
 | |
| 	mutex_lock(&ldata->led_data_mutex);
 | |
| 	ret = sprintf(buf, "%d\n", ldata->blink ? ldata->blink - 1 : 0);
 | |
| 	mutex_unlock(&ldata->led_data_mutex);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t htcleo_leds_led_blink_store(struct device *dev,
 | |
| 				   struct device_attribute *attr,
 | |
| 				   const char *buf, size_t count)
 | |
| {
 | |
| 	struct led_classdev *led_cdev;
 | |
| 	struct htcleo_leds_led_data *ldata;
 | |
| 	int val, ret;
 | |
| 	uint8_t mode;
 | |
| 
 | |
| 	val = -1;
 | |
| 	sscanf(buf, "%u", &val);
 | |
| 
 | |
| 	led_cdev = (struct led_classdev *)dev_get_drvdata(dev);
 | |
| 	ldata = container_of(led_cdev, struct htcleo_leds_led_data, ldev);
 | |
| 
 | |
| 	mutex_lock(&ldata->led_data_mutex);
 | |
| 	switch (val) {
 | |
| 	case 0: /* stop flashing */
 | |
| 		mode = ldata->mode;
 | |
| 		ldata->blink = 0;
 | |
| 		break;
 | |
| 	case 1:
 | |
| 	case 2:
 | |
| 		mode = val + 1;
 | |
| 		break;
 | |
| 
 | |
| 	default:
 | |
| 		mutex_unlock(&ldata->led_data_mutex);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	mutex_unlock(&ldata->led_data_mutex);
 | |
| 
 | |
| 	ret = htcleo_leds_write_led_mode(led_cdev, mode);
 | |
| 	if (ret)
 | |
| 		pr_err("%s set blink failed\n", led_cdev->name);
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(blink, 0644, htcleo_leds_led_blink_show,
 | |
| 				htcleo_leds_led_blink_store);
 | |
| 
 | |
| 				
 | |
| static void htcleo_leds_brightness_set(struct led_classdev *led_cdev,
 | |
| 			       enum led_brightness brightness)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 	struct i2c_client *client = to_i2c_client(led_cdev->dev->parent);
 | |
| 	struct htcleo_leds_led_data *ldata =
 | |
| 		container_of(led_cdev, struct htcleo_leds_led_data, ldev);
 | |
| 
 | |
| 	dev_dbg(&client->dev, "Setting %s brightness current %d new %d\n",
 | |
| 			led_cdev->name, led_cdev->brightness, brightness);
 | |
| 
 | |
| 	if (brightness > 255)
 | |
| 		brightness = 255;
 | |
| 	led_cdev->brightness = brightness;
 | |
| 
 | |
| 	spin_lock_irqsave(&ldata->brightness_lock, flags);
 | |
| 	ldata->brightness = brightness;
 | |
| 	spin_unlock_irqrestore(&ldata->brightness_lock, flags);
 | |
| 
 | |
| 	schedule_work(&ldata->brightness_work);
 | |
| }
 | |
| 
 | |
| static void htcleo_leds_led_brightness_set_work(struct work_struct *work)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 	struct htcleo_leds_led_data *ldata =
 | |
| 		container_of(work, struct htcleo_leds_led_data, brightness_work);
 | |
| 	struct led_classdev *led_cdev = &ldata->ldev;
 | |
| 
 | |
| 	struct i2c_client *client = to_i2c_client(led_cdev->dev->parent);
 | |
| 
 | |
| 	enum led_brightness brightness;
 | |
| 	int ret;
 | |
| 	uint8_t mode;
 | |
| 
 | |
| 	spin_lock_irqsave(&ldata->brightness_lock, flags);
 | |
| 	brightness = ldata->brightness;
 | |
| 	spin_unlock_irqrestore(&ldata->brightness_lock, flags);
 | |
| 
 | |
| 	if (brightness)
 | |
| 		mode = 1;
 | |
| 	else
 | |
| 		mode = 0;
 | |
| 
 | |
| 	ret = htcleo_leds_write_led_mode(led_cdev, mode);
 | |
| 	if (ret) {
 | |
| 		dev_err(&client->dev,
 | |
| 			 "led_brightness_set failed to set mode\n");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| struct device_attribute *green_amber_attrs[] = {
 | |
| 	&dev_attr_blink,
 | |
| };
 | |
| 
 | |
| static struct {
 | |
| 	const char *name;
 | |
| 	void (*led_set_work)(struct work_struct *);
 | |
| 	struct device_attribute **attrs;
 | |
| 	int attr_cnt;
 | |
| } htcleo_leds_leds[] = {
 | |
| 	[GREEN_LED] = {
 | |
| 		.name		= "green",
 | |
| 		.led_set_work   = htcleo_leds_led_brightness_set_work,
 | |
| 		.attrs		= green_amber_attrs,
 | |
| 		.attr_cnt	= ARRAY_SIZE(green_amber_attrs)
 | |
| 	},
 | |
| 	[AMBER_LED] = {
 | |
| 		.name		= "amber",
 | |
| 		.led_set_work   = htcleo_leds_led_brightness_set_work,
 | |
| 		.attrs		= green_amber_attrs,
 | |
| 		.attr_cnt	= ARRAY_SIZE(green_amber_attrs)
 | |
| 	},
 | |
| };
 | |
| 
 | |
| 
 | |
| static int htcleo_leds_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	int rc, i, j;
 | |
| 	struct htcleo_leds_data *cdata;
 | |
| 		
 | |
| 	rc= 0;
 | |
| 
 | |
| 	pr_info("%s\n", __func__);
 | |
| 
 | |
| 	cdata = &the_data;
 | |
| 	platform_set_drvdata(pdev, cdata);
 | |
| 
 | |
| 	htcleo_leds_disable_lights();
 | |
| 	
 | |
| 	for (i = 0; i < ARRAY_SIZE(htcleo_leds_leds) && !rc; ++i) {
 | |
| 		struct htcleo_leds_led_data *ldata = &cdata->leds[i];
 | |
| 
 | |
| 		ldata->type = i;
 | |
| 		ldata->ldev.name = htcleo_leds_leds[i].name;
 | |
| 		ldata->ldev.brightness_set = htcleo_leds_brightness_set;
 | |
| 		mutex_init(&ldata->led_data_mutex);
 | |
| 		INIT_WORK(&ldata->brightness_work, htcleo_leds_leds[i].led_set_work);
 | |
| 		spin_lock_init(&ldata->brightness_lock);
 | |
| 		rc = led_classdev_register(&pdev->dev, &ldata->ldev);
 | |
| 		if (rc) {
 | |
| 			ldata->ldev.name = NULL;
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		for (j = 0; j < htcleo_leds_leds[i].attr_cnt && !rc; ++j)
 | |
| 			rc = device_create_file(ldata->ldev.dev,
 | |
| 						 htcleo_leds_leds[i].attrs[j]);
 | |
| 	}
 | |
| 	if (rc) {
 | |
| 		dev_err(&pdev->dev, "failed to add leds\n");
 | |
| 		goto err_add_leds;
 | |
| 	}
 | |
| 	
 | |
| 
 | |
| 	goto done;
 | |
| 
 | |
| 
 | |
| err_add_leds:
 | |
| 	for (i = 0; i < ARRAY_SIZE(htcleo_leds_leds); ++i) {
 | |
| 		if (!cdata->leds[i].ldev.name)
 | |
| 			continue;
 | |
| 		led_classdev_unregister(&cdata->leds[i].ldev);
 | |
| 		for (j = 0; j < htcleo_leds_leds[i].attr_cnt; ++j)
 | |
| 			device_remove_file(cdata->leds[i].ldev.dev,
 | |
| 					   htcleo_leds_leds[i].attrs[j]);
 | |
| 	}
 | |
| 
 | |
| done:
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| static struct platform_driver htcleo_leds_driver = {
 | |
| 	.probe = htcleo_leds_probe,
 | |
| 	.driver = {
 | |
| 		.name = "htcleo-leds",
 | |
| 		.owner = THIS_MODULE
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static int __init htcleo_leds_init(void)
 | |
| {
 | |
| 	return platform_driver_register(&htcleo_leds_driver);
 | |
| }
 | |
| 
 | |
| device_initcall(htcleo_leds_init);
 | |
| 
 | |
| MODULE_DESCRIPTION("HTC LEO LED Support");
 | |
| MODULE_LICENSE("GPL");
 |