blob: 4a7cf914d768ff134b46143bb7f8381ff7892f90 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 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 <linux/slab.h>
20#include <mach/msm_iomap.h>
21
22#include "spm_driver.h"
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
36
37static uint32_t msm_spm_reg_offsets[MSM_SPM_REG_NR] = {
38 [MSM_SPM_REG_SAW2_SECURE] = 0x00,
39
40 [MSM_SPM_REG_SAW2_ID] = 0x04,
41 [MSM_SPM_REG_SAW2_CFG] = 0x08,
42 [MSM_SPM_REG_SAW2_STS0] = 0x0C,
43 [MSM_SPM_REG_SAW2_STS1] = 0x10,
44
45 [MSM_SPM_REG_SAW2_VCTL] = 0x14,
46
47 [MSM_SPM_REG_SAW2_AVS_CTL] = 0x18,
48 [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x1C,
49
50 [MSM_SPM_REG_SAW2_SPM_CTL] = 0x20,
51 [MSM_SPM_REG_SAW2_PMIC_DLY] = 0x24,
52 [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x28,
53 [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x2C,
54 [MSM_SPM_REG_SAW2_RST] = 0x30,
55
56 [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80,
57};
58
59/******************************************************************************
60 * Internal helper functions
61 *****************************************************************************/
62
63static inline void msm_spm_drv_set_vctl(struct msm_spm_driver_data *dev,
64 uint32_t vlevel)
65{
66 dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0xFF;
67 dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= vlevel;
68
69 dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] &= ~0xFF;
70 dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] |= vlevel;
71}
72
73static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
74 unsigned int reg_index)
75{
76 __raw_writel(dev->reg_shadow[reg_index],
77 dev->reg_base_addr + msm_spm_reg_offsets[reg_index]);
78}
79
80static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
81 unsigned int reg_index)
82{
83 dev->reg_shadow[reg_index] =
84 __raw_readl(dev->reg_base_addr +
85 msm_spm_reg_offsets[reg_index]);
86}
87
88static inline uint32_t msm_spm_drv_get_awake_vlevel(
89 struct msm_spm_driver_data *dev)
90{
91 return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] & 0xFF;
92}
93
94static inline uint32_t msm_spm_drv_get_sts_pmic_state(
95 struct msm_spm_driver_data *dev)
96{
97 return (dev->reg_shadow[MSM_SPM_REG_SAW2_STS0] >> 10) & 0x03;
98}
99
100static inline uint32_t msm_spm_drv_get_sts_curr_pmic_data(
101 struct msm_spm_driver_data *dev)
102{
103 return dev->reg_shadow[MSM_SPM_REG_SAW2_STS1] & 0xFF;
104}
105
106static inline uint32_t msm_spm_drv_get_num_spm_entry(
107 struct msm_spm_driver_data *dev)
108{
109 return 32;
110}
111
112static inline void msm_spm_drv_set_start_addr(
113 struct msm_spm_driver_data *dev, uint32_t addr)
114{
115 addr &= 0x7F;
116 addr <<= 4;
117 dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
118 dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
119}
120
121
122/******************************************************************************
123 * Public functions
124 *****************************************************************************/
125inline int msm_spm_drv_set_spm_enable(
126 struct msm_spm_driver_data *dev, bool enable)
127{
128 uint32_t value = enable ? 0x01 : 0x00;
129
130 if (!dev)
131 return -EINVAL;
132
133 if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) {
134
135 dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
136 dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
137
138 msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
139 wmb();
140 }
141 return 0;
142}
143void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
144{
145 int i;
146 int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
147
148 if (!dev) {
149 __WARN();
150 return;
151 }
152
153 for (i = 0; i < num_spm_entry; i++) {
154 __raw_writel(dev->reg_seq_entry_shadow[i],
155 dev->reg_base_addr
156 + msm_spm_reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
157 + 4 * i);
158 }
159 mb();
160}
161
162int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
163 uint8_t *cmd, uint32_t offset)
164{
165 uint32_t offset_w = offset / 4;
166 int ret = 0;
167
168 if (!cmd || !dev) {
169 __WARN();
170 goto failed_write_seq_data;
171 };
172
173 while (1) {
174 int i;
175 uint32_t cmd_w = 0;
176 uint8_t last_cmd = 0;
177
178 for (i = 0; i < 4; i++) {
179 last_cmd = (last_cmd == 0x0f) ? 0x0f : *(cmd + i);
180 cmd_w |= last_cmd << (i * 8);
181 ret++;
182 }
183
184 if (offset_w >= msm_spm_drv_get_num_spm_entry(dev)) {
185 __WARN();
186 goto failed_write_seq_data;
187 }
188
189 cmd += i;
190 dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
191 if (last_cmd == 0x0f)
192 break;
193 }
194 return ret;
195
196failed_write_seq_data:
197 return -EINVAL;
198}
199
200int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
201 uint32_t addr)
202{
203
204 /* SPM is configured to reset start address to zero after end of Program
205 */
206 if (!dev)
207 return -EINVAL;
208
209 msm_spm_drv_set_start_addr(dev, addr);
210
Maheshkumar Sivasubramaniandaa23bb2011-07-01 17:24:51 -0600211 msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL);
212 wmb();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700213
214 if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
215 int i;
216 for (i = 0; i < MSM_SPM_REG_NR; i++)
217 pr_info("%s: reg %02x = 0x%08x\n", __func__,
218 msm_spm_reg_offsets[i], dev->reg_shadow[i]);
219 }
220
221 return 0;
222}
223
224int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
225{
226 uint32_t timeout_us;
227
228 if (!dev)
229 return -EINVAL;
230
231 if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
232 pr_info("%s: requesting vlevel 0x%x\n",
233 __func__, vlevel);
234
235 msm_spm_drv_set_vctl(dev, vlevel);
236 msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL);
237 msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_0);
238 mb();
239
240 /* Wait for PMIC state to return to idle or until timeout */
241 timeout_us = dev->vctl_timeout_us;
242 msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS0);
243 while (msm_spm_drv_get_sts_pmic_state(dev) != MSM_SPM_PMIC_STATE_IDLE) {
244 if (!timeout_us)
245 goto set_vdd_bail;
246
247 if (timeout_us > 10) {
248 udelay(10);
249 timeout_us -= 10;
250 } else {
251 udelay(timeout_us);
252 timeout_us = 0;
253 }
254 msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS0);
255 }
256
257 msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS1);
258
259 if (msm_spm_drv_get_sts_curr_pmic_data(dev) != vlevel)
260 goto set_vdd_bail;
261
262 if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
263 pr_info("%s: done, remaining timeout %uus\n",
264 __func__, timeout_us);
265
266 return 0;
267
268set_vdd_bail:
269 pr_err("%s: failed, remaining timeout %uus, vlevel 0x%x\n",
270 __func__, timeout_us, msm_spm_drv_get_sts_curr_pmic_data(dev));
271 return -EIO;
272}
273
274int __init msm_spm_drv_init(struct msm_spm_driver_data *dev,
275 struct msm_spm_platform_data *data)
276{
277
278 int i;
279 int num_spm_entry;
280
281 BUG_ON(!dev || !data);
282
283 dev->reg_base_addr = data->reg_base_addr;
284 memcpy(dev->reg_shadow, data->reg_init_values,
285 sizeof(data->reg_init_values));
286
287 dev->vctl_timeout_us = data->vctl_timeout_us;
288
289 for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
290 msm_spm_drv_flush_shadow(dev, i);
291 /* barrier to ensure write completes before we update shadow
292 * registers
293 */
294 mb();
295
296 for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++)
297 msm_spm_drv_load_shadow(dev, i);
298
299 /* barrier to ensure read completes before we proceed further*/
300 mb();
301
302 num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
303
304 dev->reg_seq_entry_shadow =
305 kmalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
306 GFP_KERNEL);
307
308 if (!dev->reg_seq_entry_shadow)
309 return -ENOMEM;
310
311
312 memset(dev->reg_seq_entry_shadow, 0x0f,
313 num_spm_entry * sizeof(*dev->reg_seq_entry_shadow));
314
315 return 0;
316}