/**
 * \file
 *
 * \brief SD/MMC driver interface
 *
 * This is the interface to a SD/MMC driver.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All devices
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#ifndef SDMMC_SDMMC_H_INCLUDED
#define SDMMC_SDMMC_H_INCLUDED

#include <types.h>
#include <bitops.h>
#include <slist.h>
#include <buffer.h>
#include <chip/portmux.h>
#include <dmac/dma_controller.h>

/** \brief SD/MMC Card Identification register fields */
struct sdmmc_cid {
	uint8_t			manufacturer_id;
	uint16_t		application_id;
	char			name[6];
	uint8_t			revision;
	uint32_t		serial;
	uint16_t		manu_year;
	uint8_t			manu_month;
	uint8_t			checksum;
};

/** \brief SD/MMC Card-Specific Data register fields */
struct sdmmc_csd {
	uint8_t		mmc_version;
	uint16_t	command_classes;
	uint16_t	read_access_time_clks;
	uint32_t	read_access_time_ns;
	uint32_t	write_speed_factor;
	uint32_t	max_transfer_rate;
	uint32_t	read_block_length;
	uint32_t	write_block_length;
	uint32_t	capacity;
	uint32_t 	can_read_partial:1,
				can_read_misaligned:1,
				can_write_partial:1,
				can_write_misaligned:1;
};

/** \brief SD/MMC command structure */
struct sdmmc_command {
	/** Command index */
	uint32_t		opcode;
	/** Command argument */
	uint32_t		arg;
	/** Command response */
	uint32_t		resp[4];
	/** Flags indication different properties */
	unsigned long		flags;
#define SDMMC_RSP_PRESENT	(1 << 0)	/**< expect a response */
#define SDMMC_RSP_136		(1 << 1)	/**< 136-bit response */
#define SDMMC_RSP_CRC		(1 << 2)	/**< expect valid crc */
#define SDMMC_RSP_BUSY		(1 << 3)	/**< card may send busy */
#define SDMMC_RSP_MASK		(15)

/* Combinations of the above */
#define SDMMC_RSP_NONE	(0)
#define SDMMC_RSP_R1	(SDMMC_RSP_PRESENT | SDMMC_RSP_CRC)
#define SDMMC_RSP_R1B	(SDMMC_RSP_PRESENT | SDMMC_RSP_CRC | SDMMC_RSP_BUSY)
#define SDMMC_RSP_R2	(SDMMC_RSP_PRESENT | SDMMC_RSP_136 | SDMMC_RSP_CRC)
#define SDMMC_RSP_R3	(SDMMC_RSP_PRESENT)
#define SDMMC_RSP_R4	(SDMMC_RSP_PRESENT)
#define SDMMC_RSP_R5	(SDMMC_RSP_PRESENT | SDMMC_RSP_CRC)
#define SDMMC_RSP_R6	(SDMMC_RSP_PRESENT | SDMMC_RSP_CRC)
#define SDMMC_RSP_R7	(SDMMC_RSP_PRESENT | SDMMC_RSP_CRC)

#define SDMMC_CMD_OPD	(1 << 4)	/* open drain mode */

	/** Command execution status. Zero means success */
	int			status;
};

/**
 * \brief SD/MMC request flags
 */
enum sdmmc_req_flag {
	SDMMC_REQ_WRITE,	/**< Set means write, and unset means read */
	SDMMC_REQ_STOP,		/**< Send STOP CMD after data */
	SDMMC_REQ_INITSEQ,	/**< Request is special init sequence */
};

/**
 * \brief Asynchronous SD/MMC request
 */
struct sdmmc_request {
	/** \brief List of buffers associated with this request */
	struct slist		buf_list;
	/** \brief List node for use by the SD/MMC driver */
	struct slist_node       node;
	/** Command to execute */
	struct sdmmc_command	cmd;
	/** Slot this request is aimed for */
	struct sdmmc_slot	*slot;
	/** Number of blocks in a data transfer */
	uint32_t		blocks;
	/** Block size in a data transfer */
	uint32_t		block_size;
	/** \brief Flag bitfield. See sdmmc_req_flag */
	unsigned long		flags;
	/**
	 * \brief Callback called when request becomes active
	 * \param req The request
	 */
	void			(*req_started)(struct sdmmc_request *req);
	/**
 	 * \brief Callback called when request is done
	 * \param req The request
	 */
	void			(*req_done)(struct sdmmc_request *req);
	/**
	 * \brief Callback called when a list of buffers associated with
	 * \a req is done.
	 * \param req The request
	 * \param buf_list List of completed buffers
	 */
	void			(*buf_list_done)(struct sdmmc_request *req,
					struct slist *buf_list);
	/** \brief Arbitrary data for use by the client */
	void			*context;
	/** \brief Status indicating success or failure */
	int			status;
	/** \brief The number of bytes that were successfully transfered */
	size_t			bytes_xfered;
};

enum sdmmc_card_type {
	SDMMC_CARD_TYPE_SD,
	SDMMC_CARD_TYPE_SDHC,
	SDMMC_CARD_TYPE_MMC,
	SDMMC_CARD_TYPE_SDIO
};

/** \brief SD/MMC Card structure */
struct sdmmc_card {
	/** Type of card */
	enum sdmmc_card_type	type;
	/** Relative Card Address */
	uint32_t		rca;
	/** Card Identification */
	struct sdmmc_cid	cid;
	/** Card-Specific Data */
	struct sdmmc_csd	csd;
	/** Block size for card */
	uint32_t		block_size;
	/* Data read timeout (ns) */
	uint32_t		read_timeout_ns;
	/* Data read timeout (clks) */
	uint32_t		read_timeout_clks;
	/* Data write timeout (ns) */
	uint32_t		write_timeout_ns;
	/* Data write timeout (clks) */
	uint32_t		write_timeout_clks;
};

/** \brief Slot flags */
enum sdmmc_slot_flags {
	SDMMC_SLOT_CARD_DETECT,		/**< Indicates hw card detect */
	SDMMC_SLOT_CARD_PRESENT,	/**< Indicates card probed ok */
	/** Host Capacity Support. Means host support SDHC */
	SDMMC_SLOT_HCS,
	SDMMC_SLOT_HIGH_SPEED,		/**< High Speed enable */
	SDMMC_SLOT_PROBING,		/**< Slot is in probing state */
};

struct sdmmc_cd;

/** \brief SD/MMC Slot structure */
struct sdmmc_slot {
	/** Slot id identifying the slot to the underlying driver */
	int			id;
	/** Host driver */
	struct sdmmc_host	*host;
	/** Flags. See sdmmc_slot_flags */
	unsigned long		flags;
	/** \copybrief sdmmc_card */
	struct sdmmc_card	card;
	/** Host Operating Condition Register */
	uint32_t		bcr;
	/** Card Operating Condition Register */
	uint32_t		ocr;
	/** Current bus width */
	int			bus_width;
	/** Maximum bus width */
	int			bus_width_max;
	/** Maximum frequency */
	int32_t			f_max;
	/** \brief Callback for hw card detect events */
	void			(*notify_card_detect)(void *context);
	/** Arbitrary data for use by notify card detect */
	void			*context;
	/** Card detect */
	struct sdmmc_cd		*cd;
};

/** \brief SD/MMC Host structure */
struct sdmmc_host {
	/** Enable host */
	void			(*enable)(struct sdmmc_host *host);
	/** Power up function */
	void			(*power_up)(struct sdmmc_host *host,
						struct sdmmc_slot *slot);
	/** Power down function */
	void			(*power_down)(struct sdmmc_host *host,
						struct sdmmc_slot *slot);
	/** Set voltage */
	void			(*set_voltage)(struct sdmmc_host *host,
						uint32_t ocr);
	/** Update MCK rate, data timeout, bus width, etc. of a slot */
	void			(*set_bus_params)(struct sdmmc_host *host,
						struct sdmmc_slot *slot);
	/** Number of slots the host controls */
	int			slot_count;
	/** Get slot identified by id with range 0 < slot_count */
	struct sdmmc_slot	*(*get_slot)(struct sdmmc_host *host,
						int slot_id);
	/** Return true if WP switch is enabled */
	bool			(*wp_is_active)(struct sdmmc_host *host,
						struct sdmmc_slot *slot);
	/** Submit request function */
	void			(*submit_req)(struct sdmmc_host *host,
						struct sdmmc_request *req);
	/** Submit a list of buffers to an already-queued request */
	int			(*submit_buf_list)(struct sdmmc_host *host,
						struct sdmmc_request *req,
						struct slist *buf_list);
	/** Maximum frequency supported by host */
	unsigned int		f_max;
	/** Minimum frequency supporte by host */
	unsigned int		f_min;
	/** Current frequency running */
	unsigned int		f_cur;
	/** Operating Condition Register supported by host */
	uint32_t		ocr_avail;
};

/**
 * \brief Get the SD/MMC slot into which \a card is inserted
 */
static inline struct sdmmc_slot *sdmmc_card_get_slot(struct sdmmc_card *card)
{
	return container_of(card, struct sdmmc_slot, card);
}

/**
 * \brief Prepare SD/MMC request for data transfer
 *
 * \param slot Slot this request is intended for
 * \param req Request
 * \param lba Logical Block Address
 * \param nr_blocks Number of block to transfer
 * \param write Set to true for write request and to false for read request
 */
extern void sdmmc_req_prep_transfer(struct sdmmc_slot *slot,
		struct sdmmc_request *req,
		uint32_t lba, uint32_t nr_blocks, bool write);

/**
 * \brief Initialize SD/MMC request
 *
 * \param req Request
 */
static inline void sdmmc_req_init(struct sdmmc_request *req)
{
	req->flags = 0;
	slist_init(&req->buf_list);
	req->blocks = 0;
	req->block_size = 0;
}

/**
 * \brief Prepare command part of SD/MMC request
 *
 * \param req Request
 * \param opcode Command index
 * \param arg Argument
 * \param flags Flags
 */
static inline void sdmmc_req_prep_cmd(struct sdmmc_request *req,
		uint32_t opcode, uint32_t arg, unsigned long flags)
{
	req->cmd.opcode = opcode;
	req->cmd.arg = arg;
	req->cmd.flags = flags;
}

/**
 * \brief Prepare data part of SD/MMC request
 *
 * \param req Request
 * \param buf Data buffer
 */
static inline void sdmmc_req_prep_data(struct sdmmc_request *req,
		struct buffer *buf)
{
	slist_insert_tail(&req->buf_list, &buf->node);
	req->blocks = 1;
	req->block_size = buf->len;
}

/**
 * \brief Prepare callback part of SD/MMC request
 *
 * \param req Request
 * \param callback Callback when request is done
 * \param data Private data for callback
 */
static inline void sdmmc_req_prep_callback(struct sdmmc_request *req,
		void (*callback)(struct sdmmc_request *req),
		void *data)
{
	req->req_done = callback;
	req->context = data;
}

/**
 * \brief Update bus parameters of a slot
 *
 * Calling this function will trigger a recalculation of the data read
 * and write timeout values, and will also cause the host to be updated
 * according to the bus width, high-speed settings and maximum bus rate.
 *
 * \param slot A MMC/SD card slot
 */
static inline void sdmmc_slot_update(struct sdmmc_slot *slot)
{
	struct sdmmc_host	*host = slot->host;

	host->set_bus_params(host, slot);
}

/**
 * \brief Decode SD card CID (Card Identification)
 *
 * \param card Card structure to fill data into
 * \param resp Response from command requesting CID
 */
extern void sdmmc_decode_sd_cid(struct sdmmc_card *card, const be32_t *raw_cid);

/**
 * \brief Decode MMC card CID (Card Identification)
 *
 * \param card Card structure to fill data into
 * \param resp Response from command requesting CID
 *
 * \retval 0 Success
 * \retval <0 decode failure
 */
extern int sdmmc_decode_mmc_cid(struct sdmmc_card *card, const be32_t *raw_cid);

/**
 * \brief Decode SD card CSD (Card-Specific Data)
 *
 * \param card Card structure to fill data into
 * \param raw_csd Response from command requesting CSD
 *
 * \retval 0 Success
 * \retval <0 decode failure
 */
extern int sdmmc_decode_sd_csd(struct sdmmc_card *card, const be32_t *raw_csd);

/**
 * \brief Decode MMC card CSD (Card-Specific Data)
 *
 * \param card Card structure to fill data into
 * \param raw_csd Response from command requesting CSD
 *
 * \retval 0 Success
 * \retval <0 decode failure
 */
extern int sdmmc_decode_mmc_csd(struct sdmmc_card *card,
		const be32_t *raw_csd);

/**
 * \brief Set card type
 *
 * \param card Card to set the type on
 * \param type Card type
 */
static inline void sdmmc_card_set_type(struct sdmmc_card *card,
		enum sdmmc_card_type type)
{
	card->type = type;
	sdmmc_slot_update(sdmmc_card_get_slot(card));
}

/**
 * \brief Update data timeout calculations
 *
 * \param card Card to calculate timeout for
 * \param mmc_hz The current frequence running on the slot
 */
extern void sdmmc_card_update_timeouts(struct sdmmc_card *card,
		uint32_t mmc_hz);

/**
 * \brief Get decoded RCA (Relative Card Address)
 *
 * \param card Card in question
 *
 * \return Decoded RCA
 */
static inline uint32_t sdmmc_card_get_rca(struct sdmmc_card *card)
{
	return card->rca >> 16;
}

/**
 * \brief Convert block number to address used in communication
 *
 * \param card Card in question
 * \param lba Block address
 *
 * \return Address to be used in data read/write
 */
static inline uint32_t sdmmc_card_block2addr(struct sdmmc_card *card,
		uint32_t lba)
{
	if (card->type == SDMMC_CARD_TYPE_SDHC)
		return lba;

	return lba * card->block_size;
}

/**
 * \brief Submit SD/MMC request to slot
 *
 * \param slot Slot to submit request to
 * \param req Request to submit
 */
extern void sdmmc_slot_submit_req(struct sdmmc_slot *slot,
		struct sdmmc_request *req);

/**
 * \brief Submit a list of buffers for use with a request
 *
 * If successful, the host driver takes ownership of the buffers until
 * they are handed back through sdmmc_request::buf_list_done().
 *
 * \param host The MMC host handling the request
 * \param req The MMC request to receive the buffers
 * \param buf_list A list of buffers
 *
 * \retval STATUS_OK The buffers were submitted successfully
 * \retval -STATUS_FLUSHED The request was terminated before the buffers
 *	could be submitted.
 */
static inline int sdmmc_req_submit_buf_list(struct sdmmc_host *host,
		struct sdmmc_request *req, struct slist *buf_list)
{
	assert(host && req && buf_list);

	return host->submit_buf_list(host, req, buf_list);
}

/**
 * \brief Check if SD/MMC card is present in slot
 *
 * \param slot Slot to check
 *
 * \retval true Card is present
 * \retval false Card is not present
 */
static inline bool sdmmc_slot_is_card_present(struct sdmmc_slot *slot)
{
	return test_bit(SDMMC_SLOT_CARD_PRESENT, &slot->flags);
}

/**
 * \brief Check if SD/MMC card in slot is write protected
 *
 * If the card is not present, the result is undefined.
 *
 * \param slot Slot to check
 *
 * \retval true Card is write-protected
 * \retval false Card is not write-protected
 */
static inline bool sdmmc_slot_is_card_write_protected(struct sdmmc_slot *slot)
{
	return slot->host->wp_is_active(slot->host, slot);
}

/**
 * \brief Enable power to a slot
 *
 * When this function returns, any card inserted into \a slot will be
 * powered. This function also resets the bus width to 1 bit and clears
 * any high-speed state bits.
 */
extern void sdmmc_slot_power_up(struct sdmmc_slot *slot);

/**
 * \brief Disable power to a slot
 *
 * When this function returns, any card inserted into \a slot will not
 * be powered, if power switching is supported on the current board/host
 * combination. This function also sets the clock rate to "don't care"
 * so that any other slots sharing the same clock line may be able to
 * run faster.
 */
extern void sdmmc_slot_power_down(struct sdmmc_slot *slot);

/**
 * \brief Set possible supported voltage on slot
 *
 * \param slot Slot
 * \param ocr Card Operation Condition Register
 *
 * \return Possible Operation Condition Register
 */
extern uint32_t sdmmc_slot_set_voltage(struct sdmmc_slot *slot, uint32_t ocr);

/**
 * \brief Set the maximum MCK rate for a slot
 *
 * The bus frequency of the MCK line will be set to \a f_max Hz, rounded
 * down to the nearest supported value, or up to the lowest rate
 * supported by the host.
 *
 * \param slot A MMC/SD card slot
 * \param f_max The maximum MCK rate in Hz. A negative value means
 *	"don't care" (i.e. the slot is inactive). Zero means that the
 *	clock should stop completely.
 */
extern void sdmmc_slot_set_f_max(struct sdmmc_slot *slot, int32_t f_max);

/**
 * \brief Set the bus width of a slot
 *
 * \param slot A MMC/SD card slot
 * \param bits The new bus width in bits
 */
extern void sdmmc_slot_set_bus_width(struct sdmmc_slot *slot,
		unsigned int bits);

/**
 * \brief Notify slot about change in hw card detect
 *
 * Can be called to notify about change in card detection. Ususally called by
 * card detect handler.
 *
 * \param slot sdmmc_slot
 * \param value Zero for no detection and non-zero for detection
 */
extern void sdmmc_slot_notify_card_detect(struct sdmmc_slot *slot, int value);

/**
 * \brief Init slot structure
 *
 * \param slot sdmmc_slot to be initialized
 * \param host pointer to sdmmc_host structure
 * \param slot_id slot identificator
 */
extern void sdmmc_slot_init(struct sdmmc_slot *slot, struct sdmmc_host *host,
		int slot_id);

/**
 * \brief Notify probe about card detect change
 *
 * \param data Pointer to private data for probe
 */
extern void sdmmc_probe_notify_card_detect(void *data);

/**
 * \brief Init slot. Start probing for card.
 *
 * \param slot sdmmc_slot
 * \param event Callback for card detection
 * \param context Private data for event callback
 *
 * \return pointer to private probe data on success and 0 on failure
 */
extern void *sdmmc_probe_init(struct sdmmc_slot *slot,
		void (*event)(struct sdmmc_slot *slot, void *context),
		void *context);

/**
 * \brief Initialize card detection handler
 *
 * \param slot sdmmc_slot
 * \param pin gpio pin to use as card detect input
 */
extern struct sdmmc_cd *sdmmc_cd_init(struct sdmmc_slot *slot, gpio_pin_t pin);

/**
 * \brief Enable card detection handler
 *
 * \param cd Private data for card detction handler
 */
extern void sdmmc_cd_enable(struct sdmmc_cd *cd);

/**
 * \brief Enable host
 *
 * \param host Host structure
 */
static inline void sdmmc_host_enable(struct sdmmc_host *host)
{
	assert(host);
	assert(host->enable);

	host->enable(host);
}

#endif /* SDMMC_SDMMC_H_INCLUDED */

