/*
 * Copyright (C) 2013 by Uwe Bonnes(bon@elektron.ikp.physik.tu-darmstadt.de)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * For additional information see http://www.ethernut.de/
 */
/*!
 * \file dev/owibus-stm32tim.c
 * \brief Common implementation of One-Wire primitives with dual channel
 *        STM32 timer, configured at library compile time. This file is
 *        to be included from  bus specific drivers.
 *
 * The OWI Bus needs to be connected of CH1|2 or CH3|4 of some timer. The
 * base pin is set to opendrain, and CNT and CCR1|2 or CCR3|4 are use to
 * generate the pulse and capture the rising edge.
 *
 * \verbatim
 * $Id: owibus_stm32tim.c 5638 2014-04-07 13:58:48Z u_bonnes $
 * \endverbatim
 */

/* Force level to inactive at startup */
#if defined(STM32TIM_OWI_CHANNEL)
#if (STM32TIM_OWI_CHANNEL == 1)
#define  STM32TIM_OWI_CCMR CCMR1
#define  STM32TIM_OWI_COMPARE CCR1
#define  STM32TIM_OWI_CCMODE TIM_CCMR1_CC2S_1 | TIM_CCMR1_OC1M_2
#define  STM32TIM_OWI_CAPTURE CCR2
#define  STM32TIM_OWI_CCMR_SHIFT 4
#define  STM32TIM_OWI_CCER (TIM_CCER_CC2E | TIM_CCER_CC1E | TIM_CCER_CC1P)
#elif (STM32TIM_OWI_CHANNEL == 2)
#define  STM32TIM_OWI_CCMR CCMR1
#define  STM32TIM_OWI_CCMODE TIM_CCMR1_CC1S_1 | TIM_CCMR1_OC2M_2
#define  STM32TIM_OWI_COMPARE CCR2
#define  STM32TIM_OWI_CAPTURE CCR1
#define  STM32TIM_OWI_CCMR_SHIFT 12
#define  STM32TIM_OWI_CCER (TIM_CCER_CC1E | TIM_CCER_CC2E | TIM_CCER_CC2P)
#elif (STM32TIM_OWI_CHANNEL == 3)
#define  STM32TIM_OWI_CCMR CCMR2
#define  STM32TIM_OWI_CCMODE TIM_CCMR2_CC4S_1 | TIM_CCMR2_OC3M_2
#define  STM32TIM_OWI_COMPARE CCR3
#define  STM32TIM_OWI_CAPTURE CCR4
#define  STM32TIM_OWI_CCMR_SHIFT 4
#define  STM32TIM_OWI_CCER (TIM_CCER_CC4E | TIM_CCER_CC3E | TIM_CCER_CC3P)
#elif (STM32TIM_OWI_CHANNEL == 4)
#define  STM32TIM_OWI_CCMR CCMR2
#define  STM32TIM_OWI_CCMODE TIM_CCMR2_CC3S_1 | TIM_CCMR2_OC4M_2
#define  STM32TIM_OWI_COMPARE CCR4
#define  STM32TIM_OWI_CAPTURE CCR3
#define  STM32TIM_OWI_CCMR_SHIFT 12
#define  STM32TIM_OWI_CCER (TIM_CCER_CC3E | TIM_CCER_CC4E |TIM_CCER_CC4P)
#else
#warning Wrong STM32TIM_OWI_CHANNEL
#endif
#else
#warning STM32TIM_OWI_CHANNEL undefined
#endif

/* There is no timer yet with 3 channels only (20130703) */
#if (STM32TIM_OWI_CHANNEL > STM32_OWITIMER_NCH ) || (STM32_OWITIMER_NCH < 2)
#warning No compagnion timer channel available
#endif

static void Stm32Tim_OwiInterrupt(void *arg)
{
    if (TIM_Status(STM32_OWITIMER_BASE) & TIM_SR_UIF) {
        TIM_Status(STM32_OWITIMER_BASE) &= ~TIM_SR_UIF; /* Reset Flag*/
        NutEventPostFromIrq(&STM32TIM_OWI_MUTEX);
    }
}

