/****************************************************************************
 * drivers/power/mcp73871.c
 * Lower half driver for Microchip MCP73871 battery charger
 *
 *   Copyright (C) 2018 Alan Carvalho de Assis. All rights reserved.
 *   Author: Alan Carvalho de Assis <acassis@gmail.com>
 *
 *   Copyright (C) 2016 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * 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.
 *
 ****************************************************************************/

/* The MCP73871 is a standalone battery charger, so this driver is not
 * supposed to do much things, it will only report faults and battery status.
 */

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

#include <nuttx/config.h>

#include <sys/types.h>

#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/power/battery_charger.h>
#include <nuttx/power/battery_ioctl.h>
#include <nuttx/power/mcp73871.h>

/* This driver requires:
 *
 * CONFIG_BATTERY_CHARGER - Upper half battery driver support
 */

#if defined(CONFIG_BATTERY_CHARGER)

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

/* Debug ********************************************************************/

#ifdef CONFIG_DEBUG_MCP73871
#  define baterr  _err
#  define batreg  _err
#else
#  ifdef CONFIG_CPP_HAVE_VARARGS
#    define baterr(x...)
#    define batreg(x...)
#  else
#    define baterr (void)
#    define batreg (void)
#  endif
#endif

/****************************************************************************
 * Private
 ****************************************************************************/

struct mcp73871_dev_s
{
  /* The common part of the battery driver visible to the upper-half driver */

  FAR const struct battery_charger_operations_s *ops; /* Battery operations */

  sem_t batsem;                /* Enforce mutually exclusive access */

  /* MCP73871 configuration helpers */

  FAR struct mcp73871_config_s *config;
};

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

/* Battery driver lower half methods */

static int mcp73871_state(FAR struct battery_charger_dev_s *dev,
             FAR int *status);
static int mcp73871_health(FAR struct battery_charger_dev_s *dev,
             FAR int *health);
static int mcp73871_online(FAR struct battery_charger_dev_s *dev,
             FAR bool *status);
static int mcp73871_voltage(FAR struct battery_charger_dev_s *dev,
             int value);
static int mcp73871_current(FAR struct battery_charger_dev_s *dev,
             int value);
static int mcp73871_input_current(FAR struct battery_charger_dev_s *dev,
             int value);
static int mcp73871_operate(FAR struct battery_charger_dev_s *dev,
             intptr_t param);

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

static const struct battery_charger_operations_s g_mcp73871ops =
{
  mcp73871_state,
  mcp73871_health,
  mcp73871_online,
  mcp73871_voltage,
  mcp73871_current,
  mcp73871_input_current,
  mcp73871_operate
};

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

/****************************************************************************
 * Name: mcp73871_state
 *
 * Description:
 *   Return the current battery state
 *
 ****************************************************************************/

static int mcp73871_state(FAR struct battery_charger_dev_s *dev,
                          FAR int *status)
{
  FAR struct mcp73871_dev_s *priv = (FAR struct mcp73871_dev_s *)dev;
  uint8_t value = 0;

  /* Create a value with bits 2 1 0 = STAT1 | STAT2 | PG */

  value = (priv->config->read_stat1() << 2) |
          (priv->config->read_stat2() << 1) |
          (priv->config->read_pg()    << 0);

  switch (value)
  {
  case MCP73871_FAULT:
    *status = BATTERY_FAULT;
    break;

  case MCP73871_CHARGING:
    *status = BATTERY_CHARGING;
    break;

  case MCP73871_BAT_LOW:
    *status = BATTERY_DISCHARGING;
    break;

  case MCP73871_CHG_COMPLETE:
    *status = BATTERY_FULL;
    break;

  case MCP73871_NO_BATTERY:
    *status = BATTERY_FAULT;
    break;

  case MCP73871_NO_INPUT_PWR:
    *status = BATTERY_FAULT;
    break;

  default:
    *status = BATTERY_HEALTH_UNKNOWN;
    break;
  }

  return OK;
}

/****************************************************************************
 * Name: mcp73871_health
 *
 * Description:
 *   Return the current battery health state
 *
 * Note: if more than one fault happened the user needs to call this ioctl
 * again to read a new fault, repeat until receive a BATTERY_HEALTH_GOOD.
 *
 ****************************************************************************/

