//go:build rp2040 || rp2350

package machine

import (
	"device/rp"
	"errors"
	"internal/itoa"
)

// I2C on the RP2040/RP2350
var (
	I2C0  = &_I2C0
	_I2C0 = I2C{
		Bus: rp.I2C0,
	}
	I2C1  = &_I2C1
	_I2C1 = I2C{
		Bus: rp.I2C1,
	}
)

// The I2C target implementation is based on the C implementation from
// here: https://github.com/vmilea/pico_i2c_slave

// Features: Taken from datasheet.
// Default controller mode, with target mode available (not simultaneously).
// Default target address of RP2040: 0x055
// Supports 10-bit addressing in controller mode
// 16-element transmit buffer
// 16-element receive buffer
// Can be driven from DMA
// Can generate interrupts
// Fast mode plus max transfer speed (1000kb/s)

// GPIO config
// Each controller must connect its clock SCL and data SDA to one pair of GPIOs.
// The I2C standard requires that drivers drivea signal low, or when not driven the signal will be pulled high.
// This applies to SCL and SDA. The GPIO pads should be configured for:
//  Pull-up enabled
//  Slew rate limited
//  Schmitt trigger enabled
// Note: There should also be external pull-ups on the board as the internal pad pull-ups may not be strong enough to pull upexternal circuits.

// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
	Frequency uint32
	// SDA/SCL Serial Data and clock pins. Refer to datasheet to see
	// which pins match the desired bus.
	SDA, SCL Pin
	Mode     I2CMode
}

type I2C struct {
	Bus          *rp.I2C0_Type
	mode         I2CMode
	txInProgress bool
}

var (
	errInvalidI2CBaudrate  = errors.New("i2c: invalid baudrate")
	errInvalidTgtAddr      = errors.New("i2c: invalid target address: not in 0..0x80 or is reserved")
	errI2CGeneric          = errors.New("i2c: generic error")
	errI2CDisable          = errors.New("i2c: peripheral timeout in disable")
	errInvalidI2CSDA       = errors.New("i2c: invalid SDA pin")
	errInvalidI2CSCL       = errors.New("i2c: invalid SCL pin")
	errI2CAlreadyListening = errors.New("i2c: already listening")
	errI2CWrongMode        = errors.New("i2c: wrong mode")
	errI2CUnderflow        = errors.New("i2c: underflow")
)

// Tx performs a write and then a read transfer placing the result in
// in r.
//
// Passing a nil value for w or r skips the transfer corresponding to write
// or read, respectively.
//
//	i2c.Tx(addr, nil, r)
//
// Performs only a read transfer.
//
//	i2c.Tx(addr, w, nil)
//
// Performs only a write transfer.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
	if i2c.mode != I2CModeController {
		return errI2CWrongMode
	}
	return i2c.tx(uint8(addr), w, r)
}

// Listen starts listening for I2C requests sent to specified address
//
// addr is the address to listen to
func (i2c *I2C) Listen(addr uint16) error {
	if i2c.mode != I2CModeTarget {
		return errI2CWrongMode
	}

	return i2c.listen(uint8(addr))
}

// Configure initializes i2c peripheral and configures I2C config's pins passed.
// Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040:
//
//	SDA: 0, 4, 8, 12, 16, 20
//	SCL: 1, 5, 9, 13, 17, 21
//
// Same as above for I2C1 bus:
//
//	SDA: 2, 6, 10, 14, 18, 26
//	SCL: 3, 7, 11, 15, 19, 27
func (i2c *I2C) Configure(config I2CConfig) error {
	const defaultBaud uint32 = 100_000 // 100kHz standard mode
	if config.SCL == 0 && config.SDA == 0 {
		// If config pins are zero valued or clock pin is invalid then we set default values.
		switch i2c.Bus {
		case rp.I2C0:
			config.SCL = I2C0_SCL_PIN
			config.SDA = I2C0_SDA_PIN
		case rp.I2C1:
			config.SCL = I2C1_SCL_PIN
			config.SDA = I2C1_SDA_PIN
		}
	}
	var okSCL, okSDA bool
	switch i2c.Bus {
	case rp.I2C0:
		okSCL = (config.SCL+3)%4 == 0
		okSDA = (config.SDA+4)%4 == 0
	case rp.I2C1:
		okSCL = (config.SCL+1)%4 == 0
		okSDA = (config.SDA+2)%4 == 0
	}

	switch {
	case !okSCL:
		return errInvalidI2CSCL
	case !okSDA:
		return errInvalidI2CSDA
	}

	if config.Frequency == 0 {
		config.Frequency = defaultBaud
	}
	config.SDA.Configure(PinConfig{PinI2C})
	config.SCL.Configure(PinConfig{PinI2C})
	return i2c.init(config)
}

