| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 1 | /* | 
 | 2 |  * drivers/base/power/clock_ops.c - Generic clock manipulation PM callbacks | 
 | 3 |  * | 
 | 4 |  * Copyright (c) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Renesas Electronics Corp. | 
 | 5 |  * | 
 | 6 |  * This file is released under the GPLv2. | 
 | 7 |  */ | 
 | 8 |  | 
 | 9 | #include <linux/init.h> | 
 | 10 | #include <linux/kernel.h> | 
| Paul Gortmaker | 51990e8 | 2012-01-22 11:23:42 -0500 | [diff] [blame] | 11 | #include <linux/device.h> | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 12 | #include <linux/io.h> | 
 | 13 | #include <linux/pm.h> | 
| Rafael J. Wysocki | b5e8d26 | 2011-08-25 15:34:19 +0200 | [diff] [blame] | 14 | #include <linux/pm_clock.h> | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 15 | #include <linux/clk.h> | 
 | 16 | #include <linux/slab.h> | 
 | 17 | #include <linux/err.h> | 
 | 18 |  | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 19 | #ifdef CONFIG_PM | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 20 |  | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 21 | enum pce_status { | 
 | 22 | 	PCE_STATUS_NONE = 0, | 
 | 23 | 	PCE_STATUS_ACQUIRED, | 
 | 24 | 	PCE_STATUS_ENABLED, | 
 | 25 | 	PCE_STATUS_ERROR, | 
 | 26 | }; | 
 | 27 |  | 
 | 28 | struct pm_clock_entry { | 
 | 29 | 	struct list_head node; | 
 | 30 | 	char *con_id; | 
 | 31 | 	struct clk *clk; | 
 | 32 | 	enum pce_status status; | 
 | 33 | }; | 
 | 34 |  | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 35 | /** | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 36 |  * pm_clk_acquire - Acquire a device clock. | 
 | 37 |  * @dev: Device whose clock is to be acquired. | 
 | 38 |  * @ce: PM clock entry corresponding to the clock. | 
 | 39 |  */ | 
 | 40 | static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce) | 
 | 41 | { | 
 | 42 | 	ce->clk = clk_get(dev, ce->con_id); | 
 | 43 | 	if (IS_ERR(ce->clk)) { | 
 | 44 | 		ce->status = PCE_STATUS_ERROR; | 
 | 45 | 	} else { | 
 | 46 | 		ce->status = PCE_STATUS_ACQUIRED; | 
 | 47 | 		dev_dbg(dev, "Clock %s managed by runtime PM.\n", ce->con_id); | 
 | 48 | 	} | 
 | 49 | } | 
 | 50 |  | 
 | 51 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 52 |  * pm_clk_add - Start using a device clock for power management. | 
 | 53 |  * @dev: Device whose clock is going to be used for power management. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 54 |  * @con_id: Connection ID of the clock. | 
 | 55 |  * | 
 | 56 |  * Add the clock represented by @con_id to the list of clocks used for | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 57 |  * the power management of @dev. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 58 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 59 | int pm_clk_add(struct device *dev, const char *con_id) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 60 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 61 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 62 | 	struct pm_clock_entry *ce; | 
 | 63 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 64 | 	if (!psd) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 65 | 		return -EINVAL; | 
 | 66 |  | 
 | 67 | 	ce = kzalloc(sizeof(*ce), GFP_KERNEL); | 
 | 68 | 	if (!ce) { | 
 | 69 | 		dev_err(dev, "Not enough memory for clock entry.\n"); | 
 | 70 | 		return -ENOMEM; | 
 | 71 | 	} | 
 | 72 |  | 
 | 73 | 	if (con_id) { | 
 | 74 | 		ce->con_id = kstrdup(con_id, GFP_KERNEL); | 
 | 75 | 		if (!ce->con_id) { | 
 | 76 | 			dev_err(dev, | 
 | 77 | 				"Not enough memory for clock connection ID.\n"); | 
 | 78 | 			kfree(ce); | 
 | 79 | 			return -ENOMEM; | 
 | 80 | 		} | 
 | 81 | 	} | 
 | 82 |  | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 83 | 	pm_clk_acquire(dev, ce); | 
 | 84 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 85 | 	spin_lock_irq(&psd->lock); | 
 | 86 | 	list_add_tail(&ce->node, &psd->clock_list); | 
 | 87 | 	spin_unlock_irq(&psd->lock); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 88 | 	return 0; | 
 | 89 | } | 
 | 90 |  | 
 | 91 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 92 |  * __pm_clk_remove - Destroy PM clock entry. | 
 | 93 |  * @ce: PM clock entry to destroy. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 94 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 95 | static void __pm_clk_remove(struct pm_clock_entry *ce) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 96 | { | 
 | 97 | 	if (!ce) | 
 | 98 | 		return; | 
 | 99 |  | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 100 | 	if (ce->status < PCE_STATUS_ERROR) { | 
 | 101 | 		if (ce->status == PCE_STATUS_ENABLED) | 
 | 102 | 			clk_disable(ce->clk); | 
 | 103 |  | 
 | 104 | 		if (ce->status >= PCE_STATUS_ACQUIRED) | 
 | 105 | 			clk_put(ce->clk); | 
 | 106 | 	} | 
 | 107 |  | 
| Jonghwan Choi | 0ab1e79 | 2011-10-22 00:22:54 +0200 | [diff] [blame] | 108 | 	kfree(ce->con_id); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 109 | 	kfree(ce); | 
 | 110 | } | 
 | 111 |  | 
 | 112 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 113 |  * pm_clk_remove - Stop using a device clock for power management. | 
 | 114 |  * @dev: Device whose clock should not be used for PM any more. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 115 |  * @con_id: Connection ID of the clock. | 
 | 116 |  * | 
 | 117 |  * Remove the clock represented by @con_id from the list of clocks used for | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 118 |  * the power management of @dev. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 119 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 120 | void pm_clk_remove(struct device *dev, const char *con_id) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 121 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 122 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 123 | 	struct pm_clock_entry *ce; | 
 | 124 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 125 | 	if (!psd) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 126 | 		return; | 
 | 127 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 128 | 	spin_lock_irq(&psd->lock); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 129 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 130 | 	list_for_each_entry(ce, &psd->clock_list, node) { | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 131 | 		if (!con_id && !ce->con_id) | 
 | 132 | 			goto remove; | 
 | 133 | 		else if (!con_id || !ce->con_id) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 134 | 			continue; | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 135 | 		else if (!strcmp(con_id, ce->con_id)) | 
 | 136 | 			goto remove; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 137 | 	} | 
 | 138 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 139 | 	spin_unlock_irq(&psd->lock); | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 140 | 	return; | 
 | 141 |  | 
 | 142 |  remove: | 
 | 143 | 	list_del(&ce->node); | 
