Merge branch 'hpe/gxp-soc' into arm/late
Patch series from Nick Hawkins: "The GXP is the HPE BMC SoC that is used in the majority of HPE current generation servers. Traditionally the asic will last multiple generations of server before being replaced. Info about SoC: HPE GXP is the name of the HPE Soc. This SoC is used to implement many BMC features at HPE. It supports ARMv7 architecture based on the Cortex A9 core. It is capable of using an AXI bus to which a memory controller is attached. It has multiple SPI interfaces to connect boot flash and BIOS flash. It uses a 10/100/1000 MAC for network connectivity. It has multiple i2c engines to drive connectivity with a host infrastructure. The initial patches enable the watchdog and timer enabling the host to be able to boot." * hpe/gxp-soc: MAINTAINERS: Introduce HPE GXP Architecture ARM: dts: Introduce HPE GXP Device tree dt-bindings: arm: hpe: add GXP Support dt-bindings: timer: hpe,gxp-timer: Add HPE GXP Timer and Watchdog clocksource/drivers/timer-gxp: Add HPE GXP Timer watchdog: hpe-wdt: Introduce HPE GXP Watchdog ARM: configs: multi_v7_defconfig: Add HPE GXP ARCH ARM: hpe: Introduce the HPE GXP architecture Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
commit
3e11194631
|
@ -0,0 +1,27 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/arm/hpe,gxp.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: HPE BMC GXP platforms
|
||||
|
||||
maintainers:
|
||||
- Nick Hawkins <nick.hawkins@hpe.com>
|
||||
- Jean-Marie Verdun <verdun@hpe.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- description: GXP Based Boards
|
||||
items:
|
||||
- enum:
|
||||
- hpe,gxp-dl360gen10
|
||||
- const: hpe,gxp
|
||||
|
||||
required:
|
||||
- compatible
|
||||
|
||||
additionalProperties: true
|
||||
|
||||
...
|
|
@ -0,0 +1,47 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/timer/hpe,gxp-timer.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: HPE GXP Timer
|
||||
|
||||
maintainers:
|
||||
- Nick Hawkins <nick.hawkins@hpe.com>
|
||||
- Jean-Marie Verdun <verdun@hpe.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: hpe,gxp-timer
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
clock-names:
|
||||
const: iop
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
timer@c0000000 {
|
||||
compatible = "hpe,gxp-timer";
|
||||
reg = <0x80 0x16>;
|
||||
interrupts = <0>;
|
||||
interrupt-parent = <&vic0>;
|
||||
clocks = <&iopclk>;
|
||||
clock-names = "iop";
|
||||
};
|
12
MAINTAINERS
12
MAINTAINERS
|
@ -2131,6 +2131,18 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/kristoffer/linux-hpc.git
|
|||
F: arch/arm/mach-sa1100/include/mach/jornada720.h
|
||||
F: arch/arm/mach-sa1100/jornada720.c
|
||||
|
||||
ARM/HPE GXP ARCHITECTURE
|
||||
M: Jean-Marie Verdun <verdun@hpe.com>
|
||||
M: Nick Hawkins <nick.hawkins@hpe.com>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/arm/hpe,gxp.yaml
|
||||
F: Documentation/devicetree/bindings/timer/hpe,gxp-timer.yaml
|
||||
F: arch/arm/boot/dts/hpe-bmc*
|
||||
F: arch/arm/boot/dts/hpe-gxp*
|
||||
F: arch/arm/mach-hpe/
|
||||
F: drivers/clocksource/timer-gxp.c
|
||||
F: drivers/watchdog/gxp-wdt.c
|
||||
|
||||
ARM/IGEP MACHINE SUPPORT
|
||||
M: Enric Balletbo i Serra <eballetbo@gmail.com>
|
||||
M: Javier Martinez Canillas <javier@dowhile0.org>
|
||||
|
|
|
@ -550,6 +550,8 @@ source "arch/arm/mach-highbank/Kconfig"
|
|||
|
||||
source "arch/arm/mach-hisi/Kconfig"
|
||||
|
||||
source "arch/arm/mach-hpe/Kconfig"
|
||||
|
||||
source "arch/arm/mach-imx/Kconfig"
|
||||
|
||||
source "arch/arm/mach-iop32x/Kconfig"
|
||||
|
|
|
@ -179,6 +179,7 @@ machine-$(CONFIG_ARCH_FOOTBRIDGE) += footbridge
|
|||
machine-$(CONFIG_ARCH_GEMINI) += gemini
|
||||
machine-$(CONFIG_ARCH_HIGHBANK) += highbank
|
||||
machine-$(CONFIG_ARCH_HISI) += hisi
|
||||
machine-$(CONFIG_ARCH_HPE) += hpe
|
||||
machine-$(CONFIG_ARCH_IOP32X) += iop32x
|
||||
machine-$(CONFIG_ARCH_IXP4XX) += ixp4xx
|
||||
machine-$(CONFIG_ARCH_KEYSTONE) += keystone
|
||||
|
|
|
@ -259,6 +259,8 @@ dtb-$(CONFIG_ARCH_HISI) += \
|
|||
hi3519-demb.dtb
|
||||
dtb-$(CONFIG_ARCH_HIX5HD2) += \
|
||||
hisi-x5hd2-dkb.dtb
|
||||
dtb-$(CONFIG_ARCH_HPE_GXP) += \
|
||||
hpe-bmc-dl360gen10.dtb
|
||||
dtb-$(CONFIG_ARCH_INTEGRATOR) += \
|
||||
integratorap.dtb \
|
||||
integratorap-im-pd1.dtb \
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Device Tree file for HPE DL360Gen10
|
||||
*/
|
||||
|
||||
/include/ "hpe-gxp.dtsi"
|
||||
|
||||
/ {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "hpe,gxp-dl360gen10", "hpe,gxp";
|
||||
model = "Hewlett Packard Enterprise ProLiant dl360 Gen10";
|
||||
|
||||
aliases {
|
||||
serial0 = &uartc;
|
||||
};
|
||||
|
||||
chosen {
|
||||
stdout-path = "serial0:115200n8";
|
||||
};
|
||||
|
||||
memory@40000000 {
|
||||
device_type = "memory";
|
||||
reg = <0x40000000 0x20000000>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,127 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Device Tree file for HPE GXP
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
/ {
|
||||
model = "Hewlett Packard Enterprise GXP BMC";
|
||||
compatible = "hpe,gxp";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
cpus {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
cpu@0 {
|
||||
compatible = "arm,cortex-a9";
|
||||
reg = <0>;
|
||||
device_type = "cpu";
|
||||
next-level-cache = <&L2>;
|
||||
};
|
||||
};
|
||||
|
||||
clocks {
|
||||
pll: clock-0 {
|
||||
compatible = "fixed-clock";
|
||||
#clock-cells = <0>;
|
||||
clock-frequency = <1600000000>;
|
||||
};
|
||||
|
||||
iopclk: clock-1 {
|
||||
compatible = "fixed-factor-clock";
|
||||
#clock-cells = <0>;
|
||||
clock-div = <4>;
|
||||
clock-mult = <1>;
|
||||
clocks = <&pll>;
|
||||
};
|
||||
};
|
||||
|
||||
axi {
|
||||
compatible = "simple-bus";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges;
|
||||
dma-ranges;
|
||||
|
||||
L2: cache-controller@b0040000 {
|
||||
compatible = "arm,pl310-cache";
|
||||
reg = <0xb0040000 0x1000>;
|
||||
cache-unified;
|
||||
cache-level = <2>;
|
||||
};
|
||||
|
||||
ahb@c0000000 {
|
||||
compatible = "simple-bus";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
ranges = <0x0 0xc0000000 0x30000000>;
|
||||
dma-ranges;
|
||||
|
||||
vic0: interrupt-controller@eff0000 {
|
||||
compatible = "arm,pl192-vic";
|
||||
reg = <0xeff0000 0x1000>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
};
|
||||
|
||||
vic1: interrupt-controller@80f00000 {
|
||||
compatible = "arm,pl192-vic";
|
||||
reg = <0x80f00000 0x1000>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <1>;
|
||||
};
|
||||
|
||||
uarta: serial@e0 {
|
||||
compatible = "ns16550a";
|
||||
reg = <0xe0 0x8>;
|
||||
interrupts = <17>;
|
||||
interrupt-parent = <&vic0>;
|
||||
clock-frequency = <1846153>;
|
||||
reg-shift = <0>;
|
||||
};
|
||||
|
||||
uartb: serial@e8 {
|
||||
compatible = "ns16550a";
|
||||
reg = <0xe8 0x8>;
|
||||
interrupts = <18>;
|
||||
interrupt-parent = <&vic0>;
|
||||
clock-frequency = <1846153>;
|
||||
reg-shift = <0>;
|
||||
};
|
||||
|
||||
uartc: serial@f0 {
|
||||
compatible = "ns16550a";
|
||||
reg = <0xf0 0x8>;
|
||||
interrupts = <19>;
|
||||
interrupt-parent = <&vic0>;
|
||||
clock-frequency = <1846153>;
|
||||
reg-shift = <0>;
|
||||
};
|
||||
|
||||
usb0: usb@efe0000 {
|
||||
compatible = "hpe,gxp-ehci", "generic-ehci";
|
||||
reg = <0xefe0000 0x100>;
|
||||
interrupts = <7>;
|
||||
interrupt-parent = <&vic0>;
|
||||
};
|
||||
|
||||
st: timer@80 {
|
||||
compatible = "hpe,gxp-timer";
|
||||
reg = <0x80 0x16>;
|
||||
interrupts = <0>;
|
||||
interrupt-parent = <&vic0>;
|
||||
clocks = <&iopclk>;
|
||||
clock-names = "iop";
|
||||
};
|
||||
|
||||
usb1: usb@efe0100 {
|
||||
compatible = "hpe,gxp-ohci", "generic-ohci";
|
||||
reg = <0xefe0100 0x110>;
|
||||
interrupts = <6>;
|
||||
interrupt-parent = <&vic0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -43,6 +43,8 @@ CONFIG_ARCH_HI3xxx=y
|
|||
CONFIG_ARCH_HIP01=y
|
||||
CONFIG_ARCH_HIP04=y
|
||||
CONFIG_ARCH_HIX5HD2=y
|
||||
CONFIG_ARCH_HPE=y
|
||||
CONFIG_ARCH_HPE_GXP=y
|
||||
CONFIG_ARCH_MXC=y
|
||||
CONFIG_SOC_IMX50=y
|
||||
CONFIG_SOC_IMX51=y
|
||||
|
@ -563,6 +565,7 @@ CONFIG_BCM47XX_WDT=y
|
|||
CONFIG_BCM2835_WDT=y
|
||||
CONFIG_BCM_KONA_WDT=y
|
||||
CONFIG_BCM7038_WDT=m
|
||||
CONFIG_GXP_WATCHDOG=y
|
||||
CONFIG_BCMA_HOST_SOC=y
|
||||
CONFIG_BCMA_DRIVER_GMAC_CMN=y
|
||||
CONFIG_BCMA_DRIVER_GPIO=y
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
menuconfig ARCH_HPE
|
||||
bool "HPE SoC support"
|
||||
depends on ARCH_MULTI_V7
|
||||
help
|
||||
This enables support for HPE ARM based BMC chips.
|
||||
if ARCH_HPE
|
||||
|
||||
config ARCH_HPE_GXP
|
||||
bool "HPE GXP SoC"
|
||||
depends on ARCH_MULTI_V7
|
||||
select ARM_VIC
|
||||
select GENERIC_IRQ_CHIP
|
||||
select CLKSRC_MMIO
|
||||
help
|
||||
HPE GXP is the name of the HPE Soc. This SoC is used to implement many
|
||||
BMC features at HPE. It supports ARMv7 architecture based on the Cortex
|
||||
A9 core. It is capable of using an AXI bus to which a memory controller
|
||||
is attached. It has multiple SPI interfaces to connect boot flash and
|
||||
BIOS flash. It uses a 10/100/1000 MAC for network connectivity. It
|
||||
has multiple i2c engines to drive connectivity with a host
|
||||
infrastructure.
|
||||
|
||||
endif
|
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_ARCH_HPE_GXP) += gxp.o
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
|
||||
|
||||
#include <linux/of_platform.h>
|
||||
#include <asm/mach/arch.h>
|
||||
|
||||
static const char * const gxp_board_dt_compat[] = {
|
||||
"hpe,gxp",
|
||||
NULL,
|
||||
};
|
||||
|
||||
DT_MACHINE_START(GXP_DT, "HPE GXP")
|
||||
.dt_compat = gxp_board_dt_compat,
|
||||
.l2c_aux_val = 0,
|
||||
.l2c_aux_mask = ~0,
|
||||
MACHINE_END
|
|
@ -597,6 +597,14 @@ config CLKSRC_ST_LPC
|
|||
Enable this option to use the Low Power controller timer
|
||||
as clocksource.
|
||||
|
||||
config GXP_TIMER
|
||||
bool "GXP timer driver" if COMPILE_TEST && !ARCH_HPE
|
||||
default ARCH_HPE
|
||||
select TIMER_OF if OF
|
||||
help
|
||||
Provides a driver for the timer control found on HPE
|
||||
GXP SOCs. This is required for all GXP SOCs.
|
||||
|
||||
config RISCV_TIMER
|
||||
bool "Timer for the RISC-V platform" if COMPILE_TEST
|
||||
depends on GENERIC_SCHED_CLOCK && RISCV && RISCV_SBI
|
||||
|
|
|
@ -86,3 +86,4 @@ obj-$(CONFIG_HYPERV_TIMER) += hyperv_timer.o
|
|||
obj-$(CONFIG_MICROCHIP_PIT64B) += timer-microchip-pit64b.o
|
||||
obj-$(CONFIG_MSC313E_TIMER) += timer-msc313e.o
|
||||
obj-$(CONFIG_GOLDFISH_TIMER) += timer-goldfish.o
|
||||
obj-$(CONFIG_GXP_TIMER) += timer-gxp.o
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/sched_clock.h>
|
||||
|
||||
#define TIMER0_FREQ 1000000
|
||||
#define GXP_TIMER_CNT_OFS 0x00
|
||||
#define GXP_TIMESTAMP_OFS 0x08
|
||||
#define GXP_TIMER_CTRL_OFS 0x14
|
||||
|
||||
/* TCS Stands for Timer Control/Status: these are masks to be used in */
|
||||
/* the Timer Count Registers */
|
||||
#define MASK_TCS_ENABLE 0x01
|
||||
#define MASK_TCS_PERIOD 0x02
|
||||
#define MASK_TCS_RELOAD 0x04
|
||||
#define MASK_TCS_TC 0x80
|
||||
|
||||
struct gxp_timer {
|
||||
void __iomem *counter;
|
||||
void __iomem *control;
|
||||
struct clock_event_device evt;
|
||||
};
|
||||
|
||||
static struct gxp_timer *gxp_timer;
|
||||
|
||||
static void __iomem *system_clock __ro_after_init;
|
||||
|
||||
static inline struct gxp_timer *to_gxp_timer(struct clock_event_device *evt_dev)
|
||||
{
|
||||
return container_of(evt_dev, struct gxp_timer, evt);
|
||||
}
|
||||
|
||||
static u64 notrace gxp_sched_read(void)
|
||||
{
|
||||
return readl_relaxed(system_clock);
|
||||
}
|
||||
|
||||
static int gxp_time_set_next_event(unsigned long event, struct clock_event_device *evt_dev)
|
||||
{
|
||||
struct gxp_timer *timer = to_gxp_timer(evt_dev);
|
||||
|
||||
/* Stop counting and disable interrupt before updating */
|
||||
writeb_relaxed(MASK_TCS_TC, timer->control);
|
||||
writel_relaxed(event, timer->counter);
|
||||
writeb_relaxed(MASK_TCS_TC | MASK_TCS_ENABLE, timer->control);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t gxp_timer_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct gxp_timer *timer = (struct gxp_timer *)dev_id;
|
||||
|
||||
if (!(readb_relaxed(timer->control) & MASK_TCS_TC))
|
||||
return IRQ_NONE;
|
||||
|
||||
writeb_relaxed(MASK_TCS_TC, timer->control);
|
||||
|
||||
timer->evt.event_handler(&timer->evt);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int __init gxp_timer_init(struct device_node *node)
|
||||
{
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
u32 freq;
|
||||
int ret, irq;
|
||||
|
||||
gxp_timer = kzalloc(sizeof(*gxp_timer), GFP_KERNEL);
|
||||
if (!gxp_timer) {
|
||||
ret = -ENOMEM;
|
||||
pr_err("Can't allocate gxp_timer");
|
||||
return ret;
|
||||
}
|
||||
|
||||
clk = of_clk_get(node, 0);
|
||||
if (IS_ERR(clk)) {
|
||||
ret = (int)PTR_ERR(clk);
|
||||
pr_err("%pOFn clock not found: %d\n", node, ret);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(clk);
|
||||
if (ret) {
|
||||
pr_err("%pOFn clock enable failed: %d\n", node, ret);
|
||||
goto err_clk_enable;
|
||||
}
|
||||
|
||||
base = of_iomap(node, 0);
|
||||
if (!base) {
|
||||
ret = -ENXIO;
|
||||
pr_err("Can't map timer base registers");
|
||||
goto err_iomap;
|
||||
}
|
||||
|
||||
/* Set the offsets to the clock register and timer registers */
|
||||
gxp_timer->counter = base + GXP_TIMER_CNT_OFS;
|
||||
gxp_timer->control = base + GXP_TIMER_CTRL_OFS;
|
||||
system_clock = base + GXP_TIMESTAMP_OFS;
|
||||
|
||||
gxp_timer->evt.name = node->name;
|
||||
gxp_timer->evt.rating = 300;
|
||||
gxp_timer->evt.features = CLOCK_EVT_FEAT_ONESHOT;
|
||||
gxp_timer->evt.set_next_event = gxp_time_set_next_event;
|
||||
gxp_timer->evt.cpumask = cpumask_of(0);
|
||||
|
||||
irq = irq_of_parse_and_map(node, 0);
|
||||
if (irq <= 0) {
|
||||
ret = -EINVAL;
|
||||
pr_err("GXP Timer Can't parse IRQ %d", irq);
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
freq = clk_get_rate(clk);
|
||||
|
||||
ret = clocksource_mmio_init(system_clock, node->name, freq,
|
||||
300, 32, clocksource_mmio_readl_up);
|
||||
if (ret) {
|
||||
pr_err("%pOFn init clocksource failed: %d", node, ret);
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
sched_clock_register(gxp_sched_read, 32, freq);
|
||||
|
||||
irq = irq_of_parse_and_map(node, 0);
|
||||
if (irq <= 0) {
|
||||
ret = -EINVAL;
|
||||
pr_err("%pOFn Can't parse IRQ %d", node, irq);
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
clockevents_config_and_register(&gxp_timer->evt, TIMER0_FREQ,
|
||||
0xf, 0xffffffff);
|
||||
|
||||
ret = request_irq(irq, gxp_timer_interrupt, IRQF_TIMER | IRQF_SHARED,
|
||||
node->name, gxp_timer);
|
||||
if (ret) {
|
||||
pr_err("%pOFn request_irq() failed: %d", node, ret);
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
pr_debug("gxp: system timer (irq = %d)\n", irq);
|
||||
return 0;
|
||||
|
||||
err_exit:
|
||||
iounmap(base);
|
||||
err_iomap:
|
||||
clk_disable_unprepare(clk);
|
||||
err_clk_enable:
|
||||
clk_put(clk);
|
||||
err_free:
|
||||
kfree(gxp_timer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This probe gets called after the timer is already up and running. This will create
|
||||
* the watchdog device as a child since the registers are shared.
|
||||
*/
|
||||
|
||||
static int gxp_timer_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_device *gxp_watchdog_device;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
if (!gxp_timer) {
|
||||
pr_err("Gxp Timer not initialized, cannot create watchdog");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
gxp_watchdog_device = platform_device_alloc("gxp-wdt", -1);
|
||||
if (!gxp_watchdog_device) {
|
||||
pr_err("Timer failed to allocate gxp-wdt");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Pass the base address (counter) as platform data and nothing else */
|
||||
gxp_watchdog_device->dev.platform_data = gxp_timer->counter;
|
||||
gxp_watchdog_device->dev.parent = dev;
|
||||
|
||||
return platform_device_add(gxp_watchdog_device);
|
||||
}
|
||||
|
||||
static const struct of_device_id gxp_timer_of_match[] = {
|
||||
{ .compatible = "hpe,gxp-timer", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver gxp_timer_driver = {
|
||||
.probe = gxp_timer_probe,
|
||||
.driver = {
|
||||
.name = "gxp-timer",
|
||||
.of_match_table = gxp_timer_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
|
||||
builtin_platform_driver(gxp_timer_driver);
|
||||
|
||||
TIMER_OF_DECLARE(gxp, "hpe,gxp-timer", gxp_timer_init);
|
|
@ -1820,6 +1820,17 @@ config RALINK_WDT
|
|||
help
|
||||
Hardware driver for the Ralink SoC Watchdog Timer.
|
||||
|
||||
config GXP_WATCHDOG
|
||||
tristate "HPE GXP watchdog support"
|
||||
depends on ARCH_HPE_GXP
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
Say Y here to include support for the watchdog timer
|
||||
in HPE GXP SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here.
|
||||
The module will be called gxp-wdt.
|
||||
|
||||
config MT7621_WDT
|
||||
tristate "Mediatek SoC watchdog"
|
||||
select WATCHDOG_CORE
|
||||
|
|
|
@ -92,6 +92,7 @@ obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o
|
|||
obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o
|
||||
obj-$(CONFIG_PM8916_WATCHDOG) += pm8916_wdt.o
|
||||
obj-$(CONFIG_ARM_SMC_WATCHDOG) += arm_smc_wdt.o
|
||||
obj-$(CONFIG_GXP_WATCHDOG) += gxp-wdt.o
|
||||
obj-$(CONFIG_VISCONTI_WATCHDOG) += visconti_wdt.o
|
||||
obj-$(CONFIG_MSC313E_WATCHDOG) += msc313e_wdt.o
|
||||
obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define MASK_WDGCS_ENABLE 0x01
|
||||
#define MASK_WDGCS_RELOAD 0x04
|
||||
#define MASK_WDGCS_NMIEN 0x08
|
||||
#define MASK_WDGCS_WARN 0x80
|
||||
|
||||
#define WDT_MAX_TIMEOUT_MS 655350
|
||||
#define WDT_DEFAULT_TIMEOUT 30
|
||||
#define SECS_TO_WDOG_TICKS(x) ((x) * 100)
|
||||
#define WDOG_TICKS_TO_SECS(x) ((x) / 100)
|
||||
|
||||
#define GXP_WDT_CNT_OFS 0x10
|
||||
#define GXP_WDT_CTRL_OFS 0x16
|
||||
|
||||
struct gxp_wdt {
|
||||
void __iomem *base;
|
||||
struct watchdog_device wdd;
|
||||
};
|
||||
|
||||
static void gxp_wdt_enable_reload(struct gxp_wdt *drvdata)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
val = readb(drvdata->base + GXP_WDT_CTRL_OFS);
|
||||
val |= (MASK_WDGCS_ENABLE | MASK_WDGCS_RELOAD);
|
||||
writeb(val, drvdata->base + GXP_WDT_CTRL_OFS);
|
||||
}
|
||||
|
||||
static int gxp_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
|
||||
|
||||
writew(SECS_TO_WDOG_TICKS(wdd->timeout), drvdata->base + GXP_WDT_CNT_OFS);
|
||||
gxp_wdt_enable_reload(drvdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gxp_wdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
|
||||
u8 val;
|
||||
|
||||
val = readb_relaxed(drvdata->base + GXP_WDT_CTRL_OFS);
|
||||
val &= ~MASK_WDGCS_ENABLE;
|
||||
writeb(val, drvdata->base + GXP_WDT_CTRL_OFS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gxp_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
|
||||
u32 actual;
|
||||
|
||||
wdd->timeout = timeout;
|
||||
actual = min(timeout * 100, wdd->max_hw_heartbeat_ms / 10);
|
||||
writew(actual, drvdata->base + GXP_WDT_CNT_OFS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int gxp_wdt_get_timeleft(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
|
||||
u32 val = readw(drvdata->base + GXP_WDT_CNT_OFS);
|
||||
|
||||
return WDOG_TICKS_TO_SECS(val);
|
||||
}
|
||||
|
||||
static int gxp_wdt_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
|
||||
|
||||
gxp_wdt_enable_reload(drvdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gxp_restart(struct watchdog_device *wdd, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
|
||||
|
||||
writew(1, drvdata->base + GXP_WDT_CNT_OFS);
|
||||
gxp_wdt_enable_reload(drvdata);
|
||||
mdelay(100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct watchdog_ops gxp_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = gxp_wdt_start,
|
||||
.stop = gxp_wdt_stop,
|
||||
.ping = gxp_wdt_ping,
|
||||
.set_timeout = gxp_wdt_set_timeout,
|
||||
.get_timeleft = gxp_wdt_get_timeleft,
|
||||
.restart = gxp_restart,
|
||||
};
|
||||
|
||||
static const struct watchdog_info gxp_wdt_info = {
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
|
||||
.identity = "HPE GXP Watchdog timer",
|
||||
};
|
||||
|
||||
static int gxp_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct gxp_wdt *drvdata;
|
||||
int err;
|
||||
u8 val;
|
||||
|
||||
drvdata = devm_kzalloc(dev, sizeof(struct gxp_wdt), GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* The register area where the timer and watchdog reside is disarranged.
|
||||
* Hence mapping individual register blocks for the timer and watchdog
|
||||
* is not recommended as they would have access to each others
|
||||
* registers. Based on feedback the watchdog is no longer part of the
|
||||
* device tree file and the timer driver now creates the watchdog as a
|
||||
* child device. During the watchdogs creation, the timer driver passes
|
||||
* the base address to the watchdog over the private interface.
|
||||
*/
|
||||
|
||||
drvdata->base = (void __iomem *)dev->platform_data;
|
||||
|
||||
drvdata->wdd.info = &gxp_wdt_info;
|
||||
drvdata->wdd.ops = &gxp_wdt_ops;
|
||||
drvdata->wdd.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT_MS;
|
||||
drvdata->wdd.parent = dev;
|
||||
drvdata->wdd.timeout = WDT_DEFAULT_TIMEOUT;
|
||||
|
||||
watchdog_set_drvdata(&drvdata->wdd, drvdata);
|
||||
watchdog_set_nowayout(&drvdata->wdd, WATCHDOG_NOWAYOUT);
|
||||
|
||||
val = readb(drvdata->base + GXP_WDT_CTRL_OFS);
|
||||
|
||||
if (val & MASK_WDGCS_ENABLE)
|
||||
set_bit(WDOG_HW_RUNNING, &drvdata->wdd.status);
|
||||
|
||||
watchdog_set_restart_priority(&drvdata->wdd, 128);
|
||||
|
||||
watchdog_stop_on_reboot(&drvdata->wdd);
|
||||
err = devm_watchdog_register_device(dev, &drvdata->wdd);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed to register watchdog device");
|
||||
return err;
|
||||
}
|
||||
|
||||
dev_info(dev, "HPE GXP watchdog timer");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver gxp_wdt_driver = {
|
||||
.probe = gxp_wdt_probe,
|
||||
.driver = {
|
||||
.name = "gxp-wdt",
|
||||
},
|
||||
};
|
||||
module_platform_driver(gxp_wdt_driver);
|
||||
|
||||
MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>");
|
||||
MODULE_AUTHOR("Jean-Marie Verdun <verdun@hpe.com>");
|
||||
MODULE_DESCRIPTION("Driver for GXP watchdog timer");
|
Loading…
Reference in New Issue