/****************************************************************************
 * drivers/leds/lp503x.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/config.h>

#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/leds/lp503x.h>

#if defined(CONFIG_I2C) && defined(CONFIG_LP503X)

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

#ifdef CONFIG_DEBUG_LP503X
#  define lp503x_err(x, ...)        _err(x, ##__VA_ARGS__)
#  define lp503x_info(x, ...)       _info(x, ##__VA_ARGS__)
#else
#  define lp503x_err(x, ...)        uerr(x, ##__VA_ARGS__)
#  define lp503x_info(x, ...)       uinfo(x, ##__VA_ARGS__)
#endif

/****************************************************************************
 * Private Type Definitions
 ****************************************************************************/

enum lp503x_state
{
  LP503X_STATE_UNINIT = 0,
  LP503X_STATE_RESET,
  LP503X_STATE_CONFIGURED,
};

struct lp503x_dev_s
{
  struct  i2c_master_s  *i2c;
  uint8_t               i2c_addr;
  int                   i2c_freq;
  int                   count;

  /* device configuration/setup data */

  struct lp503x_config_s *lp503x_config;

  /* current state of the lp503x device */

  enum lp503x_state state;
};

/* A set of default config parameters as set in LED driver Kconfig */

struct lp503x_config_s config_default =
{
#ifdef CONFIG_LP503X_LOG_MODE
  .enable_log_mode          = 1,
#else
  .enable_log_mode          = 0,
#endif
#ifdef CONFIG_LP503X_POWER_SAVE
  .enable_power_save        = 1,
#else
  .enable_power_save        = 0,
#endif
#ifdef CONFIG_LP503X_DITHER_MODE
  .enable_pwm_dithering     = 1,
#else
  .enable_pwm_dithering     = 0,
#endif
#ifdef CONFIG_LP503X_MAX_CURRENT
  .set_max_current_35ma     = 1,
#else
  .set_max_current_35ma     = 0,
#endif
#ifdef CONFIG_LP503X_GLOBAL_SHUTDOWN
  .enable_all_led_shutdown  = 1,
#else
  .enable_all_led_shutdown  = 0,
#endif

  /* all leds will default to independent control, not bank control */

  .led_mode[0]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[1]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[2]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[3]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[4]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[5]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[6]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[7]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[8]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[9]  = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[10] = LP503X_LED_BANK_MODE_DISABLED,
  .led_mode[11] = LP503X_LED_BANK_MODE_DISABLED,
};

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

static int lp503x_i2c_write_reg(struct lp503x_dev_s *priv,
                                uint8_t const reg_addr,
                                uint8_t const reg_val);
static int lp503x_i2c_read_reg(struct lp503x_dev_s *priv,
                               uint8_t const reg_addr,
                               uint8_t *regval);
static int lp503x_open(struct file *filep);
static int lp503x_close(struct file *filep);
static int lp503x_ioctl(struct file *filep, int cmd,
                        unsigned long arg);
#ifdef CONFIG_DEBUG_LP503X
static int lp503x_dump_registers(struct lp503x_dev_s *priv,
                                 const char *msg);
#else
#  define lp503x_dump_registers(priv, msg);
#endif
static int lp503x_reset(struct lp503x_dev_s *priv);
static int lp503x_enable(struct lp503x_dev_s *priv, bool enable);
static int lp503x_set_rgbbrightness(struct lp503x_dev_s *priv, int led,
                                    int brightness);
static int lp503x_set_outcolour(struct lp503x_dev_s *priv, int led,
                                int colour);
static int lp503x_set_config(struct lp503x_dev_s *priv);
static int lp503x_set_bank_mode(struct lp503x_dev_s *priv);
static int lp503x_set_bank_colour(struct lp503x_dev_s *priv, char bank,
                                  int brightness);
static int lp503x_set_bank_brightness(struct lp503x_dev_s *priv,
                                      int brightness);

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

static const struct file_operations g_lp503x_fileops =
{
  lp503x_open,               /* open */
  lp503x_close,              /* close */
  NULL,                      /* read */
  NULL,                      /* write */
  NULL,                      /* seek */
  lp503x_ioctl,              /* ioctl */
};

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

