forked from Minki/linux
- Add the missing DT bindings for the MTU nomadik timer (Linus
Walleij) - Fix grammar typo in the ARM global timer Kconfig option (Randy Dunlap) - Add the tegra186 timer and use it on the tegra234 board (Thierry Reding) - Add the 'CPUXGPT' CPU timer for Mediatek MT6795 and implement a workaround to overcome an ATF bug where the timer is not correctly initialized (AngeloGioacchino Del Regno) - Rework the suspend/resume approach to enable the feature on the timer even it is not an active clock and fix a compilation warning (Claudiu Beznea) - Add the Add R-Car Gen4 timer support along with the DT bindings (Wolfram Sang) - Add compatible for ti,am654-timer to support AM6 SoC (Tony Lindgren) - Fix Kconfig option to put it back to 'bool' instead of 'tristate' for the tegra186 (Daniel Lezcano) - Sort 'family,type' DT bindings for the Renesas timers (Geert Uytterhoeven) - Add compatible 'allwinner,sun20i-d1-timer' for Allwinner D1 (Samuel Holland) - Remove unnecessary (void*) conversions for sun4i (XU pengfei) - Remove unnecessary (void*) conversions for sun5i (Li zeming) -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEGn3N4YVz0WNVyHskqDIjiipP6E8FAmLhWF0ACgkQqDIjiipP 6E+ziAgArY8BJ/09w4rt63LDO5nynorr0+Uuf0E9hCmc0KzRsSF3hO39EcuLj1zD ubKKSXwaOucAcRYj7gCeqaSk/IOs9EmwESRVtLLIwEwYsg44iC1vmq4pJ8cX66qN h5TqkOiq77fPIY3BR/nSFxddmlU2farO8oSUqdLNsVrqoIq2rttaQYADdYGa4LYn dvQHEmh6jvJRoZM3aR6It0EWM7pyHKxAWFChWv+SbikheDCgIcugz+FUTy9lyhmf 39wLm2srmrCLbjaGdJnyc38/y/UPIJ0aqeSf7XSiHTWQ+dfNBfdbrmPTSJHiyIt+ iPrlx0ShvrMXeOxgbSxse53e+9Q88w== =ApcN -----END PGP SIGNATURE----- Merge tag 'timers-v5.20-rc1' of https://git.linaro.org/people/daniel.lezcano/linux into timers/core Pull clockevent/source updates from Daniel Lezcano: - Add the missing DT bindings for the MTU nomadik timer (Linus Walleij) - Fix grammar typo in the ARM global timer Kconfig option (Randy Dunlap) - Add the tegra186 timer and use it on the tegra234 board (Thierry Reding) - Add the 'CPUXGPT' CPU timer for Mediatek MT6795 and implement a workaround to overcome an ATF bug where the timer is not correctly initialized (AngeloGioacchino Del Regno) - Rework the suspend/resume approach to enable the feature on the timer even it is not an active clock and fix a compilation warning (Claudiu Beznea) - Add the Add R-Car Gen4 timer support along with the DT bindings (Wolfram Sang) - Add compatible for ti,am654-timer to support AM6 SoC (Tony Lindgren) - Fix Kconfig option to put it back to 'bool' instead of 'tristate' for the tegra186 (Daniel Lezcano) - Sort 'family,type' DT bindings for the Renesas timers (Geert Uytterhoeven) - Add compatible 'allwinner,sun20i-d1-timer' for Allwinner D1 (Samuel Holland) - Remove unnecessary (void*) conversions for sun4i (XU pengfei) - Remove unnecessary (void*) conversions for sun5i (Li zeming) Link: https://lore.kernel.org/all/7472984e-f502-5f27-82bf-070127dd85a5@linaro.org
This commit is contained in:
commit
75fed76ebc
@ -20,6 +20,7 @@ properties:
|
||||
- allwinner,suniv-f1c100s-timer
|
||||
- items:
|
||||
- enum:
|
||||
- allwinner,sun20i-d1-timer
|
||||
- allwinner,sun50i-a64-timer
|
||||
- allwinner,sun50i-h6-timer
|
||||
- allwinner,sun50i-h616-timer
|
||||
|
@ -113,7 +113,7 @@ properties:
|
||||
patternProperties:
|
||||
"^watchdog@[a-f0-9]+$":
|
||||
type: object
|
||||
$ref: ../watchdog/watchdog.yaml#
|
||||
$ref: /schemas/watchdog/watchdog.yaml#
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
@ -145,7 +145,7 @@ patternProperties:
|
||||
|
||||
"^pwm@[a-f0-9]+$":
|
||||
type: object
|
||||
$ref: ../pwm/pwm.yaml#
|
||||
$ref: /schemas/pwm/pwm.yaml#
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
|
@ -1,7 +1,8 @@
|
||||
MediaTek Timers
|
||||
---------------
|
||||
|
||||
MediaTek SoCs have two different timers on different platforms,
|
||||
MediaTek SoCs have different timers on different platforms,
|
||||
- CPUX (ARM/ARM64 System Timer)
|
||||
- GPT (General Purpose Timer)
|
||||
- SYST (System Timer)
|
||||
|
||||
@ -29,6 +30,9 @@ Required properties:
|
||||
* "mediatek,mt7629-timer" for MT7629 compatible timers (SYST)
|
||||
* "mediatek,mt6765-timer" for MT6765 and all above compatible timers (SYST)
|
||||
|
||||
For those SoCs that use CPUX
|
||||
* "mediatek,mt6795-systimer" for MT6795 compatible timers (CPUX)
|
||||
|
||||
- reg: Should contain location and length for timer register.
|
||||
- clocks: Should contain system clock.
|
||||
|
||||
|
@ -80,7 +80,6 @@ properties:
|
||||
- renesas,r8a77980-cmt0 # 32-bit CMT0 on R-Car V3H
|
||||
- renesas,r8a77990-cmt0 # 32-bit CMT0 on R-Car E3
|
||||
- renesas,r8a77995-cmt0 # 32-bit CMT0 on R-Car D3
|
||||
- renesas,r8a779a0-cmt0 # 32-bit CMT0 on R-Car V3U
|
||||
- const: renesas,rcar-gen3-cmt0 # 32-bit CMT0 on R-Car Gen3 and RZ/G2
|
||||
|
||||
- items:
|
||||
@ -97,9 +96,20 @@ properties:
|
||||
- renesas,r8a77980-cmt1 # 48-bit CMT on R-Car V3H
|
||||
- renesas,r8a77990-cmt1 # 48-bit CMT on R-Car E3
|
||||
- renesas,r8a77995-cmt1 # 48-bit CMT on R-Car D3
|
||||
- renesas,r8a779a0-cmt1 # 48-bit CMT on R-Car V3U
|
||||
- const: renesas,rcar-gen3-cmt1 # 48-bit CMT on R-Car Gen3 and RZ/G2
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,r8a779a0-cmt0 # 32-bit CMT0 on R-Car V3U
|
||||
- renesas,r8a779f0-cmt0 # 32-bit CMT0 on R-Car S4-8
|
||||
- const: renesas,rcar-gen4-cmt0 # 32-bit CMT0 on R-Car Gen4
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- renesas,r8a779a0-cmt1 # 48-bit CMT on R-Car V3U
|
||||
- renesas,r8a779f0-cmt1 # 48-bit CMT on R-Car S4-8
|
||||
- const: renesas,rcar-gen4-cmt1 # 48-bit CMT on R-Car Gen4
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
@ -135,6 +145,7 @@ allOf:
|
||||
enum:
|
||||
- renesas,rcar-gen2-cmt0
|
||||
- renesas,rcar-gen3-cmt0
|
||||
- renesas,rcar-gen4-cmt0
|
||||
then:
|
||||
properties:
|
||||
interrupts:
|
||||
@ -148,6 +159,7 @@ allOf:
|
||||
enum:
|
||||
- renesas,rcar-gen2-cmt1
|
||||
- renesas,rcar-gen3-cmt1
|
||||
- renesas,rcar-gen4-cmt1
|
||||
then:
|
||||
properties:
|
||||
interrupts:
|
||||
|
58
Documentation/devicetree/bindings/timer/st,nomadik-mtu.yaml
Normal file
58
Documentation/devicetree/bindings/timer/st,nomadik-mtu.yaml
Normal file
@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
# Copyright 2022 Linaro Ltd.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: "http://devicetree.org/schemas/timer/st,nomadik-mtu.yaml#"
|
||||
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
|
||||
|
||||
title: ST Microelectronics Nomadik Multi-Timer Unit MTU Timer
|
||||
|
||||
maintainers:
|
||||
- Linus Walleij <linus.walleij@linaro.org>
|
||||
|
||||
description: This timer is found in the ST Microelectronics Nomadik
|
||||
SoCs STn8800, STn8810 and STn8815 as well as in ST-Ericsson DB8500.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- const: st,nomadik-mtu
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
description: The first clock named TIMCLK clocks the actual timers and
|
||||
the second clock clocks the digital interface to the interconnect.
|
||||
maxItems: 2
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: timclk
|
||||
- const: apb_pclk
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/mfd/dbx500-prcmu.h>
|
||||
timer@a03c6000 {
|
||||
compatible = "st,nomadik-mtu";
|
||||
reg = <0xa03c6000 0x1000>;
|
||||
interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
|
||||
|
||||
clocks = <&prcmu_clk PRCMU_TIMCLK>, <&prcc_pclk 6 6>;
|
||||
clock-names = "timclk", "apb_pclk";
|
||||
};
|
@ -105,6 +105,7 @@ config ARCH_OMAP2PLUS
|
||||
select MACH_OMAP_GENERIC
|
||||
select MEMORY
|
||||
select MFD_SYSCON
|
||||
select OMAP_DM_SYSTIMER
|
||||
select OMAP_DM_TIMER
|
||||
select OMAP_GPMC
|
||||
select PINCTRL
|
||||
@ -209,6 +210,7 @@ config SOC_OMAP2420
|
||||
bool "OMAP2420 support"
|
||||
depends on ARCH_OMAP2
|
||||
default y
|
||||
select OMAP_DM_SYSTIMER
|
||||
select OMAP_DM_TIMER
|
||||
select SOC_HAS_OMAP2_SDRC
|
||||
|
||||
|
@ -22,7 +22,7 @@ config CLKEVT_I8253
|
||||
config I8253_LOCK
|
||||
bool
|
||||
|
||||
config OMAP_DM_TIMER
|
||||
config OMAP_DM_SYSTIMER
|
||||
bool
|
||||
select TIMER_OF
|
||||
|
||||
@ -56,6 +56,13 @@ config DIGICOLOR_TIMER
|
||||
help
|
||||
Enables the support for the digicolor timer driver.
|
||||
|
||||
config OMAP_DM_TIMER
|
||||
bool "OMAP dual-mode timer driver" if ARCH_K3 || COMPILE_TEST
|
||||
default y if ARCH_K3
|
||||
select TIMER_OF
|
||||
help
|
||||
Enables the support for the TI dual-mode timer driver.
|
||||
|
||||
config DW_APB_TIMER
|
||||
bool "DW APB timer driver" if COMPILE_TEST
|
||||
help
|
||||
@ -150,6 +157,14 @@ config TEGRA_TIMER
|
||||
help
|
||||
Enables support for the Tegra driver.
|
||||
|
||||
config TEGRA186_TIMER
|
||||
bool "NVIDIA Tegra186 timer driver"
|
||||
depends on ARCH_TEGRA || COMPILE_TEST
|
||||
depends on WATCHDOG && WATCHDOG_CORE
|
||||
help
|
||||
Enables support for the timers and watchdogs found on NVIDIA
|
||||
Tegra186 and later SoCs.
|
||||
|
||||
config VT8500_TIMER
|
||||
bool "VT8500 timer driver" if COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
@ -367,7 +382,7 @@ config ARM_GT_INITIAL_PRESCALER_VAL
|
||||
depends on ARM_GLOBAL_TIMER
|
||||
help
|
||||
When the ARM global timer initializes, its current rate is declared
|
||||
to the kernel and maintained forever. Should it's parent clock
|
||||
to the kernel and maintained forever. Should its parent clock
|
||||
change, the driver tries to fix the timer's internal prescaler.
|
||||
On some machs (i.e. Zynq) the initial prescaler value thus poses
|
||||
bounds about how much the parent clock is allowed to decrease or
|
||||
|
@ -18,7 +18,7 @@ obj-$(CONFIG_CLKSRC_MMIO) += mmio.o
|
||||
obj-$(CONFIG_DAVINCI_TIMER) += timer-davinci.o
|
||||
obj-$(CONFIG_DIGICOLOR_TIMER) += timer-digicolor.o
|
||||
obj-$(CONFIG_OMAP_DM_TIMER) += timer-ti-dm.o
|
||||
obj-$(CONFIG_OMAP_DM_TIMER) += timer-ti-dm-systimer.o
|
||||
obj-$(CONFIG_OMAP_DM_SYSTIMER) += timer-ti-dm-systimer.o
|
||||
obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o
|
||||
obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o
|
||||
obj-$(CONFIG_FTTMR010_TIMER) += timer-fttmr010.o
|
||||
@ -36,6 +36,7 @@ obj-$(CONFIG_SUN4I_TIMER) += timer-sun4i.o
|
||||
obj-$(CONFIG_SUN5I_HSTIMER) += timer-sun5i.o
|
||||
obj-$(CONFIG_MESON6_TIMER) += timer-meson6.o
|
||||
obj-$(CONFIG_TEGRA_TIMER) += timer-tegra.o
|
||||
obj-$(CONFIG_TEGRA186_TIMER) += timer-tegra186.o
|
||||
obj-$(CONFIG_VT8500_TIMER) += timer-vt8500.o
|
||||
obj-$(CONFIG_NSPIRE_TIMER) += timer-zevio.o
|
||||
obj-$(CONFIG_BCM_KONA_TIMER) += bcm_kona_timer.o
|
||||
|
@ -981,6 +981,14 @@ static const struct of_device_id sh_cmt_of_table[] __maybe_unused = {
|
||||
.compatible = "renesas,rcar-gen3-cmt1",
|
||||
.data = &sh_cmt_info[SH_CMT1_RCAR_GEN2]
|
||||
},
|
||||
{
|
||||
.compatible = "renesas,rcar-gen4-cmt0",
|
||||
.data = &sh_cmt_info[SH_CMT0_RCAR_GEN2]
|
||||
},
|
||||
{
|
||||
.compatible = "renesas,rcar-gen4-cmt1",
|
||||
.data = &sh_cmt_info[SH_CMT1_RCAR_GEN2]
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sh_cmt_of_table);
|
||||
|
@ -22,6 +22,19 @@
|
||||
|
||||
#define TIMER_SYNC_TICKS (3)
|
||||
|
||||
/* cpux mcusys wrapper */
|
||||
#define CPUX_CON_REG 0x0
|
||||
#define CPUX_IDX_REG 0x4
|
||||
|
||||
/* cpux */
|
||||
#define CPUX_IDX_GLOBAL_CTRL 0x0
|
||||
#define CPUX_ENABLE BIT(0)
|
||||
#define CPUX_CLK_DIV_MASK GENMASK(10, 8)
|
||||
#define CPUX_CLK_DIV1 BIT(8)
|
||||
#define CPUX_CLK_DIV2 BIT(9)
|
||||
#define CPUX_CLK_DIV4 BIT(10)
|
||||
#define CPUX_IDX_GLOBAL_IRQ 0x30
|
||||
|
||||
/* gpt */
|
||||
#define GPT_IRQ_EN_REG 0x00
|
||||
#define GPT_IRQ_ENABLE(val) BIT((val) - 1)
|
||||
@ -72,6 +85,52 @@
|
||||
|
||||
static void __iomem *gpt_sched_reg __read_mostly;
|
||||
|
||||
static u32 mtk_cpux_readl(u32 reg_idx, struct timer_of *to)
|
||||
{
|
||||
writel(reg_idx, timer_of_base(to) + CPUX_IDX_REG);
|
||||
return readl(timer_of_base(to) + CPUX_CON_REG);
|
||||
}
|
||||
|
||||
static void mtk_cpux_writel(u32 val, u32 reg_idx, struct timer_of *to)
|
||||
{
|
||||
writel(reg_idx, timer_of_base(to) + CPUX_IDX_REG);
|
||||
writel(val, timer_of_base(to) + CPUX_CON_REG);
|
||||
}
|
||||
|
||||
static void mtk_cpux_set_irq(struct timer_of *to, bool enable)
|
||||
{
|
||||
const unsigned long *irq_mask = cpumask_bits(cpu_possible_mask);
|
||||
u32 val;
|
||||
|
||||
val = mtk_cpux_readl(CPUX_IDX_GLOBAL_IRQ, to);
|
||||
|
||||
if (enable)
|
||||
val |= *irq_mask;
|
||||
else
|
||||
val &= ~(*irq_mask);
|
||||
|
||||
mtk_cpux_writel(val, CPUX_IDX_GLOBAL_IRQ, to);
|
||||
}
|
||||
|
||||
static int mtk_cpux_clkevt_shutdown(struct clock_event_device *clkevt)
|
||||
{
|
||||
/* Clear any irq */
|
||||
mtk_cpux_set_irq(to_timer_of(clkevt), false);
|
||||
|
||||
/*
|
||||
* Disabling CPUXGPT timer will crash the platform, especially
|
||||
* if Trusted Firmware is using it (usually, for sleep states),
|
||||
* so we only mask the IRQ and call it a day.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_cpux_clkevt_resume(struct clock_event_device *clkevt)
|
||||
{
|
||||
mtk_cpux_set_irq(to_timer_of(clkevt), true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_syst_ack_irq(struct timer_of *to)
|
||||
{
|
||||
/* Clear and disable interrupt */
|
||||
@ -281,6 +340,60 @@ static struct timer_of to = {
|
||||
},
|
||||
};
|
||||
|
||||
static int __init mtk_cpux_init(struct device_node *node)
|
||||
{
|
||||
static struct timer_of to_cpux;
|
||||
u32 freq, val;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* There are per-cpu interrupts for the CPUX General Purpose Timer
|
||||
* but since this timer feeds the AArch64 System Timer we can rely
|
||||
* on the CPU timer PPIs as well, so we don't declare TIMER_OF_IRQ.
|
||||
*/
|
||||
to_cpux.flags = TIMER_OF_BASE | TIMER_OF_CLOCK;
|
||||
to_cpux.clkevt.name = "mtk-cpuxgpt";
|
||||
to_cpux.clkevt.rating = 10;
|
||||
to_cpux.clkevt.cpumask = cpu_possible_mask;
|
||||
to_cpux.clkevt.set_state_shutdown = mtk_cpux_clkevt_shutdown;
|
||||
to_cpux.clkevt.tick_resume = mtk_cpux_clkevt_resume;
|
||||
|
||||
/* If this fails, bad things are about to happen... */
|
||||
ret = timer_of_init(node, &to_cpux);
|
||||
if (ret) {
|
||||
WARN(1, "Cannot start CPUX timers.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if we're given a clock with the right frequency for this
|
||||
* timer, otherwise warn but keep going with the setup anyway, as
|
||||
* that makes it possible to still boot the kernel, even though
|
||||
* it may not work correctly (random lockups, etc).
|
||||
* The reason behind this is that having an early UART may not be
|
||||
* possible for everyone and this gives a chance to retrieve kmsg
|
||||
* for eventual debugging even on consumer devices.
|
||||
*/
|
||||
freq = timer_of_rate(&to_cpux);
|
||||
if (freq > 13000000)
|
||||
WARN(1, "Requested unsupported timer frequency %u\n", freq);
|
||||
|
||||
/* Clock input is 26MHz, set DIV2 to achieve 13MHz clock */
|
||||
val = mtk_cpux_readl(CPUX_IDX_GLOBAL_CTRL, &to_cpux);
|
||||
val &= ~CPUX_CLK_DIV_MASK;
|
||||
val |= CPUX_CLK_DIV2;
|
||||
mtk_cpux_writel(val, CPUX_IDX_GLOBAL_CTRL, &to_cpux);
|
||||
|
||||
/* Enable all CPUXGPT timers */
|
||||
val = mtk_cpux_readl(CPUX_IDX_GLOBAL_CTRL, &to_cpux);
|
||||
mtk_cpux_writel(val | CPUX_ENABLE, CPUX_IDX_GLOBAL_CTRL, &to_cpux);
|
||||
|
||||
clockevents_config_and_register(&to_cpux.clkevt, timer_of_rate(&to_cpux),
|
||||
TIMER_SYNC_TICKS, 0xffffffff);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init mtk_syst_init(struct device_node *node)
|
||||
{
|
||||
int ret;
|
||||
@ -339,3 +452,4 @@ static int __init mtk_gpt_init(struct device_node *node)
|
||||
}
|
||||
TIMER_OF_DECLARE(mtk_mt6577, "mediatek,mt6577-timer", mtk_gpt_init);
|
||||
TIMER_OF_DECLARE(mtk_mt6765, "mediatek,mt6765-timer", mtk_syst_init);
|
||||
TIMER_OF_DECLARE(mtk_mt6795, "mediatek,mt6795-systimer", mtk_cpux_init);
|
||||
|
@ -61,7 +61,7 @@ struct mchp_pit64b_timer {
|
||||
};
|
||||
|
||||
/**
|
||||
* mchp_pit64b_clkevt - PIT64B clockevent data structure
|
||||
* struct mchp_pit64b_clkevt - PIT64B clockevent data structure
|
||||
* @timer: PIT64B timer
|
||||
* @clkevt: clockevent
|
||||
*/
|
||||
@ -75,7 +75,7 @@ struct mchp_pit64b_clkevt {
|
||||
struct mchp_pit64b_clkevt, clkevt))
|
||||
|
||||
/**
|
||||
* mchp_pit64b_clksrc - PIT64B clocksource data structure
|
||||
* struct mchp_pit64b_clksrc - PIT64B clocksource data structure
|
||||
* @timer: PIT64B timer
|
||||
* @clksrc: clocksource
|
||||
*/
|
||||
@ -173,7 +173,8 @@ static int mchp_pit64b_clkevt_shutdown(struct clock_event_device *cedev)
|
||||
{
|
||||
struct mchp_pit64b_timer *timer = clkevt_to_mchp_pit64b_timer(cedev);
|
||||
|
||||
writel_relaxed(MCHP_PIT64B_CR_SWRST, timer->base + MCHP_PIT64B_CR);
|
||||
if (!clockevent_state_detached(cedev))
|
||||
mchp_pit64b_suspend(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -182,12 +183,28 @@ static int mchp_pit64b_clkevt_set_periodic(struct clock_event_device *cedev)
|
||||
{
|
||||
struct mchp_pit64b_timer *timer = clkevt_to_mchp_pit64b_timer(cedev);
|
||||
|
||||
if (clockevent_state_shutdown(cedev))
|
||||
mchp_pit64b_resume(timer);
|
||||
|
||||
mchp_pit64b_reset(timer, mchp_pit64b_ce_cycles, MCHP_PIT64B_MR_CONT,
|
||||
MCHP_PIT64B_IER_PERIOD);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mchp_pit64b_clkevt_set_oneshot(struct clock_event_device *cedev)
|
||||
{
|
||||
struct mchp_pit64b_timer *timer = clkevt_to_mchp_pit64b_timer(cedev);
|
||||
|
||||
if (clockevent_state_shutdown(cedev))
|
||||
mchp_pit64b_resume(timer);
|
||||
|
||||
mchp_pit64b_reset(timer, mchp_pit64b_ce_cycles, MCHP_PIT64B_MR_ONE_SHOT,
|
||||
MCHP_PIT64B_IER_PERIOD);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mchp_pit64b_clkevt_set_next_event(unsigned long evt,
|
||||
struct clock_event_device *cedev)
|
||||
{
|
||||
@ -199,20 +216,6 @@ static int mchp_pit64b_clkevt_set_next_event(unsigned long evt,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mchp_pit64b_clkevt_suspend(struct clock_event_device *cedev)
|
||||
{
|
||||
struct mchp_pit64b_timer *timer = clkevt_to_mchp_pit64b_timer(cedev);
|
||||
|
||||
mchp_pit64b_suspend(timer);
|
||||
}
|
||||
|
||||
static void mchp_pit64b_clkevt_resume(struct clock_event_device *cedev)
|
||||
{
|
||||
struct mchp_pit64b_timer *timer = clkevt_to_mchp_pit64b_timer(cedev);
|
||||
|
||||
mchp_pit64b_resume(timer);
|
||||
}
|
||||
|
||||
static irqreturn_t mchp_pit64b_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct mchp_pit64b_clkevt *irq_data = dev_id;
|
||||
@ -242,8 +245,10 @@ static void __init mchp_pit64b_pres_compute(u32 *pres, u32 clk_rate,
|
||||
}
|
||||
|
||||
/**
|
||||
* mchp_pit64b_init_mode - prepare PIT64B mode register value to be used at
|
||||
* runtime; this includes prescaler and SGCLK bit
|
||||
* mchp_pit64b_init_mode() - prepare PIT64B mode register value to be used at
|
||||
* runtime; this includes prescaler and SGCLK bit
|
||||
* @timer: pointer to pit64b timer to init
|
||||
* @max_rate: maximum rate that timer's clock could use
|
||||
*
|
||||
* PIT64B timer may be fed by gclk or pclk. When gclk is used its rate has to
|
||||
* be at least 3 times lower that pclk's rate. pclk rate is fixed, gclk rate
|
||||
@ -341,6 +346,7 @@ static int __init mchp_pit64b_init_clksrc(struct mchp_pit64b_timer *timer,
|
||||
if (!cs)
|
||||
return -ENOMEM;
|
||||
|
||||
mchp_pit64b_resume(timer);
|
||||
mchp_pit64b_reset(timer, ULLONG_MAX, MCHP_PIT64B_MR_CONT, 0);
|
||||
|
||||
mchp_pit64b_cs_base = timer->base;
|
||||
@ -362,8 +368,7 @@ static int __init mchp_pit64b_init_clksrc(struct mchp_pit64b_timer *timer,
|
||||
pr_debug("clksrc: Failed to register PIT64B clocksource!\n");
|
||||
|
||||
/* Stop timer. */
|
||||
writel_relaxed(MCHP_PIT64B_CR_SWRST,
|
||||
timer->base + MCHP_PIT64B_CR);
|
||||
mchp_pit64b_suspend(timer);
|
||||
kfree(cs);
|
||||
|
||||
return ret;
|
||||
@ -395,9 +400,8 @@ static int __init mchp_pit64b_init_clkevt(struct mchp_pit64b_timer *timer,
|
||||
ce->clkevt.rating = 150;
|
||||
ce->clkevt.set_state_shutdown = mchp_pit64b_clkevt_shutdown;
|
||||
ce->clkevt.set_state_periodic = mchp_pit64b_clkevt_set_periodic;
|
||||
ce->clkevt.set_state_oneshot = mchp_pit64b_clkevt_set_oneshot;
|
||||
ce->clkevt.set_next_event = mchp_pit64b_clkevt_set_next_event;
|
||||
ce->clkevt.suspend = mchp_pit64b_clkevt_suspend;
|
||||
ce->clkevt.resume = mchp_pit64b_clkevt_resume;
|
||||
ce->clkevt.cpumask = cpumask_of(0);
|
||||
ce->clkevt.irq = irq;
|
||||
|
||||
@ -448,19 +452,10 @@ static int __init mchp_pit64b_dt_init_timer(struct device_node *node,
|
||||
if (ret)
|
||||
goto irq_unmap;
|
||||
|
||||
ret = clk_prepare_enable(timer.pclk);
|
||||
if (ret)
|
||||
goto irq_unmap;
|
||||
|
||||
if (timer.mode & MCHP_PIT64B_MR_SGCLK) {
|
||||
ret = clk_prepare_enable(timer.gclk);
|
||||
if (ret)
|
||||
goto pclk_unprepare;
|
||||
|
||||
if (timer.mode & MCHP_PIT64B_MR_SGCLK)
|
||||
clk_rate = clk_get_rate(timer.gclk);
|
||||
} else {
|
||||
else
|
||||
clk_rate = clk_get_rate(timer.pclk);
|
||||
}
|
||||
clk_rate = clk_rate / (MCHP_PIT64B_MODE_TO_PRES(timer.mode) + 1);
|
||||
|
||||
if (clkevt)
|
||||
@ -469,15 +464,10 @@ static int __init mchp_pit64b_dt_init_timer(struct device_node *node,
|
||||
ret = mchp_pit64b_init_clksrc(&timer, clk_rate);
|
||||
|
||||
if (ret)
|
||||
goto gclk_unprepare;
|
||||
goto irq_unmap;
|
||||
|
||||
return 0;
|
||||
|
||||
gclk_unprepare:
|
||||
if (timer.mode & MCHP_PIT64B_MR_SGCLK)
|
||||
clk_disable_unprepare(timer.gclk);
|
||||
pclk_unprepare:
|
||||
clk_disable_unprepare(timer.pclk);
|
||||
irq_unmap:
|
||||
irq_dispose_mapping(irq);
|
||||
io_unmap:
|
||||
|
@ -128,7 +128,7 @@ static void sun4i_timer_clear_interrupt(void __iomem *base)
|
||||
|
||||
static irqreturn_t sun4i_timer_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct clock_event_device *evt = (struct clock_event_device *)dev_id;
|
||||
struct clock_event_device *evt = dev_id;
|
||||
struct timer_of *to = to_timer_of(evt);
|
||||
|
||||
sun4i_timer_clear_interrupt(timer_of_base(to));
|
||||
|
@ -142,7 +142,7 @@ static int sun5i_clkevt_next_event(unsigned long evt,
|
||||
|
||||
static irqreturn_t sun5i_timer_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct sun5i_timer_clkevt *ce = (struct sun5i_timer_clkevt *)dev_id;
|
||||
struct sun5i_timer_clkevt *ce = dev_id;
|
||||
|
||||
writel(0x1, ce->timer.base + TIMER_IRQ_ST_REG);
|
||||
ce->clkevt.event_handler(&ce->clkevt);
|
||||
|
514
drivers/clocksource/timer-tegra186.c
Normal file
514
drivers/clocksource/timer-tegra186.c
Normal file
@ -0,0 +1,514 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2019-2020 NVIDIA Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
/* shared registers */
|
||||
#define TKETSC0 0x000
|
||||
#define TKETSC1 0x004
|
||||
#define TKEUSEC 0x008
|
||||
#define TKEOSC 0x00c
|
||||
|
||||
#define TKEIE(x) (0x100 + ((x) * 4))
|
||||
#define TKEIE_WDT_MASK(x, y) ((y) << (16 + 4 * (x)))
|
||||
|
||||
/* timer registers */
|
||||
#define TMRCR 0x000
|
||||
#define TMRCR_ENABLE BIT(31)
|
||||
#define TMRCR_PERIODIC BIT(30)
|
||||
#define TMRCR_PTV(x) ((x) & 0x0fffffff)
|
||||
|
||||
#define TMRSR 0x004
|
||||
#define TMRSR_INTR_CLR BIT(30)
|
||||
|
||||
#define TMRCSSR 0x008
|
||||
#define TMRCSSR_SRC_USEC (0 << 0)
|
||||
|
||||
/* watchdog registers */
|
||||
#define WDTCR 0x000
|
||||
#define WDTCR_SYSTEM_POR_RESET_ENABLE BIT(16)
|
||||
#define WDTCR_SYSTEM_DEBUG_RESET_ENABLE BIT(15)
|
||||
#define WDTCR_REMOTE_INT_ENABLE BIT(14)
|
||||
#define WDTCR_LOCAL_FIQ_ENABLE BIT(13)
|
||||
#define WDTCR_LOCAL_INT_ENABLE BIT(12)
|
||||
#define WDTCR_PERIOD_MASK (0xff << 4)
|
||||
#define WDTCR_PERIOD(x) (((x) & 0xff) << 4)
|
||||
#define WDTCR_TIMER_SOURCE_MASK 0xf
|
||||
#define WDTCR_TIMER_SOURCE(x) ((x) & 0xf)
|
||||
|
||||
#define WDTCMDR 0x008
|
||||
#define WDTCMDR_DISABLE_COUNTER BIT(1)
|
||||
#define WDTCMDR_START_COUNTER BIT(0)
|
||||
|
||||
#define WDTUR 0x00c
|
||||
#define WDTUR_UNLOCK_PATTERN 0x0000c45a
|
||||
|
||||
struct tegra186_timer_soc {
|
||||
unsigned int num_timers;
|
||||
unsigned int num_wdts;
|
||||
};
|
||||
|
||||
struct tegra186_tmr {
|
||||
struct tegra186_timer *parent;
|
||||
void __iomem *regs;
|
||||
unsigned int index;
|
||||
unsigned int hwirq;
|
||||
};
|
||||
|
||||
struct tegra186_wdt {
|
||||
struct watchdog_device base;
|
||||
|
||||
void __iomem *regs;
|
||||
unsigned int index;
|
||||
bool locked;
|
||||
|
||||
struct tegra186_tmr *tmr;
|
||||
};
|
||||
|
||||
static inline struct tegra186_wdt *to_tegra186_wdt(struct watchdog_device *wdd)
|
||||
{
|
||||
return container_of(wdd, struct tegra186_wdt, base);
|
||||
}
|
||||
|
||||
struct tegra186_timer {
|
||||
const struct tegra186_timer_soc *soc;
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
|
||||
struct tegra186_wdt *wdt;
|
||||
struct clocksource usec;
|
||||
struct clocksource tsc;
|
||||
struct clocksource osc;
|
||||
};
|
||||
|
||||
static void tmr_writel(struct tegra186_tmr *tmr, u32 value, unsigned int offset)
|
||||
{
|
||||
writel_relaxed(value, tmr->regs + offset);
|
||||
}
|
||||
|
||||
static void wdt_writel(struct tegra186_wdt *wdt, u32 value, unsigned int offset)
|
||||
{
|
||||
writel_relaxed(value, wdt->regs + offset);
|
||||
}
|
||||
|
||||
static u32 wdt_readl(struct tegra186_wdt *wdt, unsigned int offset)
|
||||
{
|
||||
return readl_relaxed(wdt->regs + offset);
|
||||
}
|
||||
|
||||
static struct tegra186_tmr *tegra186_tmr_create(struct tegra186_timer *tegra,
|
||||
unsigned int index)
|
||||
{
|
||||
unsigned int offset = 0x10000 + index * 0x10000;
|
||||
struct tegra186_tmr *tmr;
|
||||
|
||||
tmr = devm_kzalloc(tegra->dev, sizeof(*tmr), GFP_KERNEL);
|
||||
if (!tmr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
tmr->parent = tegra;
|
||||
tmr->regs = tegra->regs + offset;
|
||||
tmr->index = index;
|
||||
tmr->hwirq = 0;
|
||||
|
||||
return tmr;
|
||||
}
|
||||
|
||||
static const struct watchdog_info tegra186_wdt_info = {
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
|
||||
.identity = "NVIDIA Tegra186 WDT",
|
||||
};
|
||||
|
||||
static void tegra186_wdt_disable(struct tegra186_wdt *wdt)
|
||||
{
|
||||
/* unlock and disable the watchdog */
|
||||
wdt_writel(wdt, WDTUR_UNLOCK_PATTERN, WDTUR);
|
||||
wdt_writel(wdt, WDTCMDR_DISABLE_COUNTER, WDTCMDR);
|
||||
|
||||
/* disable timer */
|
||||
tmr_writel(wdt->tmr, 0, TMRCR);
|
||||
}
|
||||
|
||||
static void tegra186_wdt_enable(struct tegra186_wdt *wdt)
|
||||
{
|
||||
struct tegra186_timer *tegra = wdt->tmr->parent;
|
||||
u32 value;
|
||||
|
||||
/* unmask hardware IRQ, this may have been lost across powergate */
|
||||
value = TKEIE_WDT_MASK(wdt->index, 1);
|
||||
writel(value, tegra->regs + TKEIE(wdt->tmr->hwirq));
|
||||
|
||||
/* clear interrupt */
|
||||
tmr_writel(wdt->tmr, TMRSR_INTR_CLR, TMRSR);
|
||||
|
||||
/* select microsecond source */
|
||||
tmr_writel(wdt->tmr, TMRCSSR_SRC_USEC, TMRCSSR);
|
||||
|
||||
/* configure timer (system reset happens on the fifth expiration) */
|
||||
value = TMRCR_PTV(wdt->base.timeout * USEC_PER_SEC / 5) |
|
||||
TMRCR_PERIODIC | TMRCR_ENABLE;
|
||||
tmr_writel(wdt->tmr, value, TMRCR);
|
||||
|
||||
if (!wdt->locked) {
|
||||
value = wdt_readl(wdt, WDTCR);
|
||||
|
||||
/* select the proper timer source */
|
||||
value &= ~WDTCR_TIMER_SOURCE_MASK;
|
||||
value |= WDTCR_TIMER_SOURCE(wdt->tmr->index);
|
||||
|
||||
/* single timer period since that's already configured */
|
||||
value &= ~WDTCR_PERIOD_MASK;
|
||||
value |= WDTCR_PERIOD(1);
|
||||
|
||||
/* enable local interrupt for WDT petting */
|
||||
value |= WDTCR_LOCAL_INT_ENABLE;
|
||||
|
||||
/* enable local FIQ and remote interrupt for debug dump */
|
||||
if (0)
|
||||
value |= WDTCR_REMOTE_INT_ENABLE |
|
||||
WDTCR_LOCAL_FIQ_ENABLE;
|
||||
|
||||
/* enable system debug reset (doesn't properly reboot) */
|
||||
if (0)
|
||||
value |= WDTCR_SYSTEM_DEBUG_RESET_ENABLE;
|
||||
|
||||
/* enable system POR reset */
|
||||
value |= WDTCR_SYSTEM_POR_RESET_ENABLE;
|
||||
|
||||
wdt_writel(wdt, value, WDTCR);
|
||||
}
|
||||
|
||||
wdt_writel(wdt, WDTCMDR_START_COUNTER, WDTCMDR);
|
||||
}
|
||||
|
||||
static int tegra186_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd);
|
||||
|
||||
tegra186_wdt_enable(wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra186_wdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd);
|
||||
|
||||
tegra186_wdt_disable(wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra186_wdt_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd);
|
||||
|
||||
tegra186_wdt_disable(wdt);
|
||||
tegra186_wdt_enable(wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra186_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd);
|
||||
|
||||
if (watchdog_active(&wdt->base))
|
||||
tegra186_wdt_disable(wdt);
|
||||
|
||||
wdt->base.timeout = timeout;
|
||||
|
||||
if (watchdog_active(&wdt->base))
|
||||
tegra186_wdt_enable(wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct watchdog_ops tegra186_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = tegra186_wdt_start,
|
||||
.stop = tegra186_wdt_stop,
|
||||
.ping = tegra186_wdt_ping,
|
||||
.set_timeout = tegra186_wdt_set_timeout,
|
||||
};
|
||||
|
||||
static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,
|
||||
unsigned int index)
|
||||
{
|
||||
unsigned int offset = 0x10000, source;
|
||||
struct tegra186_wdt *wdt;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
offset += tegra->soc->num_timers * 0x10000 + index * 0x10000;
|
||||
|
||||
wdt = devm_kzalloc(tegra->dev, sizeof(*wdt), GFP_KERNEL);
|
||||
if (!wdt)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
wdt->regs = tegra->regs + offset;
|
||||
wdt->index = index;
|
||||
|
||||
/* read the watchdog configuration since it might be locked down */
|
||||
value = wdt_readl(wdt, WDTCR);
|
||||
|
||||
if (value & WDTCR_LOCAL_INT_ENABLE)
|
||||
wdt->locked = true;
|
||||
|
||||
source = value & WDTCR_TIMER_SOURCE_MASK;
|
||||
|
||||
wdt->tmr = tegra186_tmr_create(tegra, source);
|
||||
if (IS_ERR(wdt->tmr))
|
||||
return ERR_CAST(wdt->tmr);
|
||||
|
||||
wdt->base.info = &tegra186_wdt_info;
|
||||
wdt->base.ops = &tegra186_wdt_ops;
|
||||
wdt->base.min_timeout = 1;
|
||||
wdt->base.max_timeout = 255;
|
||||
wdt->base.parent = tegra->dev;
|
||||
|
||||
err = watchdog_init_timeout(&wdt->base, 5, tegra->dev);
|
||||
if (err < 0) {
|
||||
dev_err(tegra->dev, "failed to initialize timeout: %d\n", err);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
err = devm_watchdog_register_device(tegra->dev, &wdt->base);
|
||||
if (err < 0) {
|
||||
dev_err(tegra->dev, "failed to register WDT: %d\n", err);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
return wdt;
|
||||
}
|
||||
|
||||
static u64 tegra186_timer_tsc_read(struct clocksource *cs)
|
||||
{
|
||||
struct tegra186_timer *tegra = container_of(cs, struct tegra186_timer,
|
||||
tsc);
|
||||
u32 hi, lo, ss;
|
||||
|
||||
hi = readl_relaxed(tegra->regs + TKETSC1);
|
||||
|
||||
/*
|
||||
* The 56-bit value of the TSC is spread across two registers that are
|
||||
* not synchronized. In order to read them atomically, ensure that the
|
||||
* high 24 bits match before and after reading the low 32 bits.
|
||||
*/
|
||||
do {
|
||||
/* snapshot the high 24 bits */
|
||||
ss = hi;
|
||||
|
||||
lo = readl_relaxed(tegra->regs + TKETSC0);
|
||||
hi = readl_relaxed(tegra->regs + TKETSC1);
|
||||
} while (hi != ss);
|
||||
|
||||
return (u64)hi << 32 | lo;
|
||||
}
|
||||
|
||||
static int tegra186_timer_tsc_init(struct tegra186_timer *tegra)
|
||||
{
|
||||
tegra->tsc.name = "tsc";
|
||||
tegra->tsc.rating = 300;
|
||||
tegra->tsc.read = tegra186_timer_tsc_read;
|
||||
tegra->tsc.mask = CLOCKSOURCE_MASK(56);
|
||||
tegra->tsc.flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
||||
|
||||
return clocksource_register_hz(&tegra->tsc, 31250000);
|
||||
}
|
||||
|
||||
static u64 tegra186_timer_osc_read(struct clocksource *cs)
|
||||
{
|
||||
struct tegra186_timer *tegra = container_of(cs, struct tegra186_timer,
|
||||
osc);
|
||||
|
||||
return readl_relaxed(tegra->regs + TKEOSC);
|
||||
}
|
||||
|
||||
static int tegra186_timer_osc_init(struct tegra186_timer *tegra)
|
||||
{
|
||||
tegra->osc.name = "osc";
|
||||
tegra->osc.rating = 300;
|
||||
tegra->osc.read = tegra186_timer_osc_read;
|
||||
tegra->osc.mask = CLOCKSOURCE_MASK(32);
|
||||
tegra->osc.flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
||||
|
||||
return clocksource_register_hz(&tegra->osc, 38400000);
|
||||
}
|
||||
|
||||
static u64 tegra186_timer_usec_read(struct clocksource *cs)
|
||||
{
|
||||
struct tegra186_timer *tegra = container_of(cs, struct tegra186_timer,
|
||||
usec);
|
||||
|
||||
return readl_relaxed(tegra->regs + TKEUSEC);
|
||||
}
|
||||
|
||||
static int tegra186_timer_usec_init(struct tegra186_timer *tegra)
|
||||
{
|
||||
tegra->usec.name = "usec";
|
||||
tegra->usec.rating = 300;
|
||||
tegra->usec.read = tegra186_timer_usec_read;
|
||||
tegra->usec.mask = CLOCKSOURCE_MASK(32);
|
||||
tegra->usec.flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
||||
|
||||
return clocksource_register_hz(&tegra->usec, USEC_PER_SEC);
|
||||
}
|
||||
|
||||
static irqreturn_t tegra186_timer_irq(int irq, void *data)
|
||||
{
|
||||
struct tegra186_timer *tegra = data;
|
||||
|
||||
if (watchdog_active(&tegra->wdt->base)) {
|
||||
tegra186_wdt_disable(tegra->wdt);
|
||||
tegra186_wdt_enable(tegra->wdt);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tegra186_timer_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct tegra186_timer *tegra;
|
||||
unsigned int irq;
|
||||
int err;
|
||||
|
||||
tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL);
|
||||
if (!tegra)
|
||||
return -ENOMEM;
|
||||
|
||||
tegra->soc = of_device_get_match_data(dev);
|
||||
dev_set_drvdata(dev, tegra);
|
||||
tegra->dev = dev;
|
||||
|
||||
tegra->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(tegra->regs))
|
||||
return PTR_ERR(tegra->regs);
|
||||
|
||||
err = platform_get_irq(pdev, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
irq = err;
|
||||
|
||||
/* create a watchdog using a preconfigured timer */
|
||||
tegra->wdt = tegra186_wdt_create(tegra, 0);
|
||||
if (IS_ERR(tegra->wdt)) {
|
||||
err = PTR_ERR(tegra->wdt);
|
||||
dev_err(dev, "failed to create WDT: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = tegra186_timer_tsc_init(tegra);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to register TSC counter: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = tegra186_timer_osc_init(tegra);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to register OSC counter: %d\n", err);
|
||||
goto unregister_tsc;
|
||||
}
|
||||
|
||||
err = tegra186_timer_usec_init(tegra);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to register USEC counter: %d\n", err);
|
||||
goto unregister_osc;
|
||||
}
|
||||
|
||||
err = devm_request_irq(dev, irq, tegra186_timer_irq, 0,
|
||||
"tegra186-timer", tegra);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to request IRQ#%u: %d\n", irq, err);
|
||||
goto unregister_usec;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_usec:
|
||||
clocksource_unregister(&tegra->usec);
|
||||
unregister_osc:
|
||||
clocksource_unregister(&tegra->osc);
|
||||
unregister_tsc:
|
||||
clocksource_unregister(&tegra->tsc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tegra186_timer_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tegra186_timer *tegra = platform_get_drvdata(pdev);
|
||||
|
||||
clocksource_unregister(&tegra->usec);
|
||||
clocksource_unregister(&tegra->osc);
|
||||
clocksource_unregister(&tegra->tsc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused tegra186_timer_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra186_timer *tegra = dev_get_drvdata(dev);
|
||||
|
||||
if (watchdog_active(&tegra->wdt->base))
|
||||
tegra186_wdt_disable(tegra->wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused tegra186_timer_resume(struct device *dev)
|
||||
{
|
||||
struct tegra186_timer *tegra = dev_get_drvdata(dev);
|
||||
|
||||
if (watchdog_active(&tegra->wdt->base))
|
||||
tegra186_wdt_enable(tegra->wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tegra186_timer_pm_ops, tegra186_timer_suspend,
|
||||
tegra186_timer_resume);
|
||||
|
||||
static const struct tegra186_timer_soc tegra186_timer = {
|
||||
.num_timers = 10,
|
||||
.num_wdts = 3,
|
||||
};
|
||||
|
||||
static const struct tegra186_timer_soc tegra234_timer = {
|
||||
.num_timers = 16,
|
||||
.num_wdts = 3,
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra186_timer_of_match[] = {
|
||||
{ .compatible = "nvidia,tegra186-timer", .data = &tegra186_timer },
|
||||
{ .compatible = "nvidia,tegra234-timer", .data = &tegra234_timer },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tegra186_timer_of_match);
|
||||
|
||||
static struct platform_driver tegra186_wdt_driver = {
|
||||
.driver = {
|
||||
.name = "tegra186-timer",
|
||||
.pm = &tegra186_timer_pm_ops,
|
||||
.of_match_table = tegra186_timer_of_match,
|
||||
},
|
||||
.probe = tegra186_timer_probe,
|
||||
.remove = tegra186_timer_remove,
|
||||
};
|
||||
module_platform_driver(tegra186_wdt_driver);
|
||||
|
||||
MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
|
||||
MODULE_DESCRIPTION("NVIDIA Tegra186 timers driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -44,6 +44,121 @@ enum {
|
||||
REQUEST_BY_NODE,
|
||||
};
|
||||
|
||||
static inline u32 __omap_dm_timer_read(struct omap_dm_timer *timer, u32 reg,
|
||||
int posted)
|
||||
{
|
||||
if (posted)
|
||||
while (readl_relaxed(timer->pend) & (reg >> WPSHIFT))
|
||||
cpu_relax();
|
||||
|
||||
return readl_relaxed(timer->func_base + (reg & 0xff));
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_write(struct omap_dm_timer *timer,
|
||||
u32 reg, u32 val, int posted)
|
||||
{
|
||||
if (posted)
|
||||
while (readl_relaxed(timer->pend) & (reg >> WPSHIFT))
|
||||
cpu_relax();
|
||||
|
||||
writel_relaxed(val, timer->func_base + (reg & 0xff));
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_init_regs(struct omap_dm_timer *timer)
|
||||
{
|
||||
u32 tidr;
|
||||
|
||||
/* Assume v1 ip if bits [31:16] are zero */
|
||||
tidr = readl_relaxed(timer->io_base);
|
||||
if (!(tidr >> 16)) {
|
||||
timer->revision = 1;
|
||||
timer->irq_stat = timer->io_base + OMAP_TIMER_V1_STAT_OFFSET;
|
||||
timer->irq_ena = timer->io_base + OMAP_TIMER_V1_INT_EN_OFFSET;
|
||||
timer->irq_dis = timer->io_base + OMAP_TIMER_V1_INT_EN_OFFSET;
|
||||
timer->pend = timer->io_base + _OMAP_TIMER_WRITE_PEND_OFFSET;
|
||||
timer->func_base = timer->io_base;
|
||||
} else {
|
||||
timer->revision = 2;
|
||||
timer->irq_stat = timer->io_base + OMAP_TIMER_V2_IRQSTATUS;
|
||||
timer->irq_ena = timer->io_base + OMAP_TIMER_V2_IRQENABLE_SET;
|
||||
timer->irq_dis = timer->io_base + OMAP_TIMER_V2_IRQENABLE_CLR;
|
||||
timer->pend = timer->io_base +
|
||||
_OMAP_TIMER_WRITE_PEND_OFFSET +
|
||||
OMAP_TIMER_V2_FUNC_OFFSET;
|
||||
timer->func_base = timer->io_base + OMAP_TIMER_V2_FUNC_OFFSET;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* __omap_dm_timer_enable_posted - enables write posted mode
|
||||
* @timer: pointer to timer instance handle
|
||||
*
|
||||
* Enables the write posted mode for the timer. When posted mode is enabled
|
||||
* writes to certain timer registers are immediately acknowledged by the
|
||||
* internal bus and hence prevents stalling the CPU waiting for the write to
|
||||
* complete. Enabling this feature can improve performance for writing to the
|
||||
* timer registers.
|
||||
*/
|
||||
static inline void __omap_dm_timer_enable_posted(struct omap_dm_timer *timer)
|
||||
{
|
||||
if (timer->posted)
|
||||
return;
|
||||
|
||||
if (timer->errata & OMAP_TIMER_ERRATA_I103_I767) {
|
||||
timer->posted = OMAP_TIMER_NONPOSTED;
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_IF_CTRL_REG, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_IF_CTRL_REG,
|
||||
OMAP_TIMER_CTRL_POSTED, 0);
|
||||
timer->context.tsicr = OMAP_TIMER_CTRL_POSTED;
|
||||
timer->posted = OMAP_TIMER_POSTED;
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_stop(struct omap_dm_timer *timer,
|
||||
int posted, unsigned long rate)
|
||||
{
|
||||
u32 l;
|
||||
|
||||
l = __omap_dm_timer_read(timer, OMAP_TIMER_CTRL_REG, posted);
|
||||
if (l & OMAP_TIMER_CTRL_ST) {
|
||||
l &= ~0x1;
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_CTRL_REG, l, posted);
|
||||
#ifdef CONFIG_ARCH_OMAP2PLUS
|
||||
/* Readback to make sure write has completed */
|
||||
__omap_dm_timer_read(timer, OMAP_TIMER_CTRL_REG, posted);
|
||||
/*
|
||||
* Wait for functional clock period x 3.5 to make sure that
|
||||
* timer is stopped
|
||||
*/
|
||||
udelay(3500000 / rate + 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Ack possibly pending interrupt */
|
||||
writel_relaxed(OMAP_TIMER_INT_OVERFLOW, timer->irq_stat);
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_int_enable(struct omap_dm_timer *timer,
|
||||
unsigned int value)
|
||||
{
|
||||
writel_relaxed(value, timer->irq_ena);
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, value, 0);
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
__omap_dm_timer_read_counter(struct omap_dm_timer *timer, int posted)
|
||||
{
|
||||
return __omap_dm_timer_read(timer, OMAP_TIMER_COUNTER_REG, posted);
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_write_status(struct omap_dm_timer *timer,
|
||||
unsigned int value)
|
||||
{
|
||||
writel_relaxed(value, timer->irq_stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* omap_dm_timer_read_reg - read timer registers in posted and non-posted mode
|
||||
* @timer: timer pointer over which read operation to perform
|
||||
@ -921,6 +1036,10 @@ static const struct dmtimer_platform_data omap3plus_pdata = {
|
||||
.timer_ops = &dmtimer_ops,
|
||||
};
|
||||
|
||||
static const struct dmtimer_platform_data am6_pdata = {
|
||||
.timer_ops = &dmtimer_ops,
|
||||
};
|
||||
|
||||
static const struct of_device_id omap_timer_match[] = {
|
||||
{
|
||||
.compatible = "ti,omap2420-timer",
|
||||
@ -949,6 +1068,10 @@ static const struct of_device_id omap_timer_match[] = {
|
||||
.compatible = "ti,dm816-timer",
|
||||
.data = &omap3plus_pdata,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,am654-timer",
|
||||
.data = &am6_pdata,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_timer_match);
|
||||
|
@ -399,6 +399,10 @@ static const struct of_device_id rcar_gen3_thermal_dt_ids[] = {
|
||||
.compatible = "renesas,r8a779a0-thermal",
|
||||
.data = &rcar_gen3_ths_tj_1,
|
||||
},
|
||||
{
|
||||
.compatible = "renesas,r8a779f0-thermal",
|
||||
.data = &rcar_gen3_ths_tj_1,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids);
|
||||
|
@ -247,148 +247,4 @@ int omap_dm_timers_active(void);
|
||||
#define OMAP_TIMER_TICK_INT_MASK_COUNT_REG \
|
||||
(_OMAP_TIMER_TICK_INT_MASK_COUNT_OFFSET | (WP_TOWR << WPSHIFT))
|
||||
|
||||
/*
|
||||
* The below are inlined to optimize code size for system timers. Other code
|
||||
* should not need these at all.
|
||||
*/
|
||||
#if defined(CONFIG_ARCH_OMAP1) || defined(CONFIG_ARCH_OMAP2PLUS)
|
||||
static inline u32 __omap_dm_timer_read(struct omap_dm_timer *timer, u32 reg,
|
||||
int posted)
|
||||
{
|
||||
if (posted)
|
||||
while (readl_relaxed(timer->pend) & (reg >> WPSHIFT))
|
||||
cpu_relax();
|
||||
|
||||
return readl_relaxed(timer->func_base + (reg & 0xff));
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_write(struct omap_dm_timer *timer,
|
||||
u32 reg, u32 val, int posted)
|
||||
{
|
||||
if (posted)
|
||||
while (readl_relaxed(timer->pend) & (reg >> WPSHIFT))
|
||||
cpu_relax();
|
||||
|
||||
writel_relaxed(val, timer->func_base + (reg & 0xff));
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_init_regs(struct omap_dm_timer *timer)
|
||||
{
|
||||
u32 tidr;
|
||||
|
||||
/* Assume v1 ip if bits [31:16] are zero */
|
||||
tidr = readl_relaxed(timer->io_base);
|
||||
if (!(tidr >> 16)) {
|
||||
timer->revision = 1;
|
||||
timer->irq_stat = timer->io_base + OMAP_TIMER_V1_STAT_OFFSET;
|
||||
timer->irq_ena = timer->io_base + OMAP_TIMER_V1_INT_EN_OFFSET;
|
||||
timer->irq_dis = timer->io_base + OMAP_TIMER_V1_INT_EN_OFFSET;
|
||||
timer->pend = timer->io_base + _OMAP_TIMER_WRITE_PEND_OFFSET;
|
||||
timer->func_base = timer->io_base;
|
||||
} else {
|
||||
timer->revision = 2;
|
||||
timer->irq_stat = timer->io_base + OMAP_TIMER_V2_IRQSTATUS;
|
||||
timer->irq_ena = timer->io_base + OMAP_TIMER_V2_IRQENABLE_SET;
|
||||
timer->irq_dis = timer->io_base + OMAP_TIMER_V2_IRQENABLE_CLR;
|
||||
timer->pend = timer->io_base +
|
||||
_OMAP_TIMER_WRITE_PEND_OFFSET +
|
||||
OMAP_TIMER_V2_FUNC_OFFSET;
|
||||
timer->func_base = timer->io_base + OMAP_TIMER_V2_FUNC_OFFSET;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* __omap_dm_timer_enable_posted - enables write posted mode
|
||||
* @timer: pointer to timer instance handle
|
||||
*
|
||||
* Enables the write posted mode for the timer. When posted mode is enabled
|
||||
* writes to certain timer registers are immediately acknowledged by the
|
||||
* internal bus and hence prevents stalling the CPU waiting for the write to
|
||||
* complete. Enabling this feature can improve performance for writing to the
|
||||
* timer registers.
|
||||
*/
|
||||
static inline void __omap_dm_timer_enable_posted(struct omap_dm_timer *timer)
|
||||
{
|
||||
if (timer->posted)
|
||||
return;
|
||||
|
||||
if (timer->errata & OMAP_TIMER_ERRATA_I103_I767) {
|
||||
timer->posted = OMAP_TIMER_NONPOSTED;
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_IF_CTRL_REG, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_IF_CTRL_REG,
|
||||
OMAP_TIMER_CTRL_POSTED, 0);
|
||||
timer->context.tsicr = OMAP_TIMER_CTRL_POSTED;
|
||||
timer->posted = OMAP_TIMER_POSTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* __omap_dm_timer_override_errata - override errata flags for a timer
|
||||
* @timer: pointer to timer handle
|
||||
* @errata: errata flags to be ignored
|
||||
*
|
||||
* For a given timer, override a timer errata by clearing the flags
|
||||
* specified by the errata argument. A specific erratum should only be
|
||||
* overridden for a timer if the timer is used in such a way the erratum
|
||||
* has no impact.
|
||||
*/
|
||||
static inline void __omap_dm_timer_override_errata(struct omap_dm_timer *timer,
|
||||
u32 errata)
|
||||
{
|
||||
timer->errata &= ~errata;
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_stop(struct omap_dm_timer *timer,
|
||||
int posted, unsigned long rate)
|
||||
{
|
||||
u32 l;
|
||||
|
||||
l = __omap_dm_timer_read(timer, OMAP_TIMER_CTRL_REG, posted);
|
||||
if (l & OMAP_TIMER_CTRL_ST) {
|
||||
l &= ~0x1;
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_CTRL_REG, l, posted);
|
||||
#ifdef CONFIG_ARCH_OMAP2PLUS
|
||||
/* Readback to make sure write has completed */
|
||||
__omap_dm_timer_read(timer, OMAP_TIMER_CTRL_REG, posted);
|
||||
/*
|
||||
* Wait for functional clock period x 3.5 to make sure that
|
||||
* timer is stopped
|
||||
*/
|
||||
udelay(3500000 / rate + 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Ack possibly pending interrupt */
|
||||
writel_relaxed(OMAP_TIMER_INT_OVERFLOW, timer->irq_stat);
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_load_start(struct omap_dm_timer *timer,
|
||||
u32 ctrl, unsigned int load,
|
||||
int posted)
|
||||
{
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_COUNTER_REG, load, posted);
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_CTRL_REG, ctrl, posted);
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_int_enable(struct omap_dm_timer *timer,
|
||||
unsigned int value)
|
||||
{
|
||||
writel_relaxed(value, timer->irq_ena);
|
||||
__omap_dm_timer_write(timer, OMAP_TIMER_WAKEUP_EN_REG, value, 0);
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
__omap_dm_timer_read_counter(struct omap_dm_timer *timer, int posted)
|
||||
{
|
||||
return __omap_dm_timer_read(timer, OMAP_TIMER_COUNTER_REG, posted);
|
||||
}
|
||||
|
||||
static inline void __omap_dm_timer_write_status(struct omap_dm_timer *timer,
|
||||
unsigned int value)
|
||||
{
|
||||
writel_relaxed(value, timer->irq_stat);
|
||||
}
|
||||
#endif /* CONFIG_ARCH_OMAP1 || CONFIG_ARCH_OMAP2PLUS */
|
||||
#endif /* __CLOCKSOURCE_DMTIMER_H */
|
||||
|
Loading…
Reference in New Issue
Block a user