From 8e4bff4cab0ddac6060645b0715210484d02ff40 Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Wed, 30 Aug 2017 08:25:59 +0000 Subject: Initial file dump from open source release --- drivers/rtc/rtc-sprd.c | 884 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 884 insertions(+) create mode 100644 drivers/rtc/rtc-sprd.c (limited to 'drivers/rtc/rtc-sprd.c') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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); -- cgit v1.3.1