qemu/hw/misc/npcm7xx_mft.c
<<
>>
Prefs
   1/*
   2 * Nuvoton NPCM7xx MFT Module
   3 *
   4 * Copyright 2021 Google LLC
   5 *
   6 * This program is free software; you can redistribute it and/or modify it
   7 * under the terms of the GNU General Public License as published by the
   8 * Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 *
  11 * This program is distributed in the hope that it will be useful, but WITHOUT
  12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  14 * for more details.
  15 */
  16
  17#include "qemu/osdep.h"
  18#include "hw/irq.h"
  19#include "hw/qdev-clock.h"
  20#include "hw/qdev-properties.h"
  21#include "hw/misc/npcm7xx_mft.h"
  22#include "hw/misc/npcm7xx_pwm.h"
  23#include "hw/registerfields.h"
  24#include "migration/vmstate.h"
  25#include "qapi/error.h"
  26#include "qapi/visitor.h"
  27#include "qemu/bitops.h"
  28#include "qemu/error-report.h"
  29#include "qemu/log.h"
  30#include "qemu/module.h"
  31#include "qemu/timer.h"
  32#include "qemu/units.h"
  33#include "trace.h"
  34
  35/*
  36 * Some of the registers can only accessed via 16-bit ops and some can only
  37 * be accessed via 8-bit ops. However we mark all of them using REG16 to
  38 * simplify implementation. npcm7xx_mft_check_mem_op checks the access length
  39 * of memory operations.
  40 */
  41REG16(NPCM7XX_MFT_CNT1, 0x00);
  42REG16(NPCM7XX_MFT_CRA, 0x02);
  43REG16(NPCM7XX_MFT_CRB, 0x04);
  44REG16(NPCM7XX_MFT_CNT2, 0x06);
  45REG16(NPCM7XX_MFT_PRSC, 0x08);
  46REG16(NPCM7XX_MFT_CKC, 0x0a);
  47REG16(NPCM7XX_MFT_MCTRL, 0x0c);
  48REG16(NPCM7XX_MFT_ICTRL, 0x0e);
  49REG16(NPCM7XX_MFT_ICLR, 0x10);
  50REG16(NPCM7XX_MFT_IEN, 0x12);
  51REG16(NPCM7XX_MFT_CPA, 0x14);
  52REG16(NPCM7XX_MFT_CPB, 0x16);
  53REG16(NPCM7XX_MFT_CPCFG, 0x18);
  54REG16(NPCM7XX_MFT_INASEL, 0x1a);
  55REG16(NPCM7XX_MFT_INBSEL, 0x1c);
  56
  57/* Register Fields */
  58#define NPCM7XX_MFT_CKC_C2CSEL          BIT(3)
  59#define NPCM7XX_MFT_CKC_C1CSEL          BIT(0)
  60
  61#define NPCM7XX_MFT_MCTRL_TBEN          BIT(6)
  62#define NPCM7XX_MFT_MCTRL_TAEN          BIT(5)
  63#define NPCM7XX_MFT_MCTRL_TBEDG         BIT(4)
  64#define NPCM7XX_MFT_MCTRL_TAEDG         BIT(3)
  65#define NPCM7XX_MFT_MCTRL_MODE5         BIT(2)
  66
  67#define NPCM7XX_MFT_ICTRL_TFPND         BIT(5)
  68#define NPCM7XX_MFT_ICTRL_TEPND         BIT(4)
  69#define NPCM7XX_MFT_ICTRL_TDPND         BIT(3)
  70#define NPCM7XX_MFT_ICTRL_TCPND         BIT(2)
  71#define NPCM7XX_MFT_ICTRL_TBPND         BIT(1)
  72#define NPCM7XX_MFT_ICTRL_TAPND         BIT(0)
  73
  74#define NPCM7XX_MFT_ICLR_TFCLR          BIT(5)
  75#define NPCM7XX_MFT_ICLR_TECLR          BIT(4)
  76#define NPCM7XX_MFT_ICLR_TDCLR          BIT(3)
  77#define NPCM7XX_MFT_ICLR_TCCLR          BIT(2)
  78#define NPCM7XX_MFT_ICLR_TBCLR          BIT(1)
  79#define NPCM7XX_MFT_ICLR_TACLR          BIT(0)
  80
  81#define NPCM7XX_MFT_IEN_TFIEN           BIT(5)
  82#define NPCM7XX_MFT_IEN_TEIEN           BIT(4)
  83#define NPCM7XX_MFT_IEN_TDIEN           BIT(3)
  84#define NPCM7XX_MFT_IEN_TCIEN           BIT(2)
  85#define NPCM7XX_MFT_IEN_TBIEN           BIT(1)
  86#define NPCM7XX_MFT_IEN_TAIEN           BIT(0)
  87
  88#define NPCM7XX_MFT_CPCFG_GET_B(rv)     extract8((rv), 4, 4)
  89#define NPCM7XX_MFT_CPCFG_GET_A(rv)     extract8((rv), 0, 4)
  90#define NPCM7XX_MFT_CPCFG_HIEN          BIT(3)
  91#define NPCM7XX_MFT_CPCFG_EQEN          BIT(2)
  92#define NPCM7XX_MFT_CPCFG_LOEN          BIT(1)
  93#define NPCM7XX_MFT_CPCFG_CPSEL         BIT(0)
  94
  95#define NPCM7XX_MFT_INASEL_SELA         BIT(0)
  96#define NPCM7XX_MFT_INBSEL_SELB         BIT(0)
  97
  98/* Max CNT values of the module. The CNT value is a countdown from it. */
  99#define NPCM7XX_MFT_MAX_CNT             0xFFFF
 100
 101/* Each fan revolution should generated 2 pulses */
 102#define NPCM7XX_MFT_PULSE_PER_REVOLUTION 2
 103
 104typedef enum NPCM7xxMFTCaptureState {
 105    /* capture succeeded with a valid CNT value. */
 106    NPCM7XX_CAPTURE_SUCCEED,
 107    /* capture stopped prematurely due to reaching CPCFG condition. */
 108    NPCM7XX_CAPTURE_COMPARE_HIT,
 109    /* capture fails since it reaches underflow condition for CNT. */
 110    NPCM7XX_CAPTURE_UNDERFLOW,
 111} NPCM7xxMFTCaptureState;
 112
 113static void npcm7xx_mft_reset(NPCM7xxMFTState *s)
 114{
 115    int i;
 116
 117    /* Only registers PRSC ~ INBSEL need to be reset. */
 118    for (i = R_NPCM7XX_MFT_PRSC; i <= R_NPCM7XX_MFT_INBSEL; ++i) {
 119        s->regs[i] = 0;
 120    }
 121}
 122
 123static void npcm7xx_mft_clear_interrupt(NPCM7xxMFTState *s, uint8_t iclr)
 124{
 125    /*
 126     * Clear bits in ICTRL where corresponding bits in iclr is 1.
 127     * Both iclr and ictrl are 8-bit regs. (See npcm7xx_mft_check_mem_op)
 128     */
 129    s->regs[R_NPCM7XX_MFT_ICTRL] &= ~iclr;
 130}
 131
 132/*
 133 * If the CPCFG's condition should be triggered during count down from
 134 * NPCM7XX_MFT_MAX_CNT to src if compared to tgt, return the count when
 135 * the condition is triggered.
 136 * Otherwise return -1.
 137 * Since tgt is uint16_t it must always <= NPCM7XX_MFT_MAX_CNT.
 138 */
 139static int npcm7xx_mft_compare(int32_t src, uint16_t tgt, uint8_t cpcfg)
 140{
 141    if (cpcfg & NPCM7XX_MFT_CPCFG_HIEN) {
 142        return NPCM7XX_MFT_MAX_CNT;
 143    }
 144    if ((cpcfg & NPCM7XX_MFT_CPCFG_EQEN) && (src <= tgt)) {
 145        return tgt;
 146    }
 147    if ((cpcfg & NPCM7XX_MFT_CPCFG_LOEN) && (tgt > 0) && (src < tgt)) {
 148        return tgt - 1;
 149    }
 150
 151    return -1;
 152}
 153
 154/* Compute CNT according to corresponding fan's RPM. */
 155static NPCM7xxMFTCaptureState npcm7xx_mft_compute_cnt(
 156    Clock *clock, uint32_t max_rpm, uint32_t duty, uint16_t tgt,
 157    uint8_t cpcfg, uint16_t *cnt)
 158{
 159    uint32_t rpm = (uint64_t)max_rpm * (uint64_t)duty / NPCM7XX_PWM_MAX_DUTY;
 160    int32_t count;
 161    int stopped;
 162    NPCM7xxMFTCaptureState state;
 163
 164    if (rpm == 0) {
 165        /*
 166         * If RPM = 0, capture won't happen. CNT will continue count down.
 167         * So it's effective equivalent to have a cnt > NPCM7XX_MFT_MAX_CNT
 168         */
 169        count = NPCM7XX_MFT_MAX_CNT + 1;
 170    } else {
 171        /*
 172         * RPM = revolution/min. The time for one revlution (in ns) is
 173         * MINUTE_TO_NANOSECOND / RPM.
 174         */
 175        count = clock_ns_to_ticks(clock, (60 * NANOSECONDS_PER_SECOND) /
 176            (rpm * NPCM7XX_MFT_PULSE_PER_REVOLUTION));
 177    }
 178
 179    if (count > NPCM7XX_MFT_MAX_CNT) {
 180        count = -1;
 181    } else {
 182        /* The CNT is a countdown value from NPCM7XX_MFT_MAX_CNT. */
 183        count = NPCM7XX_MFT_MAX_CNT - count;
 184    }
 185    stopped = npcm7xx_mft_compare(count, tgt, cpcfg);
 186    if (stopped == -1) {
 187        if (count == -1) {
 188            /* Underflow */
 189            state = NPCM7XX_CAPTURE_UNDERFLOW;
 190        } else {
 191            state = NPCM7XX_CAPTURE_SUCCEED;
 192        }
 193    } else {
 194        count = stopped;
 195        state = NPCM7XX_CAPTURE_COMPARE_HIT;
 196    }
 197
 198    if (count != -1) {
 199        *cnt = count;
 200    }
 201    trace_npcm7xx_mft_rpm(clock->canonical_path, clock_get_hz(clock),
 202                          state, count, rpm, duty);
 203    return state;
 204}
 205
 206/*
 207 * Capture Fan RPM and update CNT and CR registers accordingly.
 208 * Raise IRQ if certain contidions are met in IEN.
 209 */
 210static void npcm7xx_mft_capture(NPCM7xxMFTState *s)
 211{
 212    int irq_level = 0;
 213    NPCM7xxMFTCaptureState state;
 214    int sel;
 215    uint8_t cpcfg;
 216
 217    /*
 218     * If not mode 5, the behavior is undefined. We just do nothing in this
 219     * case.
 220     */
 221    if (!(s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_MODE5)) {
 222        return;
 223    }
 224
 225    /* Capture input A. */
 226    if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TAEN &&
 227        s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) {
 228        sel = s->regs[R_NPCM7XX_MFT_INASEL] & NPCM7XX_MFT_INASEL_SELA;
 229        cpcfg = NPCM7XX_MFT_CPCFG_GET_A(s->regs[R_NPCM7XX_MFT_CPCFG]);
 230        state = npcm7xx_mft_compute_cnt(s->clock_1,
 231                                        sel ? s->max_rpm[2] : s->max_rpm[0],
 232                                        sel ? s->duty[2] : s->duty[0],
 233                                        s->regs[R_NPCM7XX_MFT_CPA],
 234                                        cpcfg,
 235                                        &s->regs[R_NPCM7XX_MFT_CNT1]);
 236        switch (state) {
 237        case NPCM7XX_CAPTURE_SUCCEED:
 238            /* Interrupt on input capture on TAn transition - TAPND */
 239            s->regs[R_NPCM7XX_MFT_CRA] = s->regs[R_NPCM7XX_MFT_CNT1];
 240            s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TAPND;
 241            if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TAIEN) {
 242                irq_level = 1;
 243            }
 244            break;
 245
 246        case NPCM7XX_CAPTURE_COMPARE_HIT:
 247            /* Compare Hit - TEPND */
 248            s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TEPND;
 249            if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TEIEN) {
 250                irq_level = 1;
 251            }
 252            break;
 253
 254        case NPCM7XX_CAPTURE_UNDERFLOW:
 255            /* Underflow - TCPND */
 256            s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TCPND;
 257            if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TCIEN) {
 258                irq_level = 1;
 259            }
 260            break;
 261
 262        default:
 263            g_assert_not_reached();
 264        }
 265    }
 266
 267    /* Capture input B. */
 268    if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TBEN &&
 269        s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) {
 270        sel = s->regs[R_NPCM7XX_MFT_INBSEL] & NPCM7XX_MFT_INBSEL_SELB;
 271        cpcfg = NPCM7XX_MFT_CPCFG_GET_B(s->regs[R_NPCM7XX_MFT_CPCFG]);
 272        state = npcm7xx_mft_compute_cnt(s->clock_2,
 273                                        sel ? s->max_rpm[3] : s->max_rpm[1],
 274                                        sel ? s->duty[3] : s->duty[1],
 275                                        s->regs[R_NPCM7XX_MFT_CPB],
 276                                        cpcfg,
 277                                        &s->regs[R_NPCM7XX_MFT_CNT2]);
 278        switch (state) {
 279        case NPCM7XX_CAPTURE_SUCCEED:
 280            /* Interrupt on input capture on TBn transition - TBPND */
 281            s->regs[R_NPCM7XX_MFT_CRB] = s->regs[R_NPCM7XX_MFT_CNT2];
 282            s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TBPND;
 283            if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TBIEN) {
 284                irq_level = 1;
 285            }
 286            break;
 287
 288        case NPCM7XX_CAPTURE_COMPARE_HIT:
 289            /* Compare Hit - TFPND */
 290            s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TFPND;
 291            if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TFIEN) {
 292                irq_level = 1;
 293            }
 294            break;
 295
 296        case NPCM7XX_CAPTURE_UNDERFLOW:
 297            /* Underflow - TDPND */
 298            s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TDPND;
 299            if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TDIEN) {
 300                irq_level = 1;
 301            }
 302            break;
 303
 304        default:
 305            g_assert_not_reached();
 306        }
 307    }
 308
 309    trace_npcm7xx_mft_capture(DEVICE(s)->canonical_path, irq_level);
 310    qemu_set_irq(s->irq, irq_level);
 311}
 312
 313/* Update clock for counters. */
 314static void npcm7xx_mft_update_clock(void *opaque, ClockEvent event)
 315{
 316    NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
 317    uint64_t prescaled_clock_period;
 318
 319    prescaled_clock_period = clock_get(s->clock_in) *
 320        (s->regs[R_NPCM7XX_MFT_PRSC] + 1ULL);
 321    trace_npcm7xx_mft_update_clock(s->clock_in->canonical_path,
 322                                   s->regs[R_NPCM7XX_MFT_CKC],
 323                                   clock_get(s->clock_in),
 324                                   prescaled_clock_period);
 325    /* Update clock 1 */
 326    if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) {
 327        /* Clock is prescaled. */
 328        clock_update(s->clock_1, prescaled_clock_period);
 329    } else {
 330        /* Clock stopped. */
 331        clock_update(s->clock_1, 0);
 332    }
 333    /* Update clock 2 */
 334    if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) {
 335        /* Clock is prescaled. */
 336        clock_update(s->clock_2, prescaled_clock_period);
 337    } else {
 338        /* Clock stopped. */
 339        clock_update(s->clock_2, 0);
 340    }
 341
 342    npcm7xx_mft_capture(s);
 343}
 344
 345static uint64_t npcm7xx_mft_read(void *opaque, hwaddr offset, unsigned size)
 346{
 347    NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
 348    uint16_t value = 0;
 349
 350    switch (offset) {
 351    case A_NPCM7XX_MFT_ICLR:
 352        qemu_log_mask(LOG_GUEST_ERROR,
 353                      "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n",
 354                      __func__, offset);
 355        break;
 356
 357    default:
 358        value = s->regs[offset / 2];
 359    }
 360
 361    trace_npcm7xx_mft_read(DEVICE(s)->canonical_path, offset, value);
 362    return value;
 363}
 364
 365static void npcm7xx_mft_write(void *opaque, hwaddr offset,
 366                              uint64_t v, unsigned size)
 367{
 368    NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
 369
 370    trace_npcm7xx_mft_write(DEVICE(s)->canonical_path, offset, v);
 371    switch (offset) {
 372    case A_NPCM7XX_MFT_ICLR:
 373        npcm7xx_mft_clear_interrupt(s, v);
 374        break;
 375
 376    case A_NPCM7XX_MFT_CKC:
 377    case A_NPCM7XX_MFT_PRSC:
 378        s->regs[offset / 2] = v;
 379        npcm7xx_mft_update_clock(s, ClockUpdate);
 380        break;
 381
 382    default:
 383        s->regs[offset / 2] = v;
 384        npcm7xx_mft_capture(s);
 385        break;
 386    }
 387}
 388
 389static bool npcm7xx_mft_check_mem_op(void *opaque, hwaddr offset,
 390                                     unsigned size, bool is_write,
 391                                     MemTxAttrs attrs)
 392{
 393    switch (offset) {
 394    /* 16-bit registers. Must be accessed with 16-bit read/write.*/
 395    case A_NPCM7XX_MFT_CNT1:
 396    case A_NPCM7XX_MFT_CRA:
 397    case A_NPCM7XX_MFT_CRB:
 398    case A_NPCM7XX_MFT_CNT2:
 399    case A_NPCM7XX_MFT_CPA:
 400    case A_NPCM7XX_MFT_CPB:
 401        return size == 2;
 402
 403    /* 8-bit registers. Must be accessed with 8-bit read/write.*/
 404    case A_NPCM7XX_MFT_PRSC:
 405    case A_NPCM7XX_MFT_CKC:
 406    case A_NPCM7XX_MFT_MCTRL:
 407    case A_NPCM7XX_MFT_ICTRL:
 408    case A_NPCM7XX_MFT_ICLR:
 409    case A_NPCM7XX_MFT_IEN:
 410    case A_NPCM7XX_MFT_CPCFG:
 411    case A_NPCM7XX_MFT_INASEL:
 412    case A_NPCM7XX_MFT_INBSEL:
 413        return size == 1;
 414
 415    default:
 416        /* Invalid registers. */
 417        return false;
 418    }
 419}
 420
 421static void npcm7xx_mft_get_max_rpm(Object *obj, Visitor *v, const char *name,
 422                                    void *opaque, Error **errp)
 423{
 424    visit_type_uint32(v, name, (uint32_t *)opaque, errp);
 425}
 426
 427static void npcm7xx_mft_set_max_rpm(Object *obj, Visitor *v, const char *name,
 428                                    void *opaque, Error **errp)
 429{
 430    NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
 431    uint32_t *max_rpm = opaque;
 432    uint32_t value;
 433
 434    if (!visit_type_uint32(v, name, &value, errp)) {
 435        return;
 436    }
 437
 438    *max_rpm = value;
 439    npcm7xx_mft_capture(s);
 440}
 441
 442static void npcm7xx_mft_duty_handler(void *opaque, int n, int value)
 443{
 444    NPCM7xxMFTState *s = NPCM7XX_MFT(opaque);
 445
 446    trace_npcm7xx_mft_set_duty(DEVICE(s)->canonical_path, n, value);
 447    s->duty[n] = value;
 448    npcm7xx_mft_capture(s);
 449}
 450
 451static const struct MemoryRegionOps npcm7xx_mft_ops = {
 452    .read       = npcm7xx_mft_read,
 453    .write      = npcm7xx_mft_write,
 454    .endianness = DEVICE_LITTLE_ENDIAN,
 455    .valid      = {
 456        .min_access_size        = 1,
 457        .max_access_size        = 2,
 458        .unaligned              = false,
 459        .accepts                = npcm7xx_mft_check_mem_op,
 460    },
 461};
 462
 463static void npcm7xx_mft_enter_reset(Object *obj, ResetType type)
 464{
 465    NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
 466
 467    npcm7xx_mft_reset(s);
 468}
 469
 470static void npcm7xx_mft_hold_reset(Object *obj)
 471{
 472    NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
 473
 474    qemu_irq_lower(s->irq);
 475}
 476
 477static void npcm7xx_mft_init(Object *obj)
 478{
 479    NPCM7xxMFTState *s = NPCM7XX_MFT(obj);
 480    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
 481    DeviceState *dev = DEVICE(obj);
 482
 483    memory_region_init_io(&s->iomem, obj, &npcm7xx_mft_ops, s,
 484                          TYPE_NPCM7XX_MFT, 4 * KiB);
 485    sysbus_init_mmio(sbd, &s->iomem);
 486    sysbus_init_irq(sbd, &s->irq);
 487    s->clock_in = qdev_init_clock_in(dev, "clock-in", npcm7xx_mft_update_clock,
 488                                     s, ClockUpdate);
 489    s->clock_1 = qdev_init_clock_out(dev, "clock1");
 490    s->clock_2 = qdev_init_clock_out(dev, "clock2");
 491
 492    for (int i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
 493        object_property_add(obj, "max_rpm[*]", "uint32",
 494                            npcm7xx_mft_get_max_rpm,
 495                            npcm7xx_mft_set_max_rpm,
 496                            NULL, &s->max_rpm[i]);
 497    }
 498    qdev_init_gpio_in_named(dev, npcm7xx_mft_duty_handler, "duty",
 499                            NPCM7XX_MFT_FANIN_COUNT);
 500}
 501
 502static const VMStateDescription vmstate_npcm7xx_mft = {
 503    .name = "npcm7xx-mft-module",
 504    .version_id = 0,
 505    .minimum_version_id = 0,
 506    .fields = (VMStateField[]) {
 507        VMSTATE_CLOCK(clock_in, NPCM7xxMFTState),
 508        VMSTATE_CLOCK(clock_1, NPCM7xxMFTState),
 509        VMSTATE_CLOCK(clock_2, NPCM7xxMFTState),
 510        VMSTATE_UINT16_ARRAY(regs, NPCM7xxMFTState, NPCM7XX_MFT_NR_REGS),
 511        VMSTATE_UINT32_ARRAY(max_rpm, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT),
 512        VMSTATE_UINT32_ARRAY(duty, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT),
 513        VMSTATE_END_OF_LIST(),
 514    },
 515};
 516
 517static void npcm7xx_mft_class_init(ObjectClass *klass, void *data)
 518{
 519    ResettableClass *rc = RESETTABLE_CLASS(klass);
 520    DeviceClass *dc = DEVICE_CLASS(klass);
 521
 522    dc->desc = "NPCM7xx MFT Controller";
 523    dc->vmsd = &vmstate_npcm7xx_mft;
 524    rc->phases.enter = npcm7xx_mft_enter_reset;
 525    rc->phases.hold = npcm7xx_mft_hold_reset;
 526}
 527
 528static const TypeInfo npcm7xx_mft_info = {
 529    .name               = TYPE_NPCM7XX_MFT,
 530    .parent             = TYPE_SYS_BUS_DEVICE,
 531    .instance_size      = sizeof(NPCM7xxMFTState),
 532    .class_init         = npcm7xx_mft_class_init,
 533    .instance_init      = npcm7xx_mft_init,
 534};
 535
 536static void npcm7xx_mft_register_type(void)
 537{
 538    type_register_static(&npcm7xx_mft_info);
 539}
 540type_init(npcm7xx_mft_register_type);
 541