static int mcp73871_health(FAR struct battery_charger_dev_s *dev,
                           FAR int *health)
{
  FAR struct mcp73871_dev_s *priv = (FAR struct mcp73871_dev_s *)dev;
  uint8_t value = 0;

  /* Create a value with bits 2 1 0 = STAT1 | STAT2 | PG */

  value = (priv->config->read_stat1() << 2) |
          (priv->config->read_stat2() << 1) |
          (priv->config->read_pg()    << 0);

  switch (value)
  {
  case MCP73871_FAULT:
    *health = BATTERY_HEALTH_SAFE_TMR_EXP;
    break;

  case MCP73871_CHARGING:
  case MCP73871_CHG_COMPLETE:
    *health = BATTERY_HEALTH_GOOD;
    break;

  case MCP73871_NO_BATTERY:
    *health = BATTERY_HEALTH_DISCONNECTED;
    break;

  case MCP73871_BAT_LOW:
  case MCP73871_NO_INPUT_PWR:
  default:
    *health = BATTERY_HEALTH_UNKNOWN;
    break;
  }

  return OK;
}

/****************************************************************************
 * Name: mcp73871_online
 *
 * Description:
 *   Return true if the battery is online
 *
 ****************************************************************************/

static int mcp73871_online(FAR struct battery_charger_dev_s *dev,
                           FAR bool *status)
{
  /* There is no concept of online/offline in this driver */

  *status = true;
  return OK;
}

/****************************************************************************
 * Name: mcp73871_powersupply
 *
 * Description:
 *   Set the Power Supply Current Limit.
 *
 ****************************************************************************/

static inline int mcp73871_powersupply(FAR struct mcp73871_dev_s *priv,
                                       int current)
{
  /* This chip doesn't support setting current limit by software */

  return OK;
}

/****************************************************************************
 * Name: mcp73871_setvolt
 *
 * Description:
 *   Set the voltage level to charge the battery. Voltage value in mV.
 *
 ****************************************************************************/

static inline int mcp73871_setvolt(FAR struct mcp73871_dev_s *priv,
                                   int volts)
{
  /* This chip doesn't support setting voltage by software */

  return OK;
}

/****************************************************************************
 * Name: mcp73871_setcurr
 *
 * Description:
 *   Set the current to charge the battery. Current value in mA.
 *
 ****************************************************************************/

static inline int mcp73871_setcurr(FAR struct mcp73871_dev_s *priv,
                                   int current)
{
  /* This chip doesn't support setting current by software */

  return OK;
}

/****************************************************************************
 * Name: mcp73871_voltage
 *
 * Description:
 *   Set battery charger voltage
 *
 ****************************************************************************/

static int mcp73871_voltage(FAR struct battery_charger_dev_s *dev, int value)
{
  /* This chip doesn't support setting voltage by software */

  return OK;
}

/****************************************************************************
 * Name: mcp73871_current
 *
 * Description:
 *   Set the battery charger current rate for charging
 *
 ****************************************************************************/

static int mcp73871_current(FAR struct battery_charger_dev_s *dev, int value)
{
  /* This chip doesn't support setting current rate by software */

  return OK;
}

/****************************************************************************
 * Name: mcp73871_input_current
 *
 * Description:
 *   Set the power-supply input current limit
 *
 ****************************************************************************/

static int mcp73871_input_current(FAR struct battery_charger_dev_s *dev,
                                  int value)
{
  /* This chip doesn't support setting input current limit by software */

  return OK;
}

/****************************************************************************
 * Name: mcp73871_operate
 *
 * Description:
 *   Do miscellaneous battery ioctl()
 *
 ****************************************************************************/

static int mcp73871_operate(FAR struct battery_charger_dev_s *dev,
                            uintptr_t param)
{
  return -ENOSYS;
}

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

/****************************************************************************
 * Name: mcp73871_initialize
 *
 * Description:
 *   Initialize the BQ2425x battery driver and return an instance of the
 *   lower_half interface that may be used with battery_charger_register();
 *
 *   This driver requires:
 *
 *   CONFIG_BATTERY_CHARGER - Upper half battery driver support
 *
 * Input Parameters:
 *   config   - The config structure with function pointers to read/write.
 *
 * Returned Value:
 *   A pointer to the initialized lower-half driver instance.  A NULL
 *   pointer is returned on a failure to initialize the BQ2425x lower half.
 *
 ****************************************************************************/

FAR struct battery_charger_dev_s *
           mcp73871_initialize(FAR struct mcp73871_config_s *config)
{
  FAR struct mcp73871_dev_s *priv;

  /* Allocate the MCP73871 device structure */

  priv = (FAR struct mcp73871_dev_s *)
    kmm_zalloc(sizeof(struct mcp73871_dev_s));

  if (priv)
    {
      /* Initialize the MCP73871 device structure */

      nxsem_init(&priv->batsem, 0, 1);
      priv->ops    = &g_mcp73871ops;
      priv->config = config;

      /* Enable the battery charge */

      priv->config->set_chg_ce(true);
    }

  return (FAR struct battery_charger_dev_s *)priv;
}

#endif /* CONFIG_BATTERY_CHARGER */