| Rafael J. Wysocki | 0d41da2 | 2011-09-26 20:12:45 +0200 | [diff] [blame] | 144 | 	spin_unlock_irq(&psd->lock); | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 145 |  | 
 | 146 | 	__pm_clk_remove(ce); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 147 | } | 
 | 148 |  | 
 | 149 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 150 |  * pm_clk_init - Initialize a device's list of power management clocks. | 
 | 151 |  * @dev: Device to initialize the list of PM clocks for. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 152 |  * | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 153 |  * Initialize the lock and clock_list members of the device's pm_subsys_data | 
 | 154 |  * object. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 155 |  */ | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 156 | void pm_clk_init(struct device *dev) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 157 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 158 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | ef27bed | 2011-08-25 15:34:01 +0200 | [diff] [blame] | 159 | 	if (psd) | 
 | 160 | 		INIT_LIST_HEAD(&psd->clock_list); | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 161 | } | 
 | 162 |  | 
 | 163 | /** | 
 | 164 |  * pm_clk_create - Create and initialize a device's list of PM clocks. | 
 | 165 |  * @dev: Device to create and initialize the list of PM clocks for. | 
 | 166 |  * | 
 | 167 |  * Allocate a struct pm_subsys_data object, initialize its lock and clock_list | 
 | 168 |  * members and make the @dev's power.subsys_data field point to it. | 
 | 169 |  */ | 
 | 170 | int pm_clk_create(struct device *dev) | 
 | 171 | { | 
| Rafael J. Wysocki | ef27bed | 2011-08-25 15:34:01 +0200 | [diff] [blame] | 172 | 	int ret = dev_pm_get_subsys_data(dev); | 
 | 173 | 	return ret < 0 ? ret : 0; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 174 | } | 
 | 175 |  | 
 | 176 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 177 |  * pm_clk_destroy - Destroy a device's list of power management clocks. | 
 | 178 |  * @dev: Device to destroy the list of PM clocks for. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 179 |  * | 
 | 180 |  * Clear the @dev's power.subsys_data field, remove the list of clock entries | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 181 |  * from the struct pm_subsys_data object pointed to by it before and free | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 182 |  * that object. | 
 | 183 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 184 | void pm_clk_destroy(struct device *dev) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 185 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 186 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 187 | 	struct pm_clock_entry *ce, *c; | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 188 | 	struct list_head list; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 189 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 190 | 	if (!psd) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 191 | 		return; | 
 | 192 |  | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 193 | 	INIT_LIST_HEAD(&list); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 194 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 195 | 	spin_lock_irq(&psd->lock); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 196 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 197 | 	list_for_each_entry_safe_reverse(ce, c, &psd->clock_list, node) | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 198 | 		list_move(&ce->node, &list); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 199 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 200 | 	spin_unlock_irq(&psd->lock); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 201 |  | 
