 593b910ebe
			
		
	
	
		593b910ebe
		
	
	
	
	
		
			
			The value of an arithmetic expression 'rpm * NPCM7XX_MFT_PULSE_PER_REVOLUTION' is a subject to overflow because its operands are not cast to a larger data type before performing arithmetic. Thus, need to cast rpm to uint64_t. Found by Linux Verification Center (linuxtesting.org) with SVACE. Signed-off-by: Tigran Sogomonian <tsogomonian@astralinux.ru> Reviewed-by: Patrick Leis <venture@google.com> Reviewed-by: Hao Wu <wuhaotsh@google.com> Message-id: 20241226130311.1349-1-tsogomonian@astralinux.ru Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
		
			
				
	
	
		
			542 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			542 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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,
 | |
|             (uint64_t)(60 * NANOSECONDS_PER_SECOND) /
 | |
|             ((uint64_t)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, ResetType type)
 | |
| {
 | |
|     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 = (const 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);
 |