qemu/hw/misc/npcm7xx_pwm.c
Hao Wu 1e943c586a hw/misc: Add a PWM module for NPCM7XX
The PWM module is part of NPCM7XX module. Each NPCM7XX module has two
identical PWM modules. Each module contains 4 PWM entries. Each PWM has
two outputs: frequency and duty_cycle. Both are computed using inputs
from software side.

This module does not model detail pulse signals since it is expensive.
It also does not model interrupts and watchdogs that are dependant on
the detail models. The interfaces for these are left in the module so
that anyone in need for these functionalities can implement on their
own.

The user can read the duty cycle and frequency using qom-get command.

Reviewed-by: Havard Skinnemoen <hskinnemoen@google.com>
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Signed-off-by: Hao Wu <wuhaotsh@google.com>
Message-id: 20210108190945.949196-5-wuhaotsh@google.com
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2021-01-12 21:19:02 +00:00

551 lines
15 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)
/* 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)
{
uint64_t duty;
if (p->running) {
if (p->cnr == 0) {
duty = 0;
} else if (p->cmr >= p->cnr) {
duty = NPCM7XX_PWM_MAX_DUTY;
} else {
duty = 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;
}
}
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)];
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)];
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;
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);
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);
}
}
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);