// SetBaudRate sets the I2C frequency. It has the side effect of also
// enabling the I2C hardware if disabled beforehand.
//
//go:inline
func (i2c *I2C) SetBaudRate(br uint32) error {

	if br == 0 {
		return errInvalidI2CBaudrate
	}

	// I2C is synchronous design that runs from clk_sys
	freqin := CPUFrequency()

	// TODO there are some subtleties to I2C timing which we are completely ignoring here
	period := (freqin + br/2) / br
	lcnt := period * 3 / 5 // oof this one hurts
	hcnt := period - lcnt
	// Check for out-of-range divisors:
	if hcnt > rp.I2C0_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_Msk || hcnt < 8 || lcnt > rp.I2C0_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_Msk || lcnt < 8 {
		return errInvalidI2CBaudrate
	}

	// Per I2C-bus specification a device in standard or fast mode must
	// internally provide a hold time of at least 300ns for the SDA signal to
	// bridge the undefined region of the falling edge of SCL. A smaller hold
	// time of 120ns is used for fast mode plus.

	// sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns)
	// Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint.
	// Add 1 to avoid division truncation.
	sdaTxHoldCnt := ((freqin * 3) / 10000000) + 1
	if br >= 1_000_000 {
		// sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns)
		// Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint.
		// Add 1 to avoid division truncation.
		sdaTxHoldCnt = ((freqin * 3) / 25000000) + 1
	}

	if sdaTxHoldCnt > lcnt-2 {
		return errInvalidI2CBaudrate
	}
	err := i2c.disable()
	if err != nil {
		return err
	}
	// Always use "fast" mode (<= 400 kHz, works fine for standard mode too)

	i2c.Bus.IC_CON.ReplaceBits(rp.I2C0_IC_CON_SPEED_FAST<<rp.I2C0_IC_CON_SPEED_Pos, rp.I2C0_IC_CON_SPEED_Msk, 0)
	i2c.Bus.IC_FS_SCL_HCNT.Set(hcnt)
	i2c.Bus.IC_FS_SCL_LCNT.Set(lcnt)

	i2c.Bus.IC_FS_SPKLEN.Set(u32max(1, lcnt/16))

	i2c.Bus.IC_SDA_HOLD.ReplaceBits(sdaTxHoldCnt<<rp.I2C0_IC_SDA_HOLD_IC_SDA_TX_HOLD_Pos, rp.I2C0_IC_SDA_HOLD_IC_SDA_TX_HOLD_Msk, 0)
	i2c.enable()
	return nil
}

//go:inline
func (i2c *I2C) enable() {
	i2c.Bus.IC_ENABLE.ReplaceBits(rp.I2C0_IC_ENABLE_ENABLE<<rp.I2C0_IC_ENABLE_ENABLE_Pos, rp.I2C0_IC_ENABLE_ENABLE_Msk, 0)
}

// Implemented as per 4.3.10.3. Disabling DW_apb_i2c section.
//
//go:inline
func (i2c *I2C) disable() error {
	const timeout_us = 4_000
	deadline := ticks() + timeout_us
	i2c.Bus.IC_ENABLE.Set(0)
	for i2c.Bus.IC_ENABLE_STATUS.Get()&1 != 0 {
		if ticks() > deadline {
			return errI2CDisable
		}
	}
	return nil
}

