#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>


void find_AB( int32_t AB, int32_t * a_A, int32_t * a_B ) {
  int32_t A = 1;
  int32_t B = 1;
  int32_t R = 0; // желаемая разница между AB и A*B, сначала ищем точное совпадение
  bool vExit = false;
  if ( AB > 8 ) {
    for (;;) {
      A = 8;
      while ( A > 0 ) {
        B = 1;
        while ( B < 65 ) {
          if ( (AB > (A * B)) && (B != 1) && (R >= (AB % (A * B))) ) {
            vExit = (AB == (A * B));
            break;
          }
          int v_df = (AB >= (A * B) ? AB - (A * B) : (A * B) - AB);
          if ( R >= v_df ) {
            vExit = true;
            break;
          }
          ++B;
        }
        if ( vExit ) {
          break;
        }
        if ( 65 == B ) {
          // AB - не делится нацело ни на одно число из диапазона 2..64
          // увеличиваем A и ставим R = 1
          --A;
        } else {
          if ( (AB / A) < 65 ) {
            B = AB / A;
            break;
          } else {
            --A;
          }
        }
      }
      if ( vExit ) {
        break;
      }
      if ( A > 0 && A < 9 ) {
        break;
      } else {
        ++R;
      }
    }
  } else {
    A = AB;
  }
  //
  *a_A = A;
  *a_B = B;
}

// диапазон частот fVCO
#define VCO_MIN_FREQ  220000000ull
#define VCO_MAX_FREQ 1520000000ull


int main( int argc, char ** argv ) {
  if ( argc < 4 ) {
    return 1;
  }
  uint32_t a_hse_clock = (uint32_t)strtoul( argv[1], 0, 0 );
  uint32_t a_pll_clock = (uint32_t)strtoul( argv[2], 0, 0 );
  uint32_t a_pll_clock2 = (uint32_t)strtoul( argv[3], 0, 0 );
  uint64_t v_vco_max_freq = (160ull * a_hse_clock) < VCO_MAX_FREQ ? (160ull * a_hse_clock) : VCO_MAX_FREQ;
  // fVCO должна быть в диапазоне 200..1600 МГц
  // "выходные" частоты - не более 60 МГц
  // обе "выходные" частоты получаются делением fVCO на целый делитель
  // находим НОД (наибольший общий делитель) a_pll_clock и a_pll_clock2
  uint32_t aMAX = a_pll_clock2 > a_pll_clock ? a_pll_clock2 : a_pll_clock;
  uint32_t aNOD = a_pll_clock2 > a_pll_clock ? a_pll_clock : a_pll_clock2;
  for (;;) {
    aMAX %= aNOD;
    if ( 0 == aMAX ) {
      break;
    }
    uint32_t t = aMAX;
    aMAX = aNOD;
    aNOD = t;
  }
  // в переменной aNOD наибольший общий делитель
  // теперь считаем НОК (наименьшее общее кратное) a_pll_clock и a_pll_clock2
  uint64_t fVCO = (((uint64_t)a_pll_clock) * a_pll_clock2) / aNOD;
  // проверяем, влезает ли в диапазон для fVCO
  if ( fVCO < VCO_MIN_FREQ ) {
    // умножаем на целое число чтобы стало больше 250 МГц
    fVCO = ((VCO_MIN_FREQ + fVCO - 1ull) / fVCO ) * fVCO;
  } else {
    if ( fVCO > v_vco_max_freq ) {
      // получаем частоту, которая равна N * a_pll_clock и не больше 1.5 ГГц
      uint32_t t = 160u;
      while ( t >= 16u ) {
        fVCO = ((uint64_t)t) * a_hse_clock;
        if ( fVCO <= v_vco_max_freq ) {
          break;
        } else {
          --t;
        }
      }
      if ( t < 16 ) {
        return 1;
      }
    }
  }
  printf( "fVCO = %lu\n", fVCO );
  // fVCO содержит частоту на выходе PLL, теперь подбираем множители/делители
  // подбираем делитель для получения a_pll_clock
  int32_t AB = (int32_t)(fVCO / a_pll_clock);
  if ( AB > (8 * 64) ) {
    // шляпа, такого делителя не подобрать
    return 1;
  }
  // подбираем делители A и B, чтобы AB = A * B
  int32_t A = 1;
  int32_t B = 1;
  find_AB( AB, &A, &B );
  printf( "AB = %d, A = %d, B = %d, A * B = %d, fVCO / (A*B) = %lu\n", AB, A, B, A * B, fVCO / (uint32_t)(A * B) );
  fVCO = ((uint64_t)a_pll_clock) * (A * B);
  printf( "fVCO = %lu\n", fVCO );
  // теперь считаем множитель для PLL
  uint64_t REFDIV = 1u;
  uint64_t FBDIV = fVCO / a_hse_clock;
  uint64_t FRAC = 0;
  if ( fVCO == (FBDIV * a_hse_clock) ) {
    // целочисленный множитель
    if ( FBDIV < 16u ) {
      // множитель слишком мелкий, надо поделить входную частоту
      REFDIV = 8u;
      FBDIV *= 8u;
    } else {
      if ( FBDIV > 160u ) {
        // множитель слишком велик
        FBDIV = 160u;
      }
    }
  } else {
    // множитель с дробной частью
    if ( FBDIV < 20 ) {
      // множитель слишком мелкий, надо поделить входную частоту
      if ( FBDIV < 16u ) {
        // множитель слишком мелкий, надо поделить входную частоту
        REFDIV = 8u;
      } else {
        REFDIV = 4u;
      }
      FBDIV = (fVCO * REFDIV) / a_hse_clock;
    }
    if ( FBDIV > 160u ) {
      // множитель слишком велик
      FBDIV = 160u;
    }
    FRAC = fVCO*REFDIV*(1u<<24)/a_hse_clock - FBDIV*(1u<<24);
  }
  uint64_t fVCOp = ((a_hse_clock * ((FBDIV << 24) + FRAC)) / REFDIV) >> 24;
  printf( "REFDIV = %lu, FBDIV = %lu, FRAC = %lu, fVCO = %lu, PLL = %lu, A = %d, B = %d\n", REFDIV, FBDIV, FRAC, fVCOp, (fVCOp + ((uint32_t)(A * B / 2))) / (uint32_t)(A * B), A, B );
  // вторая частота
  // fVCO содержит частоту на выходе PLL, теперь подбираем множители/делители
  // подбираем делитель для получения a_pll_clock2
  AB = (int32_t)(fVCO / a_pll_clock2);
  int32_t AB_ = (int32_t)((fVCO + a_pll_clock2 - 1) / a_pll_clock2);
  while ( AB <= AB_ ) {
    find_AB( AB, &A, &B );
    if ( (fVCO / (uint32_t)(A * B)) <= a_pll_clock2 ) {
      break;
    }
    ++AB;
  }
  printf( "[2] AB = %d, A = %d, B = %d, A * B = %d, fVCO / (A*B) = %lu\n", AB, A, B, A * B, (fVCOp + ((uint32_t)(A * B / 2))) / (uint32_t)(A * B) );
  return 0;
}
