qemu/hw/watchdog/wdt_aspeed.c
<<
>>
Prefs
   1/*
   2 * ASPEED Watchdog Controller
   3 *
   4 * Copyright (C) 2016-2017 IBM Corp.
   5 *
   6 * This code is licensed under the GPL version 2 or later. See the
   7 * COPYING file in the top-level directory.
   8 */
   9
  10#include "qemu/osdep.h"
  11
  12#include "qapi/error.h"
  13#include "qemu/log.h"
  14#include "qemu/timer.h"
  15#include "sysemu/watchdog.h"
  16#include "hw/misc/aspeed_scu.h"
  17#include "hw/sysbus.h"
  18#include "hw/watchdog/wdt_aspeed.h"
  19
  20#define WDT_STATUS                      (0x00 / 4)
  21#define WDT_RELOAD_VALUE                (0x04 / 4)
  22#define WDT_RESTART                     (0x08 / 4)
  23#define WDT_CTRL                        (0x0C / 4)
  24#define   WDT_CTRL_RESET_MODE_SOC       (0x00 << 5)
  25#define   WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5)
  26#define   WDT_CTRL_1MHZ_CLK             BIT(4)
  27#define   WDT_CTRL_WDT_EXT              BIT(3)
  28#define   WDT_CTRL_WDT_INTR             BIT(2)
  29#define   WDT_CTRL_RESET_SYSTEM         BIT(1)
  30#define   WDT_CTRL_ENABLE               BIT(0)
  31#define WDT_RESET_WIDTH                 (0x18 / 4)
  32#define   WDT_RESET_WIDTH_ACTIVE_HIGH   BIT(31)
  33#define     WDT_POLARITY_MASK           (0xFF << 24)
  34#define     WDT_ACTIVE_HIGH_MAGIC       (0xA5 << 24)
  35#define     WDT_ACTIVE_LOW_MAGIC        (0x5A << 24)
  36#define   WDT_RESET_WIDTH_PUSH_PULL     BIT(30)
  37#define     WDT_DRIVE_TYPE_MASK         (0xFF << 24)
  38#define     WDT_PUSH_PULL_MAGIC         (0xA8 << 24)
  39#define     WDT_OPEN_DRAIN_MAGIC        (0x8A << 24)
  40
  41#define WDT_TIMEOUT_STATUS              (0x10 / 4)
  42#define WDT_TIMEOUT_CLEAR               (0x14 / 4)
  43
  44#define WDT_RESTART_MAGIC               0x4755
  45
  46static bool aspeed_wdt_is_enabled(const AspeedWDTState *s)
  47{
  48    return s->regs[WDT_CTRL] & WDT_CTRL_ENABLE;
  49}
  50
  51static bool is_ast2500(const AspeedWDTState *s)
  52{
  53    switch (s->silicon_rev) {
  54    case AST2500_A0_SILICON_REV:
  55    case AST2500_A1_SILICON_REV:
  56        return true;
  57    case AST2400_A0_SILICON_REV:
  58    case AST2400_A1_SILICON_REV:
  59    default:
  60        break;
  61    }
  62
  63    return false;
  64}
  65
  66static uint64_t aspeed_wdt_read(void *opaque, hwaddr offset, unsigned size)
  67{
  68    AspeedWDTState *s = ASPEED_WDT(opaque);
  69
  70    offset >>= 2;
  71
  72    switch (offset) {
  73    case WDT_STATUS:
  74        return s->regs[WDT_STATUS];
  75    case WDT_RELOAD_VALUE:
  76        return s->regs[WDT_RELOAD_VALUE];
  77    case WDT_RESTART:
  78        qemu_log_mask(LOG_GUEST_ERROR,
  79                      "%s: read from write-only reg at offset 0x%"
  80                      HWADDR_PRIx "\n", __func__, offset);
  81        return 0;
  82    case WDT_CTRL:
  83        return s->regs[WDT_CTRL];
  84    case WDT_RESET_WIDTH:
  85        return s->regs[WDT_RESET_WIDTH];
  86    case WDT_TIMEOUT_STATUS:
  87    case WDT_TIMEOUT_CLEAR:
  88        qemu_log_mask(LOG_UNIMP,
  89                      "%s: uninmplemented read at offset 0x%" HWADDR_PRIx "\n",
  90                      __func__, offset);
  91        return 0;
  92    default:
  93        qemu_log_mask(LOG_GUEST_ERROR,
  94                      "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
  95                      __func__, offset);
  96        return 0;
  97    }
  98
  99}
 100
 101static void aspeed_wdt_reload(AspeedWDTState *s, bool pclk)
 102{
 103    uint64_t reload;
 104
 105    if (pclk) {
 106        reload = muldiv64(s->regs[WDT_RELOAD_VALUE], NANOSECONDS_PER_SECOND,
 107                          s->pclk_freq);
 108    } else {
 109        reload = s->regs[WDT_RELOAD_VALUE] * 1000ULL;
 110    }
 111
 112    if (aspeed_wdt_is_enabled(s)) {
 113        timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload);
 114    }
 115}
 116
 117static void aspeed_wdt_write(void *opaque, hwaddr offset, uint64_t data,
 118                             unsigned size)
 119{
 120    AspeedWDTState *s = ASPEED_WDT(opaque);
 121    bool enable = data & WDT_CTRL_ENABLE;
 122
 123    offset >>= 2;
 124
 125    switch (offset) {
 126    case WDT_STATUS:
 127        qemu_log_mask(LOG_GUEST_ERROR,
 128                      "%s: write to read-only reg at offset 0x%"
 129                      HWADDR_PRIx "\n", __func__, offset);
 130        break;
 131    case WDT_RELOAD_VALUE:
 132        s->regs[WDT_RELOAD_VALUE] = data;
 133        break;
 134    case WDT_RESTART:
 135        if ((data & 0xFFFF) == WDT_RESTART_MAGIC) {
 136            s->regs[WDT_STATUS] = s->regs[WDT_RELOAD_VALUE];
 137            aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK));
 138        }
 139        break;
 140    case WDT_CTRL:
 141        if (enable && !aspeed_wdt_is_enabled(s)) {
 142            s->regs[WDT_CTRL] = data;
 143            aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK));
 144        } else if (!enable && aspeed_wdt_is_enabled(s)) {
 145            s->regs[WDT_CTRL] = data;
 146            timer_del(s->timer);
 147        }
 148        break;
 149    case WDT_RESET_WIDTH:
 150    {
 151        uint32_t property = data & WDT_POLARITY_MASK;
 152
 153        if (property && is_ast2500(s)) {
 154            if (property == WDT_ACTIVE_HIGH_MAGIC) {
 155                s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_ACTIVE_HIGH;
 156            } else if (property == WDT_ACTIVE_LOW_MAGIC) {
 157                s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_ACTIVE_HIGH;
 158            } else if (property == WDT_PUSH_PULL_MAGIC) {
 159                s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_PUSH_PULL;
 160            } else if (property == WDT_OPEN_DRAIN_MAGIC) {
 161                s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_PUSH_PULL;
 162            }
 163        }
 164        s->regs[WDT_RESET_WIDTH] &= ~s->ext_pulse_width_mask;
 165        s->regs[WDT_RESET_WIDTH] |= data & s->ext_pulse_width_mask;
 166        break;
 167    }
 168    case WDT_TIMEOUT_STATUS:
 169    case WDT_TIMEOUT_CLEAR:
 170        qemu_log_mask(LOG_UNIMP,
 171                      "%s: uninmplemented write at offset 0x%" HWADDR_PRIx "\n",
 172                      __func__, offset);
 173        break;
 174    default:
 175        qemu_log_mask(LOG_GUEST_ERROR,
 176                      "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
 177                      __func__, offset);
 178    }
 179    return;
 180}
 181
 182static WatchdogTimerModel model = {
 183    .wdt_name = TYPE_ASPEED_WDT,
 184    .wdt_description = "Aspeed watchdog device",
 185};
 186
 187static const VMStateDescription vmstate_aspeed_wdt = {
 188    .name = "vmstate_aspeed_wdt",
 189    .version_id = 0,
 190    .minimum_version_id = 0,
 191    .fields = (VMStateField[]) {
 192        VMSTATE_TIMER_PTR(timer, AspeedWDTState),
 193        VMSTATE_UINT32_ARRAY(regs, AspeedWDTState, ASPEED_WDT_REGS_MAX),
 194        VMSTATE_END_OF_LIST()
 195    }
 196};
 197
 198static const MemoryRegionOps aspeed_wdt_ops = {
 199    .read = aspeed_wdt_read,
 200    .write = aspeed_wdt_write,
 201    .endianness = DEVICE_LITTLE_ENDIAN,
 202    .valid.min_access_size = 4,
 203    .valid.max_access_size = 4,
 204    .valid.unaligned = false,
 205};
 206
 207static void aspeed_wdt_reset(DeviceState *dev)
 208{
 209    AspeedWDTState *s = ASPEED_WDT(dev);
 210
 211    s->regs[WDT_STATUS] = 0x3EF1480;
 212    s->regs[WDT_RELOAD_VALUE] = 0x03EF1480;
 213    s->regs[WDT_RESTART] = 0;
 214    s->regs[WDT_CTRL] = 0;
 215    s->regs[WDT_RESET_WIDTH] = 0xFF;
 216
 217    timer_del(s->timer);
 218}
 219
 220static void aspeed_wdt_timer_expired(void *dev)
 221{
 222    AspeedWDTState *s = ASPEED_WDT(dev);
 223
 224    qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
 225    watchdog_perform_action();
 226    timer_del(s->timer);
 227}
 228
 229#define PCLK_HZ 24000000
 230
 231static void aspeed_wdt_realize(DeviceState *dev, Error **errp)
 232{
 233    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
 234    AspeedWDTState *s = ASPEED_WDT(dev);
 235
 236    if (!is_supported_silicon_rev(s->silicon_rev)) {
 237        error_setg(errp, "Unknown silicon revision: 0x%" PRIx32,
 238                s->silicon_rev);
 239        return;
 240    }
 241
 242    switch (s->silicon_rev) {
 243    case AST2400_A0_SILICON_REV:
 244    case AST2400_A1_SILICON_REV:
 245        s->ext_pulse_width_mask = 0xff;
 246        break;
 247    case AST2500_A0_SILICON_REV:
 248    case AST2500_A1_SILICON_REV:
 249        s->ext_pulse_width_mask = 0xfffff;
 250        break;
 251    default:
 252        g_assert_not_reached();
 253    }
 254
 255    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, aspeed_wdt_timer_expired, dev);
 256
 257    /* FIXME: This setting should be derived from the SCU hw strapping
 258     * register SCU70
 259     */
 260    s->pclk_freq = PCLK_HZ;
 261
 262    memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_wdt_ops, s,
 263                          TYPE_ASPEED_WDT, ASPEED_WDT_REGS_MAX * 4);
 264    sysbus_init_mmio(sbd, &s->iomem);
 265}
 266
 267static Property aspeed_wdt_properties[] = {
 268    DEFINE_PROP_UINT32("silicon-rev", AspeedWDTState, silicon_rev, 0),
 269    DEFINE_PROP_END_OF_LIST(),
 270};
 271
 272static void aspeed_wdt_class_init(ObjectClass *klass, void *data)
 273{
 274    DeviceClass *dc = DEVICE_CLASS(klass);
 275
 276    dc->realize = aspeed_wdt_realize;
 277    dc->reset = aspeed_wdt_reset;
 278    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
 279    dc->vmsd = &vmstate_aspeed_wdt;
 280    dc->props = aspeed_wdt_properties;
 281}
 282
 283static const TypeInfo aspeed_wdt_info = {
 284    .parent = TYPE_SYS_BUS_DEVICE,
 285    .name  = TYPE_ASPEED_WDT,
 286    .instance_size  = sizeof(AspeedWDTState),
 287    .class_init = aspeed_wdt_class_init,
 288};
 289
 290static void wdt_aspeed_register_types(void)
 291{
 292    watchdog_add_model(&model);
 293    type_register_static(&aspeed_wdt_info);
 294}
 295
 296type_init(wdt_aspeed_register_types)
 297