diff options
author | David S. Miller <davem@sunset.davemloft.net> | 2006-03-01 20:32:46 -0500 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-03-20 04:14:10 -0500 |
commit | 8ba706a95bb92c3b14b812f6d507890336d19136 (patch) | |
tree | 2456134d50898a7bd8efda4adcfa636ab0ea43cf /arch/sparc64 | |
parent | b830ab665ad96c6b20d51a89b35cbc09ab5a2c29 (diff) |
[SPARC64]: Add mini-RTC driver for Starfire and SUN4V.
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'arch/sparc64')
-rw-r--r-- | arch/sparc64/kernel/time.c | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/arch/sparc64/kernel/time.c b/arch/sparc64/kernel/time.c index f6275adbc811..d50ebeae144a 100644 --- a/arch/sparc64/kernel/time.c +++ b/arch/sparc64/kernel/time.c | |||
@@ -30,6 +30,8 @@ | |||
30 | #include <linux/cpufreq.h> | 30 | #include <linux/cpufreq.h> |
31 | #include <linux/percpu.h> | 31 | #include <linux/percpu.h> |
32 | #include <linux/profile.h> | 32 | #include <linux/profile.h> |
33 | #include <linux/miscdevice.h> | ||
34 | #include <linux/rtc.h> | ||
33 | 35 | ||
34 | #include <asm/oplib.h> | 36 | #include <asm/oplib.h> |
35 | #include <asm/mostek.h> | 37 | #include <asm/mostek.h> |
@@ -45,6 +47,7 @@ | |||
45 | #include <asm/smp.h> | 47 | #include <asm/smp.h> |
46 | #include <asm/sections.h> | 48 | #include <asm/sections.h> |
47 | #include <asm/cpudata.h> | 49 | #include <asm/cpudata.h> |
50 | #include <asm/uaccess.h> | ||
48 | 51 | ||
49 | DEFINE_SPINLOCK(mostek_lock); | 52 | DEFINE_SPINLOCK(mostek_lock); |
50 | DEFINE_SPINLOCK(rtc_lock); | 53 | DEFINE_SPINLOCK(rtc_lock); |
@@ -702,6 +705,14 @@ static u32 starfire_get_time(void) | |||
702 | return unix_tod; | 705 | return unix_tod; |
703 | } | 706 | } |
704 | 707 | ||
708 | static int starfire_set_time(u32 val) | ||
709 | { | ||
710 | /* Do nothing, time is set using the service processor | ||
711 | * console on this platform. | ||
712 | */ | ||
713 | return 0; | ||
714 | } | ||
715 | |||
705 | static u32 hypervisor_get_time(void) | 716 | static u32 hypervisor_get_time(void) |
706 | { | 717 | { |
707 | register unsigned long func asm("%o5"); | 718 | register unsigned long func asm("%o5"); |
@@ -731,6 +742,33 @@ retry: | |||
731 | return 0; | 742 | return 0; |
732 | } | 743 | } |
733 | 744 | ||
745 | static int hypervisor_set_time(u32 secs) | ||
746 | { | ||
747 | register unsigned long func asm("%o5"); | ||
748 | register unsigned long arg0 asm("%o0"); | ||
749 | int retries = 10000; | ||
750 | |||
751 | retry: | ||
752 | func = HV_FAST_TOD_SET; | ||
753 | arg0 = secs; | ||
754 | __asm__ __volatile__("ta %4" | ||
755 | : "=&r" (func), "=&r" (arg0) | ||
756 | : "0" (func), "1" (arg0), | ||
757 | "i" (HV_FAST_TRAP)); | ||
758 | if (arg0 == HV_EOK) | ||
759 | return 0; | ||
760 | if (arg0 == HV_EWOULDBLOCK) { | ||
761 | if (--retries > 0) { | ||
762 | udelay(100); | ||
763 | goto retry; | ||
764 | } | ||
765 | printk(KERN_WARNING "SUN4V: tod_set() timed out.\n"); | ||
766 | return -EAGAIN; | ||
767 | } | ||
768 | printk(KERN_WARNING "SUN4V: tod_set() not supported.\n"); | ||
769 | return -EOPNOTSUPP; | ||
770 | } | ||
771 | |||
734 | void __init clock_probe(void) | 772 | void __init clock_probe(void) |
735 | { | 773 | { |
736 | struct linux_prom_registers clk_reg[2]; | 774 | struct linux_prom_registers clk_reg[2]; |
@@ -1221,3 +1259,244 @@ static int set_rtc_mmss(unsigned long nowtime) | |||
1221 | return retval; | 1259 | return retval; |
1222 | } | 1260 | } |
1223 | } | 1261 | } |
1262 | |||
1263 | #define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */ | ||
1264 | static unsigned char mini_rtc_status; /* bitmapped status byte. */ | ||
1265 | |||
1266 | /* months start at 0 now */ | ||
1267 | static unsigned char days_in_mo[] = | ||
1268 | {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; | ||
1269 | |||
1270 | #define FEBRUARY 2 | ||
1271 | #define STARTOFTIME 1970 | ||
1272 | #define SECDAY 86400L | ||
1273 | #define SECYR (SECDAY * 365) | ||
1274 | #define leapyear(year) ((year) % 4 == 0 && \ | ||
1275 | ((year) % 100 != 0 || (year) % 400 == 0)) | ||
1276 | #define days_in_year(a) (leapyear(a) ? 366 : 365) | ||
1277 | #define days_in_month(a) (month_days[(a) - 1]) | ||
1278 | |||
1279 | static int month_days[12] = { | ||
1280 | 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 | ||
1281 | }; | ||
1282 | |||
1283 | /* | ||
1284 | * This only works for the Gregorian calendar - i.e. after 1752 (in the UK) | ||
1285 | */ | ||
1286 | static void GregorianDay(struct rtc_time * tm) | ||
1287 | { | ||
1288 | int leapsToDate; | ||
1289 | int lastYear; | ||
1290 | int day; | ||
1291 | int MonthOffset[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; | ||
1292 | |||
1293 | lastYear = tm->tm_year - 1; | ||
1294 | |||
1295 | /* | ||
1296 | * Number of leap corrections to apply up to end of last year | ||
1297 | */ | ||
1298 | leapsToDate = lastYear / 4 - lastYear / 100 + lastYear / 400; | ||
1299 | |||
1300 | /* | ||
1301 | * This year is a leap year if it is divisible by 4 except when it is | ||
1302 | * divisible by 100 unless it is divisible by 400 | ||
1303 | * | ||
1304 | * e.g. 1904 was a leap year, 1900 was not, 1996 is, and 2000 was | ||
1305 | */ | ||
1306 | day = tm->tm_mon > 2 && leapyear(tm->tm_year); | ||
1307 | |||
1308 | day += lastYear*365 + leapsToDate + MonthOffset[tm->tm_mon-1] + | ||
1309 | tm->tm_mday; | ||
1310 | |||
1311 | tm->tm_wday = day % 7; | ||
1312 | } | ||
1313 | |||
1314 | static void to_tm(int tim, struct rtc_time *tm) | ||
1315 | { | ||
1316 | register int i; | ||
1317 | register long hms, day; | ||
1318 | |||
1319 | day = tim / SECDAY; | ||
1320 | hms = tim % SECDAY; | ||
1321 | |||
1322 | /* Hours, minutes, seconds are easy */ | ||
1323 | tm->tm_hour = hms / 3600; | ||
1324 | tm->tm_min = (hms % 3600) / 60; | ||
1325 | tm->tm_sec = (hms % 3600) % 60; | ||
1326 | |||
1327 | /* Number of years in days */ | ||
1328 | for (i = STARTOFTIME; day >= days_in_year(i); i++) | ||
1329 | day -= days_in_year(i); | ||
1330 | tm->tm_year = i; | ||
1331 | |||
1332 | /* Number of months in days left */ | ||
1333 | if (leapyear(tm->tm_year)) | ||
1334 | days_in_month(FEBRUARY) = 29; | ||
1335 | for (i = 1; day >= days_in_month(i); i++) | ||
1336 | day -= days_in_month(i); | ||
1337 | days_in_month(FEBRUARY) = 28; | ||
1338 | tm->tm_mon = i; | ||
1339 | |||
1340 | /* Days are what is left over (+1) from all that. */ | ||
1341 | tm->tm_mday = day + 1; | ||
1342 | |||
1343 | /* | ||
1344 | * Determine the day of week | ||
1345 | */ | ||
1346 | GregorianDay(tm); | ||
1347 | } | ||
1348 | |||
1349 | /* Both Starfire and SUN4V give us seconds since Jan 1st, 1970, | ||
1350 | * aka Unix time. So we have to convert to/from rtc_time. | ||
1351 | */ | ||
1352 | static inline void mini_get_rtc_time(struct rtc_time *time) | ||
1353 | { | ||
1354 | unsigned long flags; | ||
1355 | u32 seconds; | ||
1356 | |||
1357 | spin_lock_irqsave(&rtc_lock, flags); | ||
1358 | seconds = 0; | ||
1359 | if (this_is_starfire) | ||
1360 | seconds = starfire_get_time(); | ||
1361 | else if (tlb_type == hypervisor) | ||
1362 | seconds = hypervisor_get_time(); | ||
1363 | spin_unlock_irqrestore(&rtc_lock, flags); | ||
1364 | |||
1365 | to_tm(seconds, time); | ||
1366 | } | ||
1367 | |||
1368 | static inline int mini_set_rtc_time(struct rtc_time *time) | ||
1369 | { | ||
1370 | u32 seconds = mktime(time->tm_year + 1900, time->tm_mon + 1, | ||
1371 | time->tm_mday, time->tm_hour, | ||
1372 | time->tm_min, time->tm_sec); | ||
1373 | unsigned long flags; | ||
1374 | int err; | ||
1375 | |||
1376 | spin_lock_irqsave(&rtc_lock, flags); | ||
1377 | err = -ENODEV; | ||
1378 | if (this_is_starfire) | ||
1379 | err = starfire_set_time(seconds); | ||
1380 | else if (tlb_type == hypervisor) | ||
1381 | err = hypervisor_set_time(seconds); | ||
1382 | spin_unlock_irqrestore(&rtc_lock, flags); | ||
1383 | |||
1384 | return err; | ||
1385 | } | ||
1386 | |||
1387 | static int mini_rtc_ioctl(struct inode *inode, struct file *file, | ||
1388 | unsigned int cmd, unsigned long arg) | ||
1389 | { | ||
1390 | struct rtc_time wtime; | ||
1391 | void __user *argp = (void __user *)arg; | ||
1392 | |||
1393 | switch (cmd) { | ||
1394 | |||
1395 | case RTC_PLL_GET: | ||
1396 | return -EINVAL; | ||
1397 | |||
1398 | case RTC_PLL_SET: | ||
1399 | return -EINVAL; | ||
1400 | |||
1401 | case RTC_UIE_OFF: /* disable ints from RTC updates. */ | ||
1402 | return 0; | ||
1403 | |||
1404 | case RTC_UIE_ON: /* enable ints for RTC updates. */ | ||
1405 | return -EINVAL; | ||
1406 | |||
1407 | case RTC_RD_TIME: /* Read the time/date from RTC */ | ||
1408 | /* this doesn't get week-day, who cares */ | ||
1409 | memset(&wtime, 0, sizeof(wtime)); | ||
1410 | mini_get_rtc_time(&wtime); | ||
1411 | |||
1412 | return copy_to_user(argp, &wtime, sizeof(wtime)) ? -EFAULT : 0; | ||
1413 | |||
1414 | case RTC_SET_TIME: /* Set the RTC */ | ||
1415 | { | ||
1416 | int year; | ||
1417 | unsigned char leap_yr; | ||
1418 | |||
1419 | if (!capable(CAP_SYS_TIME)) | ||
1420 | return -EACCES; | ||
1421 | |||
1422 | if (copy_from_user(&wtime, argp, sizeof(wtime))) | ||
1423 | return -EFAULT; | ||
1424 | |||
1425 | year = wtime.tm_year + 1900; | ||
1426 | leap_yr = ((!(year % 4) && (year % 100)) || | ||
1427 | !(year % 400)); | ||
1428 | |||
1429 | if ((wtime.tm_mon < 0 || wtime.tm_mon > 11) || (wtime.tm_mday < 1)) | ||
1430 | return -EINVAL; | ||
1431 | |||
1432 | if (wtime.tm_mday < 0 || wtime.tm_mday > | ||
1433 | (days_in_mo[wtime.tm_mon] + ((wtime.tm_mon == 1) && leap_yr))) | ||
1434 | return -EINVAL; | ||
1435 | |||
1436 | if (wtime.tm_hour < 0 || wtime.tm_hour >= 24 || | ||
1437 | wtime.tm_min < 0 || wtime.tm_min >= 60 || | ||
1438 | wtime.tm_sec < 0 || wtime.tm_sec >= 60) | ||
1439 | return -EINVAL; | ||
1440 | |||
1441 | return mini_set_rtc_time(&wtime); | ||
1442 | } | ||
1443 | } | ||
1444 | |||
1445 | return -EINVAL; | ||
1446 | } | ||
1447 | |||
1448 | static int mini_rtc_open(struct inode *inode, struct file *file) | ||
1449 | { | ||
1450 | if (mini_rtc_status & RTC_IS_OPEN) | ||
1451 | return -EBUSY; | ||
1452 | |||
1453 | mini_rtc_status |= RTC_IS_OPEN; | ||
1454 | |||
1455 | return 0; | ||
1456 | } | ||
1457 | |||
1458 | static int mini_rtc_release(struct inode *inode, struct file *file) | ||
1459 | { | ||
1460 | mini_rtc_status &= ~RTC_IS_OPEN; | ||
1461 | return 0; | ||
1462 | } | ||
1463 | |||
1464 | |||
1465 | static struct file_operations mini_rtc_fops = { | ||
1466 | .owner = THIS_MODULE, | ||
1467 | .ioctl = mini_rtc_ioctl, | ||
1468 | .open = mini_rtc_open, | ||
1469 | .release = mini_rtc_release, | ||
1470 | }; | ||
1471 | |||
1472 | static struct miscdevice rtc_mini_dev = | ||
1473 | { | ||
1474 | .minor = RTC_MINOR, | ||
1475 | .name = "rtc", | ||
1476 | .fops = &mini_rtc_fops, | ||
1477 | }; | ||
1478 | |||
1479 | static int __init rtc_mini_init(void) | ||
1480 | { | ||
1481 | int retval; | ||
1482 | |||
1483 | if (tlb_type != hypervisor && !this_is_starfire) | ||
1484 | return -ENODEV; | ||
1485 | |||
1486 | printk(KERN_INFO "Mini RTC Driver\n"); | ||
1487 | |||
1488 | retval = misc_register(&rtc_mini_dev); | ||
1489 | if (retval < 0) | ||
1490 | return retval; | ||
1491 | |||
1492 | return 0; | ||
1493 | } | ||
1494 | |||
1495 | static void __exit rtc_mini_exit(void) | ||
1496 | { | ||
1497 | misc_deregister(&rtc_mini_dev); | ||
1498 | } | ||
1499 | |||
1500 | |||
1501 | module_init(rtc_mini_init); | ||
1502 | module_exit(rtc_mini_exit); | ||