1/*
2 *	Real Time Clock interface for Linux on the MVME16x
3 *
4 * Based on the PC driver by Paul Gortmaker.
5 */
6
7#define RTC_VERSION		"1.00"
8
9#include <linux/types.h>
10#include <linux/errno.h>
11#include <linux/miscdevice.h>
12#include <linux/ioport.h>
13#include <linux/capability.h>
14#include <linux/fcntl.h>
15#include <linux/init.h>
16#include <linux/poll.h>
17#include <linux/mc146818rtc.h>	/* For struct rtc_time and ioctls, etc */
18#include <linux/bcd.h>
19#include <asm/mvme16xhw.h>
20
21#include <asm/io.h>
22#include <asm/uaccess.h>
23#include <asm/setup.h>
24
25/*
26 *	We sponge a minor off of the misc major. No need slurping
27 *	up another valuable major dev number for this. If you add
28 *	an ioctl, make sure you don't conflict with SPARC's RTC
29 *	ioctls.
30 */
31
32static const unsigned char days_in_mo[] =
33{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
34
35static atomic_t rtc_ready = ATOMIC_INIT(1);
36
37static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
38{
39	volatile MK48T08ptr_t rtc = (MK48T08ptr_t)MVME_RTC_BASE;
40	unsigned long flags;
41	struct rtc_time wtime;
42	void __user *argp = (void __user *)arg;
43
44	switch (cmd) {
45	case RTC_RD_TIME:	/* Read the time/date from RTC	*/
46	{
47		local_irq_save(flags);
48		/* Ensure clock and real-time-mode-register are accessible */
49		rtc->ctrl = RTC_READ;
50		memset(&wtime, 0, sizeof(struct rtc_time));
51		wtime.tm_sec =  bcd2bin(rtc->bcd_sec);
52		wtime.tm_min =  bcd2bin(rtc->bcd_min);
53		wtime.tm_hour = bcd2bin(rtc->bcd_hr);
54		wtime.tm_mday =  bcd2bin(rtc->bcd_dom);
55		wtime.tm_mon =  bcd2bin(rtc->bcd_mth)-1;
56		wtime.tm_year = bcd2bin(rtc->bcd_year);
57		if (wtime.tm_year < 70)
58			wtime.tm_year += 100;
59		wtime.tm_wday = bcd2bin(rtc->bcd_dow)-1;
60		rtc->ctrl = 0;
61		local_irq_restore(flags);
62		return copy_to_user(argp, &wtime, sizeof wtime) ?
63								-EFAULT : 0;
64	}
65	case RTC_SET_TIME:	/* Set the RTC */
66	{
67		struct rtc_time rtc_tm;
68		unsigned char mon, day, hrs, min, sec, leap_yr;
69		unsigned int yrs;
70
71		if (!capable(CAP_SYS_ADMIN))
72			return -EACCES;
73
74		if (copy_from_user(&rtc_tm, argp, sizeof(struct rtc_time)))
75			return -EFAULT;
76
77		yrs = rtc_tm.tm_year;
78		if (yrs < 1900)
79			yrs += 1900;
80		mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
81		day = rtc_tm.tm_mday;
82		hrs = rtc_tm.tm_hour;
83		min = rtc_tm.tm_min;
84		sec = rtc_tm.tm_sec;
85
86		leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
87
88		if ((mon > 12) || (day == 0))
89			return -EINVAL;
90
91		if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
92			return -EINVAL;
93
94		if ((hrs >= 24) || (min >= 60) || (sec >= 60))
95			return -EINVAL;
96
97		if (yrs >= 2070)
98			return -EINVAL;
99
100		local_irq_save(flags);
101		rtc->ctrl     = RTC_WRITE;
102
103		rtc->bcd_sec  = bin2bcd(sec);
104		rtc->bcd_min  = bin2bcd(min);
105		rtc->bcd_hr   = bin2bcd(hrs);
106		rtc->bcd_dom  = bin2bcd(day);
107		rtc->bcd_mth  = bin2bcd(mon);
108		rtc->bcd_year = bin2bcd(yrs%100);
109
110		rtc->ctrl     = 0;
111		local_irq_restore(flags);
112		return 0;
113	}
114	default:
115		return -EINVAL;
116	}
117}
118
119/*
120 * We enforce only one user at a time here with the open/close.
121 */
122static int rtc_open(struct inode *inode, struct file *file)
123{
124	if( !atomic_dec_and_test(&rtc_ready) )
125	{
126		atomic_inc( &rtc_ready );
127		return -EBUSY;
128	}
129	return 0;
130}
131
132static int rtc_release(struct inode *inode, struct file *file)
133{
134	atomic_inc( &rtc_ready );
135	return 0;
136}
137
138/*
139 *	The various file operations we support.
140 */
141
142static const struct file_operations rtc_fops = {
143	.unlocked_ioctl	= rtc_ioctl,
144	.open		= rtc_open,
145	.release	= rtc_release,
146	.llseek		= noop_llseek,
147};
148
149static struct miscdevice rtc_dev=
150{
151	.minor =	RTC_MINOR,
152	.name =		"rtc",
153	.fops =		&rtc_fops
154};
155
156static int __init rtc_MK48T08_init(void)
157{
158	if (!MACH_IS_MVME16x)
159		return -ENODEV;
160
161	printk(KERN_INFO "MK48T08 Real Time Clock Driver v%s\n", RTC_VERSION);
162	return misc_register(&rtc_dev);
163}
164device_initcall(rtc_MK48T08_init);
165