Skip to content

Commit

Permalink
hw/watchdog: Implement full i.MX watchdog support
Browse files Browse the repository at this point in the history
Implement full support for the watchdog in i.MX systems.
Pretimeout support is optional because the watchdog hardware
on i.MX31 does not support pretimeouts.

Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Message-id: 20200517162135.110364-3-linux@roeck-us.net
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
[PMM: added Property array terminator entry]
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
  • Loading branch information
groeck authored and pm215 committed May 21, 2020
1 parent 37f9595 commit daca13d
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 14 deletions.
238 changes: 226 additions & 12 deletions hw/watchdog/wdt_imx2.c
Expand Up @@ -13,24 +13,189 @@
#include "qemu/bitops.h"
#include "qemu/module.h"
#include "sysemu/watchdog.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"

#include "hw/watchdog/wdt_imx2.h"

#define IMX2_WDT_WCR_WDA BIT(5) /* -> External Reset WDOG_B */
#define IMX2_WDT_WCR_SRS BIT(4) /* -> Software Reset Signal */
static void imx2_wdt_interrupt(void *opaque)
{
IMX2WdtState *s = IMX2_WDT(opaque);

s->wicr |= IMX2_WDT_WICR_WTIS;
qemu_set_irq(s->irq, 1);
}

static void imx2_wdt_expired(void *opaque)
{
IMX2WdtState *s = IMX2_WDT(opaque);

s->wrsr = IMX2_WDT_WRSR_TOUT;

/* Perform watchdog action if watchdog is enabled */
if (s->wcr & IMX2_WDT_WCR_WDE) {
s->wrsr = IMX2_WDT_WRSR_TOUT;
watchdog_perform_action();
}
}

static void imx2_wdt_reset(DeviceState *dev)
{
IMX2WdtState *s = IMX2_WDT(dev);

ptimer_transaction_begin(s->timer);
ptimer_stop(s->timer);
ptimer_transaction_commit(s->timer);

if (s->pretimeout_support) {
ptimer_transaction_begin(s->itimer);
ptimer_stop(s->itimer);
ptimer_transaction_commit(s->itimer);
}

s->wicr_locked = false;
s->wcr_locked = false;
s->wcr_wde_locked = false;

s->wcr = IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS;
s->wsr = 0;
s->wrsr &= ~(IMX2_WDT_WRSR_TOUT | IMX2_WDT_WRSR_SFTW);
s->wicr = IMX2_WDT_WICR_WICT_DEF;
s->wmcr = IMX2_WDT_WMCR_PDE;
}

static uint64_t imx2_wdt_read(void *opaque, hwaddr addr,
unsigned int size)
static uint64_t imx2_wdt_read(void *opaque, hwaddr addr, unsigned int size)
{
IMX2WdtState *s = IMX2_WDT(opaque);

switch (addr) {
case IMX2_WDT_WCR:
return s->wcr;
case IMX2_WDT_WSR:
return s->wsr;
case IMX2_WDT_WRSR:
return s->wrsr;
case IMX2_WDT_WICR:
return s->wicr;
case IMX2_WDT_WMCR:
return s->wmcr;
}
return 0;
}

static void imx_wdt2_update_itimer(IMX2WdtState *s, bool start)
{
bool running = (s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT);
bool enabled = s->wicr & IMX2_WDT_WICR_WIE;

ptimer_transaction_begin(s->itimer);
if (start || !enabled) {
ptimer_stop(s->itimer);
}
if (running && enabled) {
int count = ptimer_get_count(s->timer);
int pretimeout = s->wicr & IMX2_WDT_WICR_WICT;

/*
* Only (re-)start pretimeout timer if its counter value is larger
* than 0. Otherwise it will fire right away and we'll get an
* interrupt loop.
*/
if (count > pretimeout) {
ptimer_set_count(s->itimer, count - pretimeout);
if (start) {
ptimer_run(s->itimer, 1);
}
}
}
ptimer_transaction_commit(s->itimer);
}

static void imx_wdt2_update_timer(IMX2WdtState *s, bool start)
{
ptimer_transaction_begin(s->timer);
if (start) {
ptimer_stop(s->timer);
}
if ((s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT)) {
int count = (s->wcr & IMX2_WDT_WCR_WT) >> 8;

/* A value of 0 reflects one period (0.5s). */
ptimer_set_count(s->timer, count + 1);
if (start) {
ptimer_run(s->timer, 1);
}
}
ptimer_transaction_commit(s->timer);
if (s->pretimeout_support) {
imx_wdt2_update_itimer(s, start);
}
}

