/**
 * \file
 *
 * \brief SD/MMC block device
 *
 * This is the block device interfaces against the 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.
 */
#include <assert.h>
#include <bitops.h>
#include <debug.h>
#include <malloc.h>
#include <status-codes.h>
#include <string.h>
#include <workqueue.h>
#include <sdmmc/sdmmc.h>
#include <block/device.h>
#include <block/sdmmc.h>

struct sdmmc_bdev {
	struct block_device	bdev;
	struct sdmmc_slot	*slot;
	void			(*event)(struct block_device *blkdev,
			void *context);
	void			*context;
};

struct sdmmc_breq {
	struct block_request	block_req;
	struct sdmmc_request	sdmmc_req;
};

static inline struct sdmmc_breq *sdmmc_breq_of(struct block_request *req)
{
	return container_of(req, struct sdmmc_breq, block_req);
}

static inline struct sdmmc_slot *sdmmc_slot_of(struct block_device *bdev)
{
	struct sdmmc_bdev *sbdev = container_of(bdev, struct sdmmc_bdev, bdev);
	return sbdev->slot;
}

static void sdmmc_blkdev_submit_req(struct block_device *bdev,
		struct block_request *req)
{
	struct sdmmc_slot	*slot = sdmmc_slot_of(bdev);
	struct sdmmc_breq	*breq = sdmmc_breq_of(req);

	if (!slist_is_empty(&breq->block_req.buf_list))
		slist_move_to_tail(&breq->sdmmc_req.buf_list,
				&breq->block_req.buf_list);
	sdmmc_slot_submit_req(slot, &breq->sdmmc_req);
}

static int sdmmc_blkdev_submit_buf_list(struct block_device *bdev,
		struct block_request *req, struct slist *buf_list)
{
	struct sdmmc_slot	*slot = sdmmc_slot_of(bdev);
	struct sdmmc_breq	*breq = sdmmc_breq_of(req);

	return sdmmc_req_submit_buf_list(slot->host,
			&breq->sdmmc_req, buf_list);
}

static void sdmmc_blkdev_req_started(struct sdmmc_request *req)
{
	struct sdmmc_breq	*breq = req->context;

	if (breq->block_req.req_started)
		breq->block_req.req_started(breq->block_req.bdev,
				&breq->block_req);
}

static void sdmmc_blkdev_req_done(struct sdmmc_request *req)
{
	struct sdmmc_breq	*breq = req->context;

	breq->block_req.status = req->status;
	breq->block_req.bytes_xfered = req->bytes_xfered;

	if (!slist_is_empty(&req->buf_list))
		slist_move_to_tail(&breq->block_req.buf_list, &req->buf_list);

	breq->block_req.req_done(breq->block_req.bdev, &breq->block_req);
}

static void sdmmc_blkdev_buf_list_done(struct sdmmc_request *req,
		struct slist *buf_list)
{
	struct sdmmc_breq	*breq = req->context;

	if (breq->block_req.buf_list_done)
		breq->block_req.buf_list_done(breq->block_req.bdev,
				&breq->block_req, buf_list);
	else
		slist_move_to_tail(&breq->block_req.buf_list, buf_list);
}

static void sdmmc_blkdev_prepare_req(struct block_device *bdev,
		struct block_request *req,
		uint32_t lba, uint32_t nr_blocks,
		enum block_operation operation)
{
	struct sdmmc_breq	*breq = sdmmc_breq_of(req);
	struct sdmmc_slot	*slot = sdmmc_slot_of(bdev);
	bool			write;

	slist_init(&breq->block_req.buf_list);
	breq->block_req.status = -STATUS_IN_PROGRESS;
	breq->block_req.bytes_xfered = 0;
	breq->block_req.req_submit = sdmmc_blkdev_submit_req;
	breq->block_req.req_submit_buf_list = sdmmc_blkdev_submit_buf_list;

	if (operation == BLK_OP_WRITE)
		write = true;
	else
		write = false;

	sdmmc_req_prep_transfer(slot, &breq->sdmmc_req, lba, nr_blocks, write);
	slist_init(&breq->sdmmc_req.buf_list);
	breq->sdmmc_req.req_started = sdmmc_blkdev_req_started;
	breq->sdmmc_req.req_done = sdmmc_blkdev_req_done;
	breq->sdmmc_req.buf_list_done = sdmmc_blkdev_buf_list_done;
	breq->sdmmc_req.context = breq;
}

static struct block_request *sdmmc_blkdev_alloc_req(struct block_device *bdev)
{
	struct sdmmc_breq	*req;

	req = malloc(sizeof(struct sdmmc_breq));
	if (unlikely(!req))
		return NULL;
	memset(req, 0, sizeof(struct sdmmc_breq));

	req->block_req.req_submit = sdmmc_blkdev_submit_req;

	return &req->block_req;
}

static void sdmmc_blkdev_free_req(struct block_device *bdev,
		struct block_request *req)
{
	struct sdmmc_breq	*breq = sdmmc_breq_of(req);

	free(breq);
}

static void sdmmc_blkdev_event(struct sdmmc_slot *slot, void *context)
{
	struct sdmmc_bdev	*bdev = context;

	if (sdmmc_slot_is_card_present(slot)) {
		if (sdmmc_slot_is_card_write_protected(slot))
			clear_bit(BDEV_WRITEABLE, &bdev->bdev.flags);
		else
			set_bit(BDEV_WRITEABLE, &bdev->bdev.flags);

		set_bit(BDEV_PRESENT, &bdev->bdev.flags);
		bdev->bdev.block_size = slot->card.block_size;
		bdev->bdev.nr_blocks = slot->card.csd.capacity;
	}
	else
		clear_bit(BDEV_PRESENT, &bdev->bdev.flags);
	bdev->event(&bdev->bdev, bdev->context);
}

static uint32_t sdmmc_blkdev_get_dev_id(struct block_device *bdev)
{
	struct sdmmc_slot	*slot = sdmmc_slot_of(bdev);

	return slot->card.cid.serial;
}

/**
 * \brief Initialize a SD/MMC block device
 *
 * \param slot SD/MMC slot structure
 *
 * \return A new block device, or NULL if insufficient memory is
 * available.
 */
struct block_device *sdmmc_blkdev_init_new(struct sdmmc_slot *slot,
		void (*event)(struct block_device *blkdev, void *context),
		void *context)
{
	struct sdmmc_bdev *bdev;

	bdev = malloc(sizeof(struct sdmmc_bdev));
	if (!bdev)
		return NULL;
	memset(bdev, 0, sizeof(struct sdmmc_bdev));

	bdev->bdev.prepare_req = sdmmc_blkdev_prepare_req;
	bdev->bdev.alloc_req = sdmmc_blkdev_alloc_req;
	bdev->bdev.free_req = sdmmc_blkdev_free_req;
	bdev->bdev.get_dev_id = sdmmc_blkdev_get_dev_id;

	bdev->slot = slot;
	bdev->event = event;
	bdev->context = context;

	sdmmc_probe_init(slot, sdmmc_blkdev_event, bdev);

	return &bdev->bdev;
}

