// SPDX-License-Identifier: GPL-2.0
/*
 * Renesas Timer Support - OSTM
 *
 * Copyright (C) 2017 Renesas Electronics America, Inc.
 * Copyright (C) 2017 Chris Brandt
 */

#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>

#include "timer-of.h"

/*
 * The OSTM contains independent channels.
 * The first OSTM channel probed will be set up as a free running
 * clocksource. Additionally we will use this clocksource for the system
 * schedule timer sched_clock().
 *
 * The second (or more) channel probed will be set up as an interrupt
 * driven clock event.
 */

static void __iomem *system_clock;	/* For sched_clock() */

/* OSTM REGISTERS */
#define	OSTM_CMP		0x000	/* RW,32 */
#define	OSTM_CNT		0x004	/* R,32 */
#define	OSTM_TE			0x010	/* R,8 */
#define	OSTM_TS			0x014	/* W,8 */
#define	OSTM_TT			0x018	/* W,8 */
#define	OSTM_CTL		0x020	/* RW,8 */

#define	TE			0x01
#define	TS			0x01
#define	TT			0x01
#define	CTL_PERIODIC		0x00
#define	CTL_ONESHOT		0x02
#define	CTL_FREERUN		0x02

static void ostm_timer_stop(struct timer_of *to)
{
	if (readb(timer_of_base(to) + OSTM_TE) & TE) {
		writeb(TT, timer_of_base(to) + OSTM_TT);

		/*
		 * Read back the register simply to confirm the write operation
		 * has completed since I/O writes can sometimes get queued by
		 * the bus architecture.
		 */
		while (readb(timer_of_base(to) + OSTM_TE) & TE)
			;
	}
}

static int __init ostm_init_clksrc(struct timer_of *to)
{
	ostm_timer_stop(to);

	writel(0, timer_of_base(to) + OSTM_CMP);
	writeb(CTL_FREERUN, timer_of_base(to) + OSTM_CTL);
	writeb(TS, timer_of_base(to) + OSTM_TS);

	return clocksource_mmio_init(timer_of_base(to) + OSTM_CNT,
				     to->np->full_name, timer_of_rate(to), 300,
				     32, clocksource_mmio_readl_up);
}

static u64 notrace ostm_read_sched_clock(void)
{
	return readl(system_clock);
}

static void __init ostm_init_sched_clock(struct timer_of *to)
{
	system_clock = timer_of_base(to) + OSTM_CNT;
	sched_clock_register(ostm_read_sched_clock, 32, timer_of_rate(to));
}

static int ostm_clock_event_next(unsigned long delta,
				 struct clock_event_device *ced)
{
	struct timer_of *to = to_timer_of(ced);

	ostm_timer_stop(to);

	writel(delta, timer_of_base(to) + OSTM_CMP);
	writeb(CTL_ONESHOT, timer_of_base(to) + OSTM_CTL);
	writeb(TS, timer_of_base(to) + OSTM_TS);

	return 0;
}

static int ostm_shutdown(struct clock_event_device *ced)
{
	struct timer_of *to = to_timer_of(ced);

	ostm_timer_stop(to);

	return 0;
}
static int ostm_set_periodic(struct clock_event_device *ced)
{
	struct timer_of *to = to_timer_of(ced);

	if (clockevent_state_oneshot(ced) || clockevent_state_periodic(ced))
		ostm_timer_stop(to);

	writel(timer_of_period(to) - 1, timer_of_base(to) + OSTM_CMP);
	writeb(CTL_PERIODIC, timer_of_base(to) + OSTM_CTL);
	writeb(TS, timer_of_base(to) + OSTM_TS);

	return 0;
}

static int ostm_set_oneshot(struct clock_event_device *ced)
{
	struct timer_of *to = to_timer_of(ced);

	ostm_timer_stop(to);

	return 0;
}

static irqreturn_t ostm_timer_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *ced = dev_id;

	if (clockevent_state_oneshot(ced))
		ostm_timer_stop(to_timer_of(ced));

	/* notify clockevent layer */
	if (ced->event_handler)
		ced->event_handler(ced);

	return IRQ_HANDLED;
}

static int __init ostm_init_clkevt(struct timer_of *to)
{
	struct clock_event_device *ced = &to->clkevt;

	ced->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC;
	ced->set_state_shutdown = ostm_shutdown;
	ced->set_state_periodic = ostm_set_periodic;
	ced->set_state_oneshot = ostm_set_oneshot;
	ced->set_next_event = ostm_clock_event_next;
	ced->shift = 32;
	ced->rating = 300;
	ced->cpumask = cpumask_of(0);
	clockevents_config_and_register(ced, timer_of_rate(to), 0xf,
					0xffffffff);

	return 0;
}

static int __init ostm_init(struct device_node *np)
{
	struct reset_control *rstc;
	struct timer_of *to;
	int ret;

	to = kzalloc_obj(*to);
	if (!to)
		return -ENOMEM;

	rstc = of_reset_control_get_optional_exclusive(np, NULL);
	if (IS_ERR(rstc)) {
		ret = PTR_ERR(rstc);
		goto err_free;
	}

	reset_control_deassert(rstc);

	to->flags = TIMER_OF_BASE | TIMER_OF_CLOCK;
	if (system_clock) {
		/*
		 * clock sources don't use interrupts, clock events do
		 */
		to->flags |= TIMER_OF_IRQ;
		to->of_irq.flags = IRQF_TIMER | IRQF_IRQPOLL;
		to->of_irq.handler = ostm_timer_interrupt;
	}

	ret = timer_of_init(np, to);
	if (ret)
		goto err_reset;

	/*
	 * First probed device will be used as system clocksource. Any
	 * additional devices will be used as clock events.
	 */
	if (!system_clock) {
		ret = ostm_init_clksrc(to);
		if (ret)
			goto err_cleanup;

		ostm_init_sched_clock(to);
		pr_info("%pOF: used for clocksource\n", np);
	} else {
		ret = ostm_init_clkevt(to);
		if (ret)
			goto err_cleanup;

		pr_info("%pOF: used for clock events\n", np);
	}

	of_node_set_flag(np, OF_POPULATED);
	return 0;

err_cleanup:
	timer_of_cleanup(to);
err_reset:
	reset_control_assert(rstc);
	reset_control_put(rstc);
err_free:
	kfree(to);
	return ret;
}

TIMER_OF_DECLARE(ostm, "renesas,ostm", ostm_init);

static int __init ostm_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;

	return ostm_init(dev->of_node);
}

static const struct of_device_id __maybe_unused ostm_of_table[] = {
	{ .compatible = "renesas,ostm", },
	{ /* sentinel */ }
};

static struct platform_driver ostm_device_driver = {
	.driver = {
		.name = "renesas_ostm",
		.of_match_table = of_match_ptr(ostm_of_table),
		.suppress_bind_attrs = true,
	},
};
builtin_platform_driver_probe(ostm_device_driver, ostm_probe);