/****************************************************************************
 * Name: lp503x_dumpregs
 *
 * Description:
 *   Dump the contents of all lp503x registers
 *
 * Input Parameters:
 *   priv - A reference to the lp503x peripheral state
 *   msg  - Message to print before the register data
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_DEBUG_LP503X
static int lp503x_dump_registers(struct lp503x_dev_s *priv,
                                 const char *msg)
{
  uint8_t val1;
  uint8_t val2;
  uint8_t val3;
  uint8_t val4;
  int ret;
  lp503x_info("lp503x Registers: %s\n", msg);
  ret = lp503x_i2c_read_reg(priv, LP503X_DEVICE_CONFIG0,   &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_DEVICE_CONFIG1,   &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED_CONFIG0,      &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED_CONFIG1,      &val4);
  lp503x_info
    ("Dev Config0:\t%02x Dev Conf1:\t%02x \
      LED Conf0:\t%02x: LED Conf1: \t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_BANK_BRIGHTNESS,  &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_BANK_A_COLOUR,    &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_BANK_B_COLOUR,    &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_BANK_C_COLOUR,    &val4);
  lp503x_info
    ("Bank Bright:\t%02x BankA Col:\t%02x \
      BankB Col:\t%02x: BankC Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_LED0_BRIGHTNESS,  &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED1_BRIGHTNESS,  &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED2_BRIGHTNESS,  &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED3_BRIGHTNESS,  &val4);
  lp503x_info
    ("LED0 Bright:\t%02x LED1 Col:\t%02x \
      LED2 Bright:\t%02x: LED3 Bright: %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_LED4_BRIGHTNESS,  &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED5_BRIGHTNESS,  &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED6_BRIGHTNESS,  &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED7_BRIGHTNESS,  &val4);
  lp503x_info
    ("LED4 Bright:\t%02x LED5 Bright:\t%02x \
      LED6 Bright:\t%02x: LED7 Bright: %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_LED8_BRIGHTNESS,  &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED9_BRIGHTNESS,  &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED10_BRIGHTNESS, &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_LED11_BRIGHTNESS, &val4);
  lp503x_info
    ("LED8 Bright:\t%02x LED9 Bright:\t%02x \
      LED10 Bright:\t%02x: LED11 Bright:%02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT0_COLOUR,      &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT1_COLOUR,      &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT2_COLOUR,      &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT3_COLOUR,      &val4);
  lp503x_info
    ("Out0 Col:\t%02x Out1 Col:\t%02x \
      Out2 Col:\t\t%02x  Out3 Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT4_COLOUR,      &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT5_COLOUR,      &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT6_COLOUR,      &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT7_COLOUR,      &val4);
  lp503x_info
    ("Out4 Col:\t%02x Out5 Col:\t%02x \
      Out6 Col:\t\t%02x  Out7 Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT8_COLOUR,      &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT9_COLOUR,      &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT10_COLOUR,     &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT11_COLOUR,     &val4);
  lp503x_info
     ("Out8 Col:\t%02x Out9 Col:\t%02x \
       Out10 Col:\t%02x  Out11 Col:\t %02x\n",
       val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT12_COLOUR,     &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT13_COLOUR,     &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT14_COLOUR,     &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT15_COLOUR,     &val4);
  lp503x_info
    ("Out12 Col:\t%02x Out13 Col:\t%02x \
      Out14 Col:\t%02x  Out15 Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT16_COLOUR,     &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT17_COLOUR,     &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT18_COLOUR,     &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT19_COLOUR,     &val4);
  lp503x_info
    ("Out16 Col:\t%02x Out17 Col:\t%02x \
      Out18 Col:\t%02x  Out19 Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT20_COLOUR,     &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT21_COLOUR,     &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT22_COLOUR,     &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT23_COLOUR,     &val4);
  lp503x_info
    ("Out20 Col:\t%02x Out21 Col:\t%02x \
      Out22 Col:\t%02x  Out23 Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT24_COLOUR,     &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT25_COLOUR,     &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT26_COLOUR,     &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT27_COLOUR,     &val4);
  lp503x_info
    ("Out24 Col:\t%02x Out25 Col:\t%02x \
      Out26 Col:\t%02x  Out27 Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT28_COLOUR,     &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT29_COLOUR,     &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT30_COLOUR,     &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT31_COLOUR,     &val4);
  lp503x_info
    ("Out28 Col:\t%02x Out29 Col:\t%02x \
      Out30 Col:\t%02x  Out31 Col:\t %02x\n",
      val1, val2, val3, val4);

  ret = lp503x_i2c_read_reg(priv, LP503X_OUT32_COLOUR,     &val1);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT33_COLOUR,     &val2);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT34_COLOUR,     &val3);
  ret = lp503x_i2c_read_reg(priv, LP503X_OUT35_COLOUR,     &val4);
  lp503x_info
    ("Out28 Col:\t%02x Out29 Col:\t%02x \
      Out30 Col:\t%02x  Out31 Col:\t %02x\n",
      val1, val2, val3, val4);

  return ret;
}

#endif
/****************************************************************************
 * Name: lp503x_i2c_write_reg
 *
 * Description:
 *   Write a single byte to one of the LP503X configuration registers.
 *
 ****************************************************************************/

