lkml.org 
[lkml]   [2003]   [Mar]   [21]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
SubjectPATCH: real time clock support for PC9800 systems

diff -u --new-file --recursive --exclude-from /usr/src/exclude linux-2.5.65/drivers/char/upd4990a.c linux-2.5.65-ac2/drivers/char/upd4990a.c
--- linux-2.5.65/drivers/char/upd4990a.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.5.65-ac2/drivers/char/upd4990a.c 2003-03-14 01:23:19.000000000 +0000
@@ -0,0 +1,423 @@
+/*
+ * NEC PC-9800 Real Time Clock interface for Linux
+ *
+ * Copyright (C) 1997-2001 Linux/98 project,
+ * Kyoto University Microcomputer Club.
+ *
+ * Based on:
+ * drivers/char/rtc.c by Paul Gortmaker
+ *
+ * Changes:
+ * 2001-02-09 Call check_region on rtc_init and do not request I/O 0033h.
+ * Call del_timer and release_region on rtc_exit. -- tak
+ * 2001-07-14 Rewrite <linux/upd4990a.h> and split to <linux/upd4990a.h>
+ * and <asm-i386/upd4990a.h>.
+ * Introduce a lot of spin_lock/unlock (&rtc_lock).
+ */
+
+#define RTC98_VERSION "1.2"
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/upd4990a.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/*
+ * We sponge a minor off of the misc major. No need slurping
+ * up another valuable major dev number for this. If you add
+ * an ioctl, make sure you don't conflict with SPARC's RTC
+ * ioctls.
+ */
+
+static struct fasync_struct *rtc_async_queue;
+
+static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);
+
+static struct timer_list rtc_uie_timer;
+static u8 old_refclk;
+
+static int rtc_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static int rtc_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+
+/*
+ * Bits in rtc_status. (5 bits of room for future expansion)
+ */
+
+#define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */
+#define RTC_TIMER_ON 0x02 /* not used */
+#define RTC_UIE_TIMER_ON 0x04 /* UIE emulation timer is active */
+
+/*
+ * rtc_status is never changed by rtc_interrupt, and ioctl/open/close is
+ * protected by the big kernel lock. However, ioctl can still disable the timer
+ * in rtc_status and then with del_timer after the interrupt has read
+ * rtc_status but before mod_timer is called, which would then reenable the
+ * timer (but you would need to have an awful timing before you'd trip on it)
+ */
+static unsigned char rtc_status; /* bitmapped status byte. */
+static unsigned long rtc_irq_data; /* our output to the world */
+
+static const unsigned char days_in_mo[] =
+{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+extern spinlock_t rtc_lock; /* defined in arch/i386/kernel/time.c */
+
+static void rtc_uie_intr(unsigned long data)
+{
+ u8 refclk, tmp;
+
+ /* Kernel timer does del_timer internally before calling
+ each timer entry, so this is unnecessary.
+ del_timer(&rtc_uie_timer); */
+ spin_lock(&rtc_lock);
+
+ /* Detect rising edge of 1Hz reference clock. */
+ refclk = UPD4990A_READ_DATA();
+ tmp = old_refclk & refclk;
+ old_refclk = ~refclk;
+ if (!(tmp & 1))
+ rtc_irq_data += 0x100;
+
+ spin_unlock(&rtc_lock);
+
+ if (!(tmp & 1)) {
+ /* Now do the rest of the actions */
+ wake_up_interruptible(&rtc_wait);
+ kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
+ }
+
+ rtc_uie_timer.expires = jiffies + 1;
+ add_timer(&rtc_uie_timer);
+}
+
+/*
+ * Now all the various file operations that we export.
+ */
+
+static ssize_t rtc_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long data;
+ ssize_t retval = 0;
+
+ if (count < sizeof(unsigned long))
+ return -EINVAL;
+
+ add_wait_queue(&rtc_wait, &wait);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ do {
+ /* First make it right. Then make it fast. Putting this whole
+ * block within the parentheses of a while would be too
+ * confusing. And no, xchg() is not the answer. */
+ spin_lock_irq(&rtc_lock);
+ data = rtc_irq_data;
+ rtc_irq_data = 0;
+ spin_unlock_irq(&rtc_lock);
+
+ if (data != 0)
+ break;
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto out;
+ }
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ goto out;
+ }
+ schedule();
+ } while (1);
+
+ retval = put_user(data, (unsigned long *)buf);
+ if (!retval)
+ retval = sizeof(unsigned long);
+ out:
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&rtc_wait, &wait);
+
+ return retval;
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rtc_time wtime;
+ struct upd4990a_raw_data raw;
+
+ switch (cmd) {
+ case RTC_UIE_OFF: /* Mask ints from RTC updates. */
+ spin_lock_irq(&rtc_lock);
+ if (rtc_status & RTC_UIE_TIMER_ON) {
+ rtc_status &= ~RTC_UIE_TIMER_ON;
+ del_timer(&rtc_uie_timer);
+ }
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+
+ case RTC_UIE_ON: /* Allow ints for RTC updates. */
+ spin_lock_irq(&rtc_lock);
+ rtc_irq_data = 0;
+ if (!(rtc_status & RTC_UIE_TIMER_ON)) {
+ rtc_status |= RTC_UIE_TIMER_ON;
+ rtc_uie_timer.expires = jiffies + 1;
+ add_timer(&rtc_uie_timer);
+ }
+ /* Just in case... */
+ upd4990a_serial_command(UPD4990A_REGISTER_HOLD);
+ old_refclk = ~UPD4990A_READ_DATA();
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+
+ case RTC_RD_TIME: /* Read the time/date from RTC */
+ spin_lock_irq(&rtc_lock);
+ upd4990a_get_time(&raw, 0);
+ spin_unlock_irq(&rtc_lock);
+
+ wtime.tm_sec = BCD2BIN(raw.sec);
+ wtime.tm_min = BCD2BIN(raw.min);
+ wtime.tm_hour = BCD2BIN(raw.hour);
+ wtime.tm_mday = BCD2BIN(raw.mday);
+ wtime.tm_mon = raw.mon - 1; /* convert to 0-base */
+ wtime.tm_wday = raw.wday;
+
+ /*
+ * Account for differences between how the RTC uses the values
+ * and how they are defined in a struct rtc_time;
+ */
+ if ((wtime.tm_year = BCD2BIN(raw.year)) < 95)
+ wtime.tm_year += 100;
+
+ wtime.tm_isdst = 0;
+ break;
+
+ case RTC_SET_TIME: /* Set the RTC */
+ {
+ int leap_yr;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ if (copy_from_user(&wtime, (struct rtc_time *) arg,
+ sizeof (struct rtc_time)))
+ return -EFAULT;
+
+ /* Valid year is 1995 - 2094, inclusive. */
+ if (wtime.tm_year < 95 || wtime.tm_year > 194)
+ return -EINVAL;
+
+ if (wtime.tm_mon > 11 || wtime.tm_mday == 0)
+ return -EINVAL;
+
+ /* For acceptable year domain (1995 - 2094),
+ this IS sufficient. */
+ leap_yr = !(wtime.tm_year % 4);
+
+ if (wtime.tm_mday > (days_in_mo[wtime.tm_mon]
+ + (wtime.tm_mon == 2 && leap_yr)))
+ return -EINVAL;
+
+ if (wtime.tm_hour >= 24
+ || wtime.tm_min >= 60 || wtime.tm_sec >= 60)
+ return -EINVAL;
+
+ if (wtime.tm_wday > 6)
+ return -EINVAL;
+
+ raw.sec = BIN2BCD(wtime.tm_sec);
+ raw.min = BIN2BCD(wtime.tm_min);
+ raw.hour = BIN2BCD(wtime.tm_hour);
+ raw.mday = BIN2BCD(wtime.tm_mday);
+ raw.mon = wtime.tm_mon + 1;
+ raw.wday = wtime.tm_wday;
+ raw.year = BIN2BCD(wtime.tm_year % 100);
+
+ spin_lock_irq(&rtc_lock);
+ upd4990a_set_time(&raw, 0);
+ spin_unlock_irq(&rtc_lock);
+
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+ return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ * Also clear the previous interrupt data on an open, and clean
+ * up things on a close.
+ */
+
+static int rtc_open(struct inode *inode, struct file *file)
+{
+ spin_lock_irq(&rtc_lock);
+
+ if(rtc_status & RTC_IS_OPEN)
+ goto out_busy;
+
+ rtc_status |= RTC_IS_OPEN;
+
+ rtc_irq_data = 0;
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+
+ out_busy:
+ spin_unlock_irq(&rtc_lock);
+ return -EBUSY;
+}
+
+static int rtc_fasync(int fd, struct file *filp, int on)
+{
+ return fasync_helper(fd, filp, on, &rtc_async_queue);
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+ del_timer(&rtc_uie_timer);
+
+ if (file->f_flags & FASYNC)
+ rtc_fasync(-1, file, 0);
+
+ rtc_irq_data = 0;
+
+ /* No need for locking -- nobody else can do anything until this rmw is
+ * committed, and no timer is running. */
+ rtc_status &= ~(RTC_IS_OPEN | RTC_UIE_TIMER_ON);
+ return 0;
+}
+
+static unsigned int rtc_poll(struct file *file, poll_table *wait)
+{
+ unsigned long l;
+
+ poll_wait(file, &rtc_wait, wait);
+
+ spin_lock_irq(&rtc_lock);
+ l = rtc_irq_data;
+ spin_unlock_irq(&rtc_lock);
+
+ if (l != 0)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations rtc_fops = {
+ .owner = THIS_MODULE,
+ .read = rtc_read,
+ .poll = rtc_poll,
+ .ioctl = rtc_ioctl,
+ .open = rtc_open,
+ .release = rtc_release,
+ .fasync = rtc_fasync,
+};
+
+static struct miscdevice rtc_dev=
+{
+ .minor = RTC_MINOR,
+ .name = "rtc",
+ .fops = &rtc_fops,
+};
+
+static int __init rtc_init(void)
+{
+ if (!request_region(UPD4990A_IO, 1, "rtc")) {
+ printk(KERN_ERR "upd4990a: could not acquire I/O port %#x\n",
+ UPD4990A_IO);
+ return -EBUSY;
+ }
+
+#if 0
+ printk(KERN_INFO "\xB6\xDA\xDD\xC0\xDE \xC4\xDE\xB9\xB2 Driver\n"); /* Calender Clock Driver */
+#else
+ printk(KERN_INFO
+ "Real Time Clock driver for NEC PC-9800 v" RTC98_VERSION "\n");
+#endif
+ misc_register(&rtc_dev);
+ create_proc_read_entry("driver/rtc", 0, NULL, rtc_read_proc, NULL);
+
+ init_timer(&rtc_uie_timer);
+ rtc_uie_timer.function = rtc_uie_intr;
+
+ return 0;
+}
+
+module_init (rtc_init);
+
+static void __exit rtc_exit(void)
+{
+ del_timer(&rtc_uie_timer);
+ release_region(UPD4990A_IO, 1);
+ remove_proc_entry("driver/rtc", NULL);
+ misc_deregister(&rtc_dev);
+}
+
+module_exit (rtc_exit);
+
+/*
+ * Info exported via "/proc/driver/rtc".
+ */
+
+static inline int rtc_get_status(char *buf)
+{
+ char *p;
+ unsigned int year;
+ struct upd4990a_raw_data data;
+
+ p = buf;
+
+ upd4990a_get_time(&data, 0);
+
+ /*
+ * There is no way to tell if the luser has the RTC set for local
+ * time or for Universal Standard Time (GMT). Probably local though.
+ */
+ if ((year = BCD2BIN(data.year) + 1900) < 1995)
+ year += 100;
+ p += sprintf(p,
+ "rtc_time\t: %02d:%02d:%02d\n"
+ "rtc_date\t: %04d-%02d-%02d\n",
+ BCD2BIN(data.hour), BCD2BIN(data.min),
+ BCD2BIN(data.sec),
+ year, data.mon, BCD2BIN(data.mday));
+
+ return p - buf;
+}
+
+static int rtc_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len = rtc_get_status(page);
+
+ if (len <= off + count)
+ *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+ return len;
+}
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
\
 
 \ /
  Last update: 2005-03-22 13:34    [W:0.056 / U:0.696 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site