summaryrefslogtreecommitdiffstats
path: root/arch/m68k/bvme6000/rtc.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /arch/m68k/bvme6000/rtc.c
parentInitial commit. (diff)
downloadlinux-upstream.tar.xz
linux-upstream.zip
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--arch/m68k/bvme6000/rtc.c175
1 files changed, 175 insertions, 0 deletions
diff --git a/arch/m68k/bvme6000/rtc.c b/arch/m68k/bvme6000/rtc.c
new file mode 100644
index 000000000..b43eeef09
--- /dev/null
+++ b/arch/m68k/bvme6000/rtc.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Real Time Clock interface for Linux on the BVME6000
+ *
+ * Based on the PC driver by Paul Gortmaker.
+ */
+
+#define RTC_VERSION "1.00"
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/capability.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/module.h>
+#include <linux/rtc.h> /* For struct rtc_time and ioctls, etc */
+#include <linux/bcd.h>
+#include <asm/bvme6000hw.h>
+
+#include <asm/io.h>
+#include <linux/uaccess.h>
+#include <asm/setup.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 unsigned char days_in_mo[] =
+{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static atomic_t rtc_status = ATOMIC_INIT(1);
+
+static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ volatile RtcPtr_t rtc = (RtcPtr_t)BVME_RTC_BASE;
+ unsigned char msr;
+ unsigned long flags;
+ struct rtc_time wtime;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case RTC_RD_TIME: /* Read the time/date from RTC */
+ {
+ local_irq_save(flags);
+ /* Ensure clock and real-time-mode-register are accessible */
+ msr = rtc->msr & 0xc0;
+ rtc->msr = 0x40;
+ memset(&wtime, 0, sizeof(struct rtc_time));
+ do {
+ wtime.tm_sec = bcd2bin(rtc->bcd_sec);
+ wtime.tm_min = bcd2bin(rtc->bcd_min);
+ wtime.tm_hour = bcd2bin(rtc->bcd_hr);
+ wtime.tm_mday = bcd2bin(rtc->bcd_dom);
+ wtime.tm_mon = bcd2bin(rtc->bcd_mth)-1;
+ wtime.tm_year = bcd2bin(rtc->bcd_year);
+ if (wtime.tm_year < 70)
+ wtime.tm_year += 100;
+ wtime.tm_wday = bcd2bin(rtc->bcd_dow)-1;
+ } while (wtime.tm_sec != bcd2bin(rtc->bcd_sec));
+ rtc->msr = msr;
+ local_irq_restore(flags);
+ return copy_to_user(argp, &wtime, sizeof wtime) ?
+ -EFAULT : 0;
+ }
+ case RTC_SET_TIME: /* Set the RTC */
+ {
+ struct rtc_time rtc_tm;
+ unsigned char mon, day, hrs, min, sec, leap_yr;
+ unsigned int yrs;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ if (copy_from_user(&rtc_tm, argp, sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ yrs = rtc_tm.tm_year;
+ if (yrs < 1900)
+ yrs += 1900;
+ mon = rtc_tm.tm_mon + 1; /* tm_mon starts at zero */
+ day = rtc_tm.tm_mday;
+ hrs = rtc_tm.tm_hour;
+ min = rtc_tm.tm_min;
+ sec = rtc_tm.tm_sec;
+
+ leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+ if ((mon > 12) || (mon < 1) || (day == 0))
+ return -EINVAL;
+
+ if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+ return -EINVAL;
+
+ if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+ return -EINVAL;
+
+ if (yrs >= 2070)
+ return -EINVAL;
+
+ local_irq_save(flags);
+ /* Ensure clock and real-time-mode-register are accessible */
+ msr = rtc->msr & 0xc0;
+ rtc->msr = 0x40;
+
+ rtc->t0cr_rtmr = yrs%4;
+ rtc->bcd_tenms = 0;
+ rtc->bcd_sec = bin2bcd(sec);
+ rtc->bcd_min = bin2bcd(min);
+ rtc->bcd_hr = bin2bcd(hrs);
+ rtc->bcd_dom = bin2bcd(day);
+ rtc->bcd_mth = bin2bcd(mon);
+ rtc->bcd_year = bin2bcd(yrs%100);
+ if (rtc_tm.tm_wday >= 0)
+ rtc->bcd_dow = bin2bcd(rtc_tm.tm_wday+1);
+ rtc->t0cr_rtmr = yrs%4 | 0x08;
+
+ rtc->msr = msr;
+ local_irq_restore(flags);
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ */
+static int rtc_open(struct inode *inode, struct file *file)
+{
+ if (!atomic_dec_and_test(&rtc_status)) {
+ atomic_inc(&rtc_status);
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+ atomic_inc(&rtc_status);
+ return 0;
+}
+
+/*
+ * The various file operations we support.
+ */
+
+static const struct file_operations rtc_fops = {
+ .unlocked_ioctl = rtc_ioctl,
+ .open = rtc_open,
+ .release = rtc_release,
+ .llseek = noop_llseek,
+};
+
+static struct miscdevice rtc_dev = {
+ .minor = RTC_MINOR,
+ .name = "rtc",
+ .fops = &rtc_fops
+};
+
+static int __init rtc_DP8570A_init(void)
+{
+ if (!MACH_IS_BVME6000)
+ return -ENODEV;
+
+ pr_info("DP8570A Real Time Clock Driver v%s\n", RTC_VERSION);
+ return misc_register(&rtc_dev);
+}
+module_init(rtc_DP8570A_init);