static int lp503x_i2c_write_reg(struct lp503x_dev_s *priv,
                                uint8_t const reg_addr,
                                uint8_t const reg_val)
{
  struct i2c_config_s config;
  int ret;

  /* assemble the 2 byte message comprised of reg_addr and reg_val */

  uint8_t const BUFFER_SIZE = 2;
  uint8_t buffer[BUFFER_SIZE];

  buffer[0] = reg_addr;
  buffer[1] = reg_val;

  /* Setup up the I2C configuration */

  config.frequency = priv->i2c_freq;
  config.address   = priv->i2c_addr;
  config.addrlen   = 7;

  /* Write the register address followed by the data (no RESTART) */

  ledinfo("i2c addr: 0x%02X reg addr: 0x%02X value: 0x%02X\n",
          priv->i2c_addr, buffer[0], buffer[1]);

  ret = i2c_write(priv->i2c, &config, buffer, BUFFER_SIZE);
  if (ret < 0)
    {
      lederr("ERROR: i2c_write returned error code %d\n", ret);
      return ret;
    }

  return OK;
}

/****************************************************************************
 * Name: lp503x_i2c_read_reg
 *
 * Description:
 *   Read a single byte from one of the LP503X configuration registers.
 *
 ****************************************************************************/

static int lp503x_i2c_read_reg(struct lp503x_dev_s *priv,
                               uint8_t const reg_addr,
                               uint8_t *regval)
{
  struct i2c_config_s config;
  int ret;

  /* Setup up the I2C configuration */

  config.frequency = priv->i2c_freq;
  config.address   = priv->i2c_addr;
  config.addrlen   = 7;

  /* Write the register address followed by the data (no RESTART) */

  ret = i2c_write(priv->i2c, &config, &reg_addr, 1);
  ret = i2c_read(priv->i2c, &config, regval, 1);

  return ret;
}

/****************************************************************************
 * Name: lp503x_open
 *
 * Description:
 *   This function is called whenever a LP503X device is opened.
 *
 ****************************************************************************/

static int lp503x_open(struct file *filep)
{
  struct inode *inode = filep->f_inode;
  struct lp503x_dev_s *priv = inode->i_private;
  int ret;

  ledinfo("INFO: Opening, resetting and enabling the LP503X for business\n");

  /* reset and enable the device */

  /* means the device was possibly never regsitered? */

  if (priv->state == LP503X_STATE_UNINIT)
    {
      return -ENODEV;
    }
  else if (priv->state == LP503X_STATE_RESET)
    {
      ret = lp503x_enable(priv, true);

      if (ret != 0)
        {
          lederr("ERROR: unable to enable lp503x\n");
          return -EIO;
        }
      else
        {
          /* use device defaults */

          priv->lp503x_config = &config_default;
        }

      ret = lp503x_set_config(priv);
      if (ret != 0)
        {
          lederr("ERROR: Unable to set device config: %d\n", ret);
          return -EIO;
        }

      priv->state = LP503X_STATE_CONFIGURED;
    }

  lp503x_dump_registers(priv, "File Open");

  return ret;
}

/****************************************************************************
 * Name: lp503x_close
 *
 * Description:
 *   This function is called whenever a LP503X device is closed.
 *
 ****************************************************************************/

static int lp503x_close(struct file *filep)
{
  int ret;

  struct inode *inode = filep->f_inode;
  struct lp503x_dev_s *priv = inode->i_private;

  ret = lp503x_enable(priv, false);
  if (ret < 0)
    {
      lederr("ERROR: Could not disable LP503X\n");
    }

  return ret;
}

/****************************************************************************
 * Name: lp503x_reset
 *
 * Description:
 *   Resets all registers to default values
 *
 ****************************************************************************/

static int lp503x_reset(struct lp503x_dev_s *priv)
{
  int ret;

  ret = lp503x_i2c_write_reg(priv, LP503X_RESET, LP503X_RESET_ALL_REGISTERS);
  if (ret != 0)
    {
      return -EIO;
    }
  else
    {
      priv->state = LP503X_STATE_RESET;
      return OK;
    }
}

