qemu/hw/misc/npcm7xx_pwm.c
Hao Wu 71b50b9d1c hw/misc: Add GPIOs for duty in NPCM7xx PWM
This patch adds GPIOs in NPCM7xx PWM module for its duty values.
The purpose of this is to connect it to the MFT module to provide
an input for measuring a PWM fan's RPM. Each PWM module has
NPCM7XX_PWM_PER_MODULE of GPIOs, each one corresponds to
one PWM instance and can connect to multiple fan instances in MFT.

Reviewed-by: Doug Evans <dje@google.com>
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Message-id: 20210311180855.149764-2-wuhaotsh@google.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2021-03-12 12:48:56 +00:00

570 lines
16 KiB
C

/*
* Nuvoton NPCM7xx PWM Module
*
* Copyright 2020 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_pwm.h"
#include "hw/registerfields.h"
#include "migration/vmstate.h"
#include "qemu/bitops.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/units.h"
#include "trace.h"
REG32(NPCM7XX_PWM_PPR, 0x00);
REG32(NPCM7XX_PWM_CSR, 0x04);
REG32(NPCM7XX_PWM_PCR, 0x08);
REG32(NPCM7XX_PWM_CNR0, 0x0c);
REG32(NPCM7XX_PWM_CMR0, 0x10);
REG32(NPCM7XX_PWM_PDR0, 0x14);
REG32(NPCM7XX_PWM_CNR1, 0x18);
REG32(NPCM7XX_PWM_CMR1, 0x1c);
REG32(NPCM7XX_PWM_PDR1, 0x20);
REG32(NPCM7XX_PWM_CNR2, 0x24);
REG32(NPCM7XX_PWM_CMR2, 0x28);
REG32(NPCM7XX_PWM_PDR2, 0x2c);
REG32(NPCM7XX_PWM_CNR3, 0x30);
REG32(NPCM7XX_PWM_CMR3, 0x34);
REG32(NPCM7XX_PWM_PDR3, 0x38);
REG32(NPCM7XX_PWM_PIER, 0x3c);
REG32(NPCM7XX_PWM_PIIR, 0x40);
REG32(NPCM7XX_PWM_PWDR0, 0x44);
REG32(NPCM7XX_PWM_PWDR1, 0x48);
REG32(NPCM7XX_PWM_PWDR2, 0x4c);
REG32(NPCM7XX_PWM_PWDR3, 0x50);
/* Register field definitions. */
#define NPCM7XX_PPR(rv, index) extract32((rv), npcm7xx_ppr_base[index], 8)
#define NPCM7XX_CSR(rv, index) extract32((rv), npcm7xx_csr_base[index], 3)
#define NPCM7XX_CH(rv, index) extract32((rv), npcm7xx_ch_base[index], 4)
#define NPCM7XX_CH_EN BIT(0)
#define NPCM7XX_CH_INV BIT(2)
#define NPCM7XX_CH_MOD BIT(3)
#define NPCM7XX_MAX_CMR 65535
#define NPCM7XX_MAX_CNR 65535
/* Offset of each PWM channel's prescaler in the PPR register. */
static const int npcm7xx_ppr_base[] = { 0, 0, 8, 8 };
/* Offset of each PWM channel's clock selector in the CSR register. */
static const int npcm7xx_csr_base[] = { 0, 4, 8, 12 };
/* Offset of each PWM channel's control variable in the PCR register. */
static const int npcm7xx_ch_base[] = { 0, 8, 12, 16 };
static uint32_t npcm7xx_pwm_calculate_freq(NPCM7xxPWM *p)
{
uint32_t ppr;
uint32_t csr;
uint32_t freq;
if (!p->running) {
return 0;
}
csr = NPCM7XX_CSR(p->module->csr, p->index);
ppr = NPCM7XX_PPR(p->module->ppr, p->index);
freq = clock_get_hz(p->module->clock);
freq /= ppr + 1;
/* csr can only be 0~4 */
if (csr > 4) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid csr value %u\n",
__func__, csr);
csr = 4;
}
/* freq won't be changed if csr == 4. */
if (csr < 4) {
freq >>= csr + 1;
}
return freq / (p->cnr + 1);
}
static uint32_t npcm7xx_pwm_calculate_duty(NPCM7xxPWM *p)
{
uint32_t duty;
if (p->running) {
if (p->cnr == 0) {
duty = 0;
} else if (p->cmr >= p->cnr) {
duty = NPCM7XX_PWM_MAX_DUTY;
} else {
duty = (uint64_t)NPCM7XX_PWM_MAX_DUTY * (p->cmr + 1) / (p->cnr + 1);
}
} else {
duty = 0;
}
if (p->inverted) {
duty = NPCM7XX_PWM_MAX_DUTY - duty;
}
return duty;
}
static void npcm7xx_pwm_update_freq(NPCM7xxPWM *p)
{
uint32_t freq = npcm7xx_pwm_calculate_freq(p);
if (freq != p->freq) {
trace_npcm7xx_pwm_update_freq(DEVICE(p->module)->canonical_path,
p->index, p->freq, freq);
p->freq = freq;
}
}
static void npcm7xx_pwm_update_duty(NPCM7xxPWM *p)
{
uint32_t duty = npcm7xx_pwm_calculate_duty(p);
if (duty != p->duty) {
trace_npcm7xx_pwm_update_duty(DEVICE(p->module)->canonical_path,
p->index, p->duty, duty);
p->duty = duty;
qemu_set_irq(p->module->duty_gpio_out[p->index], p->duty);
}
}
static void npcm7xx_pwm_update_output(NPCM7xxPWM *p)
{
npcm7xx_pwm_update_freq(p);
npcm7xx_pwm_update_duty(p);
}
static void npcm7xx_pwm_write_ppr(NPCM7xxPWMState *s, uint32_t new_ppr)
{
int i;
uint32_t old_ppr = s->ppr;
QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ppr_base) != NPCM7XX_PWM_PER_MODULE);
s->ppr = new_ppr;
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
if (NPCM7XX_PPR(old_ppr, i) != NPCM7XX_PPR(new_ppr, i)) {
npcm7xx_pwm_update_freq(&s->pwm[i]);
}
}
}
static void npcm7xx_pwm_write_csr(NPCM7xxPWMState *s, uint32_t new_csr)
{
int i;
uint32_t old_csr = s->csr;
QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_csr_base) != NPCM7XX_PWM_PER_MODULE);
s->csr = new_csr;
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
if (NPCM7XX_CSR(old_csr, i) != NPCM7XX_CSR(new_csr, i)) {
npcm7xx_pwm_update_freq(&s->pwm[i]);
}
}
}
static void npcm7xx_pwm_write_pcr(NPCM7xxPWMState *s, uint32_t new_pcr)
{
int i;
bool inverted;
uint32_t pcr;
NPCM7xxPWM *p;
s->pcr = new_pcr;
QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_ch_base) != NPCM7XX_PWM_PER_MODULE);
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
p = &s->pwm[i];
pcr = NPCM7XX_CH(new_pcr, i);
inverted = pcr & NPCM7XX_CH_INV;
/*
* We only run a PWM channel with toggle mode. Single-shot mode does not
* generate frequency and duty-cycle values.
*/
if ((pcr & NPCM7XX_CH_EN) && (pcr & NPCM7XX_CH_MOD)) {
if (p->running) {
/* Re-run this PWM channel if inverted changed. */
if (p->inverted ^ inverted) {
p->inverted = inverted;
npcm7xx_pwm_update_duty(p);
}
} else {
/* Run this PWM channel. */
p->running = true;
p->inverted = inverted;
npcm7xx_pwm_update_output(p);
}
} else {
/* Clear this PWM channel. */
p->running = false;
p->inverted = inverted;
npcm7xx_pwm_update_output(p);
}
}
}
static hwaddr npcm7xx_cnr_index(hwaddr offset)
{
switch (offset) {
case A_NPCM7XX_PWM_CNR0:
return 0;
case A_NPCM7XX_PWM_CNR1:
return 1;
case A_NPCM7XX_PWM_CNR2:
return 2;
case A_NPCM7XX_PWM_CNR3:
return 3;
default:
g_assert_not_reached();
}
}
static hwaddr npcm7xx_cmr_index(hwaddr offset)
{
switch (offset) {
case A_NPCM7XX_PWM_CMR0:
return 0;
case A_NPCM7XX_PWM_CMR1:
return 1;
case A_NPCM7XX_PWM_CMR2:
return 2;
case A_NPCM7XX_PWM_CMR3:
return 3;
default:
g_assert_not_reached();
}
}
static hwaddr npcm7xx_pdr_index(hwaddr offset)
{
switch (offset) {
case A_NPCM7XX_PWM_PDR0:
return 0;
case A_NPCM7XX_PWM_PDR1:
return 1;
case A_NPCM7XX_PWM_PDR2:
return 2;
case A_NPCM7XX_PWM_PDR3:
return 3;
default:
g_assert_not_reached();
}
}
static hwaddr npcm7xx_pwdr_index(hwaddr offset)
{
switch (offset) {
case A_NPCM7XX_PWM_PWDR0:
return 0;
case A_NPCM7XX_PWM_PWDR1:
return 1;
case A_NPCM7XX_PWM_PWDR2:
return 2;
case A_NPCM7XX_PWM_PWDR3:
return 3;
default:
g_assert_not_reached();
}
}
static uint64_t npcm7xx_pwm_read(void *opaque, hwaddr offset, unsigned size)
{
NPCM7xxPWMState *s = opaque;
uint64_t value = 0;
switch (offset) {
case A_NPCM7XX_PWM_CNR0:
case A_NPCM7XX_PWM_CNR1:
case A_NPCM7XX_PWM_CNR2:
case A_NPCM7XX_PWM_CNR3:
value = s->pwm[npcm7xx_cnr_index(offset)].cnr;
break;
case A_NPCM7XX_PWM_CMR0:
case A_NPCM7XX_PWM_CMR1:
case A_NPCM7XX_PWM_CMR2:
case A_NPCM7XX_PWM_CMR3:
value = s->pwm[npcm7xx_cmr_index(offset)].cmr;
break;
case A_NPCM7XX_PWM_PDR0:
case A_NPCM7XX_PWM_PDR1:
case A_NPCM7XX_PWM_PDR2:
case A_NPCM7XX_PWM_PDR3:
value = s->pwm[npcm7xx_pdr_index(offset)].pdr;
break;
case A_NPCM7XX_PWM_PWDR0:
case A_NPCM7XX_PWM_PWDR1:
case A_NPCM7XX_PWM_PWDR2:
case A_NPCM7XX_PWM_PWDR3:
value = s->pwm[npcm7xx_pwdr_index(offset)].pwdr;
break;
case A_NPCM7XX_PWM_PPR:
value = s->ppr;
break;
case A_NPCM7XX_PWM_CSR:
value = s->csr;
break;
case A_NPCM7XX_PWM_PCR:
value = s->pcr;
break;
case A_NPCM7XX_PWM_PIER:
value = s->pier;
break;
case A_NPCM7XX_PWM_PIIR:
value = s->piir;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid offset 0x%04" HWADDR_PRIx "\n",
__func__, offset);
break;
}
trace_npcm7xx_pwm_read(DEVICE(s)->canonical_path, offset, value);
return value;
}
static void npcm7xx_pwm_write(void *opaque, hwaddr offset,
uint64_t v, unsigned size)
{
NPCM7xxPWMState *s = opaque;
NPCM7xxPWM *p;
uint32_t value = v;
trace_npcm7xx_pwm_write(DEVICE(s)->canonical_path, offset, value);
switch (offset) {
case A_NPCM7XX_PWM_CNR0:
case A_NPCM7XX_PWM_CNR1:
case A_NPCM7XX_PWM_CNR2:
case A_NPCM7XX_PWM_CNR3:
p = &s->pwm[npcm7xx_cnr_index(offset)];
if (value > NPCM7XX_MAX_CNR) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid cnr value: %u", __func__, value);
p->cnr = NPCM7XX_MAX_CNR;
} else {
p->cnr = value;
}
npcm7xx_pwm_update_output(p);
break;
case A_NPCM7XX_PWM_CMR0:
case A_NPCM7XX_PWM_CMR1:
case A_NPCM7XX_PWM_CMR2:
case A_NPCM7XX_PWM_CMR3:
p = &s->pwm[npcm7xx_cmr_index(offset)];
if (value > NPCM7XX_MAX_CMR) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid cmr value: %u", __func__, value);
p->cmr = NPCM7XX_MAX_CMR;
} else {
p->cmr = value;
}
npcm7xx_pwm_update_output(p);
break;
case A_NPCM7XX_PWM_PDR0:
case A_NPCM7XX_PWM_PDR1:
case A_NPCM7XX_PWM_PDR2:
case A_NPCM7XX_PWM_PDR3:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
__func__, offset);
break;
case A_NPCM7XX_PWM_PWDR0:
case A_NPCM7XX_PWM_PWDR1:
case A_NPCM7XX_PWM_PWDR2:
case A_NPCM7XX_PWM_PWDR3:
qemu_log_mask(LOG_UNIMP,
"%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
__func__, offset);
break;
case A_NPCM7XX_PWM_PPR:
npcm7xx_pwm_write_ppr(s, value);
break;
case A_NPCM7XX_PWM_CSR:
npcm7xx_pwm_write_csr(s, value);
break;
case A_NPCM7XX_PWM_PCR:
npcm7xx_pwm_write_pcr(s, value);
break;
case A_NPCM7XX_PWM_PIER:
qemu_log_mask(LOG_UNIMP,
"%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
__func__, offset);
break;
case A_NPCM7XX_PWM_PIIR:
qemu_log_mask(LOG_UNIMP,
"%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
__func__, offset);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid offset 0x%04" HWADDR_PRIx "\n",
__func__, offset);
break;
}
}
static const struct MemoryRegionOps npcm7xx_pwm_ops = {
.read = npcm7xx_pwm_read,
.write = npcm7xx_pwm_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
.unaligned = false,
},
};
static void npcm7xx_pwm_enter_reset(Object *obj, ResetType type)
{
NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
int i;
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
NPCM7xxPWM *p = &s->pwm[i];
p->cnr = 0x00000000;
p->cmr = 0x00000000;
p->pdr = 0x00000000;
p->pwdr = 0x00000000;
}
s->ppr = 0x00000000;
s->csr = 0x00000000;
s->pcr = 0x00000000;
s->pier = 0x00000000;
s->piir = 0x00000000;
}
static void npcm7xx_pwm_hold_reset(Object *obj)
{
NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
int i;
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
qemu_irq_lower(s->pwm[i].irq);
}
}
static void npcm7xx_pwm_init(Object *obj)
{
NPCM7xxPWMState *s = NPCM7XX_PWM(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
int i;
QEMU_BUILD_BUG_ON(ARRAY_SIZE(s->pwm) != NPCM7XX_PWM_PER_MODULE);
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; i++) {
NPCM7xxPWM *p = &s->pwm[i];
p->module = s;
p->index = i;
sysbus_init_irq(sbd, &p->irq);
}
memory_region_init_io(&s->iomem, obj, &npcm7xx_pwm_ops, s,
TYPE_NPCM7XX_PWM, 4 * KiB);
sysbus_init_mmio(sbd, &s->iomem);
s->clock = qdev_init_clock_in(DEVICE(s), "clock", NULL, NULL, 0);
for (i = 0; i < NPCM7XX_PWM_PER_MODULE; ++i) {
object_property_add_uint32_ptr(obj, "freq[*]",
&s->pwm[i].freq, OBJ_PROP_FLAG_READ);
object_property_add_uint32_ptr(obj, "duty[*]",
&s->pwm[i].duty, OBJ_PROP_FLAG_READ);
}
qdev_init_gpio_out_named(DEVICE(s), s->duty_gpio_out,
"duty-gpio-out", NPCM7XX_PWM_PER_MODULE);
}
static const VMStateDescription vmstate_npcm7xx_pwm = {
.name = "npcm7xx-pwm",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_BOOL(running, NPCM7xxPWM),
VMSTATE_BOOL(inverted, NPCM7xxPWM),
VMSTATE_UINT8(index, NPCM7xxPWM),
VMSTATE_UINT32(cnr, NPCM7xxPWM),
VMSTATE_UINT32(cmr, NPCM7xxPWM),
VMSTATE_UINT32(pdr, NPCM7xxPWM),
VMSTATE_UINT32(pwdr, NPCM7xxPWM),
VMSTATE_UINT32(freq, NPCM7xxPWM),
VMSTATE_UINT32(duty, NPCM7xxPWM),
VMSTATE_END_OF_LIST(),
},
};
static const VMStateDescription vmstate_npcm7xx_pwm_module = {
.name = "npcm7xx-pwm-module",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_CLOCK(clock, NPCM7xxPWMState),
VMSTATE_STRUCT_ARRAY(pwm, NPCM7xxPWMState,
NPCM7XX_PWM_PER_MODULE, 0, vmstate_npcm7xx_pwm,
NPCM7xxPWM),
VMSTATE_UINT32(ppr, NPCM7xxPWMState),
VMSTATE_UINT32(csr, NPCM7xxPWMState),
VMSTATE_UINT32(pcr, NPCM7xxPWMState),
VMSTATE_UINT32(pier, NPCM7xxPWMState),
VMSTATE_UINT32(piir, NPCM7xxPWMState),
VMSTATE_END_OF_LIST(),
},
};
static void npcm7xx_pwm_class_init(ObjectClass *klass, void *data)
{
ResettableClass *rc = RESETTABLE_CLASS(klass);
DeviceClass *dc = DEVICE_CLASS(klass);
dc->desc = "NPCM7xx PWM Controller";
dc->vmsd = &vmstate_npcm7xx_pwm_module;
rc->phases.enter = npcm7xx_pwm_enter_reset;
rc->phases.hold = npcm7xx_pwm_hold_reset;
}
static const TypeInfo npcm7xx_pwm_info = {
.name = TYPE_NPCM7XX_PWM,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(NPCM7xxPWMState),
.class_init = npcm7xx_pwm_class_init,
.instance_init = npcm7xx_pwm_init,
};
static void npcm7xx_pwm_register_type(void)
{
type_register_static(&npcm7xx_pwm_info);
}
type_init(npcm7xx_pwm_register_type);