/****************************************************************************
 * drivers/regmap/regmap.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 <nuttx/regmap/regmap.h>
#include <nuttx/lib/math32.h>
#include <nuttx/kmalloc.h>

#include <debug.h>

#include "internal.h"

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

#define REGMAP_ALIGNED(n, a) (((uint32_t)(n) & ((a) - 1)) == 0)
#define REGMAP_DIVUP(n,d) (((n) + (d) - 1) / (d))

#define REGMAP_DEFAULT_BIT 8

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

static void regmap_lock_unlock_none(FAR void *context)
{
}

static void regmap_lock_mutex(FAR void *context)
{
  FAR struct regmap_s *map = context;
  nxmutex_lock(&map->mutex[0]);
}

static void regmap_unlock_mutex(FAR void *context)
{
  FAR struct regmap_s *map = context;
  nxmutex_unlock(&map->mutex[0]);
}

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

/****************************************************************************
 * Name: regmap_init
 *
 * Description:
 *   Initialize the internal configuration of regmap. The first parameter
 *   must be the handle of the bus, and the second parameter is the
 *   configuration parameter of the bus. Finally, these two parameters will
 *   be transparent to the corresponding bus.
 *
 * Input Parameters:
 *   dev    - device handle.
 *   bus    - device configuration.
 *   config - regmap configuration.
 *
 * Returned Value:
 *   Description of the value returned by this function (if any),
 *   including an enumeration of all possible error values.
 *
 * Assumptions/Limitations:
 *   None.
 *
 ****************************************************************************/

FAR struct regmap_s *regmap_init(FAR struct regmap_bus_s *bus,
                                 FAR const struct regmap_config_s *config)
{
  FAR struct regmap_s *map;

  if (config == NULL || bus == NULL)
    {
      return NULL;
    }

  if (config->disable_locking)
    {
      map = kmm_zalloc(sizeof(*map));
      if (map == NULL)
        {
          return NULL;
        }

      map->lock   = regmap_lock_unlock_none;
      map->unlock = regmap_lock_unlock_none;
    }
  else
    {
      map = kmm_zalloc(sizeof(*map) + sizeof(mutex_t));
      if (map == NULL)
        {
          return NULL;
        }

      nxmutex_init(&map->mutex[0]);
      map->lock   = regmap_lock_mutex;
      map->unlock = regmap_unlock_mutex;
    }

  if (config->reg_stride != 0)
    {
      map->reg_stride = config->reg_stride;
    }
  else
    {
      map->reg_stride = 1;
    }

  map->reg_bytes = REGMAP_DIVUP(config->reg_bits, REGMAP_DEFAULT_BIT);
  map->val_bytes = REGMAP_DIVUP(config->val_bits, REGMAP_DEFAULT_BIT);

  map->bus = bus;
  map->reg_read  = bus->reg_read;
  map->reg_write = bus->reg_write;
  map->read  = bus->read;
  map->write = bus->write;

  return map;
}

/****************************************************************************
 * Name: regmap_write
 *
 * Description:
 *   Regmap write, called after initializing the regmap bus device.
 *   the first parameter is regmap pointer.
 *
 * Input Parameters:
 *   map - regmap handler, from regmap bus init function return.
 *   reg - register address to be write.
 *   val - write data.
 *
 * Returned Value:
 *   Description of the value returned by this function (if any),
 *   including an enumeration of all possible error values.
 *
 * Assumptions/Limitations:
 *   None.
 *
 ****************************************************************************/

int regmap_write(FAR struct regmap_s *map, unsigned int reg,
                 unsigned int val)
{
  int ret;

  DEBUGASSERT(REGMAP_ALIGNED(reg, map->reg_stride));

  map->lock(map);

  ret = map->reg_write(map->bus, reg, val);

  map->unlock(map);

  return ret;
}

/****************************************************************************
 * Name: regmap_bulk_write
 *
 * Description:
 *   Regmap bulk write, called after initializing the regmap bus device.
 *   the first parameter is regmap pointer.
 *
 * Input Parameters:
 *   map       - regmap handler, from regmap bus init function return.
 *   reg       - register address to be write.
 *   val       - write data buffer.
 *   val_count - write data buffer size.
 *
 * Returned Value:
 *   Description of the value returned by this function (if any),
 *   including an enumeration of all possible error values.
 *
 * Assumptions/Limitations:
 *   None.
 *
 ****************************************************************************/

