290 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* arch/arm/mach-msm/htc_pwrsink.c
 | |
|  *
 | |
|  * Copyright (C) 2008 HTC Corporation
 | |
|  * Copyright (C) 2008 Google, Inc.
 | |
|  * Author: San Mehat <san@google.com>
 | |
|  *         Kant Kang <kant_kang@htc.com>
 | |
|  *         Eiven Peng <eiven_peng@htc.com>
 | |
|  *
 | |
|  * 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/platform_device.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/debugfs.h>
 | |
| #include <linux/earlysuspend.h>
 | |
| #include <mach/msm_smd.h>
 | |
| #include <mach/htc_pwrsink.h>
 | |
| 
 | |
| #include "smd_private.h"
 | |
| 
 | |
| enum {
 | |
| 	PWRSINK_DEBUG_CURR_CHANGE = 1U << 0,
 | |
| 	PWRSINK_DEBUG_CURR_CHANGE_AUDIO = 1U << 1,
 | |
| };
 | |
| static int pwrsink_debug_mask;
 | |
| module_param_named(debug_mask, pwrsink_debug_mask, int,
 | |
| 		S_IRUGO | S_IWUSR | S_IWGRP);
 | |
| 
 | |
| static int initialized;
 | |
| static unsigned audio_path = 1;	/* HTC_SND_DEVICE_SPEAKER = 1 */
 | |
| static struct pwr_sink_audio audio_sink_array[PWRSINK_AUDIO_LAST + 1];
 | |
| static struct pwr_sink *sink_array[PWRSINK_LAST + 1];
 | |
| static DEFINE_SPINLOCK(sink_lock);
 | |
| static DEFINE_SPINLOCK(audio_sink_lock);
 | |
| static unsigned long total_sink;
 | |
| static uint32_t *smem_total_sink;
 | |
| 
 | |
| int htc_pwrsink_set(pwrsink_id_type id, unsigned percent_utilized)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (!smem_total_sink)
 | |
| 		smem_total_sink = smem_alloc(SMEM_ID_VENDOR0, sizeof(uint32_t));
 | |
| 
 | |
| 	if (!initialized)
 | |
| 		return -EAGAIN;
 | |
| 
 | |
| 	if (id < 0 || id > PWRSINK_LAST)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	spin_lock_irqsave(&sink_lock, flags);
 | |
| 
 | |
| 	if (!sink_array[id]) {
 | |
| 		spin_unlock_irqrestore(&sink_lock, flags);
 | |
| 		return -ENOENT;
 | |
| 	}
 | |
| 
 | |
