msm: Wall clock kernel module
This driver simulates the wall-clock hardware block on fsm8064_ep
femto emulation platform. This block will be used to provide
clock information to the LTE Layer 2 module running on the hexagon
processor
Change-Id: I017f61b9b0c175eb4f2f8129b5c63c9bd489ae38
Acked-by: Jie Luo <jluo@qti.qualcomm.com>
Signed-off-by: Rohit Vaswani <rvaswani@codeaurora.org>
diff --git a/arch/arm/mach-msm/wallclk.c b/arch/arm/mach-msm/wallclk.c
new file mode 100644
index 0000000..9624795
--- /dev/null
+++ b/arch/arm/mach-msm/wallclk.c
@@ -0,0 +1,475 @@
+/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/semaphore.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/hrtimer.h>
+#include <linux/timer.h>
+#include <linux/io.h>
+#include <linux/ctype.h>
+#include <linux/uaccess.h>
+#include <linux/errno.h>
+
+#include <mach/msm_iomap.h>
+
+#include "wallclk.h"
+
+#define WALLCLK_MODULE_NAME "wallclk"
+#define WALLCLK_MODULE_NAME_LEN 10
+
+#define FLAG_WALLCLK_INITED 0x1
+#define FLAG_WALLCLK_SFN_REF_SET 0x2
+#define FLAG_WALLCLK_ENABLED 0x4
+
+#define WALLCLK_TIMER_INTERVAL_NS 100000
+
+#define WALLCLK_SHARED_MEM_SIZE 1024
+#define ALIGN_64(addr) (((addr) + 7) & ~0x7)
+
+#define GPS_EPOCH_DIFF 315964800
+
+struct wallclk_cnt {
+ u32 pulse;
+ u32 clk;
+};
+
+struct wallclk_reg {
+ u32 ctrl;
+ u32 pulse_cnt;
+ u32 snapshot_clock_cnt;
+ u32 clock_cnt;
+ u32 __unused__[5];
+ u32 base_time0;
+ u32 base_time1;
+};
+
+struct wallclk_sm {
+ struct wallclk_reg reg;
+ u32 sfn_ref;
+};
+
+struct wallclk_cfg {
+ u32 ppns;
+ u32 clk_rate;
+ u32 clk_rate_v; /* clk_rate = clk_rate_v x clk_rate_p */
+ u32 clk_rate_p; /* power of 10 */
+ u32 ns_per_clk_rate_v;
+};
+
+struct wallclk {
+ struct wallclk_sm *shm;
+
+ struct wallclk_cfg cfg;
+
+ struct timespec tv;
+
+ struct hrtimer timer;
+ ktime_t interval;
+
+ spinlock_t lock;
+ u32 flags;
+
+ char name[WALLCLK_MODULE_NAME_LEN];
+};
+
+static struct wallclk wall_clk;
+
+static inline int is_valid_register(u32 offset)
+{
+ int rc = 0;
+
+ switch (offset) {
+ case CTRL_REG_OFFSET:
+ case PULSE_CNT_REG_OFFSET:
+ case CLK_CNT_SNAPSHOT_REG_OFFSET:
+ case CLK_CNT_REG_OFFSET:
+ case CLK_BASE_TIME0_OFFSET:
+ case CLK_BASE_TIME1_OFFSET:
+ rc = 1;
+ break;
+ default:
+ break;
+ }
+ return rc;
+}
+
+static inline void wallclk_ctrl_reg_set(struct wallclk *wclk, u32 v)
+{
+ struct wallclk_reg *reg = &wclk->shm->reg;
+
+ if (v & CTRL_ENABLE_MASK) {
+ if (!(wclk->flags & FLAG_WALLCLK_ENABLED)) {
+ getnstimeofday(&wclk->tv);
+ __raw_writel(0, ®->snapshot_clock_cnt);
+ __raw_writel(0, ®->clock_cnt);
+ __raw_writel(0, ®->pulse_cnt);
+ hrtimer_start(&wclk->timer,
+ wclk->interval,
+ HRTIMER_MODE_REL);
+ wclk->flags |= FLAG_WALLCLK_ENABLED;
+ }
+ } else {
+ if (wclk->flags & FLAG_WALLCLK_ENABLED) {
+ hrtimer_cancel(&wclk->timer);
+ wclk->flags &= ~FLAG_WALLCLK_ENABLED;
+ }
+ }
+
+ __raw_writel(v, ®->ctrl);
+}
+
+static inline void wallclk_cfg_init(struct wallclk_cfg *cfg,
+ u32 ppns,
+ u32 clk_rate)
+{
+ cfg->ppns = ppns;
+ cfg->clk_rate = clk_rate;
+ cfg->clk_rate_v = clk_rate;
+ cfg->clk_rate_p = 1;
+ cfg->ns_per_clk_rate_v = 1000000000;
+
+ while (!(cfg->clk_rate_v % 10)) {
+ cfg->clk_rate_v /= 10;
+ cfg->clk_rate_p *= 10;
+ cfg->ns_per_clk_rate_v /= 10;
+ }
+}
+
+static inline struct timespec timestamp_convert(const struct timespec *tv)
+{
+ struct timespec rc;
+
+ rc.tv_sec = tv->tv_sec - GPS_EPOCH_DIFF;
+ rc.tv_nsec = tv->tv_nsec;
+
+ return rc;
+}
+
+static inline void timespec_delta_to_wclk_cnt(const struct timespec *tv,
+ const struct wallclk_cfg *cfg,
+ struct wallclk_cnt *wclk_cnt)
+{
+ long ns;
+
+ wclk_cnt->pulse = tv->tv_sec / cfg->ppns;
+ wclk_cnt->clk = (tv->tv_sec % cfg->ppns) * cfg->clk_rate;
+
+ ns = tv->tv_nsec;
+ while (ns >= cfg->ns_per_clk_rate_v) {
+ ns -= cfg->ns_per_clk_rate_v;
+ wclk_cnt->clk += cfg->clk_rate_v;
+ }
+
+ wclk_cnt->clk += (ns * cfg->clk_rate_v)/cfg->ns_per_clk_rate_v;
+}
+
+static inline u32 wallclk_cnt_to_sfn(const struct wallclk_cnt *cnt,
+ const struct wallclk_cfg *cfg)
+{
+ u32 sfn;
+ u32 delta, p;
+
+ sfn = SFN_PER_SECOND * cnt->pulse * cfg->ppns;
+ if (cfg->clk_rate_p > 100) {
+ p = cfg->clk_rate_p/100;
+ delta = cnt->clk/(cfg->clk_rate_v * p);
+ } else {
+ p = 100/cfg->clk_rate_p;
+ delta = (cnt->clk * p)/cfg->clk_rate_v;
+ }
+ sfn += delta;
+
+ return sfn;
+}
+
+static void update_wallclk(struct wallclk *wclk)
+{
+ struct timespec tv;
+ struct timespec delta_tv;
+ struct wallclk_cnt cnt;
+ struct wallclk_reg *reg = &wclk->shm->reg;
+
+ spin_lock(&wclk->lock);
+ getnstimeofday(&tv);
+ delta_tv = timespec_sub(tv, wclk->tv);
+ timespec_delta_to_wclk_cnt(&delta_tv, &wclk->cfg, &cnt);
+ __raw_writel(cnt.pulse, ®->pulse_cnt);
+ __raw_writel(cnt.clk, ®->clock_cnt);
+ __raw_writel(cnt.clk, ®->snapshot_clock_cnt);
+
+ spin_unlock(&wclk->lock);
+}
+
+static int set_sfn(struct wallclk *wclk, u16 sfn)
+{
+ int rc = 0;
+ struct wallclk_reg *reg = &wclk->shm->reg;
+ u32 v;
+ struct timespec ts;
+
+ if (sfn > MAX_SFN) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (!(wclk->flags & FLAG_WALLCLK_INITED)) {
+ rc = -EIO;
+ goto out;
+ }
+
+ spin_lock_bh(&wclk->lock);
+
+ v = __raw_readl(®->ctrl);
+ wallclk_ctrl_reg_set(wclk, v & ~CTRL_ENABLE_MASK);
+
+ getnstimeofday(&wclk->tv);
+ ts = timestamp_convert(&wclk->tv);
+ __raw_writel(ts.tv_sec, ®->base_time0);
+ __raw_writel(ts.tv_nsec, ®->base_time1);
+
+ wclk->shm->sfn_ref = sfn;
+ wclk->flags |= FLAG_WALLCLK_SFN_REF_SET;
+
+ __raw_writel(0, ®->pulse_cnt);
+ __raw_writel(0, ®->clock_cnt);
+ __raw_writel(0, ®->snapshot_clock_cnt);
+ hrtimer_start(&wclk->timer, wclk->interval, HRTIMER_MODE_REL);
+ wclk->flags |= FLAG_WALLCLK_ENABLED;
+ __raw_writel(v | CTRL_ENABLE_MASK, ®->ctrl);
+
+ spin_unlock_bh(&wclk->lock);
+
+out:
+ return rc;
+}
+
+static int get_sfn(struct wallclk *wclk)
+{
+ struct wallclk_cnt cnt;
+ int rc = 0;
+ u32 sfn;
+
+ if (!(wclk->flags & FLAG_WALLCLK_INITED)) {
+ rc = -EIO;
+ goto out;
+ }
+
+ spin_lock_bh(&wclk->lock);
+
+ if (!(wclk->flags & FLAG_WALLCLK_ENABLED) ||
+ !(wclk->flags & FLAG_WALLCLK_SFN_REF_SET)) {
+ rc = -EIO;
+ goto unlock;
+ }
+
+ cnt.pulse = __raw_readl(&(wclk->shm->reg.pulse_cnt));
+ cnt.clk = __raw_readl(&(wclk->shm->reg.clock_cnt));
+ sfn = wallclk_cnt_to_sfn(&cnt, &wclk->cfg);
+
+ sfn += wclk->shm->sfn_ref;
+ rc = sfn & MAX_SFN;
+
+unlock:
+ spin_unlock_bh(&wclk->lock);
+out:
+ return rc;
+}
+
+enum hrtimer_restart wallclk_timer_cb(struct hrtimer *timer)
+{
+ update_wallclk(&wall_clk);
+ hrtimer_forward_now(timer, wall_clk.interval);
+ return HRTIMER_RESTART;
+}
+
+int wallclk_set_sfn(u16 sfn)
+{
+ return set_sfn(&wall_clk, sfn);
+}
+EXPORT_SYMBOL_GPL(wallclk_set_sfn);
+
+int wallclk_get_sfn(void)
+{
+ return get_sfn(&wall_clk);
+}
+EXPORT_SYMBOL_GPL(wallclk_get_sfn);
+
+int wallclk_set_sfn_ref(u16 sfn)
+{
+ int rc = 0;
+
+ if (sfn > MAX_SFN) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
+ rc = -EIO;
+ goto out;
+ }
+
+ spin_lock_bh(&wall_clk.lock);
+
+ wall_clk.shm->sfn_ref = sfn;
+ wall_clk.flags |= FLAG_WALLCLK_SFN_REF_SET;
+
+ spin_unlock_bh(&wall_clk.lock);
+
+out:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(wallclk_set_sfn_ref);
+
+int wallclk_get_sfn_ref(void)
+{
+ int rc = 0;
+
+ if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
+ rc = -EIO;
+ goto out;
+ }
+
+ spin_lock_bh(&wall_clk.lock);
+
+ if (!(wall_clk.flags & FLAG_WALLCLK_SFN_REF_SET)) {
+ rc = -EAGAIN;
+ goto unlock;
+ }
+ rc = wall_clk.shm->sfn_ref;
+
+unlock:
+ spin_unlock_bh(&wall_clk.lock);
+out:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(wallclk_get_sfn_ref);
+
+int wallclk_reg_read(u32 offset, u32 *p)
+{
+ int rc = 0;
+
+ if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
+ rc = -EIO;
+ goto out;
+ }
+
+ if (!is_valid_register(offset)) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ spin_lock_bh(&wall_clk.lock);
+ *p = __raw_readl((char *)&wall_clk.shm->reg + offset);
+ spin_unlock_bh(&wall_clk.lock);
+out:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(wallclk_reg_read);
+
+int wallclk_reg_write(u32 offset, u32 val)
+{
+ int rc = 0;
+ char *p;
+
+ if (!(wall_clk.flags & FLAG_WALLCLK_INITED)) {
+ rc = -EIO;
+ goto out;
+ }
+
+ p = (char *)&wall_clk.shm->reg;
+
+ spin_lock_bh(&wall_clk.lock);
+ switch (offset) {
+ case CTRL_REG_OFFSET:
+ wallclk_ctrl_reg_set(&wall_clk, val);
+ break;
+ case PULSE_CNT_REG_OFFSET:
+ case CLK_BASE_TIME0_OFFSET:
+ case CLK_BASE_TIME1_OFFSET:
+ __raw_writel(val, p + offset);
+ break;
+ case CLK_CNT_REG_OFFSET:
+ __raw_writel(val, p + CLK_CNT_REG_OFFSET);
+ __raw_writel(val, p + CLK_CNT_SNAPSHOT_REG_OFFSET);
+ break;
+ case CLK_CNT_SNAPSHOT_REG_OFFSET:
+ rc = -EIO;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ spin_unlock_bh(&wall_clk.lock);
+out:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(wallclk_reg_write);
+
+static int __init wallclk_init(void)
+{
+ int rc = 0;
+ u32 addr;
+
+ memset(&wall_clk, 0, sizeof(wall_clk));
+
+ addr = (u32)MSM_SHARED_RAM_BASE + MSM_SHARED_RAM_SIZE -
+ WALLCLK_SHARED_MEM_SIZE;
+ wall_clk.shm = (struct wallclk_sm *)ALIGN_64(addr);
+
+ __raw_writel(0, &(wall_clk.shm->reg.ctrl));
+ __raw_writel(0, &(wall_clk.shm->reg.pulse_cnt));
+ __raw_writel(0, &(wall_clk.shm->reg.snapshot_clock_cnt));
+ __raw_writel(0, &(wall_clk.shm->reg.clock_cnt));
+ __raw_writel(0, &(wall_clk.shm->reg.clock_cnt));
+ __raw_writel(0, &(wall_clk.shm->reg.base_time0));
+ __raw_writel(0, &(wall_clk.shm->reg.base_time1));
+
+ wall_clk.shm->sfn_ref = 0;
+
+ wallclk_cfg_init(&wall_clk.cfg, PPNS_PULSE, CLK_RATE);
+
+ strlcpy(wall_clk.name, WALLCLK_MODULE_NAME, WALLCLK_MODULE_NAME_LEN);
+
+ hrtimer_init(&wall_clk.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ wall_clk.timer.function = wallclk_timer_cb;
+ wall_clk.interval = ns_to_ktime(WALLCLK_TIMER_INTERVAL_NS);
+ spin_lock_init(&wall_clk.lock);
+
+ wall_clk.flags |= FLAG_WALLCLK_INITED;
+
+ printk(KERN_INFO "%s: clk_rate=%u ppns=%u clk_reg_addr=0x%x\n",
+ wall_clk.name, wall_clk.cfg.clk_rate, wall_clk.cfg.ppns,
+ (int)(&wall_clk.shm->reg));
+ return rc;
+}
+
+static void __exit wallclk_exit(void)
+{
+ if (wall_clk.flags & FLAG_WALLCLK_INITED) {
+ spin_lock_bh(&wall_clk.lock);
+ wallclk_ctrl_reg_set(&wall_clk, 0);
+ wall_clk.flags = 0;
+ spin_unlock_bh(&wall_clk.lock);
+ }
+}
+
+module_init(wallclk_init);
+module_exit(wallclk_exit);
+
+MODULE_DESCRIPTION("Wall clock");
+MODULE_LICENSE("GPL v2");