/****************************************************************************
 * drivers/power/battery/goldfish_battery.c
 *
 * 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.
 *
 ****************************************************************************/

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

#include <stdint.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/power/battery_gauge.h>
#include <nuttx/power/battery_ioctl.h>

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

#define GOLDFISH_BATTERY_READ(data, addr) \
    (*(FAR volatile uint32_t *)(data->reg_base + addr))
#define GOLDFISH_BATTERY_WRITE(data, addr, x) \
    (*(FAR volatile uint32_t *)(data->reg_base + addr) = (x))
#define GOLDFISH_GAUGE "/dev/charge/goldfish_battery"

/****************************************************************************
 * Private type
 ****************************************************************************/

enum
{
  /* Status register */

  BATTERY_INT_STATUS = 0x00,

  /* Set this to enable IRQ */

  BATTERY_INT_ENABLE = 0x04,
  BATTERY_AC_ONLINE = 0x08,
  BATTERY_STATUS = 0x0c,
  BATTERY_HEALTH = 0x10,
  BATTERY_PRESENT = 0x14,
  BATTERY_CAPACITY = 0x18,
  BATTERY_VOLTAGE = 0x1c,
  BATTERY_TEMP = 0x20,
  BATTERY_CHARGE_COUNTER = 0x24,
  BATTERY_VOLTAGE_MAX = 0x28,
  BATTERY_CURRENT_MAX = 0x2c,
  BATTERY_CURRENT_NOW = 0x30,
  BATTERY_CURRENT_AVG = 0x34,
  BATTERY_CHARGE_FULL_UAH = 0x38,
  BATTERY_CYCLE_COUNT = 0x40,
  BATTERY_STATUS_CHANGED = 1u << 0,
  AC_STATUS_CHANGED = 1u << 1,
  BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED,
};

enum
{
  POWER_SUPPLY_STATUS_UNKNOWN,
  POWER_SUPPLY_STATUS_CHARGING,
  POWER_SUPPLY_STATUS_DISCHARGING,
  POWER_SUPPLY_STATUS_NOT_CHARGING,
  POWER_SUPPLY_STATUS_FULL,
};

struct goldfish_battery_data_s
{
  FAR void *reg_base;
  int irq;
  struct battery_gauge_dev_s battery;
  struct work_s work;
};

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

static int goldfish_charger_online(FAR struct battery_gauge_dev_s *dev,
                                   FAR bool *status);
static int goldfish_battery_state(FAR struct battery_gauge_dev_s *dev,
                                  FAR int *status);
static int goldfish_battery_voltage(FAR struct battery_gauge_dev_s *dev,
                                    FAR b16_t *value);
static int  goldfish_battery_capacity(FAR struct battery_gauge_dev_s *dev,
                                      FAR b16_t *value);
static int goldfish_battery_current(FAR struct battery_gauge_dev_s *dev,
                                    FAR b16_t *value);
static int goldfish_battery_temp(FAR struct battery_gauge_dev_s *dev,
                                 FAR b8_t *value);

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

static const struct battery_gauge_operations_s g_goldfish_gauge_ops =
{
  goldfish_battery_state,
  goldfish_charger_online,
  goldfish_battery_voltage,
  goldfish_battery_capacity,
  goldfish_battery_current,
  goldfish_battery_temp,
  NULL,
};

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

static int goldfish_charger_online(FAR struct battery_gauge_dev_s *dev,
                                   FAR bool *status)
{
  FAR struct goldfish_battery_data_s *priv =
      container_of(dev, struct goldfish_battery_data_s, battery);

  *status = GOLDFISH_BATTERY_READ(priv, BATTERY_AC_ONLINE) & 0x1;
  return OK;
}

static int goldfish_battery_state(FAR struct battery_gauge_dev_s *dev,
                                  FAR int *status)
{
  FAR struct goldfish_battery_data_s *priv =
      container_of(dev, struct goldfish_battery_data_s, battery);
  uint32_t regval;

  regval = GOLDFISH_BATTERY_READ(priv, BATTERY_STATUS);

  switch (regval)
  {
    case POWER_SUPPLY_STATUS_UNKNOWN:
      *status = BATTERY_UNKNOWN;
      break;
    case POWER_SUPPLY_STATUS_CHARGING:
      *status = BATTERY_CHARGING;
      break;
    case POWER_SUPPLY_STATUS_DISCHARGING:
      *status = BATTERY_DISCHARGING;
      break;
    case POWER_SUPPLY_STATUS_NOT_CHARGING:
      *status = BATTERY_IDLE;
      break;
    case POWER_SUPPLY_STATUS_FULL:
      *status = BATTERY_FULL;
      break;
    default:
      *status = BATTERY_UNKNOWN;
      break;
  }

  return OK;
}

