#include "stm32g4xx.h"
#include "drill_dual_adc.h"
#include "drill_adc_capture.h"
#include "drill_pwm_stage.h"
#include "drill_spi_transport.h"
#include "drill_config_binding.h"
#include "drill_timebase.h"
#include <stddef.h>
#define DU_COMMON (ADC_CCR_CKMODE|ADC_CCR_DUAL_2|ADC_CCR_DUAL_1)
#define DU_TRIGGER (ADC_CFGR_EXTSEL_3|ADC_CFGR_EXTSEL_1|ADC_CFGR_EXTEN_0)
#define DU_FLAGS (ADC_ISR_OVR|ADC_ISR_JQOVF|ADC_ISR_AWD1|ADC_ISR_AWD2|ADC_ISR_AWD3|ADC_ISR_JEOC|ADC_ISR_JEOS)
volatile du_state drill_dual_state;
volatile uint32_t drill_dual_error;
static uint32_t du_last,du_phase,du_origin,du_sequence;
static du_sample du_pending;
#define DS_COUNT 2u
#define DS_CCR (DMA_CCR_CIRC|DMA_CCR_MINC|DMA_CCR_PSIZE_1|DMA_CCR_MSIZE_1|DMA_CCR_PL_1|DMA_CCR_EN)
#define DS_CLOCK (RCC_AHB1ENR_DMA1EN|RCC_AHB1ENR_DMAMUX1EN)
static volatile uint32_t ds_ring[DS_COUNT];
static bool ds_owned,ds_pending;
static uint32_t ds_remaining,ds_progress,ds_last,ds_sequence;
static du_sample ds_sample;
static void ds_release(void);
static bool ds_config(void);
static void ds_service(uint32_t now);
static bool du_regs(ADC_TypeDef *p,unsigned channel,bool master){
 if(p->CFGR!=(master?DU_TRIGGER:0u)||p->CFGR2||p->IER||p->DIFSEL||p->JSQR||p->SMPR1!=(2u<<(3u*channel))||p->SMPR2)return false;
 if(p->SQR1!=(channel<<6)||p->SQR2||p->SQR3||p->SQR4||p->OFR1||p->OFR2||p->OFR3||p->OFR4||p->GCOMP)return false;
 uint32_t expect=ADC_CR_ADVREGEN,allowed=0;
 if(drill_dual_state==DU_CAL)allowed=ADC_CR_ADCAL;
 if(drill_dual_state>=DU_ENABLE&&drill_dual_state<=DU_STREAM)expect|=ADC_CR_ADEN;
 if(drill_dual_state==DU_WAIT||drill_dual_state==DU_STREAM)allowed=ADC_CR_ADSTART;
 if(drill_dual_state==DU_STOP)allowed=ADC_CR_ADSTART|ADC_CR_ADSTP;
 return (p->CR&~allowed)==expect;
}
static bool du_ok(void){
 return ((TIM1->CR1&TIM_CR1_CEN)!=0)==(drill_dual_state==DU_WAIT||drill_dual_state==DU_STREAM)&&drill_adc_state==DA_IDLE&&dc_feedback_ready(&drill_config)&&drill_spi_sampling_available()&&
 (GPIOF->ODR&2u)&&(GPIOF->IDR&2u)&&((GPIOA->MODER>>12)&3u)==0u&&(GPIOA->IDR&(1u<<6))&&
 ((GPIOA->MODER>>24)&3u)==1u&&!(GPIOA->OTYPER&(1u<<12))&&(GPIOA->ODR&(1u<<12))&&(GPIOA->IDR&(1u<<12))&&
 (GPIOA->MODER&15u)==15u&&!(GPIOA->PUPDR&15u)&&(RCC->AHB2ENR&RCC_AHB2ENR_ADC12EN)&&!(RCC->AHB2RSTR&RCC_AHB2RSTR_ADC12RST)&&
 ADC12_COMMON->CCR==(DU_COMMON|(drill_dual_state==DU_STREAM?(ADC_CCR_MDMA_1|ADC_CCR_DMACFG):0u))&&du_regs(ADC1,1,true)&&du_regs(ADC2,2,false);
}
static void du_fail(unsigned reason){
 drill_spi_abort();drill_pwm_sampling_abort();drill_config.state=DC_FAILED;drill_config.error=DC_INTERLOCK;
 if(RCC->AHB2ENR&RCC_AHB2ENR_ADC12EN){RCC->AHB2RSTR|=RCC_AHB2RSTR_ADC12RST;__DSB();RCC->AHB2RSTR&=~RCC_AHB2RSTR_ADC12RST;__DSB();RCC->AHB2ENR&=~RCC_AHB2ENR_ADC12EN;}
 ds_release();
 du_pending=(du_sample){0};drill_dual_error=reason;drill_dual_state=DU_FAILED;
}
static void du_prepare(ADC_TypeDef *p,unsigned channel,bool master){
 p->CFGR=master?DU_TRIGGER:0u;p->CFGR2=0;p->IER=0;p->DIFSEL=0;p->JSQR=0;p->SMPR1=2u<<(3u*channel);p->SMPR2=0;
 p->SQR1=channel<<6;p->SQR2=p->SQR3=p->SQR4=0;p->OFR1=p->OFR2=p->OFR3=p->OFR4=p->GCOMP=0;
 p->CR&=~ADC_CR_DEEPPWD;p->CR|=ADC_CR_ADVREGEN;
}
bool drill_dual_bench_begin(uint32_t now,bool qualified){
 if(!qualified||drill_dual_state!=DU_IDLE||drill_adc_state!=DA_IDLE||!dc_feedback_ready(&drill_config)||!drill_spi_available()||drill_spi_active())return false;
 if((RCC->AHB2ENR&RCC_AHB2ENR_ADC12EN)||(RCC->AHB2RSTR&RCC_AHB2RSTR_ADC12RST))return false;
 if(!drill_pwm_sampling_arm())return false;
 GPIOA->MODER|=15u;GPIOA->PUPDR&=~15u;__DSB();RCC->AHB2ENR|=RCC_AHB2ENR_ADC12EN;(void)RCC->AHB2ENR;__DSB();
 RCC->AHB2RSTR|=RCC_AHB2RSTR_ADC12RST;__DSB();RCC->AHB2RSTR&=~RCC_AHB2RSTR_ADC12RST;__DSB();
 ADC12_COMMON->CCR=DU_COMMON;du_prepare(ADC1,1,true);du_prepare(ADC2,2,false);__DSB();
 du_last=du_phase=du_origin=now;du_sequence=0;du_pending=(du_sample){0};drill_dual_error=0;drill_dual_state=DU_REG;
 if(!du_ok()){du_fail(1);return false;}return true;
}
void drill_dual_poll(uint32_t now){
 if(drill_dual_state==DU_IDLE||drill_dual_state==DU_FAILED)return;
 if(now-du_last>0x7fffffffu){du_fail(2);return;}du_last=now;
 if(!du_ok()){du_fail(1);return;}
 uint32_t a=ADC1->ISR,b=ADC2->ISR;if((a|b)&DU_FLAGS){du_fail(3);return;}
 if(drill_dual_state==DU_STREAM){ds_service(now);return;}
 if(drill_dual_state<DU_READY&&now-du_origin>5000u){du_fail(2);return;}
 if(drill_dual_state==DU_REG){if(now-du_phase>=40u){ADC1->CR|=ADC_CR_ADCAL;ADC2->CR|=ADC_CR_ADCAL;drill_dual_state=DU_CAL;}return;}
 if(drill_dual_state==DU_CAL){if(!((ADC1->CR|ADC2->CR)&ADC_CR_ADCAL)){drill_dual_state=DU_GAP;du_phase=now;}return;}
 if(drill_dual_state==DU_GAP){if(now-du_phase>=2u){ADC1->ISR=ADC_ISR_ADRDY;ADC2->ISR=ADC_ISR_ADRDY;__DSB();ADC2->CR|=ADC_CR_ADEN;ADC1->CR|=ADC_CR_ADEN;drill_dual_state=DU_ENABLE;}return;}
 if(drill_dual_state==DU_ENABLE){if((a&b)&ADC_ISR_ADRDY)drill_dual_state=DU_READY;return;}
 if(drill_dual_state==DU_WAIT){
  if(now-du_phase>40u){du_fail(2);return;}
  if(!((a&b)&ADC_ISR_EOC))return;
  if(!((a&b)&ADC_ISR_EOS)){du_fail(3);return;}
  /* Pause the internal timer before another period can replace this pair. */
  drill_pwm_sampling_pause();
  uint32_t paused;
  if(!drill_timebase_get(&paused)||paused-du_phase>40u){du_fail(2);return;}
  a=ADC1->ISR;b=ADC2->ISR;
  if(((a|b)&DU_FLAGS)||!((a&b)&ADC_ISR_EOC)||!((a&b)&ADC_ISR_EOS)){du_fail(3);return;}
  uint32_t raw=ADC12_COMMON->CDR;
  if(raw&0xf000f000u){du_fail(4);return;}
  du_pending.ia_raw=(uint16_t)raw;du_pending.ib_raw=(uint16_t)(raw>>16);du_pending.observed_us=paused;du_pending.timer_count=(uint16_t)TIM1->CNT;
  ADC1->ISR=ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_EOSMP;ADC2->ISR=ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_EOSMP;__DSB();
  if(ADC1->CR&ADC_CR_ADSTART)ADC1->CR|=ADC_CR_ADSTP;
  drill_dual_state=DU_STOP;du_phase=now;return;
 }
 if(drill_dual_state==DU_STOP){
  if(now-du_phase>100u){du_fail(2);return;}
  if((ADC1->CR|ADC2->CR)&(ADC_CR_ADSTART|ADC_CR_ADSTP))return;
  du_pending.sequence=++du_sequence;drill_dual_state=DU_DONE;
 }
}
bool drill_dual_capture_start(uint32_t now){
 if(drill_dual_state!=DU_READY)return false;
 drill_dual_poll(now);if(drill_dual_state!=DU_READY||drill_spi_active())return false;
 du_pending=(du_sample){.armed_us=now};ADC1->ISR=ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_EOSMP;ADC2->ISR=ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_EOSMP;__DSB();
 ADC1->CR|=ADC_CR_ADSTART;drill_dual_state=DU_WAIT;du_phase=now;
 if(!drill_pwm_sampling_start()){du_fail(1);return false;}return true;
}
bool drill_dual_take(du_sample *out,uint32_t now){
 if(!out||drill_dual_state!=DU_DONE)return false;
 drill_dual_poll(now);if(drill_dual_state!=DU_DONE)return false;
 if(now-du_pending.observed_us>100u){du_fail(2);return false;}*out=du_pending;du_pending=(du_sample){0};drill_dual_state=DU_READY;return true;
}
bool drill_dual_busy(void){return drill_dual_state!=DU_IDLE&&drill_dual_state!=DU_READY&&drill_dual_state!=DU_DONE&&drill_dual_state!=DU_FAILED;}

