* [PATCH v3 1/6] dt-bindings: timer: Add Tegra186 & Tegra234 Timer
2022-07-04 8:13 [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Kartik
@ 2022-07-04 8:13 ` Kartik
2022-07-05 20:47 ` Rob Herring
2022-07-04 8:13 ` [PATCH v3 2/6] clocksource: Add Tegra186 timers support Kartik
` (5 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Kartik @ 2022-07-04 8:13 UTC (permalink / raw)
To: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt,
thierry.reding, jonathanh, spujar, mperttunen, rgumasta, amhetre,
akhilrajeev, pshete, vidyas, sumitg, linux-kernel, devicetree,
linux-tegra
The Tegra186 timer provides ten 29-bit timer counters and one 32-bit
timestamp counter. The Tegra234 timer provides sixteen 29-bit timer
counters and one 32-bit timestamp counter. Each NV timer selects its
timing reference signal from the 1 MHz reference generated by USEC,
TSC or either clk_m or OSC. Each TMR can be programmed to generate
one-shot, periodic, or watchdog interrupts.
Signed-off-by: Kartik <kkartik@nvidia.com>
---
.../bindings/timer/nvidia,tegra186-timer.yaml | 109 ++++++++++++++++++
1 file changed, 109 insertions(+)
create mode 100644 Documentation/devicetree/bindings/timer/nvidia,tegra186-timer.yaml
diff --git a/Documentation/devicetree/bindings/timer/nvidia,tegra186-timer.yaml b/Documentation/devicetree/bindings/timer/nvidia,tegra186-timer.yaml
new file mode 100644
index 000000000000..db8b5595540f
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/nvidia,tegra186-timer.yaml
@@ -0,0 +1,109 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/timer/nvidia,tegra186-timer.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: NVIDIA Tegra186 timer
+
+maintainers:
+ - Thierry Reding <treding@nvidia.com>
+
+description: >
+ The Tegra timer provides 29-bit timer counters and a 32-bit timestamp
+ counter. Each NV timer selects its timing reference signal from the 1 MHz
+ reference generated by USEC, TSC or either clk_m or OSC. Each TMR can be
+ programmed to generate one-shot, periodic, or watchdog interrupts.
+
+
+properties:
+ compatible:
+ oneOf:
+ - const: nvidia,tegra186-timer
+ description: >
+ The Tegra186 timer provides ten 29-bit timer counters.
+ - const: nvidia,tegra234-timer
+ description: >
+ The Tegra234 timer provides sixteen 29-bit timer counters.
+
+ reg:
+ maxItems: 1
+
+ interrupts: true
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: nvidia,tegra186-timer
+ then:
+ properties:
+ interrupts:
+ maxItems: 10
+ description: >
+ One per each timer channels 0 through 9.
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: nvidia,tegra234-timer
+ then:
+ properties:
+ interrupts:
+ maxItems: 16
+ description: >
+ One per each timer channels 0 through 15.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ timer@3010000 {
+ compatible = "nvidia,tegra186-timer";
+ reg = <0x03010000 0x000e0000>;
+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ timer@2080000 {
+ compatible = "nvidia,tegra234-timer";
+ reg = <0x02080000 0x00121000>;
+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
+ };
--
2.17.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 1/6] dt-bindings: timer: Add Tegra186 & Tegra234 Timer
2022-07-04 8:13 ` [PATCH v3 1/6] dt-bindings: timer: Add Tegra186 & Tegra234 Timer Kartik
@ 2022-07-05 20:47 ` Rob Herring
0 siblings, 0 replies; 14+ messages in thread
From: Rob Herring @ 2022-07-05 20:47 UTC (permalink / raw)
To: Kartik
Cc: thierry.reding, devicetree, akhilrajeev, tglx, sumitg, rgumasta,
pshete, vidyas, krzysztof.kozlowski+dt, jonathanh, linux-tegra,
amhetre, daniel.lezcano, robh+dt, spujar, linux-kernel,
mperttunen
On Mon, 04 Jul 2022 13:43:37 +0530, Kartik wrote:
> The Tegra186 timer provides ten 29-bit timer counters and one 32-bit
> timestamp counter. The Tegra234 timer provides sixteen 29-bit timer
> counters and one 32-bit timestamp counter. Each NV timer selects its
> timing reference signal from the 1 MHz reference generated by USEC,
> TSC or either clk_m or OSC. Each TMR can be programmed to generate
> one-shot, periodic, or watchdog interrupts.
>
> Signed-off-by: Kartik <kkartik@nvidia.com>
> ---
> .../bindings/timer/nvidia,tegra186-timer.yaml | 109 ++++++++++++++++++
> 1 file changed, 109 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/timer/nvidia,tegra186-timer.yaml
>
Reviewed-by: Rob Herring <robh@kernel.org>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 2/6] clocksource: Add Tegra186 timers support
2022-07-04 8:13 [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Kartik
2022-07-04 8:13 ` [PATCH v3 1/6] dt-bindings: timer: Add Tegra186 & Tegra234 Timer Kartik
@ 2022-07-04 8:13 ` Kartik
2022-07-08 14:47 ` Thierry Reding
2022-07-28 10:44 ` [tip: timers/core] " tip-bot2 for Thierry Reding
2022-07-04 8:13 ` [PATCH v3 3/6] clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC Kartik
` (4 subsequent siblings)
6 siblings, 2 replies; 14+ messages in thread
From: Kartik @ 2022-07-04 8:13 UTC (permalink / raw)
To: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt,
thierry.reding, jonathanh, spujar, mperttunen, rgumasta, amhetre,
akhilrajeev, pshete, vidyas, sumitg, linux-kernel, devicetree,
linux-tegra
From: Thierry Reding <treding@nvidia.com>
Currently this only supports a single watchdog, which uses a timer in
the background for countdown. Eventually the timers could be used for
various time-keeping tasks, but by default the architected timer will
already provide that functionality.
Signed-off-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Kartik <kkartik@nvidia.com>
---
drivers/clocksource/Kconfig | 8 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/timer-tegra186.c | 508 +++++++++++++++++++++++++++
3 files changed, 517 insertions(+)
create mode 100644 drivers/clocksource/timer-tegra186.c
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 49960eb22f19..5bbc4aacff4c 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -156,6 +156,14 @@ config TEGRA_TIMER
help
Enables support for the Tegra driver.
+config TEGRA186_TIMER
+ tristate "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
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 59c8909df9f3..64ab547de97b 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -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
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
new file mode 100644
index 000000000000..4515517c87a5
--- /dev/null
+++ b/drivers/clocksource/timer-tegra186.c
@@ -0,0 +1,508 @@
+// 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 of_device_id tegra186_timer_of_match[] = {
+ { .compatible = "nvidia,tegra186-timer", .data = &tegra186_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");
--
2.17.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/6] clocksource: Add Tegra186 timers support
2022-07-04 8:13 ` [PATCH v3 2/6] clocksource: Add Tegra186 timers support Kartik
@ 2022-07-08 14:47 ` Thierry Reding
2022-07-15 22:39 ` Daniel Lezcano
2022-07-28 10:44 ` [tip: timers/core] " tip-bot2 for Thierry Reding
1 sibling, 1 reply; 14+ messages in thread
From: Thierry Reding @ 2022-07-08 14:47 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Kartik, tglx, robh+dt, krzysztof.kozlowski+dt, jonathanh, spujar,
mperttunen, rgumasta, amhetre, akhilrajeev, pshete, vidyas,
sumitg, linux-kernel, devicetree, linux-tegra
[-- Attachment #1: Type: text/plain, Size: 979 bytes --]
On Mon, Jul 04, 2022 at 01:43:38PM +0530, Kartik wrote:
> From: Thierry Reding <treding@nvidia.com>
>
> Currently this only supports a single watchdog, which uses a timer in
> the background for countdown. Eventually the timers could be used for
> various time-keeping tasks, but by default the architected timer will
> already provide that functionality.
>
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> Signed-off-by: Kartik <kkartik@nvidia.com>
> ---
> drivers/clocksource/Kconfig | 8 +
> drivers/clocksource/Makefile | 1 +
> drivers/clocksource/timer-tegra186.c | 508 +++++++++++++++++++++++++++
> 3 files changed, 517 insertions(+)
> create mode 100644 drivers/clocksource/timer-tegra186.c
Daniel, any more work needed on this? Looks good to me, so:
Acked-by: Thierry Reding <treding@nvidia.com>
I've picked up patches 1 and 4-6 into the Tegra tree, so you only need
to take patches 2 and 3.
Thanks,
Thierry
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 2/6] clocksource: Add Tegra186 timers support
2022-07-08 14:47 ` Thierry Reding
@ 2022-07-15 22:39 ` Daniel Lezcano
0 siblings, 0 replies; 14+ messages in thread
From: Daniel Lezcano @ 2022-07-15 22:39 UTC (permalink / raw)
To: Thierry Reding
Cc: Kartik, tglx, robh+dt, krzysztof.kozlowski+dt, jonathanh, spujar,
mperttunen, rgumasta, amhetre, akhilrajeev, pshete, vidyas,
sumitg, linux-kernel, devicetree, linux-tegra
On 08/07/2022 16:47, Thierry Reding wrote:
> On Mon, Jul 04, 2022 at 01:43:38PM +0530, Kartik wrote:
>> From: Thierry Reding <treding@nvidia.com>
>>
>> Currently this only supports a single watchdog, which uses a timer in
>> the background for countdown. Eventually the timers could be used for
>> various time-keeping tasks, but by default the architected timer will
>> already provide that functionality.
>>
>> Signed-off-by: Thierry Reding <treding@nvidia.com>
>> Signed-off-by: Kartik <kkartik@nvidia.com>
>> ---
>> drivers/clocksource/Kconfig | 8 +
>> drivers/clocksource/Makefile | 1 +
>> drivers/clocksource/timer-tegra186.c | 508 +++++++++++++++++++++++++++
>> 3 files changed, 517 insertions(+)
>> create mode 100644 drivers/clocksource/timer-tegra186.c
>
> Daniel, any more work needed on this? Looks good to me, so:
>
> Acked-by: Thierry Reding <treding@nvidia.com>
>
> I've picked up patches 1 and 4-6 into the Tegra tree, so you only need
> to take patches 2 and 3.
Applied, thanks
--
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs
Follow Linaro: <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog
^ permalink raw reply [flat|nested] 14+ messages in thread
* [tip: timers/core] clocksource: Add Tegra186 timers support
2022-07-04 8:13 ` [PATCH v3 2/6] clocksource: Add Tegra186 timers support Kartik
2022-07-08 14:47 ` Thierry Reding
@ 2022-07-28 10:44 ` tip-bot2 for Thierry Reding
1 sibling, 0 replies; 14+ messages in thread
From: tip-bot2 for Thierry Reding @ 2022-07-28 10:44 UTC (permalink / raw)
To: linux-tip-commits
Cc: Thierry Reding, Kartik, Daniel Lezcano, x86, linux-kernel
The following commit has been merged into the timers/core branch of tip:
Commit-ID: 42cee19a9f839f2d60f1cd237d6905d8649127aa
Gitweb: https://git.kernel.org/tip/42cee19a9f839f2d60f1cd237d6905d8649127aa
Author: Thierry Reding <treding@nvidia.com>
AuthorDate: Mon, 04 Jul 2022 13:43:38 +05:30
Committer: Daniel Lezcano <daniel.lezcano@linaro.org>
CommitterDate: Sat, 16 Jul 2022 00:38:50 +02:00
clocksource: Add Tegra186 timers support
Currently this only supports a single watchdog, which uses a timer in
the background for countdown. Eventually the timers could be used for
various time-keeping tasks, but by default the architected timer will
already provide that functionality.
Signed-off-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Kartik <kkartik@nvidia.com>
Acked-by: Thierry Reding <treding@nvidia.com>
Link: https://lore.kernel.org/r/1656922422-25823-3-git-send-email-kkartik@nvidia.com
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
drivers/clocksource/Kconfig | 8 +-
drivers/clocksource/Makefile | 1 +-
drivers/clocksource/timer-tegra186.c | 508 ++++++++++++++++++++++++++-
3 files changed, 517 insertions(+)
create mode 100644 drivers/clocksource/timer-tegra186.c
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 0367a1c..2dcdf02 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -150,6 +150,14 @@ config TEGRA_TIMER
help
Enables support for the Tegra driver.
+config TEGRA186_TIMER
+ tristate "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
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 9c85ee2..685430d 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -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
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
new file mode 100644
index 0000000..4515517
--- /dev/null
+++ b/drivers/clocksource/timer-tegra186.c
@@ -0,0 +1,508 @@
+// 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 of_device_id tegra186_timer_of_match[] = {
+ { .compatible = "nvidia,tegra186-timer", .data = &tegra186_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");
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 3/6] clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC
2022-07-04 8:13 [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Kartik
2022-07-04 8:13 ` [PATCH v3 1/6] dt-bindings: timer: Add Tegra186 & Tegra234 Timer Kartik
2022-07-04 8:13 ` [PATCH v3 2/6] clocksource: Add Tegra186 timers support Kartik
@ 2022-07-04 8:13 ` Kartik
2022-07-08 14:43 ` Thierry Reding
2022-07-28 10:44 ` [tip: timers/core] " tip-bot2 for Kartik
2022-07-04 8:13 ` [PATCH v3 4/6] arm64: tegra: Enable native timers on Tegra186 Kartik
` (3 subsequent siblings)
6 siblings, 2 replies; 14+ messages in thread
From: Kartik @ 2022-07-04 8:13 UTC (permalink / raw)
To: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt,
thierry.reding, jonathanh, spujar, mperttunen, rgumasta, amhetre,
akhilrajeev, pshete, vidyas, sumitg, linux-kernel, devicetree,
linux-tegra
The timer IP block present on Tegra234 SoC supports watchdog timer
functionality that can be used to recover from system hangs. The
watchdog timer uses a timer in the background for countdown.
Signed-off-by: Kartik <kkartik@nvidia.com>
---
drivers/clocksource/timer-tegra186.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
index 4515517c87a5..ea742889ee06 100644
--- a/drivers/clocksource/timer-tegra186.c
+++ b/drivers/clocksource/timer-tegra186.c
@@ -486,8 +486,14 @@ static const struct tegra186_timer_soc tegra186_timer = {
.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);
--
2.17.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/6] clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC
2022-07-04 8:13 ` [PATCH v3 3/6] clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC Kartik
@ 2022-07-08 14:43 ` Thierry Reding
2022-07-28 10:44 ` [tip: timers/core] " tip-bot2 for Kartik
1 sibling, 0 replies; 14+ messages in thread
From: Thierry Reding @ 2022-07-08 14:43 UTC (permalink / raw)
To: Kartik
Cc: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt, jonathanh,
spujar, mperttunen, rgumasta, amhetre, akhilrajeev, pshete,
vidyas, sumitg, linux-kernel, devicetree, linux-tegra
[-- Attachment #1: Type: text/plain, Size: 453 bytes --]
On Mon, Jul 04, 2022 at 01:43:39PM +0530, Kartik wrote:
> The timer IP block present on Tegra234 SoC supports watchdog timer
> functionality that can be used to recover from system hangs. The
> watchdog timer uses a timer in the background for countdown.
>
> Signed-off-by: Kartik <kkartik@nvidia.com>
> ---
> drivers/clocksource/timer-tegra186.c | 6 ++++++
> 1 file changed, 6 insertions(+)
Acked-by: Thierry Reding <treding@nvidia.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* [tip: timers/core] clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC
2022-07-04 8:13 ` [PATCH v3 3/6] clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC Kartik
2022-07-08 14:43 ` Thierry Reding
@ 2022-07-28 10:44 ` tip-bot2 for Kartik
1 sibling, 0 replies; 14+ messages in thread
From: tip-bot2 for Kartik @ 2022-07-28 10:44 UTC (permalink / raw)
To: linux-tip-commits
Cc: Kartik, Thierry Reding, Daniel Lezcano, x86, linux-kernel
The following commit has been merged into the timers/core branch of tip:
Commit-ID: 07385a6055a8649593052703b1bfd6aef49db02a
Gitweb: https://git.kernel.org/tip/07385a6055a8649593052703b1bfd6aef49db02a
Author: Kartik <kkartik@nvidia.com>
AuthorDate: Mon, 04 Jul 2022 13:43:39 +05:30
Committer: Daniel Lezcano <daniel.lezcano@linaro.org>
CommitterDate: Sat, 16 Jul 2022 00:38:50 +02:00
clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC
The timer IP block present on Tegra234 SoC supports watchdog timer
functionality that can be used to recover from system hangs. The
watchdog timer uses a timer in the background for countdown.
Signed-off-by: Kartik <kkartik@nvidia.com>
Acked-by: Thierry Reding <treding@nvidia.com>
Link: https://lore.kernel.org/r/1656922422-25823-4-git-send-email-kkartik@nvidia.com
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
drivers/clocksource/timer-tegra186.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
index 4515517..ea74288 100644
--- a/drivers/clocksource/timer-tegra186.c
+++ b/drivers/clocksource/timer-tegra186.c
@@ -486,8 +486,14 @@ static const struct tegra186_timer_soc tegra186_timer = {
.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);
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 4/6] arm64: tegra: Enable native timers on Tegra186
2022-07-04 8:13 [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Kartik
` (2 preceding siblings ...)
2022-07-04 8:13 ` [PATCH v3 3/6] clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC Kartik
@ 2022-07-04 8:13 ` Kartik
2022-07-04 8:13 ` [PATCH v3 5/6] arm64: tegra: Enable native timers on Tegra194 Kartik
` (2 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Kartik @ 2022-07-04 8:13 UTC (permalink / raw)
To: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt,
thierry.reding, jonathanh, spujar, mperttunen, rgumasta, amhetre,
akhilrajeev, pshete, vidyas, sumitg, linux-kernel, devicetree,
linux-tegra
Enable the native timers on Tegra186 chips to allow using the watchdog
functionality to recover from system hangs.
Signed-off-by: Kartik <kkartik@nvidia.com>
---
arch/arm64/boot/dts/nvidia/tegra186.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/nvidia/tegra186.dtsi b/arch/arm64/boot/dts/nvidia/tegra186.dtsi
index a87b52434939..a07acdaf345f 100644
--- a/arch/arm64/boot/dts/nvidia/tegra186.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra186.dtsi
@@ -599,7 +599,7 @@
<GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
- status = "disabled";
+ status = "okay";
};
uarta: serial@3100000 {
--
2.17.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 5/6] arm64: tegra: Enable native timers on Tegra194
2022-07-04 8:13 [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Kartik
` (3 preceding siblings ...)
2022-07-04 8:13 ` [PATCH v3 4/6] arm64: tegra: Enable native timers on Tegra186 Kartik
@ 2022-07-04 8:13 ` Kartik
2022-07-04 8:13 ` [PATCH v3 6/6] arm64: tegra: Enable native timers on Tegra234 Kartik
2022-07-08 14:42 ` [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Thierry Reding
6 siblings, 0 replies; 14+ messages in thread
From: Kartik @ 2022-07-04 8:13 UTC (permalink / raw)
To: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt,
thierry.reding, jonathanh, spujar, mperttunen, rgumasta, amhetre,
akhilrajeev, pshete, vidyas, sumitg, linux-kernel, devicetree,
linux-tegra
From: Thierry Reding <treding@nvidia.com>
The native timers IP block found on NVIDIA Tegra SoCs implements a
watchdog timer that can be used to recover from system hangs. Add and
enable the device tree node on Tegra194.
Signed-off-by: Thierry Reding <treding@nvidia.com>
Signed-off-by: Kartik <kkartik@nvidia.com>
---
arch/arm64/boot/dts/nvidia/tegra194.dtsi | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/arch/arm64/boot/dts/nvidia/tegra194.dtsi b/arch/arm64/boot/dts/nvidia/tegra194.dtsi
index 9566c6388ed9..4b37aec69448 100644
--- a/arch/arm64/boot/dts/nvidia/tegra194.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra194.dtsi
@@ -698,6 +698,22 @@
};
};
+ timer@3010000 {
+ compatible = "nvidia,tegra186-timer";
+ reg = <0x03010000 0x000e0000>;
+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
+ status = "okay";
+ };
+
uarta: serial@3100000 {
compatible = "nvidia,tegra194-uart", "nvidia,tegra20-uart";
reg = <0x03100000 0x40>;
--
2.17.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 6/6] arm64: tegra: Enable native timers on Tegra234
2022-07-04 8:13 [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Kartik
` (4 preceding siblings ...)
2022-07-04 8:13 ` [PATCH v3 5/6] arm64: tegra: Enable native timers on Tegra194 Kartik
@ 2022-07-04 8:13 ` Kartik
2022-07-08 14:42 ` [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Thierry Reding
6 siblings, 0 replies; 14+ messages in thread
From: Kartik @ 2022-07-04 8:13 UTC (permalink / raw)
To: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt,
thierry.reding, jonathanh, spujar, mperttunen, rgumasta, amhetre,
akhilrajeev, pshete, vidyas, sumitg, linux-kernel, devicetree,
linux-tegra
The native timers IP block found on NVIDIA Tegra SoCs implements a
watchdog timer that can be used to recover from system hangs. Add and
enable the device tree node on Tegra234.
Signed-off-by: Kartik <kkartik@nvidia.com>
---
arch/arm64/boot/dts/nvidia/tegra234.dtsi | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
index cf611eff7f6b..aa8ceb3c329b 100644
--- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
@@ -519,6 +519,28 @@
status = "okay";
};
+ timer@2080000 {
+ compatible = "nvidia,tegra234-timer";
+ reg = <0x02080000 0x00121000>;
+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
+ status = "okay";
+ };
+
gpio: gpio@2200000 {
compatible = "nvidia,tegra234-gpio";
reg-names = "security", "gpio";
--
2.17.1
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs
2022-07-04 8:13 [PATCH v3 0/6] Add watchdog timer support for Tegra186/194/234 SoCs Kartik
` (5 preceding siblings ...)
2022-07-04 8:13 ` [PATCH v3 6/6] arm64: tegra: Enable native timers on Tegra234 Kartik
@ 2022-07-08 14:42 ` Thierry Reding
6 siblings, 0 replies; 14+ messages in thread
From: Thierry Reding @ 2022-07-08 14:42 UTC (permalink / raw)
To: Kartik
Cc: daniel.lezcano, tglx, robh+dt, krzysztof.kozlowski+dt, jonathanh,
spujar, mperttunen, rgumasta, amhetre, akhilrajeev, pshete,
vidyas, sumitg, linux-kernel, devicetree, linux-tegra
[-- Attachment #1: Type: text/plain, Size: 1513 bytes --]
On Mon, Jul 04, 2022 at 01:43:36PM +0530, Kartik wrote:
> The native timers IP block found on Tegra SoCs implements a watchdog
> timer that can be used to recover from system hangs. This series of
> patches adds support for watchdog timers available on Tegra186,
> Tegra194 and Tegra234 SOC's.
>
> To keep the history intact, I added Tegra234 driver change as separate.
> The original patchset is an old one authored by Thierry.
>
> Kartik (4):
> dt-bindings: timer: Add Tegra186 & Tegra234 Timer
> clocksource/drivers/timer-tegra186: Add support for Tegra234 SoC
> arm64: tegra: Enable native timers on Tegra186
> arm64: tegra: Enable native timers on Tegra234
>
> Thierry Reding (2):
> clocksource: Add Tegra186 timers support
> arm64: tegra: Enable native timers on Tegra194
>
> .../bindings/timer/nvidia,tegra186-timer.yaml | 109 ++++
> arch/arm64/boot/dts/nvidia/tegra186.dtsi | 2 +-
> arch/arm64/boot/dts/nvidia/tegra194.dtsi | 16 +
> arch/arm64/boot/dts/nvidia/tegra234.dtsi | 22 +
> drivers/clocksource/Kconfig | 8 +
> drivers/clocksource/Makefile | 1 +
> drivers/clocksource/timer-tegra186.c | 514 ++++++++++++++++++
> 7 files changed, 671 insertions(+), 1 deletion(-)
> create mode 100644 Documentation/devicetree/bindings/timer/nvidia,tegra186-timer.yaml
> create mode 100644 drivers/clocksource/timer-tegra186.c
Applied patches 1 and 4-6 to the Tegra tree, thanks.
Thierry
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread