/*
  I2C_DMAC is a non-blocking I2C library that uses a SERCOM in 
	conjunction with the Direct Memory Access Controller (DMAC).
	
	Copyright (C) Martin Lindupp 2018
	
	V1.0.0 -- Initial release
	V1.1.0 -- Add Arduino MKR and SAMD51 support, plus multiple I2C instances
	V1.1.1 -- Replaced pinPeripheral() function with port register manipulation
	V1.1.2 -- Allow other classes to simultaneously use remaining DMAC channels
	V1.1.3 -- Fixed issue with consecutive calls to writeByte() overwriting data
	V1.1.4 -- Allow the DMAC to resume normal operation after an early NACK is received
	V1.1.5 -- Activate internal pull-up resistors and increase driver strength
	V1.1.6 -- Add SERCOM ALT (alternative) peripheral switch for the Metro M4
	V1.1.7 -- Arduino IDE library manager release
	V1.1.8 -- Code optimisation
	V1.1.9 -- Use default arguments for begin() member function
	V1.1.10 -- Remove I2C instance on writeBusy flag in ReadByte() and ReadBytes() functions
	V1.1.11 -- Arduino IDE library manager release
	
  The MIT License (MIT)
	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:
	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/

#ifndef _I2C_DMAC_h
#define _I2C_DMAC_h

#include <Arduino.h>
#include "DMAC.h"

enum { REG_ADDR_8BIT = 1, REG_ADDR_16BIT };										// Register address mode definitions, 8-bit address or 16-bit address

class I2C_DMAC {
	public:
		I2C_DMAC(SERCOM* s, uint8_t pinSDA, uint8_t pinSCL);			// Class constructor to initialise, SERCOM class, pins and member variables
		void begin(uint32_t baudrate = 100000, 										// Begin with 100kHz default clock rate and 8-bit register address mode
							 uint8_t regAddrMode = REG_ADDR_8BIT, 
							 EPioType ulPeripheral = PIO_SERCOM);
		void end();																								// Tear down and tidy up resources
		void setClock(uint32_t baudrate);													// Set the I2C bus clock speed to the specified baud rate
		void setWriteChannel(uint8_t channel);										// Set the DMAC write channel number (0 - 11), default 0
		void setReadChannel(uint8_t channel);											// Set the DMAC read channel number (0 - 11), default 1
		void setPriority(uint8_t priority);												// Set the priority level for both read and write DMAC channels (0 - 3), default 0
		void setRegAddrMode(uint8_t regAddrMode);									// Set the register address mode REG_ADDR_8BIT or REG_ADDR_16BIT
		void initWriteBytes(uint8_t devAddress, uint8_t* data, uint8_t count);			// Initialise DMAC to send data, (no register address)								
		void initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count);  // Initialise DMAC to send register address + data
		void initWriteByte(uint8_t devAddress, uint16_t regAddress, uint8_t data);  // Initialise DMAC to send register address + 1 data byte 
		void initWriteRegAddr(uint8_t devAddress, uint16_t regAddress);							// Initialise DMAC to send register address, (no data)
		void initReadBytes(uint8_t devAddress, uint8_t* data, uint8_t count);				// Initialise DMAC to receive data
		void initReadByte(uint8_t devAddress);																			// Initialise DMAC to receive 1 data byte
		uint8_t getData();																				// Retrieve the received data byte
		void write();																							// Transmit on I2C bus
		void read();																							// Receive on I2C bus
		void writeBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count);	// Transmit data to the I2C device's register address
		void writeByte(uint8_t devAddress, uint16_t regAddress, uint8_t data);		// Transmit 1 data byte to the I2C device's register address
		void writeRegAddr(uint8_t devAddress, uint16_t regAddress);								// Write the register address to the I2C device
		void readBytes(uint8_t devAddress, uint8_t* data, uint8_t count);					// Receive data from the I2C device
		void readByte(uint8_t devAddress);																				// Receive 1 data byte from the I2C device
		void readBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count);	// Receive data from the I2C device's register address
		void readByte(uint8_t devAddress, uint16_t regAddress);		// Receive a byte from the I2C device's register address
		void attachWriteCallback(voidFuncPtr callback);						// Attach a DMAC write callback function
		void attachReadCallback(voidFuncPtr callback);						// Attach a DMAC read callback function
		void attachDmacErrorCallback(voidFuncPtr callback);				// Attach a DMAC error callback function
		void attachSercomErrorCallback(voidFuncPtr callback);			// Attach a SERCOM error callback function
		void detachWriteCallback();																// Detach the DMAC write callback function
		void detachReadCallback();																// Detach the DMAC read callback function
		void detachDmacErrorCallback();														// Detach the DAMC error callback function
		void detachSercomErrorCallback();													// Detach the SERCOM error callback function
		static void DMAC_IrqHandler(); 														// DMAC interrupt handler function
		void SERCOM_IrqHandler();																	// SERCOM interrupt handler function
		
		volatile boolean writeBusy;																// Write busy flag - indicates write transfer in progress
		volatile boolean readBusy;																// Read busy flag - indicates read transfer in progress
		static volatile I2C_DMAC* i2cDmacPtrs[SERCOM_INST_NUM];		// Array of pointer to each instance (object) of this class
		static volatile uint8_t instanceCounter;									// Number of instances (objects) of this class
	protected:
	// Generic initialise write DMAC transfer function
		void initWriteBytes(uint8_t devAddress, uint16_t regAddress, uint8_t* data, uint8_t count, uint8_t readAddrLength);
	private:	
		enum { WRITE, READ };                 // I2C read and write bits definitions, WRITE: 0, READ: 1
		dmacdescriptor linked_descriptor __attribute__ ((aligned (16)));			// DMAC linked descriptor		
		
		Sercom* sercom;										// Pointer to the selected SERCOMx peripheral
		uint8_t pinSDA;										// The selected SDA pin
		uint8_t pinSCL;										// The selected SCL pin
		uint8_t devAddress;								// The I2C device address
		uint8_t regAddress[2];						// The I2C device's register address sent MSB first, LSB last
		uint8_t data;											// Used for single byte data transfer
		uint8_t writeCount;								// The number of data bytes to transmit
		uint8_t readCount;								// The number of data bytes to receive
		uint8_t regAddrLength;						// The length of the register address, 0: data only, 1: 8-bit, 2: 16-bit
		uint8_t regAddrMode;							// The current register address mode: REG_ADDR_8BIT or REG_ADDR_16BIT
		uint8_t dmacPriority;							// The DMAC read and write priority level
		uint8_t dmacWriteTrigger;					// The DMAC write peripheral trigger source
		uint8_t dmacReadTrigger;					// The DMAC read peripheral trigger source
		uint8_t genericClockId;						// The generic clock ID used to select the sercom peripheral to be clocked
		IRQn_Type nvicId;									// The Nested Vector Interrupt (NVIC) interrupt type for the SERCOMx peripheral
		
		typedef void (*voidFuncPtr)(void);											// Alias for function pointer declarations
		volatile voidFuncPtr writeCallback;											// Write callback function pointer
		volatile voidFuncPtr readCallback;											// Read callback function pointer
		volatile voidFuncPtr errorDmacCallback;									// DMAC error callback function pointer
		volatile voidFuncPtr errorSercomCallback;								// SERCOM error callback function pointer
		volatile uint8_t dmacWriteChannel;											// DMAC write channel number (0 to 11), default 0
		volatile uint8_t dmacReadChannel;												// DMAC read channel number (0 to 11), default 1
};

#if WIRE_INTERFACES_COUNT > 0
  extern I2C_DMAC I2C;								// Indicate that the I2C object is externally instantiated
#endif
#if WIRE_INTERFACES_COUNT > 1
  extern I2C_DMAC I2C1;								// Indicate that the I2C1 object is externally instantiated
#endif
#if WIRE_INTERFACES_COUNT > 2
  extern I2C_DMAC I2C2;								// Indicate that the I2C2 object is externally instantiated
#endif
#if WIRE_INTERFACES_COUNT > 3
  extern I2C_DMAC I2C3;								// Indicate that the I2C3 object is externally instantiated
#endif
#if WIRE_INTERFACES_COUNT > 4
  extern I2C_DMAC I2C4;								// Indicate that the I2C4 object is externally instantiated
#endif
#if WIRE_INTERFACES_COUNT > 5
  extern I2C_DMAC I2C5;								// Indicate that the I2C5 object is externally instantiated
#endif

#endif
