[SPARC64]: Add mini-RTC driver for Starfire and SUN4V.
authorDavid S. Miller <davem@sunset.davemloft.net>
Thu, 2 Mar 2006 01:32:46 +0000 (17:32 -0800)
committerDavid S. Miller <davem@sunset.davemloft.net>
Mon, 20 Mar 2006 09:14:10 +0000 (01:14 -0800)
Signed-off-by: David S. Miller <davem@davemloft.net>
arch/sparc64/kernel/time.c

index f6275ad..d50ebea 100644 (file)
@@ -30,6 +30,8 @@
 #include <linux/cpufreq.h>
 #include <linux/percpu.h>
 #include <linux/profile.h>
+#include <linux/miscdevice.h>
+#include <linux/rtc.h>
 
 #include <asm/oplib.h>
 #include <asm/mostek.h>
@@ -45,6 +47,7 @@
 #include <asm/smp.h>
 #include <asm/sections.h>
 #include <asm/cpudata.h>
+#include <asm/uaccess.h>
 
 DEFINE_SPINLOCK(mostek_lock);
 DEFINE_SPINLOCK(rtc_lock);
@@ -702,6 +705,14 @@ static u32 starfire_get_time(void)
        return unix_tod;
 }
 
+static int starfire_set_time(u32 val)
+{
+       /* Do nothing, time is set using the service processor
+        * console on this platform.
+        */
+       return 0;
+}
+
 static u32 hypervisor_get_time(void)
 {
        register unsigned long func asm("%o5");
@@ -731,6 +742,33 @@ retry:
        return 0;
 }
 
+static int hypervisor_set_time(u32 secs)
+{
+       register unsigned long func asm("%o5");
+       register unsigned long arg0 asm("%o0");
+       int retries = 10000;
+
+retry:
+       func = HV_FAST_TOD_SET;
+       arg0 = secs;
+       __asm__ __volatile__("ta        %4"
+                            : "=&r" (func), "=&r" (arg0)
+                            : "0" (func), "1" (arg0),
+                              "i" (HV_FAST_TRAP));
+       if (arg0 == HV_EOK)
+               return 0;
+       if (arg0 == HV_EWOULDBLOCK) {
+               if (--retries > 0) {
+                       udelay(100);
+                       goto retry;
+               }
+               printk(KERN_WARNING "SUN4V: tod_set() timed out.\n");
+               return -EAGAIN;
+       }
+       printk(KERN_WARNING "SUN4V: tod_set() not supported.\n");
+       return -EOPNOTSUPP;
+}
+
 void __init clock_probe(void)
 {
        struct linux_prom_registers clk_reg[2];
@@ -1221,3 +1259,244 @@ static int set_rtc_mmss(unsigned long nowtime)
                return retval;
        }
 }
