/****************************************************************************
 * drivers/audio/wm8904_debug.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/* References:
 * - "WM8904 Ultra Low Power CODEC for Portable Audio Applications, Pre-
 *    Production", September 2012, Rev 3.3, Wolfson Microelectronics
 */

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

#include <nuttx/config.h>

#include <stdint.h>
#include <assert.h>
#include <fixedmath.h>
#include <syslog.h>

#include <nuttx/audio/audio.h>
#include <nuttx/audio/wm8904.h>

#include "wm8904.h"

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

/****************************************************************************
 * Private Types
 ****************************************************************************/

#ifdef CONFIG_WM8904_REGDUMP
struct wb8904_regdump_s
{
  FAR const char *regname;
  uint8_t regaddr;
};
#endif

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

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

#ifdef CONFIG_WM8904_REGDUMP
static const struct wb8904_regdump_s g_wm8904_debug[] =
{
  {"ID",              WM8904_ID              },
  {"BIAS_CTRL",       WM8904_BIAS_CTRL       },
  {"VMID_CTRL",       WM8904_VMID_CTRL       },
  {"MIC_BIAS_CTRL0",  WM8904_MIC_BIAS_CTRL0  },
  {"MIC_BIAS_CTRL1",  WM8904_MIC_BIAS_CTRL1  },
  {"ANALOG_ADC",      WM8904_ANALOG_ADC      },
  {"PM0",             WM8904_PM0             },
  {"PM2",             WM8904_PM2             },
  {"PM3",             WM8904_PM3             },
  {"PM6",             WM8904_PM6             },
  {"CLKRATE0",        WM8904_CLKRATE0        },
  {"CLKRATE1",        WM8904_CLKRATE1        },
  {"CLKRATE2",        WM8904_CLKRATE2        },
  {"AIF0",            WM8904_AIF0            },
  {"AIF1",            WM8904_AIF1            },
  {"AIF2",            WM8904_AIF2            },
  {"AIF3",            WM8904_AIF3            },
  {"DAC_VOL_LEFT",    WM8904_DAC_VOL_LEFT    },
  {"DAC_VOL_RIGHT",   WM8904_DAC_VOL_RIGHT   },
  {"DAC_DIGI0",       WM8904_DAC_DIGI0       },
  {"DAC_DIGI1",       WM8904_DAC_DIGI1       },
  {"ADC_VOL_LEFT",    WM8904_ADC_VOL_LEFT    },
  {"ADC_VOL_RIGHT",   WM8904_ADC_VOL_RIGHT   },
  {"ADC_DIGI",        WM8904_ADC_DIGI        },
  {"MIC_DIGI",        WM8904_MIC_DIGI        },
  {"DRC0",            WM8904_DRC0            },
  {"DRC1",            WM8904_DRC1            },
  {"DRC2",            WM8904_DRC2            },
  {"DRC3",            WM8904_DRC3            },
  {"ANA_LEFT_IN0",    WM8904_ANA_LEFT_IN0    },
  {"ANA_RIGHT_IN0",   WM8904_ANA_RIGHT_IN0   },
  {"ANA_LEFT_IN1",    WM8904_ANA_LEFT_IN1    },
  {"ANA_RIGHT_IN1",   WM8904_ANA_RIGHT_IN1   },
  {"ANA_LEFT_OUT1",   WM8904_ANA_LEFT_OUT1   },
  {"ANA_RIGHT_OUT1",  WM8904_ANA_RIGHT_OUT1  },
  {"ANA_LEFT_OUT2",   WM8904_ANA_LEFT_OUT2   },
  {"ANA_RIGHT_OUT2",  WM8904_ANA_RIGHT_OUT2  },
  {"ANA_OUT12_ZC",    WM8904_ANA_OUT12_ZC    },
  {"DC_SERVO0",       WM8904_DC_SERVO0       },
  {"DC_SERVO1",       WM8904_DC_SERVO1       },
  {"DC_SERVO2",       WM8904_DC_SERVO2       },
  {"DC_SERVO4",       WM8904_DC_SERVO4       },
  {"DC_SERVO5",       WM8904_DC_SERVO5       },
  {"DC_SERVO6",       WM8904_DC_SERVO6       },
  {"DC_SERVO7",       WM8904_DC_SERVO7       },
  {"DC_SERVO8",       WM8904_DC_SERVO8       },
  {"DC_SERVO9",       WM8904_DC_SERVO9       },
  {"DC_SERVO_RDBACK", WM8904_DC_SERVO_RDBACK },
  {"ANA_HP0",         WM8904_ANA_HP0         },
  {"ANA_LINEOUT0",    WM8904_ANA_LINEOUT0    },
  {"CHG_PUMP0",       WM8904_CHG_PUMP0       },
  {"CLASS_W0",        WM8904_CLASS_W0        },
  {"WR_SEQ0",         WM8904_WR_SEQ0         },
  {"WR_SEQ1",         WM8904_WR_SEQ1         },
  {"WR_SEQ2",         WM8904_WR_SEQ2         },
  {"WR_SEQ3",         WM8904_WR_SEQ3         },
  {"WR_SEQ4",         WM8904_WR_SEQ4         },
  {"FLL_CTRL1",       WM8904_FLL_CTRL1       },
  {"FLL_CTRL2",       WM8904_FLL_CTRL2       },
  {"FLL_CTRL3",       WM8904_FLL_CTRL3       },
  {"FLL_CTRL4",       WM8904_FLL_CTRL4       },
  {"FLL_CTRL5",       WM8904_FLL_CTRL5       },
  {"GPIO_CTRL1",      WM8904_GPIO_CTRL1      },
  {"GPIO_CTRL2",      WM8904_GPIO_CTRL2      },
  {"GPIO_CTRL3",      WM8904_GPIO_CTRL3      },
  {"GPIO_CTRL4",      WM8904_GPIO_CTRL4      },
  {"DIGI_PULLS",      WM8904_DIGI_PULLS      },
  {"INT_STATUS",      WM8904_INT_STATUS      },
  {"INT_MASK",        WM8904_INT_MASK        },
  {"INT_POL",         WM8904_INT_POL         },
  {"INT_DEBOUNCE",    WM8904_INT_DEBOUNCE    },
  {"EQ1",             WM8904_EQ1             },
  {"EQ2",             WM8904_EQ2             },
  {"EQ3",             WM8904_EQ3             },
  {"EQ4",             WM8904_EQ4             },
  {"EQ5",             WM8904_EQ5             },
  {"EQ6",             WM8904_EQ6             },
  {"EQ7",             WM8904_EQ7             },
  {"EQ8",             WM8904_EQ8             },
  {"EQ9",             WM8904_EQ9             },
  {"EQ10",            WM8904_EQ10            },
  {"EQ11",            WM8904_EQ11            },
  {"EQ12",            WM8904_EQ12            },
  {"EQ13",            WM8904_EQ13            },
  {"EQ14",            WM8904_EQ14            },
  {"EQ15",            WM8904_EQ15            },
  {"EQ16",            WM8904_EQ16            },
  {"EQ17",            WM8904_EQ17            },
  {"EQ18",            WM8904_EQ18            },
  {"EQ19",            WM8904_EQ19            },
  {"EQ20",            WM8904_EQ20            },
  {"EQ21",            WM8904_EQ21            },
  {"EQ22",            WM8904_EQ22            },
  {"EQ23",            WM8904_EQ23            },
  {"EQ24",            WM8904_EQ24            },
  {"ADC_TEST",        WM8904_ADC_TEST        },
  {"FLL_NCO_TEST0",   WM8904_FLL_NCO_TEST0   },
  {"FLL_NCO_TEST1",   WM8904_FLL_NCO_TEST1   }
};

