📄 s3c2410-rtc.c
字号:
/* * Real Time Clock interface for Samsung S3C2410 MPU * * Based on linux/drivers/char/s3c2400-rtc.c * * Copyright (C) 2003 MIZI Research, Inc. * * Author: Chan Gyun Jeong <cgjeong@mizi.com> * $Id: s3c2410-rtc.c,v 1.1.2.1 2003/01/13 14:52:07 tolkien Exp $ * */#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/miscdevice.h>#include <linux/proc_fs.h>#include <linux/poll.h>#include <linux/rtc.h>#include <asm/hardware.h>#include <asm/uaccess.h>#include <asm/irq.h>#define DRIVER_VERSION "0.1"#ifndef BCD_TO_BIN#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)#endif#ifndef BIN_TO_BCD#define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10)#endif#define RTC_LEAP_YEAR 2000/* Those are the bits from a classic RTC we want to mimic */#ifndef RTC_IRQF#define RTC_IRQF 0x80 /* any of the following 3 is active */#endif#ifndef RTC_PF#define RTC_PF 0x40#endif#ifndef RTC_AF#define RTC_AF 0x20#endif#ifndef RTC_UF#define RTC_UF 0x10#endifstatic unsigned long rtc_status;static unsigned long rtc_irq_data;static struct fasync_struct *rtc_async_queue;static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);extern spinlock_t rtc_lock;static void get_rtc_time(int alm, struct rtc_time *rtc_tm){ spin_lock_irq(&rtc_lock); if (alm == 1) { rtc_tm->tm_year = (unsigned char)ALMYEAR & Msk_RTCYEAR; rtc_tm->tm_mon = (unsigned char)ALMMON & Msk_RTCMON; rtc_tm->tm_mday = (unsigned char)ALMDAY & Msk_RTCDAY; rtc_tm->tm_hour = (unsigned char)ALMHOUR & Msk_RTCHOUR; rtc_tm->tm_min = (unsigned char)ALMMIN & Msk_RTCMIN; rtc_tm->tm_sec = (unsigned char)ALMSEC & Msk_RTCSEC; } else { read_rtc_bcd_time: rtc_tm->tm_year = (unsigned char)BCDYEAR & Msk_RTCYEAR; rtc_tm->tm_mon = (unsigned char)BCDMON & Msk_RTCMON; rtc_tm->tm_mday = (unsigned char)BCDDAY & Msk_RTCDAY; rtc_tm->tm_hour = (unsigned char)BCDHOUR & Msk_RTCHOUR; rtc_tm->tm_min = (unsigned char)BCDMIN & Msk_RTCMIN; rtc_tm->tm_sec = (unsigned char)BCDSEC & Msk_RTCSEC; if (rtc_tm->tm_sec == 0) { /* Re-read all BCD registers in case of BCDSEC is 0. See RTC section at the manual for more info. */ goto read_rtc_bcd_time; } } spin_unlock_irq(&rtc_lock); BCD_TO_BIN(rtc_tm->tm_year); BCD_TO_BIN(rtc_tm->tm_mon); BCD_TO_BIN(rtc_tm->tm_mday); BCD_TO_BIN(rtc_tm->tm_hour); BCD_TO_BIN(rtc_tm->tm_min); BCD_TO_BIN(rtc_tm->tm_sec); /* The epoch of tm_year is 1900 */ rtc_tm->tm_year += RTC_LEAP_YEAR - 1900; /* tm_mon starts at 0, but rtc month starts at 1 */ rtc_tm->tm_mon--;}static int set_rtc_time(int alm, struct rtc_time *rtc_tm){ unsigned char year, mon, day, hour, min, sec; if (rtc_tm->tm_year < (RTC_LEAP_YEAR - 1900)) { /* The year must be higher or equal than 2000 */ return -EINVAL; } year = (rtc_tm->tm_year + 1900) - RTC_LEAP_YEAR; mon = rtc_tm->tm_mon + 1; /* tm_mon starts at zero */ day = rtc_tm->tm_mday; hour = rtc_tm->tm_hour; min = rtc_tm->tm_min; sec = rtc_tm->tm_sec; BIN_TO_BCD(sec); BIN_TO_BCD(min); BIN_TO_BCD(hour); BIN_TO_BCD(day); BIN_TO_BCD(mon); BIN_TO_BCD(year); spin_lock_irq(&rtc_lock); /* Enable RTC control */ RTCCON |= RTCCON_EN; if (alm) { ALMSEC = sec & Msk_RTCSEC; ALMMIN = min & Msk_RTCMIN; ALMHOUR = hour & Msk_RTCHOUR; ALMDAY = day & Msk_RTCDAY; ALMMON = mon & Msk_RTCMON; ALMYEAR = year & Msk_RTCYEAR; } else { BCDSEC = sec & Msk_RTCSEC; BCDMIN = min & Msk_RTCMIN; BCDHOUR = hour & Msk_RTCHOUR; BCDDAY = day & Msk_RTCDAY; BCDMON = mon & Msk_RTCMON; BCDYEAR = year & Msk_RTCYEAR; } /* Disable RTC control */ RTCCON &= ~RTCCON_EN; spin_unlock_irq(&rtc_lock); return 0;}static void rtc_alm_interrupt(int irq, void *dev_id, struct pt_regs *regs){ spin_lock (&rtc_lock); /* Update IRQ data & counter */ rtc_irq_data += 0x100; rtc_irq_data |= (RTC_AF|RTC_IRQF); spin_unlock (&rtc_lock); /* Now do the rest of the actions */ wake_up_interruptible(&rtc_wait); kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);}static int rtc_open(struct inode *inode, struct file *file){ MOD_INC_USE_COUNT; spin_lock_irq(&rtc_lock); if (rtc_status) { spin_unlock_irq(&rtc_lock); MOD_DEC_USE_COUNT; return -EBUSY; } rtc_status = 1; rtc_irq_data = 0; spin_unlock_irq(&rtc_lock); return 0;}static int rtc_release(struct inode *inode, struct file *file){ /* * Turn off all interrupts once the device is no longer * in use, and clear the data. */ spin_lock_irq(&rtc_lock);#ifndef CONFIG_MIZI /* Disable alarm interrupt */ RTCCON |= RTCCON_EN; RTCALM = RTCALM_DIS; RTCCON &= ~RTCCON_EN;#endif rtc_status = 0; rtc_irq_data = 0; spin_unlock_irq(&rtc_lock); MOD_DEC_USE_COUNT; return 0;}static int rtc_fasync (int fd, struct file *filp, int on){ return fasync_helper (fd, filp, on, &rtc_async_queue);}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;}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; if (count < sizeof(unsigned long)) return -EINVAL; add_wait_queue(&rtc_wait, &wait); set_current_state(TASK_INTERRUPTIBLE); while (1) { 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(); } 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 tm; switch (cmd) { /* Alarm interrupt on/off */ case RTC_AIE_ON: spin_lock_irq (&rtc_lock); RTCCON |= RTCCON_EN; RTCALM = RTCALM_EN; RTCCON &= ~RTCCON_EN; spin_unlock_irq (&rtc_lock); return 0; case RTC_AIE_OFF: spin_lock_irq (&rtc_lock); RTCCON |= RTCCON_EN; RTCALM = RTCALM_DIS; RTCCON &= ~RTCCON_EN; spin_unlock_irq (&rtc_lock); return 0; /* Update interrupt on/off */ case RTC_UIE_ON: case RTC_UIE_OFF: /* Periodic interrupt on/off */ case RTC_PIE_ON: case RTC_PIE_OFF: case RTC_WIE_ON: case RTC_WIE_OFF: /* Periodic interrupt freq get/set */ case RTC_IRQP_READ: case RTC_IRQP_SET: /* Epoch get/set */ case RTC_EPOCH_READ: case RTC_EPOCH_SET: case RTC_WKALM_SET: case RTC_WKALM_RD: /* Not supported */ return -EINVAL; /* Alarm get/set */ case RTC_ALM_READ: get_rtc_time(1, &tm); return copy_to_user((void *)arg, &tm, sizeof(tm)) ? -EFAULT : 0; case RTC_ALM_SET: if (copy_from_user(&tm, (struct rtc_time*) arg, sizeof(tm))) { return -EFAULT; } return set_rtc_time(1, &tm); /* Time get/set */ case RTC_RD_TIME: get_rtc_time(0, &tm); return copy_to_user((void *)arg, &tm, sizeof(tm)) ? -EFAULT : 0; case RTC_SET_TIME: if (copy_from_user(&tm, (struct rtc_time*) arg, sizeof(tm))) { return -EFAULT; } return set_rtc_time(0, &tm); default: return -EINVAL; }}#ifdef CONFIG_PROC_FSstatic int rtc_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data){ char *p = page; int len; struct rtc_time tm; get_rtc_time(0, &tm); p += sprintf(p, "rtc_time\t: %02d:%02d:%02d\n" "rtc_date\t: %04d-%02d-%02d\n" "rtc_epoch\t: %04d\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, RTC_LEAP_YEAR); get_rtc_time(1, &tm); p += sprintf(p, "alarm_time\t: %02d:%02d:%02d\n" "alarm_date\t: %04d-%02d-%02d\n", tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); p += sprintf(p, "alarm_IRQ\t: %s\n", (RTCALM & RTCALM_EN) ? "yes" : "no" ); len = (p - page) - off; if (len < 0) len = 0; *eof = (len <= count) ? 1 : 0; *start = page + off; return len;}#endifstatic struct file_operations rtc_fops = { owner: THIS_MODULE, llseek: no_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){ int ret; misc_register(&rtc_dev);#ifdef CONFIG_PROC_FS create_proc_read_entry("driver/rtc", 0, 0, rtc_read_proc, NULL);#endif ret = request_irq(IRQ_RTC, rtc_alm_interrupt, SA_INTERRUPT, "RTC Alarm", NULL); if (ret) { printk("s3c2410-rtc: failed to register IRQ_RTC(%d)\n", IRQ_RTC); goto IRQ_RTC_failed; } printk("S3C2410 Real Time Clock Driver v" DRIVER_VERSION "\n"); return 0;IRQ_RTC_failed:#ifdef CONFIG_PROC_FS remove_proc_entry("driver/rtc", NULL);#endif misc_deregister(&rtc_dev); return ret;}static void __exit rtc_exit(void){ free_irq(IRQ_RTC, NULL);#ifdef CONFIG_PROC_FS remove_proc_entry("driver/rtc", NULL);#endif misc_deregister(&rtc_dev);}module_init(rtc_init);module_exit(rtc_exit);MODULE_AUTHOR("Chan Gyun Jeong <cgjeong@mizi.com>");MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Real Time Clock interface for Samsung S3C2410 MPU");EXPORT_NO_SYMBOLS;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -