/****************************************************************************
 * arch/arm/src/lc823450/lc823450_sdc.c
 *
 *   Copyright (C) 2014-2015 ON Semiconductor. All rights reserved.
 *   Copyright 2014,2015,2016,2017 Sony Video & Sound Products Inc.
 *   Author: Masayuki Ishikawa <Masayuki.Ishikawa@jp.sony.com>
 *   Author: Masatoshi Tateishi <Masatoshi.Tateishi@jp.sony.com>
 *   Author: Nobutaka Toyoshima <Nobutaka.Toyoshima@jp.sony.com>
 *
 * 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 NuttX 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 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/clock.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <nuttx/kthread.h>
#include <arch/board/board.h>

#include <inttypes.h>
#include <stdio.h>
#include <string.h>

#include "chip.h"
#include "arm_internal.h"
#include "lc823450_sddrv_if.h"
#include "lc823450_sdc.h"
#include "lc823450_syscontrol.h"
#include "lc823450_clockconfig.h"

#ifdef CONFIG_LED_ACCLED_SDIF
#  include <nuttx/led.h>
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define SDIF0_BASE  (0x4004A000)
#define SDIF1_BASE  (0x4004B000)

#define DET_TIME    (10000)    /* 10ms */

/****************************************************************************
 * Private Data
 ****************************************************************************/

static mutex_t _sdc_lock[2] =
{
  NXMUTEX_INITIALIZER,
  NXMUTEX_INITIALIZER,
};

static struct sddrcfg_s _sdch0;
static struct sddrcfg_s _sdch1;

static struct sddrcfg_s *_cfg[2] =
{
  &_sdch0,
  &_sdch1
};

static unsigned long _work0[512 / 4];
#ifdef CONFIG_LC823450_SDIF_SDC
static unsigned long _work1[512 / 4];
#endif

#ifdef CONFIG_LC823450_SDC_CACHE
static uint8_t   _sec_cache_enabled;
static uint32_t  _sec_cache[512 / 4];
static uint32_t  _sec_cache_add = 0xffffffff;
#endif

/****************************************************************************
 * Public Data
 ****************************************************************************/

/* ROM symbol address */

extern uint8_t cpu_ver;

extern SINT_T sddep0_hw_init(struct sddrcfg_s *);
extern SINT_T sddep0_hw_exit(struct sddrcfg_s *);
extern SINT_T sddep1_hw_init(struct sddrcfg_s *);
extern SINT_T sddep1_hw_exit(struct sddrcfg_s *);

extern SINT_T sddep_os_init(struct sddrcfg_s *);
extern SINT_T sddep_os_exit(struct sddrcfg_s *);
extern void   sddep_voltage_switch(struct sddrcfg_s *cfg);
extern void   sddep_set_clk(struct sddrcfg_s *);
extern SINT_T sddep_wait(UI_32, struct sddrcfg_s *);
extern SINT_T sddep_wait_status(UI_32 req, UI_32 *status,
                                struct sddrcfg_s *cfg);
extern SINT_T sddep_read(void *src, void *dst, UI_32 size, SINT_T type,
                         struct sddrcfg_s *cfg);
extern SINT_T sddep_write(void *src, void *dst, UI_32 size, SINT_T type,
                          struct sddrcfg_s *cfg);

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: _lc823450_sdc_support_trim
 ****************************************************************************/