#  define WM8904_NREGISTERS (sizeof(g_wm8904_debug)/sizeof(struct wb8904_regdump_s))
#endif /* CONFIG_WM8904_REGDUMP */

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

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

/****************************************************************************
 * Name: wm8904_dump_registers
 *
 * Description:
 *   Dump the contents of all WM8904 registers to the syslog device
 *
 * Input Parameters:
 *   dev - The device instance returned by wm8904_initialize
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

#ifdef CONFIG_WM8904_REGDUMP
void wm8904_dump_registers(FAR struct audio_lowerhalf_s *dev,
                           FAR const char *msg)
{
  int i;

  syslog(LOG_INFO, "WM8904 Registers: %s\n", msg);
  for (i = 0; i < WM8904_NREGISTERS; i++)
    {
      syslog(LOG_INFO, "%16s[%02x]: %04x\n",
             g_wm8904_debug[i].regname, g_wm8904_debug[i].regaddr,
             wm8904_readreg((FAR struct wm8904_dev_s *)dev,
                            g_wm8904_debug[i].regaddr));
    }
}
#endif /* CONFIG_WM8904_REGDUMP */

/****************************************************************************
 * Name: wm8904_clock_analysis
 *
 * Description:
 *   Analyze the settings in the clock chain and dump to syslog.
 *
 * Input Parameters:
 *   dev - The device instance returned by wm8904_initialize
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

#ifdef CONFIG_WM8904_CLKDEBUG
void wm8904_clock_analysis(FAR struct audio_lowerhalf_s *dev,
                           FAR const char *msg)
{
  FAR struct wm8904_dev_s *priv = (FAR struct wm8904_dev_s *)dev;
  uint32_t sysclk;
  uint32_t bclk;
  uint32_t lrclk;
  uint16_t regval;
  unsigned int tmp;
  double ftmp;

  syslog(LOG_INFO, "WM8904 Clock Analysis: %s\n", msg);
  DEBUGASSERT(priv && priv->lower);
  syslog(LOG_INFO, "  MCLK:            %lu Hz\n",
         (unsigned long)priv->lower->mclk);

  /* Is the SYSCLK source the FLL?  Or MCK? */

  regval = wm8904_readreg(priv, WM8904_CLKRATE2);
  if ((regval & WM8904_SYSCLK_SRC) == WM8904_SYSCLK_SRCMCLK)
    {
      /* The SYSCLK divider bypasses the FLL and takes its input
       * directly from MCLK.
       */

      sysclk = priv->lower->mclk;
      syslog(LOG_INFO, "  SYSCLK Source:   MCLK (%s)\n",
             (regval & WM8904_MCLK_INV) != 0 ? "inverted" : "not inverted");
    }
  else
    {
      uint32_t fref;
      uint32_t fvco;
      uint32_t fout;
      unsigned int outdiv;
      unsigned int frndx;
      unsigned int flln;
      unsigned int fllk;
      unsigned int fratio;
      double nk;

      /* Assume that the Fref input to the FLL is MCLK */

      fref = priv->lower->mclk;

      /* Now get the real FLL input source */

      regval = wm8904_readreg(priv, WM8904_FLL_CTRL5);
      switch (regval & WM8904_FLL_CLK_REF_SRC_MASK)
        {
          case WM8904_FLL_CLK_REF_SRC_MCLK:
            syslog(LOG_INFO, "  FLL Source:      MCLK\n");
            break;

          case WM8904_FLL_CLK_REF_SRC_BCLK:
            syslog(LOG_INFO, "  ERROR: FLL source is BCLK: %04x\n",
                   regval);
            break;

          case WM8904_FLL_CLK_REF_SRC_LRCLK:
            syslog(LOG_INFO, "  ERROR: FLL source is LRCLK: %04x\n",
                   regval);
            break;

          default:
            syslog(LOG_INFO, "  ERROR: Unrecognized FLL source: %04x\n",
                   regval);
        }

      syslog(LOG_INFO, "  Fref:            %lu Hz (before divider)\n",
             fref);
      switch (regval & WM8904_FLL_CLK_REF_DIV_MASK)
        {
          case WM8904_FLL_CLK_REF_DIV1:
            syslog(LOG_INFO, "  FLL_CLK_REF_DIV: 1\n");
            break;

          case WM8904_FLL_CLK_REF_DIV2:
            syslog(LOG_INFO, "  FLL_CLK_REF_DIV: 2\n");
            fref >>= 1;
            break;

          case WM8904_FLL_CLK_REF_DIV4:
            syslog(LOG_INFO, "  FLL_CLK_REF_DIV: 4\n");
            fref >>= 2;
            break;

          case WM8904_FLL_CLK_REF_DIV8:
            syslog(LOG_INFO, "  FLL_CLK_REF_DIV: 8\n");
            fref >>= 3;
            break;
        }

      syslog(LOG_INFO, "  Fref:            %lu Hz (after divider)\n", fref);

      regval = wm8904_readreg(priv, WM8904_FLL_CTRL2);
      frndx = (regval & WM8904_FLL_FRATIO_MASK) >>
                        WM8904_FLL_FRATIO_SHIFT;
      tmp = (regval & WM8904_FLL_CTRL_RATE_MASK) >>
                      WM8904_FLL_CTRL_RATE_SHIFT;
      outdiv = ((regval & WM8904_FLL_OUTDIV_MASK) >>
                          WM8904_FLL_OUTDIV_SHIFT) + 1;

      syslog(LOG_INFO, "  FLL_CTRL_RATE:   Fvco / %u\n", tmp + 1);

      regval = wm8904_readreg(priv, WM8904_FLL_CTRL4);
      flln   = (regval & WM8904_FLL_N_MASK) >> WM8904_FLL_N_SHIFT;
      tmp    = (regval & WM8904_FLL_GAIN_MASK) >> WM8904_FLL_GAIN_SHIFT;

      syslog(LOG_INFO, "  FLL_GAIN:        %u\n", (1 << tmp));

      fllk   = wm8904_readreg(priv, WM8904_FLL_CTRL3);
      nk     = (double)flln + ((double)fllk / 65536.0);
      fratio = g_fllratio[frndx];

      syslog(LOG_INFO, "  FLL_FRATIO:      %u\n", fratio);
      syslog(LOG_INFO, "  FLL_OUTDIV:      %u\n", outdiv);
      syslog(LOG_INFO, "  FLL_N.K:         %u.%05u\n", flln, fllk);

      ftmp = nk * (double)fref * (double)fratio;
      fvco = (uint32_t)ftmp;

      syslog(LOG_INFO, "  Fvco:            %lu Hz\n", (unsigned long)fvco);

      fout = fvco / outdiv;
      syslog(LOG_INFO, "  Fout:            %lu Hz\n", (unsigned long)fout);

      regval = wm8904_readreg(priv, WM8904_FLL_CTRL1);

      syslog(LOG_INFO, "  FLL_FRACN_ENA:   %s\n",
             (regval & WM8904_FLL_FRACN_ENA) != 0 ? "Enabled" : "Disabled");
      syslog(LOG_INFO, "  FLL_OSC_ENA:     %s\n",
             (regval & WM8904_FLL_OSC_ENA) != 0 ? "Enabled" : "Disabled");
      syslog(LOG_INFO, "  FLL_ENA:         %s\n",
             (regval & WM8904_FLL_ENA) != 0 ? "Enabled" : "Disabled");

      if ((regval & WM8904_FLL_ENA) == 0)
        {
          syslog(LOG_INFO, "  No SYSCLK\n");
          return;
        }

      sysclk = fout;
    }

  syslog(LOG_INFO, "  SYSCLK:          %lu Hz (before divider)\n",
         (unsigned long)sysclk);

  regval = wm8904_readreg(priv, WM8904_CLKRATE0);
  if ((regval & WM8904_MCLK_DIV) == WM8904_MCLK_DIV1)
    {
      syslog(LOG_INFO, "  MCLK_DIV:        1\n");
    }
  else
    {
      syslog(LOG_INFO, "  MCLK_DIV:        2\n");
      sysclk >>= 1;
    }

  syslog(LOG_INFO, "  SYSCLK:          %lu (after divider)\n",
        (unsigned long)sysclk);

  regval = wm8904_readreg(priv, WM8904_CLKRATE2);

  syslog(LOG_INFO, "  CLK_SYS_ENA:     %s\n",
         (regval & WM8904_CLK_SYS_ENA) != 0 ? "Enabled" : "Disabled");

  if ((regval & WM8904_CLK_SYS_ENA) == 0)
    {
      syslog(LOG_INFO, "  No SYSCLK\n");
      return;
    }

  regval = wm8904_readreg(priv, WM8904_AIF2);
  tmp    = (regval & WM8904_BCLK_DIV_MASK) >> WM8904_BCLK_DIV_SHIFT;
  tmp    = g_sysclk_scaleb1[tmp];
  ftmp   = (double)tmp / 2.0;

  syslog(LOG_INFO, "  BCLK_DIV:        SYSCLK / %u.%01u\n",
         (unsigned int)(tmp >> 1), (unsigned int)(5 * (tmp & 1)));

  bclk = (uint32_t)(sysclk / ftmp);

  syslog(LOG_INFO, "  BCLK:            %lu Hz\n", (unsigned long)bclk);

  regval = wm8904_readreg(priv, WM8904_AIF1);
  syslog(LOG_INFO, "  BCLK_DIR:        %s\n",
         (regval & WM8904_BCLK_DIR) != 0 ? "Output" : "Input");

  regval = wm8904_readreg(priv, WM8904_AIF3);
  tmp = (regval & WM8904_LRCLK_RATE_MASK) >> WM8904_LRCLK_RATE_SHIFT;

  lrclk = bclk / tmp;

  syslog(LOG_INFO, "  LRCLK_RATE:      BCLK / %lu\n", (unsigned long)tmp);
  syslog(LOG_INFO, "  LRCLK:           %lu Hz\n", (unsigned long)lrclk);
  syslog(LOG_INFO, "  LRCLK_DIR:       %s\n",
         (regval & WM8904_LRCLK_DIR) != 0 ? "Output" : "Input");
}
#endif /* CONFIG_WM8904_CLKDEBUG */