/*
 * 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/pwr.h>
#include <gpio.h>
#include <cpu.h>

struct StmGpio {
    volatile uint32_t MODER;
    volatile uint32_t OTYPER;
    volatile uint32_t OSPEEDR;
    volatile uint32_t PUPDR;
    volatile uint32_t IDR;
    volatile uint32_t ODR;
    volatile uint32_t BSRR;
    volatile uint32_t LCKR;
    volatile uint32_t AFR[2];
};

static const uint32_t mGpioPeriphs[] = {
    PERIPH_AHB1_GPIOA,
    PERIPH_AHB1_GPIOB,
    PERIPH_AHB1_GPIOC,
    PERIPH_AHB1_GPIOD,
    PERIPH_AHB1_GPIOE,
    PERIPH_AHB1_GPIOF,
    PERIPH_AHB1_GPIOG,
    PERIPH_AHB1_GPIOH,
    PERIPH_AHB1_GPIOI,
};

static const uint32_t mGpioBases[] = {
    GPIOA_BASE,
    GPIOB_BASE,
    GPIOC_BASE,
    GPIOD_BASE,
    GPIOE_BASE,
    GPIOF_BASE,
    GPIOG_BASE,
    GPIOH_BASE,
    GPIOI_BASE,
};

static void gpioSetWithNum(uint32_t gpioNum, bool value);


struct Gpio* gpioRequest(uint32_t number)
{
    return (struct Gpio*)(((uintptr_t)number) + GPIO_HANDLE_OFFSET);
}

void gpioRelease(struct Gpio* __restrict gpio)
{
    (void)gpio;
}

static enum StmGpioSpeed gpioSpeedFromRequestedSpeed(int32_t requestedSpeed)
{
    static const enum StmGpioSpeed mStandardSpeeds[] = {
        [-1 - GPIO_SPEED_BEST_POWER  ] = GPIO_SPEED_LOW,
        [-1 - GPIO_SPEED_BEST_SPEED  ] = GPIO_SPEED_HIGH,
        [-1 - GPIO_SPEED_DEFAULT     ] = GPIO_SPEED_MEDIUM,
        [-1 - GPIO_SPEED_1MHZ_PLUS   ] = GPIO_SPEED_LOW,
        [-1 - GPIO_SPEED_3MHZ_PLUS   ] = GPIO_SPEED_LOW,
        [-1 - GPIO_SPEED_5MHZ_PLUS   ] = GPIO_SPEED_MEDIUM,
        [-1 - GPIO_SPEED_10MHZ_PLUS  ] = GPIO_SPEED_MEDIUM,
        [-1 - GPIO_SPEED_15MHZ_PLUS  ] = GPIO_SPEED_MEDIUM,
        [-1 - GPIO_SPEED_20MHZ_PLUS  ] = GPIO_SPEED_MEDIUM,
        [-1 - GPIO_SPEED_30MHZ_PLUS  ] = GPIO_SPEED_FAST,
        [-1 - GPIO_SPEED_50MHZ_PLUS  ] = GPIO_SPEED_FAST,
        [-1 - GPIO_SPEED_100MHZ_PLUS ] = GPIO_SPEED_FAST,
        [-1 - GPIO_SPEED_150MHZ_PLUS ] = GPIO_SPEED_FAST,  //this is not fast enough, but it is all we can do
        [-1 - GPIO_SPEED_150MHZ_PLUS ] = GPIO_SPEED_FAST,  //this is not fast enough, but it is all we can do
    };

    if (requestedSpeed >= 0)
        return requestedSpeed;
    else
        return mStandardSpeeds[-requestedSpeed - 1];
}

static void gpioConfigWithNum(uint32_t gpioNum, int32_t gpioSpeed, enum GpioPullMode pull, enum GpioOpenDrainMode output)
{
    struct StmGpio *block = (struct StmGpio*)mGpioBases[gpioNum >> GPIO_PORT_SHIFT];
    const uint32_t shift_1b = gpioNum & GPIO_PIN_MASK;
    const uint32_t shift_2b = (gpioNum & GPIO_PIN_MASK) * 2;
    const uint32_t mask_1b = (1UL << shift_1b);
    const uint32_t mask_2b = (3UL << shift_2b);

    /* unit clock */
    pwrUnitClock(PERIPH_BUS_AHB1, mGpioPeriphs[gpioNum >> GPIO_PORT_SHIFT], true);

    /* speed */
    block->OSPEEDR = (block->OSPEEDR & ~mask_2b) | (((uint32_t)gpioSpeedFromRequestedSpeed(gpioSpeed)) << shift_2b);

    /* pull ups/downs */
    block->PUPDR = (block->PUPDR & ~mask_2b) | (((uint32_t)pull) << shift_2b);
    /* push/pull or open drain */
    if (output == GPIO_OUT_PUSH_PULL)
        block->OTYPER &= ~mask_1b;
    else
        block->OTYPER |= mask_1b;
}