static void ds_release(void){
 if(ds_owned){
  /* This candidate took the entire DMA1/DMAMUX1 domain from disabled clocks.
   * No other users are permitted. Stop pending bus writes without a busy wait. */
  RCC->AHB1RSTR|=RCC_AHB1RSTR_DMA1RST;__DSB();RCC->AHB1RSTR&=~RCC_AHB1RSTR_DMA1RST;__DSB();
  DMAMUX1_Channel0->CCR=0;RCC->AHB1ENR&=~DS_CLOCK;ds_owned=false;
 }
 ds_pending=false;ds_sample=(du_sample){0};
}
static bool ds_config(void){
 return ds_owned&&(RCC->AHB1ENR&(DS_CLOCK|RCC_AHB1ENR_DMA2EN))==DS_CLOCK&&!(RCC->AHB1RSTR&(RCC_AHB1RSTR_DMA1RST|RCC_AHB1RSTR_DMAMUX1RST))&&
 DMA1_Channel1->CCR==DS_CCR&&DMA1_Channel1->CPAR==(uint32_t)(uintptr_t)&ADC12_COMMON->CDR&&DMA1_Channel1->CMAR==(uint32_t)(uintptr_t)ds_ring&&DMAMUX1_Channel0->CCR==5u&&
 !(DMA1->ISR&DMA_ISR_TEIF1)&&!(DMAMUX1_ChannelStatus->CSR&DMAMUX_CSR_SOF0);
}
bool drill_dual_stream_start(uint32_t now){
 if(drill_dual_state!=DU_READY||ds_owned)return false;
 drill_dual_poll(now);if(drill_dual_state!=DU_READY||drill_spi_active())return false;
 /* Reject shared ownership before altering clocks or the existing ADC profile. */
 if((RCC->AHB1ENR&(DS_CLOCK|RCC_AHB1ENR_DMA2EN))||(RCC->AHB1RSTR&(RCC_AHB1RSTR_DMA1RST|RCC_AHB1RSTR_DMAMUX1RST)))return false;
 RCC->AHB1ENR|=DS_CLOCK;(void)RCC->AHB1ENR;__DSB();
 RCC->AHB1RSTR|=RCC_AHB1RSTR_DMA1RST|RCC_AHB1RSTR_DMAMUX1RST;__DSB();RCC->AHB1RSTR&=~(RCC_AHB1RSTR_DMA1RST|RCC_AHB1RSTR_DMAMUX1RST);__DSB();ds_owned=true;
 for(unsigned i=0;i<DS_COUNT;++i)ds_ring[i]=0;
 DMA1_Channel1->CCR=0;DMA1->IFCR=DMA_IFCR_CGIF1;DMAMUX1_ChannelStatus->CFR=DMAMUX_CFR_CSOF0;__DSB();
 DMAMUX1_Channel0->CCR=5u;DMA1_Channel1->CPAR=(uint32_t)(uintptr_t)&ADC12_COMMON->CDR;DMA1_Channel1->CMAR=(uint32_t)(uintptr_t)ds_ring;DMA1_Channel1->CNDTR=DS_COUNT;
 ADC12_COMMON->CCR=DU_COMMON|ADC_CCR_MDMA_1|ADC_CCR_DMACFG;
 ADC1->ISR=ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_EOSMP;ADC2->ISR=ADC_ISR_EOC|ADC_ISR_EOS|ADC_ISR_EOSMP;__DSB();
 du_phase=now;ds_remaining=DS_COUNT;ds_last=ds_progress=now;ds_sequence=0;ds_pending=false;ds_sample=(du_sample){0};
 DMA1_Channel1->CCR=DS_CCR;__DSB();ADC1->CR|=ADC_CR_ADSTART;drill_dual_state=DU_STREAM;
 if(!ds_config()||!drill_pwm_sampling_start()){du_fail(5);return false;}return true;
}
static void ds_service(uint32_t now){
 if(now-ds_last>30u){du_fail(6);return;}
 if(!ds_config()){du_fail(5);return;}
 uint32_t remaining=DMA1_Channel1->CNDTR;
 if(remaining>DS_COUNT){du_fail(5);return;}
 /* Zero is the circular-reload boundary, equivalent to two pending slots. */
 if(remaining==0u)remaining=DS_COUNT;
 uint32_t delta=(ds_remaining+DS_COUNT-remaining)%DS_COUNT;
 if(delta>1u||(delta&&ds_pending)){du_fail(7);return;}
 uint32_t completed=DMA1->ISR&(DMA_ISR_HTIF1|DMA_ISR_TCIF1);
 if(!delta&&completed){du_fail(7);return;}
 if(!delta){if(now-ds_progress>100u){du_fail(6);return;}ds_last=now;return;}
 uint32_t expected=remaining==1u?DMA_ISR_HTIF1:DMA_ISR_TCIF1;
 if(!completed)return; /* Counter observed before completion flag latch: wait. */
 if(completed!=expected){du_fail(7);return;}
 __DMB();uint32_t raw=ds_ring[(DS_COUNT-remaining-1u)&(DS_COUNT-1u)];__DMB();
 uint32_t after=DMA1_Channel1->CNDTR;if(after==0u)after=DS_COUNT;
 uint32_t observed;
 if(after!=remaining||!drill_timebase_get(&observed)||observed-ds_last>30u||!ds_config()||!du_ok()||((ADC1->ISR|ADC2->ISR)&DU_FLAGS)){du_fail(7);return;}
 if(raw&0xf000f000u){du_fail(4);return;}
 ds_sample=(du_sample){.ia_raw=(uint16_t)raw,.ib_raw=(uint16_t)(raw>>16),.timer_count=(uint16_t)TIM1->CNT,.armed_us=du_phase,.observed_us=observed,.sequence=++ds_sequence};
 ds_pending=true;ds_remaining=remaining;ds_progress=ds_last=observed;
 DMA1->IFCR=expected;__DSB();
}
bool drill_dual_stream_take(du_sample *out,uint32_t now){
 if(!out||drill_dual_state!=DU_STREAM)return false;
 uint32_t fresh;
 if(!drill_timebase_get(&fresh)||fresh-now>30u){du_fail(6);return false;}
 now=fresh; /* Prior poll may have timestamped the DMA result after caller now. */
 drill_dual_poll(now);if(drill_dual_state!=DU_STREAM||!ds_pending)return false;
 /* poll may publish a pair observed after the entry timestamp. Read the
  * hardware timer again; subtracting the older entry value would underflow. */
 uint32_t consumed;
 if(!drill_timebase_get(&consumed)||consumed-ds_sample.observed_us>30u){du_fail(6);return false;}
 *out=ds_sample;ds_sample=(du_sample){0};ds_pending=false;return true;
}
void drill_dual_stream_stop(void){if(ds_owned)du_fail(0);}

void drill_dual_abort(void){if(drill_dual_state!=DU_IDLE&&drill_dual_state!=DU_FAILED)du_fail(8);}
