/* 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/>.
 *
 */
/** BusVoodoo RS-485 mode (code)
 *  @file busvoodoo_rs485.c
 *  @author King Kévin <kingkevin@cuvoodoo.info>
 *  @date 2018
 *  @note peripherals used: USART @ref busvoodoo_rs485_usart
 */
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities

/* STM32 (including CM3) libraries */
#include <libopencm3/cm3/nvic.h> // interrupt utilities
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/usart.h> // USART utilities

/* own libraries */
#include "global.h" // board definitions
#include "print.h" // printing utilities
#include "menu.h" // menu definitions
#include "busvoodoo_global.h" // BusVoodoo definitions
#include "busvoodoo_oled.h" // OLED utilities
#include "busvoodoo_uart_generic.h" // generic UART mode
#include "busvoodoo_rs485.h" // own definitions

/** @defgroup busvoodoo_rs485_usart USART peripheral used for RS-485/422 communication, using a RS-485/422 transceiver
 *  @{
 */
#define BUSVOODOO_RS485_USART 2 /**< USART peripheral */
/** @} */

/** enable RS-485 transceiver driver to allow transmitting */
static void busvoodoo_rs485_drive_enable(void)
{
	gpio_set(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO(BUSVOODOO_RS485_DE_PIN)); // set high to enable RS-485 transceiver driver
}

/** enable RS-485 transceiver driver to allow transmitting */
static void busvoodoo_rs485_drive_disable(void)
{
	gpio_clear(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO(BUSVOODOO_RS485_DE_PIN)); // set low to disable RS-485 transceiver driver
}

