@@ -42,6 +42,7 @@ Supported devices
* USB host (USBH)
* GPIO controller
* Analog to Digital Converter (ADC)
+ * Pulse Width Modulation (PWM)
Missing devices
---------------
@@ -61,7 +62,6 @@ Missing devices
* Peripheral SPI controller (PSPI)
* SD/MMC host
* PECI interface
- * Pulse Width Modulation (PWM)
* Tachometer
* PCI and PCIe root complex and bridges
* VDM and MCTP support
@@ -102,6 +102,8 @@ enum NPCM7xxInterrupt {
NPCM7XX_WDG2_IRQ, /* Timer Module 2 Watchdog */
NPCM7XX_EHCI_IRQ = 61,
NPCM7XX_OHCI_IRQ = 62,
+ NPCM7XX_PWM0_IRQ = 93, /* PWM module 0 */
+ NPCM7XX_PWM1_IRQ, /* PWM module 1 */
NPCM7XX_GPIO0_IRQ = 116,
NPCM7XX_GPIO1_IRQ,
NPCM7XX_GPIO2_IRQ,
@@ -144,6 +146,12 @@ static const hwaddr npcm7xx_fiu3_flash_addr[] = {
0xb8000000, /* CS3 */
};
+/* Register base address for each PWM Module */
+static const hwaddr npcm7xx_pwm_addr[] = {
+ 0xf0103000,
+ 0xf0104000,
+};
+
static const struct {
hwaddr regs_addr;
uint32_t unconnected_pins;
@@ -353,6 +361,10 @@ static void npcm7xx_init(Object *obj)
object_initialize_child(obj, npcm7xx_fiu[i].name, &s->fiu[i],
TYPE_NPCM7XX_FIU);
}
+
+ for (i = 0; i < ARRAY_SIZE(s->pwm); i++) {
+ object_initialize_child(obj, "pwm[*]", &s->pwm[i], TYPE_NPCM7XX_PWM);
+ }
}
static void npcm7xx_realize(DeviceState *dev, Error **errp)
@@ -513,6 +525,18 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
sysbus_connect_irq(SYS_BUS_DEVICE(&s->ohci), 0,
npcm7xx_irq(s, NPCM7XX_OHCI_IRQ));
+ /* PWM Modules. Cannot fail. */
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(npcm7xx_pwm_addr) != ARRAY_SIZE(s->pwm));
+ for (i = 0; i < ARRAY_SIZE(s->pwm); i++) {
+ SysBusDevice *sbd = SYS_BUS_DEVICE(&s->pwm[i]);
+
+ qdev_connect_clock_in(DEVICE(&s->pwm[i]), "clock", qdev_get_clock_out(
+ DEVICE(&s->clk), "apb3-clock"));
+ sysbus_realize(sbd, &error_abort);
+ sysbus_mmio_map(sbd, 0, npcm7xx_pwm_addr[i]);
+ sysbus_connect_irq(sbd, i, npcm7xx_irq(s, NPCM7XX_PWM0_IRQ + i));
+ }
+
/*
* Flash Interface Unit (FIU). Can fail if incorrect number of chip selects
* specified, but this is a programming error.
@@ -580,8 +604,6 @@ static void npcm7xx_realize(DeviceState *dev, Error **errp)
create_unimplemented_device("npcm7xx.peci", 0xf0100000, 4 * KiB);
create_unimplemented_device("npcm7xx.siox[1]", 0xf0101000, 4 * KiB);
create_unimplemented_device("npcm7xx.siox[2]", 0xf0102000, 4 * KiB);
- create_unimplemented_device("npcm7xx.pwm[0]", 0xf0103000, 4 * KiB);
- create_unimplemented_device("npcm7xx.pwm[1]", 0xf0104000, 4 * KiB);
create_unimplemented_device("npcm7xx.mft[0]", 0xf0180000, 4 * KiB);
create_unimplemented_device("npcm7xx.mft[1]", 0xf0181000, 4 * KiB);
create_unimplemented_device("npcm7xx.mft[2]", 0xf0182000, 4 * KiB);
@@ -64,6 +64,7 @@ softmmu_ss.add(when: 'CONFIG_MAINSTONE', if_true: files('mst_fpga.c'))
softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files(
'npcm7xx_clk.c',
'npcm7xx_gcr.c',
+ 'npcm7xx_pwm.c',
'npcm7xx_rng.c',
))
softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files(
new file mode 100644
@@ -0,0 +1,535 @@
+/*
+ * 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 "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"
+
+/* 32-bit register indices. */
+enum NPCM7xxPWMRegisters {
+ NPCM7XX_PWM_PPR,
+ NPCM7XX_PWM_CSR,
+ NPCM7XX_PWM_PCR,
+ NPCM7XX_PWM_CNR0,
+ NPCM7XX_PWM_CMR0,
+ NPCM7XX_PWM_PDR0,
+ NPCM7XX_PWM_CNR1,
+ NPCM7XX_PWM_CMR1,
+ NPCM7XX_PWM_PDR1,
+ NPCM7XX_PWM_CNR2,
+ NPCM7XX_PWM_CMR2,
+ NPCM7XX_PWM_PDR2,
+ NPCM7XX_PWM_CNR3,
+ NPCM7XX_PWM_CMR3,
+ NPCM7XX_PWM_PDR3,
+ NPCM7XX_PWM_PIER,
+ NPCM7XX_PWM_PIIR,
+ NPCM7XX_PWM_PWDR0,
+ NPCM7XX_PWM_PWDR1,
+ NPCM7XX_PWM_PWDR2,
+ NPCM7XX_PWM_PWDR3,
+ NPCM7XX_PWM_REGS_END,
+};
+
+/* 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_calculate_output(NPCM7xxPWM *p)
+{
+ p->freq = npcm7xx_pwm_calculate_freq(p);
+ p->duty = npcm7xx_pwm_calculate_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)) {
+ s->pwm[i].freq = npcm7xx_pwm_calculate_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)) {
+ s->pwm[i].freq = npcm7xx_pwm_calculate_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;
+ p->duty = npcm7xx_pwm_calculate_duty(p);
+ }
+ } else {
+ /* Run this PWM channel. */
+ p->running = true;
+ p->inverted = inverted;
+ npcm7xx_pwm_calculate_output(p);
+ }
+ } else {
+ /* Clear this PWM channel. */
+ p->running = false;
+ p->inverted = inverted;
+ npcm7xx_pwm_calculate_output(p);
+ }
+ }
+
+}
+
+static hwaddr npcm7xx_cnr_index(hwaddr reg)
+{
+ switch (reg) {
+ case NPCM7XX_PWM_CNR0:
+ return 0;
+ case NPCM7XX_PWM_CNR1:
+ return 1;
+ case NPCM7XX_PWM_CNR2:
+ return 2;
+ case NPCM7XX_PWM_CNR3:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static hwaddr npcm7xx_cmr_index(hwaddr reg)
+{
+ switch (reg) {
+ case NPCM7XX_PWM_CMR0:
+ return 0;
+ case NPCM7XX_PWM_CMR1:
+ return 1;
+ case NPCM7XX_PWM_CMR2:
+ return 2;
+ case NPCM7XX_PWM_CMR3:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static hwaddr npcm7xx_pdr_index(hwaddr reg)
+{
+ switch (reg) {
+ case NPCM7XX_PWM_PDR0:
+ return 0;
+ case NPCM7XX_PWM_PDR1:
+ return 1;
+ case NPCM7XX_PWM_PDR2:
+ return 2;
+ case NPCM7XX_PWM_PDR3:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static hwaddr npcm7xx_pwdr_index(hwaddr reg)
+{
+ switch (reg) {
+ case NPCM7XX_PWM_PWDR0:
+ return 0;
+ case NPCM7XX_PWM_PWDR1:
+ return 1;
+ case NPCM7XX_PWM_PWDR2:
+ return 2;
+ case 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;
+ hwaddr reg = offset / sizeof(uint32_t);
+ uint64_t value = 0;
+
+ switch (reg) {
+ case NPCM7XX_PWM_CNR0:
+ case NPCM7XX_PWM_CNR1:
+ case NPCM7XX_PWM_CNR2:
+ case NPCM7XX_PWM_CNR3:
+ value = s->pwm[npcm7xx_cnr_index(reg)].cnr;
+ break;
+
+ case NPCM7XX_PWM_CMR0:
+ case NPCM7XX_PWM_CMR1:
+ case NPCM7XX_PWM_CMR2:
+ case NPCM7XX_PWM_CMR3:
+ value = s->pwm[npcm7xx_cmr_index(reg)].cmr;
+ break;
+
+ case NPCM7XX_PWM_PDR0:
+ case NPCM7XX_PWM_PDR1:
+ case NPCM7XX_PWM_PDR2:
+ case NPCM7XX_PWM_PDR3:
+ value = s->pwm[npcm7xx_pdr_index(reg)].pdr;
+ break;
+
+ case NPCM7XX_PWM_PWDR0:
+ case NPCM7XX_PWM_PWDR1:
+ case NPCM7XX_PWM_PWDR2:
+ case NPCM7XX_PWM_PWDR3:
+ value = s->pwm[npcm7xx_pwdr_index(reg)].pwdr;
+ break;
+
+ case NPCM7XX_PWM_PPR:
+ value = s->ppr;
+ break;
+
+ case NPCM7XX_PWM_CSR:
+ value = s->csr;
+ break;
+
+ case NPCM7XX_PWM_PCR:
+ value = s->pcr;
+ break;
+
+ case NPCM7XX_PWM_PIER:
+ value = s->pier;
+ break;
+
+ case 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;
+ }
+
+ return value;
+}
+
+static void npcm7xx_pwm_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ NPCM7xxPWMState *s = opaque;
+ NPCM7xxPWM *p;
+ hwaddr reg = offset / sizeof(uint32_t);
+ uint32_t value = v;
+
+ switch (reg) {
+ case NPCM7XX_PWM_CNR0:
+ case NPCM7XX_PWM_CNR1:
+ case NPCM7XX_PWM_CNR2:
+ case NPCM7XX_PWM_CNR3:
+ p = &s->pwm[npcm7xx_cnr_index(reg)];
+ p->cnr = value;
+ npcm7xx_pwm_calculate_output(p);
+ break;
+
+ case NPCM7XX_PWM_CMR0:
+ case NPCM7XX_PWM_CMR1:
+ case NPCM7XX_PWM_CMR2:
+ case NPCM7XX_PWM_CMR3:
+ p = &s->pwm[npcm7xx_cmr_index(reg)];
+ p->cmr = value;
+ npcm7xx_pwm_calculate_output(p);
+ break;
+
+ case NPCM7XX_PWM_PDR0:
+ case NPCM7XX_PWM_PDR1:
+ case NPCM7XX_PWM_PDR2:
+ case NPCM7XX_PWM_PDR3:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
+ __func__, offset);
+ break;
+
+ case NPCM7XX_PWM_PWDR0:
+ case NPCM7XX_PWM_PWDR1:
+ case NPCM7XX_PWM_PWDR2:
+ case NPCM7XX_PWM_PWDR3:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+ __func__, offset);
+ break;
+
+ case NPCM7XX_PWM_PPR:
+ npcm7xx_pwm_write_ppr(s, value);
+ break;
+
+ case NPCM7XX_PWM_CSR:
+ npcm7xx_pwm_write_csr(s, value);
+ break;
+
+ case NPCM7XX_PWM_PCR:
+ npcm7xx_pwm_write_pcr(s, value);
+ break;
+
+ case NPCM7XX_PWM_PIER:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: register @ 0x%04" HWADDR_PRIx " is not implemented\n",
+ __func__, offset);
+ break;
+
+ case 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 = &s->parent;
+ 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);
+
+ QEMU_BUILD_BUG_ON(NPCM7XX_PWM_REGS_END > NPCM7XX_PWM_NR_REGS);
+
+ 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);
@@ -23,6 +23,7 @@
#include "hw/mem/npcm7xx_mc.h"
#include "hw/misc/npcm7xx_clk.h"
#include "hw/misc/npcm7xx_gcr.h"
+#include "hw/misc/npcm7xx_pwm.h"
#include "hw/misc/npcm7xx_rng.h"
#include "hw/nvram/npcm7xx_otp.h"
#include "hw/timer/npcm7xx_timer.h"
@@ -78,6 +79,7 @@ typedef struct NPCM7xxState {
NPCM7xxCLKState clk;
NPCM7xxTimerCtrlState tim[3];
NPCM7xxADCState adc;
+ NPCM7xxPWMState pwm[2];
NPCM7xxOTPState key_storage;
NPCM7xxOTPState fuse_array;
NPCM7xxMCState mc;
new file mode 100644
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+#ifndef NPCM7XX_PWM_H
+#define NPCM7XX_PWM_H
+
+#include "qemu/osdep.h"
+#include "hw/clock.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+
+/* Each PWM module holds 4 PWM channels. */
+#define NPCM7XX_PWM_PER_MODULE 4
+
+/*
+ * Number of registers in one pwm module. Don't change this without increasing
+ * the version_id in vmstate.
+ */
+#define NPCM7XX_PWM_NR_REGS (0x54 / sizeof(uint32_t))
+
+/*
+ * The maximum duty values. Each duty unit represents 1/NPCM7XX_PWM_MAX_DUTY
+ * cycles. For example, if NPCM7XX_PWM_MAX_DUTY=1,000,000 and a PWM has a duty
+ * value of 100,000 the duty cycle for that PWM is 10%.
+ */
+#define NPCM7XX_PWM_MAX_DUTY 1000000
+
+typedef struct NPCM7xxPWMState NPCM7xxPWMState;
+
+/**
+ * struct NPCM7xxPWM - The state of a single PWM channel.
+ * @module: The PWM module that contains this channel.
+ * @irq: GIC interrupt line to fire on expiration if enabled.
+ * @running: Whether this PWM channel is generating output.
+ * @inverted: Whether this PWM channel is inverted.
+ * @index: The index of this PWM channel.
+ * @cnr: The counter register.
+ * @cmr: The comparator register.
+ * @pdr: The data register.
+ * @pwdr: The watchdog register.
+ * @freq: The frequency of this PWM channel.
+ * @duty: The duty cycle of this PWM channel. One unit represents
+ * 1/NPCM7XX_MAX_DUTY cycles.
+ */
+typedef struct NPCM7xxPWM {
+ NPCM7xxPWMState *module;
+
+ qemu_irq irq;
+
+ bool running;
+ bool inverted;
+
+ uint8_t index;
+ uint32_t cnr;
+ uint32_t cmr;
+ uint32_t pdr;
+ uint32_t pwdr;
+
+ uint32_t freq;
+ uint32_t duty;
+} NPCM7xxPWM;
+
+/**
+ * struct NPCM7xxPWMState - Pulse Width Modulation device state.
+ * @parent: System bus device.
+ * @iomem: Memory region through which registers are accessed.
+ * @clock: The PWM clock.
+ * @pwm: The PWM channels owned by this module.
+ * @ppr: The prescaler register.
+ * @csr: The clock selector register.
+ * @pcr: The control register.
+ * @pier: The interrupt enable register.
+ * @piir: The interrupt indication register.
+ */
+struct NPCM7xxPWMState {
+ SysBusDevice parent;
+
+ MemoryRegion iomem;
+
+ Clock *clock;
+ NPCM7xxPWM pwm[NPCM7XX_PWM_PER_MODULE];
+
+ uint32_t ppr;
+ uint32_t csr;
+ uint32_t pcr;
+ uint32_t pier;
+ uint32_t piir;
+};
+
+#define TYPE_NPCM7XX_PWM "npcm7xx-pwm"
+#define NPCM7XX_PWM(obj) \
+ OBJECT_CHECK(NPCM7xxPWMState, (obj), TYPE_NPCM7XX_PWM)
+
+#endif /* NPCM7XX_PWM_H */
@@ -136,6 +136,7 @@ qtests_sparc64 = \
qtests_npcm7xx = \
['npcm7xx_adc-test',
'npcm7xx_gpio-test',
+ 'npcm7xx_pwm-test',
'npcm7xx_rng-test',
'npcm7xx_timer-test',
'npcm7xx_watchdog_timer-test']
new file mode 100644
@@ -0,0 +1,490 @@
+/*
+ * QTests for Nuvoton NPCM7xx PWM Modules.
+ *
+ * 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 "qemu/bitops.h"
+#include "libqos/libqtest.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qnum.h"
+
+#define REF_HZ 25000000
+
+/* Register field definitions. */
+#define CH_EN BIT(0)
+#define CH_INV BIT(2)
+#define CH_MOD BIT(3)
+
+/* Registers shared between all PWMs in a module */
+#define PPR 0x00
+#define CSR 0x04
+#define PCR 0x08
+#define PIER 0x3c
+#define PIIR 0x40
+
+/* CLK module related */
+#define CLK_BA 0xf0801000
+#define CLKSEL 0x04
+#define CLKDIV1 0x08
+#define CLKDIV2 0x2c
+#define PLLCON0 0x0c
+#define PLLCON1 0x10
+#define PLL_INDV(rv) extract32((rv), 0, 6)
+#define PLL_FBDV(rv) extract32((rv), 16, 12)
+#define PLL_OTDV1(rv) extract32((rv), 8, 3)
+#define PLL_OTDV2(rv) extract32((rv), 13, 3)
+#define APB3CKDIV(rv) extract32((rv), 28, 2)
+#define CLK2CKDIV(rv) extract32((rv), 0, 1)
+#define CLK4CKDIV(rv) extract32((rv), 26, 2)
+#define CPUCKSEL(rv) extract32((rv), 0, 2)
+
+#define MAX_DUTY 1000000
+
+typedef struct PWMModule {
+ int irq;
+ uint64_t base_addr;
+} PWMModule;
+
+typedef struct PWM {
+ uint32_t cnr_offset;
+ uint32_t cmr_offset;
+ uint32_t pdr_offset;
+ uint32_t pwdr_offset;
+} PWM;
+
+typedef struct TestData {
+ const PWMModule *module;
+ const PWM *pwm;
+} TestData;
+
+static const PWMModule pwm_module_list[] = {
+ {
+ .irq = 93,
+ .base_addr = 0xf0103000
+ },
+ {
+ .irq = 94,
+ .base_addr = 0xf0104000
+ }
+};
+
+static const PWM pwm_list[] = {
+ {
+ .cnr_offset = 0x0c,
+ .cmr_offset = 0x10,
+ .pdr_offset = 0x14,
+ .pwdr_offset = 0x44,
+ },
+ {
+ .cnr_offset = 0x18,
+ .cmr_offset = 0x1c,
+ .pdr_offset = 0x20,
+ .pwdr_offset = 0x48,
+ },
+ {
+ .cnr_offset = 0x24,
+ .cmr_offset = 0x28,
+ .pdr_offset = 0x2c,
+ .pwdr_offset = 0x4c,
+ },
+ {
+ .cnr_offset = 0x30,
+ .cmr_offset = 0x34,
+ .pdr_offset = 0x38,
+ .pwdr_offset = 0x50,
+ },
+};
+
+static const int ppr_base[] = { 0, 0, 8, 8 };
+static const int csr_base[] = { 0, 4, 8, 12 };
+static const int pcr_base[] = { 0, 8, 12, 16 };
+
+static const uint32_t ppr_list[] = {
+ 0,
+ 1,
+ 10,
+ 100,
+ 255, /* Max possible value. */
+};
+
+static const uint32_t csr_list[] = {
+ 0,
+ 1,
+ 2,
+ 3,
+ 4, /* Max possible value. */
+};
+
+static const uint32_t cnr_list[] = {
+ 0,
+ 1,
+ 50,
+ 100,
+ 150,
+ 200,
+ 1000,
+ 10000,
+ 65535, /* Max possible value. */
+};
+
+static const uint32_t cmr_list[] = {
+ 0,
+ 1,
+ 10,
+ 50,
+ 100,
+ 150,
+ 200,
+ 1000,
+ 10000,
+ 65535, /* Max possible value. */
+};
+
+/* Returns the index of the PWM module. */
+static int pwm_module_index(const PWMModule *module)
+{
+ ptrdiff_t diff = module - pwm_module_list;
+
+ g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_module_list));
+
+ return diff;
+}
+
+/* Returns the index of the PWM entry. */
+static int pwm_index(const PWM *pwm)
+{
+ ptrdiff_t diff = pwm - pwm_list;
+
+ g_assert_true(diff >= 0 && diff < ARRAY_SIZE(pwm_list));
+
+ return diff;
+}
+
+static uint64_t pwm_qom_get(QTestState *qts, const char *path, const char *name)
+{
+ QDict *response;
+
+ g_test_message("Getting properties %s from %s", name, path);
+ response = qtest_qmp(qts, "{ 'execute': 'qom-get',"
+ " 'arguments': { 'path': %s, 'property': %s}}",
+ path, name);
+ /* The qom set message returns successfully. */
+ g_assert_true(qdict_haskey(response, "return"));
+ return qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+}
+
+static uint64_t pwm_get_freq(QTestState *qts, int module_index, int pwm_index)
+{
+ char path[100];
+ char name[100];
+
+ sprintf(path, "/machine/soc/pwm[%d]", module_index);
+ sprintf(name, "freq[%d]", pwm_index);
+
+ return pwm_qom_get(qts, path, name);
+}
+
+static uint64_t pwm_get_duty(QTestState *qts, int module_index, int pwm_index)
+{
+ char path[100];
+ char name[100];
+
+ sprintf(path, "/machine/soc/pwm[%d]", module_index);
+ sprintf(name, "duty[%d]", pwm_index);
+
+ return pwm_qom_get(qts, path, name);
+}
+
+static uint32_t get_pll(uint32_t con)
+{
+ return REF_HZ * PLL_FBDV(con) / (PLL_INDV(con) * PLL_OTDV1(con)
+ * PLL_OTDV2(con));
+}
+
+static uint64_t read_pclk(QTestState *qts)
+{
+ uint64_t freq = REF_HZ;
+ uint32_t clksel = qtest_readl(qts, CLK_BA + CLKSEL);
+ uint32_t pllcon;
+ uint32_t clkdiv1 = qtest_readl(qts, CLK_BA + CLKDIV1);
+ uint32_t clkdiv2 = qtest_readl(qts, CLK_BA + CLKDIV2);
+
+ switch (CPUCKSEL(clksel)) {
+ case 0:
+ pllcon = qtest_readl(qts, CLK_BA + PLLCON0);
+ freq = get_pll(pllcon);
+ break;
+ case 1:
+ pllcon = qtest_readl(qts, CLK_BA + PLLCON1);
+ freq = get_pll(pllcon);
+ break;
+ case 2:
+ break;
+ case 3:
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ freq >>= (CLK2CKDIV(clkdiv1) + CLK4CKDIV(clkdiv1) + APB3CKDIV(clkdiv2));
+
+ return freq;
+}
+
+static uint32_t pwm_selector(uint32_t csr)
+{
+ switch (csr) {
+ case 0:
+ return 2;
+ case 1:
+ return 4;
+ case 2:
+ return 8;
+ case 3:
+ return 16;
+ case 4:
+ return 1;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static uint64_t pwm_compute_freq(QTestState *qts, uint32_t ppr, uint32_t csr,
+ uint32_t cnr)
+{
+ return read_pclk(qts) / ((ppr + 1) * pwm_selector(csr) * (cnr + 1));
+}
+
+static uint64_t pwm_compute_duty(uint32_t cnr, uint32_t cmr, bool inverted)
+{
+ uint64_t duty;
+
+ if (cnr == 0) {
+ /* PWM is stopped. */
+ duty = 0;
+ } else if (cmr >= cnr) {
+ duty = MAX_DUTY;
+ } else {
+ duty = MAX_DUTY * (cmr + 1) / (cnr + 1);
+ }
+
+ if (inverted) {
+ duty = MAX_DUTY - duty;
+ }
+
+ return duty;
+}
+
+static uint32_t pwm_read(QTestState *qts, const TestData *td, unsigned offset)
+{
+ return qtest_readl(qts, td->module->base_addr + offset);
+}
+
+static void pwm_write(QTestState *qts, const TestData *td, unsigned offset,
+ uint32_t value)
+{
+ qtest_writel(qts, td->module->base_addr + offset, value);
+}
+
+static uint32_t pwm_read_ppr(QTestState *qts, const TestData *td)
+{
+ return extract32(pwm_read(qts, td, PPR), ppr_base[pwm_index(td->pwm)], 8);
+}
+
+static void pwm_write_ppr(QTestState *qts, const TestData *td, uint32_t value)
+{
+ pwm_write(qts, td, PPR, value << ppr_base[pwm_index(td->pwm)]);
+}
+
+static uint32_t pwm_read_csr(QTestState *qts, const TestData *td)
+{
+ return extract32(pwm_read(qts, td, CSR), csr_base[pwm_index(td->pwm)], 3);
+}
+
+static void pwm_write_csr(QTestState *qts, const TestData *td, uint32_t value)
+{
+ pwm_write(qts, td, CSR, value << csr_base[pwm_index(td->pwm)]);
+}
+
+static uint32_t pwm_read_pcr(QTestState *qts, const TestData *td)
+{
+ return extract32(pwm_read(qts, td, PCR), pcr_base[pwm_index(td->pwm)], 4);
+}
+
+static void pwm_write_pcr(QTestState *qts, const TestData *td, uint32_t value)
+{
+ pwm_write(qts, td, PCR, value << pcr_base[pwm_index(td->pwm)]);
+}
+
+static uint32_t pwm_read_cnr(QTestState *qts, const TestData *td)
+{
+ return pwm_read(qts, td, td->pwm->cnr_offset);
+}
+
+static void pwm_write_cnr(QTestState *qts, const TestData *td, uint32_t value)
+{
+ pwm_write(qts, td, td->pwm->cnr_offset, value);
+}
+
+static uint32_t pwm_read_cmr(QTestState *qts, const TestData *td)
+{
+ return pwm_read(qts, td, td->pwm->cmr_offset);
+}
+
+static void pwm_write_cmr(QTestState *qts, const TestData *td, uint32_t value)
+{
+ pwm_write(qts, td, td->pwm->cmr_offset, value);
+}
+
+/* Check pwm registers can be reset to default value */
+static void test_init(gconstpointer test_data)
+{
+ const TestData *td = test_data;
+ QTestState *qts = qtest_init("-machine quanta-gsj");
+ int module = pwm_module_index(td->module);
+ int pwm = pwm_index(td->pwm);
+
+ g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0);
+ g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0);
+
+ qtest_quit(qts);
+}
+
+/* One-shot mode should not change frequency and duty cycle. */
+static void test_oneshot(gconstpointer test_data)
+{
+ const TestData *td = test_data;
+ QTestState *qts = qtest_init("-machine quanta-gsj");
+ int module = pwm_module_index(td->module);
+ int pwm = pwm_index(td->pwm);
+ uint32_t ppr, csr, pcr;
+ int i, j;
+
+ pcr = CH_EN;
+ for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) {
+ ppr = ppr_list[i];
+ pwm_write_ppr(qts, td, ppr);
+
+ for (j = 0; j < ARRAY_SIZE(csr_list); ++j) {
+ csr = csr_list[j];
+ pwm_write_csr(qts, td, csr);
+ pwm_write_pcr(qts, td, pcr);
+
+ g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr);
+ g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr);
+ g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr);
+ g_assert_cmpuint(pwm_get_freq(qts, module, pwm), ==, 0);
+ g_assert_cmpuint(pwm_get_duty(qts, module, pwm), ==, 0);
+ }
+ }
+
+ qtest_quit(qts);
+}
+
+/* In toggle mode, the PWM generates correct outputs. */
+static void test_toggle(gconstpointer test_data)
+{
+ const TestData *td = test_data;
+ QTestState *qts = qtest_init("-machine quanta-gsj");
+ int module = pwm_module_index(td->module);
+ int pwm = pwm_index(td->pwm);
+ uint32_t ppr, csr, pcr, cnr, cmr;
+ int i, j, k, l;
+ uint64_t expected_freq, expected_duty;
+
+ pcr = CH_EN | CH_MOD;
+ for (i = 0; i < ARRAY_SIZE(ppr_list); ++i) {
+ ppr = ppr_list[i];
+ pwm_write_ppr(qts, td, ppr);
+
+ for (j = 0; j < ARRAY_SIZE(csr_list); ++j) {
+ csr = csr_list[j];
+ pwm_write_csr(qts, td, csr);
+
+ for (k = 0; k < ARRAY_SIZE(cnr_list); ++k) {
+ cnr = cnr_list[k];
+ pwm_write_cnr(qts, td, cnr);
+
+ for (l = 0; l < ARRAY_SIZE(cmr_list); ++l) {
+ cmr = cmr_list[l];
+ pwm_write_cmr(qts, td, cmr);
+ expected_freq = pwm_compute_freq(qts, ppr, csr, cnr);
+ expected_duty = pwm_compute_duty(cnr, cmr, false);
+
+ pwm_write_pcr(qts, td, pcr);
+ g_assert_cmpuint(pwm_read_ppr(qts, td), ==, ppr);
+ g_assert_cmpuint(pwm_read_csr(qts, td), ==, csr);
+ g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr);
+ g_assert_cmpuint(pwm_read_cnr(qts, td), ==, cnr);
+ g_assert_cmpuint(pwm_read_cmr(qts, td), ==, cmr);
+ g_assert_cmpuint(pwm_get_duty(qts, module, pwm),
+ ==, expected_duty);
+ if (expected_duty != 0 && expected_duty != 100) {
+ /* Duty cycle with 0 or 100 doesn't need frequency. */
+ g_assert_cmpuint(pwm_get_freq(qts, module, pwm),
+ ==, expected_freq);
+ }
+
+ /* Test inverted mode */
+ expected_duty = pwm_compute_duty(cnr, cmr, true);
+ pwm_write_pcr(qts, td, pcr | CH_INV);
+ g_assert_cmpuint(pwm_read_pcr(qts, td), ==, pcr | CH_INV);
+ g_assert_cmpuint(pwm_get_duty(qts, module, pwm),
+ ==, expected_duty);
+ if (expected_duty != 0 && expected_duty != 100) {
+ /* Duty cycle with 0 or 100 doesn't need frequency. */
+ g_assert_cmpuint(pwm_get_freq(qts, module, pwm),
+ ==, expected_freq);
+ }
+
+ }
+ }
+ }
+ }
+
+ qtest_quit(qts);
+}
+
+static void pwm_add_test(const char *name, const TestData* td,
+ GTestDataFunc fn)
+{
+ g_autofree char *full_name = g_strdup_printf(
+ "npcm7xx_pwm/module[%d]/pwm[%d]/%s", pwm_module_index(td->module),
+ pwm_index(td->pwm), name);
+ qtest_add_data_func(full_name, td, fn);
+}
+#define add_test(name, td) pwm_add_test(#name, td, test_##name)
+
+int main(int argc, char **argv)
+{
+ TestData test_data_list[ARRAY_SIZE(pwm_module_list) * ARRAY_SIZE(pwm_list)];
+
+ g_test_init(&argc, &argv, NULL);
+
+ for (int i = 0; i < ARRAY_SIZE(pwm_module_list); ++i) {
+ for (int j = 0; j < ARRAY_SIZE(pwm_list); ++j) {
+ TestData *td = &test_data_list[i * ARRAY_SIZE(pwm_list) + j];
+
+ td->module = &pwm_module_list[i];
+ td->pwm = &pwm_list[j];
+
+ add_test(init, td);
+ add_test(oneshot, td);
+ add_test(toggle, td);
+ }
+ }
+
+ return g_test_run();
+}