new file mode 100644
@@ -0,0 +1,409 @@
+/*
+ * Nuvoton NPCM8xx PCS Module
+ *
+ * Copyright 2022 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.
+ */
+
+/*
+ * Disclaimer:
+ * Currently we only implemented the default values of the registers and
+ * the soft reset feature. These are required to boot up the GMAC module
+ * in Linux kernel for NPCM845 boards. Other functionalities are not modeled.
+ */
+
+#include "qemu/osdep.h"
+
+#include "exec/hwaddr.h"
+#include "hw/registerfields.h"
+#include "hw/net/npcm_pcs.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/units.h"
+#include "trace.h"
+
+#define NPCM_PCS_IND_AC_BA 0x1fe
+#define NPCM_PCS_IND_SR_CTL 0x1e00
+#define NPCM_PCS_IND_SR_MII 0x1f00
+#define NPCM_PCS_IND_SR_TIM 0x1f07
+#define NPCM_PCS_IND_VR_MII 0x1f80
+
+REG16(NPCM_PCS_SR_CTL_ID1, 0x08)
+REG16(NPCM_PCS_SR_CTL_ID2, 0x0a)
+REG16(NPCM_PCS_SR_CTL_STS, 0x10)
+
+REG16(NPCM_PCS_SR_MII_CTRL, 0x00)
+REG16(NPCM_PCS_SR_MII_STS, 0x02)
+REG16(NPCM_PCS_SR_MII_DEV_ID1, 0x04)
+REG16(NPCM_PCS_SR_MII_DEV_ID2, 0x06)
+REG16(NPCM_PCS_SR_MII_AN_ADV, 0x08)
+REG16(NPCM_PCS_SR_MII_LP_BABL, 0x0a)
+REG16(NPCM_PCS_SR_MII_AN_EXPN, 0x0c)
+REG16(NPCM_PCS_SR_MII_EXT_STS, 0x1e)
+
+REG16(NPCM_PCS_SR_TIM_SYNC_ABL, 0x10)
+REG16(NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_LWR, 0x12)
+REG16(NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_UPR, 0x14)
+REG16(NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_LWR, 0x16)
+REG16(NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_UPR, 0x18)
+REG16(NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_LWR, 0x1a)
+REG16(NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_UPR, 0x1c)
+REG16(NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_LWR, 0x1e)
+REG16(NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_UPR, 0x20)
+
+REG16(NPCM_PCS_VR_MII_MMD_DIG_CTRL1, 0x000)
+REG16(NPCM_PCS_VR_MII_AN_CTRL, 0x002)
+REG16(NPCM_PCS_VR_MII_AN_INTR_STS, 0x004)
+REG16(NPCM_PCS_VR_MII_TC, 0x006)
+REG16(NPCM_PCS_VR_MII_DBG_CTRL, 0x00a)
+REG16(NPCM_PCS_VR_MII_EEE_MCTRL0, 0x00c)
+REG16(NPCM_PCS_VR_MII_EEE_TXTIMER, 0x010)
+REG16(NPCM_PCS_VR_MII_EEE_RXTIMER, 0x012)
+REG16(NPCM_PCS_VR_MII_LINK_TIMER_CTRL, 0x014)
+REG16(NPCM_PCS_VR_MII_EEE_MCTRL1, 0x016)
+REG16(NPCM_PCS_VR_MII_DIG_STS, 0x020)
+REG16(NPCM_PCS_VR_MII_ICG_ERRCNT1, 0x022)
+REG16(NPCM_PCS_VR_MII_MISC_STS, 0x030)
+REG16(NPCM_PCS_VR_MII_RX_LSTS, 0x040)
+REG16(NPCM_PCS_VR_MII_MP_TX_BSTCTRL0, 0x070)
+REG16(NPCM_PCS_VR_MII_MP_TX_LVLCTRL0, 0x074)
+REG16(NPCM_PCS_VR_MII_MP_TX_GENCTRL0, 0x07a)
+REG16(NPCM_PCS_VR_MII_MP_TX_GENCTRL1, 0x07c)
+REG16(NPCM_PCS_VR_MII_MP_TX_STS, 0x090)
+REG16(NPCM_PCS_VR_MII_MP_RX_GENCTRL0, 0x0b0)
+REG16(NPCM_PCS_VR_MII_MP_RX_GENCTRL1, 0x0b2)
+REG16(NPCM_PCS_VR_MII_MP_RX_LOS_CTRL0, 0x0ba)
+REG16(NPCM_PCS_VR_MII_MP_MPLL_CTRL0, 0x0f0)
+REG16(NPCM_PCS_VR_MII_MP_MPLL_CTRL1, 0x0f2)
+REG16(NPCM_PCS_VR_MII_MP_MPLL_STS, 0x110)
+REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL2, 0x126)
+REG16(NPCM_PCS_VR_MII_MP_LVL_CTRL, 0x130)
+REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL0, 0x132)
+REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL1, 0x134)
+REG16(NPCM_PCS_VR_MII_DIG_CTRL2, 0x1c2)
+REG16(NPCM_PCS_VR_MII_DIG_ERRCNT_SEL, 0x1c4)
+
+/* Register Fields */
+#define NPCM_PCS_SR_MII_CTRL_RST BIT(15)
+
+static const uint16_t npcm_pcs_sr_ctl_cold_reset_values[NPCM_PCS_NR_SR_CTLS] = {
+ [R_NPCM_PCS_SR_CTL_ID1] = 0x699e,
+ [R_NPCM_PCS_SR_CTL_STS] = 0x8000,
+};
+
+static const uint16_t npcm_pcs_sr_mii_cold_reset_values[NPCM_PCS_NR_SR_MIIS] = {
+ [R_NPCM_PCS_SR_MII_CTRL] = 0x1140,
+ [R_NPCM_PCS_SR_MII_STS] = 0x0109,
+ [R_NPCM_PCS_SR_MII_DEV_ID1] = 0x699e,
+ [R_NPCM_PCS_SR_MII_DEV_ID2] = 0xced0,
+ [R_NPCM_PCS_SR_MII_AN_ADV] = 0x0020,
+ [R_NPCM_PCS_SR_MII_EXT_STS] = 0xc000,
+};
+
+static const uint16_t npcm_pcs_sr_tim_cold_reset_values[NPCM_PCS_NR_SR_TIMS] = {
+ [R_NPCM_PCS_SR_TIM_SYNC_ABL] = 0x0003,
+ [R_NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_LWR] = 0x0038,
+ [R_NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_LWR] = 0x0038,
+ [R_NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_LWR] = 0x0058,
+ [R_NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_LWR] = 0x0048,
+};
+
+static const uint16_t npcm_pcs_vr_mii_cold_reset_values[NPCM_PCS_NR_VR_MIIS] = {
+ [R_NPCM_PCS_VR_MII_MMD_DIG_CTRL1] = 0x2400,
+ [R_NPCM_PCS_VR_MII_AN_INTR_STS] = 0x000a,
+ [R_NPCM_PCS_VR_MII_EEE_MCTRL0] = 0x899c,
+ [R_NPCM_PCS_VR_MII_DIG_STS] = 0x0010,
+ [R_NPCM_PCS_VR_MII_MP_TX_BSTCTRL0] = 0x000a,
+ [R_NPCM_PCS_VR_MII_MP_TX_LVLCTRL0] = 0x007f,
+ [R_NPCM_PCS_VR_MII_MP_TX_GENCTRL0] = 0x0001,
+ [R_NPCM_PCS_VR_MII_MP_RX_GENCTRL0] = 0x0100,
+ [R_NPCM_PCS_VR_MII_MP_RX_GENCTRL1] = 0x1100,
+ [R_NPCM_PCS_VR_MII_MP_RX_LOS_CTRL0] = 0x000e,
+ [R_NPCM_PCS_VR_MII_MP_MPLL_CTRL0] = 0x0100,
+ [R_NPCM_PCS_VR_MII_MP_MPLL_CTRL1] = 0x0032,
+ [R_NPCM_PCS_VR_MII_MP_MPLL_STS] = 0x0001,
+ [R_NPCM_PCS_VR_MII_MP_LVL_CTRL] = 0x0019,
+};
+
+static void npcm_pcs_soft_reset(NPCMPCSState *s)
+{
+ memcpy(s->sr_ctl, npcm_pcs_sr_ctl_cold_reset_values,
+ NPCM_PCS_NR_SR_CTLS * sizeof(uint16_t));
+ memcpy(s->sr_mii, npcm_pcs_sr_mii_cold_reset_values,
+ NPCM_PCS_NR_SR_MIIS * sizeof(uint16_t));
+ memcpy(s->sr_tim, npcm_pcs_sr_tim_cold_reset_values,
+ NPCM_PCS_NR_SR_TIMS * sizeof(uint16_t));
+ memcpy(s->vr_mii, npcm_pcs_vr_mii_cold_reset_values,
+ NPCM_PCS_NR_VR_MIIS * sizeof(uint16_t));
+}
+
+static uint16_t npcm_pcs_read_sr_ctl(NPCMPCSState *s, hwaddr offset)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_SR_CTLS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SR_CTL read offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return 0;
+ }
+
+ return s->sr_ctl[regno];
+}
+
+static uint16_t npcm_pcs_read_sr_mii(NPCMPCSState *s, hwaddr offset)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_SR_MIIS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SR_MII read offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return 0;
+ }
+
+ return s->sr_mii[regno];
+}
+
+static uint16_t npcm_pcs_read_sr_tim(NPCMPCSState *s, hwaddr offset)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_SR_TIMS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SR_TIM read offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return 0;
+ }
+
+ return s->sr_tim[regno];
+}
+
+static uint16_t npcm_pcs_read_vr_mii(NPCMPCSState *s, hwaddr offset)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_VR_MIIS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: VR_MII read offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return 0;
+ }
+
+ return s->vr_mii[regno];
+}
+
+static void npcm_pcs_write_sr_ctl(NPCMPCSState *s, hwaddr offset, uint16_t v)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_SR_CTLS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SR_CTL write offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return;
+ }
+
+ s->sr_ctl[regno] = v;
+}
+
+static void npcm_pcs_write_sr_mii(NPCMPCSState *s, hwaddr offset, uint16_t v)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_SR_MIIS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SR_MII write offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return;
+ }
+
+ s->sr_mii[regno] = v;
+
+ if ((offset == A_NPCM_PCS_SR_MII_CTRL) && (v & NPCM_PCS_SR_MII_CTRL_RST)) {
+ /* Trigger a soft reset */
+ npcm_pcs_soft_reset(s);
+ }
+}
+
+static void npcm_pcs_write_sr_tim(NPCMPCSState *s, hwaddr offset, uint16_t v)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_SR_TIMS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: SR_TIM write offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return;
+ }
+
+ s->sr_tim[regno] = v;
+}
+
+static void npcm_pcs_write_vr_mii(NPCMPCSState *s, hwaddr offset, uint16_t v)
+{
+ hwaddr regno = offset / sizeof(uint16_t);
+
+ if (regno >= NPCM_PCS_NR_VR_MIIS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: VR_MII write offset 0x%04" HWADDR_PRIx
+ " is out of range.",
+ DEVICE(s)->canonical_path, offset);
+ return;
+ }
+
+ s->vr_mii[regno] = v;
+}
+
+static uint64_t npcm_pcs_read(void *opaque, hwaddr offset, unsigned size)
+{
+ NPCMPCSState *s = opaque;
+ uint16_t v = 0;
+
+ if (offset == NPCM_PCS_IND_AC_BA) {
+ v = s->indirect_access_base;
+ } else {
+ switch (s->indirect_access_base) {
+ case NPCM_PCS_IND_SR_CTL:
+ v = npcm_pcs_read_sr_ctl(s, offset);
+ break;
+
+ case NPCM_PCS_IND_SR_MII:
+ v = npcm_pcs_read_sr_mii(s, offset);
+ break;
+
+ case NPCM_PCS_IND_SR_TIM:
+ v = npcm_pcs_read_sr_tim(s, offset);
+ break;
+
+ case NPCM_PCS_IND_VR_MII:
+ v = npcm_pcs_read_vr_mii(s, offset);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Read with invalid indirect address base: 0x%02"
+ PRIx16 "\n", DEVICE(s)->canonical_path,
+ s->indirect_access_base);
+ }
+ }
+
+ trace_npcm_pcs_reg_read(DEVICE(s)->canonical_path, s->indirect_access_base,
+ offset, v);
+ return v;
+}
+
+static void npcm_pcs_write(void *opaque, hwaddr offset,
+ uint64_t v, unsigned size)
+{
+ NPCMPCSState *s = opaque;
+
+ trace_npcm_pcs_reg_write(DEVICE(s)->canonical_path, s->indirect_access_base,
+ offset, v);
+ if (offset == NPCM_PCS_IND_AC_BA) {
+ s->indirect_access_base = v;
+ } else {
+ switch (s->indirect_access_base) {
+ case NPCM_PCS_IND_SR_CTL:
+ npcm_pcs_write_sr_ctl(s, offset, v);
+ break;
+
+ case NPCM_PCS_IND_SR_MII:
+ npcm_pcs_write_sr_mii(s, offset, v);
+ break;
+
+ case NPCM_PCS_IND_SR_TIM:
+ npcm_pcs_write_sr_tim(s, offset, v);
+ break;
+
+ case NPCM_PCS_IND_VR_MII:
+ npcm_pcs_write_vr_mii(s, offset, v);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Write with invalid indirect address base: 0x%02"
+ PRIx16 "\n", DEVICE(s)->canonical_path,
+ s->indirect_access_base);
+ }
+ }
+}
+
+static void npcm_pcs_reset(DeviceState *dev)
+{
+ NPCMPCSState *s = NPCM_PCS(dev);
+
+ npcm_pcs_soft_reset(s);
+}
+
+static const struct MemoryRegionOps npcm_pcs_ops = {
+ .read = npcm_pcs_read,
+ .write = npcm_pcs_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 2,
+ .max_access_size = 2,
+ .unaligned = false,
+ },
+};
+
+static void npcm_pcs_realize(DeviceState *dev, Error **errp)
+{
+ NPCMPCSState *pcs = NPCM_PCS(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&pcs->iomem, OBJECT(pcs), &npcm_pcs_ops, pcs,
+ TYPE_NPCM_PCS, 8 * KiB);
+ sysbus_init_mmio(sbd, &pcs->iomem);
+}
+
+static const VMStateDescription vmstate_npcm_pcs = {
+ .name = TYPE_NPCM_PCS,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(indirect_access_base, NPCMPCSState),
+ VMSTATE_UINT16_ARRAY(sr_ctl, NPCMPCSState, NPCM_PCS_NR_SR_CTLS),
+ VMSTATE_UINT16_ARRAY(sr_mii, NPCMPCSState, NPCM_PCS_NR_SR_MIIS),
+ VMSTATE_UINT16_ARRAY(sr_tim, NPCMPCSState, NPCM_PCS_NR_SR_TIMS),
+ VMSTATE_UINT16_ARRAY(vr_mii, NPCMPCSState, NPCM_PCS_NR_VR_MIIS),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void npcm_pcs_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->desc = "NPCM PCS Controller";
+ dc->realize = npcm_pcs_realize;
+ dc->reset = npcm_pcs_reset;
+ dc->vmsd = &vmstate_npcm_pcs;
+}
+
+static const TypeInfo npcm_pcs_types[] = {
+ {
+ .name = TYPE_NPCM_PCS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NPCMPCSState),
+ .class_init = npcm_pcs_class_init,
+ },
+};
+DEFINE_TYPES(npcm_pcs_types)
new file mode 100644
@@ -0,0 +1,42 @@
+/*
+ * Nuvoton NPCM8xx PCS Module
+ *
+ * Copyright 2022 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 NPCM_PCS_H
+#define NPCM_PCS_H
+
+#include "hw/sysbus.h"
+
+#define NPCM_PCS_NR_SR_CTLS (0x12 / sizeof(uint16_t))
+#define NPCM_PCS_NR_SR_MIIS (0x20 / sizeof(uint16_t))
+#define NPCM_PCS_NR_SR_TIMS (0x22 / sizeof(uint16_t))
+#define NPCM_PCS_NR_VR_MIIS (0x1c6 / sizeof(uint16_t))
+
+typedef struct NPCMPCSState {
+ SysBusDevice parent;
+
+ MemoryRegion iomem;
+
+ uint16_t indirect_access_base;
+ uint16_t sr_ctl[NPCM_PCS_NR_SR_CTLS];
+ uint16_t sr_mii[NPCM_PCS_NR_SR_MIIS];
+ uint16_t sr_tim[NPCM_PCS_NR_SR_TIMS];
+ uint16_t vr_mii[NPCM_PCS_NR_VR_MIIS];
+} NPCMPCSState;
+
+#define TYPE_NPCM_PCS "npcm-pcs"
+OBJECT_DECLARE_SIMPLE_TYPE(NPCMPCSState, NPCM_PCS)
+
+#endif /* NPCM_PCS_H */