| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  *  pm.c - Power management interface | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  Copyright (C) 2000 Andrew Henroid | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  *  it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  *  the Free Software Foundation; either version 2 of the License, or | 
					
						
							|  |  |  |  *  (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  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. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  *  along with this program; if not, write to the Free Software | 
					
						
							|  |  |  |  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/spinlock.h>
 | 
					
						
							|  |  |  | #include <linux/mm.h>
 | 
					
						
							|  |  |  | #include <linux/slab.h>
 | 
					
						
							|  |  |  | #include <linux/pm.h>
 | 
					
						
							| 
									
										
										
										
											2005-11-13 16:06:25 -08:00
										 |  |  | #include <linux/pm_legacy.h>
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #include <linux/interrupt.h>
 | 
					
						
							| 
									
										
										
										
											2006-03-23 03:00:24 -08:00
										 |  |  | #include <linux/mutex.h>
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  *	Locking notes: | 
					
						
							|  |  |  |  *		pm_devs_lock can be a semaphore providing pm ops are not called | 
					
						
							|  |  |  |  *	from an interrupt handler (already a bad idea so no change here). Each | 
					
						
							|  |  |  |  *	change must be protected so that an unlink of an entry doesn't clash | 
					
						
							|  |  |  |  *	with a pm send - which is permitted to sleep in the current architecture | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Module unloads clashing with pm events now work out safely, the module  | 
					
						
							|  |  |  |  *	unload path will block until the event has been sent. It may well block | 
					
						
							|  |  |  |  *	until a resume but that will be fine. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  |   | 
					
						
							| 
									
										
										
										
											2006-03-23 03:00:24 -08:00
										 |  |  | static DEFINE_MUTEX(pm_devs_lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static LIST_HEAD(pm_devs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  *	pm_register - register a device with power management | 
					
						
							|  |  |  |  *	@type: device type  | 
					
						
							|  |  |  |  *	@id: device ID | 
					
						
							|  |  |  |  *	@callback: callback function | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Add a device to the list of devices that wish to be notified about | 
					
						
							|  |  |  |  *	power management events. A &pm_dev structure is returned on success, | 
					
						
							|  |  |  |  *	on failure the return is %NULL. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *      The callback function will be called in process context and | 
					
						
							|  |  |  |  *      it may sleep. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  | struct pm_dev *pm_register(pm_dev_t type, | 
					
						
							|  |  |  | 			   unsigned long id, | 
					
						
							|  |  |  | 			   pm_callback callback) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2005-09-06 15:18:31 -07:00
										 |  |  | 	struct pm_dev *dev = kzalloc(sizeof(struct pm_dev), GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (dev) { | 
					
						
							|  |  |  | 		dev->type = type; | 
					
						
							|  |  |  | 		dev->id = id; | 
					
						
							|  |  |  | 		dev->callback = callback; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-23 03:00:24 -08:00
										 |  |  | 		mutex_lock(&pm_devs_lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		list_add(&dev->entry, &pm_devs); | 
					
						
							| 
									
										
										
										
											2006-03-23 03:00:24 -08:00
										 |  |  | 		mutex_unlock(&pm_devs_lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return dev; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  *	pm_send - send request to a single device | 
					
						
							|  |  |  |  *	@dev: device to send to | 
					
						
							|  |  |  |  *	@rqst: power management request | 
					
						
							|  |  |  |  *	@data: data for the callback | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Issue a power management request to a given device. The  | 
					
						
							|  |  |  |  *	%PM_SUSPEND and %PM_RESUME events are handled specially. The | 
					
						
							|  |  |  |  *	data field must hold the intended next state. No call is made | 
					
						
							|  |  |  |  *	if the state matches. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	BUGS: what stops two power management requests occurring in parallel | 
					
						
							|  |  |  |  *	and conflicting. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	WARNING: Calling pm_send directly is not generally recommended, in | 
					
						
							|  |  |  |  *	particular there is no locking against the pm_dev going away. The | 
					
						
							|  |  |  |  *	caller must maintain all needed locking or have 'inside knowledge' | 
					
						
							|  |  |  |  *	on the safety. Also remember that this function is not locked against | 
					
						
							|  |  |  |  *	pm_unregister. This means that you must handle SMP races on callback | 
					
						
							|  |  |  |  *	execution and unload yourself. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  | static int pm_send(struct pm_dev *dev, pm_request_t rqst, void *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int status = 0; | 
					
						
							|  |  |  | 	unsigned long prev_state, next_state; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (in_interrupt()) | 
					
						
							|  |  |  | 		BUG(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (rqst) { | 
					
						
							|  |  |  | 	case PM_SUSPEND: | 
					
						
							|  |  |  | 	case PM_RESUME: | 
					
						
							|  |  |  | 		prev_state = dev->state; | 
					
						
							|  |  |  | 		next_state = (unsigned long) data; | 
					
						
							|  |  |  | 		if (prev_state != next_state) { | 
					
						
							|  |  |  | 			if (dev->callback) | 
					
						
							|  |  |  | 				status = (*dev->callback)(dev, rqst, data); | 
					
						
							|  |  |  | 			if (!status) { | 
					
						
							|  |  |  | 				dev->state = next_state; | 
					
						
							|  |  |  | 				dev->prev_state = prev_state; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		else { | 
					
						
							|  |  |  | 			dev->prev_state = prev_state; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		if (dev->callback) | 
					
						
							|  |  |  | 			status = (*dev->callback)(dev, rqst, data); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return status; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Undo incomplete request | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static void pm_undo_all(struct pm_dev *last) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct list_head *entry = last->entry.prev; | 
					
						
							|  |  |  | 	while (entry != &pm_devs) { | 
					
						
							|  |  |  | 		struct pm_dev *dev = list_entry(entry, struct pm_dev, entry); | 
					
						
							|  |  |  | 		if (dev->state != dev->prev_state) { | 
					
						
							|  |  |  | 			/* previous state was zero (running) resume or
 | 
					
						
							|  |  |  | 			 * previous state was non-zero (suspended) suspend | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			pm_request_t undo = (dev->prev_state | 
					
						
							|  |  |  | 					     ? PM_SUSPEND:PM_RESUME); | 
					
						
							|  |  |  | 			pm_send(dev, undo, (void*) dev->prev_state); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		entry = entry->prev; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  *	pm_send_all - send request to all managed devices | 
					
						
							|  |  |  |  *	@rqst: power management request | 
					
						
							|  |  |  |  *	@data: data for the callback | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Issue a power management request to a all devices. The  | 
					
						
							|  |  |  |  *	%PM_SUSPEND events are handled specially. Any device is  | 
					
						
							|  |  |  |  *	permitted to fail a suspend by returning a non zero (error) | 
					
						
							|  |  |  |  *	value from its callback function. If any device vetoes a  | 
					
						
							|  |  |  |  *	suspend request then all other devices that have suspended  | 
					
						
							|  |  |  |  *	during the processing of this request are restored to their | 
					
						
							|  |  |  |  *	previous state. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	WARNING:  This function takes the pm_devs_lock. The lock is not dropped until | 
					
						
							|  |  |  |  *	the callbacks have completed. This prevents races against pm locking | 
					
						
							|  |  |  |  *	functions, races against module unload pm_unregister code. It does | 
					
						
							|  |  |  |  *	mean however that you must not issue pm_ functions within the callback | 
					
						
							|  |  |  |  *	or you will deadlock and users will hate you. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Zero is returned on success. If a suspend fails then the status | 
					
						
							|  |  |  |  *	from the device that vetoes the suspend is returned. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	BUGS: what stops two power management requests occurring in parallel | 
					
						
							|  |  |  |  *	and conflicting. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  | int pm_send_all(pm_request_t rqst, void *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct list_head *entry; | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2006-03-23 03:00:24 -08:00
										 |  |  | 	mutex_lock(&pm_devs_lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	entry = pm_devs.next; | 
					
						
							|  |  |  | 	while (entry != &pm_devs) { | 
					
						
							|  |  |  | 		struct pm_dev *dev = list_entry(entry, struct pm_dev, entry); | 
					
						
							|  |  |  | 		if (dev->callback) { | 
					
						
							|  |  |  | 			int status = pm_send(dev, rqst, data); | 
					
						
							|  |  |  | 			if (status) { | 
					
						
							|  |  |  | 				/* return devices to previous state on
 | 
					
						
							|  |  |  | 				 * failed suspend request | 
					
						
							|  |  |  | 				 */ | 
					
						
							|  |  |  | 				if (rqst == PM_SUSPEND) | 
					
						
							|  |  |  | 					pm_undo_all(dev); | 
					
						
							| 
									
										
										
										
											2006-03-23 03:00:24 -08:00
										 |  |  | 				mutex_unlock(&pm_devs_lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				return status; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		entry = entry->next; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2006-03-23 03:00:24 -08:00
										 |  |  | 	mutex_unlock(&pm_devs_lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | EXPORT_SYMBOL(pm_register); | 
					
						
							|  |  |  | EXPORT_SYMBOL(pm_send_all); | 
					
						
							|  |  |  | 
 |