Skip to content

Instantly share code, notes, and snippets.

@rmuxnet
Created March 19, 2026 11:55
Show Gist options
  • Select an option

  • Save rmuxnet/c2783c5a56a9955e08a5d572e466c017 to your computer and use it in GitHub Desktop.

Select an option

Save rmuxnet/c2783c5a56a9955e08a5d572e466c017 to your computer and use it in GitHub Desktop.
ps4 icc i2c
"""
ps4_icc_i2c.py
--------------
I2C bus access for PS4 Aeolia southbridge peripherals via /dev/i2c-0.
detect_devices() probe all addresses, return responding list
print_device_map() print i2cdetect-style map
i2c_read_byte_data(fd, addr, reg) read one byte
i2c_write_byte_data(fd, addr, reg, val) write one byte
i2c_read_block(fd, addr, reg, n) read n bytes
I2CBus context manager for /dev/i2c-0
Hardware: PS4 Fat (Aeolia CXD90025G)
Kernel: Linux 6.18.18-Strawberry-ThinLTO-LTS+ (CachyOS x86_64)
DEVICE PERMISSIONS
------------------
If /dev/i2c-0 does not exist or you get PermissionError:
sudo modprobe i2c-dev
sudo chmod 666 /dev/i2c-0
For /dev/icc (if also using ps4_icc_core.py):
cat /proc/devices | grep icc
sudo mknod /dev/icc c <major> 0
sudo chmod 666 /dev/icc
Permanent udev rules:
echo 'KERNEL=="i2c-0", MODE="0666"' | sudo tee /etc/udev/rules.d/99-ps4-i2c.rules
echo 'KERNEL=="icc", MODE="0666"' | sudo tee -a /etc/udev/rules.d/99-ps4-i2c.rules
sudo udevadm control --reload-rules
sudo udevadm trigger
DO NOT USE RAW ICC IOCTLS FOR I2C
-----------------------------------
Major 0x10 exposes the I2C bus, but the ICC kernel driver implements the I2C
framing internally and registers a standard Linux adapter at boot:
i2c i2c-0: adapter [icc] registered
i2c_dev: adapter [icc] registered as minor 0
All raw ICC format attempts to major 0x10 return zeros because the kernel
handles the protocol layer. Use /dev/i2c-0 via i2c-dev instead.
KNOWN I2C PERIPHERALS (not yet enumerated)
------------------------------------------
VRM likely near address 0x60
Power management IC address unknown
HDMI repeater address unknown
Bus enumeration via i2cdetect -y 0 is the highest-priority remaining
open investigation. Run it and document results.
Usage:
python ps4_icc_i2c.py detect
python ps4_icc_i2c.py read 60 9e
python ps4_icc_i2c.py write 60 9e 00
Or use i2c-tools directly:
i2cdetect -y 0
i2cget -y 0 0x60 0x9E
i2cset -y 0 0x60 0x9E 0x00
"""
import os
import fcntl
I2C_DEV = '/dev/i2c-0'
I2C_SLAVE = 0x0703
class I2CError(Exception):
pass
def i2c_read_byte_data(bus_fd, addr, reg):
try:
fcntl.ioctl(bus_fd, I2C_SLAVE, addr)
except OSError as e:
raise I2CError(f"Failed to set slave 0x{addr:02X}: {e}")
try:
os.write(bus_fd.fileno(), bytes([reg & 0xFF]))
return os.read(bus_fd.fileno(), 1)[0]
except OSError as e:
raise I2CError(f"Read failed addr=0x{addr:02X} reg=0x{reg:02X}: {e}")
def i2c_write_byte_data(bus_fd, addr, reg, value):
try:
fcntl.ioctl(bus_fd, I2C_SLAVE, addr)
except OSError as e:
raise I2CError(f"Failed to set slave 0x{addr:02X}: {e}")
try:
os.write(bus_fd.fileno(), bytes([reg & 0xFF, value & 0xFF]))
except OSError as e:
raise I2CError(f"Write failed addr=0x{addr:02X} reg=0x{reg:02X}: {e}")
def i2c_read_block(bus_fd, addr, reg, length):
fcntl.ioctl(bus_fd, I2C_SLAVE, addr)
os.write(bus_fd.fileno(), bytes([reg & 0xFF]))
return os.read(bus_fd.fileno(), length)
def detect_devices():
present = []
try:
with open(I2C_DEV, 'r+b', buffering=0) as fd:
for addr in range(0x03, 0x78):
try:
fcntl.ioctl(fd, I2C_SLAVE, addr)
os.write(fd.fileno(), b'')
present.append(addr)
except OSError:
pass
except FileNotFoundError:
raise I2CError(f"{I2C_DEV} not found. Run: sudo modprobe i2c-dev")
except PermissionError:
raise I2CError(f"Permission denied on {I2C_DEV}. Fix: sudo chmod 666 /dev/i2c-0")
return present
def print_device_map():
print(f"Probing {I2C_DEV} (ICC-backed I2C adapter)...")
present = detect_devices()
if not present:
print(" No devices detected.")
print(" Tip: i2cdetect -y 0 from i2c-tools may give more accurate results.")
return
print(f" {len(present)} device(s) found:")
for addr in present:
note = ' (possible VRM)' if addr == 0x60 else ''
print(f" 0x{addr:02X}{note}")
class I2CBus:
def __init__(self, device=I2C_DEV):
self.device = device
self._fd = None
def __enter__(self):
try:
self._fd = open(self.device, 'r+b', buffering=0)
except FileNotFoundError:
raise I2CError(f"{self.device} not found. Run: sudo modprobe i2c-dev")
except PermissionError:
raise I2CError(f"Permission denied. Fix: sudo chmod 666 {self.device}")
return self
def __exit__(self, *args):
if self._fd:
self._fd.close()
def read_byte(self, addr, reg):
return i2c_read_byte_data(self._fd, addr, reg)
def write_byte(self, addr, reg, value):
i2c_write_byte_data(self._fd, addr, reg, value)
def read_block(self, addr, reg, length):
return i2c_read_block(self._fd, addr, reg, length)
if __name__ == '__main__':
import sys
if len(sys.argv) == 2 and sys.argv[1] == 'detect':
print_device_map()
sys.exit(0)
if len(sys.argv) == 4 and sys.argv[1] == 'read':
addr = int(sys.argv[2], 16)
reg = int(sys.argv[3], 16)
with I2CBus() as bus:
val = bus.read_byte(addr, reg)
print(f"0x{addr:02X}[0x{reg:02X}] = 0x{val:02X} ({val})")
sys.exit(0)
if len(sys.argv) == 5 and sys.argv[1] == 'write':
addr = int(sys.argv[2], 16)
reg = int(sys.argv[3], 16)
value = int(sys.argv[4], 16)
with I2CBus() as bus:
bus.write_byte(addr, reg, value)
print(f"Wrote 0x{value:02X} to 0x{addr:02X}[0x{reg:02X}]")
sys.exit(0)
print(f"Usage: python {sys.argv[0]} detect")
print(f" python {sys.argv[0]} read <addr_hex> <reg_hex>")
print(f" python {sys.argv[0]} write <addr_hex> <reg_hex> <val_hex>")
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment