Introduce gmtime.c and use it in the AS3514 RTC driver

Change-Id: I00a09ae28a68f8153fb8fa854fea741ddfb0bf09
Reviewed-on: http://gerrit.rockbox.org/175
Tested-by: Bertrik Sikken <bertrik@sikken.nl>
Reviewed-by: Michael Sevakis <jethead71@rockbox.org>
Reviewed-by: Rafaël Carré <rafael.carre@gmail.com>
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 32fbfbb..9b0f83d 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -128,6 +128,7 @@
 libc/strrchr.c
 libc/strstr.c
 libc/mktime.c
+libc/gmtime.c
 #endif /* CONFIG_PLATFORM || HAVE_ROCKBOX_C_LIBRARY */
 
 /* Common */
diff --git a/firmware/drivers/rtc/rtc_as3514.c b/firmware/drivers/rtc/rtc_as3514.c
index 868fa97..a4d2eb1 100644
--- a/firmware/drivers/rtc/rtc_as3514.c
+++ b/firmware/drivers/rtc/rtc_as3514.c
@@ -23,34 +23,17 @@
 #include "rtc.h"
 #include "as3514.h"
 #include "ascodec.h"
+#include "time.h"
 
 /* AMS Sansas start counting from Jan 1st 1970 instead of 1980 (not as3525v2) */
 #if (CONFIG_CPU==AS3525)
-#define SECS_ADJUST 315532800   /* seconds between 1970-1-1 and 1980-1-1 */
-#elif (CONFIG_CPU==AS3525v2)
-#define SECS_ADJUST 315532800 - (2*365*24*3600) - 26*(24*3600) + 7*3600 + 25*60
-#else
 #define SECS_ADJUST 0
+#elif (CONFIG_CPU==AS3525v2)
+#define SECS_ADJUST ((2*365*24*3600) + 26*(24*3600) - 7*3600 - 25*60)
+#else
+#define SECS_ADJUST 315532800   /* 1980/1/1 */
 #endif
 
