mirror of https://github.com/OpenIPC/firmware.git
600 lines
19 KiB
Diff
600 lines
19 KiB
Diff
diff -drupN a/drivers/rtc/rtc-ingenic.c b/drivers/rtc/rtc-ingenic.c
|
|
--- a/drivers/rtc/rtc-ingenic.c 1970-01-01 03:00:00.000000000 +0300
|
|
+++ b/drivers/rtc/rtc-ingenic.c 2022-06-09 05:02:33.000000000 +0300
|
|
@@ -0,0 +1,595 @@
|
|
+/*
|
|
+ * Copyright (C) 2015 Ingenic Semiconductor Co., Ltd.
|
|
+ * Author: cli <chen.li@ingenic.com>
|
|
+ *
|
|
+ * Real Time Clock interface for Ingenic's SOC, such as X1000,
|
|
+ * and so on. (kernel.4.4)
|
|
+ *
|
|
+ * Base on:rtc-generic: RTC driver using the generic RTC abstraction
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify it
|
|
+ * under the terms of the GNU General Public License as published by the
|
|
+ * Free Software Foundation; either version 2 of the License, or (at your
|
|
+ * option) any later version.
|
|
+ */
|
|
+
|
|
+/*#define DEBUG*/
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/rtc.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/reboot.h>
|
|
+#include <soc/xburst/reboot.h>
|
|
+
|
|
+#include "rtc-ingenic.h"
|
|
+
|
|
+#if defined(CONFIG_DEBUG_FS) && defined(DEBUG)
|
|
+#include <linux/slab.h>
|
|
+#include <linux/debugfs.h>
|
|
+#endif
|
|
+
|
|
+struct ingenic_rtc_device {
|
|
+ struct rtc_device *rtc;
|
|
+ struct device *dev;
|
|
+ struct mutex reg_mutex;
|
|
+ bool pmic_vailed; /*power manage ic is vailed*/
|
|
+ void __iomem *reg_base;
|
|
+ int irq;
|
|
+ uint32_t lprs_pwon_ms; /*long press power ms time on Wakeup pin*/
|
|
+ uint32_t hr_assert_ms; /*HIBERNATE Reset assert time n*125ms*/
|
|
+ struct clk *rtc_gate;
|
|
+ int rtc_clk_rate;
|
|
+ unsigned int hwrsr;
|
|
+ struct notifier_block restart_handler;
|
|
+#if defined(CONFIG_DEBUG_FS) && defined(DEBUG)
|
|
+ struct dentry *debugfs;
|
|
+#endif
|
|
+#ifdef CONFIG_SUSPEND_TEST
|
|
+ unsigned int sleep_count;
|
|
+ unsigned int os_alarm_time;
|
|
+ unsigned int save_rtccr;
|
|
+#endif
|
|
+};
|
|
+
|
|
+static struct ingenic_rtc_device *m_rtc;
|
|
+
|
|
+static bool ingenic_rtc_clk_disconnect = false;
|
|
+
|
|
+static int ingenic_rtc_read(struct ingenic_rtc_device* rtc, int reg)
|
|
+{
|
|
+ return readl(rtc->reg_base + reg);
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_write(struct ingenic_rtc_device* rtc, int reg, int val)
|
|
+{
|
|
+ int timeout = 1000000;
|
|
+
|
|
+ mutex_lock(&rtc->reg_mutex);
|
|
+
|
|
+ writel(WENR_WENPAT_WRITABLE, rtc->reg_base + RTC_WENR);
|
|
+
|
|
+ while (!(readl(rtc->reg_base + RTC_WENR) & WENR_WEN) &&
|
|
+ --timeout);
|
|
+ if (!timeout) {
|
|
+ dev_warn(rtc->dev, "wait rtc wenr timeout\n");
|
|
+ mutex_unlock(&rtc->reg_mutex);
|
|
+ return -EIO;
|
|
+ }
|
|
+ timeout = 1000000;
|
|
+ while(!(readl(rtc->reg_base + RTC_RTCCR) & RTCCR_WRDY) &&
|
|
+ --timeout);
|
|
+ if (!timeout) {
|
|
+ dev_warn(rtc->dev, "wait rtc write ready timeout 1 %x:%x (rtccr:%x)\n",
|
|
+ reg, val,
|
|
+ readl(rtc->reg_base + RTC_RTCCR));
|
|
+ mutex_unlock(&rtc->reg_mutex);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ writel(val, rtc->reg_base + reg);
|
|
+
|
|
+ timeout = 1000000;
|
|
+ while(!(readl(rtc->reg_base + RTC_RTCCR) & RTCCR_WRDY) &&
|
|
+ --timeout);
|
|
+ if (!timeout) {
|
|
+ dev_warn(rtc->dev, "wait rtc write ready timeout 2 %x:%x (rtccr:%x)\n",
|
|
+ reg, val,
|
|
+ readl(rtc->reg_base + RTC_RTCCR));
|
|
+ mutex_unlock(&rtc->reg_mutex);
|
|
+ return -EIO;
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&rtc->reg_mutex);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_prepare_enable(struct ingenic_rtc_device *rtc)
|
|
+{
|
|
+ if (ingenic_rtc_read(rtc, RTC_HSPR) != HSPR_RTCV) { /*rtc power on*/
|
|
+ unsigned int rtccr, tmp;
|
|
+ int ret;
|
|
+ dev_info(rtc->dev, "usb rtc clk, system power on first time\n");
|
|
+
|
|
+ /*Try use rtc clk*/
|
|
+ rtccr = RTCCR_RTCE;
|
|
+ //writel(rtccr, rtc->reg_base + RTC_RTCCR);
|
|
+ ingenic_rtc_clk_disconnect = false;
|
|
+ ret = ingenic_rtc_write(rtc, RTC_RTCGR, (rtc->rtc_clk_rate - 1) & RTCGR_NC1HZ_MASK);
|
|
+ if (!ret && (ingenic_rtc_read(rtc, RTC_RTCGR) & RTCGR_NC1HZ_MASK) ==
|
|
+ (rtc->rtc_clk_rate - 1))
|
|
+ goto rtc_power_on;
|
|
+
|
|
+ /*Try use ext clk / 512*/
|
|
+ dev_info(rtc->dev, "use (extern clk)/512\n");
|
|
+ rtccr = RTCCR_SELEXC | RTCCR_RTCE;
|
|
+ writel(rtccr, rtc->reg_base + RTC_RTCCR);
|
|
+ rtc->rtc_clk_rate = 24000000/512;
|
|
+ ingenic_rtc_clk_disconnect = true;
|
|
+ ret = ingenic_rtc_write(rtc, RTC_RTCGR, (rtc->rtc_clk_rate - 1) & RTCGR_NC1HZ_MASK);
|
|
+ if (ret || (ingenic_rtc_read(rtc, RTC_RTCGR) & RTCGR_NC1HZ_MASK) !=
|
|
+ (rtc->rtc_clk_rate - 1))
|
|
+ return -ENODEV;
|
|
+rtc_power_on:
|
|
+ ingenic_rtc_write(rtc, RTC_HWFCR, HWFCR_WAIT_TIME(rtc->lprs_pwon_ms, rtc->rtc_clk_rate));
|
|
+ ingenic_rtc_write(rtc, RTC_HRCR, HRCR_WAIT_TIME(rtc->hr_assert_ms, rtc->rtc_clk_rate));
|
|
+ ingenic_rtc_write(rtc, RTC_RTCSR, 0);
|
|
+ ingenic_rtc_write(rtc, RTC_RTCSAR, 0);
|
|
+ tmp = ingenic_rtc_read(rtc, RTC_WKUPPINCR);
|
|
+ tmp &= ~(WKUPPINCR_P_JUD_EN);
|
|
+ ingenic_rtc_write(rtc, RTC_WKUPPINCR, tmp);
|
|
+ ingenic_rtc_write(rtc, RTC_RTCCR, rtccr);
|
|
+ ingenic_rtc_write(rtc, RTC_HSPR, HSPR_RTCV);
|
|
+ } else {
|
|
+ int temp, temp_new;
|
|
+
|
|
+ temp = HWFCR_WAIT_TIME(rtc->lprs_pwon_ms, rtc->rtc_clk_rate);
|
|
+ if (temp != (ingenic_rtc_read(rtc, RTC_HWFCR) & HWFCR_MASK))
|
|
+ ingenic_rtc_write(rtc, RTC_HWFCR, temp);
|
|
+
|
|
+ temp = HRCR_WAIT_TIME(rtc->hr_assert_ms, rtc->rtc_clk_rate);
|
|
+ if (temp != (ingenic_rtc_read(rtc, RTC_HRCR) & HRCR_MASK))
|
|
+ ingenic_rtc_write(rtc, RTC_HRCR, temp);
|
|
+
|
|
+ temp = ingenic_rtc_read(rtc, RTC_WKUPPINCR);
|
|
+ temp_new = temp & (~WKUPPINCR_P_JUD_EN);
|
|
+ if (temp_new != temp)
|
|
+ ingenic_rtc_write(rtc, RTC_WKUPPINCR, temp_new);
|
|
+
|
|
+ rtc->hwrsr = ingenic_rtc_read(rtc, RTC_HWRSR);
|
|
+ }
|
|
+ ingenic_rtc_write(rtc, RTC_HWRSR, 0x0);
|
|
+ ingenic_rtc_write(rtc, RTC_RTCGR, RTCGR_LOCK | ingenic_rtc_read(rtc, RTC_RTCGR));
|
|
+ ingenic_rtc_write(rtc, RTC_HWCR, HWCR_EALM | EPDET_ENABLE);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static irqreturn_t ingenic_rtc_interrupt_thread_handler(int irq, void *dev_id)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = dev_id;
|
|
+ unsigned int rtccr;
|
|
+
|
|
+ mutex_lock(&rtc->rtc->ops_lock);
|
|
+ rtccr = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+ rtccr &= ~(RTCCR_1HZ | RTCCR_AF);
|
|
+ ingenic_rtc_write(rtc, RTC_RTCCR, rtccr);
|
|
+ mutex_unlock(&rtc->rtc->ops_lock);
|
|
+
|
|
+ rtc_update_irq(rtc->rtc, 0/*uncare*/, 0/*uncare*/);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_get_time(struct device *dev, struct rtc_time *tm)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = dev_get_drvdata(dev);
|
|
+ uint32_t secs, secs2;
|
|
+ int timeout = 10;
|
|
+
|
|
+ /* If the seconds register is read while it is updated, it can contain a
|
|
+ * bogus value. This can be avoided by making sure that two consecutive
|
|
+ * reads have the same value.
|
|
+ */
|
|
+ secs = readl(rtc->reg_base + RTC_RTCSR);
|
|
+ secs = readl(rtc->reg_base + RTC_RTCSR);
|
|
+
|
|
+ while (secs != secs2 && --timeout) {
|
|
+ secs = secs2;
|
|
+ secs2 = readl(rtc->reg_base + RTC_RTCSR);
|
|
+ }
|
|
+
|
|
+ if (timeout == 0)
|
|
+ return -EIO;
|
|
+
|
|
+ rtc_time_to_tm(secs, tm);
|
|
+
|
|
+ return rtc_valid_tm(tm);
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_set_mmss(struct device *dev, unsigned long secs)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = dev_get_drvdata(dev);
|
|
+ return ingenic_rtc_write(rtc, RTC_RTCSR, secs);
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = dev_get_drvdata(dev);
|
|
+ unsigned int rtccr_new, rtccr;
|
|
+
|
|
+ rtccr = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+ /*WARN_ON(!(enabled && (rtccr & (RTCCR_1HZIE|RTCCR_AIE))));*/
|
|
+
|
|
+ if (!enabled) {
|
|
+ rtccr_new = rtccr;
|
|
+ if (!rtc->rtc->uie_rtctimer.enabled)
|
|
+ rtccr_new &= ~(RTCCR_1HZIE);
|
|
+ rtccr_new &= ~(RTCCR_AIE | RTCCR_AE);
|
|
+ if (rtccr_new != rtccr)
|
|
+ ingenic_rtc_write(rtc, RTC_RTCCR, rtccr_new);
|
|
+ }
|
|
+ pr_debug("=====> %s %d enabled %d, %x\n", __func__, __LINE__,
|
|
+ enabled,
|
|
+ ingenic_rtc_read(rtc, RTC_RTCCR));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_read_alarm(struct device * dev, struct rtc_wkalrm *alrm)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = dev_get_drvdata(dev);
|
|
+ uint32_t secs;
|
|
+ uint32_t ctrl;
|
|
+
|
|
+ secs = ingenic_rtc_read(rtc, RTC_RTCSAR);
|
|
+ ctrl = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+
|
|
+ alrm->enabled = !!(ctrl & RTCCR_AIE);
|
|
+ alrm->pending = !!(ctrl & RTCCR_AF);
|
|
+
|
|
+ rtc_time_to_tm(secs, &alrm->time);
|
|
+
|
|
+ return rtc_valid_tm(&alrm->time);
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = dev_get_drvdata(dev);
|
|
+ struct timerqueue_node *next = timerqueue_getnext(&rtc->rtc->timerqueue);
|
|
+ struct rtc_timer *timer;
|
|
+ unsigned long secs;
|
|
+ unsigned int rtccr_new, rtccr;
|
|
+ int ret = 0;
|
|
+
|
|
+ BUG_ON(!next);
|
|
+ timer = container_of(next, struct rtc_timer, node);
|
|
+ rtc_tm_to_time(&alrm->time, &secs);
|
|
+
|
|
+ if (&rtc->rtc->uie_rtctimer == timer) {
|
|
+ /*1HZ peroid interrupt*/
|
|
+ rtccr = rtccr_new = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+ rtccr_new &= ~(RTCCR_AIE | RTCCR_AE);
|
|
+ rtccr_new |= RTCCR_1HZIE;
|
|
+ if (rtccr_new != rtccr)
|
|
+ ret = ingenic_rtc_write(rtc, RTC_RTCCR, rtccr_new);
|
|
+ } else {
|
|
+ /*alarm interrupt*/
|
|
+ ret = ingenic_rtc_write(rtc, RTC_RTCSAR, secs);
|
|
+ if (!ret) {
|
|
+ rtccr = rtccr_new = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+ rtccr_new &= ~(RTCCR_1HZIE);
|
|
+ rtccr_new |= RTCCR_AIE | RTCCR_AE;
|
|
+ if (rtccr_new != rtccr) {
|
|
+ ret = ingenic_rtc_write(rtc, RTC_RTCCR, rtccr_new);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ pr_debug("=====> %s %d %x:%x:%x\n", __func__, __LINE__,
|
|
+ ingenic_rtc_read(rtc, RTC_RTCSR),
|
|
+ ingenic_rtc_read(rtc, RTC_RTCSAR),
|
|
+ ingenic_rtc_read(rtc, RTC_RTCCR));
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_rtc_proc(struct device *dev, struct seq_file *seq)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = dev_get_drvdata(dev);
|
|
+
|
|
+ seq_printf(seq, "RTC regulator\t: 0x%08x\n",
|
|
+ ingenic_rtc_read(rtc, RTC_RTCGR));
|
|
+ seq_printf(seq, "update_IRQ\t: %s\n",
|
|
+ (ingenic_rtc_read(rtc, RTC_RTCCR) & RTCCR_1HZIE) ? "yes" : "no");
|
|
+
|
|
+ if (rtc->hwrsr & HWRSR_APD)
|
|
+ seq_printf(seq, "Accident power down\n");
|
|
+ if (rtc->hwrsr & HWRSR_HR)
|
|
+ seq_printf(seq, "Hibernate Reset: \n");
|
|
+ if (rtc->hwrsr & HWRSR_PIN)
|
|
+ seq_printf(seq, "\tWakeup Pin wakeup system\n");
|
|
+ if (rtc->hwrsr & HWRSR_ALM)
|
|
+ seq_printf(seq, "\tAlarm wakeup system\n");
|
|
+ if (rtc->hwrsr & HWRSR_PPR)
|
|
+ seq_printf(seq, "PAD PIN Reset\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct rtc_class_ops ingenic_rtc_ops = {
|
|
+ .read_time = ingenic_rtc_get_time,
|
|
+ .set_mmss = ingenic_rtc_set_mmss,
|
|
+ .alarm_irq_enable = ingenic_rtc_alarm_irq_enable,
|
|
+ .read_alarm = ingenic_rtc_read_alarm,
|
|
+ .set_alarm = ingenic_rtc_set_alarm,
|
|
+ .proc = ingenic_rtc_rtc_proc,
|
|
+};
|
|
+
|
|
+#define INGENIC_RTC_DEFUALT_PWON_PRESS_MS (2000)
|
|
+#define INGENIC_RTC_DEFUALT_HIBERNATE_RESET_ASSERT_MS (60) /*copy from old rtc driver*/
|
|
+
|
|
+static void ingenic_rtc_hibernate(void)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = m_rtc;
|
|
+
|
|
+ mutex_lock(&rtc->rtc->ops_lock);
|
|
+ ingenic_rtc_write(rtc, RTC_HCR, HCR_PD);
|
|
+ mdelay(2000);
|
|
+ mutex_unlock(&rtc->rtc->ops_lock);
|
|
+ pr_err("RTC hibernate has been run, but extern power not down RTC_HCR(%x)\n",
|
|
+ ingenic_rtc_read(rtc, RTC_HCR));
|
|
+ while(1);
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_reset_handler(struct notifier_block *this, unsigned long mode,
|
|
+ void *cmd)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = m_rtc;
|
|
+ uint32_t rtc_rtcsr,rtc_rtccr;
|
|
+
|
|
+ /*
|
|
+ * Use setup params "reboot=h" (mode == REBOOT_HARD) ,
|
|
+ * enable the hibernate reset function
|
|
+ */
|
|
+ if (mode != REBOOT_HARD || (cmd && (!strcmp(cmd, REBOOT_CMD_RECOVERY) ||
|
|
+ !strcmp(cmd, REBOOT_CMD_SOFTBURN)))) {
|
|
+ pr_debug("%s %d mode %lu cmd %s\n", __func__, __LINE__, mode, cmd ? (char*)cmd : "null");
|
|
+ return NOTIFY_DONE;
|
|
+ }
|
|
+ pr_info("hibernate reset");
|
|
+ mutex_lock(&rtc->rtc->ops_lock);
|
|
+ rtc_rtcsr = ingenic_rtc_read(rtc, RTC_RTCSR);
|
|
+ ingenic_rtc_write(rtc, RTC_RTCSAR, rtc_rtcsr + 5); /*5s delay*/
|
|
+ rtc_rtccr = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+ rtc_rtccr &= ~RTCCR_AF;
|
|
+ rtc_rtccr |= RTCCR_AIE | RTCCR_AE;
|
|
+ ingenic_rtc_write(rtc, RTC_RTCCR, rtc_rtccr);
|
|
+ ingenic_rtc_write(rtc, RTC_HWRSR, 0x0);
|
|
+ ingenic_rtc_write(rtc, RTC_HWCR, HWCR_EALM|EPDET_ENABLE);
|
|
+ ingenic_rtc_write(rtc, RTC_HCR, HCR_PD);
|
|
+ while(1) {
|
|
+ mdelay(200);
|
|
+ printk("%s:We should NOT come here.%08x\n",
|
|
+ __func__, ingenic_rtc_read(rtc, RTC_HCR));
|
|
+ }
|
|
+ mutex_unlock(&rtc->rtc->ops_lock);
|
|
+ return NOTIFY_STOP;
|
|
+}
|
|
+
|
|
+#if defined(CONFIG_DEBUG_FS) && defined(DEBUG)
|
|
+static ssize_t ingenic_rtc_show_regs(struct file *file, char __user *user_buf,
|
|
+ size_t count, loff_t *ppos)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = file->private_data;
|
|
+ char *buf;
|
|
+ int len = 0;
|
|
+ ssize_t ret;
|
|
+
|
|
+#define REGS_BUFSIZE 1024
|
|
+ buf = kzalloc(REGS_BUFSIZE, GFP_KERNEL);
|
|
+ if (!buf)
|
|
+ return 0;
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "rtc register: %p\n", rtc->reg_base);
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_RTCCR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_RTCCR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_RTCSR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_RTCSR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_RTCSAR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_RTCSAR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_RTCGR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_RTCGR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_HCR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_HCR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_HWFCR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_HWFCR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_HRCR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_HRCR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_HWCR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_HWCR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_HWRSR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_HWRSR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_HSPR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_HSPR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_WENR : 0x%08x\n", ingenic_rtc_read(rtc, RTC_WENR));
|
|
+ len += snprintf(buf + len, REGS_BUFSIZE - len, "RTC_WKUPPINCR: 0x%08x\n", ingenic_rtc_read(rtc, RTC_WKUPPINCR));
|
|
+ ret = simple_read_from_buffer(user_buf, count, ppos, buf, len);
|
|
+ kfree(buf);
|
|
+#undef REGS_BUFSIZE
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static struct file_operations ingenic_rtc_reg_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = simple_open,
|
|
+ .read = ingenic_rtc_show_regs,
|
|
+ .llseek = default_llseek,
|
|
+};
|
|
+#endif
|
|
+
|
|
+static int __init ingenic_rtc_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct ingenic_rtc_device *ingenic_rtc;
|
|
+ struct resource *res;
|
|
+ struct clk *rtc_clk;
|
|
+ struct clk *rtc_gate;
|
|
+ int ret;
|
|
+
|
|
+ ingenic_rtc = devm_kzalloc(&pdev->dev, sizeof(struct ingenic_rtc_device), GFP_KERNEL);
|
|
+ if (!ingenic_rtc)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ingenic_rtc->pmic_vailed = of_property_read_bool(pdev->dev.of_node,
|
|
+ "system-power-controller");
|
|
+ if (ingenic_rtc->pmic_vailed) {
|
|
+ ret = of_property_read_u32(pdev->dev.of_node,
|
|
+ "power-on-press-ms",
|
|
+ &ingenic_rtc->lprs_pwon_ms);
|
|
+ if (ret)
|
|
+ ingenic_rtc->lprs_pwon_ms = INGENIC_RTC_DEFUALT_PWON_PRESS_MS;
|
|
+ ingenic_rtc->hr_assert_ms = INGENIC_RTC_DEFUALT_HIBERNATE_RESET_ASSERT_MS;
|
|
+ if (!pm_power_off) {
|
|
+ m_rtc = ingenic_rtc;
|
|
+ pm_power_off = ingenic_rtc_hibernate;
|
|
+ }
|
|
+ }
|
|
+ ingenic_rtc->dev = &pdev->dev;
|
|
+ mutex_init(&ingenic_rtc->reg_mutex);
|
|
+
|
|
+ rtc_gate = clk_get(&pdev->dev, "gate_rtc");
|
|
+ if (IS_ERR(rtc_gate))
|
|
+ return PTR_ERR(rtc_gate);
|
|
+ clk_prepare_enable(rtc_gate);
|
|
+ clk_put(rtc_gate);
|
|
+
|
|
+ rtc_clk = clk_get(&pdev->dev, "rtc");
|
|
+ if (IS_ERR_OR_NULL(rtc_clk) ||
|
|
+ (ingenic_rtc->rtc_clk_rate = clk_get_rate(rtc_clk)) <= 0) {
|
|
+ dev_warn(&pdev->dev, "Impossible: can not find fin_rtc(use 32768)\n");
|
|
+ ingenic_rtc->rtc_clk_rate = 32768;
|
|
+ }
|
|
+ if (!IS_ERR_OR_NULL(rtc_clk))
|
|
+ clk_put(rtc_clk);
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ if (!res)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ingenic_rtc->reg_base = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(ingenic_rtc->reg_base))
|
|
+ return PTR_ERR(ingenic_rtc->reg_base);
|
|
+
|
|
+ ingenic_rtc->irq = platform_get_irq(pdev, 0);
|
|
+ if (ingenic_rtc->irq < 0)
|
|
+ return ingenic_rtc->irq;
|
|
+
|
|
+ ret = ingenic_rtc_prepare_enable(ingenic_rtc);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ device_init_wakeup(&pdev->dev, true);
|
|
+ platform_set_drvdata(pdev, ingenic_rtc);
|
|
+
|
|
+ ingenic_rtc->rtc = devm_rtc_device_register(&pdev->dev, "rtc-ingenic",
|
|
+ &ingenic_rtc_ops, THIS_MODULE);
|
|
+ if (IS_ERR(ingenic_rtc->rtc))
|
|
+ return PTR_ERR(ingenic_rtc->rtc);
|
|
+
|
|
+ ret = devm_request_threaded_irq(&pdev->dev, ingenic_rtc->irq, NULL,
|
|
+ ingenic_rtc_interrupt_thread_handler,
|
|
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
|
+ "rtc 1HZ and alarm",
|
|
+ (void *)ingenic_rtc);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ingenic_rtc->restart_handler.notifier_call = ingenic_rtc_reset_handler;
|
|
+ ingenic_rtc->restart_handler.priority = RTC_HIBERNATE_RESET_PROR;
|
|
+ ret = register_restart_handler(&ingenic_rtc->restart_handler);
|
|
+ if (ret)
|
|
+ dev_warn(&pdev->dev,
|
|
+ "cannot register rtc restart\
|
|
+ handler (err=%d)\n", ret);
|
|
+
|
|
+
|
|
+#if defined(CONFIG_DEBUG_FS) && defined(DEBUG)
|
|
+ ingenic_rtc->debugfs = debugfs_create_file("rtc_reg", 0x444, NULL,
|
|
+ ingenic_rtc, &ingenic_rtc_reg_ops);
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void __exit ingenic_rtc_remove(struct platform_device *pdev)
|
|
+{
|
|
+#if defined(CONFIG_DEBUG_FS) && defined(DEBUG)
|
|
+ struct ingenic_rtc_device *ingenic_rtc = platform_get_drvdata(pdev);
|
|
+ if (ingenic_rtc->debugfs)
|
|
+ debugfs_remove(ingenic_rtc->debugfs);
|
|
+#endif
|
|
+}
|
|
+
|
|
+static const struct of_device_id ingenic_rtc_of_match[] = {
|
|
+ { .compatible = "ingenic,rtc", .data = NULL, },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, ingenic_rtc_of_match);
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int ingenic_rtc_suspend(struct platform_device *pdev, pm_message_t state)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = platform_get_drvdata(pdev);
|
|
+#ifdef CONFIG_SUSPEND_TEST
|
|
+ unsigned int val;
|
|
+ unsigned int test_alarm_time, sr_time;
|
|
+
|
|
+ val = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+ if(val & RTCCR_AE) {
|
|
+ rtc->save_rtccr = val;
|
|
+ rtc->os_alarm_time = ingenic_rtc_read(rtc, RTC_RTCSAR);
|
|
+ }
|
|
+ val |= RTCCR_AIE | RTCCR_AE;
|
|
+ ingenic_rtc_write(rtc, RTC_RTCCR, val);
|
|
+
|
|
+ sr_time = ingenic_rtc_read(rtc, RTC_RTCSR);
|
|
+ test_alarm_time = sr_time + CONFIG_SUSPEND_ALARM_TIME;
|
|
+ if(rtc->os_alarm_time && rtc->os_alarm_time > sr_time \
|
|
+ && rtc->os_alarm_time < test_alarm_time)
|
|
+ test_alarm_time = rtc->os_alarm_time;
|
|
+ ingenic_rtc_write(rtc, RTC_RTCSAR, test_alarm_time);
|
|
+
|
|
+ printk("-------suspend count = %d\n", rtc->sleep_count++);
|
|
+#endif
|
|
+ if (device_may_wakeup(&pdev->dev))
|
|
+ enable_irq_wake(rtc->irq);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ingenic_rtc_resume(struct platform_device *pdev)
|
|
+{
|
|
+ struct ingenic_rtc_device *rtc = platform_get_drvdata(pdev);
|
|
+#ifdef CONFIG_SUSPEND_TEST
|
|
+ if(rtc->save_rtccr & RTCCR_AE) {
|
|
+ ingenic_rtc_write(rtc, RTC_RTCSAR, rtc->os_alarm_time);
|
|
+ ingenic_rtc_write(rtc, RTC_RTCCR, rtc->save_rtccr);
|
|
+ rtc->os_alarm_time = 0;
|
|
+ rtc->save_rtccr = 0;
|
|
+ } else {
|
|
+ unsigned int val;
|
|
+ val = ingenic_rtc_read(rtc, RTC_RTCCR);
|
|
+ val &= ~ (RTCCR_AF |RTCCR_AIE | RTCCR_AE);
|
|
+ ingenic_rtc_write(rtc, RTC_RTCCR, val);
|
|
+ }
|
|
+#endif
|
|
+ if (device_may_wakeup(&pdev->dev))
|
|
+ disable_irq_wake(rtc->irq);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#else
|
|
+#define ingenic_rtc_suspend NULL
|
|
+#define ingenic_rtc_resume NULL
|
|
+#endif
|
|
+
|
|
+static struct platform_driver ingenic_rtc_driver = {
|
|
+ .driver = {
|
|
+ .name = "rtc-ingenic",
|
|
+ .of_match_table = ingenic_rtc_of_match,
|
|
+ },
|
|
+ .remove = __exit_p(ingenic_rtc_remove),
|
|
+ .suspend = ingenic_rtc_suspend,
|
|
+ .resume = ingenic_rtc_resume,
|
|
+};
|
|
+module_platform_driver_probe(ingenic_rtc_driver, ingenic_rtc_probe);
|
|
+
|
|
+MODULE_AUTHOR("Cli <chen.li@ingenic.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("Ingenic RTC driver");
|
|
+MODULE_ALIAS("platform:rtc-ingenic");
|