Files
MiPi_TEST/device_server.py

182 lines
6.0 KiB
Python
Raw Normal View History

2026-04-09 10:29:53 +01:00
"""
device_server.py deploy this on the target device (192.168.45.8)
Provides:
PUT /display {"state": "on"|"off"} blank/unblank framebuffer
GET /registers read MIPI DSI PHY registers via memtool
Add addresses to REGISTER_COMMANDS to capture more register ranges.
"""
import os
import re
import subprocess
from flask import Flask, jsonify, request
app = Flask(__name__)
# ---------------------------------------------------------------------------
# Register commands to execute on each GET /registers request.
# Each entry is a complete memtool command string.
# ---------------------------------------------------------------------------
REGISTER_COMMANDS = [
"memtool md -l 0x32e100b4+0x0c", # DSIM_PHYTIMING / PHYTIMING1 / PHYTIMING2
]
2026-04-20 13:48:24 +01:00
# ---------------------------------------------------------------------------
# SN65DSI83 I2C configuration
# ---------------------------------------------------------------------------
SN65_I2C_BUS = 2 # i2c-2 on i.MX 8M Mini — change if bridge is on a different bus
SN65_I2C_ADDR = 0x2C # SN65DSI83 fixed 7-bit I2C address
2026-04-09 10:29:53 +01:00
# Known Samsung DSIM register names (base 0x32E10000, i.MX 8M Mini)
_DSIM_NAMES = {
0x32e10004: "DSIM_STATUS",
0x32e10008: "DSIM_CLKCTRL",
0x32e1000c: "DSIM_TIMEOUT",
0x32e10010: "DSIM_CONFIG",
0x32e10014: "DSIM_ESCMODE",
0x32e100ac: "DSIM_PHYACCHR",
0x32e100b0: "DSIM_PHYACCHR1",
0x32e100b4: "DSIM_PHYTIMING",
0x32e100b8: "DSIM_PHYTIMING1",
0x32e100bc: "DSIM_PHYTIMING2",
}
def _parse_memtool_output(raw: str) -> list:
"""
Parse 'memtool md -l' output into a list of dicts.
Handles both formats:
32e100b4: 00000001 12345678 ...
0x32e100b4: 0x00000001 0x12345678 ...
"""
registers = []
for line in raw.splitlines():
line = line.strip()
if not line:
continue
m = re.match(r"(?:0x)?([0-9a-fA-F]+)\s*:\s*(.+)", line)
if not m:
continue
base_addr = int(m.group(1), 16)
values = re.findall(r"[0-9a-fA-F]{8}", m.group(2))
for i, val in enumerate(values):
addr = base_addr + i * 4
registers.append({
"address": f"0x{addr:08x}",
"value": f"0x{val.lower()}",
"name": _DSIM_NAMES.get(addr, ""),
})
return registers
# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------
@app.route("/display", methods=["PUT"])
def control_display():
data = request.get_json()
state = data.get("state", "").lower()
if state == "off":
os.system("echo 4 > /sys/class/graphics/fb0/blank")
return jsonify({"status": "Display OFF"}), 200
elif state == "on":
os.system("echo 0 > /sys/class/graphics/fb0/blank")
return jsonify({"status": "Display ON"}), 200
else:
return jsonify({"error": "Invalid state. Use 'on' or 'off'"}), 400
@app.route("/registers", methods=["GET"])
def get_registers():
"""Read MIPI DSI PHY timing registers via memtool and return JSON."""
all_registers = []
raw_lines = []
errors = []
for cmd_str in REGISTER_COMMANDS:
try:
result = subprocess.run(
cmd_str.split(), capture_output=True, text=True, timeout=5
)
raw = result.stdout.strip()
if raw:
raw_lines.append(raw)
all_registers.extend(_parse_memtool_output(raw))
if result.returncode != 0 and result.stderr.strip():
errors.append(f"{cmd_str}: {result.stderr.strip()}")
except FileNotFoundError:
errors.append(f"{cmd_str}: memtool not found in PATH")
except subprocess.TimeoutExpired:
errors.append(f"{cmd_str}: timed out after 5 s")
except Exception as e:
errors.append(f"{cmd_str}: {e}")
return jsonify({
"commands": REGISTER_COMMANDS,
"registers": all_registers,
"raw": "\n".join(raw_lines),
"errors": errors if errors else None,
}), 200
2026-04-20 13:48:24 +01:00
def _i2c_read_byte(bus: int, addr: int, reg: int) -> int | None:
"""Read one byte from an I2C device using i2cget. Returns None on failure."""
try:
result = subprocess.run(
["i2cget", "-y", str(bus), f"0x{addr:02x}", f"0x{reg:02x}"],
capture_output=True, text=True, timeout=3
)
if result.returncode == 0:
return int(result.stdout.strip(), 16)
except Exception:
pass
return None
@app.route("/sn65_registers", methods=["GET"])
def get_sn65_registers():
"""Read SN65DSI83 CSR 0x0A (PLL/CLK status) and 0xE5 (error flags) via I2C."""
csr_0a = _i2c_read_byte(SN65_I2C_BUS, SN65_I2C_ADDR, 0x0A)
csr_e5 = _i2c_read_byte(SN65_I2C_BUS, SN65_I2C_ADDR, 0xE5)
regs = {}
errors = []
if csr_0a is not None:
regs["csr_0a"] = {
"value": f"0x{csr_0a:02x}",
"pll_lock": bool(csr_0a & 0x80), # bit 7: PLL_EN_STAT (powered + locked)
"clk_det": bool(csr_0a & 0x40), # bit 6: high-speed CLK lane detected
}
else:
errors.append("CSR 0x0A: i2cget failed")
if csr_e5 is not None:
regs["csr_e5"] = {
"value": f"0x{csr_e5:02x}",
"pll_unlock": bool(csr_e5 & 0x01), # default=1 at reset; must clear after init
"cha_sot_bit_err": bool(csr_e5 & 0x04),
"cha_llp_err": bool(csr_e5 & 0x08), # SoT/EoT/lane-merge error
"cha_ecc_err": bool(csr_e5 & 0x10),
"cha_lp_err": bool(csr_e5 & 0x20),
"cha_crc_err": bool(csr_e5 & 0x40),
}
else:
errors.append("CSR 0xE5: i2cget failed")
return jsonify({
"i2c_bus": SN65_I2C_BUS,
"i2c_addr": f"0x{SN65_I2C_ADDR:02x}",
"registers": regs,
"errors": errors if errors else None,
}), 200
2026-04-09 10:29:53 +01:00
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)