2015-12-01 13:02:13

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 01/11] clocksource: Add brcm,bcm6345-timer/brcm,bcm6318-timer device tree binding

Add device tree bindings for the BCM6345/BCM6318 timers. This is required
for the BCM6345 watchdog which needs to respond to one of the timer
interrupts.

Signed-off-by: Simon Arlott <[email protected]>
Acked-by: Rob Herring <[email protected]>
---
.../bindings/timer/brcm,bcm6318-timer.txt | 44 ++++++++++++++++++++
.../bindings/timer/brcm,bcm6345-timer.txt | 47 ++++++++++++++++++++++
2 files changed, 91 insertions(+)
create mode 100644 Documentation/devicetree/bindings/timer/brcm,bcm6318-timer.txt
create mode 100644 Documentation/devicetree/bindings/timer/brcm,bcm6345-timer.txt

diff --git a/Documentation/devicetree/bindings/timer/brcm,bcm6318-timer.txt b/Documentation/devicetree/bindings/timer/brcm,bcm6318-timer.txt
new file mode 100644
index 0000000..cf4be7e
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/brcm,bcm6318-timer.txt
@@ -0,0 +1,44 @@
+Broadcom BCM6318 Timer
+
+This block is a timer that is connected to multiple interrupts on the main
+interrupt controller and functions as a programmable interrupt controller for
+timer events. There is a main timer interrupt for all timers.
+
+- 4 independent timers with their own interrupt, and own maskable level
+ interrupt bit in the main timer interrupt
+
+- 1 watchdog timer with an unmaskable level interrupt bit in the main timer
+ interrupt
+
+- Contains one enable/status word pair
+
+- No atomic set/clear operations
+
+Required properties:
+
+- compatible: should be "brcm,bcm<soc>-timer", "brcm,bcm6318-timer"
+- reg: specifies the base physical address and size of the registers, excluding
+ the watchdog registers
+- interrupt-controller: identifies the node as an interrupt controller
+- #interrupt-cells: specifies the number of cells needed to encode an interrupt
+ source, should be 1.
+- interrupt-parent: specifies the phandle to the parent interrupt controller(s)
+ this one is cascaded from
+- interrupts: specifies the interrupt line(s) in the interrupt-parent controller
+ node for the main timer interrupt, followed by the individual timer
+ interrupts; valid values depend on the type of parent interrupt controller
+- clocks: phandle of timer reference clock (periph)
+
+Example:
+
+timer: timer@10000040 {
+ compatible = "brcm,bcm63148-timer", "brcm,bcm6318-timer";
+ reg = <0x10000040 0x28>;
+
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ interrupt-parent = <&periph_intc>;
+ interrupts = <31>, <0>, <1>, <2>, <3>;
+ clock = <&periph_osc>;
+};
diff --git a/Documentation/devicetree/bindings/timer/brcm,bcm6345-timer.txt b/Documentation/devicetree/bindings/timer/brcm,bcm6345-timer.txt
new file mode 100644
index 0000000..03250dd
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/brcm,bcm6345-timer.txt
@@ -0,0 +1,47 @@
+Broadcom BCM6345 Timer
+
+This block is a timer that is connected to one interrupt on the main interrupt
+controller and functions as a programmable interrupt controller for timer
+events.
+
+- 3 independent timers with their own maskable level interrupt bit (but not
+ per CPU because there is only one parent interrupt and the timers share it)
+
+- 1 watchdog timer with an unmaskable level interrupt
+
+- Contains one enable/status word pair
+
+- No atomic set/clear operations
+
+The lack of per CPU ability of timers makes them unusable as a set of
+clockevent devices, otherwise they could be attached to the remaining
+interrupts.
+
+Required properties:
+
+- compatible: should be "brcm,bcm<soc>-timer", "brcm,bcm6345-timer"
+- reg: specifies the base physical address and size of the registers, excluding
+ the watchdog registers
+- interrupt-controller: identifies the node as an interrupt controller
+- #interrupt-cells: specifies the number of cells needed to encode an interrupt
+ source, should be 1.
+- interrupt-parent: specifies the phandle to the parent interrupt controller(s)
+ this one is cascaded from
+- interrupts: specifies the interrupt line(s) in the interrupt-parent controller
+ node for the timer interrupt; valid values depend on the type of parent
+ interrupt controller
+- clocks: phandle of timer reference clock (periph)
+
+Example:
+
+timer: timer@10000080 {
+ compatible = "brcm,bcm63168-timer", "brcm,bcm6345-timer";
+ reg = <0x10000080 0x1c>;
+
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ interrupt-parent = <&periph_intc>;
+ interrupts = <0>;
+ clock·=·<&periph_osc>;
+};
--
2.1.4

--
Simon Arlott


2015-12-01 13:02:58

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 02/11] MIPS: bmips: Add bcm6345-l2-timer interrupt controller

Add the BCM6345/BCM6318 timer as an interrupt controller so that it can be
used by the watchdog to warn that its timer will expire soon.

Support for clocksource/clockevents is not implemented as the timer
interrupt is not per CPU (except on the BCM6318) and the MIPS clock is
better. This could be added later if required without changing the device
tree binding.

Signed-off-by: Simon Arlott <[email protected]>
---
drivers/irqchip/Kconfig | 5 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-bcm6345-l2-timer.c | 386 +++++++++++++++++++++++++++++++++
3 files changed, 392 insertions(+)
create mode 100644 drivers/irqchip/irq-bcm6345-l2-timer.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index d307bb3..21c3d9b 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -70,6 +70,11 @@ config BCM6345_L1_IRQ
select GENERIC_IRQ_CHIP
select IRQ_DOMAIN

