blob: 99f456a87f49129acfe4abf0da8dc675cf418656 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Synaptics AS390 SoC timer support
*
* Copyright (C) 2019 Synaptics Incorporated
*
* Author: Jisheng Zhang <jszhang@kernel.org>
*/
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#define TIMER_NAME "as390_timer"
#define CONTROL 0x00
#define WD_CONTROL 0x04
#define INT_STAT 0x08
#define TERMINAL_COUNT 0x0c
#define RESET_TERMINAL_COUNT 0x10
#define WD_CMD_WINDOWN 0x14
#define CURRENT_COUNT 0x18
struct as390_timer {
void __iomem *base;
struct clk *clk;
unsigned long freq;
};
struct as390_clkevt {
struct clock_event_device ced;
struct as390_timer timer;
};
static inline struct as390_timer *
ced_to_as390_timer(struct clock_event_device *ced)
{
return &container_of(ced, struct as390_clkevt, ced)->timer;
}
static inline u32 rdl(struct as390_timer *timer, u32 offset)
{
return readl_relaxed(timer->base + offset);
}
static inline void wrl(struct as390_timer *timer, u32 offset, u32 data)
{
writel_relaxed(data, timer->base + offset);
}
static int as390_timer_set_next_event(unsigned long cycles,
struct clock_event_device *ced)
{
u32 val;
struct as390_timer *timer = ced_to_as390_timer(ced);
val = rdl(timer, CONTROL);
val &= ~(1 << 1);
wrl(timer, CONTROL, val);
wrl(timer, CURRENT_COUNT, 0);
wrl(timer, TERMINAL_COUNT, cycles);
val = rdl(timer, CONTROL);
val |= (1 << 1);
wrl(timer, CONTROL, val);
return 0;
}
static int as390_timer_shutdown(struct clock_event_device *ced)
{
u32 val;
struct as390_timer *timer = ced_to_as390_timer(ced);
val = rdl(timer, CONTROL);
val &= ~(1 << 1);
wrl(timer, CONTROL, val);
return 0;
}
static int as390_timer_set_periodic(struct clock_event_device *ced)
{
u32 val;
struct as390_timer *timer = ced_to_as390_timer(ced);
wrl(timer, CONTROL, 1 << 0);
wrl(timer, CURRENT_COUNT, 0);
val = DIV_ROUND_UP(timer->freq, HZ);
wrl(timer, TERMINAL_COUNT, val);
val = (1 << 0);
val |= (1 << 1);
val |= (1 << 5);
val |= (1 << 4);
wrl(timer, CONTROL, val);
return 0;
}
static int as390_timer_set_oneshot(struct clock_event_device *ced)
{
u32 val;
struct as390_timer *timer = ced_to_as390_timer(ced);
wrl(timer, CONTROL, 1 << 0);
val = (1 << 0);
val |= (1 << 1);
val |= (1 << 5);
wrl(timer, CONTROL, val);
return 0;
}
static irqreturn_t as390_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *ced = dev_id;
struct as390_timer *timer = ced_to_as390_timer(ced);
wrl(timer, INT_STAT, 1);
ced->event_handler(ced);
return IRQ_HANDLED;
}
static int __init
as390_timer_probe(struct as390_timer *timer, struct device_node *np)
{
struct clk *clk;
int ret;
timer->base = of_iomap(np, 0);
if (!timer->base) {
pr_err("Failed to get base address for '%s'\n", TIMER_NAME);
return -ENXIO;
}
clk = of_clk_get_by_name(np, "timer");
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
pr_err("Failed to get timer clock for '%s'\n", TIMER_NAME);
goto out_unmap;
}
ret = clk_prepare_enable(clk);
if (ret) {
pr_err("Failed to enable timer clock\n");
goto out_unmap;
}
timer->clk = clk;
timer->freq = clk_get_rate(clk);
return 0;
out_unmap:
iounmap(timer->base);
return ret;
}
static void __init as390_timer_cleanup(struct as390_timer *timer)
{
clk_disable_unprepare(timer->clk);
iounmap(timer->base);
}
static int __init as390_clkevt_init(struct device_node *np)
{
struct as390_clkevt *as390_clkevt;
struct clock_event_device *ced;
int ret, irq;
as390_clkevt = kzalloc(sizeof(struct as390_clkevt), GFP_KERNEL);
if (!as390_clkevt)
return -ENOMEM;
irq = irq_of_parse_and_map(np, 0);
if (!irq) {
ret = -EINVAL;
pr_err("Failed to map interrupts for '%s'\n", TIMER_NAME);
goto out_probe;
}
ret = as390_timer_probe(&as390_clkevt->timer, np);
if (ret)
goto out_probe;
ced = &as390_clkevt->ced;
ced->name = TIMER_NAME;
ced->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
CLOCK_EVT_FEAT_DYNIRQ;
ced->set_next_event = as390_timer_set_next_event;
ced->set_state_shutdown = as390_timer_shutdown;
ced->set_state_periodic = as390_timer_set_periodic;
ced->set_state_oneshot = as390_timer_set_oneshot;
ced->irq = irq;
ced->cpumask = cpu_possible_mask;
ced->rating = 200;
ret = request_irq(ced->irq, as390_timer_interrupt, IRQF_TIMER,
TIMER_NAME, ced);
if (ret)
goto out_irq;
clockevents_config_and_register(&as390_clkevt->ced,
as390_clkevt->timer.freq, 1, UINT_MAX);
return 0;
out_irq:
as390_timer_cleanup(&as390_clkevt->timer);
out_probe:
kfree(as390_clkevt);
return ret;
}
static int __init as390_clksrc_init(struct device_node *np)
{
struct as390_timer *as390_clksrc;
int ret;
u32 val;
as390_clksrc = kzalloc(sizeof(struct as390_timer), GFP_KERNEL);
if (!as390_clksrc)
return -ENOMEM;
ret = as390_timer_probe(as390_clksrc, np);
if (ret)
goto out_probe;
wrl(as390_clksrc, CONTROL, 1);
wrl(as390_clksrc, TERMINAL_COUNT, ~0);
val = (1 << 0);
val |= (1 << 1);
val |= (1 << 4);
wrl(as390_clksrc, CONTROL, val);
ret = clocksource_mmio_init(as390_clksrc->base + CURRENT_COUNT,
TIMER_NAME, as390_clksrc->freq, 200, 32,
clocksource_mmio_readl_up);
if (ret) {
pr_err("Failed to register clocksource");
goto out_clocksource;
}
return 0;
out_clocksource:
as390_timer_cleanup(as390_clksrc);
out_probe:
kfree(as390_clksrc);
return ret;
}
static int num_called __initdata;
static int __init as390_timer_init(struct device_node *np)
{
switch (num_called) {
case 0:
as390_clkevt_init(np);
break;
case 1:
as390_clksrc_init(np);
break;
default:
break;
}
num_called++;
return 0;
}
TIMER_OF_DECLARE(as3903288_timer, "syna,as390-timer", as390_timer_init);