// SPDX-License-Identifier: GPL-2.0 // // adsp_timesync.c-- Mediatek ADSP timesync // // Copyright (c) 2020 MediaTek Inc. // Author: Celine Liu #include #include #include #include #include #include #include #include #include "adsp_helper.h" #include "adsp_timesync.h" struct adsp_ts_context_t { spinlock_t lock; struct work_struct work; ktime_t wrap_kt; u8 enabled; u8 init_synced; u64 base_tick; u64 base_ts; }; static struct adsp_ts_context_t adsp_ts_ctx; static struct timecounter adsp_ts_counter; static struct hrtimer adsp_ts_refresh_timer; static u32 adsp_base_ver; static int adsp_ts_update(int fz, u64 tick, u64 ts) { adsp_base_ver = (adsp_base_ver+1) & TIMESYNC_MAX_VER; if (is_adsp_ready(ADSP_A_ID) == 1 || !adsp_ts_ctx.init_synced) { adsp_reg_sync_writel((tick >> 32) & 0xFFFFFFFF, ADSP_TIMESYNC_TICK_H); adsp_reg_sync_writel(tick & 0xFFFFFFFF, ADSP_TIMESYNC_TICK_L); adsp_reg_sync_writel((ts >> 32) & 0xFFFFFFFF, ADSP_TIMESYNC_TS_H); adsp_reg_sync_writel(ts & 0xFFFFFFFF, ADSP_TIMESYNC_TS_L); adsp_reg_sync_writel(fz, ADSP_TIMESYNC_FREEZE); return 0; } return -1; } static u64 adsp_ts_tick_read(const struct cyclecounter *cc) { return arch_timer_read_counter(); } static struct cyclecounter adsp_timesync_cc __ro_after_init = { .read = adsp_ts_tick_read, .mask = CLOCKSOURCE_MASK(56), }; static void adsp_timesync_sync_base_internal(unsigned int flag) { u64 tick, ts; unsigned long irq_flags = 0; int freeze, unfreeze; int updated; spin_lock_irqsave(&adsp_ts_ctx.lock, irq_flags); ts = timecounter_read(&adsp_ts_counter); tick = adsp_ts_counter.cycle_last; adsp_ts_ctx.base_tick = tick; adsp_ts_ctx.base_ts = ts; freeze = (flag & TIMESYNC_FLAG_FREEZE) ? 1 : 0; unfreeze = (flag & TIMESYNC_FLAG_UNFREEZE) ? 1 : 0; /* sync with adsp */ updated = adsp_ts_update(freeze, tick, ts); spin_unlock_irqrestore(&adsp_ts_ctx.lock, irq_flags); pr_info("%s update base: updated=%d, ts=%llu, tick=0x%llx, fz=%d, ver=%d\n", TIMESYNC_TAG, updated, ts, tick, freeze, adsp_base_ver); } void adsp_timesync_sync_base(unsigned int flag) { if (!adsp_ts_ctx.enabled) return; if (flag & TIMESYNC_FLAG_ASYNC) schedule_work(&(adsp_ts_ctx.work)); else adsp_timesync_sync_base_internal(flag); } static enum hrtimer_restart adsp_ts_refresh(struct hrtimer *hrt) { hrtimer_forward_now(hrt, adsp_ts_ctx.wrap_kt); /* snchronize new sched_clock base to co-processors */ adsp_timesync_sync_base(TIMESYNC_FLAG_ASYNC); return HRTIMER_RESTART; } static void adsp_timesync_ws(struct work_struct *ws) { adsp_timesync_sync_base(TIMESYNC_FLAG_SYNC); } int __init adsp_timesync_init(void) { adsp_ts_ctx.enabled = 0; adsp_ts_ctx.init_synced = 0; INIT_WORK(&(adsp_ts_ctx.work), adsp_timesync_ws); spin_lock_init(&adsp_ts_ctx.lock); /* init cyclecounter mult and shift as sched_clock */ clocks_calc_mult_shift(&adsp_timesync_cc.mult, &adsp_timesync_cc.shift, arch_timer_get_cntfrq(), NSEC_PER_SEC, 3600); adsp_ts_ctx.wrap_kt = ns_to_ktime(TIMESYNC_WRAP_TIME); /* Init time counter: * start_time: current sched_clock * read: arch timer counter */ timecounter_init(&adsp_ts_counter, &adsp_timesync_cc, sched_clock()); hrtimer_init(&adsp_ts_refresh_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); adsp_ts_refresh_timer.function = adsp_ts_refresh; hrtimer_start(&adsp_ts_refresh_timer, adsp_ts_ctx.wrap_kt, HRTIMER_MODE_REL); pr_info("%s ts: cycle_last %lld, time_base:%lld, wrap:%lld\n", TIMESYNC_TAG, adsp_ts_counter.cycle_last, adsp_ts_counter.nsec, adsp_ts_ctx.wrap_kt); adsp_ts_ctx.enabled = 1; adsp_timesync_sync_base(TIMESYNC_FLAG_SYNC); adsp_ts_ctx.init_synced = 1; return 0; } void adsp_timesync_suspend(u8 fz) { if (!adsp_ts_ctx.enabled) return; hrtimer_cancel(&adsp_ts_refresh_timer); flush_work(&(adsp_ts_ctx.work)); /* snchronize new sched_clock base to co-processors */ fz = (fz) ? TIMESYNC_FLAG_FREEZE : 0; adsp_timesync_sync_base(TIMESYNC_FLAG_SYNC | fz); } void adsp_timesync_resume(void) { if (!adsp_ts_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(&adsp_ts_counter, &adsp_timesync_cc, sched_clock()); hrtimer_start(&adsp_ts_refresh_timer, adsp_ts_ctx.wrap_kt, HRTIMER_MODE_REL); /* snchronize new sched_clock base to co-processors */ adsp_timesync_sync_base(TIMESYNC_FLAG_SYNC | TIMESYNC_FLAG_UNFREEZE); }