+config BCM6345_L2_TIMER_IRQ
+ bool
+ select GENERIC_IRQ_CHIP
+ select IRQ_DOMAIN
+
config BCM7038_L1_IRQ
bool
select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index ded59cf..2687dea 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
obj-$(CONFIG_BCM6345_L1_IRQ) += irq-bcm6345-l1.o
+obj-$(CONFIG_BCM6345_L2_TIMER_IRQ) += irq-bcm6345-l2-timer.o
obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o
diff --git a/drivers/irqchip/irq-bcm6345-l2-timer.c b/drivers/irqchip/irq-bcm6345-l2-timer.c
new file mode 100644
index 0000000..f3acda7
--- /dev/null
+++ b/drivers/irqchip/irq-bcm6345-l2-timer.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2015 Simon Arlott
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Based on arch/mips/bcm63xx/timer.c:
+ * Copyright (C) 2008 Maxime Bizon <[email protected]>
+ *
+ * Registers for SoCs with 4 timers: BCM6345, BCM6328, BCM6362, BCM6816,
+ * BCM68220,BCM63168, BCM63268
+ * 0x02: Interrupt enable (u8)
+ * 0x03: Interrupt status (u8)
+ * 0x04: Timer 0 control
+ * 0x08: Timer 1 control
+ * 0x0c: Timer 2 control
+ * 0x10: Timer 0 count
+ * 0x14: Timer 1 count
+ * 0x18: Timer 2 count
+ * 0x1c+: Watchdog registers
+ *
+ * Registers for SoCs with 5 timers: BCM6318
+ * 0x00: Interrupt enable (u32)
+ * 0x04: Interrupt status (u32)
+ * 0x08: Timer 0 control
+ * 0x0c: Timer 1 control
+ * 0x10: Timer 2 control
+ * 0x14: Timer 3 control
+ * 0x18: Timer 0 count
+ * 0x1c: Timer 1 count
+ * 0x20: Timer 2 count
+ * 0x24: Timer 3 count
+ * 0x28+: Watchdog registers
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+
+enum timer_regs {
+ /* Interrupt enable register:
+ * 1 bit per timer (without the watchdog)
+ */
+ TIMER_INT_ENABLE = 0,
+
+ /* Interrupt status register:
+ * 1 bit per timer (plus the watchdog)
+ * Read for status
+ * Write bit to ack
+ */
+ TIMER_INT_STATUS,
+
+ /* Per-timer control register */
+ TIMER_CONTROL,
+
+ /* Per-timer count register */
+ TIMER_COUNT,
+
+
+ /* Number of registers in enum */
+ __TIMER_REGS_ENUM_SIZE
+};
+
+/* Watchdog interrupt is immediately after the timers */
+#define WATCHDOG_INT_BIT(x) (BIT((x)->variant.nr_timers))
+
+#define CONTROL_COUNTDOWN_MASK (0x3fffffff)
+#define CONTROL_RSTCNTCLR_MASK (1 << 30)
+#define CONTROL_ENABLE_MASK (1 << 31)
+
+#define COUNT_MASK (0x3fffffff)
+
+struct bcm6345_timer *timer;
+
+struct bcm6345_timer_variant {
+ unsigned int nr_timers;
+ u32 (*int_read)(struct bcm6345_timer *timer, int reg);
+ void (*int_write)(struct bcm6345_timer *timer, int reg, u32 val);
+ long regs[__TIMER_REGS_ENUM_SIZE];
+};
+
+struct bcm6345_timer {
+ raw_spinlock_t lock;
+ void __iomem *base;
+ unsigned int irq;
+ struct irq_domain *domain;
+
+ struct bcm6345_timer_variant variant;
+ unsigned int nr_interrupts;
+};
+
+
+/* Interrupt enable/status are either 8-bit or 32-bit registers */
+
+static u32 bcm6345_timer_int_readl(struct bcm6345_timer *timer, int reg)
+{
+ return __raw_readl(timer->base + timer->variant.regs[reg]);
+}
+
+static void bcm6345_timer_int_writel(struct bcm6345_timer *timer,
+ int reg, u32 val)
+{
+ __raw_writel(val, timer->base + timer->variant.regs[reg]);
+}
+
+static u32 bcm6345_timer_int_readb(struct bcm6345_timer *timer, int reg)
+{
+ return __raw_readb(timer->base + timer->variant.regs[reg]);
+}
+
+static void bcm6345_timer_int_writeb(struct bcm6345_timer *timer,
+ int reg, u32 val)
+{
+ __raw_writeb(val, timer->base + timer->variant.regs[reg]);
+}
+
+
+/* Timer variants */
+
+static const struct bcm6345_timer_variant timer_bcm6318 __initconst = {
+ .nr_timers = 4,
+ .regs = {
+ [TIMER_INT_ENABLE] = 0x00,
+ [TIMER_INT_STATUS] = 0x04,
+ [TIMER_CONTROL] = 0x08,
+ [TIMER_COUNT] = 0x18,
+ },
+ .int_read = bcm6345_timer_int_readl,
+ .int_write = bcm6345_timer_int_writel,
+};
+
+static const struct bcm6345_timer_variant timer_bcm6345 __initconst = {
+ .nr_timers = 3,
+ .regs = {
+ [TIMER_INT_ENABLE] = 0x02,
+ [TIMER_INT_STATUS] = 0x03,
+ [TIMER_CONTROL] = 0x04,
+ [TIMER_COUNT] = 0x10,
+ },
+ .int_read = bcm6345_timer_int_readb,
+ .int_write = bcm6345_timer_int_writeb,
+};
+
+
+/* Register access functions */
+
+static inline u32 bcm6345_timer_read_int_status(struct bcm6345_timer *timer)
+{
+ return timer->variant.int_read(timer, TIMER_INT_STATUS);
+}
+
+static inline void bcm6345_timer_write_int_status(struct bcm6345_timer *timer,
+ u32 val)
+{
+ timer->variant.int_write(timer, TIMER_INT_STATUS, val);
+}
+
+static inline u32 bcm6345_timer_read_int_enable(struct bcm6345_timer *timer)
+{
+ return timer->variant.int_read(timer, TIMER_INT_ENABLE);
+}
+
+static inline void bcm6345_timer_write_int_enable(struct bcm6345_timer *timer,
+ u32 val)
+{
+ timer->variant.int_write(timer, TIMER_INT_ENABLE, val);
+}
+
+static inline __init void bcm6345_timer_write_control(
+ struct bcm6345_timer *timer, unsigned int id, u32 val)
+{
+ __raw_writel(val,
+ timer->base + timer->variant.regs[TIMER_CONTROL] + id * 4);
+}
+
+static inline __init void bcm6345_timer_write_count(
+ struct bcm6345_timer *timer, unsigned int id, u32 val)
+{
+ __raw_writel(val,
+ timer->base + timer->variant.regs[TIMER_COUNT] + id * 4);
+}
+
+static inline __init void bcm6345_timer_stop(struct bcm6345_timer *timer,
+ unsigned int id)
+{
+ bcm6345_timer_write_control(timer, id, 0);
+ bcm6345_timer_write_count(timer, id, 0);
+ bcm6345_timer_write_int_status(timer, BIT(id));
+}
+
+
+/* Interrupt handler functions */
+
+static void bcm6345_timer_interrupt(struct irq_desc *desc)
+{
+ struct bcm6345_timer *timer = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long pending;
+ irq_hw_number_t hwirq;
+ unsigned int irq;
+
+ chained_irq_enter(chip, desc);
+
+ pending = bcm6345_timer_read_int_status(timer);
+ pending &= bcm6345_timer_read_int_enable(timer) |
+ WATCHDOG_INT_BIT(timer); /* Watchdog can't be masked */
+
+ for_each_set_bit(hwirq, &pending, timer->nr_interrupts) {
+ irq = irq_linear_revmap(timer->domain, hwirq);
+ if (irq)
+ do_IRQ(irq);
+ else
+ spurious_interrupt();
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void bcm6345_timer_enable(struct irq_data *d)
+{
+ struct bcm6345_timer *timer = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ u8 val;
+
+ raw_spin_lock_irqsave(&timer->lock, flags);
+ val = bcm6345_timer_read_int_enable(timer);
+ val |= BIT(d->hwirq);
+ bcm6345_timer_write_int_enable(timer, val);
+ raw_spin_unlock_irqrestore(&timer->lock, flags);
+}
+
+static void bcm6345_timer_disable(struct irq_data *d)
+{
+ struct bcm6345_timer *timer = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&timer->lock, flags);
+ val = bcm6345_timer_read_int_enable(timer);
+ val &= ~BIT(d->hwirq);
+ bcm6345_timer_write_int_enable(timer, val);
+ raw_spin_unlock_irqrestore(&timer->lock, flags);
+}
+
+static void bcm6345_timer_eoi(struct irq_data *d)
+{
+ struct bcm6345_timer *timer = irq_data_get_irq_chip_data(d);
+
+ bcm6345_timer_write_int_status(timer, BIT(d->hwirq));
+}
+
+static struct irq_chip bcm6345_timer_chip = {
+ .name = "bcm6345-timer",
+ .irq_enable = bcm6345_timer_enable,
+ .irq_disable = bcm6345_timer_disable,
+ .irq_eoi = bcm6345_timer_eoi,
+};
+
+static void bcm6345_timer_irq_noop(struct irq_data *d)
+{
+ /* The watchdog interrupt can't be masked (its
+ * enable bit has no effect), so do nothing.
+ */
+}
+
+static struct irq_chip bcm6345_timer_wdt_chip = {
+ .name = "bcm6345-timer",
+ .irq_enable = bcm6345_timer_irq_noop,
+ .irq_disable = bcm6345_timer_irq_noop,
+};
+
+static int bcm6345_timer_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct bcm6345_timer *timer = d->host_data;
+
+ if (hwirq < timer->variant.nr_timers) {
+ irq_set_chip_and_handler(virq, &bcm6345_timer_chip,
+ handle_fasteoi_irq);
+ } else {
+ /* Watchdog interrupt can't be disabled or acked */
+ irq_set_chip_and_handler(virq, &bcm6345_timer_wdt_chip,
+ handle_simple_irq);
+ }
+ irq_set_chip_data(virq, timer);
+ return 0;
+}
+
+static const struct irq_domain_ops bcm6345_timer_domain_ops = {
+ .xlate = irq_domain_xlate_onecell,
+ .map = bcm6345_timer_map,
+};
+
+static int __init bcm63xx_timer_init(struct device_node *node,
+ const char *name, const struct bcm6345_timer_variant *variant)
+{
+ struct bcm6345_timer *timer;
+ unsigned int i;
+ int ret;
+
+ timer = kzalloc(sizeof(*timer), GFP_KERNEL);
+ if (!timer)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&timer->lock);
+ memcpy(&timer->variant, variant, sizeof(*variant));
+ /* The watchdog warning event is the next interrupt bit
+ * after the timers. It has different control/countdown
+ * registers, handled by the watchdog driver.
+ */
+ timer->nr_interrupts = timer->variant.nr_timers + 1;
+
+ timer->irq = irq_of_parse_and_map(node, 0);
+ if (!timer->irq) {
+ pr_err("unable to map parent IRQ\n");
+ ret = -EINVAL;
+ goto free_timer;
+ }
+
+ timer->base = of_iomap(node, 0);
+ if (!timer->base) {
+ pr_err("unable to map registers\n");
+ ret = -ENOMEM;
+ goto free_timer;
+ }
+
+ timer->domain = irq_domain_add_linear(node, timer->nr_interrupts,
+ &bcm6345_timer_domain_ops, timer);
+ if (!timer->domain) {
+ pr_err("unable to add IRQ domain");
+ ret = -ENOMEM;
+ goto unmap_io;
+ }
+
+ /* Mask all interrupts and stop all timers */
+ bcm6345_timer_write_int_enable(timer, 0);
+ for (i = 0; i < timer->variant.nr_timers; i++)
+ bcm6345_timer_stop(timer, i);
+
+ irq_set_chained_handler_and_data(timer->irq,
+ bcm6345_timer_interrupt, timer);
+
+ pr_info("registered %s L2 (timer) intc at MMIO 0x%p (irq = %d, IRQs: %d)\n",
+ name, timer->base, timer->irq, timer->nr_interrupts);
+ return 0;
+
+unmap_io:
+ iounmap(timer->base);
+free_timer:
+ kfree(timer);
+ return ret;
+}
+
+static int __init bcm6318_timer_init(struct device_node *node,
+ struct device_node *parent)
+{
+ return bcm63xx_timer_init(node, "BCM6318", &timer_bcm6318);
+}
+
+static int __init bcm6345_timer_init(struct device_node *node,
+ struct device_node *parent)
+{
+ return bcm63xx_timer_init(node, "BCM6345", &timer_bcm6345);
+}
+
+IRQCHIP_DECLARE(bcm6318_l2_timer, "brcm,bcm6318-timer", bcm6318_timer_init);
+IRQCHIP_DECLARE(bcm6345_l2_timer, "brcm,bcm6345-timer", bcm6345_timer_init);
--
2.1.4

--
Simon Arlott

