/*
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 *
 * This file is part of PortaPack.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "rssi_dma.hpp"

#include <cstdint>
#include <cstddef>
#include <array>

#include "hal.h"
#include "gpdma.hpp"

using namespace lpc43xx;

#include "portapack_dma.hpp"
#include "portapack_adc.hpp"

namespace rf {
namespace rssi {
namespace dma {

/* TODO: SO MUCH REPEATED CODE IN touch_dma.cpp!!! */

static constexpr auto& gpdma_channel = gpdma::channels[portapack::adc1_gpdma_channel_number];

constexpr uint32_t gpdma_ahb_master_peripheral = 1;
constexpr uint32_t gpdma_ahb_master_memory = 0;
constexpr uint32_t gpdma_ahb_master_lli_fetch = 0;

constexpr uint32_t gpdma_peripheral = 0xe;
constexpr uint32_t gpdma_src_peripheral = gpdma_peripheral;
constexpr uint32_t gpdma_dest_peripheral = gpdma_peripheral;

constexpr gpdma::channel::LLIPointer lli_pointer(const void* lli) {
	return {
		.lm = gpdma_ahb_master_lli_fetch,
		.r = 0,
		.lli = reinterpret_cast<uint32_t>(lli),
	};
}

constexpr gpdma::channel::Control control(const size_t number_of_transfers) {
	return {
		.transfersize = number_of_transfers,
		.sbsize = 0,  /* Burst size: 1 transfer */
		.dbsize = 0,  /* Burst size: 1 transfer */
		.swidth = 0,  /* Source transfer width: byte (8 bits) */
		.dwidth = 2,  /* Destination transfer width: word (32 bits) */
		.s = gpdma_ahb_master_peripheral,
		.d = gpdma_ahb_master_memory,
		.si = 0,
		.di = 1,
		.prot1 = 0,
		.prot2 = 0,
		.prot3 = 0,
		.i = 1,
	};
}

constexpr gpdma::channel::Config config() {
	return {
		.e = 0,
		.srcperipheral = gpdma_src_peripheral,
		.destperipheral = gpdma_dest_peripheral,
		.flowcntrl = gpdma::FlowControl::PeripheralToMemory_DMAControl,
		.ie = 1,
		.itc = 1,
		.l = 0,
		.a = 0,
		.h = 0,
	};
}

struct buffers_config_t {
	size_t count;
	size_t items_per_buffer;
};

static buffers_config_t			buffers_config;

static sample_t				*samples	{ nullptr };
static gpdma::channel::LLI	*lli		{ nullptr };

static Semaphore semaphore;
static volatile const gpdma::channel::LLI* next_lli = nullptr;

static void transfer_complete() {
	next_lli = gpdma_channel.next_lli();
	chSemSignalI(&semaphore);
}

static void dma_error() {
	disable();
}

void init() {
	chSemInit(&semaphore, 0);
	gpdma_channel.set_handlers(transfer_complete, dma_error);

	// LPC_GPDMA->SYNC |= (1 << gpdma_peripheral);
}

void allocate(size_t buffer_count, size_t items_per_buffer) {
	buffers_config = {
		.count = buffer_count,
		.items_per_buffer = items_per_buffer,
	};

	const auto peripheral = reinterpret_cast<uint32_t>(&LPC_ADC1->DR[portapack::adc1_rssi_input]) + 1;
	const auto control_value = control(gpdma::buffer_words(buffers_config.items_per_buffer, 1));

	samples = new sample_t[buffers_config.count * buffers_config.items_per_buffer];
	lli = new gpdma::channel::LLI[buffers_config.count];

	for(size_t i=0; i<buffers_config.count; i++) {
		const auto memory = reinterpret_cast<uint32_t>(&samples[i * buffers_config.items_per_buffer]);
		lli[i].srcaddr = peripheral;
		lli[i].destaddr = memory;
		lli[i].lli = lli_pointer(&lli[(i + 1) % buffers_config.count]);
		lli[i].control = control_value;
	}
}

void free() {
	delete samples;
	delete lli;
}

void enable() {
	const auto gpdma_config = config();
	gpdma_channel.configure(lli[0], gpdma_config);

	chSemReset(&semaphore, 0);
	gpdma_channel.enable();
}

bool is_enabled() {
	return gpdma_channel.is_enabled();
}

void disable() {
	gpdma_channel.disable_force();
}

rf::rssi::buffer_t wait_for_buffer() {
	const auto status = chSemWait(&semaphore);
	if( status == RDY_OK ) {
		const auto next = next_lli;
		if( next ) {
			const size_t next_index = next - &lli[0];
			const size_t free_index = (next_index + buffers_config.count - 2) % buffers_config.count;
			return { reinterpret_cast<sample_t*>(lli[free_index].destaddr), buffers_config.items_per_buffer };
		} else {
			return { nullptr, 0 };
		}
	} else {
		// TODO: Should I return here, or loop if RDY_RESET?
		return { nullptr, 0 };
	}
}

} /* namespace dma */
} /* namespace rssi */
} /* namespace rf */