/****************************************************************************
 * Name: lp503x_enable
 *
 * Description:
 *   enables or disables the entire device
 *
 ****************************************************************************/

static int lp503x_enable(struct lp503x_dev_s *priv, bool enable)
{
  int ret;

  if (enable)
    {
      ret = lp503x_i2c_write_reg(priv, LP503X_DEVICE_CONFIG0,
                                       LP503X_CHIP_ENABLE);
      ledinfo("INFO: LP503x enabled\n");
    }
  else
    {
      ret = lp503x_i2c_write_reg(priv, LP503X_DEVICE_CONFIG0,
                                       LP503X_CHIP_DISABLE);
      ledinfo("INFO: LP503x disabled\n");
    }

  return ret;
}

/****************************************************************************
 * Name: lp503x_set_config
 *
 * Description:
 *   configures basic operation modes of the device
 *
 ****************************************************************************/

static int lp503x_set_config(struct lp503x_dev_s *priv)
{
  int ret;
  uint8_t regval;
  struct lp503x_config_s *config;

  config = priv->lp503x_config;

  ret = lp503x_i2c_read_reg(priv, LP503X_DEVICE_CONFIG1, &regval);
  if (config->enable_log_mode)
    {
      regval |= LP503X_CONFIG1_LOG_SCALE;
    }
  else
    {
      regval &= ~LP503X_CONFIG1_LOG_SCALE;
    }

  if (config->enable_power_save)
    {
      regval |= LP503X_CONFIG1_PWRSAVE;
    }
  else
    {
      regval &= ~LP503X_CONFIG1_PWRSAVE;
    }

  if (config->enable_pwm_dithering)
    {
      regval |= LP503X_CONFIG1_DITHERING;
    }
  else
    {
      regval &= ~LP503X_CONFIG1_DITHERING;
    }

  if (config->set_max_current_35ma)
    {
      regval |= LP503X_CONFIG1_PWRSAVE;
    }
  else
    {
      regval &= ~LP503X_CONFIG1_PWRSAVE;
    }

  if (config->enable_all_led_shutdown)
    {
      regval |= LP503X_CONFIG1_GLOBAL_OFF;
    }
  else
    {
      regval &= ~LP503X_CONFIG1_GLOBAL_OFF;
    }

  ret = lp503x_i2c_write_reg(priv, LP503X_DEVICE_CONFIG1, regval);

  return ret;
}

/****************************************************************************
 * Name: lp503x_set_bank_brightness
 *
 * Description:
 *   sets banks to the  required brightness
 *
 ****************************************************************************/

static int lp503x_set_bank_brightness(struct lp503x_dev_s *priv,
                                      int brightness)
{
  if (brightness > MAX_BRIGHTNESS)
    {
      return -EINVAL;
    }
  else
    {
      return lp503x_i2c_write_reg(priv, LP503X_BANK_BRIGHTNESS, brightness);
    }
}

/****************************************************************************
 * Name: lp503x_set_bank_colour
 *
 * Description:
 *   sets bank A, B or C led to required coloiur (mix)
 *
 ****************************************************************************/

static int lp503x_set_bank_colour(struct lp503x_dev_s *priv, char bank,
                                  int brightness)
{
  if (brightness > MAX_BRIGHTNESS)
    {
      return -EINVAL;
    }
  else
    {
      if (bank == 'A')
        {
          return lp503x_i2c_write_reg(priv, LP503X_BANK_A_COLOUR,
                                      brightness);
        }
      else if (bank == 'B')
        {
          return lp503x_i2c_write_reg(priv, LP503X_BANK_B_COLOUR,
                                      brightness);
        }
      else if (bank == 'C')
        {
          return lp503x_i2c_write_reg(priv, LP503X_BANK_C_COLOUR,
                                      brightness);
        }
      else
        {
          return -EINVAL;
        }
    }
}

/****************************************************************************
 * Name: lp503x_set_bank_mode
 *
 * Description:
 *   enables or disables bank mode for selected LED
 *
 ****************************************************************************/

