diff options
Diffstat (limited to 'drivers/clocksource/timer-msc313e.c')
| -rw-r--r-- | drivers/clocksource/timer-msc313e.c | 253 | 
1 files changed, 253 insertions, 0 deletions
| diff --git a/drivers/clocksource/timer-msc313e.c b/drivers/clocksource/timer-msc313e.c new file mode 100644 index 000000000000..54c54ca7c786 --- /dev/null +++ b/drivers/clocksource/timer-msc313e.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MStar timer driver + * + * Copyright (C) 2021 Daniel Palmer + * Copyright (C) 2021 Romain Perier + * + */ + +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqreturn.h> +#include <linux/sched_clock.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#ifdef CONFIG_ARM +#include <linux/delay.h> +#endif + +#include "timer-of.h" + +#define TIMER_NAME "msc313e_timer" + +#define MSC313E_REG_CTRL		0x00 +#define MSC313E_REG_CTRL_TIMER_EN	BIT(0) +#define MSC313E_REG_CTRL_TIMER_TRIG	BIT(1) +#define MSC313E_REG_CTRL_TIMER_INT_EN	BIT(8) +#define MSC313E_REG_TIMER_MAX_LOW	0x08 +#define MSC313E_REG_TIMER_MAX_HIGH	0x0c +#define MSC313E_REG_COUNTER_LOW		0x10 +#define MSC313E_REG_COUNTER_HIGH	0x14 +#define MSC313E_REG_TIMER_DIVIDE	0x18 + +#define MSC313E_CLK_DIVIDER		9 +#define TIMER_SYNC_TICKS		3 + +#ifdef CONFIG_ARM +struct msc313e_delay { +	void __iomem *base; +	struct delay_timer delay; +}; +static struct msc313e_delay msc313e_delay; +#endif + +static void __iomem *msc313e_clksrc; + +static void msc313e_timer_stop(void __iomem *base) +{ +	writew(0, base + MSC313E_REG_CTRL); +} + +static void msc313e_timer_start(void __iomem *base, bool periodic) +{ +	u16 reg; + +	reg = readw(base + MSC313E_REG_CTRL); +	if (periodic) +		reg |= MSC313E_REG_CTRL_TIMER_EN; +	else +		reg |= MSC313E_REG_CTRL_TIMER_TRIG; +	writew(reg | MSC313E_REG_CTRL_TIMER_INT_EN, base + MSC313E_REG_CTRL); +} + +static void msc313e_timer_setup(void __iomem *base, unsigned long delay) +{ +	unsigned long flags; + +	local_irq_save(flags); +	writew(delay >> 16, base + MSC313E_REG_TIMER_MAX_HIGH); +	writew(delay & 0xffff, base + MSC313E_REG_TIMER_MAX_LOW); +	local_irq_restore(flags); +} + +static unsigned long msc313e_timer_current_value(void __iomem *base) +{ +	unsigned long flags; +	u16 l, h; + +	local_irq_save(flags); +	l = readw(base + MSC313E_REG_COUNTER_LOW); +	h = readw(base + MSC313E_REG_COUNTER_HIGH); +	local_irq_restore(flags); + +	return (((u32)h) << 16 | l); +} + +static int msc313e_timer_clkevt_shutdown(struct clock_event_device *evt) +{ +	struct timer_of *timer = to_timer_of(evt); + +	msc313e_timer_stop(timer_of_base(timer)); + +	return 0; +} + +static int msc313e_timer_clkevt_set_oneshot(struct clock_event_device *evt) +{ +	struct timer_of *timer = to_timer_of(evt); + +	msc313e_timer_stop(timer_of_base(timer)); +	msc313e_timer_start(timer_of_base(timer), false); + +	return 0; +} + +static int msc313e_timer_clkevt_set_periodic(struct clock_event_device *evt) +{ +	struct timer_of *timer = to_timer_of(evt); + +	msc313e_timer_stop(timer_of_base(timer)); +	msc313e_timer_setup(timer_of_base(timer), timer_of_period(timer)); +	msc313e_timer_start(timer_of_base(timer), true); + +	return 0; +} + +static int msc313e_timer_clkevt_next_event(unsigned long evt, struct clock_event_device *clkevt) +{ +	struct timer_of *timer = to_timer_of(clkevt); + +	msc313e_timer_stop(timer_of_base(timer)); +	msc313e_timer_setup(timer_of_base(timer), evt); +	msc313e_timer_start(timer_of_base(timer), false); + +	return 0; +} + +static irqreturn_t msc313e_timer_clkevt_irq(int irq, void *dev_id) +{ +	struct clock_event_device *evt = dev_id; + +	evt->event_handler(evt); + +	return IRQ_HANDLED; +} + +static u64 msc313e_timer_clksrc_read(struct clocksource *cs) +{ +	return msc313e_timer_current_value(msc313e_clksrc) & cs->mask; +} + +#ifdef CONFIG_ARM +static unsigned long msc313e_read_delay_timer_read(void) +{ +	return msc313e_timer_current_value(msc313e_delay.base); +} +#endif + +static u64 msc313e_timer_sched_clock_read(void) +{ +	return msc313e_timer_current_value(msc313e_clksrc); +} + +static struct clock_event_device msc313e_clkevt = { +	.name = TIMER_NAME, +	.rating = 300, +	.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, +	.set_state_shutdown = msc313e_timer_clkevt_shutdown, +	.set_state_periodic = msc313e_timer_clkevt_set_periodic, +	.set_state_oneshot = msc313e_timer_clkevt_set_oneshot, +	.tick_resume = msc313e_timer_clkevt_shutdown, +	.set_next_event = msc313e_timer_clkevt_next_event, +}; + +static int __init msc313e_clkevt_init(struct device_node *np) +{ +	int ret; +	struct timer_of *to; + +	to = kzalloc(sizeof(*to), GFP_KERNEL); +	if (!to) +		return -ENOMEM; + +	to->flags = TIMER_OF_IRQ | TIMER_OF_CLOCK | TIMER_OF_BASE; +	to->of_irq.handler = msc313e_timer_clkevt_irq; +	ret = timer_of_init(np, to); +	if (ret) +		return ret; + +	if (of_device_is_compatible(np, "sstar,ssd20xd-timer")) { +		to->of_clk.rate = clk_get_rate(to->of_clk.clk) / MSC313E_CLK_DIVIDER; +		to->of_clk.period = DIV_ROUND_UP(to->of_clk.rate, HZ); +		writew(MSC313E_CLK_DIVIDER - 1, timer_of_base(to) + MSC313E_REG_TIMER_DIVIDE); +	} + +	msc313e_clkevt.cpumask = cpu_possible_mask; +	msc313e_clkevt.irq = to->of_irq.irq; +	to->clkevt = msc313e_clkevt; + +	clockevents_config_and_register(&to->clkevt, timer_of_rate(to), +					TIMER_SYNC_TICKS, 0xffffffff); +	return 0; +} + +static int __init msc313e_clksrc_init(struct device_node *np) +{ +	struct timer_of to = { 0 }; +	int ret; +	u16 reg; + +	to.flags = TIMER_OF_BASE | TIMER_OF_CLOCK; +	ret = timer_of_init(np, &to); +	if (ret) +		return ret; + +	msc313e_clksrc = timer_of_base(&to); +	reg = readw(msc313e_clksrc + MSC313E_REG_CTRL); +	reg |= MSC313E_REG_CTRL_TIMER_EN; +	writew(reg, msc313e_clksrc + MSC313E_REG_CTRL); + +#ifdef CONFIG_ARM +	msc313e_delay.base = timer_of_base(&to); +	msc313e_delay.delay.read_current_timer = msc313e_read_delay_timer_read; +	msc313e_delay.delay.freq = timer_of_rate(&to); + +	register_current_timer_delay(&msc313e_delay.delay); +#endif + +	sched_clock_register(msc313e_timer_sched_clock_read, 32, timer_of_rate(&to)); +	return clocksource_mmio_init(timer_of_base(&to), TIMER_NAME, timer_of_rate(&to), 300, 32, +				     msc313e_timer_clksrc_read); +} + +static int __init msc313e_timer_init(struct device_node *np) +{ +	int ret = 0; +	static int num_called; + +	switch (num_called) { +	case 0: +		ret = msc313e_clksrc_init(np); +		if (ret) +			return ret; +		break; + +	default: +		ret = msc313e_clkevt_init(np); +		if (ret) +			return ret; +		break; +	} + +	num_called++; + +	return 0; +} + +TIMER_OF_DECLARE(msc313, "mstar,msc313e-timer", msc313e_timer_init); +TIMER_OF_DECLARE(ssd20xd, "sstar,ssd20xd-timer", msc313e_timer_init); |