/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * 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.
 *  * Neither the name of the copyright holder nor the names of its
 *    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
 * HOLDER 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.
 *
 *****************************************************************************/

/*-----------------------------------------------------------------------------
 *-----------------------------------------------------------------------------
 * $Author: brianpc.huang $
 * $Date: 2015/05/29 $
 * $RCSfile: mtk_rtc_ext.c,v $
 * $Revision: #1 $
 *
 * Description:
 *         Digimedia's RTC external hardware ISL1208 driver implementation.
 *---------------------------------------------------------------------------*/

//----------------------------------------------------------------------------
// Constant definitions
//----------------------------------------------------------------------------
#include "x_lint.h"
LINT_EXT_HEADER_BEGIN
#include "sif_if.h" 
#include "rtc_if.h"
#include "x_debug.h"
LINT_EXT_HEADER_END

//----------------------------------------------------------------------------
// Constant definitions
//----------------------------------------------------------------------------

#define ISL1208_REG_RTC_SC 0x0
#define ISL1208_REG_RTC_MN 0x1
#define ISL1208_REG_RTC_HR 0x2
#define ISL1208_REG_RTC_DT 0x3
#define ISL1208_REG_RTC_MO 0x4
#define ISL1208_REG_RTC_YR 0x5
#define ISL1208_REG_RTC_DW 0x6
#define ISL1208_RTC_HR_MIL (1U << 7)

#define ISL1208_REG_CAS_SR 0x7
#define ISL1208_REG_CAS_INT 0x8
#define ISL1208_REG_CAS_ATR 0xA
#define ISL1208_REG_CAS_DTR 0xB
#define ISL1208_CAS_SR_RTCF (1U << 0)
#define ISL1208_CAS_SR_BAT (1U << 1)
#define ISL1208_CAS_SR_ALM (1U << 2)
#define ISL1208_CAS_SR_WRTC (1U << 4)
#define ISL1208_CAS_SR_XTOSCB (1U << 6)
#define ISL1208_CAS_SR_ARST (1U << 7)

#define ISL1208_CAS_INT_FOBATB (1U << 4)
#define ISL1208_CAS_INT_LPMODE (1U << 5)
#define ISL1208_CAS_INT_ALME (1U << 6)
#define ISL1208_CAS_INT_IM (1U << 7)
#define ISL1208_CAS_ATR_BMATR0 (1U << 6)

#define ISL1208_REG_ALM_SCA 0xC
#define ISL1208_REG_ALM_MNA 0xD
#define ISL1208_REG_ALM_HRA 0xE
#define ISL1208_REG_ALM_DTA 0xF
#define ISL1208_REG_ALM_MOA 0x10
#define ISL1208_REG_ALM_DWA 0x11
#define ISL1208_ALM_MATCH (1U << 7)

#define ISL1208_REG_USR_USR1 0x12
#define ISL1208_REG_USR_USR2 0x13

#define ISL1208_DEV_ADDR 0x6F

#define ISL1208_CLK_DIV 					0x100

//----------------------------------------------------------------------------
// Prototypes
//----------------------------------------------------------------------------
static BOOL _ISL1208_Init(void);
static BOOL _ISL1208_GetTimeDate(UINT64* prTime);
static BOOL _ISL1208_SetTimeDate(const UINT64* prTime);
static BOOL _ISL1208_GetAlertTime(UINT64* prTime);
static BOOL _ISL1208_SetAlertTime(UINT64 u8Utc);

//----------------------------------------------------------------------------
// Interface
//----------------------------------------------------------------------------
static RTC_FuncTbl _RTC_EXT_FuncTbl =
{
    _ISL1208_Init,
    _ISL1208_GetTimeDate,
    _ISL1208_SetTimeDate,
    _ISL1208_GetAlertTime,
    _ISL1208_SetAlertTime  
} ;
RTC_FuncTbl* _RTC_EXT_GetFunc(void)
{
    return &_RTC_EXT_FuncTbl;
}

//----------------------------------------------------------------------------
// Static variables
//----------------------------------------------------------------------------
static BOOL _fgRTCInit = FALSE;
static BOOL _fgRTCDisableRTC = FALSE;

//----------------------------------------------------------------------------
// Private functions
//----------------------------------------------------------------------------

static void _ISL1208_Read(UINT8 u1Addr, UINT8* pu1Data, UINT16 u4Len)
{
    UINT32 u4ReadCount = 0; 
    
    u4ReadCount = SIF_Read(ISL1208_CLK_DIV, ISL1208_DEV_ADDR << 1, u1Addr, pu1Data, u4Len);

    if (u4ReadCount > 0)
    {
    	;
    }	
    else
    {
    	LOG(1,"[RTC] Error ISL1208 register setting\n");
    }	
}