static int lp503x_set_bank_mode(struct lp503x_dev_s *priv)
{
  int ret;
  int count;
  int regval;

  struct lp503x_config_s *config;

  config = priv->lp503x_config;

  regval = 0;
  for (count = 0; count < 8; count++)
    {
      if (config->led_mode[count] == LP503X_LED_BANK_MODE_ENABLED)
        {
          regval |= (LP503X_LED0_BANK_ENABLE << count);
        }
      else
        {
          regval &= ~(LP503X_LED0_BANK_ENABLE << count);
        }
    }

  ret = lp503x_i2c_write_reg(priv, LP503X_LED_CONFIG0, regval);

  for (count = 8; count < 12; count++)
    {
      if (config->led_mode[count] == LP503X_LED_BANK_MODE_ENABLED)
        {
          regval |= (LP503X_LED0_BANK_ENABLE << (count - 8));
        }
      else
        {
          regval &= ~(LP503X_LED0_BANK_ENABLE << (count - 8));
        }
    }

  ret = lp503x_i2c_write_reg(priv, LP503X_LED_CONFIG1, regval);

  return ret;
}

/****************************************************************************
 * Name: lp503x_set_rgbled_colour
 *
 * Description:
 *   sets RGB led to chosen html colour
 *
 ****************************************************************************/

static int lp503x_set_rgbled_colour(struct lp503x_dev_s *priv,
                                    int led, int colour)
{
  int ret;
  int regaddr;

  if ((led > MAX_RGB_LEDS) || (colour > MAX_RGB_COLOUR))
    {
      ret = -EINVAL;
    }
  else
    {
      regaddr = LP503X_OUT0_COLOUR + (3*led);

      ret = lp503x_i2c_write_reg(priv, regaddr++, (colour >> 16) & 0xff);
      ret = lp503x_i2c_write_reg(priv, regaddr++, (colour >> 8)  & 0xff);
      ret = lp503x_i2c_write_reg(priv, regaddr,   (colour >> 0)  & 0xff);
      ledinfo("INFO: RGB LED %d set to RGB colour %06x\n", led, colour);
    }

  return ret;
}

/****************************************************************************
 * Name: lp503x_set_outcolour
 *
 * Description:
 *   Sets OUT brightness ("colour" of individual LED outputs
 *
 ****************************************************************************/

static int lp503x_set_outcolour(struct lp503x_dev_s *priv, int led,
                                int brightness)
{
  int ret;
  if ((led > MAX_LEDS) || (brightness > MAX_BRIGHTNESS))
    {
      ret = -EINVAL;
    }
  else
    {
      ret = lp503x_i2c_write_reg(priv, LP503X_OUT0_COLOUR + led, brightness);
      ledinfo("INFO: individual LED %d set to brightness %d\n", led,
              brightness);
    }

  return ret;
}

/****************************************************************************
 * Name: lp503x_set_rgbbrightness
 *
 * Description:
 *   Sets brightness of all RGB LED
 *
 ****************************************************************************/

static int lp503x_set_rgbbrightness(struct lp503x_dev_s *priv, int led,
                                    int brightness)
{
  int ret;

  if ((led > MAX_RGB_LEDS) || (brightness > MAX_BRIGHTNESS))
    {
      ret = -EINVAL;
    }
  else
    {
      ret = lp503x_i2c_write_reg(priv, LP503X_LED0_BRIGHTNESS + led,
                                 brightness);
      ledinfo("INFO: LED %d set to brightness %d\n", led, brightness);
    }

  return ret;
}

/****************************************************************************
 * Name: lp503x_ioctl
 *
 * Description:
 *   This function is called whenever an ioctl call to a LP503X is
 *   performed.
 *
 ****************************************************************************/