//go:inline
func (i2c *I2C) init(config I2CConfig) error {
	i2c.reset()
	if err := i2c.disable(); err != nil {
		return err
	}

	i2c.mode = config.Mode

	// Configure as fast-mode with RepStart support, 7-bit addresses
	mode := uint32(rp.I2C0_IC_CON_SPEED_FAST<<rp.I2C0_IC_CON_SPEED_Pos) |
		rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status
	if config.Mode == I2CModeController {
		mode |= rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE
	}
	i2c.Bus.IC_CON.Set(mode)

	// Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0.
	if config.Mode == I2CModeController {
		i2c.Bus.IC_TX_TL.Set(0)
		i2c.Bus.IC_RX_TL.Set(0)
	}

	// Always enable the DREQ signalling -- harmless if DMA isn't listening
	i2c.Bus.IC_DMA_CR.Set(rp.I2C0_IC_DMA_CR_TDMAE | rp.I2C0_IC_DMA_CR_RDMAE)
	return i2c.SetBaudRate(config.Frequency)
}

// reset sets I2C register RESET bits in the reset peripheral and then clears them.
//
//go:inline
func (i2c *I2C) reset() {
	resetVal := i2c.deinit()
	unresetBlockWait(resetVal)
}

// deinit sets reset bit for I2C. Must call reset to reenable I2C after deinit.
//
//go:inline
func (i2c *I2C) deinit() (resetVal uint32) {
	switch {
	case i2c.Bus == rp.I2C0:
		resetVal = rp.RESETS_RESET_I2C0
	case i2c.Bus == rp.I2C1:
		resetVal = rp.RESETS_RESET_I2C1
	}
	// Perform I2C reset.
	resetBlock(resetVal)

	return resetVal
}

// tx performs blocking write followed by read to I2C bus.
func (i2c *I2C) tx(addr uint8, tx, rx []byte) (err error) {
	if addr >= 0x80 || isReservedI2CAddr(addr) {
		return errInvalidTgtAddr
	}
	txlen := len(tx)
	rxlen := len(rx)
	// Quick return if possible.
	if txlen == 0 && rxlen == 0 {
		return nil
	}

	// Base 4ms for small register pokes.
	// Add per-byte budget. 100us/byte is conservative at 400kHz and still ok at 100kHz for modest sizes.
	timeout_us := uint64(4_000) + uint64(txlen+rxlen)*100
	// Cap so it doesn't go insane:
	timeout_us = min(timeout_us, 500_000)

	deadline := ticks() + timeout_us

	err = i2c.disable()
	if err != nil {
		return err
	}
	i2c.Bus.IC_TAR.Set(uint32(addr))
	i2c.enable()
	abort := false
	var abortReason i2cAbortError
	txStop := rxlen == 0
	for txCtr := 0; txCtr < txlen; txCtr++ {
		if abort {
			break
		}
		first := txCtr == 0
		last := txCtr == txlen-1 && rxlen == 0
		i2c.Bus.IC_DATA_CMD.Set(
			(boolToBit(first) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) |
				(boolToBit(last && txStop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) |
				uint32(tx[txCtr]))

		// Wait until the transmission of the address/data from the internal
		// shift register has completed. For this to function correctly, the
		// TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag
		// was set in i2c_init.

		// IC_RAW_INTR_STAT_TX_EMPTY: This bit is set to 1 when the transmit buffer is at or below
		// the threshold value set in the IC_TX_TL register and the
		// transmission of the address/data from the internal shift
		// register for the most recently popped command is
		// completed. It is automatically cleared by hardware when
		// the buffer level goes above the threshold. When
		// IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held
		// in reset. There the TX FIFO looks like it has no data within
		// it, so this bit is set to 1, provided there is activity in the
		// controller or target state machines. When there is no longer
		// any activity, then with ic_en=0, this bit is set to 0.
		for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) {
			if ticks() > deadline {
				return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
			}

			before := ticks()
			gosched()
			deadline += ticks() - before
		}

		abortReason = i2c.getAbortReason()
		if abortReason != 0 {
			i2c.clearAbortReason()
			abort = true
		}
		if abort || last {
			// If the transaction was aborted or if it completed
			// successfully wait until the STOP condition has occurred.

			// TODO Could there be an abort while waiting for the STOP
			// condition here? If so, additional code would be needed here
			// to take care of the abort.
			for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) {
				if ticks() > deadline {
					if abort {
						return abortReason
					}
					return errI2CWriteTimeout
				}

				before := ticks()
				gosched()
				deadline += ticks() - before
			}
			i2c.Bus.IC_CLR_STOP_DET.Get()
		}
	}

	// Midway check for abort. Related issue https://github.com/tinygo-org/tinygo/issues/3671.
	// The root cause for an abort after writing registers was "tx data no ack" (abort code=8).
	// If the abort code was not registered then the whole peripheral would remain in disabled state forever.
	abortReason = i2c.getAbortReason()
	if abortReason != 0 {
		i2c.clearAbortReason()
		abort = true
	}

	rxStart := txlen == 0
	if rxlen > 0 && !abort {
		for rxCtr := 0; rxCtr < rxlen; rxCtr++ {
			first := rxCtr == 0
			last := rxCtr == rxlen-1
			for i2c.writeAvailable() == 0 {
				before := ticks()
				gosched()
				deadline += ticks() - before
			}
			i2c.Bus.IC_DATA_CMD.Set(
				boolToBit(first && rxStart)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
					boolToBit(last)<<rp.I2C0_IC_DATA_CMD_STOP_Pos |
					rp.I2C0_IC_DATA_CMD_CMD) // -> 1 for read

			for !abort && i2c.readAvailable() == 0 {
				abortReason = i2c.getAbortReason()
				if abortReason != 0 {
					i2c.clearAbortReason()
					abort = true
				}
				if ticks() > deadline {
					return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
				}

				before := ticks()
				gosched()
				deadline += ticks() - before
			}
			if abort {
				break
			}
			rx[rxCtr] = uint8(i2c.Bus.IC_DATA_CMD.Get())
		}
	}
	// From Pico SDK: A lot of things could have just happened due to the ingenious and
	// creative design of I2C. Try to figure things out.
	if abort {
		switch {
		case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0:
			// No reported errors - seems to happen if there is nothing connected to the bus.
			// Address byte not acknowledged
			err = errI2CGeneric
		case abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0:
			// Address acknowledged, some data not acknowledged
			fallthrough
		default:
			err = abortReason
		}
	}
	return err
}

