| /* |
| * Nuvoton NPCM7xx MFT Module |
| * |
| * Copyright 2021 Google LLC |
| * |
| * 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. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "hw/irq.h" |
| #include "hw/qdev-clock.h" |
| #include "hw/qdev-properties.h" |
| #include "hw/misc/npcm7xx_mft.h" |
| #include "hw/misc/npcm7xx_pwm.h" |
| #include "hw/registerfields.h" |
| #include "migration/vmstate.h" |
| #include "qapi/error.h" |
| #include "qapi/visitor.h" |
| #include "qemu/bitops.h" |
| #include "qemu/error-report.h" |
| #include "qemu/log.h" |
| #include "qemu/module.h" |
| #include "qemu/timer.h" |
| #include "qemu/units.h" |
| #include "trace.h" |
| |
| /* |
| * Some of the registers can only accessed via 16-bit ops and some can only |
| * be accessed via 8-bit ops. However we mark all of them using REG16 to |
| * simplify implementation. npcm7xx_mft_check_mem_op checks the access length |
| * of memory operations. |
| */ |
| REG16(NPCM7XX_MFT_CNT1, 0x00); |
| REG16(NPCM7XX_MFT_CRA, 0x02); |
| REG16(NPCM7XX_MFT_CRB, 0x04); |
| REG16(NPCM7XX_MFT_CNT2, 0x06); |
| REG16(NPCM7XX_MFT_PRSC, 0x08); |
| REG16(NPCM7XX_MFT_CKC, 0x0a); |
| REG16(NPCM7XX_MFT_MCTRL, 0x0c); |
| REG16(NPCM7XX_MFT_ICTRL, 0x0e); |
| REG16(NPCM7XX_MFT_ICLR, 0x10); |
| REG16(NPCM7XX_MFT_IEN, 0x12); |
| REG16(NPCM7XX_MFT_CPA, 0x14); |
| REG16(NPCM7XX_MFT_CPB, 0x16); |
| REG16(NPCM7XX_MFT_CPCFG, 0x18); |
| REG16(NPCM7XX_MFT_INASEL, 0x1a); |
| REG16(NPCM7XX_MFT_INBSEL, 0x1c); |
| |
| /* Register Fields */ |
| #define NPCM7XX_MFT_CKC_C2CSEL BIT(3) |
| #define NPCM7XX_MFT_CKC_C1CSEL BIT(0) |
| |
| #define NPCM7XX_MFT_MCTRL_TBEN BIT(6) |
| #define NPCM7XX_MFT_MCTRL_TAEN BIT(5) |
| #define NPCM7XX_MFT_MCTRL_TBEDG BIT(4) |
| #define NPCM7XX_MFT_MCTRL_TAEDG BIT(3) |
| #define NPCM7XX_MFT_MCTRL_MODE5 BIT(2) |
| |
| #define NPCM7XX_MFT_ICTRL_TFPND BIT(5) |
| #define NPCM7XX_MFT_ICTRL_TEPND BIT(4) |
| #define NPCM7XX_MFT_ICTRL_TDPND BIT(3) |
| #define NPCM7XX_MFT_ICTRL_TCPND BIT(2) |
| #define NPCM7XX_MFT_ICTRL_TBPND BIT(1) |
| #define NPCM7XX_MFT_ICTRL_TAPND BIT(0) |
| |
| #define NPCM7XX_MFT_ICLR_TFCLR BIT(5) |
| #define NPCM7XX_MFT_ICLR_TECLR BIT(4) |
| #define NPCM7XX_MFT_ICLR_TDCLR BIT(3) |
| #define NPCM7XX_MFT_ICLR_TCCLR BIT(2) |
| #define NPCM7XX_MFT_ICLR_TBCLR BIT(1) |
| #define NPCM7XX_MFT_ICLR_TACLR BIT(0) |
| |
| #define NPCM7XX_MFT_IEN_TFIEN BIT(5) |
| #define NPCM7XX_MFT_IEN_TEIEN BIT(4) |
| #define NPCM7XX_MFT_IEN_TDIEN BIT(3) |
| #define NPCM7XX_MFT_IEN_TCIEN BIT(2) |
| #define NPCM7XX_MFT_IEN_TBIEN BIT(1) |
| #define NPCM7XX_MFT_IEN_TAIEN BIT(0) |
| |
| #define NPCM7XX_MFT_CPCFG_GET_B(rv) extract8((rv), 4, 4) |
| #define NPCM7XX_MFT_CPCFG_GET_A(rv) extract8((rv), 0, 4) |
| #define NPCM7XX_MFT_CPCFG_HIEN BIT(3) |
| #define NPCM7XX_MFT_CPCFG_EQEN BIT(2) |
| #define NPCM7XX_MFT_CPCFG_LOEN BIT(1) |
| #define NPCM7XX_MFT_CPCFG_CPSEL BIT(0) |
| |
| #define NPCM7XX_MFT_INASEL_SELA BIT(0) |
| #define NPCM7XX_MFT_INBSEL_SELB BIT(0) |
| |
| /* Max CNT values of the module. The CNT value is a countdown from it. */ |
| #define NPCM7XX_MFT_MAX_CNT 0xFFFF |
| |
| /* Each fan revolution should generated 2 pulses */ |
| #define NPCM7XX_MFT_PULSE_PER_REVOLUTION 2 |
| |
| typedef enum NPCM7xxMFTCaptureState { |
| /* capture succeeded with a valid CNT value. */ |
| NPCM7XX_CAPTURE_SUCCEED, |
| /* capture stopped prematurely due to reaching CPCFG condition. */ |
| NPCM7XX_CAPTURE_COMPARE_HIT, |
| /* capture fails since it reaches underflow condition for CNT. */ |
| NPCM7XX_CAPTURE_UNDERFLOW, |
| } NPCM7xxMFTCaptureState; |
| |
| static void npcm7xx_mft_reset(NPCM7xxMFTState *s) |
| { |
| int i; |
| |
| /* Only registers PRSC ~ INBSEL need to be reset. */ |
| for (i = R_NPCM7XX_MFT_PRSC; i <= R_NPCM7XX_MFT_INBSEL; ++i) { |
| s->regs[i] = 0; |
| } |
| } |
| |
| static void npcm7xx_mft_clear_interrupt(NPCM7xxMFTState *s, uint8_t iclr) |
| { |
| /* |
| * Clear bits in ICTRL where corresponding bits in iclr is 1. |
| * Both iclr and ictrl are 8-bit regs. (See npcm7xx_mft_check_mem_op) |
| */ |
| s->regs[R_NPCM7XX_MFT_ICTRL] &= ~iclr; |
| } |
| |
| /* |
| * If the CPCFG's condition should be triggered during count down from |
| * NPCM7XX_MFT_MAX_CNT to src if compared to tgt, return the count when |
| * the condition is triggered. |
| * Otherwise return -1. |
| * Since tgt is uint16_t it must always <= NPCM7XX_MFT_MAX_CNT. |
| */ |
| static int npcm7xx_mft_compare(int32_t src, uint16_t tgt, uint8_t cpcfg) |
| { |
| if (cpcfg & NPCM7XX_MFT_CPCFG_HIEN) { |
| return NPCM7XX_MFT_MAX_CNT; |
| } |
| if ((cpcfg & NPCM7XX_MFT_CPCFG_EQEN) && (src <= tgt)) { |
| return tgt; |
| } |
| if ((cpcfg & NPCM7XX_MFT_CPCFG_LOEN) && (tgt > 0) && (src < tgt)) { |
| return tgt - 1; |
| } |
| |
| return -1; |
| } |
| |
| /* Compute CNT according to corresponding fan's RPM. */ |
| static NPCM7xxMFTCaptureState npcm7xx_mft_compute_cnt( |
| Clock *clock, uint32_t max_rpm, uint32_t duty, uint16_t tgt, |
| uint8_t cpcfg, uint16_t *cnt) |
| { |
| uint32_t rpm = (uint64_t)max_rpm * (uint64_t)duty / NPCM7XX_PWM_MAX_DUTY; |
| int32_t count; |
| int stopped; |
| NPCM7xxMFTCaptureState state; |
| |
| if (rpm == 0) { |
| /* |
| * If RPM = 0, capture won't happen. CNT will continue count down. |
| * So it's effective equivalent to have a cnt > NPCM7XX_MFT_MAX_CNT |
| */ |
| count = NPCM7XX_MFT_MAX_CNT + 1; |
| } else { |
| /* |
| * RPM = revolution/min. The time for one revlution (in ns) is |
| * MINUTE_TO_NANOSECOND / RPM. |
| */ |
| count = clock_ns_to_ticks(clock, (60 * NANOSECONDS_PER_SECOND) / |
| (rpm * NPCM7XX_MFT_PULSE_PER_REVOLUTION)); |
| } |
| |
| if (count > NPCM7XX_MFT_MAX_CNT) { |
| count = -1; |
| } else { |
| /* The CNT is a countdown value from NPCM7XX_MFT_MAX_CNT. */ |
| count = NPCM7XX_MFT_MAX_CNT - count; |
| } |
| stopped = npcm7xx_mft_compare(count, tgt, cpcfg); |
| if (stopped == -1) { |
| if (count == -1) { |
| /* Underflow */ |
| state = NPCM7XX_CAPTURE_UNDERFLOW; |
| } else { |
| state = NPCM7XX_CAPTURE_SUCCEED; |
| } |
| } else { |
| count = stopped; |
| state = NPCM7XX_CAPTURE_COMPARE_HIT; |
| } |
| |
| if (count != -1) { |
| *cnt = count; |
| } |
| trace_npcm7xx_mft_rpm(clock->canonical_path, clock_get_hz(clock), |
| state, count, rpm, duty); |
| return state; |
| } |
| |
| /* |
| * Capture Fan RPM and update CNT and CR registers accordingly. |
| * Raise IRQ if certain contidions are met in IEN. |
| */ |
| static void npcm7xx_mft_capture(NPCM7xxMFTState *s) |
| { |
| int irq_level = 0; |
| NPCM7xxMFTCaptureState state; |
| int sel; |
| uint8_t cpcfg; |
| |
| /* |
| * If not mode 5, the behavior is undefined. We just do nothing in this |
| * case. |
| */ |
| if (!(s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_MODE5)) { |
| return; |
| } |
| |
| /* Capture input A. */ |
| if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TAEN && |
| s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) { |
| sel = s->regs[R_NPCM7XX_MFT_INASEL] & NPCM7XX_MFT_INASEL_SELA; |
| cpcfg = NPCM7XX_MFT_CPCFG_GET_A(s->regs[R_NPCM7XX_MFT_CPCFG]); |
| state = npcm7xx_mft_compute_cnt(s->clock_1, |
| sel ? s->max_rpm[2] : s->max_rpm[0], |
| sel ? s->duty[2] : s->duty[0], |
| s->regs[R_NPCM7XX_MFT_CPA], |
| cpcfg, |
| &s->regs[R_NPCM7XX_MFT_CNT1]); |
| switch (state) { |
| case NPCM7XX_CAPTURE_SUCCEED: |
| /* Interrupt on input capture on TAn transition - TAPND */ |
| s->regs[R_NPCM7XX_MFT_CRA] = s->regs[R_NPCM7XX_MFT_CNT1]; |
| s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TAPND; |
| if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TAIEN) { |
| irq_level = 1; |
| } |
| break; |
| |
| case NPCM7XX_CAPTURE_COMPARE_HIT: |
| /* Compare Hit - TEPND */ |
| s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TEPND; |
| if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TEIEN) { |
| irq_level = 1; |
| } |
| break; |
| |
| case NPCM7XX_CAPTURE_UNDERFLOW: |
| /* Underflow - TCPND */ |
| s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TCPND; |
| if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TCIEN) { |
| irq_level = 1; |
| } |
| break; |
| |
| default: |
| g_assert_not_reached(); |
| } |
| } |
| |
| /* Capture input B. */ |
| if (s->regs[R_NPCM7XX_MFT_MCTRL] & NPCM7XX_MFT_MCTRL_TBEN && |
| s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) { |
| sel = s->regs[R_NPCM7XX_MFT_INBSEL] & NPCM7XX_MFT_INBSEL_SELB; |
| cpcfg = NPCM7XX_MFT_CPCFG_GET_B(s->regs[R_NPCM7XX_MFT_CPCFG]); |
| state = npcm7xx_mft_compute_cnt(s->clock_2, |
| sel ? s->max_rpm[3] : s->max_rpm[1], |
| sel ? s->duty[3] : s->duty[1], |
| s->regs[R_NPCM7XX_MFT_CPB], |
| cpcfg, |
| &s->regs[R_NPCM7XX_MFT_CNT2]); |
| switch (state) { |
| case NPCM7XX_CAPTURE_SUCCEED: |
| /* Interrupt on input capture on TBn transition - TBPND */ |
| s->regs[R_NPCM7XX_MFT_CRB] = s->regs[R_NPCM7XX_MFT_CNT2]; |
| s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TBPND; |
| if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TBIEN) { |
| irq_level = 1; |
| } |
| break; |
| |
| case NPCM7XX_CAPTURE_COMPARE_HIT: |
| /* Compare Hit - TFPND */ |
| s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TFPND; |
| if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TFIEN) { |
| irq_level = 1; |
| } |
| break; |
| |
| case NPCM7XX_CAPTURE_UNDERFLOW: |
| /* Underflow - TDPND */ |
| s->regs[R_NPCM7XX_MFT_ICTRL] |= NPCM7XX_MFT_ICTRL_TDPND; |
| if (s->regs[R_NPCM7XX_MFT_IEN] & NPCM7XX_MFT_IEN_TDIEN) { |
| irq_level = 1; |
| } |
| break; |
| |
| default: |
| g_assert_not_reached(); |
| } |
| } |
| |
| trace_npcm7xx_mft_capture(DEVICE(s)->canonical_path, irq_level); |
| qemu_set_irq(s->irq, irq_level); |
| } |
| |
| /* Update clock for counters. */ |
| static void npcm7xx_mft_update_clock(void *opaque, ClockEvent event) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); |
| uint64_t prescaled_clock_period; |
| |
| prescaled_clock_period = clock_get(s->clock_in) * |
| (s->regs[R_NPCM7XX_MFT_PRSC] + 1ULL); |
| trace_npcm7xx_mft_update_clock(s->clock_in->canonical_path, |
| s->regs[R_NPCM7XX_MFT_CKC], |
| clock_get(s->clock_in), |
| prescaled_clock_period); |
| /* Update clock 1 */ |
| if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C1CSEL) { |
| /* Clock is prescaled. */ |
| clock_update(s->clock_1, prescaled_clock_period); |
| } else { |
| /* Clock stopped. */ |
| clock_update(s->clock_1, 0); |
| } |
| /* Update clock 2 */ |
| if (s->regs[R_NPCM7XX_MFT_CKC] & NPCM7XX_MFT_CKC_C2CSEL) { |
| /* Clock is prescaled. */ |
| clock_update(s->clock_2, prescaled_clock_period); |
| } else { |
| /* Clock stopped. */ |
| clock_update(s->clock_2, 0); |
| } |
| |
| npcm7xx_mft_capture(s); |
| } |
| |
| static uint64_t npcm7xx_mft_read(void *opaque, hwaddr offset, unsigned size) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); |
| uint16_t value = 0; |
| |
| switch (offset) { |
| case A_NPCM7XX_MFT_ICLR: |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "%s: register @ 0x%04" HWADDR_PRIx " is write-only\n", |
| __func__, offset); |
| break; |
| |
| default: |
| value = s->regs[offset / 2]; |
| } |
| |
| trace_npcm7xx_mft_read(DEVICE(s)->canonical_path, offset, value); |
| return value; |
| } |
| |
| static void npcm7xx_mft_write(void *opaque, hwaddr offset, |
| uint64_t v, unsigned size) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); |
| |
| trace_npcm7xx_mft_write(DEVICE(s)->canonical_path, offset, v); |
| switch (offset) { |
| case A_NPCM7XX_MFT_ICLR: |
| npcm7xx_mft_clear_interrupt(s, v); |
| break; |
| |
| case A_NPCM7XX_MFT_CKC: |
| case A_NPCM7XX_MFT_PRSC: |
| s->regs[offset / 2] = v; |
| npcm7xx_mft_update_clock(s, ClockUpdate); |
| break; |
| |
| default: |
| s->regs[offset / 2] = v; |
| npcm7xx_mft_capture(s); |
| break; |
| } |
| } |
| |
| static bool npcm7xx_mft_check_mem_op(void *opaque, hwaddr offset, |
| unsigned size, bool is_write, |
| MemTxAttrs attrs) |
| { |
| switch (offset) { |
| /* 16-bit registers. Must be accessed with 16-bit read/write.*/ |
| case A_NPCM7XX_MFT_CNT1: |
| case A_NPCM7XX_MFT_CRA: |
| case A_NPCM7XX_MFT_CRB: |
| case A_NPCM7XX_MFT_CNT2: |
| case A_NPCM7XX_MFT_CPA: |
| case A_NPCM7XX_MFT_CPB: |
| return size == 2; |
| |
| /* 8-bit registers. Must be accessed with 8-bit read/write.*/ |
| case A_NPCM7XX_MFT_PRSC: |
| case A_NPCM7XX_MFT_CKC: |
| case A_NPCM7XX_MFT_MCTRL: |
| case A_NPCM7XX_MFT_ICTRL: |
| case A_NPCM7XX_MFT_ICLR: |
| case A_NPCM7XX_MFT_IEN: |
| case A_NPCM7XX_MFT_CPCFG: |
| case A_NPCM7XX_MFT_INASEL: |
| case A_NPCM7XX_MFT_INBSEL: |
| return size == 1; |
| |
| default: |
| /* Invalid registers. */ |
| return false; |
| } |
| } |
| |
| static void npcm7xx_mft_get_max_rpm(Object *obj, Visitor *v, const char *name, |
| void *opaque, Error **errp) |
| { |
| visit_type_uint32(v, name, (uint32_t *)opaque, errp); |
| } |
| |
| static void npcm7xx_mft_set_max_rpm(Object *obj, Visitor *v, const char *name, |
| void *opaque, Error **errp) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(obj); |
| uint32_t *max_rpm = opaque; |
| uint32_t value; |
| |
| if (!visit_type_uint32(v, name, &value, errp)) { |
| return; |
| } |
| |
| *max_rpm = value; |
| npcm7xx_mft_capture(s); |
| } |
| |
| static void npcm7xx_mft_duty_handler(void *opaque, int n, int value) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(opaque); |
| |
| trace_npcm7xx_mft_set_duty(DEVICE(s)->canonical_path, n, value); |
| s->duty[n] = value; |
| npcm7xx_mft_capture(s); |
| } |
| |
| static const struct MemoryRegionOps npcm7xx_mft_ops = { |
| .read = npcm7xx_mft_read, |
| .write = npcm7xx_mft_write, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .valid = { |
| .min_access_size = 1, |
| .max_access_size = 2, |
| .unaligned = false, |
| .accepts = npcm7xx_mft_check_mem_op, |
| }, |
| }; |
| |
| static void npcm7xx_mft_enter_reset(Object *obj, ResetType type) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(obj); |
| |
| npcm7xx_mft_reset(s); |
| } |
| |
| static void npcm7xx_mft_hold_reset(Object *obj) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(obj); |
| |
| qemu_irq_lower(s->irq); |
| } |
| |
| static void npcm7xx_mft_init(Object *obj) |
| { |
| NPCM7xxMFTState *s = NPCM7XX_MFT(obj); |
| SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
| DeviceState *dev = DEVICE(obj); |
| |
| memory_region_init_io(&s->iomem, obj, &npcm7xx_mft_ops, s, |
| TYPE_NPCM7XX_MFT, 4 * KiB); |
| sysbus_init_mmio(sbd, &s->iomem); |
| sysbus_init_irq(sbd, &s->irq); |
| s->clock_in = qdev_init_clock_in(dev, "clock-in", npcm7xx_mft_update_clock, |
| s, ClockUpdate); |
| s->clock_1 = qdev_init_clock_out(dev, "clock1"); |
| s->clock_2 = qdev_init_clock_out(dev, "clock2"); |
| |
| for (int i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) { |
| object_property_add(obj, "max_rpm[*]", "uint32", |
| npcm7xx_mft_get_max_rpm, |
| npcm7xx_mft_set_max_rpm, |
| NULL, &s->max_rpm[i]); |
| } |
| qdev_init_gpio_in_named(dev, npcm7xx_mft_duty_handler, "duty", |
| NPCM7XX_MFT_FANIN_COUNT); |
| } |
| |
| static const VMStateDescription vmstate_npcm7xx_mft = { |
| .name = "npcm7xx-mft-module", |
| .version_id = 0, |
| .minimum_version_id = 0, |
| .fields = (VMStateField[]) { |
| VMSTATE_CLOCK(clock_in, NPCM7xxMFTState), |
| VMSTATE_CLOCK(clock_1, NPCM7xxMFTState), |
| VMSTATE_CLOCK(clock_2, NPCM7xxMFTState), |
| VMSTATE_UINT16_ARRAY(regs, NPCM7xxMFTState, NPCM7XX_MFT_NR_REGS), |
| VMSTATE_UINT32_ARRAY(max_rpm, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT), |
| VMSTATE_UINT32_ARRAY(duty, NPCM7xxMFTState, NPCM7XX_MFT_FANIN_COUNT), |
| VMSTATE_END_OF_LIST(), |
| }, |
| }; |
| |
| static void npcm7xx_mft_class_init(ObjectClass *klass, void *data) |
| { |
| ResettableClass *rc = RESETTABLE_CLASS(klass); |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->desc = "NPCM7xx MFT Controller"; |
| dc->vmsd = &vmstate_npcm7xx_mft; |
| rc->phases.enter = npcm7xx_mft_enter_reset; |
| rc->phases.hold = npcm7xx_mft_hold_reset; |
| } |
| |
| static const TypeInfo npcm7xx_mft_info = { |
| .name = TYPE_NPCM7XX_MFT, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(NPCM7xxMFTState), |
| .class_init = npcm7xx_mft_class_init, |
| .instance_init = npcm7xx_mft_init, |
| }; |
| |
| static void npcm7xx_mft_register_type(void) |
| { |
| type_register_static(&npcm7xx_mft_info); |
| } |
| type_init(npcm7xx_mft_register_type); |