/*
 * ds1302.c:
 *	Real Time clock
 *
 * Copyright (c) 2013 Gordon Henderson.
 ***********************************************************************
 * This file is part of wiringPi:
 *	https://projects.drogon.net/raspberry-pi/wiringpi/
 *
 *    wiringPi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    wiringPi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with wiringPi.  If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#include <wiringPi.h>
#include <ds1302.h>

// Register defines

#define	RTC_SECS	 0
#define	RTC_MINS	 1
#define	RTC_HOURS	 2
#define	RTC_DATE	 3
#define	RTC_MONTH	 4
#define	RTC_DAY		 5
#define	RTC_YEAR	 6
#define	RTC_WP		 7
#define	RTC_TC		 8
#define	RTC_BM		31


static unsigned int masks [] = { 0x7F, 0x7F, 0x3F, 0x3F, 0x1F, 0x07, 0xFF } ;


/*
 * bcdToD: dToBCD:
 *	BCD decode/encode
 *********************************************************************************
 */

static int bcdToD (unsigned int byte, unsigned int mask)
{
  unsigned int b1, b2 ;
  byte &= mask ;
  b1 = byte & 0x0F ;
  b2 = ((byte >> 4) & 0x0F) * 10 ;
  return b1 + b2 ;
}

static unsigned int dToBcd (unsigned int byte)
{
  return ((byte / 10) << 4) + (byte % 10);
}


/*
 * ramTest:
 *	Simple test of the 31 bytes of RAM inside the DS1302 chip
 *********************************************************************************
 */

static int ramTestValues [] =
  { 0x00, 0xFF, 0xAA, 0x55, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0xF0, 0x0F, -1 } ;

static int ramTest (void)
{
  int addr ;
  int got ;
  int i = 0 ;
  int errors = 0 ;
  int testVal ;

  printf("DS1302 RAM TEST\n");

  testVal = ramTestValues [i] ;

  while (testVal != -1)
    {
      for (addr = 0 ; addr < 31 ; ++addr)
	ds1302ramWrite (addr, testVal);

      for (addr = 0 ; addr < 31 ; ++addr)
	if ((got = ds1302ramRead (addr)) != testVal)
	  {
	    printf("DS1302 RAM Failure: Address: %2d, Expected: 0x%02X, Got: 0x%02X\n",
		   addr, testVal, got);
	    ++errors ;
	  }
      testVal = ramTestValues [++i] ;
    }

  for (addr = 0 ; addr < 31 ; ++addr)
    ds1302ramWrite (addr, addr);

  for (addr = 0 ; addr < 31 ; ++addr)
    if ((got = ds1302ramRead (addr)) != addr)
      {
	printf("DS1302 RAM Failure: Address: %2d, Expected: 0x%02X, Got: 0x%02X\n",
	       addr, addr, got);
	++errors ;
      }

  if (errors == 0)
    printf("-- DS1302 RAM TEST: OK\n");
  else
    printf("-- DS1302 RAM TEST FAILURE. %d errors.\n", errors);

  return 0 ;
}

/*
 * setLinuxClock:
 *	Set the Linux clock from the hardware
 *********************************************************************************
 */

static int setLinuxClock (void)
{
  char dateTime [20] ;
  char command [64] ;
  int  clock [8] ;


  printf("Setting the Linux Clock from the DS1302... "); fflush (stdout);

  ds1302clockRead (clock);

  // [MMDDhhmm[[CC]YY][.ss]]

  sprintf(dateTime, "%02d%02d%02d%02d%02d%02d.%02d",
	  bcdToD (clock [RTC_MONTH], masks [RTC_MONTH]),
	  bcdToD (clock [RTC_DATE],  masks [RTC_DATE]),
	  bcdToD (clock [RTC_HOURS], masks [RTC_HOURS]),
	  bcdToD (clock [RTC_MINS],  masks [RTC_MINS]),
	  20,
	  bcdToD (clock [RTC_YEAR],  masks [RTC_YEAR]),
	  bcdToD (clock [RTC_SECS],  masks [RTC_SECS]));

  sprintf(command, "/bin/date %s", dateTime);
  system (command);

  return 0 ;
}

/*
 * setDSclock:
 *	Set the DS1302 block from Linux time
 *********************************************************************************
 */

static int setDSclock(void)
{
  int clock[8];
  time_t now = time(NULL);
  struct tm* t = localtime(&now);

  int second = t->tm_sec;        // seconds after the minute(0~61)
  int minute = t->tm_min;        // minutes after the hour(0~59)
  int hour = t->tm_hour;         // hours since midnight(0~23)
  int day = t->tm_mday;          // day of the month(1~31)
  int month = t->tm_mon + 1;     // months since January(0~11 --> 1~12)
  int weekDay = t->tm_wday + 1;  // days since Sunday(0~6 --> 1~7)
  int year = t->tm_year - 100;   // years

  printf("Setting the clock in the DS1302 from Linux time [%d-%d-%d %d:%d:%d]...", year, month, day, hour, minute, second);

  clock[0] = dToBcd(second);	// seconds
  clock[1] = dToBcd(minute);	// mins
  clock[2] = dToBcd(hour);	// hours
  clock[3] = dToBcd(day);	// date
  clock[4] = dToBcd(month);	// months 0-11 --> 1-12
  clock[5] = dToBcd(weekDay);	// weekdays (sun 0)
  clock[6] = dToBcd(year);      // years
  clock[7] = 0;			// W-Protect off

  ds1302clockWrite(clock);

  printf("OK\n");

  return 0 ;
}

void printUsage() {
  printf("Usage: ./ds1302 SCLK_port SDA0_port CE0_port [-slc | -sdsc | -rtest]\n");
}

int main(int argc, char *argv [])
{
  if (argc < 4) {
    printUsage();
    return EXIT_FAILURE;
  }

  wiringPiSetup();

  int portSCLK = atoi(argv[1]);
  int portSDA0 = atoi(argv[2]);
  int portCE0 = atoi(argv[3]);
  printf("SCLK port: [%d], SDA0 port: [%d], CE0 port: [%d]\n", portSCLK, portSDA0, portCE0);
  ds1302setup(portSCLK, portSDA0, portCE0);

  int i;
  int clock[8];

  const char* param = argv[4];
  if (argc >= 5) {
    if (strcmp(param, "-slc") == 0) {
      return setLinuxClock();
    } else if (strcmp(param, "-sdsc") == 0) {
      return setDSclock();
    } else if (strcmp(param, "-rtest") == 0) {
      return ramTest();
    } else {
      printUsage();
      return EXIT_FAILURE ;
    }
  }

  for (i = 0 ;; ++i) {
    printf("%5d:  ", i);
    ds1302clockRead (clock);
    printf(" %2d:%02d:%02d",
	   bcdToD (clock [2], masks [2]), bcdToD (clock [1], masks [1]), bcdToD (clock [0], masks [0]));
    printf(" %2d/%02d/%04d",
	   bcdToD (clock [3], masks [3]), bcdToD (clock [4], masks [4]), bcdToD (clock [6], masks [6]) + 2000);
    printf("\n");
    delay (200);
  }
 
  return 0 ;
}
