/* 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
/** library for enhanced USART communication (code)
 *  @file usart_enhanced.c
 *  @author King Kévin <kingkevin@cuvoodoo.info>
 *  @date 2018
 *  @details the USART peripherals only support 8 or 9-bit word and even or odd parity (included in the data bits). The library adds support for 5 to 8-bit words, none/even/odd/mark/space parity (on top of the data bits)
 *  @note since parity is handled in software, the parity error (PE) flag is unused and should be replaced by the value return by usart_enhanced_parity_error
 *  @remark 9-bit raw communication is not supported since this is not common and can be done without this library
 */
/* standard libraries */
#include <stdint.h> // standard integer types

/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/usart.h> // USART utilities

/* own libraries */
#include "usart_enhanced.h" // utilities for USART enhancements

/** number of available USART peripherals */
#define USART_PERIPHERALS_NB 3
/** configured enhanced USART word size */
static uint8_t usart_enhanced_databits[USART_PERIPHERALS_NB];
/** configured enhanced USART parity */
static enum usart_enhanced_parity_t usart_enhanced_parity[USART_PERIPHERALS_NB];
/** last enhanced USART parity error status */
static bool usart_enhanced_parity_errors[USART_PERIPHERALS_NB];

const bool usart_enhanced_even_parity_lut[256] = { true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, true, false, false, true, false, true, true, false, true, false, false, true, false, true, true, false, false, true, true, false, true, false, false, true, };

/** get index of corresponding enhanced USART configurations
 *  @param[in] usart USART peripheral base address
 *  @return index used for the individual USART configurations
 *  @note the returned value is valid only is less than USART_PERIPHERALS_NB
 */
static uint8_t usart_enhanced_id(uint32_t usart)
{
	uint8_t to_return = USART_PERIPHERALS_NB;
	switch (usart) {
	case USART1:
		to_return = 0;
		break;
	case USART2:
		to_return = 1;
		break;
	case USART3:
		to_return = 2;
		break;
	default:
		to_return = USART_PERIPHERALS_NB;
		break;
	}
	return to_return;
}

bool usart_enhanced_config(uint32_t usart, uint8_t databits, enum usart_enhanced_parity_t parity)
{
	/* sanity check */
	uint8_t id = usart_enhanced_id(usart);
	if (id>=USART_PERIPHERALS_NB) {
		return false;
	}
	if (databits<5 || databits>8) {
		return false;
	}
	if (parity>USART_ENHANCED_PARITY_SPACE) {
		return false;
	}

	// save configuration for later use
	usart_enhanced_databits[id] = databits;
	usart_enhanced_parity[id] = parity;
	// configure USART peripheral
	if (8 == databits && USART_ENHANCED_PARITY_NONE != parity) { // the parity bit is additional to the data bits
		usart_set_databits(usart, 9);
	} else {
		usart_set_databits(usart, 8);
	}
	usart_set_parity(usart, USART_PARITY_NONE); // set no parity since we will take care of it ourselves
	// we could also lower the number of stop bits when less than 8 bits are used, for higher throughput, but this is not a good idea since most UART transceiver parse 8 bits even is less is used
	return true;
}

void usart_enhanced_send(uint32_t usart, uint8_t data)
{
	/* sanity check */
	uint8_t id = usart_enhanced_id(usart);
	if (id >= USART_PERIPHERALS_NB) {
		return;
	}

	data &= ~(0xff << usart_enhanced_databits[id]); // only keep the data bits
	uint16_t output = data; // put value in output buffer
	switch (usart_enhanced_parity[id]) {
	case USART_ENHANCED_PARITY_NONE: // a mark is also decoded as idle/stop
	case USART_ENHANCED_PARITY_MARK:
		output |= (1 << usart_enhanced_databits[id]); // add idle state
		break;
	case USART_ENHANCED_PARITY_EVEN:
		if (!usart_enhanced_even_parity_lut[data]) {
			output |= (1 << usart_enhanced_databits[id]);
		}
		// no need to clear a bit if the parity is even
		break;
	case USART_ENHANCED_PARITY_ODD:
		if (usart_enhanced_even_parity_lut[data]) {
			output |= (1 << usart_enhanced_databits[id]);
		}
		// no need to clear a bit if the parity is odd
		break;
	case USART_ENHANCED_PARITY_SPACE:
		// no need to clear the bit
		break;
	}
	output |= (0xffff << (usart_enhanced_databits[id] + 1)); // set additional bits to idle (high)
	usart_send(usart, output); // transmit character
}

uint8_t usart_enhanced_recv(uint32_t usart)
{
	/* sanity check */
	uint8_t id = usart_enhanced_id(usart);
	if (id >= USART_PERIPHERALS_NB) {
		return 0xff;
	}

	uint16_t input = usart_recv(usart); // read received character (also clears the error flags)
	uint8_t data = input & ~(0xffff << usart_enhanced_databits[id]); // only keep the data bits
	// check the parity
	uint16_t parity = input & (1 << usart_enhanced_databits[id]); // only keep the parity bit
	usart_enhanced_parity_errors[id] = false;
	switch (usart_enhanced_parity[id]) {
	case USART_ENHANCED_PARITY_NONE:
		usart_enhanced_parity_errors[id] = false;
		break;
	case USART_ENHANCED_PARITY_EVEN:
		if (parity) {
			usart_enhanced_parity_errors[id] = !usart_enhanced_even_parity_lut[data];
		} else {
			usart_enhanced_parity_errors[id] = usart_enhanced_even_parity_lut[data];
		}
		break;
	case USART_ENHANCED_PARITY_ODD:
		if (parity) {
			usart_enhanced_parity_errors[id] = usart_enhanced_even_parity_lut[data];
		} else {
			usart_enhanced_parity_errors[id] = !usart_enhanced_even_parity_lut[data];
		}
		break;
	case USART_ENHANCED_PARITY_MARK:
		usart_enhanced_parity_errors[id] = !parity;
		break;
	case USART_ENHANCED_PARITY_SPACE:
		usart_enhanced_parity_errors[id] = parity;
		break;
	}
	return data;
}

bool usart_enhanced_parity_error(uint32_t usart)
{
	/* sanity check */
	uint8_t id = usart_enhanced_id(usart);
	if (id >= USART_PERIPHERALS_NB) {
		return false;
	}
	return usart_enhanced_parity_errors[id];
}