2015-12-01 13:03:56

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 03/11] watchdog: Add brcm,bcm6345-wdt device tree binding

Add device tree binding for the BCM6345 watchdog.

This uses the BCM6345 timer for its warning interrupt.

Signed-off-by: Simon Arlott <[email protected]>
Acked-by: Rob Herring <[email protected]>
---
.../bindings/watchdog/brcm,bcm6345-wdt.txt | 35 ++++++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/brcm,bcm6345-wdt.txt

diff --git a/Documentation/devicetree/bindings/watchdog/brcm,bcm6345-wdt.txt b/Documentation/devicetree/bindings/watchdog/brcm,bcm6345-wdt.txt
new file mode 100644
index 0000000..9d852d4
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/brcm,bcm6345-wdt.txt
@@ -0,0 +1,35 @@
+BCM6345 Watchdog timer
+
+Required properties:
+
+- compatible: should be "brcm,bcm63<soc>-wdt", "brcm,bcm6345-wdt"
+- reg: Specifies base physical address and size of the registers.
+- clocks: Specify the clock used for timing
+
+Optional properties:
+
+- interrupt-parent: phandle to the interrupt controller
+- interrupts: Specify the interrupt used for the watchdog timout warning
+- timeout-sec: Contains the default watchdog timeout in seconds
+
+Example:
+
+watchdog {
+ compatible = "brcm,bcm63168-wdt", "brcm,bcm6345-wdt";
+ reg = <0x1000009c 0x0c>;
+ clocks = <&periph_clk>;
+
+ interrupt-parent = <&timer>;
+ interrupts = <3>;
+ timeout-sec = <30>;
+};
+
+watchdog {
+ compatible = "brcm,bcm6318-wdt", "brcm,bcm6345-wdt";
+ reg = <0x10000068 0x0c>;
+ clocks = <&periph_clk>;
+
+ interrupt-parent = <&timer>;
+ interrupts = <3>;
+ timeout-sec = <30>;
+};
--
2.1.4


--
Simon Arlott

2015-12-01 13:04:59

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 04/11] watchdog: bcm63xx_wdt: Handle hardware interrupt and remove software timer

There is a level triggered interrupt for the watchdog timer as part of
the bcm63xx_timer device. The interrupt occurs when the hardware watchdog
timer reaches 50% of the remaining time.

It is not possible to mask the interrupt within the bcm63xx_timer device.
To get around this limitation, handle the interrupt by restarting the
watchdog with the current remaining time (which will be half the previous
timeout) so that the interrupt occurs again at 1/4th, 1/8th, etc. of the
original timeout value until the watchdog forces a reboot.

The software timer was restarting the hardware watchdog with a 85 second
timeout until the software timer expired, and then causing a panic()
about 42.5 seconds later when the hardware interrupt occurred. The
hardware watchdog would not reboot until a further 42.5 seconds had
passed.

Remove the software timer and rely on the hardware timer directly,
reducing the maximum timeout from 256 seconds to 85 seconds
(2^32 / WDT_HZ).

Signed-off-by: Simon Arlott <[email protected]>
---
drivers/watchdog/bcm63xx_wdt.c | 125 ++++++++++++++++++++++++-----------------
1 file changed, 73 insertions(+), 52 deletions(-)

diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index ab26fd9..3f55cba 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -3,6 +3,7 @@
*
* Copyright (C) 2007, Miguel Gaio <[email protected]>
* Copyright (C) 2008, Florian Fainelli <[email protected]>
+ * Copyright 2015 Simon Arlott
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -20,11 +21,10 @@
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/watchdog.h>
-#include <linux/timer.h>
-#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/resource.h>
@@ -37,16 +37,17 @@

#define PFX KBUILD_MODNAME

-#define WDT_HZ 50000000 /* Fclk */
-#define WDT_DEFAULT_TIME 30 /* seconds */
-#define WDT_MAX_TIME 256 /* seconds */
+#define WDT_HZ 50000000 /* Fclk */
+#define WDT_DEFAULT_TIME 30 /* seconds */
+#define WDT_MAX_TIME (0xffffffff / WDT_HZ) /* seconds */

-static struct {
+struct bcm63xx_wdt_hw {
+ raw_spinlock_t lock;
void __iomem *regs;
- struct timer_list timer;
unsigned long inuse;
- atomic_t ticks;
-} bcm63xx_wdt_device;
+ bool running;
+};
+static struct bcm63xx_wdt_hw bcm63xx_wdt_device;

static int expect_close;

@@ -59,48 +60,67 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
/* HW functions */
static void bcm63xx_wdt_hw_start(void)
{
- bcm_writel(0xfffffffe, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bcm63xx_wdt_device.lock, flags);
+ bcm_writel(wdt_time * WDT_HZ, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG);
bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
+ bcm63xx_wdt_device.running = true;
+ raw_spin_unlock_irqrestore(&bcm63xx_wdt_device.lock, flags);
}

static void bcm63xx_wdt_hw_stop(void)
{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&bcm63xx_wdt_device.lock, flags);
bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
+ bcm63xx_wdt_device.running = false;
+ raw_spin_unlock_irqrestore(&bcm63xx_wdt_device.lock, flags);
}

+/* The watchdog interrupt occurs when half the timeout is remaining */
static void bcm63xx_wdt_isr(void *data)
{
- struct pt_regs *regs = get_irq_regs();
-
- die(PFX " fire", regs);
-}
-
-static void bcm63xx_timer_tick(unsigned long unused)
-{
- if (!atomic_dec_and_test(&bcm63xx_wdt_device.ticks)) {
- bcm63xx_wdt_hw_start();
- mod_timer(&bcm63xx_wdt_device.timer, jiffies + HZ);
- } else
- pr_crit("watchdog will restart system\n");
-}
-
-static void bcm63xx_wdt_pet(void)
-{
- atomic_set(&bcm63xx_wdt_device.ticks, wdt_time);
-}
-
-static void bcm63xx_wdt_start(void)
-{
- bcm63xx_wdt_pet();
- bcm63xx_timer_tick(0);
-}
+ struct bcm63xx_wdt_hw *hw = &bcm63xx_wdt_device;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&hw->lock, flags);
+ if (!hw->running) {
+ /* Stop the watchdog as it shouldn't be running */
+ bcm_writel(WDT_STOP_1, hw->regs + WDT_CTL_REG);
+ bcm_writel(WDT_STOP_2, hw->regs + WDT_CTL_REG);
+ } else {
+ u32 timeleft = bcm_readl(hw->regs + WDT_CTL_REG);
+ u32 ms;
+
+ if (timeleft >= 2) {
+ /* The only way to clear this level triggered interrupt
+ * without disrupting the normal running of the watchdog
+ * is to restart the watchdog with the current remaining
+ * time value (which will be half the previous timeout)
+ * so the interrupt occurs again at 1/4th, 1/8th, etc.
+ * of the original timeout value until we reboot.
+ *
+ * This is done with a lock held in case userspace is
+ * trying to restart the watchdog on another CPU.
+ */
+ bcm_writel(timeleft, hw->regs + WDT_DEFVAL_REG);
+ bcm_writel(WDT_START_1, hw->regs + WDT_CTL_REG);
+ bcm_writel(WDT_START_2, hw->regs + WDT_CTL_REG);
+ } else {
+ /* The watchdog cannot be started with a time of less
+ * than 2 ticks (it won't fire).
+ */
+ die(PFX ": watchdog timer expired\n", get_irq_regs());
+ }

-static void bcm63xx_wdt_pause(void)
-{
- del_timer_sync(&bcm63xx_wdt_device.timer);
- bcm63xx_wdt_hw_stop();
+ ms = timeleft / (WDT_HZ / 1000);
+ pr_alert("warning timer fired, reboot in %ums\n", ms);
+ }
+ raw_spin_unlock_irqrestore(&hw->lock, flags);
}

static int bcm63xx_wdt_settimeout(int new_time)
@@ -118,17 +138,17 @@ static int bcm63xx_wdt_open(struct inode *inode, struct file *file)
if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse))
return -EBUSY;

- bcm63xx_wdt_start();
+ bcm63xx_wdt_hw_start();
return nonseekable_open(inode, file);
}

static int bcm63xx_wdt_release(struct inode *inode, struct file *file)
{
if (expect_close == 42)
- bcm63xx_wdt_pause();
+ bcm63xx_wdt_hw_stop();
else {
pr_crit("Unexpected close, not stopping watchdog!\n");
- bcm63xx_wdt_start();
+ bcm63xx_wdt_hw_start();
}
clear_bit(0, &bcm63xx_wdt_device.inuse);
expect_close = 0;
@@ -153,7 +173,7 @@ static ssize_t bcm63xx_wdt_write(struct file *file, const char *data,
expect_close = 42;
}
}
- bcm63xx_wdt_pet();
+ bcm63xx_wdt_hw_start();
}
return len;
}
@@ -187,18 +207,18 @@ static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd,
return -EFAULT;

if (new_value & WDIOS_DISABLECARD) {
- bcm63xx_wdt_pause();
+ bcm63xx_wdt_hw_stop();
retval = 0;
}
if (new_value & WDIOS_ENABLECARD) {
- bcm63xx_wdt_start();
+ bcm63xx_wdt_hw_start();
retval = 0;
}

return retval;

case WDIOC_KEEPALIVE:
- bcm63xx_wdt_pet();
+ bcm63xx_wdt_hw_start();
return 0;

