Commit bf224e69 authored by Mao Minkai's avatar Mao Minkai Committed by guzitao
Browse files

drivers: clocksource: add sw64 support

Sunway inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I8Y8CY



--------------------------------

Add clocksource driver for SW64.

Signed-off-by: default avatarMao Minkai <maominkai@wxiat.com>
Reviewed-by: default avatarHe Sheng <hesheng@wxiat.com>
Signed-off-by: default avatarGu Zitao <guzitao@wxiat.com>
parent fc031da9
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -737,4 +737,7 @@ config ARM_ARCH_TIMER_WORKAROUND_IN_USERSPACE
	default y
	depends on ARM_ARCH_TIMER_OOL_WORKAROUND

config SW64_TIMER
	bool

endmenu
+1 −0
Original line number Diff line number Diff line
@@ -89,3 +89,4 @@ obj-$(CONFIG_MSC313E_TIMER) += timer-msc313e.o
obj-$(CONFIG_GOLDFISH_TIMER)		+= timer-goldfish.o
obj-$(CONFIG_GXP_TIMER)			+= timer-gxp.o
obj-$(CONFIG_CLKSRC_LOONGSON1_PWM)	+= timer-loongson1-pwm.o
obj-$(CONFIG_SW64_TIMER)		+= timer-sw64.o
+411 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/kconfig.h>
#include <linux/percpu-defs.h>
#include <linux/sched_clock.h>
#include <linux/types.h>

#include <asm/csr.h>
#include <asm/debug.h>
#include <asm/hmcall.h>
#include <asm/hw_init.h>
#include <asm/sw64_init.h>

#define SHTCLK_RATE_KHZ	25000
#define SHTCLK_RATE	(SHTCLK_RATE_KHZ * 1000)

#if defined(CONFIG_SUBARCH_C4)
static u64 read_longtime(struct clocksource *cs)
{
	return read_csr(CSR_SHTCLOCK);
}

static struct clocksource clocksource_longtime = {
	.name	= "longtime",
	.rating	= 100,
	.flags	= CLOCK_SOURCE_IS_CONTINUOUS,
	.mask	= CLOCKSOURCE_MASK(64),
	.shift	= 0,
	.mult	= 0,
	.read	= read_longtime,
};

static u64 notrace read_sched_clock(void)
{
	return read_csr(CSR_SHTCLOCK);
}

void __init sw64_setup_clocksource(void)
{
	clocksource_register_khz(&clocksource_longtime, SHTCLK_RATE_KHZ);
	sched_clock_register(read_sched_clock, BITS_PER_LONG, SHTCLK_RATE);
}

void __init setup_sched_clock(void) { }
#elif defined(CONFIG_SUBARCH_C3B)
#ifdef CONFIG_SMP
static u64 read_longtime(struct clocksource *cs)
{
	unsigned long node;

	node = __this_cpu_read(hard_node_id);
	return __io_read_longtime(node);
}

static int longtime_enable(struct clocksource *cs)
{
	switch (cpu_desc.model) {
	case CPU_SW3231:
		sw64_io_write(0, GPIO_SWPORTA_DR, 0);
		sw64_io_write(0, GPIO_SWPORTA_DDR, 0xff);
		break;
	case CPU_SW831:
		__io_write_longtime_start_en(0, 0x1);
		break;
	default:
		break;
	}

	return 0;
}

static struct clocksource clocksource_longtime = {
	.name	= "longtime",
	.rating	= 100,
	.enable	= longtime_enable,
	.flags	= CLOCK_SOURCE_IS_CONTINUOUS,
	.mask	= CLOCKSOURCE_MASK(64),
	.shift	= 0,
	.mult	= 0,
	.read	= read_longtime,
};

static u64 read_vtime(struct clocksource *cs)
{
	unsigned long vtime_addr;

	vtime_addr = IO_BASE | LONG_TIME;
	return rdio64(vtime_addr);
}

static int vtime_enable(struct clocksource *cs)
{
	return 0;
}

static struct clocksource clocksource_vtime = {
	.name	= "vtime",
	.rating	= 100,
	.enable	= vtime_enable,
	.flags	= CLOCK_SOURCE_IS_CONTINUOUS,
	.mask	= CLOCKSOURCE_MASK(64),
	.shift	= 0,
	.mult	= 0,
	.read	= read_vtime,
};
#else /* !SMP */
static u64 read_tc(struct clocksource *cs)
{
	return rdtc();
}