-#define MINUTE_SECONDS      60
-#define HOUR_SECONDS        3600
-#define DAY_SECONDS         86400
-#define WEEK_SECONDS        604800
-#define YEAR_SECONDS        31536000
-#define LEAP_YEAR_SECONDS   31622400
-
-/* Days in each month */
-static unsigned int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-
-static inline bool is_leapyear(int year)
-{
-    if( ((year%4)==0) && (((year%100)!=0) || ((year%400)==0)) )
-        return true;
-    else
-        return false;
-}
-
 #ifdef HAVE_RTC_ALARM /* as3543 */
 static struct {
     unsigned int seconds;   /* total seconds to wakeup */
@@ -197,109 +180,31 @@
 int rtc_read_datetime(struct tm *tm)
 {
     char tmp[4];
-    int i, year, mday, hour, min;
     unsigned int seconds;
+    int i;
 
-    /* RTC_AS3514's slave address is 0x46*/
+    /* Get seconds time stamp from RTC */
     for (i = 0; i < 4; i++){
         tmp[i] = ascodec_read(AS3514_RTC_0 + i);
     }
     seconds = tmp[0] + (tmp[1]<<8) + (tmp[2]<<16) + (tmp[3]<<24);
-    seconds -= SECS_ADJUST;
 
-    /* Convert seconds since Jan-1-1980 to format compatible with
-     * get_time() from firmware/common/timefuncs.c */
+    /* convert to struct tm */
+    time_t time = seconds + SECS_ADJUST;
+    gmtime_r(&time, tm);
 
-    /* weekday */
-    tm->tm_wday = ((seconds % WEEK_SECONDS) / DAY_SECONDS + 2) % 7;
-
-    /* Year */
-    year = 1980;
-    while (seconds >= LEAP_YEAR_SECONDS)
-    {
-        if (is_leapyear(year)){
-            seconds -= LEAP_YEAR_SECONDS;
-        } else {
-            seconds -= YEAR_SECONDS;
-        }
-
-        year++;
-    }
-
-    if (is_leapyear(year)) {
-        days_in_month[1] = 29;
-    } else {
-        days_in_month[1] = 28;
-        if(seconds>YEAR_SECONDS){
-            year++;
-            seconds -= YEAR_SECONDS;
-        }
-    }
-    tm->tm_year = year%100 + 100;
-
-    /* Month */
-    for (i = 0; i < 12; i++)
-    {
-        if (seconds < days_in_month[i]*DAY_SECONDS){
-            tm->tm_mon = i;
-            break;
-        }
-
-        seconds -= days_in_month[i]*DAY_SECONDS;
-    }
-
-    /* Month Day */
-    mday = seconds/DAY_SECONDS;
-    seconds -= mday*DAY_SECONDS;
-    tm->tm_mday = mday + 1; /* 1 ... 31 */
-
-    /* Hour */
-    hour = seconds/HOUR_SECONDS;
-    seconds -= hour*HOUR_SECONDS;
-    tm->tm_hour = hour;
-
-    /* Minute */
-    min = seconds/MINUTE_SECONDS;
-    seconds -= min*MINUTE_SECONDS;
-    tm->tm_min = min;
-
-    /* Second */
-    tm->tm_sec = seconds;
-
-    return 7;
+    return 1;
 }
 
 int rtc_write_datetime(const struct tm *tm)
 {
-    int i, year;
-    unsigned int year_days = 0;
-    unsigned int month_days = 0;
-    unsigned int seconds = 0;
-
-    year = 2000 + tm->tm_year - 100;
-
-    if(is_leapyear(year)) {
-        days_in_month[1] = 29;
-    } else {
-        days_in_month[1] = 28;
-    }
+    time_t time;
+    unsigned int seconds;
+    int i;
     
-    /* Number of days in months gone by this year*/
-    for(i = 0; i < tm->tm_mon; i++){
-        month_days += days_in_month[i];
-    }
-    
-    /* Number of days in years gone by since 1-Jan-1980 */
-    year_days = 365*(tm->tm_year-100+20) + (tm->tm_year-100-1)/4 + 6;
-
-    /* Convert to seconds since 1-Jan-1980 */
-    seconds = tm->tm_sec
-            + tm->tm_min*MINUTE_SECONDS
-            + tm->tm_hour*HOUR_SECONDS
-            + (tm->tm_mday-1)*DAY_SECONDS
-            + month_days*DAY_SECONDS
-            + year_days*DAY_SECONDS;
-    seconds += SECS_ADJUST;
+    /* convert struct tm to time stamp */
+    time = mktime((struct tm *)tm);
+    seconds = time - SECS_ADJUST;
 
     /* Send data to RTC */
     for (i=0; i<4; i++){
diff --git a/firmware/libc/gmtime.c b/firmware/libc/gmtime.c
new file mode 100644
index 0000000..23b9c7b
--- /dev/null
+++ b/firmware/libc/gmtime.c
@@ -0,0 +1,112 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2012 by Bertrik Sikken
+ *
+ * Based on code from: rtc_as3514.c
+ * Copyright (C) 2007 by Barry Wardell
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdbool.h>
+#include "time.h"
+
+#define MINUTE_SECONDS      60
+#define HOUR_SECONDS        3600
+#define DAY_SECONDS         86400
+#define WEEK_SECONDS        604800
+#define YEAR_SECONDS        31536000
+#define LEAP_YEAR_SECONDS   31622400
+
+/* Days in each month */
+static int days_in_month[] =
+    {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static inline bool is_leapyear(int year)
+{
+    return (((year%4)==0) && (((year%100)!=0) || ((year%400)==0)));
+}
+
+struct tm *gmtime(const time_t *timep)
+{
+    static struct tm time;
+    return gmtime_r(timep, &time);
+}
+
+struct tm *gmtime_r(const time_t *timep, struct tm *tm)
+{
+    time_t seconds = *timep;
+    int year, i, mday, hour, min;
+
+    /* weekday */
+    tm->tm_wday = ((seconds % WEEK_SECONDS) / DAY_SECONDS + 4) % 7;
+
+    /* Year */
+    year = 1970;
+    while (seconds >= LEAP_YEAR_SECONDS)
+    {
+        if (is_leapyear(year)){
+            seconds -= LEAP_YEAR_SECONDS;
+        } else {
+            seconds -= YEAR_SECONDS;
+        }
+
+        year++;
+    }
+
+    if (is_leapyear(year)) {
+        days_in_month[1] = 29;
+    } else {
+        days_in_month[1] = 28;
+        if(seconds>YEAR_SECONDS){
+            year++;
+            seconds -= YEAR_SECONDS;
+        }
+    }
+    tm->tm_year = year%100 + 100;
+
+    /* Month */
+    for (i = 0; i < 12; i++)
+    {
+        if (seconds < days_in_month[i]*DAY_SECONDS){
+            tm->tm_mon = i;
+            break;
+        }
+
+        seconds -= days_in_month[i]*DAY_SECONDS;
+    }
+
+    /* Month Day */
+    mday = seconds/DAY_SECONDS;
+    seconds -= mday*DAY_SECONDS;
+    tm->tm_mday = mday + 1; /* 1 ... 31 */
+
+    /* Hour */
+    hour = seconds/HOUR_SECONDS;
+    seconds -= hour*HOUR_SECONDS;
+    tm->tm_hour = hour;
+
+    /* Minute */
+    min = seconds/MINUTE_SECONDS;
+    seconds -= min*MINUTE_SECONDS;
+    tm->tm_min = min;
+
+    /* Second */
+    tm->tm_sec = seconds;
+
+    return tm;
+}
+
diff --git a/firmware/libc/include/time.h b/firmware/libc/include/time.h
index 912fafe..4796b8b 100644
--- a/firmware/libc/include/time.h
+++ b/firmware/libc/include/time.h
@@ -34,6 +34,8 @@
 #define _TIME_T_DECLARED
 time_t time(time_t *t);
 struct tm *localtime(const time_t *timep);
+struct tm *gmtime(const time_t *timep);
+struct tm *gmtime_r(const time_t *timep, struct tm *result);
 time_t mktime(struct tm *t);
 
 #endif /* SIMULATOR */