diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2022-06-02 15:27:44 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2022-06-02 15:27:44 -0700 |
commit | 09a018176ba246f00d6b6b526047d38dcd2955d3 (patch) | |
tree | 72179ffd954265eb70471e6416f915a9fd3a584d /drivers/clocksource/timer-gxp.c | |
parent | 96479c09803b21d195c95fd4b145cd3a5a591ba0 (diff) | |
parent | 9c76d723edd4f4d7d10b33e7e56f72ecbe6cee34 (diff) |
Merge tag 'arm-late-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
Pull more ARM SoC updates from Arnd Bergmann:
"This is the second part of the general SoC updates, containing
everything that did not make it in the initial pull request, or that
came in as a bugfix later.
- Devicetree updates for SoCFPGA, ASPEED, AT91 and Rockchip,
including a new machine using an ASPEED BMC.
- More DT fixes from Krzysztof Kozlowski across platforms
- A new SoC platform for the GXP baseboard management controller,
used in current server products from HPE"
* tag 'arm-late-5.19' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (56 commits)
ARM: configs: Enable more audio support for i.MX
tee: optee: Pass a pointer to virt_addr_valid()
arm64: dts: rockchip: rename Quartz64-A bluetooth gpios
arm64: dts: rockchip: add clocks property to cru node rk3368
arm64: dts: rockchip: add clocks property to cru node rk3308
arm64: dts: rockchip: add clocks to rk356x cru
ARM: dts: rockchip: add clocks property to cru node rk3228
ARM: dts: rockchip: add clocks property to cru node rk3036
ARM: dts: rockchip: add clocks property to cru node rk3066a/rk3188
ARM: dts: rockchip: add clocks property to cru node rk3288
ARM: dts: rockchip: Remove "amba" bus nodes from rv1108
ARM: dts: rockchip: add clocks property to cru node rv1108
arm64: dts: sprd: use new 'dma-channels' property
ARM: dts: da850: use new 'dma-channels' property
ARM: dts: pxa: use new 'dma-channels/requests' properties
soc: ixp4xx/qmgr: Fix unused match warning
ARM: ep93xx: Make ts72xx_register_flash() static
ARM: configs: enable support for Kontron KSwitch D10
ep93xx: clock: Do not return the address of the freed memory
arm64: dts: intel: add device tree for n6000
...
Diffstat (limited to 'drivers/clocksource/timer-gxp.c')
-rw-r--r-- | drivers/clocksource/timer-gxp.c | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/drivers/clocksource/timer-gxp.c b/drivers/clocksource/timer-gxp.c new file mode 100644 index 0000000000000..8b38b32123880 --- /dev/null +++ b/drivers/clocksource/timer-gxp.c @@ -0,0 +1,209 @@ +// 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/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; + + 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; + + return platform_device_add(gxp_watchdog_device); +} + +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); |