static struct clocksource clocksource_tc = {
	.name		= "tc",
	.rating		= 300,
	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
	.mask		= CLOCKSOURCE_MASK(64),
	.shift		= 22,
	.mult		= 0,  /* To be filled in */
	.read		= read_tc,
};
#endif /* SMP */

#define DEFAULT_MCLK	25	/* Mhz */

void __init sw64_setup_clocksource(void)
{
	unsigned int mclk = *((unsigned char *)__va(MB_MCLK));

	if (!mclk)
		mclk = DEFAULT_MCLK;

#ifdef CONFIG_SMP
	if (is_in_host())
		clocksource_register_khz(&clocksource_longtime, mclk * 1000);
	else
		clocksource_register_khz(&clocksource_vtime, DEFAULT_MCLK * 1000);
#else
	clocksource_register_hz(&clocksource_tc, get_cpu_freq());
	pr_info("Setup clocksource TC, mult = %d\n", clocksource_tc.mult);
#endif
}

DECLARE_PER_CPU(u64, tc_offset);
static u64 sc_start, sc_shift, sc_multi;
DEFINE_STATIC_KEY_FALSE(use_tc_as_sched_clock);

static int __init sched_clock_setup(char *opt)
{
	if (!opt)
		return -EINVAL;

	if (!strncmp(opt, "on", 2)) {
		static_branch_enable(&use_tc_as_sched_clock);
		pr_info("Using TC instead of jiffies as source of sched_clock()\n");
	}

	return 0;
}
early_param("tc_sched_clock", sched_clock_setup);

static void __init calibrate_sched_clock(void)
{
	sc_start = rdtc();
}

void __init setup_sched_clock(void)
{
	unsigned long step;

	sc_shift = 7;
	step = 1UL << sc_shift;
	sc_multi = step * NSEC_PER_SEC / get_cpu_freq();
	calibrate_sched_clock();

	pr_info("sched_clock: sc_multi=%llu, sc_shift=%llu\n", sc_multi, sc_shift);
}

#ifdef CONFIG_GENERIC_SCHED_CLOCK
static u64 notrace read_sched_clock(void)
{
	return (rdtc() - sc_start) >> sc_shift;
}

void __init sw64_sched_clock_init(void)
{
	sched_clock_register(sched_clock_read, BITS_PER_LONG, get_cpu_freq() >> sc_shift);
}
#else /* !CONFIG_GENERIC_SCHED_CLOCK */
/*
 * scheduler clock - returns current time in nanoseconds.
 */
unsigned long long notrace sched_clock(void)
{
	if (static_branch_likely(&use_tc_as_sched_clock))
		return ((rdtc() - sc_start + __this_cpu_read(tc_offset)) >> sc_shift) * sc_multi;
	else
		return (jiffies - INITIAL_JIFFIES) * (NSEC_PER_SEC / HZ);
}

#ifdef CONFIG_DEBUG_FS
static ssize_t sched_clock_status_read(struct file *file, char __user *user_buf,
				   size_t count, loff_t *ppos)
{
	char buf[2];

	if (static_key_enabled(&use_tc_as_sched_clock))
		buf[0] = 'Y';
	else
		buf[0] = 'N';
	buf[1] = '\n';
	return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
}

static ssize_t sched_clock_status_write(struct file *file, const char __user *user_buf,
				   size_t count, loff_t *ppos)
{
	int r;
	bool bv;
	bool val = static_key_enabled(&use_tc_as_sched_clock);

	r = kstrtobool_from_user(user_buf, count, &bv);
	if (!r) {
		if (val != bv) {
			if (bv) {
				static_branch_enable(&use_tc_as_sched_clock);
				pr_info("source of sched_clock() switched from jiffies to TC\n");
			} else {
				static_branch_disable(&use_tc_as_sched_clock);
				pr_info("source of sched_clock() switched from TC to jiffies\n");
			}
		} else {
			if (val)
				pr_info("source of sched_clock() unchanged (using TC)\n");
			else
				pr_info("source of sched_clock() unchanged (using jiffies)\n");
		}
	}

	return count;
}

static const struct file_operations sched_clock_status_fops = {
	.read		= sched_clock_status_read,
	.write		= sched_clock_status_write,
	.open		= nonseekable_open,
	.llseek		= no_llseek,
};

static int __init sched_clock_debug_init(void)
{
	struct dentry *sched_clock_status;

	if (!sw64_debugfs_dir)
		return -ENODEV;

	sched_clock_status = debugfs_create_file("tc_sched_clock",
			0644, sw64_debugfs_dir, NULL,
			&sched_clock_status_fops);

	if (!sched_clock_status)
		return -ENOMEM;

	return 0;
}
late_initcall(sched_clock_debug_init);
#endif /* CONFIG_DEBUG_FS */
#endif /* CONFIG_GENERIC_SCHED_CLOCK */