static void imx2_wdt_write(void *opaque, hwaddr addr,
uint64_t value, unsigned int size)
{
if (addr == IMX2_WDT_WCR &&
(~value & (IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS))) {
watchdog_perform_action();
IMX2WdtState *s = IMX2_WDT(opaque);

switch (addr) {
case IMX2_WDT_WCR:
if (s->wcr_locked) {
value &= ~IMX2_WDT_WCR_LOCK_MASK;
value |= (s->wicr & IMX2_WDT_WCR_LOCK_MASK);
}
s->wcr_locked = true;
if (s->wcr_wde_locked) {
value &= ~IMX2_WDT_WCR_WDE;
value |= (s->wicr & ~IMX2_WDT_WCR_WDE);
} else if (value & IMX2_WDT_WCR_WDE) {
s->wcr_wde_locked = true;
}
if (s->wcr_wdt_locked) {
value &= ~IMX2_WDT_WCR_WDT;
value |= (s->wicr & ~IMX2_WDT_WCR_WDT);
} else if (value & IMX2_WDT_WCR_WDT) {
s->wcr_wdt_locked = true;
}

s->wcr = value;
if (!(value & IMX2_WDT_WCR_SRS)) {
s->wrsr = IMX2_WDT_WRSR_SFTW;
}
if (!(value & (IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS)) ||
(!(value & IMX2_WDT_WCR_WT) && (value & IMX2_WDT_WCR_WDE))) {
watchdog_perform_action();
}
s->wcr |= IMX2_WDT_WCR_SRS;
imx_wdt2_update_timer(s, true);
break;
case IMX2_WDT_WSR:
if (s->wsr == IMX2_WDT_SEQ1 && value == IMX2_WDT_SEQ2) {
imx_wdt2_update_timer(s, false);
}
s->wsr = value;
break;
case IMX2_WDT_WRSR:
break;
case IMX2_WDT_WICR:
if (!s->pretimeout_support) {
return;
}
value &= IMX2_WDT_WICR_LOCK_MASK | IMX2_WDT_WICR_WTIS;
if (s->wicr_locked) {
value &= IMX2_WDT_WICR_WTIS;
value |= (s->wicr & IMX2_WDT_WICR_LOCK_MASK);
}
s->wicr = value | (s->wicr & IMX2_WDT_WICR_WTIS);
if (value & IMX2_WDT_WICR_WTIS) {
s->wicr &= ~IMX2_WDT_WICR_WTIS;
qemu_set_irq(s->irq, 0);
}
imx_wdt2_update_itimer(s, true);
s->wicr_locked = true;
break;
case IMX2_WDT_WMCR:
s->wmcr = value & IMX2_WDT_WMCR_PDE;
break;
}
}

Expand All @@ -45,28 +210,77 @@ static const MemoryRegionOps imx2_wdt_ops = {
* real device but in practice there is no reason for a guest
* to access this device unaligned.
*/
.min_access_size = 4,
.max_access_size = 4,
.min_access_size = 2,
.max_access_size = 2,
.unaligned = false,
},
};

static const VMStateDescription vmstate_imx2_wdt = {
.name = "imx2.wdt",
.fields = (VMStateField[]) {
VMSTATE_PTIMER(timer, IMX2WdtState),
VMSTATE_PTIMER(itimer, IMX2WdtState),
VMSTATE_BOOL(wicr_locked, IMX2WdtState),
VMSTATE_BOOL(wcr_locked, IMX2WdtState),
VMSTATE_BOOL(wcr_wde_locked, IMX2WdtState),
VMSTATE_BOOL(wcr_wdt_locked, IMX2WdtState),
VMSTATE_UINT16(wcr, IMX2WdtState),
VMSTATE_UINT16(wsr, IMX2WdtState),
VMSTATE_UINT16(wrsr, IMX2WdtState),
VMSTATE_UINT16(wmcr, IMX2WdtState),
VMSTATE_UINT16(wicr, IMX2WdtState),
VMSTATE_END_OF_LIST()
}
};

static void imx2_wdt_realize(DeviceState *dev, Error **errp)
{
IMX2WdtState *s = IMX2_WDT(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);

memory_region_init_io(&s->mmio, OBJECT(dev),
&imx2_wdt_ops, s,
TYPE_IMX2_WDT".mmio",
IMX2_WDT_REG_NUM * sizeof(uint16_t));
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
TYPE_IMX2_WDT,
IMX2_WDT_MMIO_SIZE);
sysbus_init_mmio(sbd, &s->mmio);
sysbus_init_irq(sbd, &s->irq);