| Rafael J. Wysocki | ef27bed | 2011-08-25 15:34:01 +0200 | [diff] [blame] | 202 | 	dev_pm_put_subsys_data(dev); | 
| Rafael J. Wysocki | e8b364b | 2011-09-26 19:40:23 +0200 | [diff] [blame] | 203 |  | 
 | 204 | 	list_for_each_entry_safe_reverse(ce, c, &list, node) { | 
 | 205 | 		list_del(&ce->node); | 
 | 206 | 		__pm_clk_remove(ce); | 
 | 207 | 	} | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 208 | } | 
 | 209 |  | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 210 | #endif /* CONFIG_PM */ | 
 | 211 |  | 
 | 212 | #ifdef CONFIG_PM_RUNTIME | 
 | 213 |  | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 214 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 215 |  * pm_clk_suspend - Disable clocks in a device's PM clock list. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 216 |  * @dev: Device to disable the clocks for. | 
 | 217 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 218 | int pm_clk_suspend(struct device *dev) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 219 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 220 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 221 | 	struct pm_clock_entry *ce; | 
| Rafael J. Wysocki | b7ab83e | 2011-08-24 21:40:56 +0200 | [diff] [blame] | 222 | 	unsigned long flags; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 223 |  | 
 | 224 | 	dev_dbg(dev, "%s()\n", __func__); | 
 | 225 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 226 | 	if (!psd) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 227 | 		return 0; | 
 | 228 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 229 | 	spin_lock_irqsave(&psd->lock, flags); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 230 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 231 | 	list_for_each_entry_reverse(ce, &psd->clock_list, node) { | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 232 | 		if (ce->status < PCE_STATUS_ERROR) { | 
| Magnus Damm | 2405095 | 2011-11-10 00:44:10 +0100 | [diff] [blame] | 233 | 			if (ce->status == PCE_STATUS_ENABLED) | 
 | 234 | 				clk_disable(ce->clk); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 235 | 			ce->status = PCE_STATUS_ACQUIRED; | 
 | 236 | 		} | 
 | 237 | 	} | 
 | 238 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 239 | 	spin_unlock_irqrestore(&psd->lock, flags); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 240 |  | 
 | 241 | 	return 0; | 
 | 242 | } | 
 | 243 |  | 
 | 244 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 245 |  * pm_clk_resume - Enable clocks in a device's PM clock list. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 246 |  * @dev: Device to enable the clocks for. | 
 | 247 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 248 | int pm_clk_resume(struct device *dev) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 249 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 250 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 251 | 	struct pm_clock_entry *ce; | 
