forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
6.6 KiB
278 lines
6.6 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright (C) 2013 Pengutronix |
|
* Uwe Kleine-Koenig <[email protected]> |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/clocksource.h> |
|
#include <linux/clockchips.h> |
|
#include <linux/irq.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/of.h> |
|
#include <linux/of_address.h> |
|
#include <linux/of_irq.h> |
|
#include <linux/clk.h> |
|
|
|
#define TIMERn_CTRL 0x00 |
|
#define TIMERn_CTRL_PRESC(val) (((val) & 0xf) << 24) |
|
#define TIMERn_CTRL_PRESC_1024 TIMERn_CTRL_PRESC(10) |
|
#define TIMERn_CTRL_CLKSEL(val) (((val) & 0x3) << 16) |
|
#define TIMERn_CTRL_CLKSEL_PRESCHFPERCLK TIMERn_CTRL_CLKSEL(0) |
|
#define TIMERn_CTRL_OSMEN 0x00000010 |
|
#define TIMERn_CTRL_MODE(val) (((val) & 0x3) << 0) |
|
#define TIMERn_CTRL_MODE_UP TIMERn_CTRL_MODE(0) |
|
#define TIMERn_CTRL_MODE_DOWN TIMERn_CTRL_MODE(1) |
|
|
|
#define TIMERn_CMD 0x04 |
|
#define TIMERn_CMD_START 0x00000001 |
|
#define TIMERn_CMD_STOP 0x00000002 |
|
|
|
#define TIMERn_IEN 0x0c |
|
#define TIMERn_IF 0x10 |
|
#define TIMERn_IFS 0x14 |
|
#define TIMERn_IFC 0x18 |
|
#define TIMERn_IRQ_UF 0x00000002 |
|
|
|
#define TIMERn_TOP 0x1c |
|
#define TIMERn_CNT 0x24 |
|
|
|
struct efm32_clock_event_ddata { |
|
struct clock_event_device evtdev; |
|
void __iomem *base; |
|
unsigned periodic_top; |
|
}; |
|
|
|
static int efm32_clock_event_shutdown(struct clock_event_device *evtdev) |
|
{ |
|
struct efm32_clock_event_ddata *ddata = |
|
container_of(evtdev, struct efm32_clock_event_ddata, evtdev); |
|
|
|
writel_relaxed(TIMERn_CMD_STOP, ddata->base + TIMERn_CMD); |
|
return 0; |
|
} |
|
|
|
static int efm32_clock_event_set_oneshot(struct clock_event_device *evtdev) |
|
{ |
|
struct efm32_clock_event_ddata *ddata = |
|
container_of(evtdev, struct efm32_clock_event_ddata, evtdev); |
|
|
|
writel_relaxed(TIMERn_CMD_STOP, ddata->base + TIMERn_CMD); |
|
writel_relaxed(TIMERn_CTRL_PRESC_1024 | |
|
TIMERn_CTRL_CLKSEL_PRESCHFPERCLK | |
|
TIMERn_CTRL_OSMEN | |
|
TIMERn_CTRL_MODE_DOWN, |
|
ddata->base + TIMERn_CTRL); |
|
return 0; |
|
} |
|
|
|
static int efm32_clock_event_set_periodic(struct clock_event_device *evtdev) |
|
{ |
|
struct efm32_clock_event_ddata *ddata = |
|
container_of(evtdev, struct efm32_clock_event_ddata, evtdev); |
|
|
|
writel_relaxed(TIMERn_CMD_STOP, ddata->base + TIMERn_CMD); |
|
writel_relaxed(ddata->periodic_top, ddata->base + TIMERn_TOP); |
|
writel_relaxed(TIMERn_CTRL_PRESC_1024 | |
|
TIMERn_CTRL_CLKSEL_PRESCHFPERCLK | |
|
TIMERn_CTRL_MODE_DOWN, |
|
ddata->base + TIMERn_CTRL); |
|
writel_relaxed(TIMERn_CMD_START, ddata->base + TIMERn_CMD); |
|
return 0; |
|
} |
|
|
|
static int efm32_clock_event_set_next_event(unsigned long evt, |
|
struct clock_event_device *evtdev) |
|
{ |
|
struct efm32_clock_event_ddata *ddata = |
|
container_of(evtdev, struct efm32_clock_event_ddata, evtdev); |
|
|
|
writel_relaxed(TIMERn_CMD_STOP, ddata->base + TIMERn_CMD); |
|
writel_relaxed(evt, ddata->base + TIMERn_CNT); |
|
writel_relaxed(TIMERn_CMD_START, ddata->base + TIMERn_CMD); |
|
|
|
return 0; |
|
} |
|
|
|
static irqreturn_t efm32_clock_event_handler(int irq, void *dev_id) |
|
{ |
|
struct efm32_clock_event_ddata *ddata = dev_id; |
|
|
|
writel_relaxed(TIMERn_IRQ_UF, ddata->base + TIMERn_IFC); |
|
|
|
ddata->evtdev.event_handler(&ddata->evtdev); |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
static struct efm32_clock_event_ddata clock_event_ddata = { |
|
.evtdev = { |
|
.name = "efm32 clockevent", |
|
.features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, |
|
.set_state_shutdown = efm32_clock_event_shutdown, |
|
.set_state_periodic = efm32_clock_event_set_periodic, |
|
.set_state_oneshot = efm32_clock_event_set_oneshot, |
|
.set_next_event = efm32_clock_event_set_next_event, |
|
.rating = 200, |
|
}, |
|
}; |
|
|
|
static int __init efm32_clocksource_init(struct device_node *np) |
|
{ |
|
struct clk *clk; |
|
void __iomem *base; |
|
unsigned long rate; |
|
int ret; |
|
|
|
clk = of_clk_get(np, 0); |
|
if (IS_ERR(clk)) { |
|
ret = PTR_ERR(clk); |
|
pr_err("failed to get clock for clocksource (%d)\n", ret); |
|
goto err_clk_get; |
|
} |
|
|
|
ret = clk_prepare_enable(clk); |
|
if (ret) { |
|
pr_err("failed to enable timer clock for clocksource (%d)\n", |
|
ret); |
|
goto err_clk_enable; |
|
} |
|
rate = clk_get_rate(clk); |
|
|
|
base = of_iomap(np, 0); |
|
if (!base) { |
|
ret = -EADDRNOTAVAIL; |
|
pr_err("failed to map registers for clocksource\n"); |
|
goto err_iomap; |
|
} |
|
|
|
writel_relaxed(TIMERn_CTRL_PRESC_1024 | |
|
TIMERn_CTRL_CLKSEL_PRESCHFPERCLK | |
|
TIMERn_CTRL_MODE_UP, base + TIMERn_CTRL); |
|
writel_relaxed(TIMERn_CMD_START, base + TIMERn_CMD); |
|
|
|
ret = clocksource_mmio_init(base + TIMERn_CNT, "efm32 timer", |
|
DIV_ROUND_CLOSEST(rate, 1024), 200, 16, |
|
clocksource_mmio_readl_up); |
|
if (ret) { |
|
pr_err("failed to init clocksource (%d)\n", ret); |
|
goto err_clocksource_init; |
|
} |
|
|
|
return 0; |
|
|
|
err_clocksource_init: |
|
|
|
iounmap(base); |
|
err_iomap: |
|
|
|
clk_disable_unprepare(clk); |
|
err_clk_enable: |
|
|
|
clk_put(clk); |
|
err_clk_get: |
|
|
|
return ret; |
|
} |
|
|
|
static int __init efm32_clockevent_init(struct device_node *np) |
|
{ |
|
struct clk *clk; |
|
void __iomem *base; |
|
unsigned long rate; |
|
int irq; |
|
int ret; |
|
|
|
clk = of_clk_get(np, 0); |
|
if (IS_ERR(clk)) { |
|
ret = PTR_ERR(clk); |
|
pr_err("failed to get clock for clockevent (%d)\n", ret); |
|
goto err_clk_get; |
|
} |
|
|
|
ret = clk_prepare_enable(clk); |
|
if (ret) { |
|
pr_err("failed to enable timer clock for clockevent (%d)\n", |
|
ret); |
|
goto err_clk_enable; |
|
} |
|
rate = clk_get_rate(clk); |
|
|
|
base = of_iomap(np, 0); |
|
if (!base) { |
|
ret = -EADDRNOTAVAIL; |
|
pr_err("failed to map registers for clockevent\n"); |
|
goto err_iomap; |
|
} |
|
|
|
irq = irq_of_parse_and_map(np, 0); |
|
if (!irq) { |
|
ret = -ENOENT; |
|
pr_err("failed to get irq for clockevent\n"); |
|
goto err_get_irq; |
|
} |
|
|
|
writel_relaxed(TIMERn_IRQ_UF, base + TIMERn_IEN); |
|
|
|
clock_event_ddata.base = base; |
|
clock_event_ddata.periodic_top = DIV_ROUND_CLOSEST(rate, 1024 * HZ); |
|
|
|
clockevents_config_and_register(&clock_event_ddata.evtdev, |
|
DIV_ROUND_CLOSEST(rate, 1024), |
|
0xf, 0xffff); |
|
|
|
ret = request_irq(irq, efm32_clock_event_handler, IRQF_TIMER, |
|
"efm32 clockevent", &clock_event_ddata); |
|
if (ret) { |
|
pr_err("Failed setup irq\n"); |
|
goto err_setup_irq; |
|
} |
|
|
|
return 0; |
|
|
|
err_setup_irq: |
|
err_get_irq: |
|
|
|
iounmap(base); |
|
err_iomap: |
|
|
|
clk_disable_unprepare(clk); |
|
err_clk_enable: |
|
|
|
clk_put(clk); |
|
err_clk_get: |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* This function asserts that we have exactly one clocksource and one |
|
* clock_event_device in the end. |
|
*/ |
|
static int __init efm32_timer_init(struct device_node *np) |
|
{ |
|
static int has_clocksource, has_clockevent; |
|
int ret = 0; |
|
|
|
if (!has_clocksource) { |
|
ret = efm32_clocksource_init(np); |
|
if (!ret) { |
|
has_clocksource = 1; |
|
return 0; |
|
} |
|
} |
|
|
|
if (!has_clockevent) { |
|
ret = efm32_clockevent_init(np); |
|
if (!ret) { |
|
has_clockevent = 1; |
|
return 0; |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
TIMER_OF_DECLARE(efm32compat, "efm32,timer", efm32_timer_init); |
|
TIMER_OF_DECLARE(efm32, "energymicro,efm32-timer", efm32_timer_init);
|
|
|