case WDIOC_SETTIMEOUT:
@@ -208,7 +228,7 @@ static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd,
if (bcm63xx_wdt_settimeout(new_value))
return -EINVAL;

- bcm63xx_wdt_pet();
+ bcm63xx_wdt_hw_start();

case WDIOC_GETTIMEOUT:
return put_user(wdt_time, p);
@@ -240,8 +260,6 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
int ret;
struct resource *r;

- setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_timer_tick, 0L);
-
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
dev_err(&pdev->dev, "failed to get resources\n");
@@ -255,6 +273,9 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
return -ENXIO;
}

+ raw_spin_lock_init(&bcm63xx_wdt_device.lock);
+ bcm63xx_wdt_device.running = false;
+
ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, NULL);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register wdt timer isr\n");
@@ -264,8 +285,8 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
if (bcm63xx_wdt_settimeout(wdt_time)) {
bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME);
dev_info(&pdev->dev,
- ": wdt_time value must be 1 <= wdt_time <= 256, using %d\n",
- wdt_time);
+ ": wdt_time value must be 1 <= wdt_time <= %d, using %d\n",
+ WDT_MAX_TIME, wdt_time);
}

ret = misc_register(&bcm63xx_wdt_miscdev);
@@ -287,7 +308,7 @@ unregister_timer:
static int bcm63xx_wdt_remove(struct platform_device *pdev)
{
if (!nowayout)
- bcm63xx_wdt_pause();
+ bcm63xx_wdt_hw_stop();

misc_deregister(&bcm63xx_wdt_miscdev);
bcm63xx_timer_unregister(TIMER_WDT_ID);
@@ -296,7 +317,7 @@ static int bcm63xx_wdt_remove(struct platform_device *pdev)

static void bcm63xx_wdt_shutdown(struct platform_device *pdev)
{
- bcm63xx_wdt_pause();
+ bcm63xx_wdt_hw_stop();
}

static struct platform_driver bcm63xx_wdt_driver = {
--
2.1.4


--
Simon Arlott

2015-12-01 13:05:49

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 05/11] watchdog: bcm63xx_wdt: Use WATCHDOG_CORE

Convert bcm63xx_wdt to use WATCHDOG_CORE.

The default and maximum time constants that are only used once have been
moved to the initialisation of the struct watchdog_device.

Signed-off-by: Simon Arlott <[email protected]>
---
drivers/watchdog/Kconfig | 1 +
drivers/watchdog/bcm63xx_wdt.c | 259 +++++++++++++----------------------------
2 files changed, 79 insertions(+), 181 deletions(-)

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 7a8a6c6..6815b74 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1273,6 +1273,7 @@ config OCTEON_WDT
config BCM63XX_WDT
tristate "Broadcom BCM63xx hardware watchdog"
depends on BCM63XX
+ select WATCHDOG_CORE
help
Watchdog driver for the built in watchdog hardware in Broadcom
BCM63xx SoC.
diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index 3f55cba..2257924 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -13,20 +13,15 @@

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

-#include <linux/bitops.h>
#include <linux/errno.h>
-#include <linux/fs.h>
#include <linux/io.h>
#include <linux/kernel.h>
-#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/spinlock.h>
#include <linux/types.h>
-#include <linux/uaccess.h>
#include <linux/watchdog.h>
#include <linux/interrupt.h>
-#include <linux/ptrace.h>
#include <linux/resource.h>
#include <linux/platform_device.h>

@@ -38,53 +33,59 @@
#define PFX KBUILD_MODNAME

#define WDT_HZ 50000000 /* Fclk */
-#define WDT_DEFAULT_TIME 30 /* seconds */
-#define WDT_MAX_TIME (0xffffffff / WDT_HZ) /* seconds */

struct bcm63xx_wdt_hw {
+ struct watchdog_device wdd;
raw_spinlock_t lock;
void __iomem *regs;
- unsigned long inuse;
bool running;
};
-static struct bcm63xx_wdt_hw bcm63xx_wdt_device;

-static int expect_close;
+#define to_wdt_hw(x) container_of(x, struct bcm63xx_wdt_hw, wdd)

-static int wdt_time = WDT_DEFAULT_TIME;
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

-/* HW functions */
-static void bcm63xx_wdt_hw_start(void)
+static int bcm63xx_wdt_start(struct watchdog_device *wdd)
{
+ struct bcm63xx_wdt_hw *hw = to_wdt_hw(wdd);
unsigned long flags;

- raw_spin_lock_irqsave(&bcm63xx_wdt_device.lock, flags);
- bcm_writel(wdt_time * WDT_HZ, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG);
- bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
- bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
- bcm63xx_wdt_device.running = true;
- raw_spin_unlock_irqrestore(&bcm63xx_wdt_device.lock, flags);
+ raw_spin_lock_irqsave(&hw->lock, flags);
+ bcm_writel(wdd->timeout * WDT_HZ, hw->regs + WDT_DEFVAL_REG);
+ bcm_writel(WDT_START_1, hw->regs + WDT_CTL_REG);
+ bcm_writel(WDT_START_2, hw->regs + WDT_CTL_REG);
+ hw->running = true;
+ raw_spin_unlock_irqrestore(&hw->lock, flags);
+ return 0;
}

-static void bcm63xx_wdt_hw_stop(void)
+static int bcm63xx_wdt_stop(struct watchdog_device *wdd)
{
+ struct bcm63xx_wdt_hw *hw = to_wdt_hw(wdd);
unsigned long flags;

- raw_spin_lock_irqsave(&bcm63xx_wdt_device.lock, flags);
- bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
- bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
- bcm63xx_wdt_device.running = false;
- raw_spin_unlock_irqrestore(&bcm63xx_wdt_device.lock, flags);
+ raw_spin_lock_irqsave(&hw->lock, flags);
+ bcm_writel(WDT_STOP_1, hw->regs + WDT_CTL_REG);
+ bcm_writel(WDT_STOP_2, hw->regs + WDT_CTL_REG);
+ hw->running = false;
+ raw_spin_unlock_irqrestore(&hw->lock, flags);
+ return 0;
+}
+
+static int bcm63xx_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+ return 0;
}

/* The watchdog interrupt occurs when half the timeout is remaining */
static void bcm63xx_wdt_isr(void *data)
{
- struct bcm63xx_wdt_hw *hw = &bcm63xx_wdt_device;
+ struct bcm63xx_wdt_hw *hw = data;
unsigned long flags;

raw_spin_lock_irqsave(&hw->lock, flags);
@@ -118,147 +119,36 @@ static void bcm63xx_wdt_isr(void *data)
}

ms = timeleft / (WDT_HZ / 1000);
- pr_alert("warning timer fired, reboot in %ums\n", ms);
+ dev_alert(hw->wdd.dev,
+ "warning timer fired, reboot in %ums\n", ms);
}
raw_spin_unlock_irqrestore(&hw->lock, flags);
}

-static int bcm63xx_wdt_settimeout(int new_time)
-{
- if ((new_time <= 0) || (new_time > WDT_MAX_TIME))
- return -EINVAL;
-
- wdt_time = new_time;
-
- return 0;
-}
-
-static int bcm63xx_wdt_open(struct inode *inode, struct file *file)
-{
- if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse))
- return -EBUSY;
-
- bcm63xx_wdt_hw_start();
- return nonseekable_open(inode, file);
-}
-
-static int bcm63xx_wdt_release(struct inode *inode, struct file *file)
-{
- if (expect_close == 42)
- bcm63xx_wdt_hw_stop();
- else {
- pr_crit("Unexpected close, not stopping watchdog!\n");
- bcm63xx_wdt_hw_start();
- }
- clear_bit(0, &bcm63xx_wdt_device.inuse);
- expect_close = 0;
- return 0;
-}
-
-static ssize_t bcm63xx_wdt_write(struct file *file, const char *data,
- size_t len, loff_t *ppos)
-{
- if (len) {
- if (!nowayout) {
- size_t i;
-
- /* In case it was set long ago */
- expect_close = 0;
-
- for (i = 0; i != len; i++) {
- char c;
- if (get_user(c, data + i))
- return -EFAULT;
- if (c == 'V')
- expect_close = 42;
- }
- }
- bcm63xx_wdt_hw_start();
- }
- return len;
-}
-
-static struct watchdog_info bcm63xx_wdt_info = {
- .identity = PFX,
- .options = WDIOF_SETTIMEOUT |
- WDIOF_KEEPALIVEPING |
- WDIOF_MAGICCLOSE,
+static struct watchdog_ops bcm63xx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = bcm63xx_wdt_start,
+ .stop = bcm63xx_wdt_stop,
+ .set_timeout = bcm63xx_wdt_set_timeout,
};