static void gpioConfigInputWithNum(uint32_t gpioNum, int32_t gpioSpeed, enum GpioPullMode pull)
{
    struct StmGpio *block = (struct StmGpio*)mGpioBases[gpioNum >> GPIO_PORT_SHIFT];
    const uint32_t shift_2b = (gpioNum & GPIO_PIN_MASK) * 2;
    const uint32_t mask_2b = (3UL << shift_2b);

    gpioConfigWithNum(gpioNum, gpioSpeed, pull, GPIO_OUT_PUSH_PULL);

    /* direction */
    block->MODER = (block->MODER & ~mask_2b) | (((uint32_t)GPIO_MODE_IN) << shift_2b);
}

void gpioConfigInput(const struct Gpio* __restrict gpioHandle, int32_t gpioSpeed, enum GpioPullMode pull)
{
    if (gpioHandle)
        gpioConfigInputWithNum((uint32_t)gpioHandle - GPIO_HANDLE_OFFSET, gpioSpeed, pull);
}

static void gpioConfigOutputWithNum(uint32_t gpioNum, int32_t gpioSpeed, enum GpioPullMode pull, enum GpioOpenDrainMode output, bool value)
{
    struct StmGpio *block = (struct StmGpio*)mGpioBases[gpioNum >> GPIO_PORT_SHIFT];
    const uint32_t shift_2b = (gpioNum & GPIO_PIN_MASK) * 2;
    const uint32_t mask_2b = (3UL << shift_2b);

    gpioConfigWithNum(gpioNum, gpioSpeed, pull, output);

    /* set the initial output value */
    gpioSetWithNum(gpioNum, value);

    /* direction */
    block->MODER = (block->MODER & ~mask_2b) | (((uint32_t)GPIO_MODE_OUT) << shift_2b);
}

void gpioConfigOutput(const struct Gpio* __restrict gpioHandle, int32_t gpioSpeed, enum GpioPullMode pull, enum GpioOpenDrainMode output, bool value)
{
    if (gpioHandle)
        gpioConfigOutputWithNum((uint32_t)gpioHandle - GPIO_HANDLE_OFFSET, gpioSpeed, pull, output, value);
}

static void gpioConfigAltWithNum(uint32_t gpioNum, int32_t gpioSpeed, enum GpioPullMode pull, enum GpioOpenDrainMode output, uint32_t altFunc)
{
    struct StmGpio *block = (struct StmGpio*)mGpioBases[gpioNum >> GPIO_PORT_SHIFT];
    const uint32_t pinNo = gpioNum & GPIO_PIN_MASK;
    const uint32_t regNo = pinNo >> (GPIO_PORT_SHIFT - 1);
    const uint32_t nibbleNo = pinNo & (GPIO_PIN_MASK >> 1);
    const uint32_t shift_2b = pinNo * 2;
    const uint32_t shift_4b = nibbleNo * 4;
    const uint32_t mask_2b = (3UL << shift_2b);
    const uint32_t mask_4b = (15UL << shift_4b);

    gpioConfigWithNum(gpioNum, gpioSpeed, pull, output);

    /* assign function */
    block->AFR[regNo] = (block->AFR[regNo] & ~mask_4b) | (((uint32_t)altFunc) << shift_4b);

    /* direction */
    block->MODER = (block->MODER & ~mask_2b) | (((uint32_t)GPIO_MODE_ALTERNATE) << shift_2b);
}

void gpioConfigAlt(const struct Gpio* __restrict gpioHandle, int32_t gpioSpeed, enum GpioPullMode pull, enum GpioOpenDrainMode output, uint32_t altFunc)
{
    if (gpioHandle)
        gpioConfigAltWithNum((uint32_t)gpioHandle - GPIO_HANDLE_OFFSET, gpioSpeed, pull, output, altFunc);
}