/** enable RS-485 transceiver received to allow receiving */
static void busvoodoo_rs485_receive_enable(void)
{
	gpio_clear(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to low enable RS-485 transceiver receiver
}

/** enable RS-485 transceiver received to allow receiving */
static void busvoodoo_rs485_receive_disable(void)
{
	gpio_set(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to disable RS-485 transceiver receiver
}

#define BUSVOODOO_RS485_RX_TIMER 2 /**< timer ID to capture RX edges */
#define BUSVOODOO_RS485_RX_CHANNEL 4 /**< channel ID used as input capture to capture RX edges */

/** RS-485 specific methods that will be called by the generic methods */
static const struct busvoodoo_uart_generic_specific_t busvoodoo_uart_generic_rs485 = {
	.usart = USART(BUSVOODOO_RS485_USART),
	.usart_rcc = RCC_USART(BUSVOODOO_RS485_USART),
	.usart_rst = RST_USART(BUSVOODOO_RS485_USART),
	.multidrive = false,
	.tx_port = USART_TX_PORT(BUSVOODOO_RS485_USART),
	.tx_pin = USART_TX_PIN(BUSVOODOO_RS485_USART),
	.tx_rcc = RCC_USART_PORT(BUSVOODOO_RS485_USART),
	.tx_pre = &busvoodoo_rs485_drive_enable,
	.tx_post = &busvoodoo_rs485_drive_disable,
	.rx_port = USART_RX_PORT(BUSVOODOO_RS485_USART),
	.rx_pin = USART_RX_PIN(BUSVOODOO_RS485_USART),
	.rx_rcc = RCC_USART_PORT(BUSVOODOO_RS485_USART),
	.rx_pre = &busvoodoo_rs485_receive_enable,
	.rx_post = &busvoodoo_rs485_receive_disable,
	.hwflowctl = false,
	.rts_port = 0,
	.rts_pin = 0,
	.rts_rcc = 0,
	.cts_port = 0,
	.cts_pin = 0,
	.cts_rcc = 0,
	.timer = TIM(BUSVOODOO_RS485_RX_TIMER),
	.timer_rcc = RCC_TIM(BUSVOODOO_RS485_RX_TIMER),
	.timer_port = TIM_CH_PORT(BUSVOODOO_RS485_RX_TIMER, BUSVOODOO_RS485_RX_CHANNEL), 
	.timer_port_rcc = RCC_TIM_CH(BUSVOODOO_RS485_RX_TIMER, BUSVOODOO_RS485_RX_CHANNEL), 
	.timer_pin = TIM_CH_PIN(BUSVOODOO_RS485_RX_TIMER, BUSVOODOO_RS485_RX_CHANNEL),
	.timer_ic = TIM_IC(BUSVOODOO_RS485_RX_CHANNEL),
	.timer_ic_in_ti = TIM_IC_IN_TI(BUSVOODOO_RS485_RX_CHANNEL), 
	.timer_sr_ccif = TIM_SR_CCIF(BUSVOODOO_RS485_RX_CHANNEL),
	.timer_sr_ccof = TIM_SR_CCOF(BUSVOODOO_RS485_RX_CHANNEL),
	.timer_ccr = &TIM_CCR(BUSVOODOO_RS485_RX_TIMER, BUSVOODOO_RS485_RX_CHANNEL),
	.timer_dier_ccie = TIM_DIER_CCIE(BUSVOODOO_RS485_RX_CHANNEL),
	.timer_nvic_irq = NVIC_TIM_IRQ(BUSVOODOO_RS485_RX_TIMER),
};

/** setup RS-485 mode
 *  @param[out] prefix terminal prompt prefix
 *  @param[in] line terminal prompt line to configure mode
 *  @return if setup is complete
 */
static bool busvoodoo_rs485_setup(char** prefix, const char* line)
{
	bool complete = false; // is the setup complete
	if (NULL==line) { // first call
		busvoodoo_uart_generic_configure(&busvoodoo_uart_generic_rs485); // provide the RS-485 specific information
	}
	complete = busvoodoo_uart_generic_setup(prefix, line); // configure underlying generic UART
	if (complete) { // generic configuration finished
		gpio_clear(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO(BUSVOODOO_RS485_DE_PIN)); // set low to disable RS-485 transceiver drive
		gpio_set_mode(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(BUSVOODOO_RS485_DE_PIN)); // setup RS-485 Drive Enable GPIO  as output (pulled low to disable by default)
		gpio_set(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set RS-485 transceiver Receive Enable pin high to disable received
		gpio_set_mode(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO(BUSVOODOO_RS485_RE_PIN)); // setup RS-485 Receive Enable GPIO  as output (pulled high to disable by default)
		busvoodoo_led_blue_off(); // disable blue LED because there is no activity
		*prefix = "RS-485/422"; // display mode
		busvoodoo_oled_text_left(*prefix); // set mode title on OLED display
		const char* pinout_io[10] = {"GND", "5V", "3V3", "LV", NULL, NULL, NULL, NULL, NULL, NULL}; // RS-485 mode I/O pinout
		for (uint8_t i=0; i<LENGTH(pinout_io) && i<LENGTH(busvoodoo_global_pinout_io); i++) {
			busvoodoo_global_pinout_io[i] = pinout_io[i]; // set pin names
		}
#if BUSVOODOO_HARDWARE_VERSION==0
		const char* pinout_rscan[5] = {"HV", NULL, NULL, "B", "A"};  // RS-485 mode RS/CAN pinout for hardware version 0
#else
		const char* pinout_rscan[5] = {"HV", NULL, "A", "B", NULL};  // RS-485 mode RS/CAN pinout
#endif
		for (uint8_t i=0; i<LENGTH(pinout_rscan) && i<LENGTH(busvoodoo_global_pinout_rscan); i++) {
			busvoodoo_global_pinout_rscan[i] = pinout_rscan[i]; // set pin names
		}
		const char* pinout[10] = {pinout_rscan[0], pinout_io[0], pinout_rscan[1], pinout_io[2], pinout_rscan[2], pinout_io[4], pinout_rscan[3], pinout_io[6], pinout_rscan[4], pinout_io[8]}; // pinout to display
		busvoodoo_oled_text_pinout((const char**)pinout, false); // set pinout on display
		busvoodoo_oled_update(); // update display to show text and pinout
	}
	return complete;
}

/** exit RS-485 mode
 */
static void busvoodoo_rs485_exit(void)
{
	busvoodoo_uart_generic_exit(); // exiting the underlying generic UART does everything we need
	gpio_set(GPIO(BUSVOODOO_RS485_RE_PORT), GPIO(BUSVOODOO_RS485_RE_PIN)); // set high to disable RS-485 transceiver receiver
	gpio_clear(GPIO(BUSVOODOO_RS485_DE_PORT), GPIO(BUSVOODOO_RS485_DE_PIN)); // set low to disable RS-485 transceiver driver
}

const struct busvoodoo_mode_t busvoodoo_rs485_mode = {
	.name = "rs485",
	.description = "Recommended Standard 485/422",
	.full_only = true,
	.setup = &busvoodoo_rs485_setup,
	.commands = busvoodoo_uart_generic_commands,
	.commands_nb = busvoodoo_uart_generic_commands_nb,
	.exit = &busvoodoo_rs485_exit,
};
