#include "ngspice/cm.h"
extern void cm_ota(Mif_Private_t *);
/*
================================================================================

FILE ota/cfunc.mod

Public Domain

AUTHORS

    20 Mar 2026     Seth Hillbrand

SUMMARY

    This file contains the model-specific routines for the OTA
    (Operational Transconductance Amplifier) code model.

    DC/TRAN: Vout as current = gm * (Vp - Vn)
    AC:      Complex gain = gm
    NOISE:   Programmatic noise with en, in_noise, enk, ink, incm, incmk.

    Parameter naming follows LTSPICE convention for OTA compatibility.

================================================================================
*/

#include <stdlib.h>


void cm_ota(Mif_Private_t *mif_private)
{
    double gm_val = mif_private->param[0]->element[0].rvalue;
    double rout = mif_private->param[1]->element[0].rvalue;
    double rin = mif_private->param[2]->element[0].rvalue;

    Mif_Complex_t ac_gain;

    if (mif_private->circuit.anal_type == NOISE) {
        /* Register noise sources on every call (N_OPEN and N_CALC).
         * During N_OPEN: registers and returns index.
         * During N_CALC: returns same sequential index. */
        int src_en_w = cm_noise_add_source("en_white", 1, 0, MIF_NOISE_CURRENT);
        int src_en_f = cm_noise_add_source("en_flicker", 1, 0, MIF_NOISE_CURRENT);
        int src_in_w = cm_noise_add_source("in_white", 0, 0, MIF_NOISE_CURRENT);
        int src_in_f = cm_noise_add_source("in_flicker", 0, 0, MIF_NOISE_CURRENT);

        /* Common-mode current noise: independent sources from each input pin to ground */
        int src_icm_pw = cm_noise_add_source("incm_p_white", 0, 0, MIF_NOISE_CURRENT_POS);
        int src_icm_pf = cm_noise_add_source("incm_p_flicker", 0, 0, MIF_NOISE_CURRENT_POS);
        int src_icm_nw = cm_noise_add_source("incm_n_white", 0, 0, MIF_NOISE_CURRENT_NEG);
        int src_icm_nf = cm_noise_add_source("incm_n_flicker", 0, 0, MIF_NOISE_CURRENT_NEG);

        if (!mif_private->noise->registering) {
            double en = mif_private->param[3]->element[0].rvalue;
            double in_n = mif_private->param[4]->element[0].rvalue;
            double enk_val = mif_private->param[5]->element[0].rvalue;
            double ink_val = mif_private->param[6]->element[0].rvalue;
            double incm = mif_private->param[7]->element[0].rvalue;
            double incmk_val = mif_private->param[8]->element[0].rvalue;
            double f = mif_private->noise->freq;

            /* en referred to output as current noise: (en * gm)^2 A^2/Hz */
            mif_private->noise->density[src_en_w] = en * en * gm_val * gm_val;
            mif_private->noise->density[src_en_f] = (enk_val > 0 && f > 0) ?
                en * en * gm_val * gm_val * enk_val / f : 0.0;

            /* in as differential current noise at input: in^2 A^2/Hz */
            mif_private->noise->density[src_in_w] = in_n * in_n;
            mif_private->noise->density[src_in_f] = (ink_val > 0 && f > 0) ?
                in_n * in_n * ink_val / f : 0.0;

            /* incm as current noise from each input pin to ground */
            mif_private->noise->density[src_icm_pw] = incm * incm;
            mif_private->noise->density[src_icm_pf] = (incmk_val > 0 && f > 0) ?
                incm * incm * incmk_val / f : 0.0;
            mif_private->noise->density[src_icm_nw] = incm * incm;
            mif_private->noise->density[src_icm_nf] = (incmk_val > 0 && f > 0) ?
                incm * incm * incmk_val / f : 0.0;
        }

        return;
    }

    if (mif_private->circuit.anal_type != MIF_AC) {
        double v_in = mif_private->conn[0]->port[0]->input.rvalue;
        double v_out = mif_private->conn[1]->port[0]->input.rvalue;

        mif_private->conn[1]->port[0]->output.rvalue = gm_val * v_in + v_out / rout;
         mif_private->conn[1]->port[0]->partial[0].port[0] = gm_val;
         mif_private->conn[1]->port[0]->partial[1].port[0] = 1.0 / rout;

        mif_private->conn[0]->port[0]->output.rvalue = v_in / rin;
         mif_private->conn[0]->port[0]->partial[0].port[0] = 1.0 / rin;
    }
    else {
        Mif_Complex_t ac_rout;
        Mif_Complex_t ac_rin;

        ac_gain.real = gm_val;
        ac_gain.imag = 0.0;
         mif_private->conn[1]->port[0]->ac_gain[0].port[0] = ac_gain;

        ac_rout.real = 1.0 / rout;
        ac_rout.imag = 0.0;
         mif_private->conn[1]->port[0]->ac_gain[1].port[0] = ac_rout;

        ac_rin.real = 1.0 / rin;
        ac_rin.imag = 0.0;
         mif_private->conn[0]->port[0]->ac_gain[0].port[0] = ac_rin;
    }
}
