#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 2: PCM5102A DAC + reconstruction filter (SKiDL).

Run INSIDE the EDA container:
    cd hardware/eda && ./run.sh python3 ../eda/circuits/stage2_dac.py
Outputs ERC + a KiCad netlist at hardware/kicad/stage2_dac.net.

WHAT THIS STAGE DOES
  The RP2350 streams I2S audio (the click, and later sampled sounds) to a PCM5102A
  DAC clocked by the dedicated low-jitter oscillator (SCK=MCLK). The PCM5102A puts
  out 2.1 Vrms GROUND-CENTERED analog (no DC-block cap needed). A 2nd-order
  Sallen-Key low-pass (~75 kHz, one OPA1612 section) removes the delta-sigma HF
  residue before the click reaches the summing/output stages -> CLICK_OUT.

PINOUTS VERIFIED FROM DATASHEETS
  * PCM5102A (TI SLAS859C, PW/TSSOP-20): pin map below matches the datasheet table.
    Output 2.1Vrms GND-centered; 3.3V supplies; charge pump (CAPP/CAPM flying cap +
    VNEG) makes the negative rail for the ground-centered swing.
  * OPA1612 (dual): standard JEDEC dual-opamp SOIC-8 pinout
    (1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB 8=V+), confirmed against the
    OPA164x dual on its datasheet (identical layout). Confirm OPA1612 sheet at layout.
