blob: 3d9067824282742bd99d2a853d2a299e1362c8ad [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 */
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <linux/delay.h>
17#include <linux/init.h>
18#include <linux/io.h>
19#include <mach/msm_iomap.h>
20
21#include "spm.h"
22
23
24enum {
25 MSM_SPM_DEBUG_SHADOW = 1U << 0,
26 MSM_SPM_DEBUG_VCTL = 1U << 1,
27};
28
29static int msm_spm_debug_mask;
30module_param_named(
31 debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
32);
33
34#define MSM_SPM_PMIC_STATE_IDLE 0
35
36static uint32_t msm_spm_reg_offsets[MSM_SPM_REG_NR] = {
37 [MSM_SPM_REG_SAW_AVS_CTL] = 0x04,
38
39 [MSM_SPM_REG_SAW_VCTL] = 0x08,
40 [MSM_SPM_REG_SAW_STS] = 0x0C,
41 [MSM_SPM_REG_SAW_CFG] = 0x10,
42
43 [MSM_SPM_REG_SAW_SPM_CTL] = 0x14,
44 [MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0x18,
45 [MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0x1C,
46
47 [MSM_SPM_REG_SAW_SPM_PMIC_CTL] = 0x20,
48 [MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x24,
49 [MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x28,
50 [MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x2C,
51
52 [MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x30,
53 [MSM_SPM_REG_SAW_SLP_RST_EN] = 0x34,
54 [MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x38,
55};
56
57struct msm_spm_device {
58 void __iomem *reg_base_addr;
59 uint32_t reg_shadow[MSM_SPM_REG_NR];
60
61 uint8_t awake_vlevel;
62 uint8_t retention_vlevel;
63 uint8_t collapse_vlevel;
64 uint8_t retention_mid_vlevel;
65 uint8_t collapse_mid_vlevel;
66
67 uint32_t vctl_timeout_us;
68
69 unsigned int low_power_mode;
70 bool notify_rpm;
71 bool dirty;
72};
73
74static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_spm_devices);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070075/******************************************************************************
76 * Internal helper functions
77 *****************************************************************************/
78
79static inline void msm_spm_set_vctl(
80 struct msm_spm_device *dev, uint32_t vlevel)
81{
82 dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0xFF;
83 dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= vlevel;
84}
85
86static inline void msm_spm_set_spm_ctl(struct msm_spm_device *dev,
87 uint32_t rpm_bypass, uint32_t mode_encoding)
88{
89 dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] &= ~0x0F;
90 dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= rpm_bypass << 3;
91 dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= mode_encoding;
92}
93
94static inline void msm_spm_set_pmic_ctl(struct msm_spm_device *dev,
95 uint32_t awake_vlevel, uint32_t mid_vlevel, uint32_t sleep_vlevel)
96{
97 dev->reg_shadow[MSM_SPM_REG_SAW_SPM_PMIC_CTL] =
98 (mid_vlevel << 16) | (awake_vlevel << 8) | (sleep_vlevel);
99}
100
101static inline void msm_spm_set_slp_rst_en(
102 struct msm_spm_device *dev, uint32_t slp_rst_en)
103{
104 dev->reg_shadow[MSM_SPM_REG_SAW_SLP_RST_EN] = slp_rst_en;
105}
106
107static inline void msm_spm_flush_shadow(
108 struct msm_spm_device *dev, unsigned int reg_index)
109{
110 __raw_writel(dev->reg_shadow[reg_index],
111 dev->reg_base_addr + msm_spm_reg_offsets[reg_index]);
112}
113
114static inline void msm_spm_load_shadow(
115 struct msm_spm_device *dev, unsigned int reg_index)
116{
117 dev->reg_shadow[reg_index] = __raw_readl(dev->reg_base_addr +
118 msm_spm_reg_offsets[reg_index]);
119}
120
121static inline uint32_t msm_spm_get_sts_pmic_state(struct msm_spm_device *dev)
122{
123 return (dev->reg_shadow[MSM_SPM_REG_SAW_STS] >> 20) & 0x03;
124}
125
126static inline uint32_t msm_spm_get_sts_curr_pmic_data(
127 struct msm_spm_device *dev)
128{
129 return (dev->reg_shadow[MSM_SPM_REG_SAW_STS] >> 10) & 0xFF;
130}
131
132/******************************************************************************
133 * Public functions
134 *****************************************************************************/
135int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
136{
137 struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices);
138 uint32_t rpm_bypass = notify_rpm ? 0x00 : 0x01;
139
140 if (mode == dev->low_power_mode && notify_rpm == dev->notify_rpm
141 && !dev->dirty)
142 return 0;
143
144 switch (mode) {
145 case MSM_SPM_MODE_CLOCK_GATING:
146 msm_spm_set_spm_ctl(dev, rpm_bypass, 0x00);
147 msm_spm_set_slp_rst_en(dev, 0x00);
148 break;
149
150 case MSM_SPM_MODE_POWER_RETENTION:
151 msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02);
152 msm_spm_set_pmic_ctl(dev, dev->awake_vlevel,
153 dev->retention_mid_vlevel, dev->retention_vlevel);
154 msm_spm_set_slp_rst_en(dev, 0x00);
155 break;
156
157 case MSM_SPM_MODE_POWER_COLLAPSE:
158 msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02);
159 msm_spm_set_pmic_ctl(dev, dev->awake_vlevel,
160 dev->collapse_mid_vlevel, dev->collapse_vlevel);
161 msm_spm_set_slp_rst_en(dev, 0x01);
162 break;
163
164 default:
165 BUG();
166 }
167
168 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
169 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_PMIC_CTL);
170 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SLP_RST_EN);
171 /* Ensure that the registers are written before returning */
172 mb();
173
174 dev->low_power_mode = mode;
175 dev->notify_rpm = notify_rpm;
176 dev->dirty = false;
177
178 if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
179 int i;
180 for (i = 0; i < MSM_SPM_REG_NR; i++)
181 pr_info("%s: reg %02x = 0x%08x\n", __func__,
182 msm_spm_reg_offsets[i], dev->reg_shadow[i]);
183 }
184
185 return 0;
186}
187
188int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
189{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700190 struct msm_spm_device *dev;
191 uint32_t timeout_us;
192
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700193 dev = &per_cpu(msm_spm_devices, cpu);
194
195 if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
196 pr_info("%s: requesting cpu %u vlevel 0x%x\n",
197 __func__, cpu, vlevel);
198
199 msm_spm_set_vctl(dev, vlevel);
200 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL);
201
202 /* Wait for PMIC state to return to idle or until timeout */
203 timeout_us = dev->vctl_timeout_us;
204 msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS);
205 while (msm_spm_get_sts_pmic_state(dev) != MSM_SPM_PMIC_STATE_IDLE) {
206 if (!timeout_us)
207 goto set_vdd_bail;
208
209 if (timeout_us > 10) {
210 udelay(10);
211 timeout_us -= 10;
212 } else {
213 udelay(timeout_us);
214 timeout_us = 0;
215 }
216 msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS);
217 }
218
219 if (msm_spm_get_sts_curr_pmic_data(dev) != vlevel)
220 goto set_vdd_bail;
221
222 dev->awake_vlevel = vlevel;
223 dev->dirty = true;
224
225 if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
226 pr_info("%s: cpu %u done, remaining timeout %uus\n",
227 __func__, cpu, timeout_us);
228
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700229 return 0;
230
231set_vdd_bail:
232 pr_err("%s: cpu %u failed, remaining timeout %uus, vlevel 0x%x\n",
233 __func__, cpu, timeout_us, msm_spm_get_sts_curr_pmic_data(dev));
234
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700235 return -EIO;
236}
237
238void msm_spm_reinit(void)
239{
240 struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices);
241 int i;
242
243 for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
244 msm_spm_flush_shadow(dev, i);
245
246 /* Ensure that the registers are written before returning */
247 mb();
248}
249
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700250int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
251{
252 unsigned int cpu;
253
254 BUG_ON(nr_devs < num_possible_cpus());
255 for_each_possible_cpu(cpu) {
256 struct msm_spm_device *dev = &per_cpu(msm_spm_devices, cpu);
257 int i;
258
259 dev->reg_base_addr = data[cpu].reg_base_addr;
260 memcpy(dev->reg_shadow, data[cpu].reg_init_values,
261 sizeof(data[cpu].reg_init_values));
262
263 dev->awake_vlevel = data[cpu].awake_vlevel;
264 dev->retention_vlevel = data[cpu].retention_vlevel;
265 dev->collapse_vlevel = data[cpu].collapse_vlevel;
266 dev->retention_mid_vlevel = data[cpu].retention_mid_vlevel;
267 dev->collapse_mid_vlevel = data[cpu].collapse_mid_vlevel;
268 dev->vctl_timeout_us = data[cpu].vctl_timeout_us;
269
270 for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
271 msm_spm_flush_shadow(dev, i);
272
273 /* Ensure that the registers are written before returning */
274 mb();
275
276 dev->low_power_mode = MSM_SPM_MODE_CLOCK_GATING;
277 dev->notify_rpm = false;
278 dev->dirty = true;
279 }
280
281 return 0;
282}