+
+#define RTC_IS_OPEN            0x01    /* means /dev/rtc is in use     */
+static unsigned char mini_rtc_status;  /* bitmapped status byte.       */
+
+/* months start at 0 now */
+static unsigned char days_in_mo[] =
+{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+#define FEBRUARY       2
+#define        STARTOFTIME     1970
+#define SECDAY         86400L
+#define SECYR          (SECDAY * 365)
+#define        leapyear(year)          ((year) % 4 == 0 && \
+                                ((year) % 100 != 0 || (year) % 400 == 0))
+#define        days_in_year(a)         (leapyear(a) ? 366 : 365)
+#define        days_in_month(a)        (month_days[(a) - 1])
+
+static int month_days[12] = {
+       31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ * This only works for the Gregorian calendar - i.e. after 1752 (in the UK)
+ */
+static void GregorianDay(struct rtc_time * tm)
+{
+       int leapsToDate;
+       int lastYear;
+       int day;
+       int MonthOffset[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
+
+       lastYear = tm->tm_year - 1;
+
+       /*
+        * Number of leap corrections to apply up to end of last year
+        */
+       leapsToDate = lastYear / 4 - lastYear / 100 + lastYear / 400;
+
+       /*
+        * This year is a leap year if it is divisible by 4 except when it is
+        * divisible by 100 unless it is divisible by 400
+        *
+        * e.g. 1904 was a leap year, 1900 was not, 1996 is, and 2000 was
+        */
+       day = tm->tm_mon > 2 && leapyear(tm->tm_year);
+
+       day += lastYear*365 + leapsToDate + MonthOffset[tm->tm_mon-1] +
+                  tm->tm_mday;
+
+       tm->tm_wday = day % 7;
+}
+
+static void to_tm(int tim, struct rtc_time *tm)
+{
+       register int    i;
+       register long   hms, day;
+
+       day = tim / SECDAY;
+       hms = tim % SECDAY;
+
+       /* Hours, minutes, seconds are easy */
+       tm->tm_hour = hms / 3600;
+       tm->tm_min = (hms % 3600) / 60;
+       tm->tm_sec = (hms % 3600) % 60;
+
+       /* Number of years in days */
+       for (i = STARTOFTIME; day >= days_in_year(i); i++)
+               day -= days_in_year(i);
+       tm->tm_year = i;
+
+       /* Number of months in days left */
+       if (leapyear(tm->tm_year))
+               days_in_month(FEBRUARY) = 29;
+       for (i = 1; day >= days_in_month(i); i++)
+               day -= days_in_month(i);
+       days_in_month(FEBRUARY) = 28;
+       tm->tm_mon = i;
+
+       /* Days are what is left over (+1) from all that. */
+       tm->tm_mday = day + 1;
+
+       /*
+        * Determine the day of week
+        */
+       GregorianDay(tm);
+}
+
+/* Both Starfire and SUN4V give us seconds since Jan 1st, 1970,
+ * aka Unix time.  So we have to convert to/from rtc_time.
+ */
+static inline void mini_get_rtc_time(struct rtc_time *time)
+{
+       unsigned long flags;
+       u32 seconds;
+
+       spin_lock_irqsave(&rtc_lock, flags);
+       seconds = 0;
+       if (this_is_starfire)
+               seconds = starfire_get_time();
+       else if (tlb_type == hypervisor)
+               seconds = hypervisor_get_time();
+       spin_unlock_irqrestore(&rtc_lock, flags);
+
+       to_tm(seconds, time);
+}
+
+static inline int mini_set_rtc_time(struct rtc_time *time)
+{
+       u32 seconds = mktime(time->tm_year + 1900, time->tm_mon + 1,
+                            time->tm_mday, time->tm_hour,
+                            time->tm_min, time->tm_sec);
+       unsigned long flags;
+       int err;
+
+       spin_lock_irqsave(&rtc_lock, flags);
+       err = -ENODEV;
+       if (this_is_starfire)
+               err = starfire_set_time(seconds);
+       else  if (tlb_type == hypervisor)
+               err = hypervisor_set_time(seconds);
+       spin_unlock_irqrestore(&rtc_lock, flags);
+
+       return err;
+}
+
+static int mini_rtc_ioctl(struct inode *inode, struct file *file,
+                         unsigned int cmd, unsigned long arg)
+{
+       struct rtc_time wtime;
+       void __user *argp = (void __user *)arg;
+
+       switch (cmd) {
+
+       case RTC_PLL_GET:
+               return -EINVAL;
+
+       case RTC_PLL_SET:
+               return -EINVAL;
+
+       case RTC_UIE_OFF:       /* disable ints from RTC updates.       */
+               return 0;
+
+       case RTC_UIE_ON:        /* enable ints for RTC updates. */
+               return -EINVAL;
+
+       case RTC_RD_TIME:       /* Read the time/date from RTC  */
+               /* this doesn't get week-day, who cares */
+               memset(&wtime, 0, sizeof(wtime));
+               mini_get_rtc_time(&wtime);
+
+               return copy_to_user(argp, &wtime, sizeof(wtime)) ? -EFAULT : 0;
+
+       case RTC_SET_TIME:      /* Set the RTC */
+           {
+               int year;
+               unsigned char leap_yr;
+
+               if (!capable(CAP_SYS_TIME))
+                       return -EACCES;
+
+               if (copy_from_user(&wtime, argp, sizeof(wtime)))
+                       return -EFAULT;
+
+               year = wtime.tm_year + 1900;
+               leap_yr = ((!(year % 4) && (year % 100)) ||
+                          !(year % 400));
+
+               if ((wtime.tm_mon < 0 || wtime.tm_mon > 11) || (wtime.tm_mday < 1))
+                       return -EINVAL;
+
+               if (wtime.tm_mday < 0 || wtime.tm_mday >
+                   (days_in_mo[wtime.tm_mon] + ((wtime.tm_mon == 1) && leap_yr)))
+                       return -EINVAL;
+
+               if (wtime.tm_hour < 0 || wtime.tm_hour >= 24 ||
+                   wtime.tm_min < 0 || wtime.tm_min >= 60 ||
+                   wtime.tm_sec < 0 || wtime.tm_sec >= 60)
+                       return -EINVAL;
+
+               return mini_set_rtc_time(&wtime);
+           }
+       }
+
+       return -EINVAL;
+}
+
+static int mini_rtc_open(struct inode *inode, struct file *file)
+{
+       if (mini_rtc_status & RTC_IS_OPEN)
+               return -EBUSY;
+
+       mini_rtc_status |= RTC_IS_OPEN;
+
+       return 0;
+}
+
+static int mini_rtc_release(struct inode *inode, struct file *file)
+{
+       mini_rtc_status &= ~RTC_IS_OPEN;
+       return 0;
+}
+
+
+static struct file_operations mini_rtc_fops = {
+       .owner          = THIS_MODULE,
+       .ioctl          = mini_rtc_ioctl,
+       .open           = mini_rtc_open,
+       .release        = mini_rtc_release,
+};
+
+static struct miscdevice rtc_mini_dev =
+{
+       .minor          = RTC_MINOR,
+       .name           = "rtc",
+       .fops           = &mini_rtc_fops,
+};
+
+static int __init rtc_mini_init(void)
+{
+       int retval;
+
+       if (tlb_type != hypervisor && !this_is_starfire)
+               return -ENODEV;
+
+       printk(KERN_INFO "Mini RTC Driver\n");
+
+       retval = misc_register(&rtc_mini_dev);
+       if (retval < 0)
+               return retval;
+
+       return 0;
+}
+
+static void __exit rtc_mini_exit(void)
+{
+       misc_deregister(&rtc_mini_dev);
+}
+
+
+module_init(rtc_mini_init);
+module_exit(rtc_mini_exit);