{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving #-}

module System.Hardware.BusPirate.SPI
  ( -- * Types
    SpiM
  , spiMode
    -- * Bus operations
  , bulkTransfer
  , setCS
    -- * Configuration
  , PeripheralConfig(..)
  , setConfig
  , SpiSpeed(..)
  , setSpeed
  ) where

import Control.Applicative
import Control.Monad (replicateM)
import Control.Monad.IO.Class
import Control.Monad.Trans.Either
import Data.Word

import qualified Data.ByteString as BS
import Data.ByteString (ByteString)

import System.Hardware.BusPirate.Core

newtype SpiM a = SpiM (BusPirateM a)
               deriving (Functor, Applicative, Monad, MonadIO)

err :: String -> SpiM a
err = SpiM . BPM . left

-- | Enter I2C mode and run given action
spiMode :: SpiM a -> BusPirateM a
spiMode (SpiM m) = commandExpect 0x1 "SPI1" >> m

-- | Set chip select
setCS :: Bool -> SpiM ()
setCS True = SpiM $ command 0x21
setCS False = SpiM $ command 0x20

-- | Perform a read/write transaction of up to 16 bytes
bulkTransfer :: ByteString -> SpiM ByteString
bulkTransfer d
  | BS.null d = return BS.empty
  | BS.length d > 16 = SpiM $ BPM $ left "Too many bytes"
  | otherwise = SpiM $ do
    command $ fromIntegral $ 0x10 + BS.length d - 1
    put d
    reply <- replicateM (BS.length d) $ getByte
    return $ BS.pack reply

-- | Set Bus Pirate peripheral configuration bits
setConfig :: PeripheralConfig -> SpiM ()
setConfig config = SpiM $ setPeripherals config

data SpiSpeed
    = Spi30KHz
    | Spi125KHz
    | Spi250KHz
    | Spi1MHz
    | Spi2MHz
    | Spi2_6MHz
    | Spi4MHz
    | Spi8MHz
    deriving (Show, Ord, Eq, Enum, Bounded)

-- | Set bus speed
setSpeed :: SpiSpeed -> SpiM ()
setSpeed speed = SpiM $ command $ 0x6 + fromIntegral (fromEnum speed)

-- | Write-then-read
--
-- This is very likely broken as the command structure itself appears
-- to be horribly broken, requiring a conditional read of a status byte.
writeRead :: Bool -> ByteString -> Word16 -> SpiM ByteString
writeRead toggleCS write readLen
  | readLen >= 4096 = err "Read length too long"
  | BS.length write >= 4096 = err "Write length too long"
  | otherwise = SpiM $ do
    putByte $ if toggleCS then 0x4 else 0x5
    putWord16 $ fromIntegral $ BS.length write
    putWord16 readLen
    status <- getByte
    case status of
      0 -> BPM $ left "Data too long"
      1 -> do put write
              get (fromIntegral readLen)