static int _lc823450_sdc_support_trim(struct sddrcfg_s *cf)
{
  /* NOTE: to avoid conflicts, SDDR_SUPPORT_TRIM() macro is not used here */

  int ret = ((sddr_refmediatype(cf) == SDDR_MEDIA_TYPE_MMC) ?
             (((cf)->ex.mmc.extcsd_sec_feature_support & (1UL << 4)) ?
              1 : 0) : 0);
  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_access_led
 ****************************************************************************/

#ifdef CONFIG_LED_ACCLED_SDIF
static void lc823450_sdc_access_led(uint32_t ch, unsigned long sector)
{
  if (ch == 0)
    {
      if (sector >= CONFIG_MTD_CP_STARTBLOCK)
        {
          led_start_accled(false);
        }
    }
  else
    {
      led_start_accled(false);
    }
}
#else
#  define lc823450_sdc_access_led(a,b)
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lc823450_sdc_clearcardinfo
 ****************************************************************************/

int lc823450_sdc_clearcardinfo(uint32_t ch)
{
  int ret;

  mcinfo("++++ start\n");

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret < 0)
    {
      return ret;
    }

  ret = sddr_clearcardinfo(_cfg[ch]);

#ifdef CONFIG_LC823450_SDC_CACHE
  if (ch)
    {
      _sec_cache_enabled = 0;
      _sec_cache_add = 0xffffffff; /* invalid */
    }
#endif

  nxmutex_unlock(&_sdc_lock[ch]);
  mcinfo("---- end ret=%d\n", ret);
  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_initialize
 ****************************************************************************/

int lc823450_sdc_initialize(uint32_t ch)
{
  int ret;

  /* Only ES2 is supported */

  DEBUGASSERT(1 == cpu_ver);

  struct sddrcfg_s *psd = _cfg[ch];

  psd->sysclk           = lc823450_get_systemfreq();
  psd->detecttime       = DET_TIME;

#ifdef CONFIG_LC823450_SDC_UHS1
  psd->setting          = SDDR_SD_SWITCH_18V;
#endif

  psd->deposinit        = sddep_os_init;
  psd->deposexit        = sddep_os_exit;
  psd->depsetclk        = sddep_set_clk;
  psd->depwait          = sddep_wait;
  psd->depwaitstatus    = sddep_wait_status;
  psd->depreaddata      = sddep_read;
  psd->depwritedata     = sddep_write;
  psd->depvoltageswitch = sddep_voltage_switch;

  switch (ch)
    {
      case 0:
        psd->dephwinit  = sddep0_hw_init;
        psd->dephwexit  = sddep0_hw_exit;
        psd->regbase    = SDIF0_BASE;
        psd->workbuf    = _work0;
        break;

#ifdef CONFIG_LC823450_SDIF_SDC
      case 1:
        psd->dephwinit  = sddep1_hw_init;
        psd->dephwexit  = sddep1_hw_exit;
        psd->regbase    = SDIF1_BASE;
        psd->workbuf    = _work1;
        break;
#endif

      default:
        DEBUGPANIC();
    }

  mcinfo("++++ start\n");

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret >= 0)
    {
      ret = sddr_initialize(_cfg[ch]);
      nxmutex_unlock(&_sdc_lock[ch]);
      mcinfo("---- end ret=%d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_finalize
 ****************************************************************************/

int lc823450_sdc_finalize(uint32_t ch)
{
  int ret;

  mcinfo("++++ start ch=%" PRId32 "\n", ch);

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret >= 0)
    {
      ret = sddr_finalize(_cfg[ch]);
      nxmutex_unlock(&_sdc_lock[ch]);
      mcinfo("---- end ret=%d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_identifycard
 ****************************************************************************/

int lc823450_sdc_identifycard(uint32_t ch)
{
  int ret;

  mcinfo("++++ start\n");

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret < 0)
    {
      return ret;
    }

  ret = sddr_identifycard(_cfg[ch]);

#ifdef CONFIG_LC823450_SDC_CACHE
  if (ch)
    {
      _sec_cache_enabled = 0;
      _sec_cache_add = 0xffffffff; /* invalid */
    }
#endif

  nxmutex_unlock(&_sdc_lock[ch]);
  mcinfo("---- end ret=%d\n", ret);
  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_setclock
 ****************************************************************************/

int lc823450_sdc_setclock(uint32_t ch, uint32_t limitclk, uint32_t sysclk)
{
  int ret;

  mcinfo("++++ start ch=%" PRId32 " limitClk=%" PRId32
         " sysClk=%" PRId32 "\n", ch, limitclk, sysclk);

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret >= 0)
    {
      ret = sddr_setclock(limitclk, sysclk, _cfg[ch]);
      nxmutex_unlock(&_sdc_lock[ch]);
      mcinfo("---- end ret=%d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_refmediatype
 *
 * Returned Value:
 *   0(sd), 1(emmc)
 *
 ****************************************************************************/

int lc823450_sdc_refmediatype(uint32_t ch)
{
  int ret;

  mcinfo("++++ start\n");

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret >= 0)
    {
      ret = sddr_refmediatype(_cfg[ch]);
      nxmutex_unlock(&_sdc_lock[ch]);
      mcinfo("---- end ret=%d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_getcardsize
 ****************************************************************************/

int lc823450_sdc_getcardsize(uint32_t ch,
                             unsigned long *psecnum, unsigned long *psecsize)
{
  int ret;

  mcinfo("++++ start\n");

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret >= 0)
    {
      ret = sddr_getcardsize(psecnum, psecsize, _cfg[ch]);

      nxmutex_unlock(&_sdc_lock[ch]);
      mcinfo("---- end ret=%d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_readsector
 ****************************************************************************/

int lc823450_sdc_readsector(uint32_t ch,
                            unsigned long addr, unsigned short cnt,
                            void *pbuf, unsigned long type)
{
  int ret;
  int i = 0;

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret < 0)
    {
      return ret;
    }

#ifdef CONFIG_LC823450_SDC_LOG
  mcinfo("++++ start ch=%d, addr=%ld, cnt=%d\n", ch, addr, cnt);
#endif

#ifdef CONFIG_LC823450_SDC_CACHE
  if (ch && _sec_cache_enabled && 1 == cnt && addr == _sec_cache_add)
    {
      memcpy(pbuf, _sec_cache, sizeof(_sec_cache));
      goto errout_with_lock;
    }
#endif

  lc823450_sdc_access_led(ch, addr);

#ifdef CONFIG_SCHED_INSTRUMENTATION_IO
  sched_add_bi((uint64_t)cnt);
#endif

  for (i = 0; i < 5; i++)
    {
#ifdef CONFIG_LC823450_SDIF_PATCH
      ret = fixed_sddr_readsector(addr, cnt, pbuf, type, _cfg[ch]);
#else
      ret = sddr_readsector(addr, cnt, pbuf, type, _cfg[ch]);
#endif
      if (0 == ret)
        {
          break;
        }

      mcinfo("ret=%d ch=%" PRId32 " add=%ld cnt=%d i=%d\n",
             ret, ch, addr, cnt, i);
    }

#ifdef CONFIG_LC823450_SDC_CACHE
  if (ch)
    {
      if (0 == addr)
        {
          uint8_t *p = (pbuf + 0x1c2); /* partition id */
          if (0x7 == *p)
            {
              mcinfo("exFAT (NTFS) detected\n");
              _sec_cache_enabled = 1;
            }
        }

      if (_sec_cache_enabled)
        {
          if (1 == cnt && 0 == ret)
            {
              memcpy(_sec_cache, pbuf, sizeof(_sec_cache));
              _sec_cache_add = addr;
            }
          else
            {
              _sec_cache_add = 0xffffffff; /* invalid */
            }
        }
    }

errout_with_lock:
#endif
  nxmutex_unlock(&_sdc_lock[ch]);

  mcinfo("----  end ret=%d\n", ret);
  return ret;
}

/****************************************************************************
 * name: lc823450_sdc_writesector
 ****************************************************************************/

int lc823450_sdc_writesector(uint32_t ch,
                             unsigned long addr, unsigned short cnt,
                             void *pbuf, unsigned long type)
{
  int ret;

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret < 0)
    {
      return ret;
    }

#ifdef CONFIG_LC823450_SDC_LOG
  mcinfo("++++ start ch=%d, addr=%ld, cnt=%d\n", ch, addr, cnt);
#endif

#ifdef CONFIG_LC823450_SDC_CACHE
  if (1 == ch && _sec_cache_enabled)
    {
      _sec_cache_add = 0xffffffff; /* invalid */
    }
#endif

  lc823450_sdc_access_led(ch, addr);

#ifdef CONFIG_SCHED_INSTRUMENTATION_IO
  sched_add_bo((uint64_t)cnt);
#endif

  ret = sddr_writesector(addr, cnt, pbuf, type, _cfg[ch]);

  if (0 > ret)
    {
      mcinfo("ret=%d ch=%" PRId32 " add=%ld cnt=%d\n", ret, ch, addr, cnt);
    }

  nxmutex_unlock(&_sdc_lock[ch]);

  mcinfo("----  end ret=%d\n", ret);
  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_checktrim
 ****************************************************************************/

int lc823450_sdc_checktrim(uint32_t ch)
{
  return _lc823450_sdc_support_trim(_cfg[ch]);
}

/****************************************************************************
 * Name: lc823450_sdc_trimsector
 ****************************************************************************/

int lc823450_sdc_trimsector(uint32_t ch, unsigned long addr,
                            unsigned short cnt)
{
  int ret;

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret < 0)
    {
      return ret;
    }

#ifdef CONFIG_LC823450_SDC_LOG
  mcinfo("++++ start ch=%d, addr=%ld, cnt=%d\n", ch, addr, cnt);
#endif

  lc823450_sdc_access_led(ch, addr);

#ifdef CONFIG_SCHED_INSTRUMENTATION_IO
  sched_add_bt((uint64_t)cnt);
#endif

  ret = sddr_eraseseq(0x00000001, addr, cnt, _cfg[ch]);
  if (0 > ret)
    {
      mcinfo("ret=%d ch=%" PRId32 " add=%ld cnt=%d\n", ret, ch, addr, cnt);
    }

  nxmutex_unlock(&_sdc_lock[ch]);

  mcinfo("----  end ret=%d\n", ret);
  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_cachectl
 ****************************************************************************/

int lc823450_sdc_cachectl(uint32_t ch, int ctrl)
{
  int ret;

  mcinfo("++++ ch=%" PRId32 ", ctrl=%d\n", ch, ctrl);

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret >= 0)
    {
      ret = sddr_cachectrl(ctrl, _cfg[ch]);

      nxmutex_unlock(&_sdc_lock[ch]);
      mcinfo("----  end ret=%d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_changespeedmode
 ****************************************************************************/

int lc823450_sdc_changespeedmode(uint32_t ch, int mode)
{
  int ret;

  mcinfo("++++ ch=%" PRId32 ", mode=%d\n", ch, mode);

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret < 0)
    {
      return ret;
    }

  ret = sddr_changespeedmode(mode, _cfg[ch]);

  if (0 == ret)
    {
      switch (mode)
        {
          case 1: /* High Speed */
            modifyreg32(SDCTL,
                        SDCTL_ACSMODE0_MASK << (ch * 8),
                        SDCTL_ACSMODE0_HS << (ch * 8));
            break;

          case 4: /* DDR */
            modifyreg32(SDCTL,
                        SDCTL_ACSMODE0_MASK << (ch * 8),
                        SDCTL_ACSMODE0_MMCDDR << (ch * 8));
            break;
        }
    }

  nxmutex_unlock(&_sdc_lock[ch]);
  mcinfo("----  end ret=%d\n", ret);
  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_getcid
 ****************************************************************************/

int lc823450_sdc_getcid(uint32_t ch, char *cidstr, int length)
{
  uint8_t cid[16];
  int ret;

  mcinfo("++++ ch=%" PRId32 "\n", ch);

  ret = nxmutex_lock(&_sdc_lock[ch]);
  if (ret < 0)
    {
      return ret;
    }

  ret = sddr_getcid((UI_32 *)cid, _cfg[ch]);

  if (0 == ret && length >= (2 * sizeof(cid) + 1))
    {
      int i;

      for (i = 15; i >= 0; i--)
        {
          snprintf(cidstr, 3, "%02x", cid[i]);
          cidstr += 2;
        }

      *cidstr = '\0';
    }

  nxmutex_unlock(&_sdc_lock[ch]);
  mcinfo("----  end ret=%d\n", ret);
  return ret;
}

/****************************************************************************
 * Name: lc823450_sdc_locked
 ****************************************************************************/

int lc823450_sdc_locked(void)
{
  int ret;
  int i;

  ret = 0;

  for (i = 0; i < 2; i++)
    {
      if (nxmutex_is_locked(&_sdc_lock[i]))
        {
          ret = 1;
          break;
        }
    }

  return ret;
}