"""
import os
from skidl import *

set_default_tool(KICAD9)

R  = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C  = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")

# ---- rails / signals ----
p3v3, gnd, p15, n15 = Net("+3V3"), Net("GND"), Net("+15V"), Net("-15V")
for n in (p3v3, gnd, p15, n15):
    n.drive = POWER
mclk   = Net("MCLK")        # low-jitter audio clock -> SCK
i2s_bck, i2s_din, i2s_lrck = Net("I2S_BCK"), Net("I2S_DIN"), Net("I2S_LRCK")  # from RP2350
dac_xsmt = Net("DAC_XSMT")  # GPIO soft-mute (high=unmute), pulled up
click_out = Net("CLICK_OUT")# filtered click -> Stage 3 summing

# ---- PCM5102A (pinout verified, TI SLAS859C) ----
DAC = Part(name="PCM5102A", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
    footprint="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm",
    pins=[
        Pin(num=1,  name="CPVDD", func=Pin.types.PWRIN),
        Pin(num=2,  name="CAPP",  func=Pin.types.PASSIVE),
        Pin(num=3,  name="CPGND", func=Pin.types.PWRIN),
        Pin(num=4,  name="CAPM",  func=Pin.types.PASSIVE),
        Pin(num=5,  name="VNEG",  func=Pin.types.PASSIVE),
        Pin(num=6,  name="OUTL",  func=Pin.types.OUTPUT),
        Pin(num=7,  name="OUTR",  func=Pin.types.OUTPUT),
        Pin(num=8,  name="AVDD",  func=Pin.types.PWRIN),
        Pin(num=9,  name="AGND",  func=Pin.types.PWRIN),
        Pin(num=10, name="DEMP",  func=Pin.types.INPUT),
        Pin(num=11, name="FLT",   func=Pin.types.INPUT),
        Pin(num=12, name="SCK",   func=Pin.types.INPUT),
        Pin(num=13, name="BCK",   func=Pin.types.INPUT),
        Pin(num=14, name="DIN",   func=Pin.types.INPUT),
        Pin(num=15, name="LRCK",  func=Pin.types.INPUT),
        Pin(num=16, name="FMT",   func=Pin.types.INPUT),
        Pin(num=17, name="XSMT",  func=Pin.types.INPUT),
        Pin(num=18, name="LDOO",  func=Pin.types.PWROUT),
        Pin(num=19, name="DGND",  func=Pin.types.PWRIN),
        Pin(num=20, name="DVDD",  func=Pin.types.PWRIN),
    ])
dac = DAC(ref="U3")

# ---- OPA1612 dual op-amp (standard dual pinout) ----
OPA2 = Part(name="OPA1612", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
    footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
    pins=[
        Pin(num=1, name="OUTA", func=Pin.types.OUTPUT),
        Pin(num=2, name="-INA", func=Pin.types.INPUT),
        Pin(num=3, name="+INA", func=Pin.types.INPUT),
        Pin(num=4, name="V-",   func=Pin.types.PWRIN),
        Pin(num=5, name="+INB", func=Pin.types.INPUT),
        Pin(num=6, name="-INB", func=Pin.types.INPUT),
        Pin(num=7, name="OUTB", func=Pin.types.OUTPUT),
        Pin(num=8, name="V+",   func=Pin.types.PWRIN),
    ])
flt = OPA2(ref="U4")

# ---- PCM5102A supplies + grounds (all grounds tied; <0.2V per datasheet) ----
dac["AVDD"]  += p3v3
dac["CPVDD"] += p3v3
dac["DVDD"]  += p3v3
dac["AGND"]  += gnd
dac["DGND"]  += gnd
dac["CPGND"] += gnd
for pin in ("AVDD", "CPVDD", "DVDD"):
    c = C(value="100nF"); dac[pin] += c[1]; c[2] += gnd
cbulk = C(value="10uF", footprint="Capacitor_SMD:C_1206_3216Metric")
dac["AVDD"] += cbulk[1]; cbulk[2] += gnd

# charge pump: flying cap across CAPP/CAPM, VNEG + LDOO decoupling
cfly = C(value="2.2uF", footprint="Capacitor_SMD:C_0805_2012Metric")
dac["CAPP"] += cfly[1]; dac["CAPM"] += cfly[2]
cvneg = C(value="2.2uF"); dac["VNEG"] += cvneg[1]; cvneg[2] += gnd
cldoo = C(value="1uF");   dac["LDOO"] += cldoo[1]; cldoo[2] += gnd

# control-pin tie-offs: no de-emphasis, normal latency, I2S format
dac["DEMP"] += gnd
dac["FLT"]  += gnd
dac["FMT"]  += gnd
# soft-mute: GPIO with 10k pull-up to keep un-muted by default
dac["XSMT"] += dac_xsmt
rpull = R(value="10k"); dac_xsmt += rpull[1]; rpull[2] += p3v3

# clocks + data
dac["SCK"]  += mclk
dac["BCK"]  += i2s_bck
dac["DIN"]  += i2s_din
dac["LRCK"] += i2s_lrck

# OUTR unused (mono click on L): give it the datasheet-recommended load
rload = R(value="2.2k"); dac["OUTR"] += rload[1]; rload[2] += gnd

# ---- Sallen-Key reconstruction LPF on OUTL (R=1.5k, Ca=2.2n, Cb=1n ~75kHz) ----
r1, r2 = R(value="1.5k"), R(value="1.5k")
ca = C(value="2.2nF"); cb = C(value="1nF")
nodeA = Net("RC_A")
dac["OUTL"] += r1[1]; r1[2] += nodeA
r2[1] += nodeA; r2[2] += flt["+INA"]
ca[1] += nodeA; ca[2] += flt["OUTA"]       # feedback cap
cb[1] += flt["+INA"]; cb[2] += gnd
flt["-INA"] += flt["OUTA"]                 # unity-gain follower
flt["OUTA"] += click_out

# ---- OPA1612 supplies + decoupling; park unused section B ----
flt["V+"] += p15; flt["V-"] += n15
for rail in (p15, n15):
    c = C(value="100nF"); rail += c[1]; c[2] += gnd
flt["+INB"] += gnd; flt["OUTB"] += flt["-INB"]   # B = grounded follower (parked)

ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage2_dac.net"))
generate_netlist(file_=out)
print("Stage 2 netlist ->", out)
