[SPARC64]: Add mini-RTC driver for Starfire and SUN4V.
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
b830ab665a
commit
8ba706a95b
@ -30,6 +30,8 @@
|
|||||||
#include <linux/cpufreq.h>
|
#include <linux/cpufreq.h>
|
||||||
#include <linux/percpu.h>
|
#include <linux/percpu.h>
|
||||||
#include <linux/profile.h>
|
#include <linux/profile.h>
|
||||||
|
#include <linux/miscdevice.h>
|
||||||
|
#include <linux/rtc.h>
|
||||||
|
|
||||||
#include <asm/oplib.h>
|
#include <asm/oplib.h>
|
||||||
#include <asm/mostek.h>
|
#include <asm/mostek.h>
|
||||||
@ -45,6 +47,7 @@
|
|||||||
#include <asm/smp.h>
|
#include <asm/smp.h>
|
||||||
#include <asm/sections.h>
|
#include <asm/sections.h>
|
||||||
#include <asm/cpudata.h>
|
#include <asm/cpudata.h>
|
||||||
|
#include <asm/uaccess.h>
|
||||||
|
|
||||||
DEFINE_SPINLOCK(mostek_lock);
|
DEFINE_SPINLOCK(mostek_lock);
|
||||||
DEFINE_SPINLOCK(rtc_lock);
|
DEFINE_SPINLOCK(rtc_lock);
|
||||||
@ -702,6 +705,14 @@ static u32 starfire_get_time(void)
|
|||||||
return unix_tod;
|
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)
|
static u32 hypervisor_get_time(void)
|
||||||
{
|
{
|
||||||
register unsigned long func asm("%o5");
|
register unsigned long func asm("%o5");
|
||||||
@ -731,6 +742,33 @@ retry:
|
|||||||
return 0;
|
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)
|
void __init clock_probe(void)
|
||||||
{
|
{
|
||||||
struct linux_prom_registers clk_reg[2];
|
struct linux_prom_registers clk_reg[2];
|
||||||
@ -1221,3 +1259,244 @@ static int set_rtc_mmss(unsigned long nowtime)
|
|||||||
return retval;
|
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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user