// listen sets up for async handling of requests on the I2C bus.
func (i2c *I2C) listen(addr uint8) error {
	if addr >= 0x80 || isReservedI2CAddr(addr) {
		return errInvalidTgtAddr
	}

	err := i2c.disable()
	if err != nil {
		return err
	}

	i2c.Bus.IC_SAR.Set(uint32(addr))

	i2c.enable()

	return nil
}

func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
	rxPtr := 0
	for {
		stat := i2c.Bus.IC_RAW_INTR_STAT.Get()

		if stat&rp.I2C0_IC_INTR_MASK_M_RX_FULL != 0 {
			b := uint8(i2c.Bus.IC_DATA_CMD.Get())
			if rxPtr < len(buf) {
				buf[rxPtr] = b
				rxPtr++
			}
		}

		// Stop
		if stat&rp.I2C0_IC_INTR_MASK_M_STOP_DET != 0 {
			if rxPtr > 0 {
				return I2CReceive, rxPtr, nil
			}

			i2c.Bus.IC_CLR_STOP_DET.Get() // clear
			return I2CFinish, 0, nil
		}

		// Start or restart - ignore start, return on restart
		if stat&rp.I2C0_IC_INTR_MASK_M_START_DET != 0 {
			i2c.Bus.IC_CLR_START_DET.Get() // clear restart

			// Restart
			if rxPtr > 0 {
				return I2CReceive, rxPtr, nil
			}
		}

		// Read request - leave flag set until we start to reply.
		if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ != 0 {
			return I2CRequest, 0, nil
		}

		gosched()
	}
}

