| 1 | /* |
| 2 | * Nuvoton NPCM8xx PCS Module |
| 3 | * |
| 4 | * Copyright 2022 Google LLC |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify it |
| 7 | * under the terms of the GNU General Public License as published by the |
| 8 | * Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * for more details. |
| 15 | */ |
| 16 | |
| 17 | /* |
| 18 | * Disclaimer: |
| 19 | * Currently we only implemented the default values of the registers and |
| 20 | * the soft reset feature. These are required to boot up the GMAC module |
| 21 | * in Linux kernel for NPCM845 boards. Other functionalities are not modeled. |
| 22 | */ |
| 23 | |
| 24 | #include "qemu/osdep.h" |
| 25 | |
| 26 | #include "exec/hwaddr.h" |
| 27 | #include "hw/core/registerfields.h" |
| 28 | #include "hw/net/npcm_pcs.h" |
| 29 | #include "migration/vmstate.h" |
| 30 | #include "qemu/log.h" |
| 31 | #include "qemu/units.h" |
| 32 | #include "trace.h" |
| 33 | |
| 34 | #define NPCM_PCS_IND_AC_BA 0x1fe |
| 35 | #define NPCM_PCS_IND_SR_CTL 0x1e00 |
| 36 | #define NPCM_PCS_IND_SR_MII 0x1f00 |
| 37 | #define NPCM_PCS_IND_SR_TIM 0x1f07 |
| 38 | #define NPCM_PCS_IND_VR_MII 0x1f80 |
| 39 | |
| 40 | REG16(NPCM_PCS_SR_CTL_ID1, 0x08) |
| 41 | REG16(NPCM_PCS_SR_CTL_ID2, 0x0a) |
| 42 | REG16(NPCM_PCS_SR_CTL_STS, 0x10) |
| 43 | |
| 44 | REG16(NPCM_PCS_SR_MII_CTRL, 0x00) |
| 45 | REG16(NPCM_PCS_SR_MII_STS, 0x02) |
| 46 | REG16(NPCM_PCS_SR_MII_DEV_ID1, 0x04) |
| 47 | REG16(NPCM_PCS_SR_MII_DEV_ID2, 0x06) |
| 48 | REG16(NPCM_PCS_SR_MII_AN_ADV, 0x08) |
| 49 | REG16(NPCM_PCS_SR_MII_LP_BABL, 0x0a) |
| 50 | REG16(NPCM_PCS_SR_MII_AN_EXPN, 0x0c) |
| 51 | REG16(NPCM_PCS_SR_MII_EXT_STS, 0x1e) |
| 52 | |
| 53 | REG16(NPCM_PCS_SR_TIM_SYNC_ABL, 0x10) |
| 54 | REG16(NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_LWR, 0x12) |
| 55 | REG16(NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_UPR, 0x14) |
| 56 | REG16(NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_LWR, 0x16) |
| 57 | REG16(NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_UPR, 0x18) |
| 58 | REG16(NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_LWR, 0x1a) |
| 59 | REG16(NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_UPR, 0x1c) |
| 60 | REG16(NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_LWR, 0x1e) |
| 61 | REG16(NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_UPR, 0x20) |
| 62 | |
| 63 | REG16(NPCM_PCS_VR_MII_MMD_DIG_CTRL1, 0x000) |
| 64 | REG16(NPCM_PCS_VR_MII_AN_CTRL, 0x002) |
| 65 | REG16(NPCM_PCS_VR_MII_AN_INTR_STS, 0x004) |
| 66 | REG16(NPCM_PCS_VR_MII_TC, 0x006) |
| 67 | REG16(NPCM_PCS_VR_MII_DBG_CTRL, 0x00a) |
| 68 | REG16(NPCM_PCS_VR_MII_EEE_MCTRL0, 0x00c) |
| 69 | REG16(NPCM_PCS_VR_MII_EEE_TXTIMER, 0x010) |
| 70 | REG16(NPCM_PCS_VR_MII_EEE_RXTIMER, 0x012) |
| 71 | REG16(NPCM_PCS_VR_MII_LINK_TIMER_CTRL, 0x014) |
| 72 | REG16(NPCM_PCS_VR_MII_EEE_MCTRL1, 0x016) |
| 73 | REG16(NPCM_PCS_VR_MII_DIG_STS, 0x020) |
| 74 | REG16(NPCM_PCS_VR_MII_ICG_ERRCNT1, 0x022) |
| 75 | REG16(NPCM_PCS_VR_MII_MISC_STS, 0x030) |
| 76 | REG16(NPCM_PCS_VR_MII_RX_LSTS, 0x040) |
| 77 | REG16(NPCM_PCS_VR_MII_MP_TX_BSTCTRL0, 0x070) |
| 78 | REG16(NPCM_PCS_VR_MII_MP_TX_LVLCTRL0, 0x074) |
| 79 | REG16(NPCM_PCS_VR_MII_MP_TX_GENCTRL0, 0x07a) |
| 80 | REG16(NPCM_PCS_VR_MII_MP_TX_GENCTRL1, 0x07c) |
| 81 | REG16(NPCM_PCS_VR_MII_MP_TX_STS, 0x090) |
| 82 | REG16(NPCM_PCS_VR_MII_MP_RX_GENCTRL0, 0x0b0) |
| 83 | REG16(NPCM_PCS_VR_MII_MP_RX_GENCTRL1, 0x0b2) |
| 84 | REG16(NPCM_PCS_VR_MII_MP_RX_LOS_CTRL0, 0x0ba) |
| 85 | REG16(NPCM_PCS_VR_MII_MP_MPLL_CTRL0, 0x0f0) |
| 86 | REG16(NPCM_PCS_VR_MII_MP_MPLL_CTRL1, 0x0f2) |
| 87 | REG16(NPCM_PCS_VR_MII_MP_MPLL_STS, 0x110) |
| 88 | REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL2, 0x126) |
| 89 | REG16(NPCM_PCS_VR_MII_MP_LVL_CTRL, 0x130) |
| 90 | REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL0, 0x132) |
| 91 | REG16(NPCM_PCS_VR_MII_MP_MISC_CTRL1, 0x134) |
| 92 | REG16(NPCM_PCS_VR_MII_DIG_CTRL2, 0x1c2) |
| 93 | REG16(NPCM_PCS_VR_MII_DIG_ERRCNT_SEL, 0x1c4) |
| 94 | |
| 95 | /* Register Fields */ |
| 96 | #define NPCM_PCS_SR_MII_CTRL_RST BIT(15) |
| 97 | |
| 98 | static const uint16_t npcm_pcs_sr_ctl_cold_reset_values[NPCM_PCS_NR_SR_CTLS] = { |
| 99 | [R_NPCM_PCS_SR_CTL_ID1] = 0x699e, |
| 100 | [R_NPCM_PCS_SR_CTL_STS] = 0x8000, |
| 101 | }; |
| 102 | |
| 103 | static const uint16_t npcm_pcs_sr_mii_cold_reset_values[NPCM_PCS_NR_SR_MIIS] = { |
| 104 | [R_NPCM_PCS_SR_MII_CTRL] = 0x1140, |
| 105 | [R_NPCM_PCS_SR_MII_STS] = 0x0109, |
| 106 | [R_NPCM_PCS_SR_MII_DEV_ID1] = 0x699e, |
| 107 | [R_NPCM_PCS_SR_MII_DEV_ID2] = 0xced0, |
| 108 | [R_NPCM_PCS_SR_MII_AN_ADV] = 0x0020, |
| 109 | [R_NPCM_PCS_SR_MII_EXT_STS] = 0xc000, |
| 110 | }; |
| 111 | |
| 112 | static const uint16_t npcm_pcs_sr_tim_cold_reset_values[NPCM_PCS_NR_SR_TIMS] = { |
| 113 | [R_NPCM_PCS_SR_TIM_SYNC_ABL] = 0x0003, |
| 114 | [R_NPCM_PCS_SR_TIM_SYNC_TX_MAX_DLY_LWR] = 0x0038, |
| 115 | [R_NPCM_PCS_SR_TIM_SYNC_TX_MIN_DLY_LWR] = 0x0038, |
| 116 | [R_NPCM_PCS_SR_TIM_SYNC_RX_MAX_DLY_LWR] = 0x0058, |
| 117 | [R_NPCM_PCS_SR_TIM_SYNC_RX_MIN_DLY_LWR] = 0x0048, |
| 118 | }; |
| 119 | |
| 120 | static const uint16_t npcm_pcs_vr_mii_cold_reset_values[NPCM_PCS_NR_VR_MIIS] = { |
| 121 | [R_NPCM_PCS_VR_MII_MMD_DIG_CTRL1] = 0x2400, |
| 122 | [R_NPCM_PCS_VR_MII_AN_INTR_STS] = 0x000a, |
| 123 | [R_NPCM_PCS_VR_MII_EEE_MCTRL0] = 0x899c, |
| 124 | [R_NPCM_PCS_VR_MII_DIG_STS] = 0x0010, |
| 125 | [R_NPCM_PCS_VR_MII_MP_TX_BSTCTRL0] = 0x000a, |
| 126 | [R_NPCM_PCS_VR_MII_MP_TX_LVLCTRL0] = 0x007f, |
| 127 | [R_NPCM_PCS_VR_MII_MP_TX_GENCTRL0] = 0x0001, |
| 128 | [R_NPCM_PCS_VR_MII_MP_RX_GENCTRL0] = 0x0100, |
| 129 | [R_NPCM_PCS_VR_MII_MP_RX_GENCTRL1] = 0x1100, |
| 130 | [R_NPCM_PCS_VR_MII_MP_RX_LOS_CTRL0] = 0x000e, |
| 131 | [R_NPCM_PCS_VR_MII_MP_MPLL_CTRL0] = 0x0100, |
| 132 | [R_NPCM_PCS_VR_MII_MP_MPLL_CTRL1] = 0x0032, |
| 133 | [R_NPCM_PCS_VR_MII_MP_MPLL_STS] = 0x0001, |
| 134 | [R_NPCM_PCS_VR_MII_MP_LVL_CTRL] = 0x0019, |
| 135 | }; |
| 136 | |
| 137 | static void npcm_pcs_soft_reset(NPCMPCSState *s) |
| 138 | { |
| 139 | memcpy(s->sr_ctl, npcm_pcs_sr_ctl_cold_reset_values, |
| 140 | NPCM_PCS_NR_SR_CTLS * sizeof(uint16_t)); |
| 141 | memcpy(s->sr_mii, npcm_pcs_sr_mii_cold_reset_values, |
| 142 | NPCM_PCS_NR_SR_MIIS * sizeof(uint16_t)); |
| 143 | memcpy(s->sr_tim, npcm_pcs_sr_tim_cold_reset_values, |
| 144 | NPCM_PCS_NR_SR_TIMS * sizeof(uint16_t)); |
| 145 | memcpy(s->vr_mii, npcm_pcs_vr_mii_cold_reset_values, |
| 146 | NPCM_PCS_NR_VR_MIIS * sizeof(uint16_t)); |
| 147 | } |
| 148 | |
| 149 | static uint16_t npcm_pcs_read_sr_ctl(NPCMPCSState *s, hwaddr offset) |
| 150 | { |
| 151 | hwaddr regno = offset / sizeof(uint16_t); |
| 152 | |
| 153 | if (regno >= NPCM_PCS_NR_SR_CTLS) { |
| 154 | qemu_log_mask(LOG_GUEST_ERROR, |
| 155 | "%s: SR_CTL read offset 0x%04" HWADDR_PRIx |
| 156 | " is out of range.\n", |
| 157 | DEVICE(s)->canonical_path, offset); |
| 158 | return 0; |
| 159 | } |
| 160 | |
| 161 | return s->sr_ctl[regno]; |
| 162 | } |
| 163 | |
| 164 | static uint16_t npcm_pcs_read_sr_mii(NPCMPCSState *s, hwaddr offset) |
| 165 | { |
| 166 | hwaddr regno = offset / sizeof(uint16_t); |
| 167 | |
| 168 | if (regno >= NPCM_PCS_NR_SR_MIIS) { |
| 169 | qemu_log_mask(LOG_GUEST_ERROR, |
| 170 | "%s: SR_MII read offset 0x%04" HWADDR_PRIx |
| 171 | " is out of range.\n", |
| 172 | DEVICE(s)->canonical_path, offset); |
| 173 | return 0; |
| 174 | } |
| 175 | |
| 176 | return s->sr_mii[regno]; |
| 177 | } |
| 178 | |
| 179 | static uint16_t npcm_pcs_read_sr_tim(NPCMPCSState *s, hwaddr offset) |
| 180 | { |
| 181 | hwaddr regno = offset / sizeof(uint16_t); |
| 182 | |
| 183 | if (regno >= NPCM_PCS_NR_SR_TIMS) { |
| 184 | qemu_log_mask(LOG_GUEST_ERROR, |
| 185 | "%s: SR_TIM read offset 0x%04" HWADDR_PRIx |
| 186 | " is out of range.\n", |
| 187 | DEVICE(s)->canonical_path, offset); |
| 188 | return 0; |
| 189 | } |
| 190 | |
| 191 | return s->sr_tim[regno]; |
| 192 | } |
| 193 | |
| 194 | static uint16_t npcm_pcs_read_vr_mii(NPCMPCSState *s, hwaddr offset) |
| 195 | { |
| 196 | hwaddr regno = offset / sizeof(uint16_t); |
| 197 | |
| 198 | if (regno >= NPCM_PCS_NR_VR_MIIS) { |
| 199 | qemu_log_mask(LOG_GUEST_ERROR, |
| 200 | "%s: VR_MII read offset 0x%04" HWADDR_PRIx |
| 201 | " is out of range.\n", |
| 202 | DEVICE(s)->canonical_path, offset); |
| 203 | return 0; |
| 204 | } |
| 205 | |
| 206 | return s->vr_mii[regno]; |
| 207 | } |
| 208 | |
| 209 | static void npcm_pcs_write_sr_ctl(NPCMPCSState *s, hwaddr offset, uint16_t v) |
| 210 | { |
| 211 | hwaddr regno = offset / sizeof(uint16_t); |
| 212 | |
| 213 | if (regno >= NPCM_PCS_NR_SR_CTLS) { |
| 214 | qemu_log_mask(LOG_GUEST_ERROR, |
| 215 | "%s: SR_CTL write offset 0x%04" HWADDR_PRIx |
| 216 | " is out of range.\n", |
| 217 | DEVICE(s)->canonical_path, offset); |
| 218 | return; |
| 219 | } |
| 220 | |
| 221 | s->sr_ctl[regno] = v; |
| 222 | } |
| 223 | |
| 224 | static void npcm_pcs_write_sr_mii(NPCMPCSState *s, hwaddr offset, uint16_t v) |
| 225 | { |
| 226 | hwaddr regno = offset / sizeof(uint16_t); |
| 227 | |
| 228 | if (regno >= NPCM_PCS_NR_SR_MIIS) { |
| 229 | qemu_log_mask(LOG_GUEST_ERROR, |
| 230 | "%s: SR_MII write offset 0x%04" HWADDR_PRIx |
| 231 | " is out of range.\n", |
| 232 | DEVICE(s)->canonical_path, offset); |
| 233 | return; |
| 234 | } |
| 235 | |
| 236 | s->sr_mii[regno] = v; |
| 237 | |
| 238 | if ((offset == A_NPCM_PCS_SR_MII_CTRL) && (v & NPCM_PCS_SR_MII_CTRL_RST)) { |
| 239 | /* Trigger a soft reset */ |
| 240 | npcm_pcs_soft_reset(s); |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | static void npcm_pcs_write_sr_tim(NPCMPCSState *s, hwaddr offset, uint16_t v) |
| 245 | { |
| 246 | hwaddr regno = offset / sizeof(uint16_t); |
| 247 | |
| 248 | if (regno >= NPCM_PCS_NR_SR_TIMS) { |
| 249 | qemu_log_mask(LOG_GUEST_ERROR, |
| 250 | "%s: SR_TIM write offset 0x%04" HWADDR_PRIx |
| 251 | " is out of range.\n", |
| 252 | DEVICE(s)->canonical_path, offset); |
| 253 | return; |
| 254 | } |
| 255 | |
| 256 | s->sr_tim[regno] = v; |
| 257 | } |
| 258 | |
| 259 | static void npcm_pcs_write_vr_mii(NPCMPCSState *s, hwaddr offset, uint16_t v) |
| 260 | { |
| 261 | hwaddr regno = offset / sizeof(uint16_t); |
| 262 | |
| 263 | if (regno >= NPCM_PCS_NR_VR_MIIS) { |
| 264 | qemu_log_mask(LOG_GUEST_ERROR, |
| 265 | "%s: VR_MII write offset 0x%04" HWADDR_PRIx |
| 266 | " is out of range.\n", |
| 267 | DEVICE(s)->canonical_path, offset); |
| 268 | return; |
| 269 | } |
| 270 | |
| 271 | s->vr_mii[regno] = v; |
| 272 | } |
| 273 | |
| 274 | static uint64_t npcm_pcs_read(void *opaque, hwaddr offset, unsigned size) |
| 275 | { |
| 276 | NPCMPCSState *s = opaque; |
| 277 | uint16_t v = 0; |
| 278 | |
| 279 | if (offset == NPCM_PCS_IND_AC_BA) { |
| 280 | v = s->indirect_access_base; |
| 281 | } else { |
| 282 | switch (s->indirect_access_base) { |
| 283 | case NPCM_PCS_IND_SR_CTL: |
| 284 | v = npcm_pcs_read_sr_ctl(s, offset); |
| 285 | break; |
| 286 | |
| 287 | case NPCM_PCS_IND_SR_MII: |
| 288 | v = npcm_pcs_read_sr_mii(s, offset); |
| 289 | break; |
| 290 | |
| 291 | case NPCM_PCS_IND_SR_TIM: |
| 292 | v = npcm_pcs_read_sr_tim(s, offset); |
| 293 | break; |
| 294 | |
| 295 | case NPCM_PCS_IND_VR_MII: |
| 296 | v = npcm_pcs_read_vr_mii(s, offset); |
| 297 | break; |
| 298 | |
| 299 | default: |
| 300 | qemu_log_mask(LOG_GUEST_ERROR, |
| 301 | "%s: Read with invalid indirect address base: 0x%" |
| 302 | PRIx16 "\n", DEVICE(s)->canonical_path, |
| 303 | s->indirect_access_base); |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | trace_npcm_pcs_reg_read(DEVICE(s)->canonical_path, s->indirect_access_base, |
| 308 | offset, v); |
| 309 | return v; |
| 310 | } |
| 311 | |
| 312 | static void npcm_pcs_write(void *opaque, hwaddr offset, |
| 313 | uint64_t v, unsigned size) |
| 314 | { |
| 315 | NPCMPCSState *s = opaque; |
| 316 | |
| 317 | trace_npcm_pcs_reg_write(DEVICE(s)->canonical_path, s->indirect_access_base, |
| 318 | offset, v); |
| 319 | if (offset == NPCM_PCS_IND_AC_BA) { |
| 320 | s->indirect_access_base = v; |
| 321 | } else { |
| 322 | switch (s->indirect_access_base) { |
| 323 | case NPCM_PCS_IND_SR_CTL: |
| 324 | npcm_pcs_write_sr_ctl(s, offset, v); |
| 325 | break; |
| 326 | |
| 327 | case NPCM_PCS_IND_SR_MII: |
| 328 | npcm_pcs_write_sr_mii(s, offset, v); |
| 329 | break; |
| 330 | |
| 331 | case NPCM_PCS_IND_SR_TIM: |
| 332 | npcm_pcs_write_sr_tim(s, offset, v); |
| 333 | break; |
| 334 | |
| 335 | case NPCM_PCS_IND_VR_MII: |
| 336 | npcm_pcs_write_vr_mii(s, offset, v); |
| 337 | break; |
| 338 | |
| 339 | default: |
| 340 | qemu_log_mask(LOG_GUEST_ERROR, |
| 341 | "%s: Write with invalid indirect address base: 0x%02" |
| 342 | PRIx16 "\n", DEVICE(s)->canonical_path, |
| 343 | s->indirect_access_base); |
| 344 | } |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | static void npcm_pcs_enter_reset(Object *obj, ResetType type) |
| 349 | { |
| 350 | NPCMPCSState *s = NPCM_PCS(obj); |
| 351 | |
| 352 | npcm_pcs_soft_reset(s); |
| 353 | } |
| 354 | |
| 355 | static const struct MemoryRegionOps npcm_pcs_ops = { |
| 356 | .read = npcm_pcs_read, |
| 357 | .write = npcm_pcs_write, |
| 358 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 359 | .valid = { |
| 360 | .min_access_size = 2, |
| 361 | .max_access_size = 2, |
| 362 | .unaligned = false, |
| 363 | }, |
| 364 | }; |
| 365 | |
| 366 | static void npcm_pcs_realize(DeviceState *dev, Error **errp) |
| 367 | { |
| 368 | NPCMPCSState *pcs = NPCM_PCS(dev); |
| 369 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
| 370 | |
| 371 | memory_region_init_io(&pcs->iomem, OBJECT(pcs), &npcm_pcs_ops, pcs, |
| 372 | TYPE_NPCM_PCS, 8 * KiB); |
| 373 | sysbus_init_mmio(sbd, &pcs->iomem); |
| 374 | } |
| 375 | |
| 376 | static const VMStateDescription vmstate_npcm_pcs = { |
| 377 | .name = TYPE_NPCM_PCS, |
| 378 | .version_id = 0, |
| 379 | .minimum_version_id = 0, |
| 380 | .fields = (VMStateField[]) { |
| 381 | VMSTATE_UINT16(indirect_access_base, NPCMPCSState), |
| 382 | VMSTATE_UINT16_ARRAY(sr_ctl, NPCMPCSState, NPCM_PCS_NR_SR_CTLS), |
| 383 | VMSTATE_UINT16_ARRAY(sr_mii, NPCMPCSState, NPCM_PCS_NR_SR_MIIS), |
| 384 | VMSTATE_UINT16_ARRAY(sr_tim, NPCMPCSState, NPCM_PCS_NR_SR_TIMS), |
| 385 | VMSTATE_UINT16_ARRAY(vr_mii, NPCMPCSState, NPCM_PCS_NR_VR_MIIS), |
| 386 | VMSTATE_END_OF_LIST(), |
| 387 | }, |
| 388 | }; |
| 389 | |
| 390 | static void npcm_pcs_class_init(ObjectClass *klass, const void *data) |
| 391 | { |
| 392 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 393 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 394 | |
| 395 | set_bit(DEVICE_CATEGORY_MISC, dc->categories); |
| 396 | dc->desc = "NPCM PCS Controller"; |
| 397 | dc->realize = npcm_pcs_realize; |
| 398 | dc->vmsd = &vmstate_npcm_pcs; |
| 399 | rc->phases.enter = npcm_pcs_enter_reset; |
| 400 | } |
| 401 | |
| 402 | static const TypeInfo npcm_pcs_types[] = { |
| 403 | { |
| 404 | .name = TYPE_NPCM_PCS, |
| 405 | .parent = TYPE_SYS_BUS_DEVICE, |
| 406 | .instance_size = sizeof(NPCMPCSState), |
| 407 | .class_init = npcm_pcs_class_init, |
| 408 | }, |
| 409 | }; |
| 410 | DEFINE_TYPES(npcm_pcs_types) |