/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <plat/gpio.h>
#include <plat/usart.h>
#include <plat/pwr.h>
#include <usart.h>
#include <gpio.h>

struct StmUsart {
  volatile uint16_t SR;
  uint8_t unused0[2];
  volatile uint16_t DR;
  uint8_t unused1[2];
  volatile uint16_t BRR;
  uint8_t unused2[2];
  volatile uint16_t CR1;
  uint8_t unused3[2];
  volatile uint16_t CR2;
  uint8_t unused4[2];
  volatile uint16_t CR3;
  uint8_t unused5[2];
  volatile uint16_t GTPR;
  uint8_t unused6[2];
};

static const uint32_t mUsartPorts[] = {
    USART1_BASE,
    USART2_BASE,
    USART3_BASE,
    UART4_BASE,
    UART5_BASE,
    USART6_BASE,
};

static const uint32_t mUsartPeriphs[] = {
    PERIPH_APB2_USART1,
    PERIPH_APB1_USART2,
    PERIPH_APB1_USART3,
    PERIPH_APB1_UART4,
    PERIPH_APB1_UART5,
    PERIPH_APB2_USART6,
};

static uint8_t mUsartBusses[] = {
    PERIPH_BUS_APB2,
    PERIPH_BUS_APB1,
    PERIPH_BUS_APB1,
    PERIPH_BUS_APB1,
    PERIPH_BUS_APB1,
    PERIPH_BUS_APB2,
};

static bool mUsartHasFlowControl[] = {
    true,
    true,
    true,
    false,
    false,
    true,
};

static enum StmGpioAltFunc mUsartAlt[] = {
    GPIO_AF_USART1,
    GPIO_AF_USART2,
    GPIO_AF00,
    GPIO_AF00,
    GPIO_AF00,
    GPIO_AF_USART6,
};

void usartOpen(struct usart* __restrict usart, UsartPort port,
                uint32_t txGpioNum, uint32_t rxGpioNum,
                uint32_t baud, UsartDataBitsCfg data_bits,
                UsatStopBitsCfg stop_bits, UsartParityCfg parity,
                UsartFlowControlCfg flow_control)
{
    static const uint16_t stopBitsVals[] = {0x1000, 0x0000, 0x3000, 0x2000}; // indexed by UsatStopBitsCfg
    static const uint16_t wordLengthVals[] = {0x0000, 0x1000}; // indexed by UsartDataBitsCfg
    static const uint16_t parityVals[] = {0x0000, 0x0400, 0x0600}; // indexed by UsartParityCfg
    static const uint16_t flowCtrlVals[] = {0x0000, 0x0100, 0x0200, 0x0300}; // indexed by UsartFlowControlCfg
    struct StmUsart *block = (struct StmUsart*)mUsartPorts[usart->unit = --port];
    uint32_t baseClk, div, intPart, fraPart;

    /* configure tx/rx gpios */

    usart->rx = gpioRequest(rxGpioNum); /* rx */
    gpioConfigAlt(usart->rx, GPIO_SPEED_LOW, GPIO_PULL_UP, GPIO_OUT_PUSH_PULL, mUsartAlt[port]);
    usart->tx = gpioRequest(txGpioNum); /* tx */
    gpioConfigAlt(usart->tx, GPIO_SPEED_LOW, GPIO_PULL_UP, GPIO_OUT_PUSH_PULL, mUsartAlt[port]);

    /* enable clock */
    pwrUnitClock(mUsartBusses[port], mUsartPeriphs[port], true);

    /* sanity checks */
    if (!mUsartHasFlowControl[port])
        flow_control = USART_FLOW_CONTROL_NONE;

    /* basic config as required + oversample by 8, tx+rx on */
    block->CR2 = (block->CR2 &~ 0x3000) | stopBitsVals[stop_bits];
    block->CR1 = (block->CR1 &~ 0x1600) | wordLengthVals[data_bits] | parityVals[parity] | 0x800C;
    block->CR3 = (block->CR3 &~ 0x0300) | flowCtrlVals[flow_control];

    /* clocking calc */
    baseClk = pwrGetBusSpeed(mUsartBusses[port]);
    div = (baseClk * 25) / (baud * 2);
    intPart = div / 100;
    fraPart = div % 100;

    /* clocking munging */
    intPart = intPart << 4;
    fraPart = ((fraPart * 8 + 50) / 100) & 7;
    block->BRR = intPart | fraPart;

    /* enable */
    block->CR1 |= 0x2000;
}

void usartClose(const struct usart* __restrict usart)
{
    struct StmUsart *block = (struct StmUsart*)mUsartPorts[usart->unit];

    /* Disable USART */
    block->CR1 &=~ 0x2000;

    /* Disable USART clock */
    pwrUnitClock(mUsartBusses[usart->unit], mUsartPeriphs[usart->unit], false);

    /* Release gpios */
    gpioRelease(usart->rx);
    gpioRelease(usart->tx);
}

void usartPutchar(const struct usart* __restrict usart, char c)
{
    struct StmUsart *block = (struct StmUsart*)mUsartPorts[usart->unit];

    /* wait for ready */
    while (!(block->SR & 0x0080));

    /* send */
    block->DR = (uint8_t)c;
}