int regmap_bulk_write(FAR struct regmap_s *map, unsigned int reg,
                      FAR const void *val, unsigned int val_count)
{
  size_t val_bytes = map->val_bytes;
  int ret = -ENOSYS;
  unsigned int ival;
  FAR uint8_t *ptr;
  int i;

  DEBUGASSERT(REGMAP_ALIGNED(reg, map->reg_stride));

  map->lock(map);
  if (map->write != NULL)
    {
      ret = map->write(map->bus, val, val_bytes * val_count);
      goto out;
    }

  for (i = 0; i < val_count; i++)
    {
      ptr = (FAR uint8_t *)val + (i * val_bytes);
      switch (val_bytes)
        {
          case 1:
            ival = *(FAR uint8_t *)ptr;
            break;
          case 2:
            ival = *(FAR uint16_t *)ptr;
            break;
          case 4:
            ival = *(FAR uint32_t *)ptr;
            break;
          default:
            ret = -EINVAL;
            goto out;
        }

      ret = map->reg_write(map->bus, reg + (i * map->reg_stride), ival);
      if (ret < 0)
        {
          break;
        }
    }

out:
  map->unlock(map);
  return ret;
}

/****************************************************************************
 * Name: regmap_read
 *
 * Description:
 *   Regmap read, called after initializing the regmap bus device.
 *   the first parameter is regmap pointer.
 *
 * Input Parameters:
 *   map - regmap handler, from regmap bus init function return.
 *   reg - register address to be read.
 *   val - read data buffer.
 *
 * Returned Value:
 *   Description of the value returned by this function (if any),
 *   including an enumeration of all possible error values.
 *
 * Assumptions/Limitations:
 *   None.
 *
 ****************************************************************************/

int regmap_read(FAR struct regmap_s *map, unsigned int reg, FAR void *val)
{
  int ret;

  DEBUGASSERT(REGMAP_ALIGNED(reg, map->reg_stride));

  map->lock(map);

  ret = map->reg_read(map->bus, reg, val);

  map->unlock(map);
  return ret;
}

/****************************************************************************
 * Name: regmap_bulk_read
 *
 * Description:
 *   Regmap bulk read, called after initializing the regmap bus device.
 *   the first parameter is regmap pointer.
 *
 * Input Parameters:
 *   map       - regmap handler, from regmap bus init function return.
 *   reg       - register address to be read.
 *   val       - read data buffer.
 *   val_count - read data buffer size.
 *
 * Returned Value:
 *   Description of the value returned by this function (if any),
 *   including an enumeration of all possible error values.
 *
 * Assumptions/Limitations:
 *   None.
 *
 ****************************************************************************/

int regmap_bulk_read(FAR struct regmap_s *map, unsigned int reg,
                     FAR void *val, unsigned int val_count)
{
  FAR uint32_t *u32 = val;
  FAR uint16_t *u16 = val;
  FAR uint8_t  *u8  = val;
  unsigned int ival;
  int ret = -ENOSYS;
  int i;

  DEBUGASSERT(REGMAP_ALIGNED(reg, map->reg_stride));

  map->lock(map);

  if (map->read != NULL)
    {
      ret = map->read(map->bus, &reg, map->reg_bytes, val, val_count);
    }
  else
    {
      for (i = 0; i < val_count; i++)
        {
          ret = map->reg_read(map->bus, reg + (i * map->reg_stride), &ival);
          if (ret < 0)
            {
              break;
            }

          switch (map->val_bytes)
            {
              case 4:
                u32[i] = ival;
                break;
              case 2:
                u16[i] = ival;
                break;
              case 1:
                u8[i] = ival;
                break;
              default:
                map->unlock(map);
                return -EINVAL;
            }
        }
    }

  map->unlock(map);

  return ret;
}

/****************************************************************************
 * Name: regmap_exit
 *
 * Description:
 *   Free a previously allocated register map
 *
 * Input Parameters:
 *   map - regmap handler, from regmap bus init function return.
 *
 * Assumptions/Limitations:
 *   None.
 ****************************************************************************/

void regmap_exit(FAR struct regmap_s *map)
{
  if (!map->disable_locking)
    {
      nxmutex_destroy(&map->mutex[0]);
    }

  if (map->bus->exit != NULL)
    {
      map->bus->exit(map->bus);
    }

  kmm_free(map->bus);
  kmm_free(map);
}