#endif



static int timer_next_event(unsigned long delta,
		struct clock_event_device *evt);
static int timer_set_shutdown(struct clock_event_device *evt);
static int timer_set_oneshot(struct clock_event_device *evt);

/*
 * The local apic timer can be used for any function which is CPU local.
 */
static struct clock_event_device timer_clockevent = {
	.name			= "timer",
	.features		= CLOCK_EVT_FEAT_ONESHOT,
	.shift			= 20,
	.mult			= 0,
	.set_state_shutdown	= timer_set_shutdown,
	.set_state_oneshot	= timer_set_oneshot,
	.set_next_event		= timer_next_event,
	.rating			= 300,
	.irq			= -1,
};

static int vtimer_next_event(unsigned long delta,
		struct clock_event_device *evt)
{
	hcall(HCALL_SET_CLOCKEVENT, delta, 0, 0);
	return 0;
}

static int vtimer_shutdown(struct clock_event_device *evt)
{
	hcall(HCALL_SET_CLOCKEVENT, 0, 0, 0);
	return 0;
}

static int vtimer_set_oneshot(struct clock_event_device *evt)
{
	return 0;
}
static struct clock_event_device vtimer_clockevent = {
	.name			= "vtimer",
	.features		= CLOCK_EVT_FEAT_ONESHOT,
	.shift			= 20,
	.mult			= 0,
	.set_state_shutdown	= vtimer_shutdown,
	.set_state_oneshot	= vtimer_set_oneshot,
	.set_next_event		= vtimer_next_event,
	.rating			= 300,
	.irq			= -1,
};

static DEFINE_PER_CPU(struct clock_event_device, timer_events);

/*
 * Program the next event, relative to now
 */
static int timer_next_event(unsigned long delta,
		struct clock_event_device *evt)
{
	wrtimer(delta);
	return 0;
}

static int timer_set_shutdown(struct clock_event_device *evt)
{
	wrtimer(0);
	return 0;
}

static int timer_set_oneshot(struct clock_event_device *evt)
{
	/*
	 * SW-TIMER support CLOCK_EVT_MODE_ONESHOT only, and automatically.
	 * unlike PIT and HPET, which support ONESHOT or PERIODIC by setting PIT_MOD or HPET_Tn_CFG
	 * so, nothing to do here ...
	 */
	return 0;
}

void sw64_update_clockevents(unsigned long cpu, u32 freq)
{
	struct clock_event_device *swevt = &per_cpu(timer_events, cpu);

	if (cpu == smp_processor_id())
		clockevents_update_freq(swevt, freq);
	else {
		clockevents_calc_mult_shift(swevt, freq, 4);
		swevt->min_delta_ns = clockevent_delta2ns(swevt->min_delta_ticks, swevt);
		swevt->max_delta_ns = clockevent_delta2ns(swevt->max_delta_ticks, swevt);
	}
}

/*
 * Setup the local timer for this CPU. Copy the initialized values
 * of the boot CPU and register the clock event in the framework.
 */
void sw64_setup_timer(void)
{
	unsigned long  min_delta;
	int cpu = smp_processor_id();
	struct clock_event_device *swevt = &per_cpu(timer_events, cpu);

	/* min_delta ticks => 100ns */
	min_delta = get_cpu_freq()/1000/1000/10;

	if (is_in_guest()) {
		memcpy(swevt, &vtimer_clockevent, sizeof(*swevt));
		/*
		 * CUIWEI: This value is very important.
		 * If it's too small, the timer will timeout when the IER
		 * haven't been opened.
		 */
		min_delta *= 4;
	} else {
		memcpy(swevt, &timer_clockevent, sizeof(*swevt));
	}
	swevt->cpumask = cpumask_of(cpu);
	swevt->set_state_shutdown(swevt);
	clockevents_config_and_register(swevt, get_cpu_freq(), min_delta, ULONG_MAX);
}

void sw64_timer_interrupt(void)
{
	struct clock_event_device *evt = this_cpu_ptr(&timer_events);

	irq_enter();
	if (!evt->event_handler) {
		pr_warn("Spurious local timer interrupt on cpu %d\n",
				smp_processor_id());
		timer_set_shutdown(evt);
		return;
	}

	inc_irq_stat(timer_irqs_event);

	evt->event_handler(evt);

	irq_exit();
}