static void gpioConfigAnalogWithNum(uint32_t gpioNum)
{
    struct StmGpio *block = (struct StmGpio*)mGpioBases[gpioNum >> GPIO_PORT_SHIFT];
    const uint32_t pinNo = gpioNum & GPIO_PIN_MASK;
    const uint32_t shift_2b = pinNo * 2;
    const uint32_t mask_2b = (3UL << shift_2b);

    gpioConfigWithNum(gpioNum, GPIO_SPEED_LOW, GPIO_PULL_NONE, GPIO_OUT_OPEN_DRAIN);

    /* I/O configuration */
    block->MODER = (block->MODER & ~mask_2b) | (((uint32_t)GPIO_MODE_ANALOG) << shift_2b);
}

void gpioConfigAnalog(const struct Gpio* __restrict gpioHandle)
{
    if (gpioHandle)
        gpioConfigAnalogWithNum((uint32_t)gpioHandle - GPIO_HANDLE_OFFSET);
}

static void gpioSetWithNum(uint32_t gpioNum, bool value)
{
    struct StmGpio *block = (struct StmGpio*)mGpioBases[gpioNum >> GPIO_PORT_SHIFT];
    const uint32_t shift_1b = gpioNum & GPIO_PIN_MASK;
    const uint32_t mask_set_1b = (1UL << (0  + shift_1b));
    const uint32_t mask_clr_1b = (1UL << (16 + shift_1b));

    block->BSRR = value ? mask_set_1b : mask_clr_1b;
}

void gpioSet(const struct Gpio* __restrict gpioHandle, bool value)
{
    if (gpioHandle)
        gpioSetWithNum((uint32_t)gpioHandle - GPIO_HANDLE_OFFSET, value);
}

static bool gpioGetWithNum(uint32_t gpioNum)
{
    struct StmGpio *block = (struct StmGpio*)mGpioBases[gpioNum >> GPIO_PORT_SHIFT];
    const uint32_t shift_1b = gpioNum & GPIO_PIN_MASK;
    const uint32_t mask_1b = (1UL << shift_1b);

    return !!(block->IDR & mask_1b);
}

bool gpioGet(const struct Gpio* __restrict gpioHandle)
{
    return gpioHandle ? gpioGetWithNum((uint32_t)gpioHandle - GPIO_HANDLE_OFFSET) : 0;
}


#ifdef DEBUG_UART_PIN

//this function makes more assumptions than i'd care to list, sorry...
void gpioBitbangedUartOut(uint32_t chr)
{
    static const uint32_t bsrrVals[] = {(1 << (DEBUG_UART_PIN & GPIO_PIN_MASK)) << 16, (1 << (DEBUG_UART_PIN & GPIO_PIN_MASK))};
    struct StmGpio *block = (struct StmGpio*)mGpioBases[DEBUG_UART_PIN >> GPIO_PORT_SHIFT];
    uint32_t bits[10], *bitsP = bits, base = (uint32_t)&block->BSRR;
    static bool setup = 0;
    uint64_t state;
    uint32_t i;

    if (!setup) {
        struct Gpio *gpio = gpioRequest(DEBUG_UART_PIN);

        if (!gpio)
            return;

        setup = true;
        gpioConfigOutput(gpio, GPIO_SPEED_HIGH, GPIO_PULL_NONE, GPIO_OUT_PUSH_PULL, true);
    }

    bits[0] = bsrrVals[0];
    for (i = 0; i < 8; i++, chr >>= 1)
        bits[i + 1] = bsrrVals[chr & 1];
    bits[9] = bsrrVals[1];

    #define SENDBIT "ldr %0, [%1], #4   \n\t"   \
   "str %0, [%2]   \n\t"   \
   "nop    \n\t"   \
   "nop    \n\t"   \
   "nop    \n\t"   \
   "nop    \n\t"   \
   "nop    \n\t"   \
   "nop    \n\t"

   state = cpuIntsOff();
   asm volatile(
       SENDBIT
       SENDBIT
       SENDBIT
       SENDBIT
       SENDBIT
       SENDBIT
       SENDBIT
       SENDBIT
       SENDBIT
       SENDBIT
       :"=r"(i), "=r"(bitsP), "=r"(base)
       :"0"(i), "1"(bitsP), "2"(base)
       :"memory","cc"
    );
    cpuIntsRestore(state);
}


#endif