// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include "gpueb_timesync.h" #include "gpueb_ipi.h" #include "gpueb_helper.h" #define TIMESYNC_TAG "[GPUEB_TS]" #define TIMESYNC_MAX_VER (0x7) #define TIMESYNC_HEADER_FREEZE_OFS (31) #define TIMESYNC_HEADER_FREEZE (1 << TIMESYNC_HEADER_FREEZE_OFS) #define TIMESYNC_HEADER_VER_OFS (28) #define TIMESYNC_HEADER_VER_MASK (TIMESYNC_MAX_VER << TIMESYNC_HEADER_VER_OFS) #define TIMESYNC_FLAG_SYNC (1 << 0) #define TIMESYNC_FLAG_ASYNC (1 << 1) #define TIMESYNC_FLAG_FREEZE (1 << 2) #define TIMESYNC_FLAG_UNFREEZE (1 << 3) static unsigned int g_gpueb_ts_mbox; static unsigned int g_gpueb_ts_mbox_offset_base; /* * Shared MBOX: AP write, GPUEB read * Unit for each offset: 4 bytes */ #define GPUEB_TS_MBOX_TICK_H (g_gpueb_ts_mbox_offset_base + 0) #define GPUEB_TS_MBOX_TICK_L (g_gpueb_ts_mbox_offset_base + 1) #define GPUEB_TS_MBOX_TS_H (g_gpueb_ts_mbox_offset_base + 2) #define GPUEB_TS_MBOX_TS_L (g_gpueb_ts_mbox_offset_base + 3) #define GPUEB_TS_MBOX_DEBUG_TS_H (g_gpueb_ts_mbox_offset_base + 4) #define GPUEB_TS_MBOX_DEBUG_TS_L (g_gpueb_ts_mbox_offset_base + 5) #define gpueb_ts_write(id, val) \ mtk_mbox_write(&gpueb_mboxdev, g_gpueb_ts_mbox, id, (void *)&val, 1) struct timesync_context_t { spinlock_t lock; struct work_struct work; ktime_t wrap_kt; u8 enabled; u64 base_tick; u64 base_ts; }; static struct workqueue_struct *timesync_workqueue; static struct timesync_context_t timesync_ctx; static struct timecounter timesync_counter; static struct hrtimer timesync_refresh_timer; static u8 gpueb_base_ver; static void gpueb_ts_update(int suspended, u64 tick, u64 ts) { u32 header, val; gpueb_base_ver = (gpueb_base_ver + 1)%(TIMESYNC_MAX_VER+1); /* make header: freeze and version */ header = suspended ? TIMESYNC_HEADER_FREEZE : 0; header |= ((gpueb_base_ver << TIMESYNC_HEADER_VER_OFS) & TIMESYNC_HEADER_VER_MASK); /* update tick, h -> l */ val = (tick >> 32) & 0xFFFFFFFF; val |= header; gpueb_ts_write(GPUEB_TS_MBOX_TICK_H, val); /* fix update sequence to promise atomicity */ mb(); val = tick & 0xFFFFFFFF; gpueb_ts_write(GPUEB_TS_MBOX_TICK_L, val); /* fix update sequence to promise atomicity */ mb(); /* update ts, l -> h */ val = ts & 0xFFFFFFFF; gpueb_ts_write(GPUEB_TS_MBOX_TS_L, val); /* fix update sequence to promise atomicity */ mb(); val = (ts >> 32) & 0xFFFFFFFF; val |= header; gpueb_ts_write(GPUEB_TS_MBOX_TS_H, val); /* fix update sequence to promise atomicity */ mb(); } static u64 timesync_tick_read(const struct cyclecounter *cc) { return arch_timer_read_counter(); } static struct cyclecounter timesync_cc __ro_after_init = { .read = timesync_tick_read, .mask = CLOCKSOURCE_MASK(56), }; static void timesync_sync_base_internal(unsigned int flag) { u64 tick, ts; unsigned long irq_flags = 0; int freeze, unfreeze; spin_lock_irqsave(×ync_ctx.lock, irq_flags); ts = timecounter_read(×ync_counter); tick = timesync_counter.cycle_last; timesync_ctx.base_tick = tick; timesync_ctx.base_ts = ts; freeze = (flag & TIMESYNC_FLAG_FREEZE) ? 1 : 0; unfreeze = (flag & TIMESYNC_FLAG_UNFREEZE) ? 1 : 0; /* sync with gpueb */ gpueb_ts_update(freeze, tick, ts); spin_unlock_irqrestore(×ync_ctx.lock, irq_flags); gpueb_pr_info("%s update base: ts=%llu, tick=0x%llx, fz=%d, ver=%d\n", TIMESYNC_TAG, ts, tick, freeze, gpueb_base_ver); } static void timesync_sync_base(unsigned int flag) { if (!timesync_ctx.enabled) return; if (flag & TIMESYNC_FLAG_ASYNC) queue_work(timesync_workqueue, &(timesync_ctx.work)); else timesync_sync_base_internal(flag); } static enum hrtimer_restart timesync_refresh(struct hrtimer *hrt) { hrtimer_forward_now(hrt, timesync_ctx.wrap_kt); /* snchronize new sched_clock base to co-processors */ timesync_sync_base(TIMESYNC_FLAG_ASYNC); return HRTIMER_RESTART; } static void timesync_ws(struct work_struct *ws) { timesync_sync_base(TIMESYNC_FLAG_SYNC); } static u64 get_ts_max_nsecs(u32 mult, u32 shift, u64 mask) { u64 max_nsecs, max_cycles; max_cycles = ULLONG_MAX; do_div(max_cycles, mult); max_cycles = min(max_cycles, mask); max_nsecs = clocksource_cyc2ns(max_cycles, mult, shift); /* Return 50% of the actual maximum, so we can detect bad values */ max_nsecs >>= 1; return max_nsecs; } unsigned int gpueb_timesync_init(void) { u64 wrap; g_gpueb_ts_mbox = gpueb_get_ts_mbox(); gpueb_pr_info("%s: g_gpueb_ts_mbox = %d\n", __func__, g_gpueb_ts_mbox); g_gpueb_ts_mbox_offset_base = gpueb_get_send_PIN_offset_by_name("IPI_ID_TIMER"); gpueb_pr_info("%s: g_gpueb_ts_mbox_offset_base = %d\n", __func__, g_gpueb_ts_mbox_offset_base); timesync_workqueue = create_workqueue("gpueb_ts_wq"); if (!timesync_workqueue) { gpueb_pr_info("%s workqueue create failed\n", __func__); timesync_ctx.enabled = 0; return -1; } INIT_WORK(&(timesync_ctx.work), timesync_ws); spin_lock_init(×ync_ctx.lock); /* init cyclecounter mult and shift as sched_clock */ clocks_calc_mult_shift(×ync_cc.mult, ×ync_cc.shift, arch_timer_get_cntfrq(), NSEC_PER_SEC, 3600); wrap = get_ts_max_nsecs(timesync_cc.mult, timesync_cc.shift, timesync_cc.mask); timesync_ctx.wrap_kt = ns_to_ktime(wrap); /* Init time counter: * start_time: current sched_clock * read: arch timer counter */ timecounter_init(×ync_counter, ×ync_cc, sched_clock()); hrtimer_init(×ync_refresh_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); timesync_refresh_timer.function = timesync_refresh; hrtimer_start(×ync_refresh_timer, wrap, HRTIMER_MODE_REL); gpueb_pr_info("%s ts: cycle_last %lld, time_base:%lld, wrap:%lld\n", TIMESYNC_TAG, timesync_counter.cycle_last, timesync_counter.nsec, wrap); timesync_ctx.enabled = 1; timesync_sync_base(TIMESYNC_FLAG_SYNC); return 0; } void gpueb_timesync_suspend(void) { if (!timesync_ctx.enabled) return; hrtimer_cancel(×ync_refresh_timer); /* snchronize new sched_clock base to co-processors */ timesync_sync_base(TIMESYNC_FLAG_SYNC | TIMESYNC_FLAG_FREEZE); } void gpueb_timesync_resume(void) { if (!timesync_ctx.enabled) return; /* re-init timecounter because sched_clock will be stopped during * suspend but arch timer counter is not, so we need to update * start time after resume */ timecounter_init(×ync_counter, ×ync_cc, sched_clock()); hrtimer_start(×ync_refresh_timer, timesync_ctx.wrap_kt, HRTIMER_MODE_REL); /* snchronize new sched_clock base to co-processors */ timesync_sync_base(TIMESYNC_FLAG_SYNC | TIMESYNC_FLAG_UNFREEZE); }