/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Clock utilities for s390
 *
 * Authors:
 *  Thomas Huth <thuth@redhat.com>
 *
 * Copied from the s390/intercept test by:
 *  Pierre Morel <pmorel@linux.ibm.com>
 */
#ifndef _ASMS390X_TIME_H_
#define _ASMS390X_TIME_H_

#define S390_CLOCK_SHIFT_US	(63 - 51)

#define STCK_SHIFT_US	S390_CLOCK_SHIFT_US
#define STCK_MAX	((1UL << 52) - 1)

#define CPU_TIMER_SHIFT_US	S390_CLOCK_SHIFT_US

static inline int sck(uint64_t *time)
{
	int cc;

	asm volatile(
		"	sck %[time]\n"
		"	ipm %[cc]\n"
		"	srl %[cc],28\n"
		: [cc] "=d"(cc)
		: [time] "Q"(*time)
		: "cc"
	);

	return cc;
}

static inline int stck(uint64_t *time)
{
	int cc;

	asm volatile(
		"	stck %[time]\n"
		"	ipm %[cc]\n"
		"	srl %[cc],28\n"
		: [cc] "=d" (cc), [time] "=Q" (*time)
		:
		: "cc"
	);

	return cc;
}

static inline int stckf(uint64_t *time)
{
	int cc;

	asm volatile(
		"	stckf %[time]\n"
		"	ipm %[cc]\n"
		"	srl %[cc],28\n"
		: [cc] "=d" (cc), [time] "=Q" (*time)
		:
		: "cc"
	);

	return cc;
}

static inline uint64_t get_clock_us(void)
{
	uint64_t clk;

	stck(&clk);

	return clk >> STCK_SHIFT_US;
}

static inline uint64_t get_clock_ms(void)
{
	return get_clock_us() / 1000;
}

static inline void udelay(unsigned long us)
{
	unsigned long startclk = get_clock_us();
	unsigned long c;

	do {
		c = get_clock_us();
		if (c < startclk)
			c += STCK_MAX;
	} while (c < startclk + us);
}

static inline void mdelay(unsigned long ms)
{
	udelay(ms * 1000);
}

static inline void cpu_timer_set_ms(int64_t timeout_ms)
{
	int64_t timer_value = (timeout_ms * 1000) << CPU_TIMER_SHIFT_US;

	asm volatile (
		"spt %[timer_value]\n"
		:
		: [timer_value] "Q" (timer_value)
	);
}

#endif