static int lp503x_ioctl(struct file *filep, int cmd,
                        unsigned long arg)
{
  struct inode *inode = filep->f_inode;
  struct lp503x_dev_s *priv = inode->i_private;
  struct lp503x_config_s *config;
  int ret;
  const struct ioctl_arg_s *lp503x_ioctl_args = (struct ioctl_arg_s *)arg;

  config = priv->lp503x_config;

  ret = OK;

  ledinfo("cmd: %d arg: %ld\n", cmd, arg);

  switch (cmd)
    {
      case PWMIOC_ENABLE: /* arg is true or false */
        config->enable_all_led_shutdown = lp503x_ioctl_args->param;
        ret = lp503x_set_config(priv);
        break;

      case PWMIOC_RESET:  /* no args */
        lp503x_reset(priv);
        break;

      case PWMIOC_ENABLE_LED_BANK_MODE: /* led(0..11), mode required */
        ledinfo("INFO: setting LED %d mode to %" PRIx32 "\n",
                lp503x_ioctl_args->lednum,
                lp503x_ioctl_args->param);
        config->led_mode[lp503x_ioctl_args->lednum] =
                         lp503x_ioctl_args->param;
        lp503x_set_bank_mode(priv);
        break;

      case PWMIOC_SET_BANK_MIX_COLOUR:/* bank(A/B/C),level(0-255)   */
        ledinfo("INFO: setting bank %c to brightness %" PRIx32 "\n",
                lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
        ret = lp503x_set_bank_colour(priv, lp503x_ioctl_args->lednum,
                                     lp503x_ioctl_args->param);
        break;

      case PWMIOC_SET_BANK_BRIGHTNESS:
        ledinfo("INFO: setting bank brightness to %" PRIx32 "\n",
                lp503x_ioctl_args->param);
        lp503x_set_bank_brightness(priv, lp503x_ioctl_args->param);
        break;

      case PWMIOC_CONFIG: /* config is struct within priv */
        ledinfo("INFO: setting device config to:\n");
        ledinfo("\tlog mode          = %d\n",  config->enable_log_mode);
        ledinfo("\tpower save        = %d\n",  config->enable_power_save);
        ledinfo("\tpwm dithering     = %d\n",  config->enable_pwm_dithering);
        ledinfo("\tmax current       = %s\n", (config->set_max_current_35ma)
                                        ? "30mA" : "25.5mA");
        ledinfo("\tall leds shutdown = %d\n",
                config->enable_all_led_shutdown);

        ret = lp503x_set_config(priv);
        break;

      case PWMIOC_SET_LED_COLOUR: /* led(0..35), Colour(0..255) */
        ledinfo("INFO: set LED %d to colour/brightness %" PRIx32 "\n",
                lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
        ret = lp503x_set_outcolour(priv, lp503x_ioctl_args->lednum,
                                   lp503x_ioctl_args->param);
        break;

      case PWMIOC_SET_RGB_BRIGHTNESS: /* led(0..11), level(0..255)  */
        ledinfo("INFO: requested brightness level %d for led %" PRIx32 "\n",
                lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
        ret = lp503x_set_rgbbrightness(priv, lp503x_ioctl_args->lednum,
                                       lp503x_ioctl_args->param);
        break;

      case PWMIOC_SET_RGB_COLOUR: /* led(0..11)                 */
        ledinfo("requested led %d to be RGB colour = %" PRIx32 "\n",
                lp503x_ioctl_args->lednum, lp503x_ioctl_args->param);
        ret = lp503x_set_rgbled_colour(priv, lp503x_ioctl_args->lednum,
                                       lp503x_ioctl_args->param);
        break;

      default: /* The used ioctl command was invalid */
        lederr("ERROR: Unrecognized cmd: %d\n", cmd);
        ret = -ENOTTY;
        break;
    }

  return ret;
}

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

/****************************************************************************
 * Name: lp503x_register
 *
 * Description:
 *   Register the LP503X device as 'devpath'
 *
 * Input Parameters:
 *   devpath - The full path to the driver to register. E.g., "/dev/leddrv0".
 *   i2c     - An instance of the I2C interface to use to communicate
 *             with the LM92.
 *   lp503x_i2c_addr
 *           - The I2C address of the LP503X.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno value on failure.
 *
 ****************************************************************************/

int lp503x_register(const char *devpath, struct i2c_master_s *i2c,
                    uint8_t const lp503x_i2c_addr, int const i2c_frequency)
{
  struct lp503x_dev_s *priv;
  int ret;

  /* Sanity check */

  DEBUGASSERT(devpath != NULL && i2c != NULL);

  /* Initialize the LP503X device structure */

  priv = kmm_malloc(sizeof(struct lp503x_dev_s));
  if (priv == NULL)
    {
      lederr("ERROR: Failed to allocate instance of lp503x_dev_s\n");
      return -ENOMEM;
    }

  priv->i2c           = i2c;
  priv->i2c_addr      = lp503x_i2c_addr;
  priv->i2c_freq      = i2c_frequency;
  priv->state         = LP503X_STATE_UNINIT;

  /* Register the character driver */

  ret = register_driver(devpath, &g_lp503x_fileops, 0222, priv);
  if (ret != OK)
    {
      lederr("ERROR: Failed to register driver: %d\n", ret);
      kmm_free(priv);
      return ret;
    }
  else
    {
      ret = lp503x_reset(priv);
      if (ret != OK)
        {
          lederr("ERROR: failed to reset lp503x device\n");
          return ret;
        }

      priv->state = LP503X_STATE_RESET;
    }

  return OK;
}

#endif /* CONFIG_I2C && CONFIG_I2C_LP503X */