static void _ISL1208_Write(UINT8 u1Addr, UINT8* pu1Data, UINT16 u4Len)
{
    UINT32 u4WriteCount = 0; 
    
    u4WriteCount = SIF_Write(ISL1208_CLK_DIV, ISL1208_DEV_ADDR << 1, u1Addr, pu1Data, u4Len);

    if (u4WriteCount > 0)
    {
    	;
    }	
    else
    {
    	LOG(1,"[RTC] Error ISL1208 register setting\n");
    }	
}

//----------------------------------------------------------------------------
/** RTC_Init() Initialize RTC driver and hardware
 *  @retval TRUE, Succeed; FALSE, Fail.
 */
//----------------------------------------------------------------------------
static BOOL _ISL1208_Init(void)
{
    UINT64 u8Utc;
    UINT8 u1Temp;

    if (_fgRTCInit)
    {
        // Already initialized
        return TRUE;
    }
    
    // Pass write protection
    _ISL1208_Read(ISL1208_REG_CAS_SR, &u1Temp, 1);
    u1Temp = u1Temp | ISL1208_CAS_SR_WRTC;
    u1Temp = u1Temp | ISL1208_CAS_SR_ARST;
    u1Temp = (u1Temp & (~(1u <<6)));
    _ISL1208_Write(ISL1208_REG_CAS_SR, &u1Temp, 1);

    // Check RTC flags
    _ISL1208_Read(ISL1208_REG_CAS_SR, &u1Temp, 1);
    if ((u1Temp & ISL1208_CAS_SR_WRTC) == 0)
    {
        _fgRTCDisableRTC = TRUE;
        LOG(1,"Swap on RTC.\n");
        return TRUE;
    }

    // Check if total power failure upon init
    if(u1Temp & ISL1208_CAS_SR_RTCF)
    {
#if 1
        u8Utc = UTC_INIT_TIME;
#else
        RTC_T rRtc;
        rRtc.u1Year = 0;
        rRtc.u1Month = 1;
        rRtc.u1Day = 1;
        rRtc.u1Hour = 0;
        rRtc.u1Minute = 0;
        rRtc.u1Second = 0;
        if(!RTC_RtcToUtc(&u8Utc, &rRtc))
        {
            return FALSE;
        }
#endif        
        if(!_ISL1208_SetTimeDate(&u8Utc))
        {
            return FALSE;
        }
    }

    // Enable alarm and set alarm interrupt mode
    u1Temp = ISL1208_CAS_INT_IM | ISL1208_CAS_INT_ALME;
    _ISL1208_Write(ISL1208_REG_CAS_INT, &u1Temp, 1);

    // Start RTC, set to 24-hour mode
    _ISL1208_Read(ISL1208_REG_RTC_HR, &u1Temp, 1);
    u1Temp |= ISL1208_RTC_HR_MIL;
    _ISL1208_Write(ISL1208_REG_RTC_HR, &u1Temp, 1);

    _fgRTCInit = TRUE;
    
    return TRUE;
}


//----------------------------------------------------------------------------
/** RTC_GetTimeDate() Get time from RTC
 *  @param prTime, Output, The time
 *  @retval TRUE, Succeed; FALSE, Fail.
 */
//----------------------------------------------------------------------------
static BOOL _ISL1208_GetTimeDate(UINT64* prTime)
{
    RTC_T rRtc;
    UINT64 u8Utc;
    UINT8 au1Temp[6];

    ASSERT(prTime!=NULL);
    if (_fgRTCDisableRTC)
    {
        *prTime = 0;
        return FALSE;
    }

    // Get time in second from RTC
    _ISL1208_Read(ISL1208_REG_RTC_SC, au1Temp, 6);       
    rRtc.u1Second = RTC_BcdToBinary(au1Temp[0]);  
    rRtc.u1Minute = RTC_BcdToBinary(au1Temp[1]);     
    rRtc.u1Hour = RTC_BcdToBinary(au1Temp[2] & 0x3F);
    rRtc.u1Day =  RTC_BcdToBinary(au1Temp[3]);         
    rRtc.u1Month = RTC_BcdToBinary(au1Temp[4]); 
    rRtc.u1Year = RTC_BcdToBinary(au1Temp[5]);
    
    // Translate to TIME_T(UINT64)
    if (RTC_RtcToUtc(&u8Utc, &rRtc))
    {
        *prTime = (UINT64)u8Utc;
        return TRUE;
    }

    *prTime = 0;

    return FALSE;
}


//----------------------------------------------------------------------------
/** RTC_SetTimeDate() Set time to RTC
 *  @param prTime, Input, The time to set.
 *  @retval TRUE, Succeed; FALSE, Fail.
 */
