You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qemu/hw/net/npcm_pcs.c

411 lines
13 KiB
C

/*
* 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)