func (i2c *I2C) Reply(buf []byte) error {
	txPtr := 0

	stat := i2c.Bus.IC_RAW_INTR_STAT.Get()

	if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ == 0 {
		return errI2CWrongMode
	}
	i2c.Bus.IC_CLR_RD_REQ.Get() // clear restart

	// Clear any dangling TX abort
	if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
		i2c.Bus.IC_CLR_TX_ABRT.Get()
	}

	for txPtr < len(buf) {
		if i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() != 0 {
			i2c.Bus.SetIC_DATA_CMD_DAT(uint32(buf[txPtr]))
			txPtr++
			// The DW_apb_i2c flushes/resets/empties the
			// TX_FIFO and RX_FIFO whenever there is a transmit abort
			// caused by any of the events tracked by the
			// IC_TX_ABRT_SOURCE register.
			// In other words, it's safe to block until TX FIFO is
			// EMPTY--it will empty from being transmitted or on error.
			for i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() == 0 {
			}
		}

		// This Tx abort is a normal case - we're sending more
		// data than controller wants to receive
		if i2c.Bus.GetIC_RAW_INTR_STAT_TX_ABRT() != 0 {
			i2c.Bus.GetIC_CLR_TX_ABRT_CLR_TX_ABRT()
			return nil
		}

		gosched()
	}

	return nil
}

// writeAvailable determines non-blocking write space available
//
//go:inline
func (i2c *I2C) writeAvailable() uint32 {
	return rp.I2C0_IC_COMP_PARAM_1_TX_BUFFER_DEPTH_Pos - i2c.Bus.IC_TXFLR.Get()
}

// readAvailable determines number of bytes received
//
//go:inline
func (i2c *I2C) readAvailable() uint32 {
	return i2c.Bus.IC_RXFLR.Get()
}

// Equivalent to IC_CLR_TX_ABRT.Get() (side effect clears ABORT_REASON)
//
//go:inline
func (i2c *I2C) clearAbortReason() {
	// Note clearing the abort flag also clears the reason, and
	// this instance of flag is clear-on-read! Note also the
	// IC_CLR_TX_ABRT register always reads as 0.
	i2c.Bus.IC_CLR_TX_ABRT.Get()
}

// getAbortReason reads IC_TX_ABRT_SOURCE register.
//
//go:inline
func (i2c *I2C) getAbortReason() i2cAbortError {
	return i2cAbortError(i2c.Bus.IC_TX_ABRT_SOURCE.Get())
}

// returns true if RAW_INTR_STAT bits in mask are all set. performs:
//
//	RAW_INTR_STAT & mask == mask
//
//go:inline
func (i2c *I2C) interrupted(mask uint32) bool {
	reg := i2c.Bus.IC_RAW_INTR_STAT.Get()
	return reg&mask == mask
}

type i2cAbortError uint32

func (b i2cAbortError) Error() string {
	return "i2c abort, reason " + itoa.Uitoa(uint(b))
}

func (b i2cAbortError) Reasons() (reasons []string) {
	if b == 0 {
		return nil
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0 {
		reasons = append(reasons, "7-bit address no ack")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR1_NOACK != 0 {
		reasons = append(reasons, "10-bit address first byte no ack")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR2_NOACK != 0 {
		reasons = append(reasons, "10-bit address second byte no ack")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0 {
		reasons = append(reasons, "tx data no ack")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_NOACK != 0 {
		reasons = append(reasons, "general call no ack")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_READ != 0 {
		reasons = append(reasons, "general call read")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_ACKDET != 0 {
		reasons = append(reasons, "high speed ack detect")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_ACKDET != 0 {
		reasons = append(reasons, "start byte ack detect")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_NORSTRT != 0 {
		reasons = append(reasons, "high speed no restart")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_NORSTRT != 0 {
		reasons = append(reasons, "start byte no restart")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10B_RD_NORSTRT != 0 {
		reasons = append(reasons, "10-bit read no restart")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_MASTER_DIS != 0 {
		reasons = append(reasons, "master disabled")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ARB_LOST != 0 {
		reasons = append(reasons, "arbitration lost")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVFLUSH_TXFIFO != 0 {
		reasons = append(reasons, "slave flush tx fifo")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLV_ARBLOST != 0 {
		reasons = append(reasons, "slave arbitration lost")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVRD_INTX != 0 {
		reasons = append(reasons, "slave read while inactive")
	}
	if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_USER_ABRT != 0 {
		reasons = append(reasons, "user abort")
	}
	return reasons
}
