add support for PC-9800 architecture (RTC)
PATCH 16/23 add support for PC-9800 architecture (RTC)
This is part 16/23 of patchset for add support NEC PC-9800 architecture, against 2.5.44.
Summary:
PC-9800 standard real time clock driver.(add new files)
diffstat:
drivers/char/upd4990a.c | 438 ++++++++++++++++++++++++++++++++++++++++++++ include/asm-i386/upd4990a.h | 58 +++++ include/linux/upd4990a.h | 140 ++++++++++++++ 3 files changed, 636 insertions(+)
patch:
drivers/char/upd4990a.c +438 -0
@@ -0,0 +1,438 @@ +/* +* 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/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> + +#define BCD_TO_BINARY (val) (((val) >> 4) * 10 + ((val) & 0xF)) +#define BINARY_TO_BCD (val) ((((val) / 10) << 4) | ((val) % 10)) + +/* +* 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 long long rtc_llseek (struct file *file,loff_t offset,int origin); + +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 long long rtc_llseek (struct file *file,loff_t offset,int origin) +{ +return -ESPIPE ; +} + +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 =BCD_TO_BINARY (raw.sec); + wtime.tm_min =BCD_TO_BINARY (raw.min); + wtime.tm_hour =BCD_TO_BINARY (raw.hour); + wtime.tm_mday =BCD_TO_BINARY (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 =BCD_TO_BINARY (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 =BINARY_TO_BCD (wtime.tm_sec); + raw.min =BINARY_TO_BCD (wtime.tm_min); + raw.hour =BINARY_TO_BCD (wtime.tm_hour); + raw.mday =BINARY_TO_BCD (wtime.tm_mday); + raw.mon = wtime.tm_mon +1 ; + raw.wday = wtime.tm_wday; + raw.year =BINARY_TO_BCD (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 , + llseek: rtc_llseek, + read: rtc_read, + poll: rtc_poll, + ioctl: rtc_ioctl, + open: rtc_open, + release: rtc_release, + fasync: rtc_fasync, +}; + +static struct miscdevice rtc_dev= +{ +RTC_MINOR , +"rtc" , + &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); + +#ifdef MODULE +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); +#endif + +EXPORT_NO_SYMBOLS ; + +/* +* 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 =BCD_TO_BINARY (data.year) +1900 ) <1995 ) + year +=100 ; + p +=sprintf (p, +"rtc_time\t: %02d:%02d:%02d\n" +"rtc_date\t: %04d-%02d-%02d\n" , +BCD_TO_BINARY (data.hour),BCD_TO_BINARY (data.min), +BCD_TO_BINARY (data.sec), + year, data.mon,BCD_TO_BINARY (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; +}
include/asm-i386/upd4990a.h +58 -0
58 lines not on this page · raw message
include/linux/upd4990a.h +140 -0
140 lines not on this page · raw message