| 	if (sink_array[id]->percent_util == percent_utilized) {
 | |
| 		spin_unlock_irqrestore(&sink_lock, flags);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	total_sink -= (sink_array[id]->ua_max *
 | |
| 		       sink_array[id]->percent_util / 100);
 | |
| 	sink_array[id]->percent_util = percent_utilized;
 | |
| 	total_sink += (sink_array[id]->ua_max *
 | |
| 		       sink_array[id]->percent_util / 100);
 | |
| 
 | |
| 	if (smem_total_sink)
 | |
| 		*smem_total_sink = total_sink / 1000;
 | |
| 
 | |
| 	pr_debug("htc_pwrsink: ID %d, Util %d%%, Total %lu uA %s\n",
 | |
| 		 id, percent_utilized, total_sink,
 | |
| 		 smem_total_sink ? "SET" : "");
 | |
| 
 | |
| 	spin_unlock_irqrestore(&sink_lock, flags);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(htc_pwrsink_set);
 | |
| 
 | |
| static void compute_audio_current(void)
 | |
| {
 | |
| 	/* unsigned long flags; */
 | |
| 	unsigned max_percent = 0;
 | |
| 	int i, active_audio_sinks = 0;
 | |
| 	pwrsink_audio_id_type last_active_audio_sink = 0;
 | |
| 
 | |
| 	/* Make sure this segment will be spinlocked
 | |
| 	before computing by calling function. */
 | |
| 	/* spin_lock_irqsave(&audio_sink_lock, flags); */
 | |
| 	for (i = 0; i <= PWRSINK_AUDIO_LAST; ++i) {
 | |
| 		max_percent = (audio_sink_array[i].percent > max_percent) ?
 | |
| 				audio_sink_array[i].percent : max_percent;
 | |
| 		if (audio_sink_array[i].percent > 0) {
 | |
| 			active_audio_sinks++;
 | |
| 			last_active_audio_sink = i;
 | |
| 		}
 | |
| 	}
 | |
| 	if (active_audio_sinks == 0)
 | |
| 		htc_pwrsink_set(PWRSINK_AUDIO, 0);
 | |
| 	else if (active_audio_sinks == 1) {
 | |
| 		pwrsink_audio_id_type laas =  last_active_audio_sink;
 | |
| 		/* TODO: add volume and routing path current. */
 | |
| 		if (audio_path == 1)	/* Speaker */
 | |
| 			htc_pwrsink_set(PWRSINK_AUDIO,
 | |
| 				audio_sink_array[laas].percent);
 | |
| 		else
 | |
| 			htc_pwrsink_set(PWRSINK_AUDIO,
 | |
| 				audio_sink_array[laas].percent * 9 / 10);
 | |
| 	} else if (active_audio_sinks > 1) {
 | |
| 		/* TODO: add volume and routing path current. */
 | |
| 		if (audio_path == 1)	/* Speaker */
 | |
| 			htc_pwrsink_set(PWRSINK_AUDIO, max_percent);
 | |
| 		else
 | |
| 			htc_pwrsink_set(PWRSINK_AUDIO, max_percent * 9 / 10);
 | |
| 	}
 | |
| 	/* spin_unlock_irqrestore(&audio_sink_lock, flags); */
 | |
| 
 | |
| 	if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
 | |
| 		pr_info("%s: active_audio_sinks=%d, audio_path=%d\n", __func__,
 | |
| 				active_audio_sinks, audio_path);
 | |
| }
 | |
| 
 | |
| int htc_pwrsink_audio_set(pwrsink_audio_id_type id, unsigned percent_utilized)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (id < 0 || id > PWRSINK_AUDIO_LAST)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
 | |
| 		pr_info("%s: id=%d, percent=%d, percent_old=%d\n", __func__,
 | |
| 			id, percent_utilized, audio_sink_array[id].percent);
 | |
| 
 | |
| 	spin_lock_irqsave(&audio_sink_lock, flags);
 | |
| 	if (audio_sink_array[id].percent == percent_utilized) {
 | |
| 		spin_unlock_irqrestore(&audio_sink_lock, flags);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	audio_sink_array[id].percent = percent_utilized;
 | |
| 	spin_unlock_irqrestore(&audio_sink_lock, flags);
 | |
| 	compute_audio_current();
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(htc_pwrsink_audio_set);
 | |
| 
 | |
| int htc_pwrsink_audio_volume_set(pwrsink_audio_id_type id, unsigned volume)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (id < 0 || id > PWRSINK_AUDIO_LAST)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
 | |
| 		pr_info("%s: id=%d, volume=%d, volume_old=%d\n", __func__,
 | |
| 			id, volume, audio_sink_array[id].volume);
 | |
| 
 | |
| 	spin_lock_irqsave(&audio_sink_lock, flags);
 | |
| 	if (audio_sink_array[id].volume == volume) {
 | |
| 		spin_unlock_irqrestore(&audio_sink_lock, flags);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	audio_sink_array[id].volume = volume;
 | |
| 	spin_unlock_irqrestore(&audio_sink_lock, flags);
 | |
| 	compute_audio_current();
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(htc_pwrsink_audio_volume_set);
 | |
| 
 | |
| int htc_pwrsink_audio_path_set(unsigned path)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
 | |
| 		pr_info("%s: path=%d, path_old=%d\n",
 | |
| 			__func__, path, audio_path);
 | |
| 
 | |
| 	spin_lock_irqsave(&audio_sink_lock, flags);
 | |
| 	if (audio_path == path) {
 | |
| 		spin_unlock_irqrestore(&audio_sink_lock, flags);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	audio_path = path;
 | |
| 	spin_unlock_irqrestore(&audio_sink_lock, flags);
 | |
| 	compute_audio_current();
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(htc_pwrsink_audio_path_set);
 | |
| 
 | |
| void htc_pwrsink_suspend_early(struct early_suspend *h)
 | |
| {
 | |
| 	htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 7);
 | |
| }
 | |
| 
 | |
| int htc_pwrsink_suspend_late(struct device *dev)
 | |
| {
 | |
| 	struct pwr_sink_platform_data *pdata = dev_get_platdata(dev);
 | |
| 
 | |
| 	if (pdata && pdata->suspend_late)
 | |
| 		pdata->suspend_late(to_platform_device(dev), PMSG_SUSPEND);
 | |
| 	else
 | |
| 		htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 1);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int htc_pwrsink_resume_early(struct device *dev)
 | |
| {
 | |
| 	struct pwr_sink_platform_data *pdata = dev_get_platdata(dev);;
 | |
| 
 | |
| 	if (pdata && pdata->resume_early)
 | |
| 		pdata->resume_early(to_platform_device(dev));
 | |
| 	else
 | |
| 		htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 7);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void htc_pwrsink_resume_late(struct early_suspend *h)
 | |
| {
 | |
| 	htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 38);
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_WAKELOCK
 | |
| struct early_suspend htc_pwrsink_early_suspend = {
 | |
| 	.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1,
 | |
| 	.suspend = htc_pwrsink_suspend_early,
 | |
| 	.resume = htc_pwrsink_resume_late,
 | |
| };
 | |
| #endif
 | |
| 
 | |
| static int __init htc_pwrsink_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct pwr_sink_platform_data *pdata = pdev->dev.platform_data;
 | |
| 	int i;
 | |
| 
 | |
| 	if (!pdata)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	total_sink = 0;
 | |
| 	for (i = 0; i < pdata->num_sinks; i++) {
 | |
| 		sink_array[pdata->sinks[i].id] = &pdata->sinks[i];
 | |
| 		total_sink += (pdata->sinks[i].ua_max *
 | |
| 			       pdata->sinks[i].percent_util / 100);
 | |
| 	}
 | |
| 
 | |
| 	initialized = 1;
 | |
| 
 | |
| #ifdef CONFIG_WAKELOCK
 | |
| 	if (pdata->suspend_early)
 | |
| 		htc_pwrsink_early_suspend.suspend = pdata->suspend_early;
 | |
| 	if (pdata->resume_late)
 | |
| 		htc_pwrsink_early_suspend.resume = pdata->resume_late;
 | |
| #endif
 | |
| 	register_early_suspend(&htc_pwrsink_early_suspend);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct dev_pm_ops htc_pwrsink_pm_ops = {
 | |
| 	.suspend_noirq = htc_pwrsink_suspend_late,
 | |
| 	.resume_noirq = htc_pwrsink_resume_early,
 | |
| };
 | |
| 
 | |
| static struct platform_driver htc_pwrsink_driver = {
 | |
| 	.probe = htc_pwrsink_probe,
 | |
| 	.driver = {
 | |
| 		.name = "htc_pwrsink",
 | |
| 		.owner = THIS_MODULE,
 | |
| 		.pm = &htc_pwrsink_pm_ops,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static int __init htc_pwrsink_init(void)
 | |
| {
 | |
| 	initialized = 0;
 | |
| 	memset(sink_array, 0, sizeof(sink_array));
 | |
| 	return platform_driver_register(&htc_pwrsink_driver);
 | |
| }
 | |
| 
 | |
| module_init(htc_pwrsink_init);
 |