{-# LINE 1 "src/System/Hardware/PiLcd/I2c.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LINE 2 "src/System/Hardware/PiLcd/I2c.hsc" #-}

{-|
Module      : System.Hardware.PiLcd.I2c
Description : Access peripherals via an I²C bus
Copyright   : © Patrick Pelletier, 2017
License     : BSD3
Maintainer  : code@funwithsoftware.org
Portability : Linux

You can use this module to communicate with
<https://en.wikipedia.org/wiki/I%C2%B2C I2C> peripherals on Linux.
-}

module System.Hardware.PiLcd.I2c
  ( I2cHandle
  , Segment (..)
  , i2cOpen
  , i2cTransaction
  , i2cReadReg
  , i2cWriteReg
  , i2cClose
  ) where

import Control.Applicative
import Control.Monad
import Data.Word
import Foreign.C.Error
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import System.Posix.IO
import System.Posix.Types


{-# LINE 38 "src/System/Hardware/PiLcd/I2c.hsc" #-}

{-# LINE 39 "src/System/Hardware/PiLcd/I2c.hsc" #-}

foreign import ccall "sys/ioctl.h ioctl" c_ioctl ::
  CInt -> CULong -> Ptr I2cRdwrIoctlData -> IO CInt

-- | Represents an I²C bus.
type I2cHandle = Fd

-- | Represents an I²C read or write, as part of a larger transaction.
data Segment = Read Int      -- ^ number of bytes to read
             | Write [Word8] -- ^ bytes to write

data I2cRdwrIoctlData =
  I2cRdwrIoctlData
  { i2c_msgs :: Ptr I2cMsg
  , i2c_nmsgs :: Word32
  }

instance Storable I2cRdwrIoctlData where
  sizeOf _ = (16)
{-# LINE 58 "src/System/Hardware/PiLcd/I2c.hsc" #-}
  alignment x = alignment (i2c_nmsgs x)
  peek x = I2cRdwrIoctlData
           <$> (\hsc_ptr -> peekByteOff hsc_ptr 0)  x
{-# LINE 61 "src/System/Hardware/PiLcd/I2c.hsc" #-}
           <*> (\hsc_ptr -> peekByteOff hsc_ptr 8) x
{-# LINE 62 "src/System/Hardware/PiLcd/I2c.hsc" #-}
  poke p x = do
    (\hsc_ptr -> pokeByteOff hsc_ptr 0)  p (i2c_msgs x)
{-# LINE 64 "src/System/Hardware/PiLcd/I2c.hsc" #-}
    (\hsc_ptr -> pokeByteOff hsc_ptr 8) p (i2c_nmsgs x)
{-# LINE 65 "src/System/Hardware/PiLcd/I2c.hsc" #-}

data I2cMsg =
  I2cMsg
  { i2c_addr  :: Word16
  , i2c_flags :: Word16
  , i2c_len   :: Word16
  , i2c_buf   :: Ptr Word8
  }

instance Storable I2cMsg where
  sizeOf _ = (16)
{-# LINE 76 "src/System/Hardware/PiLcd/I2c.hsc" #-}
  alignment x = alignment (i2c_buf x)
  peek x = I2cMsg
           <$> (\hsc_ptr -> peekByteOff hsc_ptr 0)  x
{-# LINE 79 "src/System/Hardware/PiLcd/I2c.hsc" #-}
           <*> (\hsc_ptr -> peekByteOff hsc_ptr 2) x
{-# LINE 80 "src/System/Hardware/PiLcd/I2c.hsc" #-}
           <*> (\hsc_ptr -> peekByteOff hsc_ptr 4)   x
{-# LINE 81 "src/System/Hardware/PiLcd/I2c.hsc" #-}
           <*> (\hsc_ptr -> peekByteOff hsc_ptr 8)   x
{-# LINE 82 "src/System/Hardware/PiLcd/I2c.hsc" #-}
  poke p x = do
    (\hsc_ptr -> pokeByteOff hsc_ptr 0)  p (i2c_addr x)
{-# LINE 84 "src/System/Hardware/PiLcd/I2c.hsc" #-}
    (\hsc_ptr -> pokeByteOff hsc_ptr 2) p (i2c_flags x)
{-# LINE 85 "src/System/Hardware/PiLcd/I2c.hsc" #-}
    (\hsc_ptr -> pokeByteOff hsc_ptr 4)   p (i2c_len x)
{-# LINE 86 "src/System/Hardware/PiLcd/I2c.hsc" #-}
    (\hsc_ptr -> pokeByteOff hsc_ptr 8)   p (i2c_buf x)
{-# LINE 87 "src/System/Hardware/PiLcd/I2c.hsc" #-}

flagRd :: Word16
flagRd = 1
{-# LINE 90 "src/System/Hardware/PiLcd/I2c.hsc" #-}

i2cRdwr :: CULong
i2cRdwr = 1799
{-# LINE 93 "src/System/Hardware/PiLcd/I2c.hsc" #-}

segLength :: Segment -> Int
segLength (Read n) = n
segLength (Write xs) = length xs

fillOutSegments :: Int -> [Segment] -> Ptr I2cMsg -> Ptr Word8 -> IO ()
fillOutSegments _ [] _ _ = return ()
fillOutSegments addr (seg:segs) segPtr bytePtr = do
  let len = segLength seg
      msg = I2cMsg
            { i2c_addr = fromIntegral addr
            , i2c_flags = case seg of
                            (Read _) -> flagRd
                            (Write _) -> 0
            , i2c_len = fromIntegral len
            , i2c_buf = bytePtr
            }
  poke segPtr msg
  case seg of
    (Read _) -> return ()
    (Write x) -> pokeArray bytePtr x
  fillOutSegments addr segs (advancePtr segPtr 1) (advancePtr bytePtr len)

collectResults :: [Segment] -> Ptr Word8 -> IO [[Word8]]
collectResults [] _ = return []
collectResults ((Read n):segs) bytePtr = do
  xs <- peekArray n bytePtr
  rest <- collectResults segs (advancePtr bytePtr n)
  return $ xs : rest
collectResults ((Write xs):segs) bytePtr =
  collectResults segs $ advancePtr bytePtr $ length xs

-- | Performs an
-- <https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/i2c/dev-interface I2C_RDWR>
-- transaction.
i2cTransaction :: I2cHandle    -- ^ handle to an I²C bus
               -> Int          -- ^ address of device on I²C bus
               -> [Segment]    -- ^ list of read/write segments to perform
               -> IO [[Word8]] -- ^ list of bytes returned for each read segment
i2cTransaction (Fd fd) addr segs = do
  let len = sum $ map segLength segs
      nSegs = length segs
  allocaArray len $ \bytePtr -> do
    allocaArray nSegs $ \segPtr -> do
      fillOutSegments addr segs segPtr bytePtr
      alloca $ \ioctlPtr -> do
        poke ioctlPtr $ I2cRdwrIoctlData segPtr $ fromIntegral nSegs
        r <- c_ioctl fd i2cRdwr ioctlPtr
        when (r < 0) $ throwErrno "i2cTransaction"
        collectResults segs bytePtr

-- | Open a handle to an I²C bus.
i2cOpen :: Int -- ^ bus number
        -> IO I2cHandle
i2cOpen bus = do
  let name = "/dev/i2c-" ++ show bus
  openFd name ReadWrite Nothing defaultFileFlags

-- | Close a handle to an I²C bus.
i2cClose :: I2cHandle -> IO ()
i2cClose = closeFd

-- | Writes the register number, and then reads the specified
-- number of bytes.
i2cReadReg :: I2cHandle  -- ^ handle to an I²C bus
           -> Int        -- ^ address of device on the I²C bus
           -> Word8      -- ^ register number
           -> Int        -- ^ number of bytes to read
           -> IO [Word8]
i2cReadReg h addr reg len = do
  [r] <- i2cTransaction h addr [Write [reg], Read len]
  return r

-- | Writes the register number, and then writes the specified bytes.
i2cWriteReg :: I2cHandle  -- ^ handle to an I²C bus
            -> Int        -- ^ address of device on the I²C bus
            -> Word8      -- ^ register number
            -> [Word8]    -- ^ bytes to write
            -> IO ()
i2cWriteReg h addr reg d = do
  i2cTransaction h addr [Write (reg:d)]
  return ()