//----------------------------------------------------------------------------
static BOOL _ISL1208_SetTimeDate(const UINT64* prTime)
{
    RTC_T rRtc;
    UINT64 u8Utc;
    UINT8 au1Temp[6];

    ASSERT(prTime!=NULL);
    // Translate to RTC
    u8Utc = (UINT64)*prTime;
    if (!RTC_UtcToRtc(&rRtc, u8Utc))
    {
        return FALSE;
    }

    if (!_fgRTCDisableRTC)
    {
        // Get time in second from RTC
    au1Temp[0] =  RTC_BinaryToBcd(rRtc.u1Second);   
    au1Temp[1] = RTC_BinaryToBcd(rRtc.u1Minute);
    au1Temp[2] = (RTC_BinaryToBcd(rRtc.u1Hour) | ISL1208_RTC_HR_MIL);
    au1Temp[3] = RTC_BinaryToBcd(rRtc.u1Day);
    au1Temp[4] = RTC_BinaryToBcd(rRtc.u1Month);
    au1Temp[5] =  RTC_BinaryToBcd(rRtc.u1Year);
    _ISL1208_Write(ISL1208_REG_RTC_YR, au1Temp, 6);
    
    // Disable RTC STOP
    // TODO
    
    }

    return TRUE;
}

//----------------------------------------------------------------------------
/** RTC_SetAlertTime() Set time to RTC
 *  @param prTime, Input, The time to set.
 *  @retval TRUE, Succeed; FALSE, Fail.
 */
//----------------------------------------------------------------------------
static BOOL _ISL1208_SetAlertTime(UINT64 u8Utc)
{
    RTC_T rRtc;
    UINT8 au1Temp[5];
    
    if (!RTC_UtcToRtc(&rRtc, u8Utc))
    {
        LOG(1,"SetAlertTime failed.\n");
        return FALSE;
    }

    // Set H24 and Stop mode
    _ISL1208_Read(ISL1208_REG_CAS_INT, au1Temp, 1);
    au1Temp[0] |= ISL1208_CAS_INT_ALME;    
    _ISL1208_Write(ISL1208_REG_CAS_INT, au1Temp, 1);
    
    // Set to RTC
    au1Temp[0] = (RTC_BinaryToBcd(rRtc.u1Second) | ISL1208_ALM_MATCH);    
    au1Temp[1] = (RTC_BinaryToBcd(rRtc.u1Minute) | ISL1208_ALM_MATCH);    
    au1Temp[2] = (RTC_BinaryToBcd(rRtc.u1Hour) | ISL1208_ALM_MATCH);        
    au1Temp[3] = (RTC_BinaryToBcd(rRtc.u1Day) | ISL1208_ALM_MATCH);        
    au1Temp[4] = (RTC_BinaryToBcd(rRtc.u1Month) | ISL1208_ALM_MATCH);    
    _ISL1208_Write(ISL1208_REG_ALM_SCA, au1Temp, 5);
    
    // Disable RTC STOP
    // Not necessary for ISL 1208

    return TRUE;
}

//----------------------------------------------------------------------------
/** RTC_GetAlertTime() Get time from RTC
 *  @param prTime, Output, The time
 *  @retval TRUE, Succeed; FALSE, Fail.
 */
//----------------------------------------------------------------------------
static BOOL _ISL1208_GetAlertTime(UINT64* prTime)
{

    RTC_T rRtc;
    UINT64 u8Utc;
    UINT8 au1Temp[5];

    ASSERT(prTime!=NULL);
    if (_fgRTCDisableRTC)
    {
        *prTime = 0;
        return FALSE;
    }

    // Get year from RTC
    _ISL1208_Read(ISL1208_REG_ALM_SCA, au1Temp, 5);
    rRtc.u1Second = RTC_BcdToBinary(au1Temp[0] & 0x7F);      
    rRtc.u1Minute = RTC_BcdToBinary(au1Temp[1] & 0x7F);     
    rRtc.u1Hour = RTC_BcdToBinary(au1Temp[2] & 0x3F);    
    rRtc.u1Day =  RTC_BcdToBinary(au1Temp[3] & 0x3F);
    rRtc.u1Month = RTC_BcdToBinary(au1Temp[4] & 0x1F); 
    _ISL1208_Read(ISL1208_REG_RTC_YR, au1Temp, 1);
    rRtc.u1Year = RTC_BcdToBinary(au1Temp[0]); 
   
    // Translate to TIME_T(UINT64)
    if (RTC_RtcToUtc(&u8Utc, &rRtc))
    {
        *prTime = (UINT64)u8Utc;
        return TRUE;
    }

    *prTime = 0;

    return FALSE;
}