/*!
 * \brief Perform One-Wire transaction.
 *
 * \param bus     Specifies the One-Wire bus.
 * \param command Either OWI_CMD_RESET or OWI_CMD_RWBIT.
 * \param value   The value to send.
 *
 * \return The value read on success, a negative value otherwise.
 */
static int Stm32Tim_OwiTransaction(NUTOWIBUS *bus, int_fast8_t command, int_fast8_t value)
{
    TIM_TypeDef *owi_timer =   (TIM_TypeDef *)STM32_OWITIMER_BASE;
    uint16_t ccmr = owi_timer->STM32TIM_OWI_CCMR;

    if (value)
        owi_timer->STM32TIM_OWI_COMPARE = -(owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_RELEASE]
                           - owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_SYNC_PULSE]);
    else
        owi_timer->STM32TIM_OWI_COMPARE = -(owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_RELEASE]
                           - owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_SYNC_PULSE_LOW]);

    owi_timer->CNT = -(owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_RELEASE]
                       - owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_SETUP]);
    NutEnterCritical();
    TIM_IRQEnable(STM32_OWITIMER_BASE);
    /* Force active level */
    ccmr &= ~( 7 <<(STM32TIM_OWI_CCMR_SHIFT));
    ccmr |=  ( 5 <<(STM32TIM_OWI_CCMR_SHIFT));
    owi_timer->STM32TIM_OWI_CCMR = ccmr;
    TIM_StartTimer(STM32_OWITIMER_BASE);
    /* Toggle on compare match */
    ccmr &= ~( 7 <<(STM32TIM_OWI_CCMR_SHIFT));
    ccmr |=  ( 3 <<(STM32TIM_OWI_CCMR_SHIFT));
    owi_timer->STM32TIM_OWI_CCMR = ccmr;
    NutExitCritical();
    if(NutEventWait(&STM32TIM_OWI_MUTEX, 10)) {
        if (command == OWI_CMD_RESET)
            return OWI_PRESENCE_ERR;
        else
            return OWI_DATA_ERROR;
    }
    owi_timer->SR = 0;
    TIM_IRQDisable(STM32_OWITIMER_BASE);
    /* We need to handle 32 and 16 bit counters. Store the value to compare
       in timer->capture, so the same thing happens to the top word*/
    owi_timer->STM32TIM_OWI_COMPARE =
        -( owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_RELEASE]
           - owi_timervalues_250ns[bus->mode & OWI_OVERDRIVE][command][OWI_PHASE_RW]);
    if (owi_timer->STM32TIM_OWI_CAPTURE > owi_timer->STM32TIM_OWI_COMPARE)
        return 0;
    else
        return 1;
}

/*!
 * \brief Reset the One-Wire bus and check if device(s) present.
 *
 * \param bus Specifies the One-Wire bus.
 *
 * \return OWI_SUCCESS on success, a negative value otherwise.
 */
static int Stm32Tim_OwiTouchReset(NUTOWIBUS *bus)
{
    return Stm32Tim_OwiTransaction(bus, OWI_CMD_RESET, 1);
}

/*!
 * \brief Exchange one bit on the One-Wire bus.
 *
 * \param bus Specifies the One-Wire bus.
 * \param bit Value for the bit to send.
 *
 * \return The bus state at the read slot on success, a negative value
 *         otherwise.
 */
static int Stm32Tim_OwiRWBit(NUTOWIBUS *bus, uint_fast8_t bit)
{
    return Stm32Tim_OwiTransaction(bus, OWI_CMD_RWBIT, bit);
}

/*!
 * \brief Write a block of data bits to the One-Wire bus.
 *
 * \param bus  Specifies the One-Wire bus.
 * \param data Data bits to send.
 * \param len  Number of bits to send.
 *
 * \return OWI_SUCCESS on success, a negative value otherwise.
 */