-
-static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
-{
- void __user *argp = (void __user *)arg;
- int __user *p = argp;
- int new_value, retval = -EINVAL;
-
- switch (cmd) {
- case WDIOC_GETSUPPORT:
- return copy_to_user(argp, &bcm63xx_wdt_info,
- sizeof(bcm63xx_wdt_info)) ? -EFAULT : 0;
-
- case WDIOC_GETSTATUS:
- case WDIOC_GETBOOTSTATUS:
- return put_user(0, p);
-
- case WDIOC_SETOPTIONS:
- if (get_user(new_value, p))
- return -EFAULT;
-
- if (new_value & WDIOS_DISABLECARD) {
- bcm63xx_wdt_hw_stop();
- retval = 0;
- }
- if (new_value & WDIOS_ENABLECARD) {
- bcm63xx_wdt_hw_start();
- retval = 0;
- }
-
- return retval;
-
- case WDIOC_KEEPALIVE:
- bcm63xx_wdt_hw_start();
- return 0;
-
- case WDIOC_SETTIMEOUT:
- if (get_user(new_value, p))
- return -EFAULT;
-
- if (bcm63xx_wdt_settimeout(new_value))
- return -EINVAL;
-
- bcm63xx_wdt_hw_start();
-
- case WDIOC_GETTIMEOUT:
- return put_user(wdt_time, p);
-
- default:
- return -ENOTTY;
-
- }
-}
-
-static const struct file_operations bcm63xx_wdt_fops = {
- .owner = THIS_MODULE,
- .llseek = no_llseek,
- .write = bcm63xx_wdt_write,
- .unlocked_ioctl = bcm63xx_wdt_ioctl,
- .open = bcm63xx_wdt_open,
- .release = bcm63xx_wdt_release,
-};
-
-static struct miscdevice bcm63xx_wdt_miscdev = {
- .minor = WATCHDOG_MINOR,
- .name = "watchdog",
- .fops = &bcm63xx_wdt_fops,
+static const struct watchdog_info bcm63xx_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "BCM63xx Watchdog",
};

-
static int bcm63xx_wdt_probe(struct platform_device *pdev)
{
- int ret;
+ struct bcm63xx_wdt_hw *hw;
+ struct watchdog_device *wdd;
struct resource *r;
+ int ret;
+
+ hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL);
+ if (!hw)
+ return -ENOMEM;
+
+ wdd = &hw->wdd;

r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
@@ -266,58 +156,65 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
return -ENODEV;
}

- bcm63xx_wdt_device.regs = devm_ioremap_nocache(&pdev->dev, r->start,
- resource_size(r));
- if (!bcm63xx_wdt_device.regs) {
+ hw->regs = devm_ioremap_nocache(&pdev->dev, r->start, resource_size(r));
+ if (!hw->regs) {
dev_err(&pdev->dev, "failed to remap I/O resources\n");
return -ENXIO;
}

- raw_spin_lock_init(&bcm63xx_wdt_device.lock);
- bcm63xx_wdt_device.running = false;
+ raw_spin_lock_init(&hw->lock);
+ hw->running = false;

- ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, NULL);
+ wdd->parent = &pdev->dev;
+ wdd->ops = &bcm63xx_wdt_ops;
+ wdd->info = &bcm63xx_wdt_info;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = 0xffffffff / WDT_HZ;
+ wdd->timeout = min(30U, wdd->max_timeout);
+
+ platform_set_drvdata(pdev, hw);
+
+ watchdog_init_timeout(wdd, 0, &pdev->dev);
+ watchdog_set_nowayout(wdd, nowayout);
+
+ ret = watchdog_register_device(wdd);
if (ret < 0) {
- dev_err(&pdev->dev, "failed to register wdt timer isr\n");
+ dev_err(&pdev->dev, "failed to register watchdog device\n");
return ret;
}

- if (bcm63xx_wdt_settimeout(wdt_time)) {
- bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME);
- dev_info(&pdev->dev,
- ": wdt_time value must be 1 <= wdt_time <= %d, using %d\n",
- WDT_MAX_TIME, wdt_time);
- }
-
- ret = misc_register(&bcm63xx_wdt_miscdev);
+ ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, hw);
if (ret < 0) {
- dev_err(&pdev->dev, "failed to register watchdog device\n");
- goto unregister_timer;
+ dev_err(&pdev->dev, "failed to register wdt timer isr\n");
+ goto unregister_watchdog;
}

- dev_info(&pdev->dev, " started, timer margin: %d sec\n",
- WDT_DEFAULT_TIME);
+ dev_info(&pdev->dev,
+ "%s at MMIO 0x%p (timeout = %us, max_timeout = %us)",
+ dev_name(wdd->dev), hw->regs,
+ wdd->timeout, wdd->max_timeout);

return 0;

-unregister_timer:
- bcm63xx_timer_unregister(TIMER_WDT_ID);
+unregister_watchdog:
+ watchdog_unregister_device(wdd);
return ret;
}

static int bcm63xx_wdt_remove(struct platform_device *pdev)
{
- if (!nowayout)
- bcm63xx_wdt_hw_stop();
+ struct bcm63xx_wdt_hw *hw = platform_get_drvdata(pdev);

- misc_deregister(&bcm63xx_wdt_miscdev);
bcm63xx_timer_unregister(TIMER_WDT_ID);
+ watchdog_unregister_device(&hw->wdd);
return 0;
}

static void bcm63xx_wdt_shutdown(struct platform_device *pdev)
{
- bcm63xx_wdt_hw_stop();
+ struct bcm63xx_wdt_hw *hw = platform_get_drvdata(pdev);
+
+ bcm63xx_wdt_stop(&hw->wdd);
}

static struct platform_driver bcm63xx_wdt_driver = {
--
2.1.4


--
Simon Arlott

2015-12-01 13:06:46

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 06/11] watchdog: bcm63xx_wdt: Obtain watchdog clock HZ from "periph" clk

Instead of using a fixed clock HZ in the driver, obtain it from the
"periph" clk that the watchdog timer uses.

Signed-off-by: Simon Arlott <[email protected]>
Reviewed-by: Florian Fainelli <[email protected]>
---
drivers/watchdog/bcm63xx_wdt.c | 36 +++++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)

diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index 2257924..0a19731 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -13,6 +13,7 @@

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

+#include <linux/clk.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/kernel.h>
@@ -32,12 +33,14 @@

#define PFX KBUILD_MODNAME

-#define WDT_HZ 50000000 /* Fclk */
+#define WDT_CLK_NAME "periph"

struct bcm63xx_wdt_hw {
struct watchdog_device wdd;
raw_spinlock_t lock;
void __iomem *regs;
+ struct clk *clk;
+ unsigned long clock_hz;
bool running;
};

@@ -54,7 +57,7 @@ static int bcm63xx_wdt_start(struct watchdog_device *wdd)
unsigned long flags;

raw_spin_lock_irqsave(&hw->lock, flags);
- bcm_writel(wdd->timeout * WDT_HZ, hw->regs + WDT_DEFVAL_REG);
+ bcm_writel(wdd->timeout * hw->clock_hz, hw->regs + WDT_DEFVAL_REG);
bcm_writel(WDT_START_1, hw->regs + WDT_CTL_REG);
bcm_writel(WDT_START_2, hw->regs + WDT_CTL_REG);
hw->running = true;
@@ -118,7 +121,7 @@ static void bcm63xx_wdt_isr(void *data)
die(PFX ": watchdog timer expired\n", get_irq_regs());
}

- ms = timeleft / (WDT_HZ / 1000);
+ ms = timeleft / (hw->clock_hz / 1000);
dev_alert(hw->wdd.dev,
"warning timer fired, reboot in %ums\n", ms);
}
@@ -162,6 +165,25 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
return -ENXIO;
}

+ hw->clk = devm_clk_get(&pdev->dev, WDT_CLK_NAME);
+ if (IS_ERR(hw->clk)) {
+ if (PTR_ERR(hw->clk) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "unable to request clock\n");
+ return PTR_ERR(hw->clk);
+ }
+
+ hw->clock_hz = clk_get_rate(hw->clk);
+ if (!hw->clock_hz) {
+ dev_err(&pdev->dev, "unable to fetch clock rate\n");
+ return -EINVAL;
+ }
+
+ ret = clk_prepare_enable(hw->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to enable clock\n");
+ return ret;
+ }
+
raw_spin_lock_init(&hw->lock);
hw->running = false;

@@ -169,7 +191,7 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
wdd->ops = &bcm63xx_wdt_ops;
wdd->info = &bcm63xx_wdt_info;
wdd->min_timeout = 1;
- wdd->max_timeout = 0xffffffff / WDT_HZ;
+ wdd->max_timeout = 0xffffffff / hw->clock_hz;
wdd->timeout = min(30U, wdd->max_timeout);

platform_set_drvdata(pdev, hw);
@@ -180,7 +202,7 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
ret = watchdog_register_device(wdd);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register watchdog device\n");
- return ret;
+ goto disable_clk;
}

ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, hw);
@@ -198,6 +220,9 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)

unregister_watchdog:
watchdog_unregister_device(wdd);
+
+disable_clk:
+ clk_disable_unprepare(hw->clk);
return ret;
}

@@ -207,6 +232,7 @@ static int bcm63xx_wdt_remove(struct platform_device *pdev)

bcm63xx_timer_unregister(TIMER_WDT_ID);
watchdog_unregister_device(&hw->wdd);
+ clk_disable_unprepare(hw->clk);
return 0;
}

--
2.1.4


--
Simon Arlott

2015-12-01 13:08:08

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 07/11] watchdog: bcm63xx_wdt: Add get_timeleft function

Return the remaining time from the hardware control register.

Signed-off-by: Simon Arlott <[email protected]>
---
drivers/watchdog/bcm63xx_wdt.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)

diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index 0a19731..ab4a794 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -78,6 +78,19 @@ static int bcm63xx_wdt_stop(struct watchdog_device *wdd)
return 0;
}

