----------------------------------------------------------------------------
-- |
-- Module      :  Hardware.SiClock.Divider
-- Copyright   :  (c) Marc Fontaine 2017
-- License     :  BSD3
-- 
-- Maintainer  :  Marc.Fontaine@gmx.de
-- Stability   :  experimental
-- Portability :  GHC-only
--
-- This module contains utility functions for fractional PLL divers.
-- Most arduino Si5351 packages use dividers with a fixed large denominator,
-- which seens to be against the spirit of the Si5351 design.
-- I use continued fractions to compute the best fractional approximation.
-- (This may be an overkill but why not.)

module Hardware.SiClock.Divider
where
  
import Data.Word
import Data.Ratio
import qualified Data.List

-- | Compute the pvalues for a rational divider.
-- Any denominator is pemissible here.
-- The function uses the best approximation.

dividerToPVal :: Rational -> (Word32, Word32, Word32)
dividerToPVal d = (fromInteger p1,fromInteger p2,fromInteger c)
  where
    (a,b,c) = dividerToABC d
    fl = ((128*b) `div` c)
    p1 = 128 * a + fl - 512
    p2 = 128 * b - c * fl

-- | Approximate the a,b,c values of a divider.
dividerToABC :: Rational ->  (Integer, Integer, Integer)
dividerToABC divider = (a, b, c)
  where
    d = denominator divider
    n = numerator divider
    a = n `div` d
    rest = (n `mod` d) % d 
    fraction = last $ filter (\r -> (denominator r < 0x100000))
                 $ approximations rest
    b = numerator fraction
    c = denominator fraction

-- | Compute a list of approximations of a rational number.
approximations :: Rational -> [Rational]
approximations r
  = map (\l -> fromContinuedFraction (n,l)) $ Data.List.inits cf
  where    
    (n,cf) = toContinuedFraction r

-- | continued fraction stuff.
toContinuedFraction :: Rational -> (Integer,[Integer])
toContinuedFraction r = (n `div` d , cf (n `mod` d) d)
  where
    n = numerator r
    d = denominator r 
    cf 0 _ = []
    cf a b = (b `div` a) : cf (b `mod` a) a

-- | continued fraction stuff.
fromContinuedFraction :: (Integer,[Integer]) -> Rational
fromContinuedFraction (n,cf ) = fromInteger n + toRat cf
  where
    toRat :: [Integer] -> Rational
    toRat [] = 0 
    toRat (h:r) = 1 / ( fromInteger h + toRat r)