blob: ea0b56c6a89ab10eeaa6e53cf6e55736f4cc4651 [file] [log] [blame]
Girish Mahadevande74c192012-10-22 10:59:10 -06001/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
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 *****************************************************************************/
Praveen Chidambaram7347bfe2012-11-01 15:21:10 -0600135/**
136 * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
137 * @mode: SPM LPM mode to enter
138 * @notify_rpm: Notify RPM in this mode
139 */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700140int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
141{
142 struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices);
143 uint32_t rpm_bypass = notify_rpm ? 0x00 : 0x01;
144
145 if (mode == dev->low_power_mode && notify_rpm == dev->notify_rpm
146 && !dev->dirty)
147 return 0;
148
149 switch (mode) {
150 case MSM_SPM_MODE_CLOCK_GATING:
151 msm_spm_set_spm_ctl(dev, rpm_bypass, 0x00);
152 msm_spm_set_slp_rst_en(dev, 0x00);
153 break;
154
155 case MSM_SPM_MODE_POWER_RETENTION:
156 msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02);
157 msm_spm_set_pmic_ctl(dev, dev->awake_vlevel,
158 dev->retention_mid_vlevel, dev->retention_vlevel);
159 msm_spm_set_slp_rst_en(dev, 0x00);
160 break;
161
162 case MSM_SPM_MODE_POWER_COLLAPSE:
163 msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02);
164 msm_spm_set_pmic_ctl(dev, dev->awake_vlevel,
165 dev->collapse_mid_vlevel, dev->collapse_vlevel);
166 msm_spm_set_slp_rst_en(dev, 0x01);
167 break;
168
169 default:
170 BUG();
171 }
172
173 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
174 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_PMIC_CTL);
175 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SLP_RST_EN);
176 /* Ensure that the registers are written before returning */
177 mb();
178
179 dev->low_power_mode = mode;
180 dev->notify_rpm = notify_rpm;
181 dev->dirty = false;
182
183 if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
184 int i;
185 for (i = 0; i < MSM_SPM_REG_NR; i++)
186 pr_info("%s: reg %02x = 0x%08x\n", __func__,
187 msm_spm_reg_offsets[i], dev->reg_shadow[i]);
188 }
189
190 return 0;
191}
192
Praveen Chidambaram7347bfe2012-11-01 15:21:10 -0600193/**
194 * msm_spm_set_vdd(): Set core voltage
195 * @cpu: core id
196 * @vlevel: Encoded PMIC data.
197 */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700198int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
199{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700200 struct msm_spm_device *dev;
201 uint32_t timeout_us;
202
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700203 dev = &per_cpu(msm_spm_devices, cpu);
204
205 if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
206 pr_info("%s: requesting cpu %u vlevel 0x%x\n",
207 __func__, cpu, vlevel);
208
209 msm_spm_set_vctl(dev, vlevel);
210 msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL);
211
212 /* Wait for PMIC state to return to idle or until timeout */
213 timeout_us = dev->vctl_timeout_us;
214 msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS);
215 while (msm_spm_get_sts_pmic_state(dev) != MSM_SPM_PMIC_STATE_IDLE) {
216 if (!timeout_us)
217 goto set_vdd_bail;
218
219 if (timeout_us > 10) {
220 udelay(10);
221 timeout_us -= 10;
222 } else {
223 udelay(timeout_us);
224 timeout_us = 0;
225 }
226 msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS);
227 }
228
229 if (msm_spm_get_sts_curr_pmic_data(dev) != vlevel)
230 goto set_vdd_bail;
231
232 dev->awake_vlevel = vlevel;
233 dev->dirty = true;
234
235 if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
236 pr_info("%s: cpu %u done, remaining timeout %uus\n",
237 __func__, cpu, timeout_us);
238
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700239 return 0;
240
241set_vdd_bail:
242 pr_err("%s: cpu %u failed, remaining timeout %uus, vlevel 0x%x\n",
243 __func__, cpu, timeout_us, msm_spm_get_sts_curr_pmic_data(dev));
244
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700245 return -EIO;
246}
247
Praveen Chidambaram7347bfe2012-11-01 15:21:10 -0600248/**
249 * msm_spm_get_vdd(): Get core voltage
250 * @cpu: core id
251 * @return: Returns encoded PMIC data.
252 */
Girish Mahadevande74c192012-10-22 10:59:10 -0600253unsigned int msm_spm_get_vdd(unsigned int cpu)
254{
255 struct msm_spm_device *dev = &per_cpu(msm_spm_devices, cpu);
256 return dev->reg_shadow[MSM_SPM_REG_SAW_VCTL];
257}
258
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700259void msm_spm_reinit(void)
260{
261 struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices);
262 int i;
263
264 for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
265 msm_spm_flush_shadow(dev, i);
266
267 /* Ensure that the registers are written before returning */
268 mb();
269}
270
Praveen Chidambaram7347bfe2012-11-01 15:21:10 -0600271/**
272 * msm_spm_init(): Board initalization function
273 * @data: platform specific SPM register configuration data
274 * @nr_devs: Number of SPM devices being initialized
275 */
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700276int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
277{
278 unsigned int cpu;
279
280 BUG_ON(nr_devs < num_possible_cpus());
281 for_each_possible_cpu(cpu) {
282 struct msm_spm_device *dev = &per_cpu(msm_spm_devices, cpu);
283 int i;
284
285 dev->reg_base_addr = data[cpu].reg_base_addr;
286 memcpy(dev->reg_shadow, data[cpu].reg_init_values,
287 sizeof(data[cpu].reg_init_values));
288
289 dev->awake_vlevel = data[cpu].awake_vlevel;
290 dev->retention_vlevel = data[cpu].retention_vlevel;
291 dev->collapse_vlevel = data[cpu].collapse_vlevel;
292 dev->retention_mid_vlevel = data[cpu].retention_mid_vlevel;
293 dev->collapse_mid_vlevel = data[cpu].collapse_mid_vlevel;
294 dev->vctl_timeout_us = data[cpu].vctl_timeout_us;
295
296 for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
297 msm_spm_flush_shadow(dev, i);
298
299 /* Ensure that the registers are written before returning */
300 mb();
301
302 dev->low_power_mode = MSM_SPM_MODE_CLOCK_GATING;
303 dev->notify_rpm = false;
304 dev->dirty = true;
305 }
306
307 return 0;
308}