diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c490505..b66c3013 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,17 @@
## Added
+- Devices
+ - Support the hongyavision HYWS-SGT0204S (PCB marking `2G5F4_10G2_V1.01`), an
+ RTL8372 based 4x2.5G + 2xSFP+ unmanaged switch with a 512 KB Bright Moon
+ `T25S40` flash. It is a sibling of the KP-9000-6XH-X2 (2M-PCB43-V1.1) with
+ the two SFP+ cages wired the other way round (physical 5 is on SDS1,
+ physical 6 on SDS0), no reset button fitted, wrong silkscreen labels on the
+ serial header, `LED_GLB_IO_EN` bit 6 has to be set for the port LEDs to
+ work, and the fitted T25S40 cannot be read in DIO mode on this board, so the
+ target selects single-IO flash reads (`flash_sio`). New machine target
+ `MACHINE_2G5F4_10G2_V1_01`, see `doc/devices/2G5F4_10G2_V1.01.md`.
+
- Web UI
- Compress the embedded assets (minify + gzip) and switch to a single-page layout. #315
- Replace the multi-page UI with a themed single-page app: light, dark and Selenized themes following the browser by default,
diff --git a/doc/devices/2G5F4_10G2_V1.01.md b/doc/devices/2G5F4_10G2_V1.01.md
new file mode 100644
index 00000000..fc3803a2
--- /dev/null
+++ b/doc/devices/2G5F4_10G2_V1.01.md
@@ -0,0 +1,178 @@
+hongyavision HYWS-SGT0204S
+
+RTL8372-based 4×2.5G + 2×SFP+ unmanaged switch.
+
+Also marked on the PCB as `2G5F4_10G2_V1.01`.
+
+Stock firmware runs a factory test console on UART at 9600 baud 8N1 (banner
+`RTL8371B start`, prompt `RTL8371B:`). There is no management CLI and no Web
+interface.
+
+Using SPI clamp in-board is the only method for initial installation and update.
+Erase the whole chip and verify the write (`flashrom -v`) before powering the
+board, so an incomplete write is caught before the board is powered up.
+
+Stock flash chip is 512 KB (Bright Moon `T25S40`, 4 Mbit, SOP-8), which the
+RTLPlayground image fills exactly.
+
+### Label specifications
+
+Case, top / front / nameplate:
+
+
+
+
+
+- **Manufacturer**: hongyavision (Shenzhen Hongyavision Electronic Technology
+ Co., Ltd.)
+- **Model**: HYWS-SGT0204S — the nameplate is printed in Chinese and reads
+ "10/100/1000/2500M fibre switch" and "4x 2.5G copper port + 2x 10G SFP+
+ optical port"
+- **Ports**:
+ - 4 × RJ45: 10/100/1000/2500 Mbps
+ - 2 × SFP+: 1G / 2.5G / 10G
+- **Power**: 12 V DC, 2 A
+- **Serial number**: `S/N-YYYYMMDDNNNN`, e.g. `S/N-202312010028`
+
+The top of the case only carries generic printing ("2.5G+10G High Speed Fiber
+Transceiver / 4-Port 2.5G Ethernet Switch+10G Fiber"), so the nameplate on the
+bottom is what identifies the device.
+
+### What works
+
+- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
+- Both SFP+ ports supporting 1G, 2.5G and 10G modules
+- Copper LEDs: white (2.5G) and blue (link/activity)
+- SFP LEDs: one single LED per port (link/activity)
+- SFP EEPROM and DDM readable on both cages, verified with a 10G module
+ (`Rate: 0x67`, module identity and DDM values via the `sfp` console command)
+- Serial console at 115200 8N1 (RTLPlayground) / 9600 8N1 (stock)
+
+### Not verified
+
+- The reset button footprint is unpopulated and the case has no opening for it,
+ so it could not be tested.
+
+### PCB overview
+
+**Board markings**
+- Top silkscreen: `2G5F4_10G2_V1.01`
+
+**Key components**
+- Main switch: RTL8372 (RTL8371B family), reported as `RTL8372` (non-N) by
+ RTLPlayground. The chip sits under a heatsink and its package marking is not
+ readable.
+- U1: SPI NOR flash, Bright Moon `T25S40`, 4 Mbit / 512 KB, SOP-8
+- A 3-hole reset button footprint is present but unpopulated, and the case has
+ no opening for it, so there is no usable reset button
+
+Top side:
+
+
+
+Bottom:
+
+
+
+The stock flash chip, Bright Moon `T25S40` (SOP-8, 4 Mbit / 512 KB):
+
+
+
+### Port layout
+
+| Front panel position | Logical port | Physical port | Type |
+|----------------------|--------------|---------------|---------|
+| SFP left | 8 | 5 | SFP+ |
+| RJ45 1 | 4 | 1 | Copper |
+| RJ45 2 | 5 | 2 | Copper |
+| RJ45 3 | 6 | 3 | Copper |
+| RJ45 4 | 7 | 4 | Copper |
+| SFP right | 3 | 6 | SFP+ |
+
+Copper ports 1–4 match the KP-9000-6XH-X2 (2M-PCB43-V1.1) layout, which this
+board is a sibling of. The two SFP+ cages are wired the other way round
+compared to that board: physical port 5 is on SDS1 and physical port 6 is on
+SDS0.
+
+### Serial console
+
+The board has three through-holes used as the serial console, next to the
+heatsink in the middle of the board and visible in the top side photo above (the
+silkscreen labels are printed rotated by 90°). **The silkscreen labels are
+wrong**: they are shifted relative to the actual signals.
+
+| Silkscreen label | Actual signal |
+|------------------|---------------|
+| TX | GND |
+| RX | TX |
+| GND | RX |
+
+Wiring (following the measured signals, not the labels):
+
+| USB-TTL adapter | Board pad |
+|-----------------|----------------------|
+| GND | pad labelled `TX` |
+| RX | pad labelled `RX` |
+| TX | pad labelled `GND` |
+
+- **Settings**: 115200 baud / 8N1 / 3.3V TTL (RTLPlayground), 9600 baud for the
+ stock firmware
+- The stock firmware only prints a few bytes and a factory test menu
+ (`?`, `rst`, `f`, `r`, `10`, `100`, `g1`, `sd`, `eye`, …); it is not an
+ interactive management console
+
+### LED configuration
+
+Case silkscreen, below the RJ45 ports:
+
+```
+White : 2.5G Blue : 1000M/2500M L/A
+```
+
+The front panel also carries a `PWR` LED and one LED per SFP cage (`SFP1`,
+`SFP2`) on the right hand side.
+
+| Port | LED | Linked, idle | With traffic |
+|------------|--------------|--------------|--------------|
+| Copper | white | 2.5G only | same |
+| Copper | blue | solid | blinks (L/A) |
+| SFP | single LED | solid | blinks (L/A) |
+
+Measured LED-ID mapping (from the RTL837x LED set registers):
+
+| LED-ID | Function |
+|--------|---------------------------------|
+| LED-ID0| copper blue LED |
+| LED-ID1| copper white LED |
+| LED-ID2| SFP single LED |
+
+**Important**: `RTL837X_REG_LED_GLB_IO_EN` bit 6 must be set in
+`machine_custom_init()`. Without it the RTL837x "solid" conditions (LINK/speed)
+do not drive the LEDs at all and only `ACT` does, which makes the white LED
+glow faintly at non-2.5G speeds.
+
+### SFP GPIO assignments
+
+| SFP | pin\_detect (ModAbs) | pin\_los | SerDes | I2C SDA | I2C SCL |
+|-------------------|-------------------------------|------------------|--------|-------------------------|--------------------------|
+| Left (logical 8, physical 5) | GPIO30\_ACL\_BIT3\_EN | GPIO37 | SDS1 | GPIO39\_I2C\_SDA4 | GPIO40\_I2C\_SCL3\_MDC1 |
+| Right (logical 3, physical 6)| GPIO50\_I2C\_SCL2\_UART1\_TX | GPIO10\_LED10 | SDS0 | GPIO41\_I2C\_SDA3\_MDIO1 | GPIO40\_I2C\_SCL3\_MDC1 |
+
+`pin_tx_disable` is GPIO\_NA on both ports. Both I2C buses are confirmed
+working: with the same 10G module inserted, the `sfp` console command reads its
+vendor and model from the left cage (`Slot 1`) and from the right cage
+(`Slot 2`), DDM values included. The insertion also shows which SerDes drives
+each cage - the left cage reports `sds_config sds: 01` and the right cage
+`sds: 00`, which matches the table above.
+
+### Machine target
+
+Build for this device with `make MACHINE=2G5F4_10G2_V1_01`, which selects the
+`machine/2G5F4_10G2_V1_01.c` board definition. It is derived from
+`machine/KP_9000_6XHML_X2.c` with the SFP cage order and the reset pin changed.
+The fitted T25S40 cannot be read in DIO mode on this board: an image that leaves
+DIO enabled does not come up at all, both at the default read clock divider of 4
+and at 8 (two boards tested), while the same image with single-IO boots and runs
+normally. The board definition therefore sets `flash_sio`, which makes
+`flash_init()` use `CMD_FREAD` at divider 8. Only the management plane is
+slower, the ASIC keeps forwarding in hardware.
diff --git a/doc/devices/photos/2G5F4_10G2_V1.01/assembled-bottom.jpg b/doc/devices/photos/2G5F4_10G2_V1.01/assembled-bottom.jpg
new file mode 100644
index 00000000..8108ce66
Binary files /dev/null and b/doc/devices/photos/2G5F4_10G2_V1.01/assembled-bottom.jpg differ
diff --git a/doc/devices/photos/2G5F4_10G2_V1.01/assembled-front.jpg b/doc/devices/photos/2G5F4_10G2_V1.01/assembled-front.jpg
new file mode 100644
index 00000000..2cb1782b
Binary files /dev/null and b/doc/devices/photos/2G5F4_10G2_V1.01/assembled-front.jpg differ
diff --git a/doc/devices/photos/2G5F4_10G2_V1.01/assembled-top.jpg b/doc/devices/photos/2G5F4_10G2_V1.01/assembled-top.jpg
new file mode 100644
index 00000000..e3ea7733
Binary files /dev/null and b/doc/devices/photos/2G5F4_10G2_V1.01/assembled-top.jpg differ
diff --git a/doc/devices/photos/2G5F4_10G2_V1.01/flash_t25s40.jpg b/doc/devices/photos/2G5F4_10G2_V1.01/flash_t25s40.jpg
new file mode 100644
index 00000000..a1af18ac
Binary files /dev/null and b/doc/devices/photos/2G5F4_10G2_V1.01/flash_t25s40.jpg differ
diff --git a/doc/devices/photos/2G5F4_10G2_V1.01/pcb_bottom.jpg b/doc/devices/photos/2G5F4_10G2_V1.01/pcb_bottom.jpg
new file mode 100644
index 00000000..0c8cbabb
Binary files /dev/null and b/doc/devices/photos/2G5F4_10G2_V1.01/pcb_bottom.jpg differ
diff --git a/doc/devices/photos/2G5F4_10G2_V1.01/pcb_top.jpg b/doc/devices/photos/2G5F4_10G2_V1.01/pcb_top.jpg
new file mode 100644
index 00000000..421462a2
Binary files /dev/null and b/doc/devices/photos/2G5F4_10G2_V1.01/pcb_top.jpg differ
diff --git a/doc/supported_devices.md b/doc/supported_devices.md
index 9711d97e..a2331d94 100644
--- a/doc/supported_devices.md
+++ b/doc/supported_devices.md
@@ -15,6 +15,7 @@ The following devices have been tested and are fully working:
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 |
+| hongyavision | HYWS-SGT0204S | No | [2G5F4_10G2_V1.01](devices/2G5F4_10G2_V1.01.md) | 0.5M | 4 + 2 |
| hongyavision | LG-SG5T1 | No | [PCB-SWTG024AS-V2.0_16895](devices/SWTG024AS-V2.0.md) | 0.5M | 5 + 1 |
| Horaco | HC-SWTGW215AS | Yes | [SWTG024AS-A-V2.0.1_19650 5C 1SFP](devices/SWTG024AS-A-V2.0.1_5C_1SFP.md) | ? | 5 + 1 |
| Horaco | HC-SWTGW218AS | Yes | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 |
diff --git a/machine.h b/machine.h
index d0b85fc1..c0e10cd5 100644
--- a/machine.h
+++ b/machine.h
@@ -64,6 +64,12 @@ struct machine {
uint8_t led_mux_custom;
uint8_t led_mux[28];
uint32_t mac_flash_offset;
+ // Read the flash with single-IO (CMD_FREAD) instead of DIO for MMIO/code
+ // fetch. Set for boards whose flash chip is not usable in DIO mode, which
+ // shows up as a board that does not boot at all, garbled console output or
+ // random resets. Costs management-plane speed only, the ASIC keeps
+ // forwarding in hardware.
+ uint8_t flash_sio;
};
struct machine_runtime
diff --git a/machine/2G5F4_10G2_V1_01.c b/machine/2G5F4_10G2_V1_01.c
new file mode 100644
index 00000000..9a08e19f
--- /dev/null
+++ b/machine/2G5F4_10G2_V1_01.c
@@ -0,0 +1,54 @@
+#include "machine_defs.h"
+
+__code const struct machine machine = {
+ .machine_name = MACHINE_MODEL,
+ .isRTL8373 = 0,
+ .min_port = 3,
+ .max_port = 8,
+ .n_sfp = 2,
+ /* Same 2M-PCB43-V1.1 board family as the KP-9000-6XH-X2 (see
+ * machine/KP_9000_6XHML_X2.c), but the two SFP+ cages (physical ports 5 and
+ * 6) are wired the other way round: physical 5 is on SDS1, physical 6 on
+ * SDS0. Copper ports 1-4 match the KP-9000 layout. */
+ .log_to_phys_port = {0, 0, 0, 6, 1, 2, 3, 4, 5},
+ .phys_to_log_port = {4, 5, 6, 7, 8, 3, 0, 0, 0},
+ .is_sfp = {0, 0, 0, 2, 0, 0, 0, 0, 1},
+ // Left SFP cage (physical port 5) = SDS1
+ .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
+ .sfp_port[0].pin_los = GPIO37,
+ .sfp_port[0].pin_tx_disable = GPIO_NA,
+ .sfp_port[0].sds = 1,
+ .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
+ // Right SFP cage (physical port 6) = SDS0
+ .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX,
+ .sfp_port[1].pin_los = GPIO10_LED10,
+ .sfp_port[1].pin_tx_disable = GPIO_NA,
+ .sfp_port[1].sds = 0,
+ .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
+ /* The board has a 3-hole reset button footprint, but it is unpopulated and
+ * the case has no opening for it, so reset_pin stays GPIO_NA. GPIO54, the
+ * reset pin on the KP-9000 layout, reads as permanently pressed here. */
+ .reset_pin = GPIO_NA,
+ /* The fitted T25S40 does not work in DIO mode on this board, so read the flash
+ * with single-IO; see doc/devices/2G5F4_10G2_V1.01.md. */
+ .flash_sio = 1,
+ .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
+ .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ /* Conditions for LED on:
+ * dual led orange: ledset_0 & ledset_2
+ * dual led green: ledset_2 & !ledset_0
+ * single right led green: ledset_0 & !ledset_1
+ *
+ * Measured on this board: LED-ID0 is the copper blue LED, LED-ID1 the
+ * copper white one (setting LED-ID0 to 0 killed blue while leaving white on
+ * at 2.5G only) and LED-ID2 carries the SFP single LED. The "solid"
+ * conditions (LINK/speed) barely drive the LEDs here and ACT is what
+ * actually lights them, which is why the white LED used to glow faintly at
+ * non-2.5G speeds; the fix for that is LED_GLB_IO_EN bit 6, set in
+ * machine_init.c. */
+ .led_sets = { { LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G,
+ LEDS_2G5 | LEDS_LINK | LEDS_10G,
+ LEDS_1G | LEDS_LINK,
+ 0 },
+ },
+};
diff --git a/machine/2G5F4_10G2_V1_01.h b/machine/2G5F4_10G2_V1_01.h
new file mode 100644
index 00000000..d372b58a
--- /dev/null
+++ b/machine/2G5F4_10G2_V1_01.h
@@ -0,0 +1,6 @@
+#ifndef _MACHINE_2G5F4_10G2_V1_01_H_
+#define _MACHINE_2G5F4_10G2_V1_01_H_
+
+#define MACHINE_MODEL "2G5F4_10G2_V1.01 / SGT0204S"
+
+#endif
diff --git a/machine_init.c b/machine_init.c
index 0e88002a..efb16e61 100644
--- a/machine_init.c
+++ b/machine_init.c
@@ -19,6 +19,7 @@
defined(MACHINE_HI_K0402WS) || \
defined(MACHINE_PCB_K0402WS_V2) || \
defined(MACHINE_PCB_K0402WS_V3) || \
+ defined(MACHINE_2G5F4_10G2_V1_01) || \
defined(MACHINE_FNS1200P)
void machine_custom_init(void) __banked
{
diff --git a/rtlplayground.c b/rtlplayground.c
index 06c02ae3..657dc77d 100644
--- a/rtlplayground.c
+++ b/rtlplayground.c
@@ -1684,7 +1684,7 @@ void main(void)
// Flash controller should be initialized before any code in other banks is being fetched
// See this issue: https://github.com/logicog/RTLPlayground/issues/70
print_string("\nInitializing Flash controller\n");
- flash_init(1);
+ flash_init(machine.flash_sio ? 0 : 1);
// Set default for SFP pins so we can start up a module already inserted
sfp_pins_last = 0x33; // signal LOS and no module inserted (for both slots, even if only 1 present)