mirror of https://gitlab.com/qemu-project/qemu
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.
411 lines
13 KiB
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)
|