static int goldfish_battery_voltage(FAR struct battery_gauge_dev_s *dev,
                                    FAR b16_t *value)
{
  FAR struct goldfish_battery_data_s *data =
      container_of(dev, struct goldfish_battery_data_s, battery);
  uint32_t regval;
  float vol;

  /* BATTERY_VOLTAGE units is µV */

  regval = GOLDFISH_BATTERY_READ(data, BATTERY_VOLTAGE);

  /* convert to unit V and fill b16_t */

  vol = regval / 1000000.0f;
  *value = ftob16(vol);
  return OK;
}

static int  goldfish_battery_capacity(FAR struct battery_gauge_dev_s *dev,
                                      FAR b16_t *value)
{
  FAR struct goldfish_battery_data_s *data =
      container_of(dev, struct goldfish_battery_data_s, battery);
  uint32_t regval;

  /* BATTERY_CAPACITY units is percentage */

  regval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY);
  *value = uitoub16(regval);
  return OK;
}

static int goldfish_battery_current(FAR struct battery_gauge_dev_s *dev,
                                    FAR b16_t *value)
{
  FAR struct goldfish_battery_data_s *data =
      container_of(dev, struct goldfish_battery_data_s, battery);
  uint32_t regval;
  float current;

  /* BATTERY_CURRENT_NOW units is µA */

  regval = GOLDFISH_BATTERY_READ(data, BATTERY_CURRENT_NOW);

  /* convert to unit mA and fill b16_t */

  current = regval / 1000.0f;
  *value = ftob16(current);
  return OK;
}

static int goldfish_battery_temp(FAR struct battery_gauge_dev_s *dev,
                                 FAR b8_t *value)
{
  FAR struct goldfish_battery_data_s *data =
      container_of(dev, struct goldfish_battery_data_s, battery);
  int32_t regval;
  float temp;

  /* BATTERY_TEMP units is 0.1 celsuis */

  regval = GOLDFISH_BATTERY_READ(data, BATTERY_TEMP);

  /* convert to unit celsuis and fill b16_t */

  temp = regval / 10.0f;
  *value = ftob8(temp);
  return OK;
}

static void goldfish_battery_work(FAR void *arg)
{
  FAR struct goldfish_battery_data_s *data = arg;
  uint32_t mask = BATTERY_STATE_CHANGED | BATTERY_VOLTAGE_CHANGED |
                  BATTERY_CURRENT_CHANGED | BATTERY_CAPACITY_CHANGED |
                  BATTERY_TEMPERATURE_CHANGED | BATTERY_ONLINE_CHANGED;
  int ret;

  ret = battery_gauge_changed(&data->battery, mask);
  if (ret < 0)
    {
      baterr("goldfish battery changed failed %d\n", ret);
    }

  return;
}

static int goldfish_battery_interrupt(int irq, FAR void *context, void *arg)
{
  FAR struct goldfish_battery_data_s *data = arg;
  uint32_t status;
  int ret;

  /* read status flags, which will clear the interrupt */

  status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS);
  status &= BATTERY_INT_MASK;
  if (status)
    {
      ret = work_queue(HPWORK, &data->work,
                       goldfish_battery_work, data, 0);
      if (ret < 0)
        {
          baterr("battery interrupt %"PRIu32
                 "work_battery ret:%d\n", status, ret);
        }
    }

  return OK;
}

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

int goldfish_battery_register(FAR void *regs, int irq)
{
  int ret;
  FAR struct goldfish_battery_data_s *data;

  if (NULL == regs || irq < 0)
    {
      baterr(" regs:%p irq:%d\n", regs, irq);
      return -EINVAL;
    }

  data = kmm_zalloc(sizeof(struct goldfish_battery_data_s));
  if (NULL == data)
    {
      baterr(" no enough memory\n");
      return -ENOMEM;
    }

  data->irq = irq;
  data->reg_base = regs;
  ret = irq_attach(data->irq, goldfish_battery_interrupt, data);
  if (ret < 0)
    {
      baterr(" attach irq %d failed\n", irq);
      goto fail;
    }

  data->battery.ops = &g_goldfish_gauge_ops;
  ret = battery_gauge_register(GOLDFISH_GAUGE, &data->battery);
  if (ret < 0)
    {
      baterr("battery_gauge_register %s failed", GOLDFISH_GAUGE);
      irq_detach(data->irq);
      goto fail;
    }

  GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK);
  up_enable_irq(data->irq);
  batinfo("goldfish_battery_register over");
  return 0;
fail:
  kmm_free(data);
  return ret;
}