/*
 * Copyright (c) 2015, Zolertia <http://www.zolertia.com>
 * All rights reserved.
 *
 * 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 of the Institute 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 INSTITUTE 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 INSTITUTE 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.
 *
 * This file is part of the Contiki operating system.
 *
 */
/*---------------------------------------------------------------------------*/
/**
 * \addtogroup remote-rtcc
 * @{
 *
 * Driver for the RE-Mote RTCC (Real Time Clock Calendar)
 * @{
 *
 * \file
 * Driver for the RE-Mote RF Real Time Clock Calendar (RTCC)
 *
 * \author
 *
 * Antonio Lignan <alinan@zolertia.com>
 * Aitor Mejias <amejias@zolertia.com>
 * Toni Lozano <tlozano@zolertia.com>
 */
/*---------------------------------------------------------------------------*/
#include "contiki.h"
#include "dev/gpio.h"
#include "dev/i2c.h"
#include "rtcc.h"
#include "rtcc-config.h"
#include "dev/leds.h"
#include <stdio.h>
/*---------------------------------------------------------------------------*/
#define DEBUG 0
#if DEBUG
#define PRINTF(...) printf(__VA_ARGS__)
#else
#define PRINTF(...)
#endif
/*---------------------------------------------------------------------------*/
#define RTC_INT1_PORT_BASE  GPIO_PORT_TO_BASE(RTC_INT1_PORT)
#define RTC_INT1_PIN_MASK   GPIO_PIN_MASK(RTC_INT1_PIN)
/*---------------------------------------------------------------------------*/
/* Callback pointers when interrupt occurs */
void (*rtcc_int1_callback)(uint8_t value);
/* -------------------------------------------------------------------------- */
static const char *ab080x_td_register_name[] =
{
  "Mseconds",
  "Seconds",
  "Minutes",
  "Hours",
  "Days",
  "Months",
  "Years",
  "Weekdays",
};
/* -------------------------------------------------------------------------- */
static const char *ab080x_config_register_name[] =
{
  "STATUS",
  "CTRL1",
  "CTRL2",
  "INTMASK",
  "SQW",
  "CAL_XT",
  "CAL_RCU",
  "CAL_RCL",
  "INTPOL",
  "TIMER_CTRL",
  "TIMER_CDOWN",
  "TIMER_INIT",
  "WDT",
  "OSC_CTRL",
  "OSC_STAT",
  "CONF_KEY",
  "TRICKLE",
  "BREF",
};
/*---------------------------------------------------------------------------*/
static uint8_t
bcd_to_dec(uint8_t val)
{
  return (uint8_t)(((val >> 4) * 10) + (val % 16));
}
/*---------------------------------------------------------------------------*/
static uint8_t
dec_to_bcd(uint8_t val)
{
  return (uint8_t)(((val / 10) << 4) + (val % 10));
}
/*---------------------------------------------------------------------------*/
static uint8_t
check_leap_year(uint8_t val)
{
  return ((val % 4) && (val % 100)) || (val % 400);
}
/*---------------------------------------------------------------------------*/
static uint16_t
ab08_read_reg(uint8_t reg, uint8_t *buf, uint8_t regnum)
{
  i2c_master_enable();
  if(i2c_single_send(AB08XX_ADDR, reg) == I2C_MASTER_ERR_NONE) {
    if(i2c_burst_receive(AB08XX_ADDR, buf, regnum) == I2C_MASTER_ERR_NONE) {
      return AB08_SUCCESS;
    }
  }
  return AB08_ERROR;
}
/*---------------------------------------------------------------------------*/
static int8_t
ab08_write_reg(uint8_t reg, uint8_t *buf, uint8_t regnum)
{
  uint8_t i, buff[INT_BUFF_SIZE];

  if(regnum > (INT_BUFF_SIZE - 1)) {
    return AB08_ERROR;
  }

  /* FIXME: Replace by single_send/burst_send */

  buff[0] = reg;
  for(i = 0; i < regnum; i++) {
    buff[(i + 1)] = buf[i];
  }

  i2c_master_enable();
  if(i2c_burst_send(AB08XX_ADDR, buff, (regnum + 1)) == I2C_MASTER_ERR_NONE) {
    return AB08_SUCCESS;
  }

  return AB08_ERROR;
}
/*---------------------------------------------------------------------------*/
static void
write_default_config(void)
{
  const ab080x_register_config_t *settings;
  settings = ab080x_default_setting;
  uint8_t i, len = (sizeof(ab080x_default_setting) / sizeof(ab080x_register_config_t));

  for(i = 0; i < len; i++) {
    ab08_write_reg(settings[i].reg, (uint8_t *)&settings[i].val, 1);
  }
}
/*---------------------------------------------------------------------------*/
static int8_t
ab08_key_reg(uint8_t unlock)
{
  if((unlock != RTCC_CONFKEY_OSCONTROL) && (unlock != RTCC_CONFKEY_SWRESET) &&
     (unlock != RTCC_CONFKEY_DEFREGS)) {
    PRINTF("RTC: invalid confkey values\n");
    return AB08_ERROR;
  }

  if(ab08_write_reg((CONFIG_MAP_OFFSET + CONF_KEY_ADDR), &unlock, 1)) {
    PRINTF("RTC: failed to write to confkey register\n");
    return AB08_ERROR;
  }

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
static int8_t
ab08_read_status(uint8_t *buf)
{
  return ab08_read_reg((STATUS_ADDR + CONFIG_MAP_OFFSET), buf, 1);
}
/*---------------------------------------------------------------------------*/
static int8_t
ab08_ctrl1_config(uint8_t cmd)
{
  uint8_t ctrl1 = 0;

  if(cmd >= RTCC_CMD_MAX) {
    return AB08_ERROR;
  }

  if(ab08_read_reg((CONFIG_MAP_OFFSET + CTRL_1_ADDR), &ctrl1, 1)) {
    PRINTF("RTC: failed to retrieve CTRL1 register\n");
    return AB08_ERROR;
  }

  switch(cmd) {
  case RTCC_CMD_LOCK:
    ctrl1 &= ~CTRL1_WRTC;
    break;
  case RTCC_CMD_UNLOCK:
    ctrl1 |= CTRL1_WRTC;
    break;
  case RTCC_CMD_ENABLE:
    ctrl1 &= ~CTRL1_STOP;
    break;
  case RTCC_CMD_STOP:
    ctrl1 |= CTRL1_STOP;
    break;
  default:
    return AB08_ERROR;
  }

  if(ab08_write_reg((CONFIG_MAP_OFFSET + CTRL_1_ADDR),
                    &ctrl1, 1) == AB08_ERROR) {
    PRINTF("RTC: failed to write to the CTRL1 register\n");
    return AB08_ERROR;
  }

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
static int8_t
ab08_check_td_format(simple_td_map *data, uint8_t alarm_state)
{
  /* Using fixed values as these are self-indicative of the variable */
  if((data->seconds > 59) || (data->minutes > 59) || (data->hours > 23)) {
    return AB08_ERROR;
  }

  if((data->months > 12) || (data->weekdays > 7) || (data->day > 31)) {
    return AB08_ERROR;
  }

  /* Fixed condition for February (month 2) */
  if(data->months == 2) {
    if(check_leap_year(data->years)) {
      if(data->day > 29) {
        return AB08_ERROR;
      }
    } else {
      if(data->day > 28) {
        return AB08_ERROR;
      }
    }
  }

  /* Alarm doesn't care about year */
  if(!alarm_state) {
    /* AB08X5 Real-Time Clock Family, page 55 (year up to 2199) */
    if(data->years > 199) {
      return AB08_ERROR;
    }
  }

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
int8_t
rtcc_set_time_date(simple_td_map *data)
{
  uint8_t aux = 0;
  uint8_t rtc_buffer[RTCC_TD_MAP_SIZE];

  if(ab08_check_td_format(data, 0) == AB08_ERROR) {
    PRINTF("RTC: Invalid time/date values\n");
    return AB08_ERROR;
  }

  if(ab08_read_reg((CTRL_1_ADDR + CONFIG_MAP_OFFSET),
                   &aux, 1) == AB08_ERROR) {
    PRINTF("RTC: failed to retrieve CONTROL1 register\n");
    return AB08_ERROR;
  }

  rtc_buffer[WEEKDAYLS_ADDR] = dec_to_bcd(data->weekdays);
  rtc_buffer[YEAR_ADDR] = dec_to_bcd(data->years);
  rtc_buffer[MONTHS_ADDR] = dec_to_bcd(data->months);
  rtc_buffer[DAY_ADDR] = dec_to_bcd(data->day);
  rtc_buffer[HOUR_ADDR] = dec_to_bcd(data->hours);
  rtc_buffer[MIN_ADDR] = dec_to_bcd(data->minutes);
  rtc_buffer[SEC_ADDR] = dec_to_bcd(data->seconds);
  rtc_buffer[CENTHS_ADDR] = dec_to_bcd(data->miliseconds);

  /* Check if we are to set the time in 12h/24h format */
  if(data->mode == RTCC_24H_MODE) {
    aux &= ~CTRL1_1224;
  } else {
    if((data->hours == 0) || (data->hours > 12)) {
      PRINTF("RTC: Invalid hour configuration (12h mode selected)\n");
      return AB08_ERROR;
    }
    aux |= CTRL1_1224;
    if(data->mode == RTCC_12H_MODE_PM) {
      /* Toggle bit for PM */
      rtc_buffer[HOUR_ADDR] |= RTCC_TOGGLE_PM_BIT;
    } else {
      PRINTF("RTC: Invalid time mode selected\n");
      return AB08_ERROR;
    }
  }

  /* Write the 12h/24h config */
  if(ab08_write_reg((CTRL_1_ADDR + CONFIG_MAP_OFFSET),
                    &aux, 1) == AB08_ERROR) {
    PRINTF("RTC: failed to write 12h/24h configuration\n");
    return AB08_ERROR;
  }

  /* Reading the STATUS register with the CONTROL1.ARST set will clear the
   * interrupt flags, we write directly to the register without caring its
   * actual status and let the interrupt handler take care of any pending flag
   */

  if(ab08_read_reg((STATUS_ADDR + CONFIG_MAP_OFFSET), &aux, 1) == AB08_ERROR) {
    PRINTF("RTC: failed to retrieve STATUS register\n");
    return AB08_ERROR;
  }

  if(data->century == RTCC_CENTURY_20XX) {
    aux |= STATUS_CB;
  } else if(data->century == RTCC_CENTURY_19XX_21XX) {
    aux |= ~STATUS_CB;
  } else {
    PRINTF("RTC: invalid century value\n");
    return AB08_ERROR;
  }

  PRINTF("RTC: current STATUS value 0x%02X\n", aux);

  if(ab08_write_reg((STATUS_ADDR + CONFIG_MAP_OFFSET), &aux, 1) == AB08_ERROR) {
    PRINTF("RTC: failed to write century to STATUS register\n");
    return AB08_ERROR;
  }

  /* Set the WRTC bit to enable writting to the counters */
  if(ab08_ctrl1_config(RTCC_CMD_UNLOCK) == AB08_ERROR) {
    return AB08_ERROR;
  }

  /* Write the buffers but the mode and century fields (used only for config) */
  if(ab08_write_reg(CENTHS_ADDR, rtc_buffer,
                    RTCC_TD_MAP_SIZE) == AB08_ERROR) {
    PRINTF("RTC: failed to write date configuration\n");
    return AB08_ERROR;
  }

  /* Lock the RTCC and return */
  if(ab08_ctrl1_config(RTCC_CMD_LOCK) == AB08_ERROR) {
    return AB08_ERROR;
  }

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
int8_t
rtcc_get_time_date(simple_td_map *data)
{
  uint8_t rtc_buffer[RTCC_TD_MAP_SIZE];

  if(ab08_read_reg(CENTHS_ADDR, rtc_buffer,
                   RTCC_TD_MAP_SIZE) == AB08_ERROR) {
    PRINTF("RTC: failed to retrieve date and time values\n");
    return AB08_ERROR;
  }

  data->weekdays = bcd_to_dec(rtc_buffer[WEEKDAYLS_ADDR]);
  data->years = bcd_to_dec(rtc_buffer[YEAR_ADDR]);
  data->months = bcd_to_dec(rtc_buffer[MONTHS_ADDR]);
  data->day = bcd_to_dec(rtc_buffer[DAY_ADDR]);
  data->hours = bcd_to_dec(rtc_buffer[HOUR_ADDR]);
  data->minutes = bcd_to_dec(rtc_buffer[MIN_ADDR]);
  data->seconds = bcd_to_dec(rtc_buffer[SEC_ADDR]);
  data->miliseconds = bcd_to_dec(rtc_buffer[CENTHS_ADDR]);

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
int8_t
rtcc_set_alarm_time_date(simple_td_map *data, uint8_t state, uint8_t repeat)
{
  uint8_t aux[4], buf[RTCC_ALARM_MAP_SIZE];

  if(state == RTCC_ALARM_OFF) {
    if(ab08_read_reg((INT_MASK_ADDR + CONFIG_MAP_OFFSET),
                     &aux[0], 1) == AB08_ERROR) {
      PRINTF("RTC: failed to retrieve INTMASK register\n");
      return AB08_ERROR;
    }

    aux[0] &= ~INTMASK_AIE;

    if(ab08_write_reg((INT_MASK_ADDR + CONFIG_MAP_OFFSET),
                      &aux[0], 1) == AB08_ERROR) {
      PRINTF("RTC: failed to clear the alarm config\n");
      return AB08_ERROR;
    }
    return AB08_SUCCESS;
  }

  if((data == NULL) || (ab08_check_td_format(data, 1) == AB08_ERROR)) {
    PRINTF("RTC: invalid alarm values\n");
    return AB08_ERROR;
  }

  if((state >= RTCC_ALARM_MAX) || (repeat >= RTCC_REPEAT_100THS)) {
    PRINTF("RTC: invalid alarm config type or state\n");
    return AB08_ERROR;
  }

  /* Stop the RTCC */
  ab08_ctrl1_config(RTCC_CMD_STOP);

  buf[WEEKDAYS_ALARM_ADDR] = dec_to_bcd(data->weekdays);
  buf[MONTHS_ALARM_ADDR] = dec_to_bcd(data->months);
  buf[DAY_ALARMS_ADDR] = dec_to_bcd(data->day);
  buf[HOURS_ALARM_ADDR] = dec_to_bcd(data->hours);
  buf[MINUTES_ALARM_ADDR] = dec_to_bcd(data->minutes);
  buf[SECONDS_ALARM_ADDR] = dec_to_bcd(data->seconds);
  buf[HUNDREDTHS_ALARM_ADDR] = dec_to_bcd(data->miliseconds);

  /* Check if the 12h/24h match the current configuration */
  if(ab08_read_reg((CTRL_1_ADDR + CONFIG_MAP_OFFSET),
                   &aux[0], 1) == AB08_ERROR) {
    PRINTF("RTC: failed to retrieve CONTROL1 register\n");
    return AB08_ERROR;
  }

  if(((aux[0] & CTRL1_1224) && (data->mode == RTCC_24H_MODE)) ||
     (!(aux[0] & CTRL1_1224) && ((data->mode == RTCC_12H_MODE_AM) ||
                                 (data->mode == RTCC_12H_MODE_PM)))) {
    PRINTF("RTC: 12/24h mode and present date config mismatch\n");
    return AB08_ERROR;
  }

  if(data->mode != RTCC_24H_MODE) {
    if((data->hours == 0) || (data->hours > 12)) {
      PRINTF("RTC: Invalid hour configuration (12h mode selected)\n");
      return AB08_ERROR;
    }

    /* Toggle the PM bit */
    if(data->mode == RTCC_12H_MODE_PM) {
      buf[HOURS_ALARM_ADDR] |= RTCC_TOGGLE_PM_BIT;
    }
  }

  /* Clear the RPT field */
  if(ab08_read_reg((TIMER_CONTROL_ADDR + CONFIG_MAP_OFFSET),
                   &aux[0], 1) == AB08_ERROR) {
    PRINTF("RTC: failed to retrieve TIMER CTRL register\n");
    return AB08_ERROR;
  }

  aux[0] &= ~COUNTDOWN_TIMER_RPT_SECOND;

  /* AB08XX application manual, table 76 */
  if(repeat == RTCC_REPEAT_10THS) {
    buf[HUNDREDTHS_ALARM_ADDR] |= RTCC_FIX_10THS_HUNDRETHS;
    repeat = RTCC_REPEAT_SECOND;
  } else if(repeat == RTCC_REPEAT_100THS) {
    buf[HUNDREDTHS_ALARM_ADDR] |= RTCC_FIX_100THS_HUNDRETHS;
    repeat = RTCC_REPEAT_SECOND;
  }

  if(repeat != RTCC_REPEAT_NONE) {
    aux[0] |= (repeat << COUNTDOWN_TIMER_RPT_SHIFT);
  }

  /* We are using as default the level interrupt instead of pulses */
  /* FIXME: make this selectable */
  aux[0] |= COUNTDOWN_TIMER_TM;
  aux[0] &= ~COUNTDOWN_TIMER_TRPT;

  if(ab08_write_reg((TIMER_CONTROL_ADDR + CONFIG_MAP_OFFSET),
                    &aux[0], 1) == AB08_ERROR) {
    PRINTF("RTC: failed to clear the alarm config\n");
    return AB08_ERROR;
  }

  if(ab08_read_reg((STATUS_ADDR + CONFIG_MAP_OFFSET),
                   aux, 4) == AB08_ERROR) {
    PRINTF("RTC: failed to read configuration registers\n");
    return AB08_ERROR;
  }

  /* Clear ALM field if any */
  aux[STATUS_ADDR] &= ~STATUS_ALM;

#if RTCC_CLEAR_INT_MANUALLY
  aux[CTRL_1_ADDR] &= ~CTRL1_ARST;
#endif

  /* Clear the AIE alarm bit */
  aux[INT_MASK_ADDR] &= ~INTMASK_AIE;

  /* Configure Interrupt parameters for Alarm Interrupt Mode in nIRQ pin,
   * and fixed level until interrupt flag is cleared
   */

  /* Enable nIRQ if at least one interrupt is enabled */
  aux[CTRL_2_ADDR] |= CTRL2_OUT1S_NIRQ_NAIRQ_OUT;

  if(repeat != RTCC_REPEAT_NONE) {
    aux[INT_MASK_ADDR] &= ~INTMASK_IM_LOW;
  } else {
    aux[INT_MASK_ADDR] |= INTMASK_IM_LOW;
  }

  if(ab08_write_reg((STATUS_ADDR + CONFIG_MAP_OFFSET), aux, 4) == AB08_ERROR) {
    PRINTF("RTC: failed to clear alarm config\n");
    return AB08_ERROR;
  }

  /* Enable interrupts */
  GPIO_ENABLE_INTERRUPT(RTC_INT1_PORT_BASE, RTC_INT1_PIN_MASK);
  ioc_set_over(RTC_INT1_PORT, RTC_INT1_PIN, IOC_OVERRIDE_PUE);
  nvic_interrupt_enable(RTC_INT1_VECTOR);

  /* Write to the alarm counters */
  if(ab08_write_reg((HUNDREDTHS_ALARM_ADDR + ALARM_MAP_OFFSET), buf,
                    RTCC_ALARM_MAP_SIZE) == AB08_ERROR) {
    PRINTF("RTC: failed to set the alarm\n");
    return AB08_ERROR;
  }

  /* And finally enable the AIE bit */
  aux[INT_MASK_ADDR] |= INTMASK_AIE;
  if(ab08_write_reg((INT_MASK_ADDR + CONFIG_MAP_OFFSET),
                    &aux[INT_MASK_ADDR], 1) == AB08_ERROR) {
    PRINTF("RTC: failed to enable the alarm\n");
    return AB08_ERROR;
  }

  /* Enable back the RTCC */
  ab08_ctrl1_config(RTCC_CMD_ENABLE);

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
PROCESS(rtcc_int_process, "RTCC interruption process handler");
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(rtcc_int_process, ev, data)
{
  static uint8_t buf;
  PROCESS_EXITHANDLER();
  PROCESS_BEGIN();
  while(1) {
    PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_POLL);

    if(ab08_read_status(&buf) == AB08_ERROR) {
      PRINTF("RTC: failed to retrieve ARST value\n");
      PROCESS_EXIT();
    }

    /* We only handle the AIE (alarm interrupt) only */
    if((buf & STATUS_ALM) && (rtcc_int1_callback != NULL)) {
#if RTCC_CLEAR_INT_MANUALLY
      buf &= ~STATUS_ALM;
      if(ab08_write_reg((STATUS_ADDR + CONFIG_MAP_OFFSET),
                        &buf, 1) == AB08_ERROR) {
        PRINTF("RTC: failed to clear the alarm\n");
        return AB08_ERROR;
      }
#endif
      rtcc_int1_callback(0);
    }
  }
  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
int8_t
rtcc_print(uint8_t value)
{
  uint8_t i, len, reg;
  char **name;
  uint8_t rtc_buffer[RTCC_CONFIG_MAP_SIZE];

  if(value >= RTCC_PRINT_MAX) {
    return AB08_ERROR;
  }

  switch(value) {
  case RTCC_PRINT_CONFIG:
    len = (RTCC_CONFIG_MAP_SIZE - 1);
    reg = STATUS_ADDR + CONFIG_MAP_OFFSET;
    name = (char **)ab080x_config_register_name;
    break;
  case RTCC_PRINT_ALARM:
  case RTCC_PRINT_ALARM_DEC:
    len = RTCC_ALARM_MAP_SIZE;
    reg = HUNDREDTHS_ALARM_ADDR + ALARM_MAP_OFFSET;
    name = (char **)ab080x_td_register_name;
    break;
  case RTCC_PRINT_DATE:
  case RTCC_PRINT_DATE_DEC:
    len = RTCC_TD_MAP_SIZE;
    reg = CENTHS_ADDR;
    name = (char **)ab080x_td_register_name;
    break;
  default:
    return AB08_ERROR;
  }

  if(ab08_read_reg(reg, rtc_buffer, len) == AB08_ERROR) {
    PRINTF("RTC: failed to retrieve values to print\n");
    return AB08_ERROR;
  }

  if(value == RTCC_PRINT_ALARM_DEC) {
    printf("%02u/%02u (%02u) %02u:%02u:%02u/%02u\n",
           bcd_to_dec(rtc_buffer[MONTHS_ALARM_ADDR]),
           bcd_to_dec(rtc_buffer[DAY_ALARMS_ADDR]),
           bcd_to_dec(rtc_buffer[WEEKDAYS_ALARM_ADDR]),
           bcd_to_dec(rtc_buffer[HOURS_ALARM_ADDR]),
           bcd_to_dec(rtc_buffer[MINUTES_ALARM_ADDR]),
           bcd_to_dec(rtc_buffer[SECONDS_ALARM_ADDR]),
           bcd_to_dec(rtc_buffer[HUNDREDTHS_ALARM_ADDR]));
    return AB08_SUCCESS;
  }

  if(value == RTCC_PRINT_DATE_DEC) {
    printf("%02u/%02u/%02u (%02u) %02u:%02u:%02u/%02u\n",
           bcd_to_dec(rtc_buffer[YEAR_ADDR]),
           bcd_to_dec(rtc_buffer[MONTHS_ADDR]),
           bcd_to_dec(rtc_buffer[DAY_ADDR]),
           bcd_to_dec(rtc_buffer[WEEKDAYLS_ADDR]),
           bcd_to_dec(rtc_buffer[HOUR_ADDR]),
           bcd_to_dec(rtc_buffer[MIN_ADDR]),
           bcd_to_dec(rtc_buffer[SEC_ADDR]),
           bcd_to_dec(rtc_buffer[CENTHS_ADDR]));
    return AB08_SUCCESS;
  }

  for(i = 0; i < len; i++) {
    printf("0x%02X <- %s\n", rtc_buffer[i], name[i]);
  }

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
static void
rtcc_interrupt_handler(uint8_t port, uint8_t pin)
{
  process_poll(&rtcc_int_process);
}
/*---------------------------------------------------------------------------*/
int8_t
rtcc_set_autocalibration(uint8_t period)
{
  uint8_t aux;

  if(period > RTCC_AUTOCAL_9_MIN) {
    PRINTF("RTC: invalid autocal value\n");
    return AB08_ERROR;
  }

  if(ab08_read_reg((OSC_CONTROL_ADDR + CONFIG_MAP_OFFSET),
                   &aux, 1) == AB08_ERROR) {
    PRINTF("RTC: failed to read oscillator registers\n");
    return AB08_ERROR;
  }

  /* Clear ACAL */
  aux &= ~OSCONTROL_ACAL_9_MIN;

  /* Unlock the key register */
  ab08_key_reg(RTCC_CONFKEY_OSCONTROL);

  switch(period) {
  case RTCC_AUTOCAL_DISABLE:
    break;
  case RTCC_AUTOCAL_ONCE:
  case RTCC_AUTOCAL_17_MIN:
    aux |= OSCONTROL_ACAL_17_MIN;
    break;
  case RTCC_AUTOCAL_9_MIN:
    aux |= OSCONTROL_ACAL_9_MIN;
    break;
  default:
    return AB08_ERROR;
  }

  if(ab08_write_reg((OSC_CONTROL_ADDR + CONFIG_MAP_OFFSET),
                    &aux, 1) == AB08_ERROR) {
    PRINTF("RTC: failed to clear the autocalibration\n");
    return AB08_ERROR;
  }

  if(period == RTCC_AUTOCAL_ONCE) {
    clock_delay_usec(10000);
    ab08_key_reg(RTCC_CONFKEY_OSCONTROL);
    aux &= ~OSCONTROL_ACAL_9_MIN;
    if(ab08_write_reg((OSC_CONTROL_ADDR + CONFIG_MAP_OFFSET),
                      &aux, 1) == AB08_ERROR) {
      PRINTF("RTC: failed to clear the autocalibration\n");
      return AB08_ERROR;
    }
  }

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
int8_t
rtcc_set_calibration(uint8_t mode, int32_t adjust)
{
  int32_t adjint;
  uint8_t adjreg[2];
  uint8_t xtcal;

  if(mode > RTCC_CAL_RC_OSC) {
    PRINTF("RTC: invalid calibration mode\n");
    return AB08_ERROR;
  }

  /* Fixed values dependant on the oscillator source (Application Manual) */
  if((mode == RTCC_CAL_XT_OSC) && ((adjust <= -610) || (adjust >= 242))) {
    PRINTF("RTC: invalid adjust value for XT oscillator\n");
    return AB08_ERROR;
  }

  if((mode == RTCC_CAL_RC_OSC) && ((adjust <= -65536) || (adjust >= 65520))) {
    PRINTF("RTC: invalid adjust value for XT oscillator\n");
    return AB08_ERROR;
  }

  /* Calibration routine taken from the Application manual */
  if(adjust < 0) {
    adjint = ((adjust) * 1000 - 953);
  } else {
    adjint = ((adjust) * 1000 + 953);
  }

  adjint = adjint / 1907;

  if(mode == RTCC_CAL_XT_OSC) {
    if(adjint > 63) {
      xtcal = 0;
      /* CMDX = 1 */
      adjreg[0] = ((adjint >> 1) & 0x3F) | 0x80;
    } else if(adjint > -65) {
      xtcal = 0;
      adjreg[0] = (adjint & 0x7F);
    } else if(adjint > -129) {
      xtcal = 1;
      adjreg[0] = ((adjint + 64) & 0x7F);
    } else if(adjint > -193) {
      xtcal = 2;
      adjreg[0] = ((adjint + 128) & 0x7F);
    } else if(adjint > -257) {
      xtcal = 3;
      adjreg[0] = ((adjint + 192) & 0x7F);
    } else {
      xtcal = 3;
      adjreg[0] = ((adjint + 192) >> 1) & 0xFF;
    }

    if(ab08_write_reg((CAL_XT_ADDR + CONFIG_MAP_OFFSET),
                      &adjreg[0], 1) == AB08_ERROR) {
      PRINTF("RTC: failed to clear the autocalibration\n");
      return AB08_ERROR;
    }

    if(ab08_read_reg((OSC_STATUS_ADDR + CONFIG_MAP_OFFSET),
                     &adjreg[0], 1) == AB08_ERROR) {
      PRINTF("RTC: failed to read oscillator registers\n");
      return AB08_ERROR;
    }

    /* Clear XTCAL and write new value */
    adjreg[0] &= 0x3F;
    adjreg[0] |= (xtcal << 6);

    if(ab08_write_reg((OSC_STATUS_ADDR + CONFIG_MAP_OFFSET),
                      &adjreg[0], 1) == AB08_ERROR) {
      PRINTF("RTC: failed to clear the autocalibration\n");
      return AB08_ERROR;
    }
  } else if(mode == RTCC_CAL_RC_OSC) {
    if(adjint > 32767) {
      adjreg[1] = ((adjint >> 3) & 0xFF);
      adjreg[0] = ((adjint >> 11) | 0xC0);
    } else if(adjint > 16383) {
      adjreg[1] = ((adjint >> 2) & 0xFF);
      adjreg[0] = ((adjint >> 10) | 0x80);
    } else if(adjint > 8191) {
      adjreg[1] = ((adjint >> 1) & 0xFF);
      adjreg[0] = ((adjint >> 9) | 0x40);
    } else if(adjint >= 0) {
      adjreg[1] = ((adjint) & 0xFF);
      adjreg[0] = (adjint >> 8);
    } else if(adjint > -8193) {
      adjreg[1] = ((adjint) & 0xFF);
      adjreg[0] = (adjint >> 8) & 0x3F;
    } else if(adjint > -16385) {
      adjreg[1] = ((adjint >> 1) & 0xFF);
      adjreg[0] = (adjint >> 9) & 0x7F;
    } else if(adjint > -32769) {
      adjreg[1] = ((adjint >> 2) & 0xFF);
      adjreg[0] = (adjint >> 10) & 0xBF;
    } else {
      adjreg[1] = ((adjint >> 3) & 0xFF);
      adjreg[0] = (adjint >> 11) & 0xFF;
    }

    if(ab08_write_reg((CAL_RC_HI_ADDR + CONFIG_MAP_OFFSET),
                      adjreg, 2) == AB08_ERROR) {
      PRINTF("RTC: failed to set the RC calibration\n");
      return AB08_ERROR;
    }

    /* This should not happen */
  } else {
    return AB08_ERROR;
  }

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
int8_t
rtcc_init(void)
{
  i2c_init(I2C_SDA_PORT, I2C_SDA_PIN, I2C_SCL_PORT, I2C_SCL_PIN,
           I2C_SCL_NORMAL_BUS_SPEED);

#if RTCC_SET_DEFAULT_CONFIG
  write_default_config();
#endif

#if RTCC_SET_AUTOCAL
  rtcc_set_autocalibration(RTCC_AUTOCAL_17_MIN);
#endif

  /* Initialize interrupts handlers */
  rtcc_int1_callback = NULL;

  /* Configure the interrupts pins */
  GPIO_SOFTWARE_CONTROL(RTC_INT1_PORT_BASE, RTC_INT1_PIN_MASK);
  GPIO_SET_INPUT(RTC_INT1_PORT_BASE, RTC_INT1_PIN_MASK);

  /* Pull-up resistor, detect falling edge */
  GPIO_DETECT_EDGE(RTC_INT1_PORT_BASE, RTC_INT1_PIN_MASK);
  GPIO_TRIGGER_SINGLE_EDGE(RTC_INT1_PORT_BASE, RTC_INT1_PIN_MASK);
  GPIO_DETECT_FALLING(RTC_INT1_PORT_BASE, RTC_INT1_PIN_MASK);
  gpio_register_callback(rtcc_interrupt_handler, RTC_INT1_PORT, RTC_INT1_PIN);

  /* Spin process until an interrupt is received */
  process_start(&rtcc_int_process, NULL);

  return AB08_SUCCESS;
}
/*---------------------------------------------------------------------------*/
/**
 * @}
 * @}
 */