static int Stm32Tim_OwiWriteBlock(
    NUTOWIBUS *bus, uint8_t *data, uint_fast8_t len)
{
    int res;
    int i;

    for (i = 0; i < len; i++) {
        res = Stm32Tim_OwiRWBit(bus, data[i >> 3] & (1 << (i & 0x7)));
        if (res < 0)
            return OWI_HW_ERROR;
    }
    return OWI_SUCCESS;
}

/*!
 * \brief Read a block of data bits from the One-Wire bus.
 *
 * \param bus  Specifies the One-Wire bus.
 * \param data Data bits received.
 * \param len  Number of bits to read.
 *
 * \return OWI_SUCCESS on success, a negative value otherwise.
 */
static int Stm32Tim_OwiReadBlock(
    NUTOWIBUS *bus, uint8_t *data, uint_fast8_t len)
{
    int res;
    int i;

    memset(data, 0, (len >> 3) + 1);
    for (i = 0; i < len; i++) {
        res = Stm32Tim_OwiRWBit(bus, 1);
        if (res < 0)
            return OWI_HW_ERROR;
        data[i >> 3] |= (res << (i & 0x7));
    }
    return OWI_SUCCESS;
}

/*!
 * \brief Initailize the driver.
 *
 * \param bus  Specifies the One-Wire bus.
 *
 * \return OWI_SUCCESS on success, a negative value otherwise.
 */
static int Stm32Tim_OwiSetup(NUTOWIBUS *bus)
{
    (void) bus;
    TIM_TypeDef *owi_timer =   (TIM_TypeDef *)STM32_OWITIMER_BASE;

#if !defined(STM32TIM_OWI_PORT) || !defined(STM32TIM_OWI_PIN) ||\
    !defined(STM32_OWITIMER_BASE) || !defined(STM32TIM_OWI_CHANNEL)
    return OWI_INVALID_HW;
#else
    STM32_OWITIMER_CLK();
    STM32_OWITIMER_RST();
    NutRegisterIrqHandler( &STM32_OWITIMER_SIG, &Stm32Tim_OwiInterrupt, NULL);
    owi_timer->PSC = (STM32_OWITIMER_PCLK/2000000L)-1;
    TIM_OnePulse(STM32_OWITIMER_BASE);
    owi_timer->STM32TIM_OWI_CCMR = STM32TIM_OWI_CCMODE;
    owi_timer->CCER = STM32TIM_OWI_CCER;
    TIM_Update(STM32_OWITIMER_BASE);
    owi_timer->SR   = 0;
    NutIrqEnable(&STM32_OWITIMER_SIG);
    GpioPinConfigSet(STM32TIM_OWI_PORT, STM32TIM_OWI_PIN,
                     GPIO_CFG_PERIPHAL|GPIO_CFG_OUTPUT|GPIO_CFG_MULTIDRIVE|
                     GPIO_CFG_PULLUP|GPIO_CFG_INIT_HIGH );

#if defined(MCU_STM32F1)
#if defined(STM32_OWITIMER_REMAP_MASK) && defined(STM32_OWITIMER_REMAP_SHIFT)
    CM3REG(AFIO_BASE, AFIO_TypeDef, MAPR) &= ~(STM32_OWITIMER_REMAP_MASK << STM32_OWITIMER_REMAP_SHIFT);
#if defined(STM32TIM_OWI_REMAP)
#if STM32TIM_OWI_REMAP > STM32_OWITIMER_REMAP_MASK
#warning Illegal Remap Value for OWI0
#endif
    CM3REG(AFIO_BASE, AFIO_TypeDef, MAPR) |=  (STM32TIM_OWI_REMAP<< STM32_OWITIMER_REMAP_SHIFT);
#endif
#endif
#else
    GPIO_PinAFConfig((GPIO_TypeDef*)STM32TIM_OWI_PORT, STM32TIM_OWI_PIN, STM32_OWITIMER_AF );
#endif

    return OWI_SUCCESS;
#endif
