#!/usr/bin/env python3
"""PM_K-1 audio chain - Stage 4: balanced output driver (THAT1646) + mute + ground-lift.

Run INSIDE the EDA container:
    cd hardware/eda && ./run.sh python3 ../eda/circuits/stage4_driver.py
Outputs ERC + a KiCad netlist at hardware/kicad/stage4_driver.net. This closes the
audio chain: MIX_OUT -> level trim -> THAT1646 -> build-out -> mute relay -> balanced
output on the analog interconnect; plus the ground-lift relay.

PINOUT VERIFIED (THAT doc 600078 rev 07, SO-8):
  THAT1646: 1=Out- 2=Sns- 3=Gnd 4=In 5=Vee(V-) 6=Vcc(V+) 7=Sns+ 8=Out+.
  Fixed +6 dB differential gain; sense pins tie to the output pins (local sense) with
  the 47 ohm build-out OUTSIDE the loop for cable stability. Supply 4-18V (we use +/-15).
  TQ2SA relay pinout per stage1b (coil 1/10; pole1 COM=3 NC=4 NO=2; pole2 COM=8 NC=7 NO=9).

KEY CHOICES
  * Level cal: the THAT1646 gain is FIXED (+6 dB), so the calibration trim is a 25-turn
    pot ahead of it (DAC full-scale -> +4 dBu, accounting for summer + the +6 dB).
  * Phase: Stage 3's summer inverted the signal; corrected here by taking AOUT_HOT from
    Out- and AOUT_COLD from Out+ (absolute polarity preserved).
  * Mute (K2): fail-safe -- de-energized shorts both legs to GND (after the 47 ohm
    build-out, so the driver is current-limited). Energized = un-muted. Driven by the
    hardware rail-supervisor via K2_DRV (in the power block); MCU can also assert.
  * Ground-lift (K3): de-energized = bonded (GND<->CHASSIS); energize to LIFT. Soft-lift
    100 ohm || 10 nF across the contact keeps an RF/safety path. A face panel switch sits
    in series downstream (CHASSIS pin on the interconnect) -> both-must-close to bond.
"""
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")

p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V")
for n in (p15, n15, gnd, p5):
    n.drive = POWER
mix_out  = Net("MIX_OUT")        # from Stage 3 summer (inverted)
aout_hot, aout_cold = Net("AOUT_HOT"), Net("AOUT_COLD")  # -> analog interconnect
chassis  = Net("CHASSIS")        # shield -> interconnect (face panel switch + chassis)
k2_drv, k3_drv = Net("K2_DRV"), Net("K3_DRV")            # relay coil drives

# ---- THAT1646 balanced line driver (pinout verified) ----
DRV = Part(name="THAT1646", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
    footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
    pins=[
        Pin(num=1, name="OUT-", func=Pin.types.OUTPUT),
        Pin(num=2, name="SNS-", func=Pin.types.INPUT),
        Pin(num=3, name="GND",  func=Pin.types.PWRIN),
        Pin(num=4, name="IN",   func=Pin.types.INPUT),
        Pin(num=5, name="V-",   func=Pin.types.PWRIN),
        Pin(num=6, name="V+",   func=Pin.types.PWRIN),
        Pin(num=7, name="SNS+", func=Pin.types.INPUT),
        Pin(num=8, name="OUT+", func=Pin.types.OUTPUT),
    ])
drv = DRV(ref="U6")

# ---- TQ2SA relay (verified pinout, reused def) ----
def tq2sa(ref):
    p = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K",
        footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA",
        pins=[
            Pin(num=1,  name="COIL_A", func=Pin.types.PASSIVE),
            Pin(num=10, name="COIL_B", func=Pin.types.PASSIVE),
            Pin(num=3,  name="P1_COM", func=Pin.types.PASSIVE),
            Pin(num=4,  name="P1_NC",  func=Pin.types.PASSIVE),
            Pin(num=2,  name="P1_NO",  func=Pin.types.PASSIVE),
            Pin(num=8,  name="P2_COM", func=Pin.types.PASSIVE),
            Pin(num=7,  name="P2_NC",  func=Pin.types.PASSIVE),
            Pin(num=9,  name="P2_NO",  func=Pin.types.PASSIVE),
            Pin(num=5,  name="NC5",    func=Pin.types.NOCONNECT),
            Pin(num=6,  name="NC6",    func=Pin.types.NOCONNECT),
        ])
    return p(ref=ref)
k2 = tq2sa("K2")   # mute
k3 = tq2sa("K3")   # ground-lift

# ---- level-cal trim (25-turn pot): MIX_OUT (top) / GND (bottom) / wiper -> THAT1646 IN ----
RV = Part("Device", "R_Potentiometer", dest=TEMPLATE,
          footprint="Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical")
rv1 = RV(value="10k", ref="RV1")
mix_out += rv1[1]; rv1[3] += gnd; rv1[2] += drv["IN"]

# ---- THAT1646: sense ties (local) + supplies ----
drv["SNS-"] += drv["OUT-"]
drv["SNS+"] += drv["OUT+"]
drv["GND"]  += gnd
drv["V+"] += p15; drv["V-"] += n15
for rail in (p15, n15):
    c = C(value="100nF"); rail += c[1]; c[2] += gnd

# ---- 47 ohm build-out (phase-corrected: HOT<-OUT-, COLD<-OUT+) ----
rbo_h, rbo_c = R(value="47"), R(value="47")
drv["OUT-"] += rbo_h[1]; rbo_h[2] += aout_hot
drv["OUT+"] += rbo_c[1]; rbo_c[2] += aout_cold

# ---- mute relay K2: de-energized shorts both legs to GND (fail-safe) ----
k2["P1_COM"] += aout_hot;  k2["P1_NC"] += gnd      # de-energized: hot -> GND (muted)
k2["P2_COM"] += aout_cold; k2["P2_NC"] += gnd      # de-energized: cold -> GND
k2["COIL_A"] += p5; k2["COIL_B"] += k2_drv         # energize (rails OK) = un-mute

# ---- ground-lift K3: de-energized bonds GND<->CHASSIS; energize to lift ----
k3["P1_COM"] += gnd; k3["P1_NC"] += chassis        # de-energized: bonded
rlift = R(value="100"); clift = C(value="10nF")
gnd += rlift[1]; rlift[2] += chassis               # soft-lift: 100 ohm || 10 nF
clift[1] += gnd; clift[2] += chassis
k3["COIL_A"] += p5; k3["COIL_B"] += k3_drv

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