+static unsigned int bcm63xx_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct bcm63xx_wdt_hw *hw = to_wdt_hw(wdd);
+ unsigned long flags;
+ u32 val;
+
+ raw_spin_lock_irqsave(&hw->lock, flags);
+ val = __raw_readl(hw->regs + WDT_CTL_REG);
+ val /= hw->clock_hz;
+ raw_spin_unlock_irqrestore(&hw->lock, flags);
+ return val;
+}
+
static int bcm63xx_wdt_set_timeout(struct watchdog_device *wdd,
unsigned int timeout)
{
@@ -132,6 +145,7 @@ static struct watchdog_ops bcm63xx_wdt_ops = {
.owner = THIS_MODULE,
.start = bcm63xx_wdt_start,
.stop = bcm63xx_wdt_stop,
+ .get_timeleft = bcm63xx_wdt_get_timeleft,
.set_timeout = bcm63xx_wdt_set_timeout,
};

@@ -256,6 +270,7 @@ module_platform_driver(bcm63xx_wdt_driver);

MODULE_AUTHOR("Miguel Gaio <[email protected]>");
MODULE_AUTHOR("Florian Fainelli <[email protected]>");
+MODULE_AUTHOR("Simon Arlott");
MODULE_DESCRIPTION("Driver for the Broadcom BCM63xx SoC watchdog");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bcm63xx-wdt");
--
2.1.4


--
Simon Arlott

2015-12-01 13:09:11

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 08/11] watchdog: bcm63xx_wdt: Warn if the watchdog is currently running

Warn when the device is registered if the hardware watchdog is currently
running and report the remaining time left.

Signed-off-by: Simon Arlott <[email protected]>
---
drivers/watchdog/bcm63xx_wdt.c | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index ab4a794..2312dc2 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -14,6 +14,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/kernel.h>
@@ -159,6 +160,8 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
struct bcm63xx_wdt_hw *hw;
struct watchdog_device *wdd;
struct resource *r;
+ u32 timeleft1, timeleft2;
+ unsigned int timeleft;
int ret;

hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL);
@@ -199,7 +202,6 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
}

raw_spin_lock_init(&hw->lock);
- hw->running = false;

wdd->parent = &pdev->dev;
wdd->ops = &bcm63xx_wdt_ops;
@@ -213,6 +215,23 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
watchdog_init_timeout(wdd, 0, &pdev->dev);
watchdog_set_nowayout(wdd, nowayout);

+ /* Compare two reads of the time left value, 2 clock ticks apart */
+ rmb();
+ timeleft1 = __raw_readl(hw->regs + WDT_CTL_REG);
+ udelay(DIV_ROUND_UP(1000000, hw->clock_hz / 2));
+ /* Ensure the register is read twice */
+ rmb();
+ timeleft2 = __raw_readl(hw->regs + WDT_CTL_REG);
+
+ /* If the time left is changing, the watchdog is running */
+ if (timeleft1 != timeleft2) {
+ hw->running = true;
+ timeleft = bcm63xx_wdt_get_timeleft(wdd);
+ } else {
+ hw->running = false;
+ timeleft = 0;
+ }
+
ret = watchdog_register_device(wdd);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register watchdog device\n");
@@ -230,6 +249,8 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
dev_name(wdd->dev), hw->regs,
wdd->timeout, wdd->max_timeout);

+ if (hw->running)
+ dev_alert(wdd->dev, "running, reboot in %us\n", timeleft);
return 0;

unregister_watchdog:
--
2.1.4


--
Simon Arlott

2015-12-01 13:10:09

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 09/11] watchdog: bcm63xx_wdt: Remove dependency on mach-bcm63xx functions/defines

The bcm_readl() and bcm_writel() functions are only defined for
mach-bcm63xx, replace them with __raw_readl() and raw_writel().

The register defines required for the watchdog are in a mach-bcm63xx
header file. Move them to include/linux/bcm63xx_wdt.h so that they are
also available on other machine types.

Signed-off-by: Simon Arlott <[email protected]>
---
MAINTAINERS | 1 +
arch/mips/bcm63xx/prom.c | 1 +
arch/mips/bcm63xx/setup.c | 1 +
arch/mips/include/asm/mach-bcm63xx/bcm63xx_regs.h | 22 --------------------
drivers/watchdog/bcm63xx_wdt.c | 25 +++++++++++------------
include/linux/bcm63xx_wdt.h | 22 ++++++++++++++++++++
6 files changed, 37 insertions(+), 35 deletions(-)
create mode 100644 include/linux/bcm63xx_wdt.h

diff --git a/MAINTAINERS b/MAINTAINERS
index cd553a0..a381176 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2376,6 +2376,7 @@ F: arch/mips/boot/dts/brcm/bcm*.dts*
F: drivers/irqchip/irq-bcm63*
F: drivers/irqchip/irq-bcm7*
F: drivers/irqchip/irq-brcmstb*
+F: include/linux/bcm63xx_wdt.h

BROADCOM TG3 GIGABIT ETHERNET DRIVER
M: Prashant Sreedharan <[email protected]>
diff --git a/arch/mips/bcm63xx/prom.c b/arch/mips/bcm63xx/prom.c
index 7019e29..d2800fb 100644
--- a/arch/mips/bcm63xx/prom.c
+++ b/arch/mips/bcm63xx/prom.c
@@ -6,6 +6,7 @@
* Copyright (C) 2008 Maxime Bizon <[email protected]>
*/

+#include <linux/bcm63xx_wdt.h>
#include <linux/init.h>
#include <linux/bootmem.h>
#include <linux/smp.h>
diff --git a/arch/mips/bcm63xx/setup.c b/arch/mips/bcm63xx/setup.c
index 2be9caa..181402f 100644
--- a/arch/mips/bcm63xx/setup.c
+++ b/arch/mips/bcm63xx/setup.c
@@ -6,6 +6,7 @@
* Copyright (C) 2008 Maxime Bizon <[email protected]>
*/

+#include <linux/bcm63xx_wdt.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/delay.h>
diff --git a/arch/mips/include/asm/mach-bcm63xx/bcm63xx_regs.h b/arch/mips/include/asm/mach-bcm63xx/bcm63xx_regs.h
index 5035f09..16a745b 100644
--- a/arch/mips/include/asm/mach-bcm63xx/bcm63xx_regs.h
+++ b/arch/mips/include/asm/mach-bcm63xx/bcm63xx_regs.h
@@ -441,28 +441,6 @@


/*************************************************************************
- * _REG relative to RSET_WDT
- *************************************************************************/
-
-/* Watchdog default count register */
-#define WDT_DEFVAL_REG 0x0
-
-/* Watchdog control register */
-#define WDT_CTL_REG 0x4
-
-/* Watchdog control register constants */
-#define WDT_START_1 (0xff00)
-#define WDT_START_2 (0x00ff)
-#define WDT_STOP_1 (0xee00)
-#define WDT_STOP_2 (0x00ee)
-
-/* Watchdog reset length register */
-#define WDT_RSTLEN_REG 0x8
-
-/* Watchdog soft reset register (BCM6328 only) */
-#define WDT_SOFTRESET_REG 0xc
-
-/*************************************************************************
* _REG relative to RSET_GPIO
*************************************************************************/

diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index 2312dc2..f409523 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -13,6 +13,7 @@

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

+#include <linux/bcm63xx_wdt.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/errno.h>
@@ -27,8 +28,6 @@
#include <linux/resource.h>
#include <linux/platform_device.h>

-#include <bcm63xx_cpu.h>
-#include <bcm63xx_io.h>
#include <bcm63xx_regs.h>
#include <bcm63xx_timer.h>

@@ -58,9 +57,9 @@ static int bcm63xx_wdt_start(struct watchdog_device *wdd)
unsigned long flags;

raw_spin_lock_irqsave(&hw->lock, flags);
- bcm_writel(wdd->timeout * hw->clock_hz, hw->regs + WDT_DEFVAL_REG);
- bcm_writel(WDT_START_1, hw->regs + WDT_CTL_REG);
- bcm_writel(WDT_START_2, hw->regs + WDT_CTL_REG);
+ __raw_writel(wdd->timeout * hw->clock_hz, hw->regs + WDT_DEFVAL_REG);
+ __raw_writel(WDT_START_1, hw->regs + WDT_CTL_REG);
+ __raw_writel(WDT_START_2, hw->regs + WDT_CTL_REG);
hw->running = true;
raw_spin_unlock_irqrestore(&hw->lock, flags);
return 0;
@@ -72,8 +71,8 @@ static int bcm63xx_wdt_stop(struct watchdog_device *wdd)
unsigned long flags;

