diff options
Diffstat (limited to 'drivers/rtc/rtc-sprd.c')
| -rw-r--r-- | drivers/rtc/rtc-sprd.c | 884 |
1 files changed, 884 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-sprd.c b/drivers/rtc/rtc-sprd.c new file mode 100644 index 00000000..de8d582a --- /dev/null +++ b/drivers/rtc/rtc-sprd.c @@ -0,0 +1,884 @@ +/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <asm/bitops.h>
+#include <linux/clkdev.h>
+#include <linux/regulator/consumer.h>
+#include <linux/clk.h>
+#include <linux/wakelock.h>
+#include <linux/of.h>
+#include <linux/spinlock.h>
+
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/arch_lock.h>
+#include <soc/sprd/adi.h>
+
+#ifdef CONFIG_64BIT
+#define RTC_BASE (SPRD_ADI_BASE + 0x8080)
+#else
+#define RTC_BASE ANA_RTC_BASE
+#endif
+
+#define ANA_RTC_SEC_CNT_VALUE (RTC_BASE + 0x00)
+#define ANA_RTC_MIN_CNT_VALUE (RTC_BASE + 0x04)
+#define ANA_RTC_HOUR_CNT_VALUE (RTC_BASE + 0x08)
+#define ANA_RTC_DAY_CNT_VALUE (RTC_BASE + 0x0C)
+
+#define ANA_RTC_SEC_CNT_UPD (RTC_BASE + 0x10)
+#define ANA_RTC_MIN_CNT_UPD (RTC_BASE + 0x14)
+#define ANA_RTC_HOUR_CNT_UPD (RTC_BASE + 0x18)
+#define ANA_RTC_DAY_CNT_UPD (RTC_BASE + 0x1C)
+
+#define ANA_RTC_SEC_ALM_UPD (RTC_BASE + 0x20)
+#define ANA_RTC_MIN_ALM_UPD (RTC_BASE + 0x24)
+#define ANA_RTC_HOUR_ALM_UPD (RTC_BASE + 0x28)
+#define ANA_RTC_DAY_ALM_UPD (RTC_BASE + 0x2C)
+
+#define ANA_RTC_INT_EN (RTC_BASE + 0x30)
+#define ANA_RTC_INT_RAW_STS (RTC_BASE + 0x34)
+#define ANA_RTC_INT_CLR (RTC_BASE + 0x38)
+#define ANA_RTC_INT_MASK_STS (RTC_BASE + 0x3C)
+
+#define ANA_RTC_SEC_ALM_VALUE (RTC_BASE + 0x40)
+#define ANA_RTC_MIN_ALM_VALUE (RTC_BASE + 0x44)
+#define ANA_RTC_HOUR_ALM_VALUE (RTC_BASE + 0x48)
+#define ANA_RTC_DAY_ALM_VALUE (RTC_BASE + 0x4C)
+
+#define ANA_RTC_SPG_VALUE (RTC_BASE + 0x50)
+#define ANA_RTC_SPG_UPD (RTC_BASE + 0x54)
+
+#define ANA_RTC_SEC_AUXALM_UPD (RTC_BASE + 0x60)
+#define ANA_RTC_MIN_AUXALM_UPD (RTC_BASE + 0x64)
+#define ANA_RTC_HOUR_AUXALM_UPD (RTC_BASE + 0x68)
+#define ANA_RTC_DAY_AUXALM_UPD (RTC_BASE + 0x6C)
+
+/* The corresponding bit of RTC_CTL register. */
+#define RTC_SEC_BIT BIT(0) /* Sec int enable */
+#define RTC_MIN_BIT BIT(1) /* Min int enable */
+#define RTC_HOUR_BIT BIT(2) /* Hour int enable */
+#define RTC_DAY_BIT BIT(3) /* Day int enable */
+#define RTC_ALARM_BIT BIT(4) /* Alarm int enable */
+#define RTCCTL_HOUR_FMT_SEL BIT(5) /* Hour format select */
+#define RTC_AUX_ALARM_BIT BIT(6) /* Auxilible alarm enable */
+#define RTC_SPG_BIT BIT(7) /* SPG int enable */
+#define RTC_SEC_ACK_BIT BIT(8) /* Sec ack int enable */
+#define RTC_MIN_ACK_BIT BIT(9) /* Min ack int enable */
+#define RTC_HOUR_ACK_BIT BIT(10) /* Hour ack int enable */
+#define RTC_DAY_ACK_BIT BIT(11) /* Day ack int enable */
+#define RTC_SEC_ALM_ACK_BIT BIT(12) /* Sec alm ack int enable */
+#define RTC_MIN_ALM_ACK_BIT BIT(13) /* Min alm ack int enable */
+#define RTC_HOUR_ALM_ACK_BIT BIT(14) /* Hour alm ack int enable */
+#define RTC_DAY_ALM_ACK_BIT BIT(15) /* Day alm ack int enable */
+
+#define RTC_UPD_TIME_MASK (RTC_SEC_ACK_BIT | RTC_MIN_ACK_BIT | RTC_HOUR_ACK_BIT | RTC_DAY_ACK_BIT)
+#define RTC_INT_ALL_MSK (0xFFFF&(~(BIT(5)|BIT(7))))
+#define RTC_INT_ALM_MSK (RTC_SEC_BIT | RTC_MIN_BIT | RTC_HOUR_BIT | RTC_DAY_BIT | RTC_ALARM_BIT | RTC_AUX_ALARM_BIT)
+#define RTC_ALM_TIME_MASK (RTC_SEC_ALM_ACK_BIT | RTC_MIN_ALM_ACK_BIT | RTC_HOUR_ALM_ACK_BIT | RTC_DAY_ALM_ACK_BIT)
+
+#define RTC_SEC_MASK (0x3F)
+#define RTC_MIN_MASK (0x3F)
+#define RTC_HOUR_MASK (0x1F)
+#define RTC_DAY_MASK (0xFFFF)
+
+#define SPRD_RTC_GET_MAX (10)
+#define SPRD_RTC_SET_MAX (150)
+#define SPRD_RTC_ALM_UNLOCK (0xa5)
+#define SPRD_RTC_ALM_LOCK (0xff & (~SPRD_RTC_ALM_UNLOCK))
+#define SPRD_RTC_ALMLOCK_MASK (0xff)
+
+#define SPRD_RTC_POWEROFF_ALARM (0x1 << 8)
+#define SPRD_RTC_POWERDOWN_RESET (0x1 << 9)
+
+/* check power down reg */
+#define RTC_POWERDOWN_VAL (0xA596)
+#define RTC_RST_CLEAN_VAL (0xFF)
+#define ANA_RTC_RST2 (ANA_CTL_GLB_BASE + 0x164)
+#define ANA_RTC_RST1 (ANA_CTL_GLB_BASE + 0x160)
+
+#define ANA_REG_INT_MASK_STATUS (ANA_CTL_INT_BASE + 0x0000)
+#define ANA_REG_INT_RAW_STATUS (ANA_CTL_INT_BASE + 0x0004)
+#define ANA_REG_INT_EN (ANA_CTL_INT_BASE + 0x0008)
+#define ANA_REG_INT_MASK_STATUS_SYNC (ANA_CTL_INT_BASE + 0x000c)
+
+struct sprd_rtc{
+ struct rtc_device *rtc;
+ unsigned int irq_no;
+ struct clk *clk;
+ struct regulator *regulator;
+};
+
+enum ALARM_TYPE {
+ SET_POWERON_ALARM = 0,
+ GET_POWERON_ALARM,
+ SET_WAKE_ALARM,
+ GET_WAKE_ALARM,
+ SET_POWEROFF_ALARM,
+ GET_POWEROFF_ALARM,
+};
+
+static struct wake_lock rtc_wake_lock;
+static struct wake_lock rtc_interrupt_wake_lock;
+static unsigned long secs_start_year_to_1970;
+
+static ssize_t sprd_show_caliberate(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+#define SPRD_CALIBERATE_ATTR_RO(_name) \
+{ \
+ .attr = { .name = #_name, .mode = S_IRUGO, }, \
+ .show = sprd_show_caliberate, \
+}
+
+static struct device_attribute sprd_caliberate[] = {
+ SPRD_CALIBERATE_ATTR_RO(default_time),
+};
+
+/* dump RTC registers for debug */
+void sprd_alarm_dump_rtc(void)
+{
+ unsigned int base[32];
+ int i;
+
+ for (i = 0; i < 32; i++)
+ if ((i != 22) && (i != 23))
+ base[i] = sci_adi_read((unsigned long)(RTC_BASE + i*4));
+
+ pr_info("\ndump rtc registers:\n");
+ pr_info("sec_cnt=%08x min_cnt=%08x hour_cnt=%08x day_cnt=%08x\n"
+ "sec_upd=%08x min_upd=%08x hour_upd=%08x day_upd=%08x\n",
+ base[0], base[1], base[2], base[3],
+ base[4], base[5], base[6], base[7]);
+
+ pr_info("sec_alm_val=%08x min_alm_val=%08x hour_alm_val=%08x day_alm_val=%08x\n"
+ "sec_alm_upd=%08x min_alm_upd=%08x hour_alm_upd=%08x day_alm_upd=%08x\n",
+ base[16], base[17], base[18], base[19],
+ base[8], base[9], base[10], base[11]);
+
+ pr_info("int_en=%08x int_raw_sts=%08x int_clr=%08x int_mask_sts=%08x\n"
+ "spg_value=%08x\tspg_upd=%08x\n",
+ base[12], base[13], base[14], base[15],
+ base[20], base[21]);
+
+ pr_info("sec_aux_upd=%08x min_aux_upd=%08x hour_aux_upd=%08x day_aux_upd=%08x\n"
+ "sec_cnt_raw=%08x min_cnt_raw=%08x hour_cnt_raw=%08x day_cnt_raw=%08x\n",
+ base[24], base[25], base[26], base[27],
+ base[28], base[29], base[30], base[31]);
+}
+
+void sprd_alarm_dump_misc(void)
+{
+ pr_info("\ndump misc registers:\n");
+ pr_info("ANA INT mask:0x%08x raw:0x%08x en:0x%08x\n",
+ sci_adi_read(ANA_REG_INT_MASK_STATUS),
+ sci_adi_read(ANA_REG_INT_RAW_STATUS),
+ sci_adi_read(ANA_REG_INT_EN));
+
+ pr_info("ANA_REG_GLB_ARM_MODULE_EN --- 0x%08x\n",
+ sci_adi_read(ANA_REG_GLB_ARM_MODULE_EN));
+ pr_info("ANA_REG_GLB_RTC_CLK_EN--- 0x%08x\n\n",
+ sci_adi_read(ANA_REG_GLB_RTC_CLK_EN));
+}
+
+static inline unsigned int get_sec(void)
+{
+ return sci_adi_read((unsigned long)ANA_RTC_SEC_CNT_VALUE) & RTC_SEC_MASK;
+}
+
+static inline unsigned int get_min(void)
+{
+ return sci_adi_read((unsigned long)ANA_RTC_MIN_CNT_VALUE) & RTC_MIN_MASK;
+}
+
+static inline unsigned int get_hour(void)
+{
+ return sci_adi_read((unsigned long)ANA_RTC_HOUR_CNT_VALUE) & RTC_HOUR_MASK;
+}
+
+static inline unsigned int get_day(void)
+{
+ return sci_adi_read((unsigned long)ANA_RTC_DAY_CNT_VALUE) & RTC_DAY_MASK;
+}
+
+static int clear_rtc_int(unsigned int mask)
+{
+ return sci_adi_raw_write(ANA_RTC_INT_CLR, mask);
+}
+
+static unsigned long sprd_rtc_get_sec(void)
+{
+ unsigned int sec, min, hour, day;
+ unsigned long second;
+
+ sec = get_sec();
+ min = get_min();
+ hour = get_hour();
+ day = get_day();
+
+ second = (((unsigned long)(day*24) + hour)*60 + min)*60 + sec;
+
+ return second;
+}
+
+static int sprd_rtc_set_sec(unsigned long secs)
+{
+ unsigned int sec, min, hour, day;
+ unsigned int int_rsts;
+ unsigned long temp;
+ int cnt = 0, ret = 0;
+
+ sec = secs % 60;
+ temp = (secs - sec)/60;
+ min = temp%60;
+ temp = (temp - min)/60;
+ hour = temp%24;
+ temp = (temp - hour)/24;
+ day = temp;
+
+ sci_adi_set((unsigned long)ANA_RTC_INT_CLR, RTC_UPD_TIME_MASK);
+ sci_adi_raw_write((unsigned long)ANA_RTC_SEC_CNT_UPD, sec);
+ sci_adi_raw_write((unsigned long)ANA_RTC_MIN_CNT_UPD, min);
+ sci_adi_raw_write((unsigned long)ANA_RTC_HOUR_CNT_UPD, hour);
+ sci_adi_raw_write((unsigned long)ANA_RTC_DAY_CNT_UPD, day);
+
+ /* wait till all update done */
+ do{
+ int_rsts = sci_adi_read((unsigned long)ANA_RTC_INT_RAW_STS) & RTC_UPD_TIME_MASK;
+
+ if(RTC_UPD_TIME_MASK == int_rsts)
+ break;
+
+ msleep(10);
+ }while(cnt++ < SPRD_RTC_SET_MAX);
+
+ if(cnt >= SPRD_RTC_SET_MAX){
+ printk(KERN_ERR "Set time values time out!\n");
+ ret = -EBUSY;
+ }
+
+ sci_adi_set((unsigned long)ANA_RTC_INT_CLR, RTC_UPD_TIME_MASK);
+ return ret;
+}
+
+static inline unsigned long sprd_rtc_get_alarm_sec(void)
+{
+ unsigned int sec, min, hour, day;
+ unsigned long alarm_sec = 0;
+
+ day = sci_adi_read((unsigned long)ANA_RTC_DAY_ALM_VALUE) & RTC_DAY_MASK;
+ hour = sci_adi_read((unsigned long)ANA_RTC_HOUR_ALM_VALUE) & RTC_HOUR_MASK;
+ min = sci_adi_read((unsigned long)ANA_RTC_MIN_ALM_VALUE) & RTC_MIN_MASK;
+ sec = sci_adi_read((unsigned long)ANA_RTC_SEC_ALM_VALUE) & RTC_SEC_MASK;
+
+ alarm_sec = (((unsigned long)(day*24) + hour)*60 + min)*60 + sec;
+ return alarm_sec;
+}
+
+static int sprd_rtc_set_alarm_sec(unsigned long secs)
+{
+ unsigned int sec, min, hour, day;
+ unsigned int timeout = 0;
+ unsigned long temp;
+ static bool rtc_alarm_in_update = false;
+
+ sec = secs % 60;
+ temp = (secs - sec)/60;
+ min = temp%60;
+ temp = (temp - min)/60;
+ hour = temp%24;
+ temp = (temp - hour)/24;
+ day = temp;
+
+ /* Optimize suspend process time */
+ if (rtc_alarm_in_update) {
+ while ((sci_adi_read((unsigned long)ANA_RTC_INT_RAW_STS) & RTC_ALM_TIME_MASK) !=
+ RTC_ALM_TIME_MASK)
+ {
+ msleep(10);
+ if (timeout++ > SPRD_RTC_SET_MAX) {
+ printk("RTC set alarm timeout!\n");
+ break;
+ }
+ }
+ }
+ sci_adi_set((unsigned long)ANA_RTC_INT_CLR, RTC_ALM_TIME_MASK);
+
+ sci_adi_raw_write((unsigned long)ANA_RTC_SEC_ALM_UPD, sec);
+ sci_adi_raw_write((unsigned long)ANA_RTC_MIN_ALM_UPD, min);
+ sci_adi_raw_write((unsigned long)ANA_RTC_HOUR_ALM_UPD, hour);
+ sci_adi_raw_write((unsigned long)ANA_RTC_DAY_ALM_UPD, day);
+
+ rtc_alarm_in_update = true;
+ return 0;
+}
+
+static int sprd_rtc_read_alarm(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ unsigned long secs = sprd_rtc_get_alarm_sec();
+
+ secs = secs + secs_start_year_to_1970;
+ rtc_time_to_tm(secs, &alrm->time);
+
+ alrm->enabled = !!(sci_adi_read((unsigned long)ANA_RTC_INT_EN) & RTC_ALARM_BIT);
+ alrm->pending = !!(sci_adi_read((unsigned long)ANA_RTC_INT_RAW_STS) & RTC_ALARM_BIT);
+
+ return 0;
+}
+
+static int sprd_rtc_read_poweroff_alarm(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ return sprd_rtc_read_alarm(dev, alrm);
+}
+
+static inline unsigned long sprd_rtc_get_aux_alarm_sec(void)
+{
+ unsigned int sec, min, hour, day;
+ unsigned long aux_alrm_sec;
+
+ day = sci_adi_read((unsigned long)ANA_RTC_DAY_AUXALM_UPD) & RTC_DAY_MASK;
+ hour = sci_adi_read((unsigned long)ANA_RTC_HOUR_AUXALM_UPD) & RTC_HOUR_MASK;
+ min = sci_adi_read((unsigned long)ANA_RTC_MIN_AUXALM_UPD) & RTC_MIN_MASK;
+ sec = sci_adi_read((unsigned long)ANA_RTC_SEC_AUXALM_UPD) & RTC_SEC_MASK;
+
+ aux_alrm_sec = (((day*24) + hour)*60 + min)*60 + sec;
+ return aux_alrm_sec;
+}
+
+static int sprd_rtc_set_aux_alarm_sec(unsigned long secs)
+{
+ unsigned int sec, min, hour, day;
+ unsigned long temp;
+
+ sec = secs % 60;
+ temp = (secs - sec)/60;
+ min = temp%60;
+ temp = (temp - min)/60;
+ hour = temp%24;
+ temp = (temp - hour)/24;
+ day = temp;
+
+ sci_adi_set((unsigned long)ANA_RTC_INT_CLR, RTC_AUX_ALARM_BIT);
+ sci_adi_raw_write((unsigned long)ANA_RTC_SEC_AUXALM_UPD, sec);
+ sci_adi_raw_write((unsigned long)ANA_RTC_MIN_AUXALM_UPD, min);
+ sci_adi_raw_write((unsigned long)ANA_RTC_HOUR_AUXALM_UPD, hour);
+ sci_adi_raw_write((unsigned long)ANA_RTC_DAY_AUXALM_UPD, day);
+
+ return 0;
+}
+
+static int sprd_rtc_read_aux_alarm(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ unsigned long secs = sprd_rtc_get_aux_alarm_sec();
+
+ secs = secs + secs_start_year_to_1970;
+ rtc_time_to_tm(secs, &alrm->time);
+
+ alrm->enabled = !!(sci_adi_read((unsigned long)ANA_RTC_INT_EN) & RTC_AUX_ALARM_BIT);
+ alrm->pending = !!(sci_adi_read((unsigned long)ANA_RTC_INT_RAW_STS) & RTC_AUX_ALARM_BIT);
+
+ return 0;
+}
+
+static int sprd_rtc_set_aux_alarm(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ unsigned long secs;
+ unsigned int int_stat;
+
+ sci_adi_raw_write(ANA_RTC_INT_CLR, RTC_AUX_ALARM_BIT);
+
+ if(alrm->enabled){
+ rtc_tm_to_time(&alrm->time, &secs);
+ if(secs < secs_start_year_to_1970){
+ printk(KERN_ERR "Can't set alarm value,need set the right time!\n");
+ return -1;
+ }
+
+ /*Notice! need to deal with normal alarm and aux alarm*/
+ int_stat = sci_adi_read((unsigned long)ANA_RTC_INT_EN);
+ int_stat |= RTC_AUX_ALARM_BIT;
+ sci_adi_raw_write((unsigned long)ANA_RTC_INT_EN, int_stat);
+
+ secs = secs - secs_start_year_to_1970;
+ wake_lock(&rtc_wake_lock);
+ sprd_rtc_set_aux_alarm_sec(secs);
+ /*unlock the rtc alrm int*/
+ wake_unlock(&rtc_wake_lock);
+ }else{
+ sci_adi_clr((unsigned long)ANA_RTC_INT_EN, RTC_AUX_ALARM_BIT);
+ }
+
+ return 0;
+}
+
+static int sprd_rtc_set_alarm(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ unsigned long secs;
+ unsigned int int_stat, spg_val;
+
+ sci_adi_raw_write((unsigned long)ANA_RTC_INT_CLR, RTC_ALARM_BIT);
+
+ if(alrm->enabled){
+ rtc_tm_to_time(&alrm->time, &secs);
+ if(secs < secs_start_year_to_1970){
+ printk(KERN_ERR "Can't set alarm value,need set the right time!\n");
+ return -1;
+ }
+
+ /* Notice! need to deal with normal alarm and aux alarm */
+ int_stat = sci_adi_read((unsigned long)ANA_RTC_INT_EN);
+ int_stat |= RTC_ALARM_BIT;
+ sci_adi_raw_write((unsigned long)ANA_RTC_INT_EN, int_stat);
+
+ secs = secs - secs_start_year_to_1970;
+ wake_lock(&rtc_wake_lock);
+ sprd_rtc_set_alarm_sec(secs);
+
+ /* unlock the rtc alrm int */
+ spg_val = sci_adi_read((unsigned long)ANA_RTC_SPG_VALUE);
+ spg_val &= (~(SPRD_RTC_ALMLOCK_MASK | SPRD_RTC_POWEROFF_ALARM));
+ spg_val |= SPRD_RTC_ALM_UNLOCK;
+ sci_adi_raw_write((unsigned long)ANA_RTC_SPG_UPD, spg_val);
+ wake_unlock(&rtc_wake_lock);
+ }else{
+ sprd_rtc_set_alarm_sec(0);
+ sci_adi_clr(ANA_RTC_INT_EN, RTC_ALARM_BIT);
+
+ /* lock the rtc alrm int */
+ spg_val = sci_adi_read((unsigned long)ANA_RTC_SPG_VALUE);
+ spg_val &= (~(SPRD_RTC_ALMLOCK_MASK | SPRD_RTC_POWEROFF_ALARM));
+ spg_val |= SPRD_RTC_ALM_LOCK;
+ sci_adi_raw_write((unsigned long)ANA_RTC_SPG_UPD, spg_val);
+ msleep(150);
+ }
+
+ return 0;
+}
+
+static int sprd_rtc_set_poweroff_alarm(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ unsigned int spg_val;
+
+ int ret;
+
+ ret = sprd_rtc_set_alarm(dev, alrm);
+ if (ret) { /* set alarm error */
+ return ret;
+ } else {
+ if (alrm->enabled) {
+ spg_val = sci_adi_read((unsigned long)ANA_RTC_SPG_VALUE);
+ spg_val &= (~SPRD_RTC_ALMLOCK_MASK);
+ spg_val |= (SPRD_RTC_ALM_UNLOCK |
+ SPRD_RTC_POWEROFF_ALARM);
+ sci_adi_raw_write((unsigned long)ANA_RTC_SPG_UPD, spg_val);
+ msleep(150);
+ }
+ }
+
+ return 0;
+}
+
+void sprd_rtc_clr_in2min_alarm(void)
+{
+ unsigned int spg_val;
+ spg_val = sci_adi_read((unsigned long)ANA_RTC_SPG_VALUE);
+ spg_val &= (~SPRD_RTC_ALMLOCK_MASK);
+ spg_val |= (SPRD_RTC_ALM_UNLOCK) ;
+ spg_val &= (~SPRD_RTC_POWEROFF_ALARM);
+
+ sci_adi_raw_write((unsigned long)ANA_RTC_SPG_UPD, spg_val);
+ msleep(50);
+}
+
+
+static int sprd_rtc_read_time(struct device *dev,
+ struct rtc_time *tm)
+{
+ unsigned long secs = sprd_rtc_get_sec();
+
+ secs = secs + secs_start_year_to_1970;
+
+ if (secs > 0x7f000000)
+ secs = secs_start_year_to_1970;
+
+ rtc_time_to_tm(secs, tm);
+
+ return rtc_valid_tm(tm);
+}
+
+static int sprd_rtc_set_time(struct device *dev,
+ struct rtc_time *tm)
+{
+ unsigned long secs;
+ int ret = 0;
+
+ rtc_tm_to_time(tm, &secs);
+ if(secs < secs_start_year_to_1970){
+ printk(KERN_ERR "Can't set time,need set the right time!\n");
+ return -1;
+ }
+
+ secs = secs - secs_start_year_to_1970;
+ ret = sprd_rtc_set_sec(secs);
+ return ret;
+}
+
+static int sprd_rtc_set_mmss(struct device *dev, unsigned long secs)
+{
+ int ret = 0;
+
+ if(secs < secs_start_year_to_1970){
+ printk(KERN_ERR "Can't set mmss,need set the right time!\n");
+ return -1;
+ }
+
+ secs = secs - secs_start_year_to_1970;
+ ret = sprd_rtc_set_sec(secs);
+ return ret;
+}
+
+#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S)
+static int sprd_rtc_check_power_down(struct device *dev)
+{
+ int rst_value;
+ int ret;
+ unsigned long secs;
+ struct rtc_time tm;
+
+ rst_value = sci_adi_read((unsigned long)ANA_RTC_RST2);
+ if(rst_value == RTC_POWERDOWN_VAL){
+ /* clear ANA_RTC_RST2 */
+ sci_adi_raw_write((unsigned long)ANA_RTC_RST1, RTC_RST_CLEAN_VAL);
+ printk("RTC power down and reset RTC time!\n");
+ secs = mktime(CONFIG_RTC_START_YEAR, 1, 1, 0, 0, 0);
+ rtc_time_to_tm(secs, &tm);
+ if((ret = sprd_rtc_set_time(dev,&tm)) < 0)
+ return ret;
+ }
+ return 0;
+}
+#else
+/*
+ * check current time
+ */
+static int sprd_check_current_time(void)
+{
+ unsigned long secs;
+ struct rtc_time tm;
+
+ secs = sprd_rtc_get_sec();
+ secs = secs + secs_start_year_to_1970;
+ rtc_time_to_tm(secs, &tm);
+
+ rtc_tm_to_time(&tm, &secs);
+ if ((secs < secs_start_year_to_1970) || (secs > 0x7f000000))
+ return -1;
+ else
+ return 0;
+}
+
+static int sprd_rtc_check_power_down(struct device *dev)
+{
+ unsigned int spg_val;
+ unsigned long secs;
+ struct rtc_time tm;
+ unsigned int_stat = 0;
+ int ret = 0, cnt = 0;
+
+ spg_val = sci_adi_read((unsigned long)ANA_RTC_SPG_VALUE);
+ if (((spg_val & SPRD_RTC_POWERDOWN_RESET) == 0) ||
+ sprd_check_current_time()) {
+ pr_info("RTC power down and reset RTC time!!\n");
+ secs = mktime(CONFIG_RTC_START_YEAR, 1, 1, 0, 0, 0);
+ rtc_time_to_tm(secs, &tm);
+ if ((ret = sprd_rtc_set_time(dev,&tm)) < 0)
+ return ret;
+
+ spg_val |= SPRD_RTC_POWERDOWN_RESET;
+ sci_adi_raw_write((unsigned long)ANA_RTC_SPG_UPD, spg_val);
+ /* wait till SPG value update done */
+ do {
+ int_stat = sci_adi_read((unsigned long)ANA_RTC_INT_RAW_STS) & RTC_SPG_BIT;
+ if (RTC_SPG_BIT == int_stat)
+ break;
+ msleep(10);
+ } while (cnt++ < SPRD_RTC_SET_MAX);
+
+ if (cnt >= SPRD_RTC_SET_MAX){
+ pr_err("reset RTC time error.\n");
+ ret = -EBUSY;
+ }
+ }
+ return ret;
+}
+#endif
+
+static int sprd_rtc_proc(struct device *dev, struct seq_file *seq)
+{
+ struct platform_device *plat_dev = to_platform_device(dev);
+
+ seq_printf(seq, "sprd_rtc\t: yes\n");
+ seq_printf(seq, "id\t\t: %d\n", plat_dev->id);
+
+ return 0;
+}
+
+void rtc_aie_update_irq(void *private);
+static irqreturn_t rtc_interrupt_handler(int irq, void *dev_id)
+{
+ struct rtc_device *rdev = dev_id;
+
+ pr_info("RTC ***** interrupt happen\n");
+ //rtc_update_irq(rdev, 1, RTC_AF | RTC_IRQF);
+ wake_lock_timeout(&rtc_interrupt_wake_lock,2*HZ);
+ rtc_aie_update_irq(rdev);
+ clear_rtc_int(RTC_INT_ALM_MSK);
+ return IRQ_HANDLED;
+}
+
+static ssize_t sprd_show_caliberate(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t retval;
+ retval = sprintf(buf, "%lu\n", secs_start_year_to_1970);
+ return retval;
+}
+
+static int sprd_creat_caliberate_attr(struct device dev)
+{
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(sprd_caliberate); i++) {
+ rc = device_create_file(&dev, &sprd_caliberate[i]);
+ if (rc)
+ goto sprd_attrs_failed;
+ }
+ goto sprd_attrs_succeed;
+
+sprd_attrs_failed:
+ while (i--)
+ device_remove_file(&dev, &sprd_caliberate[i]);
+
+sprd_attrs_succeed:
+ return rc;
+}
+
+static int sprd_remove_caliberate_attr(struct device dev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sprd_caliberate); i++) {
+ device_remove_file(&dev, &sprd_caliberate[i]);
+ }
+ return 0;
+}
+
+static int sprd_rtc_open(struct device *dev)
+{
+ unsigned int_stat = 0;
+ int ret = 0;
+
+ /* enable rtc alarm interrupt */
+ int_stat = sci_adi_read(ANA_RTC_INT_EN);
+ int_stat |= RTC_ALARM_BIT | RTC_AUX_ALARM_BIT;
+ sci_adi_raw_write(ANA_RTC_INT_EN, int_stat);
+
+ printk("RTC open is calling!\n");
+
+ return ret;
+}
+
+static int sprd_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+
+ switch(cmd){
+ case SET_POWERON_ALARM:
+ ret = sprd_rtc_set_alarm(dev,(struct rtc_wkalrm *)arg);
+ break;
+ case GET_POWERON_ALARM:
+ ret = sprd_rtc_read_alarm(dev,(struct rtc_wkalrm *)arg);
+ break;
+ case SET_WAKE_ALARM:
+ ret = sprd_rtc_set_aux_alarm(dev,(struct rtc_wkalrm *)arg);
+ break;
+ case GET_WAKE_ALARM:
+ ret = sprd_rtc_read_aux_alarm(dev,(struct rtc_wkalrm *)arg);
+ break;
+ case SET_POWEROFF_ALARM:
+ ret = sprd_rtc_set_poweroff_alarm(dev,(struct rtc_wkalrm *)arg);
+ break;
+ case GET_POWEROFF_ALARM:
+ ret = sprd_rtc_read_poweroff_alarm(dev,(struct rtc_wkalrm *)arg);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static const struct rtc_class_ops sprd_rtc_ops = {
+ .open = sprd_rtc_open,
+ .proc = sprd_rtc_proc,
+ .read_time = sprd_rtc_read_time,
+ .read_alarm = sprd_rtc_read_alarm,
+ .set_time = sprd_rtc_set_time,
+ .set_alarm = sprd_rtc_set_alarm,
+ .set_mmss = sprd_rtc_set_mmss,
+ .ioctl = sprd_rtc_ioctl,
+};
+
+static int sprd_rtc_probe(struct platform_device *plat_dev)
+{
+ struct resource *res;
+ static struct sprd_rtc *rtc_dev;
+ int ret = 0;
+
+ rtc_dev = kzalloc(sizeof(*rtc_dev), GFP_KERNEL);
+ if(IS_ERR(rtc_dev)){
+ ret = PTR_ERR(rtc_dev);
+ return ret;
+ };
+
+ /* enable rtc device */
+ rtc_dev->clk = clk_get(&plat_dev->dev, "ext_32k");
+ if (IS_ERR(rtc_dev->clk)) {
+ ret = PTR_ERR(rtc_dev->clk);
+ goto kfree_data;
+ }
+
+ ret = clk_prepare_enable(rtc_dev->clk);
+ if (ret < 0)
+ goto put_clk;
+
+ sci_adi_set(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_RTC_EN);
+ sci_adi_set(ANA_REG_GLB_RTC_CLK_EN, BIT_RTC_RTC_EN);
+
+ sprd_rtc_check_power_down(&plat_dev->dev);
+
+ /*disable and clean irq*/
+ sci_adi_clr(ANA_RTC_INT_EN, 0xffff);
+ clear_rtc_int(RTC_INT_ALL_MSK);
+
+ device_init_wakeup(&plat_dev->dev, 1);
+ rtc_dev->rtc = rtc_device_register("sprd_rtc", &plat_dev->dev,
+ &sprd_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dev->rtc)) {
+ ret = PTR_ERR(rtc_dev->rtc);
+ goto disable_clk;
+ }
+
+ res = platform_get_resource(plat_dev, IORESOURCE_IRQ, 0);
+ if(unlikely(!res)) {
+ dev_err(&plat_dev->dev, "no irq resource specified\n");
+ goto unregister_rtc;
+ }
+ rtc_dev->irq_no = res->start;
+
+ platform_set_drvdata(plat_dev, rtc_dev);
+
+ ret = request_threaded_irq(rtc_dev->irq_no, rtc_interrupt_handler,
+ NULL, 0, "sprd_rtc", rtc_dev->rtc);
+ if(ret){
+ printk(KERN_ERR "RTC regist irq error\n");
+ goto unregister_rtc;
+ }
+ sprd_creat_caliberate_attr(rtc_dev->rtc->dev);
+
+ return 0;
+
+unregister_rtc:
+ rtc_device_unregister(rtc_dev->rtc);
+disable_clk:
+ clk_disable_unprepare(rtc_dev->clk);
+put_clk:
+ clk_put(rtc_dev->clk);
+kfree_data:
+ kfree(rtc_dev);
+ return ret;
+}
+
+static int sprd_rtc_remove(struct platform_device *plat_dev)
+{
+ struct sprd_rtc *rtc_dev = platform_get_drvdata(plat_dev);
+ sprd_remove_caliberate_attr(rtc_dev->rtc->dev);
+ rtc_device_unregister(rtc_dev->rtc);
+ clk_disable_unprepare(rtc_dev->clk);
+ clk_put(rtc_dev->clk);
+ kfree(rtc_dev);
+
+ return 0;
+}
+
+static struct of_device_id sprd_rtc_match_table[] = {
+ { .compatible = "sprd,rtc", },
+ { },
+};
+
+static struct platform_driver sprd_rtc_driver = {
+ .probe = sprd_rtc_probe,
+ .remove = sprd_rtc_remove,
+ .driver = {
+ .name = "sprd_rtc",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sprd_rtc_match_table),
+ },
+};
+
+static int __init sprd_rtc_init(void)
+{
+ int err;
+ struct rtc_time read_tm;
+
+ wake_lock_init(&rtc_wake_lock, WAKE_LOCK_SUSPEND, "rtc");
+ wake_lock_init(&rtc_interrupt_wake_lock, WAKE_LOCK_SUSPEND, "rtc_interrupt");
+
+ if(CONFIG_RTC_START_YEAR > 1970)
+ secs_start_year_to_1970 = mktime(CONFIG_RTC_START_YEAR, 1, 1, 0, 0, 0);
+ else
+ secs_start_year_to_1970 = mktime(1970, 1, 1, 0, 0, 0);
+
+ if ((err = platform_driver_register(&sprd_rtc_driver))) {
+ wake_lock_destroy(&rtc_wake_lock);
+ wake_lock_destroy(&rtc_interrupt_wake_lock);
+ return err;
+ }
+
+ sprd_rtc_read_time(NULL,&read_tm);
+ printk("After init RTC time: %d-%d-%d %d:%d:%d\n",read_tm.tm_year+1900,
+ read_tm.tm_mon+1,read_tm.tm_mday,read_tm.tm_hour,read_tm.tm_min,read_tm.tm_sec);
+ return 0;
+}
+
+static void __exit sprd_rtc_exit(void)
+{
+ platform_driver_unregister(&sprd_rtc_driver);
+ wake_lock_destroy(&rtc_interrupt_wake_lock);
+ wake_lock_destroy(&rtc_wake_lock);
+}
+
+MODULE_AUTHOR("Baolin.wang@spreadtrum.com");
+MODULE_DESCRIPTION("RTC driver/device");
+MODULE_LICENSE("GPL");
+
+module_init(sprd_rtc_init);
+module_exit(sprd_rtc_exit);
|
