TP-Link TL-SG2452P
The TL-SG2452P, also known as T1600G-52PS, is a 48+4 port Gigabit PoE switch. Only v4 is supported by OpenWrt; earlier versions use a Broadcom SoC.
Non-working features
- SFP ports: No PHY support
- PoE: Startup script required (see below)
- LEDs: Startup script required (see below)
- Fan control: Binary slow/full only; temperature-based control requires workaround below
Supported Versions
Do not upgrade to OpenWrt 25.12.x. Tested and confirmed: all Ethernet ports go dead after flashing (RTL839x DSA driver regression in kernel 6.12). Stay on 24.10.x.
Hardware Highlights
Installation
TFTP recovery
The bootloader (TP-Link BOOTUTIL) supports TFTP recovery triggered by grounding the CLK pin of the SPI flash chip. Important: Use OpenWrt 23.05.0 as the recovery image - its kernel 5.15 correctly handles the flash write-protection bits that BOOTUTIL sets after recovery (in contrast to 24.10.x!). A direct sysupgrade to 24.10.x works without extra steps. The 23.05.0 sysupgrade file (~6.1 MB) also still fits into the file size limitation of the bootloader which is about 6MB. Newer images are too big for this approach.
wget -O uImage.img \ https://downloads.openwrt.org/releases/23.05.0/targets/realtek/rtl839x/openwrt-23.05.0-realtek-rtl839x-tplink_sg2452p-v4-squashfs-sysupgrade.bin
Finding U6 and pin 15
The SPI flash chip U6 (Winbond W25Q256, wide SOIC-16) is located in the very centre of the PCB. Pin 1 is marked with a dot on the chip body.
[dot = pin 1]
pin 1 ┌──────────────┐ pin 16 VCC
pin 2 │ │ pin 15 CLK ← ground this pin to trigger recovery
pin 3 │ W25Q256 │ pin 14 IO3
pin 4 │ (SOIC-16) │ pin 13 NC
pin 5 │ │ pin 12 NC
pin 6 │ │ pin 11 NC
pin 7 │ │ pin 10 NC
pin 8 └──────────────┘ pin 9 IO2
GND
Pin 15 is the second pin from the top on the right side (opposite the dot). Ground it to any GND point (chassis, capacitor leg, or J32 pin 2).
Prepare TFTP Server
You need to prepare a TFTP server from which the switch can get the system image:
- TFTP server: IP 192.168.0.146, sysupgrade file named uImage.img
- Plug your PC into port 1 of the switch
- Set your PC's IP to 192.168.0.146/24
Blind method (no serial cable necessary)
- Start packet capture:
tcpdump -i <iface> host 192.168.0.30 - Power on the switch
- After 2–3 seconds, briefly ground U6 pin 15
- Release when TFTP requests from 192.168.0.30 appear in the capture
- Switch flashes OpenWrt and reboots to 192.168.1.1
Serial console method
Header J32 (4 through-holes near board edge, arrowhead = pin 1):
| Pin | Signal |
|---|---|
| 1 | VCC — do not connect |
| 2 | GND |
| 3 | TX (SoC → adapter RX) |
| 4 | RX (SoC ← adapter TX) |
38400 baud, 8N1. The bootloader drops to a TP-Link BOOTUTIL shell; there is no known way to exit to a standard U-Boot prompt.
To install using the serial console power on device, and stop boot by pressing any key.
Once the shell is active:
- Ground out the CLK (pin 15) of the ROM (U6)
- Select option “3. Start”
- Bootloader notes that “The kernel has been damaged!”
- Release CLK as soon as bootloader thinks image is corrupted.
- Bootloader enters automatic recovery -- details printed on console
- Switch flashes OpenWrt and reboots to 192.168.1.1
Workarounds
PoE
The PoE ICs are not initialised correctly at boot. Install i2c-tools and add
to /etc/rc.local:
i2cset -y 0 0x30 0x12 0xff for i in `seq 3 14`; do echo 1 > /sys/class/hwmon/hwmon$i/in0_enable echo 1 > /sys/class/hwmon/hwmon$i/in1_enable echo 1 > /sys/class/hwmon/hwmon$i/in2_enable echo 1 > /sys/class/hwmon/hwmon$i/in3_enable done
The inX_enable files are write-only — cat returns “Permission denied”, which is normal.
LEDs
Add to /etc/rc.local (note: path says rtl838x even on this RTL8393M — this is correct):
cd /sys/kernel/debug/rtl838x/led echo 0x0060f568 > led_glb_ctrl echo 0x00007dea > led_set_0_1 echo 0xffffffff > led_copr_pmask_ctrl_0 echo 0x000fffff > led_copr_pmask_ctrl_1 echo 0xffffffff > led_combo_ctrl_0 echo 0x000fffff > led_combo_ctrl_1
Fan control
The fan runs at full speed by default — the DTS has no thermal zone wired to the
fan cooling device. The control script polls all hwmon*/temp1_input sensors
(primarily the 12 TPS23861 PoE controllers) and is fully functional without the
SoC module below. The kernel module is optional: it adds the RTL8393M die
temperature to the set of monitored sensors (CONFIG_DEVMEM is not set in the
kernel, so a module is the only way to read SoC registers from userspace).
SoC temperature module (optional)
Build with the OpenWrt 24.10.2 SDK (realtek/rtl839x). Create rtl839x_thermal.c and Makefile:
obj-m := rtl839x_thermal.o
// SPDX-License-Identifier: GPL-2.0-only // RTL839x SoC thermal sensor — exposes RTL8393M internal sensor via hwmon // Register definitions from OpenWrt main branch realtek-thermal.c // Copyright (C) 2025 Bjorn Mork <bjorn@mork.no> #include <linux/bitfield.h> #include <linux/hwmon.h> #include <linux/init.h> #include <linux/io.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/platform_device.h> #define RTL839X_SWITCHCORE_BASE 0x1b000000UL #define RTL839X_SWITCHCORE_SIZE 0x400 #define RTL8390_THERMAL_METER0_CTRL0 0x274 // bit 0 = enable #define RTL8390_THERMAL_METER0_RESULT 0x280 // bit 8 = valid, bits[6:0] = raw C #define RTL8390_TM_ENABLE BIT(0) #define RTL8390_TEMP_VALID BIT(8) #define RTL8390_TEMP_OUT_MASK GENMASK(6, 0) static void __iomem *sw_base; static struct device *hwmon_dev; static struct platform_device *pdev; static int rtl839x_read_temp(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { u32 ctrl, result; int i; if (type != hwmon_temp || attr != hwmon_temp_input) return -EOPNOTSUPP; ctrl = readl(sw_base + RTL8390_THERMAL_METER0_CTRL0); if (!(ctrl & RTL8390_TM_ENABLE)) writel(ctrl | RTL8390_TM_ENABLE, sw_base + RTL8390_THERMAL_METER0_CTRL0); for (i = 0; i < 10; i++) { result = readl(sw_base + RTL8390_THERMAL_METER0_RESULT); if (result & RTL8390_TEMP_VALID) { *val = (long)FIELD_GET(RTL8390_TEMP_OUT_MASK, result) * 1000; return 0; } msleep(10); } return -ETIMEDOUT; } static umode_t rtl839x_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { return (type == hwmon_temp && attr == hwmon_temp_input) ? 0444 : 0; } static const struct hwmon_channel_info * const rtl839x_hwmon_info[] = { HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), NULL }; static const struct hwmon_ops rtl839x_hwmon_ops = { .is_visible = rtl839x_is_visible, .read = rtl839x_read_temp, }; static const struct hwmon_chip_info rtl839x_chip_info = { .ops = &rtl839x_hwmon_ops, .info = rtl839x_hwmon_info, }; static int __init rtl839x_thermal_init(void) { int ret; pdev = platform_device_register_simple("rtl839x_thermal", -1, NULL, 0); if (IS_ERR(pdev)) return PTR_ERR(pdev); sw_base = ioremap(RTL839X_SWITCHCORE_BASE, RTL839X_SWITCHCORE_SIZE); if (!sw_base) { ret = -ENOMEM; goto err_pdev; } hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "rtl839x", NULL, &rtl839x_chip_info, NULL); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto err_ioremap; } pr_info("rtl839x_thermal: SoC thermal sensor registered\n"); return 0; err_ioremap: iounmap(sw_base); sw_base = NULL; err_pdev: platform_device_unregister(pdev); return ret; } static void __exit rtl839x_thermal_exit(void) { if (!IS_ERR_OR_NULL(hwmon_dev)) hwmon_device_unregister(hwmon_dev); if (sw_base) iounmap(sw_base); if (!IS_ERR_OR_NULL(pdev)) platform_device_unregister(pdev); } module_init(rtl839x_thermal_init); module_exit(rtl839x_thermal_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("RTL839x SoC thermal sensor (hwmon)");
Build:
SDK=/path/to/openwrt-sdk-24.10.2-realtek-rtl839x KERNEL=$SDK/build_dir/target-mips_24kc_musl/linux-realtek_rtl839x/linux-6.6.93 export STAGING_DIR=$SDK/staging_dir make -C "$KERNEL" M=$(pwd) ARCH=mips \ CROSS_COMPILE=$SDK/staging_dir/toolchain-mips_24kc_gcc-13.3.0_musl/bin/mips-openwrt-linux-musl- \ modules
Install on the switch:
KVER=$(ssh root@192.168.1.1 uname -r) scp -O rtl839x_thermal.ko root@192.168.1.1:/lib/modules/$KVER/ ssh root@192.168.1.1 ' echo "rtl839x_thermal" > /etc/modules.d/rtl839x_thermal insmod /lib/modules/$(uname -r)/rtl839x_thermal.ko'
The module registers a hwmon device (name rtl839x) with temp1_input in
millidegrees Celsius. Typical SoC idle temperature: ~35 °C.
Fan control script and service
Save as /usr/bin/fancontrol.sh (chmod +x):
#!/bin/sh # Fan control for TL-SG2452P v4 # Scans all hwmon temp1_input files (12x TPS23861 PoE chips + RTL8393M SoC) # cooling_device1 cur_state: 0=slow, 1=full speed COOLING=/sys/class/thermal/cooling_device1/cur_state TEMP_HIGH=62000 # full speed above 62 C (TPS23861 rated max: 85 C) TEMP_LOW=52000 # slow below 52 C (10 C hysteresis) INTERVAL=15 # seconds echo 0 > "$COOLING"; cur_state=0 logger -t fancontrol "Started: fan SLOW" while true; do max_temp=0 for f in /sys/class/hwmon/hwmon*/temp1_input; do t=$(cat "$f" 2>/dev/null) || continue [ "$t" -gt "$max_temp" ] && max_temp=$t done if [ "$cur_state" -eq 0 ] && [ "$max_temp" -gt "$TEMP_HIGH" ]; then echo 1 > "$COOLING"; cur_state=1 logger -t fancontrol "FULL speed (${max_temp}mC)" elif [ "$cur_state" -eq 1 ] && [ "$max_temp" -lt "$TEMP_LOW" ]; then echo 0 > "$COOLING"; cur_state=0 logger -t fancontrol "SLOW speed (${max_temp}mC)" fi sleep $INTERVAL done
Save as /etc/init.d/fancontrol (chmod +x), then enable and start it:
#!/bin/sh /etc/rc.common USE_PROCD=1 START=99 STOP=10 start_service() { procd_open_instance procd_set_param command /usr/bin/fancontrol.sh procd_set_param respawn 3600 5 5 procd_close_instance }
At idle (fan slow), temperatures are ~35 °C SoC and 39–53 °C on the PoE chips.
The fan kicks to full speed only above 62 °C and slows back below 52 °C.
Speed changes are logged: logread | grep fancontrol
Hardware
Info
| Architecture | MIPS 34Kc (mips32r2, no hardware FPU) |
|---|---|
| Vendor | Realtek |
| Bootloader | TP-Link BOOTUTIL (crippled U-Boot) |
| System-On-Chip | RTL8393M |
| CPU speed | 500 MHz |
| Flash chip | Winbond W25Q256 SOIC-16 (U6, centre of PCB) |
| Flash size | 32 MiB |
| RAM | 256 MiB DDR3 |
| Wireless | None |
| Ethernet | 48× GbE (PoE), 4× SFP |
| USB | None |
| Serial | Yes — J32, 38400 baud 8N1 |
| JTAG | No |