raw_spin_lock_irqsave(&hw->lock, flags);
- bcm_writel(WDT_STOP_1, hw->regs + WDT_CTL_REG);
- bcm_writel(WDT_STOP_2, hw->regs + WDT_CTL_REG);
+ __raw_writel(WDT_STOP_1, hw->regs + WDT_CTL_REG);
+ __raw_writel(WDT_STOP_2, hw->regs + WDT_CTL_REG);
hw->running = false;
raw_spin_unlock_irqrestore(&hw->lock, flags);
return 0;
@@ -108,10 +107,10 @@ static void bcm63xx_wdt_isr(void *data)
raw_spin_lock_irqsave(&hw->lock, flags);
if (!hw->running) {
/* Stop the watchdog as it shouldn't be running */
- bcm_writel(WDT_STOP_1, hw->regs + WDT_CTL_REG);
- bcm_writel(WDT_STOP_2, hw->regs + WDT_CTL_REG);
+ __raw_writel(WDT_STOP_1, hw->regs + WDT_CTL_REG);
+ __raw_writel(WDT_STOP_2, hw->regs + WDT_CTL_REG);
} else {
- u32 timeleft = bcm_readl(hw->regs + WDT_CTL_REG);
+ u32 timeleft = __raw_readl(hw->regs + WDT_CTL_REG);
u32 ms;

if (timeleft >= 2) {
@@ -125,9 +124,9 @@ static void bcm63xx_wdt_isr(void *data)
* This is done with a lock held in case userspace is
* trying to restart the watchdog on another CPU.
*/
- bcm_writel(timeleft, hw->regs + WDT_DEFVAL_REG);
- bcm_writel(WDT_START_1, hw->regs + WDT_CTL_REG);
- bcm_writel(WDT_START_2, hw->regs + WDT_CTL_REG);
+ __raw_writel(timeleft, hw->regs + WDT_DEFVAL_REG);
+ __raw_writel(WDT_START_1, hw->regs + WDT_CTL_REG);
+ __raw_writel(WDT_START_2, hw->regs + WDT_CTL_REG);
} else {
/* The watchdog cannot be started with a time of less
* than 2 ticks (it won't fire).
diff --git a/include/linux/bcm63xx_wdt.h b/include/linux/bcm63xx_wdt.h
new file mode 100644
index 0000000..2e9210b
--- /dev/null
+++ b/include/linux/bcm63xx_wdt.h
@@ -0,0 +1,22 @@
+#ifndef LINUX_BCM63XX_WDT_H_
+#define LINUX_BCM63XX_WDT_H_
+
+/* Watchdog default count register */
+#define WDT_DEFVAL_REG 0x0
+
+/* Watchdog control register */
+#define WDT_CTL_REG 0x4
+
+/* Watchdog control register constants */
+#define WDT_START_1 (0xff00)
+#define WDT_START_2 (0x00ff)
+#define WDT_STOP_1 (0xee00)
+#define WDT_STOP_2 (0x00ee)
+
+/* Watchdog reset length register */
+#define WDT_RSTLEN_REG 0x8
+
+/* Watchdog soft reset register (BCM6328 only) */
+#define WDT_SOFTRESET_REG 0xc
+
+#endif
--
2.1.4



--
Simon Arlott

2015-12-01 13:11:06

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 10/11] watchdog: bcm63xx_wdt: Use bcm63xx_timer interrupt directly

There is only one user of bcm63xx_timer and that is the watchdog.
To allow the watchdog driver to be used on machine types other than
mach-bcm63xx, it needs to use an interrupt instead of a custom register
function.

Modify bcm63xx_timer to only disable the timers (so that they don't
interfere with the watchdog if an interrupt occurs) and remove its
exported functions.

Use the timer interrupt directly in bcm63xx_wdt.

Signed-off-by: Simon Arlott <[email protected]>
---
arch/mips/bcm63xx/dev-wdt.c | 7 +
arch/mips/bcm63xx/timer.c | 181 +--------------------
arch/mips/include/asm/mach-bcm63xx/bcm63xx_timer.h | 11 --
drivers/watchdog/bcm63xx_wdt.c | 41 +++--
4 files changed, 36 insertions(+), 204 deletions(-)
delete mode 100644 arch/mips/include/asm/mach-bcm63xx/bcm63xx_timer.h

diff --git a/arch/mips/bcm63xx/dev-wdt.c b/arch/mips/bcm63xx/dev-wdt.c
index 2a2346a..a7a5497 100644
--- a/arch/mips/bcm63xx/dev-wdt.c
+++ b/arch/mips/bcm63xx/dev-wdt.c
@@ -17,6 +17,11 @@ static struct resource wdt_resources[] = {
.end = -1, /* filled at runtime */
.flags = IORESOURCE_MEM,
},
+ {
+ .start = -1, /* filled at runtime */
+ .end = -1, /* filled at runtime */
+ .flags = IORESOURCE_IRQ,
+ },
};

static struct platform_device bcm63xx_wdt_device = {
@@ -32,6 +37,8 @@ int __init bcm63xx_wdt_register(void)
wdt_resources[0].end = wdt_resources[0].start;
wdt_resources[0].end += RSET_WDT_SIZE - 1;

+ wdt_resources[1].start = bcm63xx_get_irq_number(IRQ_TIMER);
+
return platform_device_register(&bcm63xx_wdt_device);
}
arch_initcall(bcm63xx_wdt_register);
diff --git a/arch/mips/bcm63xx/timer.c b/arch/mips/bcm63xx/timer.c
index 2110359..9c7b41a6 100644
--- a/arch/mips/bcm63xx/timer.c
+++ b/arch/mips/bcm63xx/timer.c
@@ -9,196 +9,23 @@
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/module.h>
-#include <linux/spinlock.h>
-#include <linux/interrupt.h>
-#include <linux/clk.h>
#include <bcm63xx_cpu.h>
#include <bcm63xx_io.h>
-#include <bcm63xx_timer.h>
#include <bcm63xx_regs.h>

-static DEFINE_RAW_SPINLOCK(timer_reg_lock);
-static DEFINE_RAW_SPINLOCK(timer_data_lock);
-static struct clk *periph_clk;
-
-static struct timer_data {
- void (*cb)(void *);
- void *data;
-} timer_data[BCM63XX_TIMER_COUNT];
-
-static irqreturn_t timer_interrupt(int irq, void *dev_id)
-{
- u32 stat;
- int i;
-
- raw_spin_lock(&timer_reg_lock);
- stat = bcm_timer_readl(TIMER_IRQSTAT_REG);
- bcm_timer_writel(stat, TIMER_IRQSTAT_REG);
- raw_spin_unlock(&timer_reg_lock);
-
- for (i = 0; i < BCM63XX_TIMER_COUNT; i++) {
- if (!(stat & TIMER_IRQSTAT_TIMER_CAUSE(i)))
- continue;
-
- raw_spin_lock(&timer_data_lock);
- if (!timer_data[i].cb) {
- raw_spin_unlock(&timer_data_lock);
- continue;
- }
-
- timer_data[i].cb(timer_data[i].data);
- raw_spin_unlock(&timer_data_lock);
- }
-
- return IRQ_HANDLED;
-}
-
-int bcm63xx_timer_enable(int id)
-{
- u32 reg;
- unsigned long flags;
-
- if (id >= BCM63XX_TIMER_COUNT)
- return -EINVAL;
-
- raw_spin_lock_irqsave(&timer_reg_lock, flags);
-
- reg = bcm_timer_readl(TIMER_CTLx_REG(id));
- reg |= TIMER_CTL_ENABLE_MASK;
- bcm_timer_writel(reg, TIMER_CTLx_REG(id));
-
- reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
- reg |= TIMER_IRQSTAT_TIMER_IR_EN(id);
- bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
-
- raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
- return 0;
-}
-
-EXPORT_SYMBOL(bcm63xx_timer_enable);
-
-int bcm63xx_timer_disable(int id)
+static int bcm63xx_timer_init(void)
{
u32 reg;
- unsigned long flags;
-
- if (id >= BCM63XX_TIMER_COUNT)
- return -EINVAL;
-
- raw_spin_lock_irqsave(&timer_reg_lock, flags);
-
- reg = bcm_timer_readl(TIMER_CTLx_REG(id));
- reg &= ~TIMER_CTL_ENABLE_MASK;
- bcm_timer_writel(reg, TIMER_CTLx_REG(id));
-
- reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
- reg &= ~TIMER_IRQSTAT_TIMER_IR_EN(id);
- bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
-
- raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
- return 0;
-}
-
-EXPORT_SYMBOL(bcm63xx_timer_disable);
-
-int bcm63xx_timer_register(int id, void (*callback)(void *data), void *data)
-{
- unsigned long flags;
- int ret;
-
- if (id >= BCM63XX_TIMER_COUNT || !callback)
- return -EINVAL;
-
- ret = 0;
- raw_spin_lock_irqsave(&timer_data_lock, flags);
- if (timer_data[id].cb) {
- ret = -EBUSY;
- goto out;
- }
-
- timer_data[id].cb = callback;
- timer_data[id].data = data;
-
-out:
- raw_spin_unlock_irqrestore(&timer_data_lock, flags);
- return ret;
-}
-
-EXPORT_SYMBOL(bcm63xx_timer_register);
-
-void bcm63xx_timer_unregister(int id)
-{
- unsigned long flags;
-
- if (id >= BCM63XX_TIMER_COUNT)
- return;
-
- raw_spin_lock_irqsave(&timer_data_lock, flags);
- timer_data[id].cb = NULL;
- raw_spin_unlock_irqrestore(&timer_data_lock, flags);
-}
-
-EXPORT_SYMBOL(bcm63xx_timer_unregister);
-
-unsigned int bcm63xx_timer_countdown(unsigned int countdown_us)
-{
- return (clk_get_rate(periph_clk) / (1000 * 1000)) * countdown_us;
-}
-
-EXPORT_SYMBOL(bcm63xx_timer_countdown);
-
-int bcm63xx_timer_set(int id, int monotonic, unsigned int countdown_us)
-{
- u32 reg, countdown;
- unsigned long flags;
-
- if (id >= BCM63XX_TIMER_COUNT)
- return -EINVAL;
-
- countdown = bcm63xx_timer_countdown(countdown_us);
- if (countdown & ~TIMER_CTL_COUNTDOWN_MASK)
- return -EINVAL;
-
- raw_spin_lock_irqsave(&timer_reg_lock, flags);
- reg = bcm_timer_readl(TIMER_CTLx_REG(id));
-
- if (monotonic)
- reg &= ~TIMER_CTL_MONOTONIC_MASK;
- else
- reg |= TIMER_CTL_MONOTONIC_MASK;
-
- reg &= ~TIMER_CTL_COUNTDOWN_MASK;
- reg |= countdown;
- bcm_timer_writel(reg, TIMER_CTLx_REG(id));
-
- raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
- return 0;
-}
-
-EXPORT_SYMBOL(bcm63xx_timer_set);
-
-int bcm63xx_timer_init(void)
-{
- int ret, irq;
- u32 reg;

+ /* Disable all timers so that they won't interfere with use of the
+ * timer interrupt by the watchdog.
+ */
reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
reg &= ~TIMER_IRQSTAT_TIMER0_IR_EN;
reg &= ~TIMER_IRQSTAT_TIMER1_IR_EN;
reg &= ~TIMER_IRQSTAT_TIMER2_IR_EN;
bcm_timer_writel(reg, TIMER_IRQSTAT_REG);

- periph_clk = clk_get(NULL, "periph");
- if (IS_ERR(periph_clk))
- return -ENODEV;
-
- irq = bcm63xx_get_irq_number(IRQ_TIMER);
- ret = request_irq(irq, timer_interrupt, 0, "bcm63xx_timer", NULL);
- if (ret) {
- pr_err("%s: failed to register irq\n", __func__);
- return ret;
- }
-
return 0;
}

