Initial commit: Linux kernel 4.19 for Unplugged OS

This commit is contained in:
ziv committed 2025-10-06 13:52:42 +00:00
commit d30235b04c
80519 files changed
+35800681

No files matched your search

+31
View File
@@ -0,0 +1,31 @@
#
# Kernel configuration file for Mediatek proprietary timers
#
# Copyright (C) 2017 Mediatek Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# See the COPYING file in the top-level directory or visit
# <http://www.gnu.org/licenses/gpl-2.0.html>
#
# 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.
#
# This program is provided "AS IS" and "WITH ALL FAULTS" and
# without warranty of any kind. You are solely responsible for
# determining the appropriateness of using and distributing
# the program and assume all risks associated with your exercise
# of rights with respect to the program, including but not limited
# to infringement of third party rights, the risks and costs of
# program errors, damage to or loss of data, programs or equipment,
# and unavailability or interruption of operations. Under no
# circumstances will the contributor of this Program be liable for
# any damages of any kind arising from your use or distribution of
# this program.
source "drivers/misc/mediatek/timer/accuracy-test/Kconfig"
source "drivers/misc/mediatek/timer/timesync/Kconfig"
+15
View File
@@ -0,0 +1,15 @@
#
# Copyright (C) 2017 MediaTek Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License 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.
#
obj-y += timesync/
obj-$(CONFIG_MTK_TIMER_ACCURACY_TEST) += accuracy-test/
@@ -0,0 +1,34 @@
#
# Kernel configuration file for Mediatek proprietary timers
#
# Copyright (C) 2017 Mediatek Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# See the COPYING file in the top-level directory or visit
# <http://www.gnu.org/licenses/gpl-2.0.html>
#
# 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.
#
# This program is provided "AS IS" and "WITH ALL FAULTS" and
# without warranty of any kind. You are solely responsible for
# determining the appropriateness of using and distributing
# the program and assume all risks associated with your exercise
# of rights with respect to the program, including but not limited
# to infringement of third party rights, the risks and costs of
# program errors, damage to or loss of data, programs or equipment,
# and unavailability or interruption of operations. Under no
# circumstances will the contributor of this Program be liable for
# any damages of any kind arising from your use or distribution of
# this program.
config MTK_TIMER_ACCURACY_TEST
bool "mtk timer gpt and systimer accurancy UT test"
help
MTK_TIMER_ACCURANCY_TEST which is used to test gpt and systimer
accurancy, this config only open in UT test, normal we do not open
@@ -0,0 +1,14 @@
#
# Copyright (C) 2017 MediaTek Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License 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.
#
obj-y += systimer_compensation_test.o
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017 MediaTek Inc.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rtc.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <linux/io.h>
#define TIME_CHANGE_32K_FROM_13M 10
struct timer_list minute_timer;
struct work_struct work_poll;
struct rtc_device *rtc_dev;
static void __iomem *spm_register_lock;
static void __iomem *control_mux;
static void __iomem *select_clock;
static void __iomem *systimer_base;
#define SYSTIMER_CNTCR (0x0)
#define SYSTIMER_CNTSR (0x4)
#define SYSTIMER_CNTCV_L (0x8)
#define SYSTIMER_CNTCV_H (0xC)
static uint64_t last_delta;
static uint64_t read_rtc_time_ns(void)
{
int err = 0;
uint64_t rtc_time_ns = 0;
struct rtc_time tm;
struct timespec64 rtc_time;
err = rtc_read_time(rtc_dev, &tm);
rtc_time.tv_sec = rtc_tm_to_time64(&tm);
rtc_time.tv_nsec = tm.tm_cnt * (1000000000 / 32768);
rtc_time_ns = rtc_time.tv_sec * 1000000000 + rtc_time.tv_nsec;
return rtc_time_ns;
}
static uint64_t read_systimer_cnt_high_low(void)
{
uint64_t cyc = 0;
uint32_t high = 0, low = 0;
local_irq_disable();
low = readl(systimer_base + SYSTIMER_CNTCV_L);
high = readl(systimer_base + SYSTIMER_CNTCV_H);
local_irq_enable();
cyc = high;
cyc <<= 32;
cyc += low;
return cyc;
}
static uint64_t cyc_to_ns(uint64_t cyc)
{
u64 num, max = ULLONG_MAX;
u32 mult = 161319385;
u32 shift = 21;
s64 nsec = 0;
do_div(max, mult);
if (cyc > max) {
num = div64_u64(cyc, max);
nsec = (((u64) max * mult) >> shift) * num;
cyc -= num * max;
}
nsec += ((u64) cyc * mult) >> shift;
return nsec;
}
static void print_systimer_rtc_time(void)
{
uint64_t sys_counter_vct = 0, sys_counter_vct_ns = 0;
uint64_t sys_counter_raw = 0, sys_counter_raw_ns = 0;
uint64_t rtc_time_ns = 0;
uint64_t delta = 0;
rtc_time_ns = read_rtc_time_ns();
sys_counter_vct = arch_counter_get_cntvct();
sys_counter_raw = read_systimer_cnt_high_low();
sys_counter_vct_ns = cyc_to_ns(sys_counter_vct);
sys_counter_raw_ns = cyc_to_ns(sys_counter_raw);
delta = rtc_time_ns - sys_counter_vct_ns;
pr_debug("[systimer] rtc_time_ns=%lld\n", rtc_time_ns);
pr_debug("[systimer] sys_counter_vct=%lld, sys_counter_vct_ns=%lld\n",
sys_counter_vct, sys_counter_vct_ns);
pr_debug("[systimer] sys_counter_raw=%lld, sys_counter_raw_ns=%lld\n",
sys_counter_raw, sys_counter_raw_ns);
pr_debug("[systimer] delta=%lld, delta_delta=%lld\n",
delta, delta - last_delta);
last_delta = delta;
}
static void minute_timer_work_func(struct work_struct *work)
{
/* u32 val = 0; */
print_systimer_rtc_time();
}
static void minute_timer_timeout(unsigned long data)
{
schedule_work(&work_poll);
mod_timer(&minute_timer, jiffies + TIME_CHANGE_32K_FROM_13M * HZ);
}
static int __init systimer_compensation_init(void)
{
spm_register_lock = ioremap(0x10A06000, 4);
control_mux = ioremap(0x10A0602c, 4);
select_clock = ioremap(0x10A06008, 4);
systimer_base = ioremap(0x100e0000, 0x9c);
pr_debug("[systimer] %s\n", __func__);
INIT_WORK(&work_poll, minute_timer_work_func);
init_timer(&minute_timer);
minute_timer.function = minute_timer_timeout;
rtc_dev = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
if (rtc_dev == NULL) {
pr_info("[systimer] rtc_class_open rtc_dev fail\n");
return -1;
}
mod_timer(&minute_timer, jiffies + TIME_CHANGE_32K_FROM_13M * HZ);
return 0;
}
module_init(systimer_compensation_init);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("systimer compensation driver");
MODULE_AUTHOR("MTK");
@@ -0,0 +1,57 @@
#
# Kernel configuration file for Mediatek proprietary timers
#
# Copyright (C) 2017 Mediatek Inc.
#
# Authors:
# Stanley Chu <stanley.chu@mediatek.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# See the COPYING file in the top-level directory or visit
# <http://www.gnu.org/licenses/gpl-2.0.html>
#
# 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.
#
# This program is provided "AS IS" and "WITH ALL FAULTS" and
# without warranty of any kind. You are solely responsible for
# determining the appropriateness of using and distributing
# the program and assume all risks associated with your exercise
# of rights with respect to the program, including but not limited
# to infringement of third party rights, the risks and costs of
# program errors, damage to or loss of data, programs or equipment,
# and unavailability or interruption of operations. Under no
# circumstances will the contributor of this Program be liable for
# any damages of any kind arising from your use or distribution of
# this program.
config MTK_TIMER_TIMESYNC
tristate "Time synchronization between kernel sched_clock and sys_timer tick"
default n
help
This selects the support for time synchronization between kernel
sched_clock and sys_timer tick.
Time synchronization may be required in some sub-systems, for example,
sspm and scp ...etc, to have ability to get the same sched_clock
as kernel in AP.
If unsure, say N.
config MTK_ARCHCOUNTER_TIMESYNC
tristate "Time synchronization between archcounter time and monotonic time"
default y
help
This selects the support for time synchronization between archcounter time
and monotonic time.
Time synchronization may be required in some sub-systems, for example,
camera ...etc, to have ability to get the same monotonic time as kernel
in AP.
If unsure, say N.
@@ -0,0 +1,47 @@
#
# Copyright (C) 2017 MediaTek Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License 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.
#
ccflags-y += -I$(srctree)/drivers/misc/mediatek/include
ccflags-y += -I$(srctree)/drivers/misc/mediatek/include/mt-plat/$(MTK_PLATFORM)/include
ccflags-y += -I$(srctree)/drivers/misc/mediatek/include/mt-plat
ifeq ($(CONFIG_MTK_TINYSYS_SSPM_SUPPORT), y)
ifeq (y, $(filter y, $(CONFIG_MACH_MT6877) $(CONFIG_MACH_MT6833) $(CONFIG_MACH_MT6853) $(CONFIG_MACH_MT6885) $(CONFIG_MACH_MT6873) $(CONFIG_MACH_MT6893) $(CONFIG_MACH_MT6781)))
ccflags-y += -DSSPM_V2
ccflags-y += -I$(srctree)/drivers/misc/mediatek/sspm/v2
else
ccflags-y += -I$(srctree)/drivers/misc/mediatek/sspm/v1
endif
ccflags-y += -I$(srctree)/drivers/misc/mediatek/sspm/
ifeq (y, $(filter y, $(CONFIG_MACH_MT6781)))
ccflags-y += -I$(srctree)/drivers/misc/mediatek/sspm/mt6781/
else
ccflags-y += -I$(srctree)/drivers/misc/mediatek/sspm/$(MTK_PLATFORM)/
endif
endif
ifeq ($(CONFIG_MTK_TINYSYS_MCUPM_SUPPORT), y)
ccflags-y += -I$(srctree)/drivers/misc/mediatek/mcupm/
ccflags-y += -I$(srctree)/drivers/misc/mediatek/mcupm/$(MTK_PLATFORM)/
endif
ifeq ($(CONFIG_MTK_AUDIODSP_SUPPORT), y)
ccflags-y += -I$(srctree)/drivers/misc/mediatek/adsp/
ccflags-y += -I$(srctree)/drivers/misc/mediatek/adsp/$(MTK_PLATFORM)/
endif
obj-$(CONFIG_MTK_TIMER_TIMESYNC) += mtk_sys_timer.o
obj-y += archcounter_timesync.o
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2017 MediaTek Inc.
*/
#include <linux/module.h>
#include <asm/arch_timer.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <linux/spinlock.h>
#include <linux/math64.h>
#define FILTER_DATAPOINTS 16
#define FILTER_FREQ 10000000ULL /* 10 ms */
struct moving_average {
uint64_t last_time;
int64_t input[FILTER_DATAPOINTS];
int64_t output;
uint8_t cnt;
uint8_t tail;
};
static struct moving_average moving_average_algo_mono;
static struct moving_average moving_average_algo_boot;
static DEFINE_SPINLOCK(moving_average_lock);
/* arch counter is 13M, mult is 161319385, shift is 21 */
static uint64_t arch_counter_to_ns(uint64_t cyc)
{
u64 num, max = ULLONG_MAX;
u32 mult = 161319385;
u32 shift = 21;
s64 nsec = 0;
do_div(max, mult);
if (cyc > max) {
num = div64_u64(cyc, max);
nsec = (((u64) max * mult) >> shift) * num;
cyc -= num * max;
}
nsec += ((u64) cyc * mult) >> shift;
return nsec;
}
static void moving_average_filter(struct moving_average *filter,
uint64_t base_time, uint64_t archcounter_time)
{
int i = 0;
int64_t avg = 0;
int64_t ret_avg = 0;
if (base_time < filter->last_time + FILTER_FREQ)
return;
filter->last_time = base_time;
filter->input[filter->tail++] = base_time - archcounter_time;
filter->tail &= (FILTER_DATAPOINTS - 1);
if (filter->cnt < FILTER_DATAPOINTS)
filter->cnt++;
for (i = 1, avg = 0; i < filter->cnt; i++)
avg += (filter->input[i] - filter->input[0]);
ret_avg = div_s64(avg, filter->cnt) + filter->input[0];
WRITE_ONCE(filter->output, ret_avg);
}
static uint64_t get_filter_output(struct moving_average *filter)
{
return READ_ONCE(filter->output);
}
static void filter_algo_init(struct moving_average *filter)
{
spin_lock(&moving_average_lock);
memset(filter, 0, sizeof(*filter));
spin_unlock(&moving_average_lock);
}
void archcounter_timesync_init(uint8_t status)
{
if (status) {
filter_algo_init(&moving_average_algo_mono);
filter_algo_init(&moving_average_algo_boot);
}
}
u64 mtk_get_archcounter_time(u64 cyc)
{
return arch_counter_to_ns(cyc);
}
EXPORT_SYMBOL_GPL(mtk_get_archcounter_time);
uint64_t archcounter_timesync_to_monotonic(uint64_t hwclock)
{
unsigned long flags = 0;
uint64_t base_time = 0;
uint64_t archcounter_time = 0;
uint64_t reslut_time = 0;
spin_lock(&moving_average_lock);
local_irq_save(flags);
base_time = ktime_to_ns(ktime_get());
archcounter_time = arch_counter_to_ns(arch_counter_get_cntvct());
local_irq_restore(flags);
moving_average_filter(&moving_average_algo_mono,
base_time, archcounter_time);
reslut_time = arch_counter_to_ns(hwclock) +
get_filter_output(&moving_average_algo_mono);
spin_unlock(&moving_average_lock);
return reslut_time;
}
uint64_t archcounter_timesync_to_boot(uint64_t hwclock)
{
unsigned long flags = 0;
uint64_t base_time = 0;
uint64_t archcounter_time = 0;
uint64_t reslut_time = 0;
spin_lock(&moving_average_lock);
local_irq_save(flags);
base_time = ktime_get_boot_ns();
archcounter_time = arch_counter_to_ns(arch_counter_get_cntvct());
local_irq_restore(flags);
moving_average_filter(&moving_average_algo_boot,
base_time, archcounter_time);
reslut_time = arch_counter_to_ns(hwclock) +
get_filter_output(&moving_average_algo_boot);
spin_unlock(&moving_average_lock);
return reslut_time;
}
/* #define CONFIG_TIMESYNC_TEST */
#ifdef CONFIG_TIMESYNC_TEST
struct timer_list timesync_test_timer;
struct work_struct timesync_test_work;
#define TIMESYNC_TEST_TIME 33
static void timesync_test_work_func(struct work_struct *work)
{
unsigned long flags = 0;
uint64_t base_time = 0;
uint64_t archcounter_time = 0;
uint64_t algo_offset = 0;
local_irq_save(flags);
base_time = ktime_to_ns(ktime_get());
archcounter_time = arch_counter_to_ns(arch_counter_get_cntvct());
local_irq_restore(flags);
moving_average_filter(&moving_average_algo_mono,
base_time, archcounter_time);
algo_offset = get_filter_output(&moving_average_algo_mono);
pr_debug("[archcounter_timesync] monotonic=%lld, archcounter=%lld\n",
base_time, archcounter_time);
pr_debug("[archcounter_timesync] raw_offset=%lld\n",
base_time - archcounter_time);
pr_debug("[archcounter_timesync] algo_offset=%lld\n", algo_offset);
local_irq_save(flags);
base_time = ktime_get_boot_ns();
archcounter_time = arch_counter_to_ns(arch_counter_get_cntvct());
local_irq_restore(flags);
moving_average_filter(&moving_average_algo_mono,
base_time, archcounter_time);
algo_offset = get_filter_output(&moving_average_algo_mono);
pr_debug("[archcounter_timesync] boot=%lld, archcounter=%lld\n",
base_time, archcounter_time);
pr_debug("[archcounter_timesync] raw_offset=%lld\n",
base_time - archcounter_time);
pr_debug("[archcounter_timesync] algo_offset=%lld\n", algo_offset);
}
static void timesync_test_timer_timeout(unsigned long data)
{
schedule_work(&timesync_test_work);
mod_timer(&timesync_test_timer,
jiffies + msecs_to_jiffies(TIMESYNC_TEST_TIME));
}
#endif
static int __init archcounter_timesync_entry(void)
{
pr_debug("[archcounter_timesync] %s\n", __func__);
filter_algo_init(&moving_average_algo_mono);
filter_algo_init(&moving_average_algo_boot);
#ifdef CONFIG_TIMESYNC_TEST
INIT_WORK(&timesync_test_work, timesync_test_work_func);
init_timer(&timesync_test_timer);
timesync_test_timer.function = timesync_test_timer_timeout;
mod_timer(&timesync_test_timer,
jiffies + msecs_to_jiffies(TIMESYNC_TEST_TIME));
#endif
return 0;
}
module_init(archcounter_timesync_entry);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("archcounter timesync driver");
MODULE_AUTHOR("MTK");
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2017 MediaTek Inc.
*/
#ifndef __ARCHCOUNTER_TIMESYNC__
#define __ARCHCOUNTER_TIMESYNC__
void archcounter_timesync_init(uint8_t status);
uint64_t archcounter_timesync_to_monotonic(uint64_t hwclock);
uint64_t archcounter_timesync_to_boot(uint64_t hwclock);
u64 mtk_get_archcounter_time(u64 cyc);
#endif
@@ -0,0 +1,679 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017 MediaTek Inc.
*/
#include <linux/module.h>
#if defined(CONFIG_MTK_TIMER_TIMESYNC) && !defined(CONFIG_FPGA_EARLY_PORTING)
#include <linux/kernel.h>
#include <linux/clocksource.h>
#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/spinlock_types.h>
#include <linux/timer.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/math64.h>
#include <mt-plat/sync_write.h>
#include <mt-plat/mtk_sys_timer.h>
#include <mtk_sys_timer_typedefs.h>
#if defined(CONFIG_MTK_TINYSYS_MCUPM_SUPPORT) || \
defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
#include <mtk_sys_timer_mbox.h>
#endif
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
#include <mtk_sys_timer_mbox.h>
#include <sspm_define.h>
#ifndef SSPM_V2
#include <sspm_ipi.h>
#include <sspm_mbox.h>
#else
#include <sspm_ipi_id.h>
#include <sspm_mbox_pin.h>
#endif
#endif
#ifdef CONFIG_MTK_AUDIODSP_SUPPORT
#include <adsp_helper.h>
#endif
#define SYS_TIMER_DEBUG (0)
#define SYS_TIMER_DEBUGFS_SUPPORT (0)
#if SYS_TIMER_DEBUG
#define sys_timer_print(fmt, ...) pr_debug(fmt, ##__VA_ARGS__)
#else
#define sys_timer_print(fmt, ...)
#endif
#define sys_timer_sysram_write(val, addr) mt_reg_sync_writel(val, addr)
static void __iomem *sys_timer_base;
spinlock_t sys_timer_lock;
static const char sys_timer_node_name[] = "mediatek,sys_timer";
static struct workqueue_struct *sys_timer_workqueue;
static struct sys_timer_timesync_context_t timesync_cxt;
static int sys_timer_device_probe(struct platform_device *pdev);
static inline u64 notrace cyc_to_ns(u64 cyc, u32 mult, u32 shift)
{
return (cyc * mult) >> shift;
}
void sys_timer_timesync_print_base(void)
{
u64 temp_u64[2];
pr_info("timebase:\n");
pr_info(" enabled : %d\n", timesync_cxt.enabled);
pr_info(" base_tick: 0x%llx\n", timesync_cxt.base_tick);
temp_u64[0] = timesync_cxt.base_ts;
temp_u64[1] = do_div(temp_u64[0], NSEC_PER_SEC);
pr_info(" base_ts : %llu.%llu\n", temp_u64[0], temp_u64[1]);
pr_info(" base_fz : %d\n", timesync_cxt.base_fz);
pr_info(" base_ver : %d\n", timesync_cxt.base_ver);
}
#if defined(CONFIG_MTK_TINYSYS_MCUPM_SUPPORT) || \
defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
static int sys_timer_mbox_write(unsigned int id, unsigned int val)
{
int res;
#ifndef SSPM_V2
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
res = sspm_mbox_write(SYS_TIMER_MBOX,
SYS_TIMER_MBOX_OFFSET_BASE + id,
(void *)&val, 1);
#endif
#else
#ifdef CONFIG_MTK_TINYSYS_MCUPM_SUPPORT
res = mcupm_mbox_write(SYS_TIMER_MCUPM_MBOX,
SYS_TIMER_MCUPM_MBOX_OFFSET_BASE + id,
(void *)&val, 1);
#endif
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
res = mtk_mbox_write(&sspm_mboxdev, SYS_TIMER_MBOX,
SYS_TIMER_MBOX_OFFSET_BASE + id,
(void *)&val, 1);
#endif
#endif
return res;
}
#if !defined(SSPM_V2) && defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
static int sys_timer_mbox_read(unsigned int id, unsigned int *val)
{
return sspm_mbox_read(SYS_TIMER_MBOX, id, val, 1);
}
#endif
static u8 sys_timer_timesync_inc_ver(void)
{
u8 ver;
if (timesync_cxt.base_ver >= TIMESYNC_MAX_VER)
ver = 0;
else
ver = timesync_cxt.base_ver + 1;
timesync_cxt.base_ver = ver;
return ver;
}
static void sys_timer_timesync_update_sspm(int suspended,
u64 tick, u64 ts)
{
u32 header, val;
/* make header: freeze and version */
header = suspended ? TIMESYNC_HEADER_FREEZE : 0;
header |= ((timesync_cxt.base_ver << TIMESYNC_HEADER_VER_OFS) &
TIMESYNC_HEADER_VER_MASK);
/* update tick, h -> l */
val = (tick >> 32) & 0xFFFFFFFF;
val |= header;
sys_timer_mbox_write(MBOX_TICK_H, val);
sys_timer_print("%s: tick_h=0x%x\n", __func__, val);
/* fix update sequence to promise atomicity */
mb();
val = tick & 0xFFFFFFFF;
sys_timer_mbox_write(MBOX_TICK_L, val);
sys_timer_print("%s: tick_l=0x%x\n", __func__, val);
/* fix update sequence to promise atomicity */
mb();
/* update ts, l -> h */
val = ts & 0xFFFFFFFF;
sys_timer_mbox_write(MBOX_TS_L, val);
sys_timer_print("%s: ts_l=0x%x\n", __func__, val);
/* fix update sequence to promise atomicity */
mb();
val = (ts >> 32) & 0xFFFFFFFF;
val |= header;
sys_timer_mbox_write(MBOX_TS_H, val);
sys_timer_print("%s: ts_h=0x%x\n", __func__, val);
/* fix update sequence to promise atomicity */
mb();
}
void sys_timer_timesync_verify_sspm(void)
{
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
struct plt_ipi_data_s ipi_data;
#ifndef SSPM_V2
int ackdata = 0;
int ret = 0;
#endif
u32 ts_h = 0, ts_l = 0;
u64 ts_sspm, ts_ap1, ts_ap2, temp_u64[2];
/* reset debug mbox before test */
sys_timer_mbox_write(MBOX_DEBUG_TS_L, 0);
sys_timer_mbox_write(MBOX_DEBUG_TS_H, 0);
ts_ap1 = sched_clock();
temp_u64[0] = ts_ap1;
temp_u64[1] = do_div(temp_u64[0], NSEC_PER_SEC);
pr_info("verify-sspm:ts-ap-1=%llu.%llu\n", temp_u64[0], temp_u64[1]);
/* send ipi to sspm to trigger test */
ipi_data.cmd = PLT_TIMESYNC_SRAM_TEST;
#ifndef SSPM_V2
ret = sspm_ipi_send_sync(IPI_ID_PLATFORM, IPI_OPT_WAIT,
&ipi_data, sizeof(ipi_data) / SSPM_MBOX_SLOT_SIZE, &ackdata, 1);
if (ret != 0) {
pr_info("sspm_ipi_send_sync failed, ret=%d\n", ret);
}
/* wait until sspm writes sspm-view timestamp to sram */
while (1) {
sys_timer_mbox_read(MBOX_DEBUG_TS_L, &ts_l);
sys_timer_mbox_read(MBOX_DEBUG_TS_H, &ts_h);
if (ts_l || ts_h)
break;
pr_info("verify-sspm:polling sspm mbox ...\n");
cpu_relax();
}
#endif
ts_ap2 = sched_clock();
ts_sspm = ((u64)ts_h << 32) | (u64)ts_l;
temp_u64[0] = ts_sspm;
temp_u64[1] = do_div(temp_u64[0], NSEC_PER_SEC);
pr_info("verify-sspm:ts-sspm=%llu.%llu\n", temp_u64[0], temp_u64[1]);
temp_u64[0] = ts_ap2;
temp_u64[1] = do_div(temp_u64[0], NSEC_PER_SEC);
pr_info("verify-sspm:ts-ap-2=%llu.%llu\n", temp_u64[0], temp_u64[1]);
if (ts_ap1 >= ts_sspm || ts_ap2 <= ts_sspm)
pr_info("verify-sspm:ERROR!");
sys_timer_timesync_print_base();
#endif
}
#else
#define sys_timer_mbox_read(id, val)
#define sys_timer_mbox_write(id, val)
#define sys_timer_timesync_inc_ver(void)
#define sys_timer_timesync_update_sspm(suspended, tick, ts)
#define sys_timer_timesync_verify_sspm(void)
#endif
static void sys_timer_timesync_update_ram(void __iomem *base,
int freeze, u64 tick, u64 ts)
{
sys_timer_sysram_write((tick >> 32) & 0xFFFFFFFF,
base + TIMESYNC_BASE_TICK);
sys_timer_sysram_write(tick & 0xFFFFFFFF,
base + TIMESYNC_BASE_TICK + 4);
sys_timer_sysram_write((ts >> 32) & 0xFFFFFFFF,
base + TIMESYNC_BASE_TS);
sys_timer_sysram_write(ts & 0xFFFFFFFF,
base + TIMESYNC_BASE_TS + 4);
sys_timer_sysram_write(freeze,
base + TIMESYNC_BASE_FREEZE);
}
static void
sys_timer_timesync_sync_base_internal(unsigned int flag)
{
u64 tick, ts;
unsigned long irq_flags = 0;
int freeze, unfreeze;
spin_lock_irqsave(&timesync_cxt.lock, irq_flags);
ts = sched_clock_get_cyc(&tick);
timesync_cxt.base_tick = tick;
timesync_cxt.base_ts = ts;
sys_timer_timesync_inc_ver();
freeze = (flag & SYS_TIMER_TIMESYNC_FLAG_FREEZE) ? 1 : 0;
unfreeze = (flag & SYS_TIMER_TIMESYNC_FLAG_UNFREEZE) ? 1 : 0;
/* sync with sysram */
if (timesync_cxt.support_sysram) {
sys_timer_timesync_update_ram(timesync_cxt.ram_base,
freeze, tick, ts);
}
#ifdef CONFIG_MTK_AUDIODSP_SUPPORT
/* sync with adsp */
sys_timer_timesync_update_ram(ADSP_OSTIMER_BUFFER,
freeze, tick, ts);
#endif
/* sync with sspm */
sys_timer_timesync_update_sspm(freeze, tick, ts);
spin_unlock_irqrestore(&timesync_cxt.lock, irq_flags);
pr_debug("update base: ts=%llu, tick=0x%llx, fz=%d, ver=%d\n",
ts, tick, freeze, timesync_cxt.base_ver);
}
u64 sys_timer_timesync_tick_to_sched_clock(u64 tick)
{
u64 ret;
unsigned long flags = 0;
if (tick < timesync_cxt.base_tick)
return 0;
spin_lock_irqsave(&timesync_cxt.lock, flags);
if (timesync_cxt.base_fz)
ret = timesync_cxt.base_ts;
else {
ret = cyc_to_ns(tick - timesync_cxt.base_tick,
timesync_cxt.mult, timesync_cxt.shift) +
timesync_cxt.base_ts;
}
spin_unlock_irqrestore(&timesync_cxt.lock, flags);
return ret;
}
void sys_timer_timesync_sync_base(unsigned int flag)
{
if (!timesync_cxt.enabled)
return;
if (flag & SYS_TIMER_TIMESYNC_FLAG_ASYNC)
queue_work(sys_timer_workqueue, &(timesync_cxt.work));
else
sys_timer_timesync_sync_base_internal(flag);
}
u64 notrace sys_timer_read_tick(void)
{
unsigned long flags;
u32 tick_l, tick_h;
spin_lock_irqsave(&sys_timer_lock, flags);
tick_l = __raw_readl(sys_timer_base + SYS_TIMER_CNTCV_L);
tick_h = __raw_readl(sys_timer_base + SYS_TIMER_CNTCV_H);
spin_unlock_irqrestore(&sys_timer_lock, flags);
return (((u64)tick_h << 32) & 0xFFFFFFFF00000000) |
((u64)tick_l & 0x00000000FFFFFFFF);
}
static const struct platform_device_id sys_timer_id_table[] = {
{ "sys_timer", 0},
{ },
};
static const struct of_device_id sys_timer_of_match[] = {
{ .compatible = "mediatek,sys_timer", },
{},
};
static struct platform_driver sys_timer_driver = {
.remove = NULL,
.shutdown = NULL,
.suspend = NULL,
.resume = NULL,
.probe = sys_timer_device_probe,
.driver = {
.name = "sys_timer",
.owner = THIS_MODULE,
.of_match_table = sys_timer_of_match,
},
.id_table = sys_timer_id_table,
};
static int sys_timer_work_init(void)
{
sys_timer_workqueue = create_workqueue("sys_timer_wq");
if (!sys_timer_workqueue) {
pr_info("workqueue create failed\n");
return -1;
}
return 0;
}
static void sys_timer_timesync_ws(struct work_struct *ws)
{
sys_timer_timesync_sync_base(SYS_TIMER_TIMESYNC_FLAG_SYNC);
}
#ifdef SYS_TIMER_TIMESYNC_REGULAR
static void sys_timer_timesync_timeout(unsigned long data)
{
sys_timer_timesync_sync_base(SYS_TIMER_TIMESYNC_FLAG_ASYNC);
timesync_cxt.timer.expires = jiffies + TIMESYNC_REGULAR_SYNC_SEC;
add_timer(&(timesync_cxt.timer));
}
static int sys_timer_timesync_cfg_regular_sync(void)
{
/*
* init work for
* 1. regular sync by sys_timer itself.
* 2. regular sync by sched_clock_poll.
*/
INIT_WORK(&(timesync_cxt.work), sys_timer_timesync_ws);
/* timer for regular sync by sys_timer itself */
setup_timer(&(timesync_cxt.timer), &sys_timer_timesync_timeout, 0);
timesync_cxt.timer.expires = jiffies + TIMESYNC_REGULAR_SYNC_SEC;
add_timer(&(timesync_cxt.timer));
return 0;
}
#else
static int sys_timer_timesync_cfg_regular_sync(void)
{
/*
* init work for regular sync by sched_clock_poll only
*/
INIT_WORK(&(timesync_cxt.work), sys_timer_timesync_ws);
return 0;
}
#endif
static int sys_timer_timesync_init(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct resource *res;
int sysram_size = -1;
int ret = 0;
spin_lock_init(&timesync_cxt.lock);
/* get sysram base */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sysram_base");
timesync_cxt.ram_base = devm_ioremap_resource(dev, res);
if (IS_ERR((void const *)timesync_cxt.ram_base)) {
pr_info("unable to ioremap sysram base, might be disabled\n");
/* ensure sysram support is disabled */
timesync_cxt.support_sysram = 0;
} else {
/* get sysram size */
if (of_property_read_u32(node,
"mediatek,sysram-size", &sysram_size)) {
pr_info("unable to get sysram-size\n");
goto fail_out;
}
/* check if we have enough sysram size */
if (sysram_size < 20) {
pr_info("not enough sram size %d\n", sysram_size);
goto fail_out;
}
timesync_cxt.support_sysram = 1;
}
/* init mult and shift */
clocks_calc_mult_shift(&timesync_cxt.mult, &timesync_cxt.shift,
SYS_TIMER_CLK_RATE, NSEC_PER_SEC, TIMESYNC_MAX_SEC);
pr_info("mult=%u, shift=%u, maxsec=%u\n",
timesync_cxt.mult, timesync_cxt.shift, TIMESYNC_MAX_SEC);
timesync_cxt.enabled = 1;
sys_timer_timesync_cfg_regular_sync();
/* add the 1st base */
sys_timer_timesync_sync_base(SYS_TIMER_TIMESYNC_FLAG_SYNC);
goto out;
fail_out:
/* ensure disabled */
timesync_cxt.enabled = 0;
ret = -1;
out:
pr_info("enabled: %d, support_sysram: %d\n",
timesync_cxt.enabled, timesync_cxt.support_sysram);
return ret;
}
#if defined(CONFIG_DEBUG_FS) && (SYS_TIMER_DEBUGFS_SUPPORT == 1)
static ssize_t sys_timer_dbgfs_debug_write(struct file *filp,
const char __user *ubuf, size_t cnt,
loff_t *ppos)
{
int ret;
int val = 0;
ret = kstrtoint_from_user(ubuf, cnt, 0, &val);
if (ret) {
pr_info("invalid argument\n");
return ret;
}
if (val == 1) {
/*
* send IPI to request SSPM to write its timestamp in
* SRAM for verification
*/
sys_timer_timesync_verify_sspm();
} else if (val == 2) {
/* synchronous timesync test */
sys_timer_timesync_sync_base(SYS_TIMER_TIMESYNC_FLAG_SYNC);
} else if (val == 3) {
/* synchronous timesync test with suspend flag set */
sys_timer_timesync_sync_base(SYS_TIMER_TIMESYNC_FLAG_SYNC |
SYS_TIMER_TIMESYNC_FLAG_FREEZE);
} else if (val == 4) {
/* asynchronous timesync test */
sys_timer_timesync_sync_base(SYS_TIMER_TIMESYNC_FLAG_ASYNC);
} else if (val == 5) {
/* asynchronous timesync test with suspend flag set */
sys_timer_timesync_sync_base(SYS_TIMER_TIMESYNC_FLAG_ASYNC |
SYS_TIMER_TIMESYNC_FLAG_FREEZE);
} else
pr_info("unsupported value %d\n", val);
return cnt;
}
static int
sys_timer_dbgfs_debug_show(struct seq_file *file, void *data)
{
sys_timer_timesync_print_base();
return 0;
}
static int
sys_timer_dbgfs_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, sys_timer_dbgfs_debug_show, inode->i_private);
}
static const struct file_operations sys_timer_dbgfs_debug_fops = {
.open = sys_timer_dbgfs_debug_open,
.read = seq_read,
.write = sys_timer_dbgfs_debug_write,
};
static void sys_timer_init_debugfs(void)
{
if (timesync_cxt.dbgfs_root)
return;
timesync_cxt.dbgfs_root = debugfs_create_dir("sys_timer", NULL);
if (IS_ERR(timesync_cxt.dbgfs_root))
/* don't complain -- debugfs just isn't enabled */
goto err_no_root;
if (!timesync_cxt.dbgfs_root) {
/*
* complain -- debugfs is enabled, but it failed to
* create the directory
*/
pr_info("null debugfs root directory, exiting\n");
goto err_no_root;
}
timesync_cxt.dbgfs_debug =
debugfs_create_file("debug", 0600,
timesync_cxt.dbgfs_root, &timesync_cxt,
&sys_timer_dbgfs_debug_fops);
if (!timesync_cxt.dbgfs_debug) {
pr_info("null err_stats file, exiting\n");
goto err;
}
return;
err:
debugfs_remove_recursive(timesync_cxt.dbgfs_root);
timesync_cxt.dbgfs_root = NULL;
err_no_root:
pr_info("failed to initialize debugfs\n");
}
#else
#define sys_timer_init_debugfs()
#endif
static int sys_timer_device_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev = &pdev->dev;
int ret = 0;
spin_lock_init(&sys_timer_lock);
/* get sys timer base */
res = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "sys_timer_base");
sys_timer_base = devm_ioremap_resource(dev, res);
if (IS_ERR((void const *)sys_timer_base)) {
pr_info("unable to ioremap sys timer base\n");
ret = -1;
goto out;
}
ret = sys_timer_work_init();
if (ret)
goto out;
ret = sys_timer_timesync_init(pdev);
if (ret)
goto out;
sys_timer_init_debugfs();
out:
return ret;
}
static int __init sys_timer_device_init(void)
{
if (platform_driver_register(&sys_timer_driver)) {
pr_info("device register fail\n");
return -1;
}
return 0;
}
#else
static int __init sys_timer_device_init(void)
{
return 0;
}
#endif
/*
* shall not be prior than initialization of target sub-sys,
* for example,
* sspm: mbox shall be ready.
* adsp: io-remapped ram address for timesync base shall be ready.
*/
late_initcall(sys_timer_device_init);
MODULE_AUTHOR("Stanley Chu <stanley.chu@mediatek.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Mediatek Sys Timer");
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2017 MediaTek Inc.
*/
#ifndef __MTK_SYS_TIMER_MBOX_H__
#define __MTK_SYS_TIMER_MBOX_H__
enum mbox_idx {
MBOX_TICK_H = 0,
MBOX_TICK_L,
MBOX_TS_H,
MBOX_TS_L,
MBOX_DEBUG_TS_H,
MBOX_DEBUG_TS_L,
};
#if defined(CONFIG_MTK_TINYSYS_SSPM_SUPPORT)
#if defined(SSPM_V2)
#include <sspm_mbox_pin.h>
#else
#include <sspm_ipi_mbox_layout.h>
#endif
#define SYS_TIMER_MBOX SHAREMBOX_NO_MCDI
#define SYS_TIMER_MBOX_OFFSET_BASE SHAREMBOX_OFFSET_TIMESTAMP
#endif
#if defined(CONFIG_MTK_TINYSYS_MCUPM_SUPPORT)
#include <mcupm_driver.h>
#define SYS_TIMER_MCUPM_MBOX MCUPM_MBOX_NO_SUSPEND
#define SYS_TIMER_MCUPM_MBOX_OFFSET_BASE MCUPM_MBOX_OFFSET_TIMESTAMP
#endif
#endif /* __MTK_SYS_TIMER_MBOX_H__ */
@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2017 MediaTek Inc.
*/
#ifndef __MTK_SYS_TIMER_TYPES_H__
#define __MTK_SYS_TIMER_TYPES_H__
#include <linux/spinlock_types.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
/*
* Regular timesync by synchronizing time each 60 minutes
* to avoid overflow issue in cyc_to_ns().
*
* This is not necessary now because kernel already has regular
* "sched_clock_timer" for the same purpose.
*/
/* #define SYS_TIMER_TIMESYNC_REGULAR */
#define SYS_TIMER_CLK_RATE (13000000)
#define TIMESYNC_BASE_TICK (0)
#define TIMESYNC_BASE_TS (8)
#define TIMESYNC_BASE_FREEZE (16)
#define TIMESYNC_MAX_SEC (5000)
#define TIMESYNC_MAX_VER (0x7)
#define TIMESYNC_REGULAR_SYNC_SEC (60 * 60 * HZ)
#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 SYS_TIMER_CNTCV_L (0x08)
#define SYS_TIMER_CNTCV_H (0x0C)
struct sys_timer_timesync_context_t {
void __iomem *ram_base;
spinlock_t lock;
u32 mult;
u32 shift;
u64 base_tick;
u64 base_ts;
struct work_struct work;
#ifdef CONFIG_DEBUG_FS
struct dentry *dbgfs_root;
struct dentry *dbgfs_debug;
#endif
#ifdef SYS_TIMER_TIMESYNC_REGULAR
struct timer_list timer;
#endif
u8 base_ver;
u8 base_fz;
u8 enabled;
/* support on-chip sysram update */
u8 support_sysram;
};
#endif