s->timer = ptimer_init(imx2_wdt_expired, s,
PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
ptimer_transaction_begin(s->timer);
ptimer_set_freq(s->timer, 2);
ptimer_set_limit(s->timer, 0xff, 1);
ptimer_transaction_commit(s->timer);
if (s->pretimeout_support) {
s->itimer = ptimer_init(imx2_wdt_interrupt, s,
PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
ptimer_transaction_begin(s->itimer);
ptimer_set_freq(s->itimer, 2);
ptimer_set_limit(s->itimer, 0xff, 1);
ptimer_transaction_commit(s->itimer);
}
}

static Property imx2_wdt_properties[] = {
DEFINE_PROP_BOOL("pretimeout-support", IMX2WdtState, pretimeout_support,
false),
DEFINE_PROP_END_OF_LIST()
};

static void imx2_wdt_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);

device_class_set_props(dc, imx2_wdt_properties);
dc->realize = imx2_wdt_realize;
dc->reset = imx2_wdt_reset;
dc->vmsd = &vmstate_imx2_wdt;
dc->desc = "i.MX watchdog timer";
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}

Expand Down
61 changes: 59 additions & 2 deletions include/hw/watchdog/wdt_imx2.h
Expand Up @@ -12,22 +12,79 @@
#ifndef IMX2_WDT_H
#define IMX2_WDT_H

#include "qemu/bitops.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "hw/ptimer.h"

#define TYPE_IMX2_WDT "imx2.wdt"
#define IMX2_WDT(obj) OBJECT_CHECK(IMX2WdtState, (obj), TYPE_IMX2_WDT)

enum IMX2WdtRegisters {
IMX2_WDT_WCR = 0x0000,
IMX2_WDT_REG_NUM = 0x0008 / sizeof(uint16_t) + 1,
IMX2_WDT_WCR = 0x0000, /* Control Register */
IMX2_WDT_WSR = 0x0002, /* Service Register */
IMX2_WDT_WRSR = 0x0004, /* Reset Status Register */
IMX2_WDT_WICR = 0x0006, /* Interrupt Control Register */
IMX2_WDT_WMCR = 0x0008, /* Misc Register */
};

#define IMX2_WDT_MMIO_SIZE 0x000a

/* Control Register definitions */
#define IMX2_WDT_WCR_WT (0xFF << 8) /* Watchdog Timeout Field */
#define IMX2_WDT_WCR_WDW BIT(7) /* WDOG Disable for Wait */
#define IMX2_WDT_WCR_WDA BIT(5) /* WDOG Assertion */
#define IMX2_WDT_WCR_SRS BIT(4) /* Software Reset Signal */
#define IMX2_WDT_WCR_WDT BIT(3) /* WDOG Timeout Assertion */
#define IMX2_WDT_WCR_WDE BIT(2) /* Watchdog Enable */
#define IMX2_WDT_WCR_WDBG BIT(1) /* Watchdog Debug Enable */
#define IMX2_WDT_WCR_WDZST BIT(0) /* Watchdog Timer Suspend */

#define IMX2_WDT_WCR_LOCK_MASK (IMX2_WDT_WCR_WDZST | IMX2_WDT_WCR_WDBG \
| IMX2_WDT_WCR_WDW)

/* Service Register definitions */
#define IMX2_WDT_SEQ1 0x5555 /* service sequence 1 */
#define IMX2_WDT_SEQ2 0xAAAA /* service sequence 2 */

/* Reset Status Register definitions */
#define IMX2_WDT_WRSR_TOUT BIT(1) /* Reset due to Timeout */
#define IMX2_WDT_WRSR_SFTW BIT(0) /* Reset due to software reset */

/* Interrupt Control Register definitions */
#define IMX2_WDT_WICR_WIE BIT(15) /* Interrupt Enable */
#define IMX2_WDT_WICR_WTIS BIT(14) /* Interrupt Status */
#define IMX2_WDT_WICR_WICT 0xff /* Interrupt Timeout */
#define IMX2_WDT_WICR_WICT_DEF 0x04 /* Default interrupt timeout (2s) */

#define IMX2_WDT_WICR_LOCK_MASK (IMX2_WDT_WICR_WIE | IMX2_WDT_WICR_WICT)

/* Misc Control Register definitions */
#define IMX2_WDT_WMCR_PDE BIT(0) /* Power-Down Enable */

typedef struct IMX2WdtState {
/* <private> */
SysBusDevice parent_obj;

/*< public >*/
MemoryRegion mmio;
qemu_irq irq;

struct ptimer_state *timer;
struct ptimer_state *itimer;

bool pretimeout_support;
bool wicr_locked;

uint16_t wcr;
uint16_t wsr;
uint16_t wrsr;
uint16_t wicr;
uint16_t wmcr;

bool wcr_locked; /* affects WDZST, WDBG, and WDW */
bool wcr_wde_locked; /* affects WDE */
bool wcr_wdt_locked; /* affects WDT (never cleared) */
} IMX2WdtState;

#endif /* IMX2_WDT_H */

0 comments on commit daca13d

Please sign in to comment.