{-# LANGUAGE TypeFamilies, ScopedTypeVariables, TypeOperators, OverloadedStrings, TemplateHaskell #-}
module Hardware.KansasLava.LCD.ST7066U
	( phy_Inst_4bit_LCD
	, init_LCD
	, mm_LCD_Inst
	-- * Instruction Set
	, LCDInstruction(..)
	, setDDAddr
	, writeChar
	-- * For testing only
	, phy_4bit_LCD
	) where

import Language.KansasLava as KL
import Data.Sized.Unsigned
import Data.Sized.Ix
import Data.Sized.Matrix as M
import Control.Applicative
import Data.Char
import qualified Data.Bits as B

import Hardware.KansasLava.Text as F

----------------------------------------------------------------------
-- Example usage
----------------------------------------------------------------------

-- example_lcd_driver = init_LCD $$ phy_Inst_4bit_LCD

-- The Sitronix ST7066U is compatible with Samsung X60069X, Samsung KS0066U,
-- Hitachi HD44780, and SMOS SED1278.

----------------------------------------------------------------------
-- Controller datastructure& bit formats 

----------------------------------------------------------------------
data LCDInstruction 
	= ClearDisplay
	| ReturnHome
	| EntryMode { moveRight :: Bool, displayShift :: Bool }
	| SetDisplay { displayOn :: Bool, cursorOn :: Bool, blinkingCursor :: Bool }
	| SetShift { displayShift :: Bool, rightShift :: Bool }
	| FunctionSet { eightBit :: Bool, twoLines :: Bool, fiveByEleven :: Bool }
	| SetCGAddr { cg_addr :: U6 }
	| SetDDAddr { dd_addr :: U7 }
	| ReadBusyAddr
	| ReadRam
	| WriteChar { char :: U8 }	
   deriving (Eq, Ord, Show)

$(repBitRep ''LCDInstruction 9)

setDDAddr :: Signal comb U7 -> Signal comb LCDInstruction 
setDDAddr = funMap (return . SetDDAddr)

writeChar :: Signal comb U8 -> Signal comb LCDInstruction 
writeChar = funMap (return . WriteChar)

-- 9-bit version; am okay with making it 10-bit
instance BitRep LCDInstruction where
	-- TODO: complete
    bitRep =
	--					LCD_RS & DB(7 downto 0)
	[ (ClearDisplay, 			"00000001") ] ++ 
	[ (ReturnHome, 				"0000001X") ] ++
	[ (EntryMode (bool a) 
		     (bool b),			"000001" & a & b) 
		| a <- every
		, b <- every
	] ++
	[ (SetDisplay (bool a) 
		      (bool b)
		      (bool c),			"00001" & a & b & c)
		| a <- every
		, b <- every
		, c <- every
	] ++ 
	[ (FunctionSet (bool a) 
		       (bool b)
		       (bool c),		"0010" & a & b & c & ("XX" :: BitPat X2))
		| a <- every
		, b <- every
		, c <- every
	] ++ 
	[ (SetCGAddr (fromIntegral addr), 	"001" & addr)
		| addr <- every :: [BitPat X6]
	] ++ -- 
	[ (SetDDAddr (fromIntegral addr), 	"01" & addr)
		| addr <- every :: [BitPat X7]
	] ++ -- 
	[ (WriteChar (fromIntegral c), 		"1" & c)
		| c <- every :: [BitPat X8]
	]

----------------------------------------------------------------------
-- Low level 4-bit physical driver
----------------------------------------------------------------------

-- The physical driver for the LCD patch
--  input: RS+nibble (5bits) and pause length in cycles
-- output: RS, SF_D[11:8], LCD_E
-- assuming LCD_RW is set always low
-- assuming 50Mhz clock

phy_4bit_LCD :: forall c sig . (Clock c, sig ~ Signal c)
	=> Patch (sig (Enabled (U5,U18)))	(sig (U1,U4,Bool))
		 (sig Ack)			()
phy_4bit_LCD ~(inp,_) = (toAck inAck,out)
   where

	(inAck,out) = runRTL $ do
		state   <- newReg (5 :: X6)
		pause   <- newReg (0 :: U18)
		counter <- newReg (0 :: U20)
		ack     <- newReg False
		rs      <- newReg (0 :: U1)
		sf_d    <- newReg (0 :: U4)
		lcd_e   <- newReg False 

		let wait = waitFor counter
		
		let firstWait = 750000


		CASE [ IF (reg state .==. 0 .&&. isEnabled inp) $ do
			-- waiting for input
			ack := pureS True
			let (cmd' :: sig U5,pause' :: sig U18) = unpack (enabledVal inp)
			let (sf_d':: sig U4,rs' :: sig U1) = unappendS cmd'
			pause := pause'
			rs    := rs'
			sf_d  := sf_d'
			state := 1
		     , IF (reg state .==. 1) $ do
			wait 2 $ state := 2
		     , IF (reg state .==. 2) $ do
		 	lcd_e := commentS "lcd_e := high" high
			wait 12 $ state := 3
		     , IF (reg state .==. 3) $ do
		 	lcd_e := commentS "lcd_e := low" low
			state := 4
			wait 1 $ state := 4
		     , IF (reg state .==. 4) $ do
			wait ((unsigned) (reg pause)) $ state := 0
		     , IF (reg state .==. 5) $ do
			wait firstWait $ state := 0
		     ]

		-- Ack for one cycle only
		CASE [ IF (reg ack .==. high) $ do
			ack  := pureS False
		     ]

--		DEBUG "state" state
{-
			  wait 750000 $ state := 1
		     , IF (reg state .==. 1) $ do
			  output := pureS (Just 
		     ]
-}
		return (commentS "ack" (var ack),pack (reg rs,reg sf_d,commentS "lcd_e" $ reg lcd_e))

waitFor :: (Rep b, Eq b, Num b) => Reg s c b -> Signal c b -> RTL s c () -> RTL s c ()
waitFor counter count nextOp = do
	CASE [ IF (reg counter ./=. count) $ do
			counter := reg counter + 1
             , OTHERWISE $ do
			counter := 0
			nextOp
	     ]

----------------------------------------------------------------------
-- Instruction-based driver(s)
----------------------------------------------------------------------

-- | 'phy_4bit_Inst' gives a instruction-level interface, in terms of the 4-bit interface.
phy_Inst_4bit_LCD :: forall c sig . (Clock c, sig ~ Signal c)
	=> Patch (sig (Enabled LCDInstruction))	(sig (U1,U4,Bool))
		 (sig Ack)			()
phy_Inst_4bit_LCD = toCmds $$ prependP bootCmds $$ phy_4bit_LCD
   where
	toCmds = mapP splitCmd $$ matrixToElementsP

	bootCmds :: Matrix X4 (U5,U18)
	bootCmds = matrix 
		[ (0x3, 205000)
		, (0x3, 5000)
		, (0x3, 2000)
		, (0x2, 2000)
		] 

splitCmd :: forall comb . Signal comb LCDInstruction -> Signal comb (Matrix X2 (U5,U18))
splitCmd cmd = pack $ matrix 
	[ pack ( high_op `appendS` mode
	       , smallGap
	       )
	, pack ( low_op `appendS` mode
	       , mux ((bitwise) cmd .<=. (0x03 :: Signal comb U9)) (bigGap,hugeGap)
	       )
	]
    where
	(op :: Signal comb U8, mode :: Signal comb U1) = unappendS ((bitwise) cmd :: Signal comb U9)
	(low_op :: Signal comb U4, high_op :: Signal comb U4) = unappendS op

	smallGap = 50		-- between nibbles
	bigGap   = 2000		-- between commands
	hugeGap	 = 100000	-- after clear display or return cursor home

----------------------------------------------------------------------
-- initialization instructions
----------------------------------------------------------------------

init_LCD :: forall c sig . (Clock c, sig ~ Signal c)
	=> Patch (sig (Enabled LCDInstruction))	(sig (Enabled LCDInstruction))
		 (sig Ack)			(sig Ack)
init_LCD = prependP initCmds
   where
	initCmds :: Matrix X4 LCDInstruction
	initCmds = matrix [ FunctionSet { eightBit = False, twoLines = True, fiveByEleven = False }
			  , EntryMode { moveRight = True, displayShift = False }
			  , SetDisplay { displayOn = True, cursorOn = False, blinkingCursor = False }
			  , ClearDisplay
	 		  ]

----------------------------------------------------------------------
-- Memory Mapped version
----------------------------------------------------------------------

mm_LCD_Inst :: forall c sig . (Clock c, sig ~ Signal c)
	=> Patch (sig (Enabled ((X2,X16),U8)))	(sig (Enabled LCDInstruction))
		 (sig Ack)			(sig Ack)

mm_LCD_Inst = mapP toInsts $$ matrixToElementsP
  where
	toInsts :: forall comb . Signal comb ((X2,X16),U8) -> Signal comb (Matrix X2 LCDInstruction)
	toInsts wr = pack (matrix [ setDDAddr dd_addr, writeChar ch ] :: Matrix X2 (Signal comb LCDInstruction))
	    where
		(addr,ch) = unpack wr
		(row,col) = unpack addr

		dd_addr :: Signal comb U7
		dd_addr = mux (row .==. 0) (0x40 + (unsigned)col,0x00 + (unsigned)col)