blob: 96247951ca187699de7cf153df248b7c3f657fdc [file] [log] [blame]
Rohit Vaswani8f709c02013-02-13 15:49:56 -08001/* Copyright (c) 2013, The Linux Foundation. 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/spinlock.h>
17#include <linux/semaphore.h>
18#include <linux/kthread.h>
19#include <linux/sched.h>
20#include <linux/hrtimer.h>
21#include <linux/timer.h>
22#include <linux/io.h>
23#include <linux/ctype.h>
24#include <linux/uaccess.h>
25#include <linux/errno.h>
26
27#include <mach/msm_iomap.h>
28
29#include "wallclk.h"
30
31#define WALLCLK_MODULE_NAME "wallclk"
32#define WALLCLK_MODULE_NAME_LEN 10
33
34#define FLAG_WALLCLK_INITED 0x1
35#define FLAG_WALLCLK_SFN_REF_SET 0x2
36#define FLAG_WALLCLK_ENABLED 0x4
37
38#define WALLCLK_TIMER_INTERVAL_NS 100000
39
40#define WALLCLK_SHARED_MEM_SIZE 1024
41#define ALIGN_64(addr) (((addr) + 7) & ~0x7)
42
43#define GPS_EPOCH_DIFF 315964800
44
45struct wallclk_cnt {
46 u32 pulse;
47 u32 clk;
48};
49
50struct wallclk_reg {
51 u32 ctrl;
52 u32 pulse_cnt;
53 u32 snapshot_clock_cnt;
54 u32 clock_cnt;
55 u32 __unused__[5];
56 u32 base_time0;
57 u32 base_time1;
58};
59
60struct wallclk_sm {
61 struct wallclk_reg reg;
62 u32 sfn_ref;
63};
64
65struct wallclk_cfg {
66 u32 ppns;
67 u32 clk_rate;
68 u32 clk_rate_v; /* clk_rate = clk_rate_v x clk_rate_p */
69 u32 clk_rate_p; /* power of 10 */
70 u32 ns_per_clk_rate_v;
71};
72
73struct wallclk {
74 struct wallclk_sm *shm;
75
76 struct wallclk_cfg cfg;
77
78 struct timespec tv;
79
80 struct hrtimer timer;
81 ktime_t interval;
82
83 spinlock_t lock;
84 u32 flags;
85
86 char name[WALLCLK_MODULE_NAME_LEN];
87};
88
89static struct wallclk wall_clk;
90
91static inline int is_valid_register(u32 offset)
92{
93 int rc = 0;
94
95 switch (offset) {
96 case CTRL_REG_OFFSET:
97 case PULSE_CNT_REG_OFFSET:
98 case CLK_CNT_SNAPSHOT_REG_OFFSET:
99 case CLK_CNT_REG_OFFSET:
100 case CLK_BASE_TIME0_OFFSET:
101 case CLK_BASE_TIME1_OFFSET:
102 rc = 1;
103 break;
104 default:
105 break;
106 }
107 return rc;
108}
109
110static inline void wallclk_ctrl_reg_set(struct wallclk *wclk, u32 v)
111{
112 struct wallclk_reg *reg = &wclk->shm->reg;
113
114 if (v & CTRL_ENABLE_MASK) {
115 if (!(wclk->flags & FLAG_WALLCLK_ENABLED)) {
116 getnstimeofday(&wclk->tv);
117 __raw_writel(0, &reg->snapshot_clock_cnt);
118 __raw_writel(0, &reg->clock_cnt);
119 __raw_writel(0, &reg->pulse_cnt);
120 hrtimer_start(&wclk->timer,
121 wclk->interval,
122 HRTIMER_MODE_REL);
123 wclk->flags |= FLAG_WALLCLK_ENABLED;
124 }
125 } else {
126 if (wclk->flags & FLAG_WALLCLK_ENABLED) {
127 hrtimer_cancel(&wclk->timer);
128 wclk->flags &= ~FLAG_WALLCLK_ENABLED;
129 }
130 }
131
132 __raw_writel(v, &reg->ctrl);
133}
134
135static inline void wallclk_cfg_init(struct wallclk_cfg *cfg,
136 u32 ppns,
137 u32 clk_rate)
138{
139 cfg->ppns = ppns;
140 cfg->clk_rate = clk_rate;
141 cfg->clk_rate_v = clk_rate;
142 cfg->clk_rate_p = 1;
143 cfg->ns_per_clk_rate_v = 1000000000;
144
145 while (!(cfg->clk_rate_v % 10)) {
146 cfg->clk_rate_v /= 10;
147 cfg->clk_rate_p *= 10;
148 cfg->ns_per_clk_rate_v /= 10;
149 }
150}
151
152static inline struct timespec timestamp_convert(const struct timespec *tv)
153{
154 struct timespec rc;
155
156 rc.tv_sec = tv->tv_sec - GPS_EPOCH_DIFF;
157 rc.tv_nsec = tv->tv_nsec;
158
159 return rc;
160}
161
162static inline void timespec_delta_to_wclk_cnt(const struct timespec *tv,
163 const struct wallclk_cfg *cfg,
164 struct wallclk_cnt *wclk_cnt)
165{
166 long ns;
167
168 wclk_cnt->pulse = tv->tv_sec / cfg->ppns;
169 wclk_cnt->clk = (tv->tv_sec % cfg->ppns) * cfg->clk_rate;
170
171 ns = tv->tv_nsec;
172 while (ns >= cfg->ns_per_clk_rate_v) {
173 ns -= cfg->ns_per_clk_rate_v;
174 wclk_cnt->clk += cfg->clk_rate_v;
175 }
176
177 wclk_cnt->clk += (ns * cfg->clk_rate_v)/cfg->ns_per_clk_rate_v;
178}
179
180static inline u32 wallclk_cnt_to_sfn(const struct wallclk_cnt *cnt,
181 const struct wallclk_cfg *cfg)
182{
183 u32 sfn;
184 u32 delta, p;
185
186 sfn = SFN_PER_SECOND * cnt->pulse * cfg->ppns;
187 if (cfg->clk_rate_p > 100) {
188 p = cfg->clk_rate_p/100;
189 delta = cnt->clk/(cfg->clk_rate_v * p);
190 } else {
191 p = 100/cfg->clk_rate_p;
192 delta = (cnt->clk * p)/cfg->clk_rate_v;
193 }
194 sfn += delta;
195
196 return sfn;
197}
198
199static void update_wallclk(struct wallclk *wclk)
200{
201 struct timespec tv;
202 struct timespec delta_tv;
203 struct wallclk_cnt cnt;
204 struct wallclk_reg *reg = &wclk->shm->reg;
205
206 spin_lock(&wclk->lock);
207 getnstimeofday(&tv);
208 delta_tv = timespec_sub(tv, wclk->tv);
209 timespec_delta_to_wclk_cnt(&delta_tv, &wclk->cfg, &cnt);
210 __raw_writel(cnt.pulse, &reg->pulse_cnt);
211 __raw_writel(cnt.clk, &reg->clock_cnt);
212 __raw_writel(cnt.clk, &reg->snapshot_clock_cnt);
213
214 spin_unlock(&wclk->lock);
215}
216
217static int set_sfn(struct wallclk *wclk, u16 sfn)
218{
219 int rc = 0;
220 struct wallclk_reg *reg = &wclk->shm->reg;
221 u32 v;
222 struct timespec ts;
223
224 if (sfn > MAX_SFN) {
225 rc = -EINVAL;
226 goto out;
227 }
228
229 if (!(wclk->flags & FLAG_WALLCLK_INITED)) {
230 rc = -EIO;
231 goto out;
232 }
233
234 spin_lock_bh(&wclk->lock);
235
236 v = __raw_readl(&reg->ctrl);
237 wallclk_ctrl_reg_set(wclk, v & ~CTRL_ENABLE_MASK);
238
239 getnstimeofday(&wclk->tv);
240 ts = timestamp_convert(&wclk->tv);
241 __raw_writel(ts.tv_sec, &reg->base_time0);
242 __raw_writel(ts.tv_nsec, &reg->base_time1);
243
244 wclk->shm->sfn_ref = sfn;
245 wclk->flags |= FLAG_WALLCLK_SFN_REF_SET;
246
247 __raw_writel(0, &reg->pulse_cnt);
248 __raw_writel(0, &reg->clock_cnt);
249 __raw_writel(0, &reg->snapshot_clock_cnt);
250 hrtimer_start(&wclk->timer, wclk->interval, HRTIMER_MODE_REL);
251 wclk->flags |= FLAG_WALLCLK_ENABLED;
252 __raw_writel(v | CTRL_ENABLE_MASK, &reg->ctrl);
253
254 spin_unlock_bh(&wclk->lock);
255
256out:
257 return rc;
258}
259
260static int get_sfn(struct wallclk *wclk)
261{
262 struct wallclk_cnt cnt;
263 int rc = 0;
264 u32 sfn;
265
266 if (!(wclk->flags & FLAG_WALLCLK_INITED)) {
267 rc = -EIO;
268 goto out;
269 }
270
271 spin_lock_bh(&wclk->lock);
272
273 if (!(wclk->flags & FLAG_WALLCLK_ENABLED) ||
274 !(wclk->flags & FLAG_WALLCLK_SFN_REF_SET)) {
275 rc = -EIO;
276 goto unlock;
277 }
278
279 cnt.pulse = __raw_readl(&(wclk->shm->reg.pulse_cnt));
280 cnt.clk = __raw_readl(&(wclk->shm->reg.clock_cnt));
281 sfn = wallclk_cnt_to_sfn(&cnt, &wclk->cfg);
282
283 sfn += wclk->shm->sfn_ref;
284 rc = sfn & MAX_SFN;
285
286unlock:
287 spin_unlock_bh(&wclk->lock);
288out:
289 return rc;
290}
291
292enum hrtimer_restart wallclk_timer_cb(struct hrtimer *timer)
293{
294 update_wallclk(&wall_clk);
295 hrtimer_forward_now(timer, wall_clk.interval);
296 return HRTIMER_RESTART;
297}
298
299int wallclk_set_sfn(u16 sfn)
300{
301 return set_sfn(&wall_clk, sfn);
302}
303EXPORT_SYMBOL_GPL(wallclk_set_sfn);
304
305int wallclk_get_sfn(void)
306{
307 return get_sfn(&wall_clk);
308}
309EXPORT_SYMBOL_GPL(wallclk_get_sfn);
310
311int wallclk_set_sfn_ref(u16 sfn)
312{
313 int rc = 0;
314
315 if (sfn > MAX_SFN) {
316 rc = -EINVAL;
317 goto out;
318 }
319
320 if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
321 rc = -EIO;
322 goto out;
323 }
324
325 spin_lock_bh(&wall_clk.lock);
326
327 wall_clk.shm->sfn_ref = sfn;
328 wall_clk.flags |= FLAG_WALLCLK_SFN_REF_SET;
329
330 spin_unlock_bh(&wall_clk.lock);
331
332out:
333 return rc;
334}
335EXPORT_SYMBOL_GPL(wallclk_set_sfn_ref);
336
337int wallclk_get_sfn_ref(void)
338{
339 int rc = 0;
340
341 if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
342 rc = -EIO;
343 goto out;
344 }
345
346 spin_lock_bh(&wall_clk.lock);
347
348 if (!(wall_clk.flags & FLAG_WALLCLK_SFN_REF_SET)) {
349 rc = -EAGAIN;
350 goto unlock;
351 }
352 rc = wall_clk.shm->sfn_ref;
353
354unlock:
355 spin_unlock_bh(&wall_clk.lock);
356out:
357 return rc;
358}
359EXPORT_SYMBOL_GPL(wallclk_get_sfn_ref);
360
361int wallclk_reg_read(u32 offset, u32 *p)
362{
363 int rc = 0;
364
365 if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
366 rc = -EIO;
367 goto out;
368 }
369
370 if (!is_valid_register(offset)) {
371 rc = -EINVAL;
372 goto out;
373 }
374
375 spin_lock_bh(&wall_clk.lock);
376 *p = __raw_readl((char *)&wall_clk.shm->reg + offset);
377 spin_unlock_bh(&wall_clk.lock);
378out:
379 return rc;
380}
381EXPORT_SYMBOL_GPL(wallclk_reg_read);
382
383int wallclk_reg_write(u32 offset, u32 val)
384{
385 int rc = 0;
386 char *p;
387
388 if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
389 rc = -EIO;
390 goto out;
391 }
392
393 p = (char *)&wall_clk.shm->reg;
394
395 spin_lock_bh(&wall_clk.lock);
396 switch (offset) {
397 case CTRL_REG_OFFSET:
398 wallclk_ctrl_reg_set(&wall_clk, val);
399 break;
400 case PULSE_CNT_REG_OFFSET:
401 case CLK_BASE_TIME0_OFFSET:
402 case CLK_BASE_TIME1_OFFSET:
403 __raw_writel(val, p + offset);
404 break;
405 case CLK_CNT_REG_OFFSET:
406 __raw_writel(val, p + CLK_CNT_REG_OFFSET);
407 __raw_writel(val, p + CLK_CNT_SNAPSHOT_REG_OFFSET);
408 break;
409 case CLK_CNT_SNAPSHOT_REG_OFFSET:
410 rc = -EIO;
411 break;
412 default:
413 rc = -EINVAL;
414 break;
415 }
416
417 spin_unlock_bh(&wall_clk.lock);
418out:
419 return rc;
420}
421EXPORT_SYMBOL_GPL(wallclk_reg_write);
422
423static int __init wallclk_init(void)
424{
425 int rc = 0;
426 u32 addr;
427
428 memset(&wall_clk, 0, sizeof(wall_clk));
429
430 addr = (u32)MSM_SHARED_RAM_BASE + MSM_SHARED_RAM_SIZE -
431 WALLCLK_SHARED_MEM_SIZE;
432 wall_clk.shm = (struct wallclk_sm *)ALIGN_64(addr);
433
434 __raw_writel(0, &(wall_clk.shm->reg.ctrl));
435 __raw_writel(0, &(wall_clk.shm->reg.pulse_cnt));
436 __raw_writel(0, &(wall_clk.shm->reg.snapshot_clock_cnt));
437 __raw_writel(0, &(wall_clk.shm->reg.clock_cnt));
438 __raw_writel(0, &(wall_clk.shm->reg.clock_cnt));
439 __raw_writel(0, &(wall_clk.shm->reg.base_time0));
440 __raw_writel(0, &(wall_clk.shm->reg.base_time1));
441
442 wall_clk.shm->sfn_ref = 0;
443
444 wallclk_cfg_init(&wall_clk.cfg, PPNS_PULSE, CLK_RATE);
445
446 strlcpy(wall_clk.name, WALLCLK_MODULE_NAME, WALLCLK_MODULE_NAME_LEN);
447
448 hrtimer_init(&wall_clk.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
449 wall_clk.timer.function = wallclk_timer_cb;
450 wall_clk.interval = ns_to_ktime(WALLCLK_TIMER_INTERVAL_NS);
451 spin_lock_init(&wall_clk.lock);
452
453 wall_clk.flags |= FLAG_WALLCLK_INITED;
454
455 printk(KERN_INFO "%s: clk_rate=%u ppns=%u clk_reg_addr=0x%x\n",
456 wall_clk.name, wall_clk.cfg.clk_rate, wall_clk.cfg.ppns,
457 (int)(&wall_clk.shm->reg));
458 return rc;
459}
460
461static void __exit wallclk_exit(void)
462{
463 if (wall_clk.flags & FLAG_WALLCLK_INITED) {
464 spin_lock_bh(&wall_clk.lock);
465 wallclk_ctrl_reg_set(&wall_clk, 0);
466 wall_clk.flags = 0;
467 spin_unlock_bh(&wall_clk.lock);
468 }
469}
470
471module_init(wallclk_init);
472module_exit(wallclk_exit);
473
474MODULE_DESCRIPTION("Wall clock");
475MODULE_LICENSE("GPL v2");