/*
 * 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 <errno.h>
#include <isr.h>
#include <platform.h>

#include <plat/cmsis.h>
#include <plat/exti.h>
#include <plat/pwr.h>

struct StmExti
{
    volatile uint32_t IMR;
    volatile uint32_t EMR;
    volatile uint32_t RTSR;
    volatile uint32_t FTSR;
    volatile uint32_t SWIER;
    volatile uint32_t PR;
};

#define EXTI ((struct StmExti*)EXTI_BASE)

void extiEnableIntLine(const enum ExtiLine line, enum ExtiTrigger trigger)
{
    if (trigger == EXTI_TRIGGER_BOTH) {
        EXTI->RTSR |= (1UL << line);
        EXTI->FTSR |= (1UL << line);
    } else if (trigger == EXTI_TRIGGER_RISING) {
        EXTI->RTSR |= (1UL << line);
        EXTI->FTSR &= ~(1UL << line);
    } else if (trigger == EXTI_TRIGGER_FALLING) {
        EXTI->RTSR &= ~(1UL << line);
        EXTI->FTSR |= (1UL << line);
    }

    /* Clear pending interrupt */
    extiClearPendingLine(line);

    /* Enable hardware interrupt */
    EXTI->IMR |= (1UL << line);
}

void extiDisableIntLine(const enum ExtiLine line)
{
    EXTI->IMR &= ~(1UL << line);
}

void extiClearPendingLine(const enum ExtiLine line)
{
    EXTI->PR = (1UL << line);
}

bool extiIsPendingLine(const enum ExtiLine line)
{
    return (EXTI->PR & (1UL << line)) ? true : false;
}

struct ExtiInterrupt
{
    struct ChainedInterrupt base;
    IRQn_Type irq;
};

static void extiInterruptEnable(struct ChainedInterrupt *irq)
{
    struct ExtiInterrupt *exti = container_of(irq, struct ExtiInterrupt, base);
    NVIC_EnableIRQ(exti->irq);
}

static void extiInterruptDisable(struct ChainedInterrupt *irq)
{
    struct ExtiInterrupt *exti = container_of(irq, struct ExtiInterrupt, base);
    NVIC_DisableIRQ(exti->irq);
}

#define DECLARE_SHARED_EXTI(i) {            \
    .base = {                               \
        .enable = extiInterruptEnable,      \
        .disable = extiInterruptDisable,    \
    },                                      \
    .irq = i,                               \
}

uint32_t mMaxLatency = 0;

static struct ExtiInterrupt mInterrupts[] = {
    DECLARE_SHARED_EXTI(EXTI0_IRQn),
    DECLARE_SHARED_EXTI(EXTI1_IRQn),
    DECLARE_SHARED_EXTI(EXTI2_IRQn),
    DECLARE_SHARED_EXTI(EXTI3_IRQn),
    DECLARE_SHARED_EXTI(EXTI4_IRQn),
    DECLARE_SHARED_EXTI(EXTI9_5_IRQn),
    DECLARE_SHARED_EXTI(EXTI15_10_IRQn),
};

static void extiUpdateMaxLatency(uint32_t maxLatencyNs)
{
    if (!maxLatencyNs && mMaxLatency)
        platReleaseDevInSleepMode(Stm32sleepDevExti);
    else if (maxLatencyNs && !mMaxLatency)
        platRequestDevInSleepMode(Stm32sleepDevExti, maxLatencyNs);
    else if (maxLatencyNs && mMaxLatency)
        platAdjustDevInSleepMode(Stm32sleepDevExti, maxLatencyNs);
    mMaxLatency = maxLatencyNs;
}

static void extiCalcMaxLatency()
{
    int i;
    uint32_t maxLatency, newMaxLatency = 0;
    struct ExtiInterrupt *exti = mInterrupts;

    for (i = 0; i < ARRAY_SIZE(mInterrupts); ++i, ++exti) {
        maxLatency = maxLatencyIsr(&exti->base);
        if (!newMaxLatency || (maxLatency && maxLatency < newMaxLatency))
            newMaxLatency = maxLatency;
    }
    extiUpdateMaxLatency(newMaxLatency);
}

static inline struct ExtiInterrupt *extiForIrq(IRQn_Type n)
{
    if (n >= EXTI0_IRQn && n <= EXTI4_IRQn)
        return &mInterrupts[n - EXTI0_IRQn];
    if (n == EXTI9_5_IRQn)
        return &mInterrupts[ARRAY_SIZE(mInterrupts) - 2];
    if (n == EXTI15_10_IRQn)
        return &mInterrupts[ARRAY_SIZE(mInterrupts) - 1];
    return NULL;
}

static void extiIrqHandler(IRQn_Type n)
{
    struct ExtiInterrupt *exti = extiForIrq(n);
    dispatchIsr(&exti->base);
}

#define DEFINE_SHARED_EXTI_ISR(i)           \
    void EXTI##i##_IRQHandler(void);        \
    void EXTI##i##_IRQHandler(void) {       \
        extiIrqHandler(EXTI##i##_IRQn);     \
    }                                       \

DEFINE_SHARED_EXTI_ISR(0)
DEFINE_SHARED_EXTI_ISR(1)
DEFINE_SHARED_EXTI_ISR(2)
DEFINE_SHARED_EXTI_ISR(3)
DEFINE_SHARED_EXTI_ISR(4)
DEFINE_SHARED_EXTI_ISR(9_5)
DEFINE_SHARED_EXTI_ISR(15_10)

int extiSetMaxLatency(struct ChainedIsr *isr, uint32_t maxLatencyNs)
{
    uint32_t latency;

    if (!isr)
        return -EINVAL;

    if (maxLatencyNs != isr->maxLatencyNs) {
        latency = isr->maxLatencyNs;
        isr->maxLatencyNs = maxLatencyNs;
        if (!mMaxLatency || latency == mMaxLatency || (maxLatencyNs && maxLatencyNs < mMaxLatency)) {
            extiCalcMaxLatency();
        }
    }

    return 0;
}

int extiChainIsr(IRQn_Type n, struct ChainedIsr *isr)
{
    struct ExtiInterrupt *exti = extiForIrq(n);
    if (!exti)
        return -EINVAL;
    else if (!list_is_empty(&isr->node))
        return -EINVAL;

    chainIsr(&exti->base, isr);
    if (!mMaxLatency || (isr->maxLatencyNs && isr->maxLatencyNs < mMaxLatency))
        extiUpdateMaxLatency(isr->maxLatencyNs);

    return 0;
}

int extiUnchainIsr(IRQn_Type n, struct ChainedIsr *isr)
{
    struct ExtiInterrupt *exti = extiForIrq(n);
    if (!exti)
        return -EINVAL;
    else if (list_is_empty(&isr->node))
        return -EINVAL;

    unchainIsr(&exti->base, isr);
    if (isr->maxLatencyNs && isr->maxLatencyNs == mMaxLatency)
        extiCalcMaxLatency();
    return 0;
}

int extiUnchainAll(uint32_t tid)
{
    int i, count = 0;
    struct ExtiInterrupt *exti = mInterrupts;

    for (i = 0; i < ARRAY_SIZE(mInterrupts); ++i, ++exti)
        count += unchainIsrAll(&exti->base, tid);
    extiCalcMaxLatency();

    return count;
}