//! I2C driver for my amlib i2c interface implementation.
//!
//! See `gateware/i2c.py` for register information

use crate::{read_reg, write_reg};
use core::arch::asm;
/// TODO repr(C) a register bank, and add instances
// Using the blocking API because the peripheral requires fairly tight timing to operate
// correctly, and I don't feel like writing the gateware to resolve that.
use embedded_hal::blocking::i2c::{Read, SevenBitAddress, Write};

const CR: u32 = 0;
const SR: u32 = 1;
const DWR: u32 = 2;
const DRR: u32 = 3;

#[derive(Clone, Copy, Debug)]
pub enum Error {
    /// Device is busy for some reason
    Busy,
    /// I2C bus returned a NACK
    Nack,
}

pub struct AmlibI2c {
    base_addr: u32,
}

use core::fmt::Write as _;

impl AmlibI2c {
    pub fn new(base_addr: u32) -> Self {
        AmlibI2c { base_addr }
    }

    fn wait_while_busy(&mut self) {
        unsafe {
            while read_reg::<u8>(self.base_addr + SR) & 1 != 0 {
                asm!("nop");
            }
        }
    }

    fn is_nack(&self) -> bool {
        unsafe { read_reg::<u8>(self.base_addr + SR) & 0x02 == 0 }
    }
}

impl Write<SevenBitAddress> for AmlibI2c {
    // TODO errors
    type Error = Error;

    fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> {
        unsafe {
            if (read_reg::<u8>(self.base_addr + SR) & 1) != 0 {
                return Err(Error::Busy);
            }

            // START
            write_reg(self.base_addr + CR, 0x01u8);

            self.wait_while_busy();

            // Send address byte (R/~W = 0)
            write_reg(self.base_addr + DWR, address << 1);
            write_reg(self.base_addr + CR, 0x04u8);
            self.wait_while_busy();

            // Check NACK
            if self.is_nack() {
                return Err(Error::Nack);
            }

            // Write data
            for byte in bytes {
                // Write byte
                write_reg(self.base_addr + DWR, *byte);
                // Send byte
                write_reg(self.base_addr + CR, 0x04u8);
                self.wait_while_busy();

                if self.is_nack() {
                    return Err(Error::Nack);
                }
            }

            // STOP
            write_reg(self.base_addr + CR, 0x02u8);

            self.wait_while_busy();
            Ok(())
        }
    }
}

impl Read<SevenBitAddress> for AmlibI2c {
    // TODO errors
    type Error = Error;

    fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> {
        unsafe {
            if (read_reg::<u8>(self.base_addr + SR) & 1) != 0 {
                return Err(Error::Busy);
            }

            // Set read ACK to 1. Should probably ideally be done in an init() function
            // but it doesn't really matter
            write_reg(self.base_addr + CR, 0x30u8);

            // START
            write_reg(self.base_addr + CR, 0x01u8);

            self.wait_while_busy();

            // Send address byte (R/~W = 1)
            write_reg(self.base_addr + DWR, (address << 1) + 1);
            write_reg(self.base_addr + CR, 0x04u8);
            self.wait_while_busy();
            if self.is_nack() {
                return Err(Error::Nack);
            }

            for byte in buffer {
                // Start reading in a byte
                write_reg(self.base_addr + CR, 0x08u8);
                self.wait_while_busy();
                // Value is available once busy is clear
                *byte = read_reg::<u8>(self.base_addr + DRR);
            }

            // STOP
            write_reg(self.base_addr + CR, 0x02u8);

            Ok(())
        }
    }
}

// I don't need this for MCP4726
//impl Transactional for AmlibI2c {
//    type Error = Error;
//
//    fn exec<'a>(&mut self, address: u8, operations: &mut [Operation<'a>])
//            -> Result<(), Self::Error> {
//        Ok(())
//    }
//}
