----------------------------------------------------------------------------
-- |
-- Module      :  Hardware.SiClock
-- Copyright   :  (c) Marc Fontaine 2017
-- License     :  BSD3
-- 
-- Maintainer  :  Marc.Fontaine@gmx.de
-- Stability   :  experimental
-- Portability :  GHC-only
--
-- This is the main API.

{-# Language BinaryLiterals #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RankNTypes #-}
module Hardware.SiClock
(
  module Hardware.SiClock
 ,module I2C
)

where
import Control.Monad
import Control.Monad.Trans.Reader
import Control.Monad.IO.Class
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Word
import Data.Bits
import Data.Ratio
import Text.Read (readMaybe)
import GHC.Stack
import System.Environment (lookupEnv)
  
import Hardware.SiClock.I2C as I2C
  (SynthT,runI2CWith,writeByteData,writeI2CBlockData,dumpRegisters)
import Hardware.SiClock.Divider

type Frequency = Rational
type Divider = Rational

data Config = Config {
   _I2CDevice :: FilePath
  ,_I2CAddress  :: Word8
  ,_XtalFrequency   :: Frequency
  ,_maxPLLFrequency :: Frequency
  } deriving (Show,Eq)

-- | The defaultConfig if no environment variables are set.
defaultConfig :: Config
defaultConfig = Config {
   _I2CDevice       = "/dev/i2c-7"
  ,_I2CAddress      = 0x60
  ,_XtalFrequency   =  27000000
  ,_maxPLLFrequency = 700000000
  }                    

-- | Check that defaultConfigEnv matches your hardware before you use it.
-- Do not run any SiPLL code on a wrong i2c-bus,
-- i.e. an internal I2C bus of your PC.
-- (it might confuse and or wreck) your hardware.
-- You can overwrite config values with the following ENV variables:
-- SI_CLOCK_I2C_DEVICE
-- SI_CLOCK_I2C_ADDRESS
-- SI_CLOCK_XTAL_FREQUENCY
-- SI_CLOCK_MAX_PLL_FREQUENCY

defaultConfigEnv :: HasCallStack => IO Config
defaultConfigEnv = do
  device <- lookupEnv _SI_CLOCK_I2C_DEVICE
  when (device == Nothing)
    $ error "Check documentation! For example setenv SI_CLOCK_I2C_DEVICE=/dev/i2c-7." 
  let (Just _I2CDevice) = device
  addr <- lookupEnv _SI_CLOCK_I2C_ADDRESS
  _I2CAddress <- case addr of
     Just str -> readSafe str
     Nothing    -> return $ _I2CAddress defaultConfig
  _XtalFrequency   <- readFractional _SI_CLOCK_XTAL_FREQUENCY _XtalFrequency
  _maxPLLFrequency <- readFractional _SI_CLOCK_MAX_PLL_FREQUENCY _maxPLLFrequency
  return $ Config {..}
  where
    readFractional :: String -> (Config -> Rational) -> IO Rational
    readFractional envName field = do
      e <- lookupEnv envName
      case e of
        Nothing -> return $ field defaultConfig
        Just str -> do
          r <- readSafe str
          return (r %1)
    readSafe :: Read a => String -> IO a
    readSafe str = maybe (error $ show ("no parse",str)) 
                         return (readMaybe str)
-- | Quick test for the I2C connection (with default config).
testIO :: IO ()
testIO = runSynth dumpRegisters

type Synth a = forall m. MonadIO m => SynthT Config m a

-- | Run the Synth monad with the config from defaultConfigEnv.
-- | .i.e. reading Env
runSynth :: HasCallStack => SynthT Config IO a -> IO a
runSynth action = do
  config <- defaultConfigEnv
  runSynthWith config action

-- | Run the Synth monad with a custom configuration.
runSynthWith :: HasCallStack => Config -> SynthT Config IO a -> IO a
runSynthWith conf action
  = runI2CWith
      (_I2CDevice conf)
      (_I2CAddress conf)
      (\device -> runReaderT action (conf,device))

askXtalFrequency :: Synth Frequency
askXtalFrequency = asks ( _XtalFrequency . fst)

askMaxPLLFrequency :: Synth Frequency
askMaxPLLFrequency = asks ( _maxPLLFrequency . fst)

-- | An IC has PLL_A and PLL_B.
data PLL = PLL_A | PLL_B deriving (Show,Eq)

-- | An IC has up to 8 clocks. (CLK_0..CLK_7).
data CLK = CLK_0 | CLK_1 | CLK_2 | CLK_3 | CLK_4 | CLK_5 | CLK_6 | CLK_7
  deriving (Show,Eq,Ord,Enum)

-- | Reset (both?) PLLs
pllReset :: Synth ()
pllReset = writeByteData _SI_PLL_RESET 0xA0

-- | Turn on CLK_0 output.
clk0_On :: Synth ()
clk0_On = writeByteData _SI_CLK0_CONTROL 0x4f

-- | Turn off CLK_0 output.
clk0_Off :: Synth ()
clk0_Off = writeByteData _SI_CLK0_CONTROL 0x00

data DividerPair
 = DividerPair {_pllDivider :: Divider,_clkDivider :: Divider}
 deriving Show
          
-- | Set PLL and Clock dividers for a frequency.
setDividers :: PLL -> CLK -> Frequency -> Synth DividerPair
setDividers pll clk f = do
  dividers <- defaultDividers f
  setPLLDivider pll   $ _pllDivider dividers
  setCLKDivider clk 0 $ _clkDivider dividers
  return dividers

-- | Compute a pair of good default pll and clk dividers.
-- (clk divider is an integer)
defaultDividers :: Frequency -> Synth DividerPair
defaultDividers f = do
  pllFrequency  <- askMaxPLLFrequency
  xtalFrequency <- askXtalFrequency
  let 
    clkDivider = (floor (pllFrequency / f ) % 1)
    pllDivider = f * clkDivider / xtalFrequency
  return $ DividerPair {_pllDivider =pllDivider, _clkDivider =clkDivider}
  
-- | Set a PLL fractional divider
setPLLDivider :: PLL -> Divider -> Synth ()
setPLLDivider p divider
  = setDividerRaw p $ toDividerConf 0 divider

-- | Short for setPLLDivider PLL_A
setPLLDivider_A :: Divider -> Synth ()
setPLLDivider_A = setPLLDivider PLL_A

-- | Short for setPLLDivider PLL_B
setPLLDivider_B :: Divider -> Synth ()
setPLLDivider_B = setPLLDivider PLL_B

-- | Setup a Clock divider.
-- The rfield is passed as a plain Word8.
-- (ToDo high level API for rfields).
setCLKDivider :: CLK -> Word8 -> Divider -> Synth ()
setCLKDivider clk rfield divider
  = setDividerRaw clk $ toDividerConf rfield divider


-- | Bits in the clock control registers.
data CLK_Control_bits
  = CLK_on
  | CLK_off
  | CLK_fractional
  | CLK_integer
  | CLK_multiPLLA
  | CLK_multiPLLB
  | CLK_inverted
  | CLK_XTAL
  | CLK_CLKin
  | CLK_multi
  | CLK_DRV2
  | CLK_DRV4
  | CLK_DRV6
  | CLK_DRV8
  deriving (Show,Eq)

setCLKControl :: CLK -> [CLK_Control_bits] -> Synth ()
setCLKControl clk bits
  = setCLKControlRaw clk $ controlBitsToWord8 bits

setCLKControlRaw :: CLK -> Word8 -> Synth ()
setCLKControlRaw clk bits = writeByteData addr bits
  where
    addr = case clk of
      CLK_0 -> 16
      CLK_1 -> 17
      CLK_2 -> 18
      CLK_3 -> 19
      CLK_4 -> 20
      CLK_5 -> 21     
      CLK_6 -> 22
      CLK_7 -> 23

controlBitsToWord8 :: [CLK_Control_bits]  -> Word8
controlBitsToWord8 bits
  = foldl (\b f ->b .|. bitOf f) 0 bits
  where
    bitOf f = case f of
      CLK_on           -> 0x00
      CLK_off          -> 0x80
      CLK_fractional   -> 0x00
      CLK_integer      -> 0x40
      CLK_multiPLLA    -> 0x00
      CLK_multiPLLB    -> 0x20
      CLK_inverted     -> 0x10
      CLK_XTAL         -> 0x00
      CLK_CLKin        -> 0x08
      CLK_multi        -> 0x0c
      CLK_DRV2         -> 0x00
      CLK_DRV4         -> 0x01
      CLK_DRV6         -> 0x02
      CLK_DRV8         -> 0x03
      
-- | A DividerConf is basically  the bytestring that configures a fractional divider.
newtype DividerConf = DividerConf {unDividerConf :: ByteString}
  deriving (Show,Eq)

-- | Mangle a Divider and a rval into a DividerConf.
-- This can be used to pre-compute all the math and to get the bits
-- that define a divider.
toDividerConf :: Word8 -> Divider -> DividerConf
toDividerConf rval divider  = DividerConf $ BS.pack [
     pt  8 p3
  ,  pt  0 p3
  ,((pt 16 p1) .&. 3) .|. rval
  ,  pt  8 p1
  ,  pt  0 p1
  ,((pt 12 p3) .&. 0xf0) .|. ((pt 16 p2) .&. 0x0f)  
  ,  pt  8 p2
  ,  pt  0 p2
  ]
  where
    (p1,p2,p3) = dividerToPVal divider
    pt :: Int -> Word32 -> Word8
    pt s w = fromIntegral $ ((w `rotateR` s) .&. 0xff)

-- | Setup some fractional divider with a pre-computed config.
-- Using a pre-computed config might be faster or more convenient.
setDividerRaw :: DividerAddr hw => hw -> DividerConf -> Synth ()
setDividerRaw hw (DividerConf bs)
  = writeI2CBlockData (toDividerAddr hw) bs

-- | Get address of the fractional divider.
class DividerAddr a where
  toDividerAddr :: a -> Word8

-- | Address of a PLL divider.
instance DividerAddr PLL where
  toDividerAddr PLL_A = _SI_SYNTH_PLL_A
  toDividerAddr PLL_B = _SI_SYNTH_PLL_B

-- | Address of a Clock divider.
instance DividerAddr CLK where
  toDividerAddr clk = case clk of
    CLK_0 -> 42
    CLK_1 -> 50
    CLK_2 -> 58
    CLK_3 -> 66
    CLK_4 -> 74
    CLK_5 -> 82     
    CLK_6 -> undefined
    CLK_7 -> undefined

-- | Generic Address of fractional divider.
instance DividerAddr Word8 where
  toDividerAddr = id

_SI_CLK0_CONTROL :: Word8
_SI_CLK0_CONTROL = 16
_SI_CLK1_CONTROL :: Word8
_SI_CLK1_CONTROL = 17
_SI_CLK2_CONTROL :: Word8
_SI_CLK2_CONTROL = 18
_SI_SYNTH_PLL_A  :: Word8
_SI_SYNTH_PLL_A  = 26
_SI_SYNTH_PLL_B  :: Word8
_SI_SYNTH_PLL_B  = 34
_SI_PLL_RESET    :: Word8
_SI_PLL_RESET    = 177

_SI_CLOCK_I2C_DEVICE :: String
_SI_CLOCK_I2C_DEVICE = "SI_CLOCK_I2C_DEVICE"
_SI_CLOCK_I2C_ADDRESS :: String
_SI_CLOCK_I2C_ADDRESS = "SI_CLOCK_I2C_ADDRESS"
_SI_CLOCK_XTAL_FREQUENCY :: String
_SI_CLOCK_XTAL_FREQUENCY = "SI_CLOCK_XTAL_FREQUENCY"
_SI_CLOCK_MAX_PLL_FREQUENCY :: String
_SI_CLOCK_MAX_PLL_FREQUENCY ="SI_CLOCK_MAX_PLL_FREQUENCY"