| Rafael J. Wysocki | b7ab83e | 2011-08-24 21:40:56 +0200 | [diff] [blame] | 252 | 	unsigned long flags; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 253 |  | 
 | 254 | 	dev_dbg(dev, "%s()\n", __func__); | 
 | 255 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 256 | 	if (!psd) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 257 | 		return 0; | 
 | 258 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 259 | 	spin_lock_irqsave(&psd->lock, flags); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 260 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 261 | 	list_for_each_entry(ce, &psd->clock_list, node) { | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 262 | 		if (ce->status < PCE_STATUS_ERROR) { | 
 | 263 | 			clk_enable(ce->clk); | 
 | 264 | 			ce->status = PCE_STATUS_ENABLED; | 
 | 265 | 		} | 
 | 266 | 	} | 
 | 267 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 268 | 	spin_unlock_irqrestore(&psd->lock, flags); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 269 |  | 
 | 270 | 	return 0; | 
 | 271 | } | 
 | 272 |  | 
 | 273 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 274 |  * pm_clk_notify - Notify routine for device addition and removal. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 275 |  * @nb: Notifier block object this function is a member of. | 
 | 276 |  * @action: Operation being carried out by the caller. | 
 | 277 |  * @data: Device the routine is being run for. | 
 | 278 |  * | 
 | 279 |  * For this function to work, @nb must be a member of an object of type | 
 | 280 |  * struct pm_clk_notifier_block containing all of the requisite data. | 
| Rafael J. Wysocki | 564b905 | 2011-06-23 01:52:55 +0200 | [diff] [blame] | 281 |  * Specifically, the pm_domain member of that object is copied to the device's | 
 | 282 |  * pm_domain field and its con_ids member is used to populate the device's list | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 283 |  * of PM clocks, depending on @action. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 284 |  * | 
| Rafael J. Wysocki | 564b905 | 2011-06-23 01:52:55 +0200 | [diff] [blame] | 285 |  * If the device's pm_domain field is already populated with a value different | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 286 |  * from the one stored in the struct pm_clk_notifier_block object, the function | 
 | 287 |  * does nothing. | 
 | 288 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 289 | static int pm_clk_notify(struct notifier_block *nb, | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 290 | 				 unsigned long action, void *data) | 
 | 291 | { | 
 | 292 | 	struct pm_clk_notifier_block *clknb; | 
 | 293 | 	struct device *dev = data; | 
| Rafael J. Wysocki | 3b3eca3 | 2011-06-07 23:34:58 +0200 | [diff] [blame] | 294 | 	char **con_id; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 295 | 	int error; | 
 | 296 |  | 
 | 297 | 	dev_dbg(dev, "%s() %ld\n", __func__, action); | 
 | 298 |  | 
 | 299 | 	clknb = container_of(nb, struct pm_clk_notifier_block, nb); | 
 | 300 |  | 
 | 301 | 	switch (action) { | 
 | 302 | 	case BUS_NOTIFY_ADD_DEVICE: | 
| Rafael J. Wysocki | 564b905 | 2011-06-23 01:52:55 +0200 | [diff] [blame] | 303 | 		if (dev->pm_domain) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 304 | 			break; | 
 | 305 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 306 | 		error = pm_clk_create(dev); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 307 | 		if (error) | 
 | 308 | 			break; | 
 | 309 |  | 
| Rafael J. Wysocki | 564b905 | 2011-06-23 01:52:55 +0200 | [diff] [blame] | 310 | 		dev->pm_domain = clknb->pm_domain; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 311 | 		if (clknb->con_ids[0]) { | 
| Rafael J. Wysocki | 3b3eca3 | 2011-06-07 23:34:58 +0200 | [diff] [blame] | 312 | 			for (con_id = clknb->con_ids; *con_id; con_id++) | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 313 | 				pm_clk_add(dev, *con_id); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 314 | 		} else { | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 315 | 			pm_clk_add(dev, NULL); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 316 | 		} | 
 | 317 |  | 
 | 318 | 		break; | 
 | 319 | 	case BUS_NOTIFY_DEL_DEVICE: | 
| Rafael J. Wysocki | 564b905 | 2011-06-23 01:52:55 +0200 | [diff] [blame] | 320 | 		if (dev->pm_domain != clknb->pm_domain) | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 321 | 			break; | 
 | 322 |  | 
| Rafael J. Wysocki | 564b905 | 2011-06-23 01:52:55 +0200 | [diff] [blame] | 323 | 		dev->pm_domain = NULL; | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 324 | 		pm_clk_destroy(dev); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 325 | 		break; | 
 | 326 | 	} | 
 | 327 |  | 
 | 328 | 	return 0; | 
 | 329 | } | 
 | 330 |  | 
 | 331 | #else /* !CONFIG_PM_RUNTIME */ | 
 | 332 |  | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 333 | #ifdef CONFIG_PM | 
 | 334 |  | 
 | 335 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 336 |  * pm_clk_suspend - Disable clocks in a device's PM clock list. | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 337 |  * @dev: Device to disable the clocks for. | 
 | 338 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 339 | int pm_clk_suspend(struct device *dev) | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 340 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 341 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 342 | 	struct pm_clock_entry *ce; | 
