/* * 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.\n", 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.\n", 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.\n", 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.\n", 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.\n", 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.\n", 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.\n", 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.\n", 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%" 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_enter_reset(Object *obj, ResetType type) { NPCMPCSState *s = NPCM_PCS(obj); 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) { ResettableClass *rc = RESETTABLE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); set_bit(DEVICE_CATEGORY_MISC, dc->categories); dc->desc = "NPCM PCS Controller"; dc->realize = npcm_pcs_realize; dc->vmsd = &vmstate_npcm_pcs; rc->phases.enter = npcm_pcs_enter_reset; } 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)