diff --git a/arch/mips/include/asm/mach-bcm63xx/bcm63xx_timer.h b/arch/mips/include/asm/mach-bcm63xx/bcm63xx_timer.h
deleted file mode 100644
index c0fce83..0000000
--- a/arch/mips/include/asm/mach-bcm63xx/bcm63xx_timer.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef BCM63XX_TIMER_H_
-#define BCM63XX_TIMER_H_
-
-int bcm63xx_timer_register(int id, void (*callback)(void *data), void *data);
-void bcm63xx_timer_unregister(int id);
-int bcm63xx_timer_set(int id, int monotonic, unsigned int countdown_us);
-int bcm63xx_timer_enable(int id);
-int bcm63xx_timer_disable(int id);
-unsigned int bcm63xx_timer_countdown(unsigned int countdown_us);
-
-#endif /* !BCM63XX_TIMER_H_ */
diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index f409523..fa6c28b 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -17,6 +17,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/errno.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -28,9 +29,6 @@
#include <linux/resource.h>
#include <linux/platform_device.h>

-#include <bcm63xx_regs.h>
-#include <bcm63xx_timer.h>
-
#define PFX KBUILD_MODNAME

#define WDT_CLK_NAME "periph"
@@ -41,6 +39,7 @@ struct bcm63xx_wdt_hw {
void __iomem *regs;
struct clk *clk;
unsigned long clock_hz;
+ int irq;
bool running;
};

@@ -99,7 +98,7 @@ static int bcm63xx_wdt_set_timeout(struct watchdog_device *wdd,
}

/* The watchdog interrupt occurs when half the timeout is remaining */
-static void bcm63xx_wdt_isr(void *data)
+static irqreturn_t bcm63xx_wdt_interrupt(int irq, void *data)
{
struct bcm63xx_wdt_hw *hw = data;
unsigned long flags;
@@ -139,6 +138,7 @@ static void bcm63xx_wdt_isr(void *data)
"warning timer fired, reboot in %ums\n", ms);
}
raw_spin_unlock_irqrestore(&hw->lock, flags);
+ return IRQ_HANDLED;
}

static struct watchdog_ops bcm63xx_wdt_ops = {
@@ -237,24 +237,31 @@ static int bcm63xx_wdt_probe(struct platform_device *pdev)
goto disable_clk;
}

- ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, hw);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to register wdt timer isr\n");
- goto unregister_watchdog;
+ hw->irq = platform_get_irq(pdev, 0);
+ if (hw->irq >= 0) {
+ ret = devm_request_irq(&pdev->dev, hw->irq,
+ bcm63xx_wdt_interrupt, IRQF_TIMER,
+ dev_name(&pdev->dev), hw);
+ if (ret)
+ hw->irq = -1;
}

- dev_info(&pdev->dev,
- "%s at MMIO 0x%p (timeout = %us, max_timeout = %us)",
- dev_name(wdd->dev), hw->regs,
- wdd->timeout, wdd->max_timeout);
+ if (hw->irq >= 0) {
+ dev_info(&pdev->dev,
+ "%s at MMIO 0x%p (irq = %d, timeout = %us, max_timeout = %us)",
+ dev_name(wdd->dev), hw->regs, hw->irq,
+ wdd->timeout, wdd->max_timeout);
+ } else {
+ dev_info(&pdev->dev,
+ "%s at MMIO 0x%p (timeout = %us, max_timeout = %us)",
+ dev_name(wdd->dev), hw->regs,
+ wdd->timeout, wdd->max_timeout);
+ }

if (hw->running)
dev_alert(wdd->dev, "running, reboot in %us\n", timeleft);
return 0;

-unregister_watchdog:
- watchdog_unregister_device(wdd);
-
disable_clk:
clk_disable_unprepare(hw->clk);
return ret;
@@ -264,7 +271,9 @@ static int bcm63xx_wdt_remove(struct platform_device *pdev)
{
struct bcm63xx_wdt_hw *hw = platform_get_drvdata(pdev);

- bcm63xx_timer_unregister(TIMER_WDT_ID);
+ if (hw->irq >= 0)
+ devm_free_irq(&pdev->dev, hw->irq, hw);
+
watchdog_unregister_device(&hw->wdd);
clk_disable_unprepare(hw->clk);
return 0;
--
2.1.4


--
Simon Arlott

2015-12-01 13:12:10

by Simon Arlott

[permalink] [raw]
Subject: [PATCH 11/11] watchdog: bcm63xx_wdt: Use brcm,bcm6345-wdt device tree binding

Add of_match_table for "brcm,bcm6345-wdt".

Use a NULL clock name when not on mach-bcm63xx so that the device tree
clock name does not have to be "periph".

Allow the watchdog to be selected on BMIPS_GENERIC and select the BCM6345
timer interrupt handler.

Signed-off-by: Simon Arlott <[email protected]>
---
drivers/watchdog/Kconfig | 3 ++-
drivers/watchdog/bcm63xx_wdt.c | 14 +++++++++++++-
2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 6815b74..0c50add 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1272,8 +1272,9 @@ config OCTEON_WDT

config BCM63XX_WDT
tristate "Broadcom BCM63xx hardware watchdog"
- depends on BCM63XX
+ depends on BCM63XX || BMIPS_GENERIC
select WATCHDOG_CORE
+ select BCM6345_L2_TIMER_IRQ if BMIPS_GENERIC
help
Watchdog driver for the built in watchdog hardware in Broadcom
BCM63xx SoC.
diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c
index fa6c28b..4db4145 100644
--- a/drivers/watchdog/bcm63xx_wdt.c
+++ b/drivers/watchdog/bcm63xx_wdt.c
@@ -22,6 +22,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
+#include <linux/mod_devicetable.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/watchdog.h>
@@ -31,7 +32,11 @@

#define PFX KBUILD_MODNAME

-#define WDT_CLK_NAME "periph"
+#ifdef CONFIG_BCM63XX
+# define WDT_CLK_NAME "periph"
+#else
+# define WDT_CLK_NAME NULL
+#endif

struct bcm63xx_wdt_hw {
struct watchdog_device wdd;
@@ -286,12 +291,19 @@ static void bcm63xx_wdt_shutdown(struct platform_device *pdev)
bcm63xx_wdt_stop(&hw->wdd);
}

+static const struct of_device_id bcm63xx_wdt_dt_ids[] = {
+ { .compatible = "brcm,bcm6345-wdt" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, bcm63xx_wdt_dt_ids);
+
static struct platform_driver bcm63xx_wdt_driver = {
.probe = bcm63xx_wdt_probe,
.remove = bcm63xx_wdt_remove,
.shutdown = bcm63xx_wdt_shutdown,
.driver = {
.name = "bcm63xx-wdt",
+ .of_match_table = bcm63xx_wdt_dt_ids,
}
};

--
2.1.4


--
Simon Arlott

2015-12-03 17:22:47

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH 02/11] MIPS: bmips: Add bcm6345-l2-timer interrupt controller

On 01/12/15 13:02, Simon Arlott wrote:
> Add the BCM6345/BCM6318 timer as an interrupt controller so that it can be
> used by the watchdog to warn that its timer will expire soon.
>
> Support for clocksource/clockevents is not implemented as the timer
> interrupt is not per CPU (except on the BCM6318) and the MIPS clock is
> better. This could be added later if required without changing the device
> tree binding.
>
> Signed-off-by: Simon Arlott <[email protected]>
> ---
> drivers/irqchip/Kconfig | 5 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-bcm6345-l2-timer.c | 386 +++++++++++++++++++++++++++++++++
> 3 files changed, 392 insertions(+)
> create mode 100644 drivers/irqchip/irq-bcm6345-l2-timer.c

I'm not sure how useful it is to have a bunch of timers that are just
turned off, but from an irqchip PoV:

Acked-by: Marc Zyngier <[email protected]>

M.
--
Jazz is not dead. It just smells funny...