| Rafael J. Wysocki | b7ab83e | 2011-08-24 21:40:56 +0200 | [diff] [blame] | 343 | 	unsigned long flags; | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 344 |  | 
 | 345 | 	dev_dbg(dev, "%s()\n", __func__); | 
 | 346 |  | 
 | 347 | 	/* If there is no driver, the clocks are already disabled. */ | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 348 | 	if (!psd || !dev->driver) | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 349 | 		return 0; | 
 | 350 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 351 | 	spin_lock_irqsave(&psd->lock, flags); | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 352 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 353 | 	list_for_each_entry_reverse(ce, &psd->clock_list, node) | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 354 | 		clk_disable(ce->clk); | 
 | 355 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 356 | 	spin_unlock_irqrestore(&psd->lock, flags); | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 357 |  | 
 | 358 | 	return 0; | 
 | 359 | } | 
 | 360 |  | 
 | 361 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 362 |  * pm_clk_resume - Enable clocks in a device's PM clock list. | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 363 |  * @dev: Device to enable the clocks for. | 
 | 364 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 365 | int pm_clk_resume(struct device *dev) | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 366 | { | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 367 | 	struct pm_subsys_data *psd = dev_to_psd(dev); | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 368 | 	struct pm_clock_entry *ce; | 
| Rafael J. Wysocki | b7ab83e | 2011-08-24 21:40:56 +0200 | [diff] [blame] | 369 | 	unsigned long flags; | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 370 |  | 
 | 371 | 	dev_dbg(dev, "%s()\n", __func__); | 
 | 372 |  | 
 | 373 | 	/* If there is no driver, the clocks should remain disabled. */ | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 374 | 	if (!psd || !dev->driver) | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 375 | 		return 0; | 
 | 376 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 377 | 	spin_lock_irqsave(&psd->lock, flags); | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 378 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 379 | 	list_for_each_entry(ce, &psd->clock_list, node) | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 380 | 		clk_enable(ce->clk); | 
 | 381 |  | 
| Rafael J. Wysocki | 5c095a0 | 2011-08-25 15:33:50 +0200 | [diff] [blame] | 382 | 	spin_unlock_irqrestore(&psd->lock, flags); | 
| Rafael J. Wysocki | b7b9592 | 2011-07-01 22:13:37 +0200 | [diff] [blame] | 383 |  | 
 | 384 | 	return 0; | 
 | 385 | } | 
 | 386 |  | 
 | 387 | #endif /* CONFIG_PM */ | 
 | 388 |  | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 389 | /** | 
 | 390 |  * enable_clock - Enable a device clock. | 
 | 391 |  * @dev: Device whose clock is to be enabled. | 
 | 392 |  * @con_id: Connection ID of the clock. | 
 | 393 |  */ | 
 | 394 | static void enable_clock(struct device *dev, const char *con_id) | 
 | 395 | { | 
 | 396 | 	struct clk *clk; | 
 | 397 |  | 
 | 398 | 	clk = clk_get(dev, con_id); | 
 | 399 | 	if (!IS_ERR(clk)) { | 
 | 400 | 		clk_enable(clk); | 
 | 401 | 		clk_put(clk); | 
 | 402 | 		dev_info(dev, "Runtime PM disabled, clock forced on.\n"); | 
 | 403 | 	} | 
 | 404 | } | 
 | 405 |  | 
 | 406 | /** | 
 | 407 |  * disable_clock - Disable a device clock. | 
 | 408 |  * @dev: Device whose clock is to be disabled. | 
 | 409 |  * @con_id: Connection ID of the clock. | 
 | 410 |  */ | 
 | 411 | static void disable_clock(struct device *dev, const char *con_id) | 
 | 412 | { | 
 | 413 | 	struct clk *clk; | 
 | 414 |  | 
 | 415 | 	clk = clk_get(dev, con_id); | 
 | 416 | 	if (!IS_ERR(clk)) { | 
 | 417 | 		clk_disable(clk); | 
 | 418 | 		clk_put(clk); | 
 | 419 | 		dev_info(dev, "Runtime PM disabled, clock forced off.\n"); | 
 | 420 | 	} | 
 | 421 | } | 
 | 422 |  | 
 | 423 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 424 |  * pm_clk_notify - Notify routine for device addition and removal. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 425 |  * @nb: Notifier block object this function is a member of. | 
 | 426 |  * @action: Operation being carried out by the caller. | 
 | 427 |  * @data: Device the routine is being run for. | 
 | 428 |  * | 
 | 429 |  * For this function to work, @nb must be a member of an object of type | 
 | 430 |  * struct pm_clk_notifier_block containing all of the requisite data. | 
 | 431 |  * Specifically, the con_ids member of that object is used to enable or disable | 
 | 432 |  * the device's clocks, depending on @action. | 
 | 433 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 434 | static int pm_clk_notify(struct notifier_block *nb, | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 435 | 				 unsigned long action, void *data) | 
 | 436 | { | 
 | 437 | 	struct pm_clk_notifier_block *clknb; | 
 | 438 | 	struct device *dev = data; | 
| Rafael J. Wysocki | 3b3eca3 | 2011-06-07 23:34:58 +0200 | [diff] [blame] | 439 | 	char **con_id; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 440 |  | 
 | 441 | 	dev_dbg(dev, "%s() %ld\n", __func__, action); | 
 | 442 |  | 
 | 443 | 	clknb = container_of(nb, struct pm_clk_notifier_block, nb); | 
 | 444 |  | 
 | 445 | 	switch (action) { | 
| Rafael J. Wysocki | 4d1518f | 2011-06-21 23:24:33 +0200 | [diff] [blame] | 446 | 	case BUS_NOTIFY_BIND_DRIVER: | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 447 | 		if (clknb->con_ids[0]) { | 
| Rafael J. Wysocki | 3b3eca3 | 2011-06-07 23:34:58 +0200 | [diff] [blame] | 448 | 			for (con_id = clknb->con_ids; *con_id; con_id++) | 
 | 449 | 				enable_clock(dev, *con_id); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 450 | 		} else { | 
 | 451 | 			enable_clock(dev, NULL); | 
 | 452 | 		} | 
 | 453 | 		break; | 
| Rafael J. Wysocki | 4d1518f | 2011-06-21 23:24:33 +0200 | [diff] [blame] | 454 | 	case BUS_NOTIFY_UNBOUND_DRIVER: | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 455 | 		if (clknb->con_ids[0]) { | 
| Rafael J. Wysocki | 3b3eca3 | 2011-06-07 23:34:58 +0200 | [diff] [blame] | 456 | 			for (con_id = clknb->con_ids; *con_id; con_id++) | 
 | 457 | 				disable_clock(dev, *con_id); | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 458 | 		} else { | 
 | 459 | 			disable_clock(dev, NULL); | 
 | 460 | 		} | 
 | 461 | 		break; | 
 | 462 | 	} | 
 | 463 |  | 
 | 464 | 	return 0; | 
 | 465 | } | 
 | 466 |  | 
 | 467 | #endif /* !CONFIG_PM_RUNTIME */ | 
 | 468 |  | 
 | 469 | /** | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 470 |  * pm_clk_add_notifier - Add bus type notifier for power management clocks. | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 471 |  * @bus: Bus type to add the notifier to. | 
 | 472 |  * @clknb: Notifier to be added to the given bus type. | 
 | 473 |  * | 
 | 474 |  * The nb member of @clknb is not expected to be initialized and its | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 475 |  * notifier_call member will be replaced with pm_clk_notify().  However, | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 476 |  * the remaining members of @clknb should be populated prior to calling this | 
 | 477 |  * routine. | 
 | 478 |  */ | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 479 | void pm_clk_add_notifier(struct bus_type *bus, | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 480 | 				 struct pm_clk_notifier_block *clknb) | 
 | 481 | { | 
 | 482 | 	if (!bus || !clknb) | 
 | 483 | 		return; | 
 | 484 |  | 
| Rafael J. Wysocki | 3d5c303 | 2011-07-01 22:13:44 +0200 | [diff] [blame] | 485 | 	clknb->nb.notifier_call = pm_clk_notify; | 
| Rafael J. Wysocki | 85eb8c8 | 2011-04-30 00:25:44 +0200 | [diff] [blame] | 486 | 	bus_register_notifier(bus, &clknb->nb); | 
 | 487 | } |