Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * RDA8810PL SoC timer driver
 *
 * Copyright RDA Microelectronics Company Limited
 * Copyright (c) 2017 Andreas Färber
 * Copyright (c) 2018 Manivannan Sadhasivam
 *
 * RDA8810PL has two independent timers: OSTIMER (56 bit) and HWTIMER (64 bit).
 * Each timer provides optional interrupt support. In this driver, OSTIMER is
 * used for clockevents and HWTIMER is used for clocksource.
 */

#include <linux/init.h>
#include <linux/interrupt.h>

#include "timer-of.h"

#define RDA_OSTIMER_LOADVAL_L	0x000
#define RDA_OSTIMER_CTRL	0x004
#define RDA_HWTIMER_LOCKVAL_L	0x024
#define RDA_HWTIMER_LOCKVAL_H	0x028
#define RDA_TIMER_IRQ_MASK_SET	0x02c
#define RDA_TIMER_IRQ_MASK_CLR	0x030
#define RDA_TIMER_IRQ_CLR	0x034

#define RDA_OSTIMER_CTRL_ENABLE		BIT(24)
#define RDA_OSTIMER_CTRL_REPEAT		BIT(28)
#define RDA_OSTIMER_CTRL_LOAD		BIT(30)

#define RDA_TIMER_IRQ_MASK_OSTIMER	BIT(0)

#define RDA_TIMER_IRQ_CLR_OSTIMER	BIT(0)

static int rda_ostimer_start(void __iomem *base, bool periodic, u64 cycles)
{
	u32 ctrl, load_l;

	load_l = (u32)cycles;
	ctrl = ((cycles >> 32) & 0xffffff);
	ctrl |= RDA_OSTIMER_CTRL_LOAD | RDA_OSTIMER_CTRL_ENABLE;
	if (periodic)
		ctrl |= RDA_OSTIMER_CTRL_REPEAT;

	/* Enable ostimer interrupt first */
	writel_relaxed(RDA_TIMER_IRQ_MASK_OSTIMER,
		       base + RDA_TIMER_IRQ_MASK_SET);

	/* Write low 32 bits first, high 24 bits are with ctrl */
	writel_relaxed(load_l, base + RDA_OSTIMER_LOADVAL_L);
	writel_relaxed(ctrl, base + RDA_OSTIMER_CTRL);

	return 0;
}

static int rda_ostimer_stop(void __iomem *base)
{
	/* Disable ostimer interrupt first */
	writel_relaxed(RDA_TIMER_IRQ_MASK_OSTIMER,
		       base + RDA_TIMER_IRQ_MASK_CLR);

	writel_relaxed(0, base + RDA_OSTIMER_CTRL);

	return 0;
}

static int rda_ostimer_set_state_shutdown(struct clock_event_device *evt)
{
	struct timer_of *to = to_timer_of(evt);

	rda_ostimer_stop(timer_of_base(to));

	return 0;
}

static int rda_ostimer_set_state_oneshot(struct clock_event_device *evt)
{
	struct timer_of *to = to_timer_of(evt);

	rda_ostimer_stop(timer_of_base(to));

	return 0;
}

static int rda_ostimer_set_state_periodic(struct clock_event_device *evt)
{
	struct timer_of *to = to_timer_of(evt);
	unsigned long cycles_per_jiffy;

	rda_ostimer_stop(timer_of_base(to));

	cycles_per_jiffy = ((unsigned long long)NSEC_PER_SEC / HZ *
			     evt->mult) >> evt->shift;
	rda_ostimer_start(timer_of_base(to), true, cycles_per_jiffy);

	return 0;
}

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

static int rda_ostimer_set_next_event(unsigned long evt,
				      struct clock_event_device *ev)
{
	struct timer_of *to = to_timer_of(ev);

	rda_ostimer_start(timer_of_base(to), false, evt);

	return 0;
}

static irqreturn_t rda_ostimer_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *evt = dev_id;
	struct timer_of *to = to_timer_of(evt);

	/* clear timer int */
	writel_relaxed(RDA_TIMER_IRQ_CLR_OSTIMER,
		       timer_of_base(to) + RDA_TIMER_IRQ_CLR);

	if (evt->event_handler)
		evt->event_handler(evt);

	return IRQ_HANDLED;
}

static struct timer_of rda_ostimer_of = {
	.flags = TIMER_OF_IRQ | TIMER_OF_BASE,

	.clkevt = {
		.name = "rda-ostimer",
		.rating = 250,
		.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
			    CLOCK_EVT_FEAT_DYNIRQ,
		.set_state_shutdown = rda_ostimer_set_state_shutdown,
		.set_state_oneshot = rda_ostimer_set_state_oneshot,
		.set_state_periodic = rda_ostimer_set_state_periodic,
		.tick_resume = rda_ostimer_tick_resume,
		.set_next_event	= rda_ostimer_set_next_event,
	},

	.of_base = {
		.name = "rda-timer",
		.index = 0,
	},

	.of_irq = {
		.name = "ostimer",
		.handler = rda_ostimer_interrupt,
		.flags = IRQF_TIMER,
	},
};

static u64 rda_hwtimer_read(struct clocksource *cs)
{
	void __iomem *base = timer_of_base(&rda_ostimer_of);
	u32 lo, hi;

	/* Always read low 32 bits first */
	do {
		lo = readl_relaxed(base + RDA_HWTIMER_LOCKVAL_L);
		hi = readl_relaxed(base + RDA_HWTIMER_LOCKVAL_H);
	} while (hi != readl_relaxed(base + RDA_HWTIMER_LOCKVAL_H));

	return ((u64)hi << 32) | lo;
}

static struct clocksource rda_hwtimer_clocksource = {
	.name           = "rda-timer",
	.rating         = 400,
	.read           = rda_hwtimer_read,
	.mask           = CLOCKSOURCE_MASK(64),
	.flags          = CLOCK_SOURCE_IS_CONTINUOUS,
};

static int __init rda_timer_init(struct device_node *np)
{
	unsigned long rate = 2000000;
	int ret;

	ret = timer_of_init(np, &rda_ostimer_of);
	if (ret)
		return ret;

	clocksource_register_hz(&rda_hwtimer_clocksource, rate);

	clockevents_config_and_register(&rda_ostimer_of.clkevt, rate,
					0x2, UINT_MAX);

	return 0;
}

TIMER_OF_DECLARE(rda8810pl, "rda,8810pl-timer", rda_timer_init);