With Coherence Manager (CM) 3.5 information about the topology of the system, which has previously only been available through & accessed from the CM, is now also provided by the Cluster Power Controller (CPC). This includes a new CPC_CONFIG register mirroring GCR_CONFIG, and similarly a new CPC_Cx_CONFIG register mirroring GCR_Cx_CONFIG. In preparation for adjusting functions such as mips_cm_numcores(), which have previously only needed to access the CM, to also access the CPC this patch modifies the way we use the various CPS headers. Rather than having users include asm/mips-cm.h or asm/mips-cpc.h individually we instead have users include asm/mips-cps.h which in turn includes asm/mips-cm.h & asm/mips-cpc.h. This means that users will gain access to both CM & CPC registers by including one header, and most importantly it makes asm/mips-cps.h an ideal location for helper functions which need to access the various components of the CPS. Signed-off-by: Paul Burton <paul.burton@imgtec.com> Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/17015/ Patchwork: https://patchwork.linux-mips.org/patch/17217/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
274 lines
5.8 KiB
C
274 lines
5.8 KiB
C
/*
|
|
* Copyright (C) 2015 Imagination Technologies
|
|
* Author: Alex Smith <alex.smith@imgtec.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.
|
|
*/
|
|
|
|
#include "vdso.h"
|
|
|
|
#include <linux/compiler.h>
|
|
#include <linux/irqchip/mips-gic.h>
|
|
#include <linux/time.h>
|
|
|
|
#include <asm/clocksource.h>
|
|
#include <asm/io.h>
|
|
#include <asm/unistd.h>
|
|
#include <asm/vdso.h>
|
|
|
|
#ifdef CONFIG_MIPS_CLOCK_VSYSCALL
|
|
|
|
static __always_inline long gettimeofday_fallback(struct timeval *_tv,
|
|
struct timezone *_tz)
|
|
{
|
|
register struct timezone *tz asm("a1") = _tz;
|
|
register struct timeval *tv asm("a0") = _tv;
|
|
register long ret asm("v0");
|
|
register long nr asm("v0") = __NR_gettimeofday;
|
|
register long error asm("a3");
|
|
|
|
asm volatile(
|
|
" syscall\n"
|
|
: "=r" (ret), "=r" (error)
|
|
: "r" (tv), "r" (tz), "r" (nr)
|
|
: "$1", "$3", "$8", "$9", "$10", "$11", "$12", "$13",
|
|
"$14", "$15", "$24", "$25", "hi", "lo", "memory");
|
|
|
|
return error ? -ret : ret;
|
|
}
|
|
|
|
#endif
|
|
|
|
static __always_inline long clock_gettime_fallback(clockid_t _clkid,
|
|
struct timespec *_ts)
|
|
{
|
|
register struct timespec *ts asm("a1") = _ts;
|
|
register clockid_t clkid asm("a0") = _clkid;
|
|
register long ret asm("v0");
|
|
register long nr asm("v0") = __NR_clock_gettime;
|
|
register long error asm("a3");
|
|
|
|
asm volatile(
|
|
" syscall\n"
|
|
: "=r" (ret), "=r" (error)
|
|
: "r" (clkid), "r" (ts), "r" (nr)
|
|
: "$1", "$3", "$8", "$9", "$10", "$11", "$12", "$13",
|
|
"$14", "$15", "$24", "$25", "hi", "lo", "memory");
|
|
|
|
return error ? -ret : ret;
|
|
}
|
|
|
|
static __always_inline int do_realtime_coarse(struct timespec *ts,
|
|
const union mips_vdso_data *data)
|
|
{
|
|
u32 start_seq;
|
|
|
|
do {
|
|
start_seq = vdso_data_read_begin(data);
|
|
|
|
ts->tv_sec = data->xtime_sec;
|
|
ts->tv_nsec = data->xtime_nsec >> data->cs_shift;
|
|
} while (vdso_data_read_retry(data, start_seq));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __always_inline int do_monotonic_coarse(struct timespec *ts,
|
|
const union mips_vdso_data *data)
|
|
{
|
|
u32 start_seq;
|
|
u64 to_mono_sec;
|
|
u64 to_mono_nsec;
|
|
|
|
do {
|
|
start_seq = vdso_data_read_begin(data);
|
|
|
|
ts->tv_sec = data->xtime_sec;
|
|
ts->tv_nsec = data->xtime_nsec >> data->cs_shift;
|
|
|
|
to_mono_sec = data->wall_to_mono_sec;
|
|
to_mono_nsec = data->wall_to_mono_nsec;
|
|
} while (vdso_data_read_retry(data, start_seq));
|
|
|
|
ts->tv_sec += to_mono_sec;
|
|
timespec_add_ns(ts, to_mono_nsec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_CSRC_R4K
|
|
|
|
static __always_inline u64 read_r4k_count(void)
|
|
{
|
|
unsigned int count;
|
|
|
|
__asm__ __volatile__(
|
|
" .set push\n"
|
|
" .set mips32r2\n"
|
|
" rdhwr %0, $2\n"
|
|
" .set pop\n"
|
|
: "=r" (count));
|
|
|
|
return count;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_CLKSRC_MIPS_GIC
|
|
|
|
static __always_inline u64 read_gic_count(const union mips_vdso_data *data)
|
|
{
|
|
void __iomem *gic = get_gic(data);
|
|
u32 hi, hi2, lo;
|
|
|
|
do {
|
|
hi = __raw_readl(gic + GIC_UMV_SH_COUNTER_63_32_OFS);
|
|
lo = __raw_readl(gic + GIC_UMV_SH_COUNTER_31_00_OFS);
|
|
hi2 = __raw_readl(gic + GIC_UMV_SH_COUNTER_63_32_OFS);
|
|
} while (hi2 != hi);
|
|
|
|
return (((u64)hi) << 32) + lo;
|
|
}
|
|
|
|
#endif
|
|
|
|
static __always_inline u64 get_ns(const union mips_vdso_data *data)
|
|
{
|
|
u64 cycle_now, delta, nsec;
|
|
|
|
switch (data->clock_mode) {
|
|
#ifdef CONFIG_CSRC_R4K
|
|
case VDSO_CLOCK_R4K:
|
|
cycle_now = read_r4k_count();
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_CLKSRC_MIPS_GIC
|
|
case VDSO_CLOCK_GIC:
|
|
cycle_now = read_gic_count(data);
|
|
break;
|
|
#endif
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
delta = (cycle_now - data->cs_cycle_last) & data->cs_mask;
|
|
|
|
nsec = (delta * data->cs_mult) + data->xtime_nsec;
|
|
nsec >>= data->cs_shift;
|
|
|
|
return nsec;
|
|
}
|
|
|
|
static __always_inline int do_realtime(struct timespec *ts,
|
|
const union mips_vdso_data *data)
|
|
{
|
|
u32 start_seq;
|
|
u64 ns;
|
|
|
|
do {
|
|
start_seq = vdso_data_read_begin(data);
|
|
|
|
if (data->clock_mode == VDSO_CLOCK_NONE)
|
|
return -ENOSYS;
|
|
|
|
ts->tv_sec = data->xtime_sec;
|
|
ns = get_ns(data);
|
|
} while (vdso_data_read_retry(data, start_seq));
|
|
|
|
ts->tv_nsec = 0;
|
|
timespec_add_ns(ts, ns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __always_inline int do_monotonic(struct timespec *ts,
|
|
const union mips_vdso_data *data)
|
|
{
|
|
u32 start_seq;
|
|
u64 ns;
|
|
u64 to_mono_sec;
|
|
u64 to_mono_nsec;
|
|
|
|
do {
|
|
start_seq = vdso_data_read_begin(data);
|
|
|
|
if (data->clock_mode == VDSO_CLOCK_NONE)
|
|
return -ENOSYS;
|
|
|
|
ts->tv_sec = data->xtime_sec;
|
|
ns = get_ns(data);
|
|
|
|
to_mono_sec = data->wall_to_mono_sec;
|
|
to_mono_nsec = data->wall_to_mono_nsec;
|
|
} while (vdso_data_read_retry(data, start_seq));
|
|
|
|
ts->tv_sec += to_mono_sec;
|
|
ts->tv_nsec = 0;
|
|
timespec_add_ns(ts, ns + to_mono_nsec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_MIPS_CLOCK_VSYSCALL
|
|
|
|
/*
|
|
* This is behind the ifdef so that we don't provide the symbol when there's no
|
|
* possibility of there being a usable clocksource, because there's nothing we
|
|
* can do without it. When libc fails the symbol lookup it should fall back on
|
|
* the standard syscall path.
|
|
*/
|
|
int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz)
|
|
{
|
|
const union mips_vdso_data *data = get_vdso_data();
|
|
struct timespec ts;
|
|
int ret;
|
|
|
|
ret = do_realtime(&ts, data);
|
|
if (ret)
|
|
return gettimeofday_fallback(tv, tz);
|
|
|
|
if (tv) {
|
|
tv->tv_sec = ts.tv_sec;
|
|
tv->tv_usec = ts.tv_nsec / 1000;
|
|
}
|
|
|
|
if (tz) {
|
|
tz->tz_minuteswest = data->tz_minuteswest;
|
|
tz->tz_dsttime = data->tz_dsttime;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_MIPS_CLOCK_VSYSCALL */
|
|
|
|
int __vdso_clock_gettime(clockid_t clkid, struct timespec *ts)
|
|
{
|
|
const union mips_vdso_data *data = get_vdso_data();
|
|
int ret = -1;
|
|
|
|
switch (clkid) {
|
|
case CLOCK_REALTIME_COARSE:
|
|
ret = do_realtime_coarse(ts, data);
|
|
break;
|
|
case CLOCK_MONOTONIC_COARSE:
|
|
ret = do_monotonic_coarse(ts, data);
|
|
break;
|
|
case CLOCK_REALTIME:
|
|
ret = do_realtime(ts, data);
|
|
break;
|
|
case CLOCK_MONOTONIC:
|
|
ret = do_monotonic(ts, data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ret)
|
|
ret = clock_gettime_fallback(clkid, ts);
|
|
|
|
return ret;
|
|
}
|