mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 04:38:03 +00:00
6303d0693f
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it was merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Link: https://lore.kernel.org/r/20230714174409.4053843-1-robh@kernel.org Signed-off-by: Rob Herring <robh@kernel.org>
216 lines
5.1 KiB
C
216 lines
5.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/clockchips.h>
|
|
#include <linux/clocksource.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sched_clock.h>
|
|
|
|
#define TIMER0_FREQ 1000000
|
|
#define GXP_TIMER_CNT_OFS 0x00
|
|
#define GXP_TIMESTAMP_OFS 0x08
|
|
#define GXP_TIMER_CTRL_OFS 0x14
|
|
|
|
/* TCS Stands for Timer Control/Status: these are masks to be used in */
|
|
/* the Timer Count Registers */
|
|
#define MASK_TCS_ENABLE 0x01
|
|
#define MASK_TCS_PERIOD 0x02
|
|
#define MASK_TCS_RELOAD 0x04
|
|
#define MASK_TCS_TC 0x80
|
|
|
|
struct gxp_timer {
|
|
void __iomem *counter;
|
|
void __iomem *control;
|
|
struct clock_event_device evt;
|
|
};
|
|
|
|
static struct gxp_timer *gxp_timer;
|
|
|
|
static void __iomem *system_clock __ro_after_init;
|
|
|
|
static inline struct gxp_timer *to_gxp_timer(struct clock_event_device *evt_dev)
|
|
{
|
|
return container_of(evt_dev, struct gxp_timer, evt);
|
|
}
|
|
|
|
static u64 notrace gxp_sched_read(void)
|
|
{
|
|
return readl_relaxed(system_clock);
|
|
}
|
|
|
|
static int gxp_time_set_next_event(unsigned long event, struct clock_event_device *evt_dev)
|
|
{
|
|
struct gxp_timer *timer = to_gxp_timer(evt_dev);
|
|
|
|
/* Stop counting and disable interrupt before updating */
|
|
writeb_relaxed(MASK_TCS_TC, timer->control);
|
|
writel_relaxed(event, timer->counter);
|
|
writeb_relaxed(MASK_TCS_TC | MASK_TCS_ENABLE, timer->control);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t gxp_timer_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct gxp_timer *timer = (struct gxp_timer *)dev_id;
|
|
|
|
if (!(readb_relaxed(timer->control) & MASK_TCS_TC))
|
|
return IRQ_NONE;
|
|
|
|
writeb_relaxed(MASK_TCS_TC, timer->control);
|
|
|
|
timer->evt.event_handler(&timer->evt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int __init gxp_timer_init(struct device_node *node)
|
|
{
|
|
void __iomem *base;
|
|
struct clk *clk;
|
|
u32 freq;
|
|
int ret, irq;
|
|
|
|
gxp_timer = kzalloc(sizeof(*gxp_timer), GFP_KERNEL);
|
|
if (!gxp_timer) {
|
|
ret = -ENOMEM;
|
|
pr_err("Can't allocate gxp_timer");
|
|
return ret;
|
|
}
|
|
|
|
clk = of_clk_get(node, 0);
|
|
if (IS_ERR(clk)) {
|
|
ret = (int)PTR_ERR(clk);
|
|
pr_err("%pOFn clock not found: %d\n", node, ret);
|
|
goto err_free;
|
|
}
|
|
|
|
ret = clk_prepare_enable(clk);
|
|
if (ret) {
|
|
pr_err("%pOFn clock enable failed: %d\n", node, ret);
|
|
goto err_clk_enable;
|
|
}
|
|
|
|
base = of_iomap(node, 0);
|
|
if (!base) {
|
|
ret = -ENXIO;
|
|
pr_err("Can't map timer base registers");
|
|
goto err_iomap;
|
|
}
|
|
|
|
/* Set the offsets to the clock register and timer registers */
|
|
gxp_timer->counter = base + GXP_TIMER_CNT_OFS;
|
|
gxp_timer->control = base + GXP_TIMER_CTRL_OFS;
|
|
system_clock = base + GXP_TIMESTAMP_OFS;
|
|
|
|
gxp_timer->evt.name = node->name;
|
|
gxp_timer->evt.rating = 300;
|
|
gxp_timer->evt.features = CLOCK_EVT_FEAT_ONESHOT;
|
|
gxp_timer->evt.set_next_event = gxp_time_set_next_event;
|
|
gxp_timer->evt.cpumask = cpumask_of(0);
|
|
|
|
irq = irq_of_parse_and_map(node, 0);
|
|
if (irq <= 0) {
|
|
ret = -EINVAL;
|
|
pr_err("GXP Timer Can't parse IRQ %d", irq);
|
|
goto err_exit;
|
|
}
|
|
|
|
freq = clk_get_rate(clk);
|
|
|
|
ret = clocksource_mmio_init(system_clock, node->name, freq,
|
|
300, 32, clocksource_mmio_readl_up);
|
|
if (ret) {
|
|
pr_err("%pOFn init clocksource failed: %d", node, ret);
|
|
goto err_exit;
|
|
}
|
|
|
|
sched_clock_register(gxp_sched_read, 32, freq);
|
|
|
|
irq = irq_of_parse_and_map(node, 0);
|
|
if (irq <= 0) {
|
|
ret = -EINVAL;
|
|
pr_err("%pOFn Can't parse IRQ %d", node, irq);
|
|
goto err_exit;
|
|
}
|
|
|
|
clockevents_config_and_register(&gxp_timer->evt, TIMER0_FREQ,
|
|
0xf, 0xffffffff);
|
|
|
|
ret = request_irq(irq, gxp_timer_interrupt, IRQF_TIMER | IRQF_SHARED,
|
|
node->name, gxp_timer);
|
|
if (ret) {
|
|
pr_err("%pOFn request_irq() failed: %d", node, ret);
|
|
goto err_exit;
|
|
}
|
|
|
|
pr_debug("gxp: system timer (irq = %d)\n", irq);
|
|
return 0;
|
|
|
|
err_exit:
|
|
iounmap(base);
|
|
err_iomap:
|
|
clk_disable_unprepare(clk);
|
|
err_clk_enable:
|
|
clk_put(clk);
|
|
err_free:
|
|
kfree(gxp_timer);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* This probe gets called after the timer is already up and running. This will create
|
|
* the watchdog device as a child since the registers are shared.
|
|
*/
|
|
|
|
static int gxp_timer_probe(struct platform_device *pdev)
|
|
{
|
|
struct platform_device *gxp_watchdog_device;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
|
|
if (!gxp_timer) {
|
|
pr_err("Gxp Timer not initialized, cannot create watchdog");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
gxp_watchdog_device = platform_device_alloc("gxp-wdt", -1);
|
|
if (!gxp_watchdog_device) {
|
|
pr_err("Timer failed to allocate gxp-wdt");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Pass the base address (counter) as platform data and nothing else */
|
|
gxp_watchdog_device->dev.platform_data = gxp_timer->counter;
|
|
gxp_watchdog_device->dev.parent = dev;
|
|
|
|
ret = platform_device_add(gxp_watchdog_device);
|
|
if (ret)
|
|
platform_device_put(gxp_watchdog_device);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id gxp_timer_of_match[] = {
|
|
{ .compatible = "hpe,gxp-timer", },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver gxp_timer_driver = {
|
|
.probe = gxp_timer_probe,
|
|
.driver = {
|
|
.name = "gxp-timer",
|
|
.of_match_table = gxp_timer_of_match,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
};
|
|
|
|
builtin_platform_driver(gxp_timer_driver);
|
|
|
|
TIMER_OF_DECLARE(gxp, "hpe,gxp-timer", gxp_timer_init);
|