/* * driver/rtc/rtc-na51055.c * * Author: howard_chang@novatek.com.tw * Created: Feb 26, 2019 * Copyright: Novatek Inc. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRV_VERSION "1.11.030" #ifdef CONFIG_RTC_NVT_PWBC_SDT_USER #define RTC_PWBC_SDT_ISR_DISABLE 1 #else #define RTC_PWBC_SDT_ISR_DISABLE 0 #endif static struct completion cset_completion; static struct semaphore rtc_sem; static unsigned int _REGIOBASE; static int is_power_lost; #define loc_cpu() down(&rtc_sem); #define unl_cpu() up(&rtc_sem); void rtc_wait_pwr_alarm_done(void); void rtc_trigger_cset(void); static const unsigned short rtc_ydays[2][13] = { /* Normal years */ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, /* Leap years */ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; struct nvt_rtc_priv { struct rtc_device *rtc; struct proc_dir_entry *pproc_pwbc; struct proc_dir_entry *pproc_data; struct delayed_work cset_work; }; static void rtc_setreg(uint32_t offset, REGVALUE value) { nvt_writel(value, _REGIOBASE + offset); } static REGVALUE rtc_getreg(uint32_t offset) { return nvt_readl(_REGIOBASE + offset); } static void nvt_rtc_add_one_sec(u32* sec, u32* min, u32* hour, u32* days) { if (*sec == 59) { if (*min == 59) { *hour += 1; *min = 0; *sec = 0; } else { *min += 1; *sec = 0; } } else { *sec += 1; } if (*hour > 23) { *hour = 0; *days += 1; } } void nvt_rtc_power_control(int reboot_sec) { union RTC_PWBC_REG pwbc_reg; union RTC_CTRL_REG ctrl_reg; if (reboot_sec) { union RTC_TIMER_REG timer_reg; union RTC_DAYKEY_REG daykey_reg; union RTC_PWRALM_REG pwralm_reg; union RTC_OSCAN_REG oscan_reg; int i; u32 sec, min, hour, days; timer_reg.reg = rtc_getreg(RTC_TIMER_REG_OFS); daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); sec = timer_reg.bit.sec; min = timer_reg.bit.min; hour = timer_reg.bit.hour; days = daykey_reg.bit.day; for (i = 0;i < reboot_sec; i++) nvt_rtc_add_one_sec(&sec, &min, &hour, &days); if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { printk("RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); return; } ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); ctrl_reg.bit.cset = 0; ctrl_reg.bit.pwralarmtime_sel = 1; ctrl_reg.bit.pwralarmday_sel = 1; ctrl_reg.bit.alarm_inten = 1; rtc_setreg(RTC_CTRL_REG_OFS, ctrl_reg.reg); pwralm_reg.bit.sec = sec; pwralm_reg.bit.min = min; pwralm_reg.bit.hour = hour; pwralm_reg.bit.day = days & 0x1F; rtc_setreg(RTC_PWRALM_REG_OFS, pwralm_reg.reg); /*Set OSC analog parameter*/ oscan_reg.reg = rtc_getreg(RTC_OSCAN_REG_OFS); oscan_reg.bit.osc_analogcfg = RTC_INT_OSC_ANALOG_CFG; rtc_setreg(RTC_OSCAN_REG_OFS, oscan_reg.reg); rtc_trigger_cset(); rtc_wait_pwr_alarm_done(); pwbc_reg.reg = rtc_getreg(RTC_PWBC_REG_OFS); pwbc_reg.bit.pwralarm_en = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_reg.reg); } loc_cpu(); if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { printk("RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); unl_cpu(); return; } rtc_wait_pwr_alarm_done(); pwbc_reg.reg = 0x0; pwbc_reg.bit.reset_sdt_timer = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_reg.reg); pwbc_reg.reg = 0x0; pwbc_reg.bit.pwr_off = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_reg.reg); printk("%s: If not power off, plz check 32K crystal or pad PWR_EN\r\n", __func__); } /* //add by Y_S WU 2014.5.22 CTS default time static struct rtc_time default_tm = { .tm_year = (2014 - 1900), // year 2014 .tm_mon = 1, // month 2 .tm_mday = 5, // day 5 .tm_hour = 12, .tm_min = 0, .tm_sec = 0 }; */ static void rtc_cset_work(struct work_struct *work) { complete(&cset_completion); } static irqreturn_t rtc_update_handler(int irq, void *data) { union RTC_STATUS_REG status_reg; union RTC_PWBC_REG pwbc_reg; union RTC_PWBCSTS_REG pwbcsts_reg; struct nvt_rtc_priv *priv = (struct nvt_rtc_priv*)data; status_reg.reg = rtc_getreg(RTC_STATUS_REG_OFS); if (status_reg.bit.alarm_sts || status_reg.bit.cset_sts) { rtc_setreg(RTC_STATUS_REG_OFS, status_reg.reg); /*CSET done*/ if (status_reg.bit.cset_sts) { schedule_delayed_work(&priv->cset_work, msecs_to_jiffies(8)); } } pwbcsts_reg.reg = rtc_getreg(RTC_PWBCSTS_REG_OFS); pwbc_reg.reg = rtc_getreg(RTC_PWBC_REG_OFS); pwbcsts_reg.reg &= pwbc_reg.reg; if (pwbcsts_reg.reg) { rtc_setreg(RTC_PWBCSTS_REG_OFS, pwbcsts_reg.reg); if (pwbcsts_reg.bit.pwr_sw1_sts) { // reset shutdown timer to prevent SW1 shutdown system if (pwbc_reg.bit.reset_sdt_timer == 0) { pwbc_reg.bit.reset_sdt_timer = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_reg.reg); } } } return IRQ_HANDLED; } void rtc_trigger_cset(void) { ulong timeout; union RTC_STATUS_REG status_reg; union RTC_CTRL_REG ctrl_reg; /*Wait for RTC SRST done*/ timeout = jiffies + msecs_to_jiffies(1000); do { status_reg.reg = rtc_getreg(RTC_STATUS_REG_OFS); if (time_after_eq(jiffies, timeout)) { printk("RTC s/w reset timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); return; } } while (status_reg.bit.srst_sts == 1); /*Wait for RTC is ready for next CSET*/ timeout = jiffies + msecs_to_jiffies(1000); do { ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); if (time_after_eq(jiffies, timeout)) { printk("RTC cset timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); return; } } while (ctrl_reg.bit.cset == 1); /*Trigger CSET*/ ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); ctrl_reg.bit.cset = 1; ctrl_reg.bit.cset_inten = 1; rtc_setreg(RTC_CTRL_REG_OFS, ctrl_reg.reg); } void rtc_wait_pwr_alarm_done(void) { ulong timeout; union RTC_PWBC_REG pwbc_reg; timeout = jiffies + msecs_to_jiffies(1000); do { pwbc_reg.reg = rtc_getreg(RTC_PWBC_REG_OFS); if (time_after_eq(jiffies, timeout)) { printk("RTC cset timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); return; } } while ((pwbc_reg.bit.pwralarm_en == 1) || \ (pwbc_reg.bit.pwralarm_dis == 1)); } #ifdef CONFIG_RTC_INTF_DEV static int nvt_rtc_ioctl(struct device *dev, unsigned int cmd, \ unsigned long arg) { /* switch(cmd) { case RTC_AIE_ON: pr_info("RTC_AIE_ON\n"); break; case RTC_AIE_OFF: pr_info("RTC_AIE_OFF\n"); break; case RTC_UIE_ON: pr_info("RTC_UIE_ON\n"); break; case RTC_UIE_OFF: pr_info("RTC_UIE_OFF\n"); break; case RTC_WIE_ON: pr_info("RTC_WIE_ON\n"); break; case RTC_WIE_OFF: pr_info("RTC_WIE_OFF\n"); break; case RTC_ALM_SET: pr_info("RTC_ALM_SET\n"); break; case RTC_ALM_READ: pr_info("RTC_ALM_READ\n"); break; case RTC_RD_TIME: pr_info("RTC_RD_TIME\n"); break; case RTC_SET_TIME: pr_info("RTC_SET_TIME\n"); break; case RTC_IRQP_READ: pr_info("RTC_IRQP_READ\n"); break; case RTC_IRQP_SET: pr_info("RTC_IRQP_SET\n"); break; case RTC_EPOCH_READ: pr_info("RTC_EPOCH_READ\n"); break; case RTC_EPOCH_SET: pr_info("RTC_EPOCH_SET\n"); break; case RTC_WKALM_SET: pr_info("RTC_WKALM_SET\n"); break; case RTC_WKALM_RD: pr_info("RTC_WKALM_RD\n"); break; case RTC_PLL_SET: pr_info("RTC_PLL_SET\n"); break; case RTC_PLL_GET: pr_info("RTC_PLL_GET\n"); break; default: pr_info("unknown rtc ioctl :0X%X\n",cmd); } */ return 0; } #else #define nvt_rtc_ioctl NULL #endif #ifdef CONFIG_PROC_FS static int nvt_rtc_proc(struct device *dev, struct seq_file *seq) { return 0; } #else #define nvt_rtc_proc NULL #endif static int nvt_rtc_reset(struct device *dev) { ulong timeout; union RTC_STATUS_REG status_reg; union RTC_CTRL_REG ctrl_reg; // Wait for previous SRST done do { status_reg.reg = rtc_getreg(RTC_STATUS_REG_OFS); } while (status_reg.bit.srst_sts); // Wait for RTC is ready for next CSET do { ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); } while (ctrl_reg.bit.cset); // Do software reset ctrl_reg.reg = 0; ctrl_reg.bit.srst = 1; rtc_setreg(RTC_CTRL_REG_OFS, ctrl_reg.reg); // Wait for RTC SRST finished, RTC can do other operations except SRST timeout = jiffies + msecs_to_jiffies(1000); do { ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); if (time_after_eq(jiffies, timeout)) { dev_err(dev, "RTC s/w reset timeout, plz check RTC 32K osc or PWR_SW\n"); return -ETXTBSY; } } while(ctrl_reg.bit.srst); return 0; } static int nvt_rtc_chk_power(struct device *dev) { int ret = 0; int year_looper; uint32_t days = 0; union RTC_DAYKEY_REG daykey_reg; union RTC_CTRL_REG ctrl_reg; union RTC_OSCAN_REG oscan_reg; union RTC_PWBC_REG pwbc_reg; const int RESET_YEAR = 1970 - 1900; const int RESET_MON = 0; // set power alarm default disabled pwbc_reg.reg = rtc_getreg(RTC_PWBC_REG_OFS); pwbc_reg.bit.pwralarm_dis = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_reg.reg); for (year_looper = 0; year_looper < RESET_YEAR; year_looper++) days += rtc_ydays[is_leap_year(year_looper + 1900)][12]; days += rtc_ydays[is_leap_year(year_looper + 1900)][RESET_MON]; daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); if (daykey_reg.bit.key & RTC_INT_KEY_POR) { is_power_lost = 1; printk("%s: RTC power lost detected\r\n", __func__); ret = nvt_rtc_reset(dev); if (ret) return ret; } else { oscan_reg.reg = rtc_getreg(RTC_OSCAN_REG_OFS); if (oscan_reg.bit.osc_analogcfg == RTC_INT_OSC_ANALOG_CFG) { return 0; } is_power_lost = 1; printk("%s: RTC ANA not expected 0x%x\r\n", __func__, oscan_reg.bit.osc_analogcfg); ret = nvt_rtc_reset(dev); if (ret) return ret; } loc_cpu(); /*wait for cset done*/ wait_for_completion(&cset_completion); ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); ctrl_reg.bit.cset = 0; ctrl_reg.bit.key_sel = 1; ctrl_reg.bit.pwralarmday_sel = 1; ctrl_reg.bit.day_sel = 1; rtc_setreg(RTC_CTRL_REG_OFS, ctrl_reg.reg); daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); daykey_reg.bit.key = RTC_INT_KEY; rtc_setreg(RTC_DAYKEY_REG_OFS, daykey_reg.reg); /*Set OSC analog parameter*/ oscan_reg.reg = rtc_getreg(RTC_OSCAN_REG_OFS); oscan_reg.bit.osc_analogcfg = RTC_INT_OSC_ANALOG_CFG; rtc_setreg(RTC_OSCAN_REG_OFS, oscan_reg.reg); /*Set default year*/ daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); daykey_reg.bit.day = days; rtc_setreg(RTC_DAYKEY_REG_OFS, daykey_reg.reg); rtc_trigger_cset(); unl_cpu(); return ret; } static int nvt_rtc_read_time(struct device *dev, struct rtc_time *tm) { uint32_t days, months, years, month_days; union RTC_TIMER_REG timer_reg; union RTC_DAYKEY_REG daykey_reg; loc_cpu(); /*wait for cset done*/ if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { dev_err(dev, "RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); unl_cpu(); return -ETXTBSY; } complete(&cset_completion); unl_cpu(); timer_reg.reg = rtc_getreg(RTC_TIMER_REG_OFS); daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); days = daykey_reg.bit.day; for (years = 0; days >= rtc_ydays[is_leap_year(years + 1900)][12]; \ years++) { days -= rtc_ydays[is_leap_year(years + 1900)][12]; } for (months = 1; months < 13; months++) { if (days <= rtc_ydays[is_leap_year(years + 1900)][months]) { days -= rtc_ydays[is_leap_year(years + 1900)][months-1]; months--; break; } } month_days = rtc_ydays[is_leap_year(years + 1900)][months+1] - \ rtc_ydays[is_leap_year(years + 1900)][months]; if (days == month_days) { months++; days = 1; } else days++; /*Align linux time format*/ tm->tm_sec = timer_reg.bit.sec; tm->tm_min = timer_reg.bit.min; tm->tm_hour = timer_reg.bit.hour; tm->tm_mday = days; tm->tm_mon = months; tm->tm_year = years; pr_debug("after read time: sec = %d, min = %d, hour = %d, mday = %d," \ "mon = %d, year = %d, wday = %d, yday = %d," \ "\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \ tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday); return rtc_valid_tm(tm); } static int nvt_rtc_set_time(struct device *dev, struct rtc_time *tm) { int year_looper, ret; uint32_t days = 0; union RTC_TIMER_REG timer_reg; union RTC_DAYKEY_REG daykey_reg; union RTC_CTRL_REG ctrl_reg; pr_debug("kernel set time: sec = %d, min = %d, hour = %d, mday = %d," \ "mon = %d, year = %d, wday = %d, yday = %d," \ "\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \ tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday); ret = rtc_valid_tm(tm); if (ret < 0) return ret; for (year_looper = 0; year_looper < tm->tm_year; year_looper++) days += rtc_ydays[is_leap_year(year_looper + 1900)][12]; days += rtc_ydays[is_leap_year(year_looper + 1900)][tm->tm_mon]; tm->tm_mday--; /*subtract the day which is not ended*/ days += tm->tm_mday; loc_cpu(); /*wait for cset done*/ if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { dev_err(dev, "RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); unl_cpu(); return -ETXTBSY; } ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); ctrl_reg.bit.cset = 0; ctrl_reg.bit.day_sel = 1; ctrl_reg.bit.time_sel = 1; rtc_setreg(RTC_CTRL_REG_OFS, ctrl_reg.reg); timer_reg.reg = 0; timer_reg.bit.sec = tm->tm_sec; timer_reg.bit.min = tm->tm_min; timer_reg.bit.hour = tm->tm_hour; rtc_setreg(RTC_TIMER_REG_OFS, timer_reg.reg); daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); daykey_reg.bit.day = days; rtc_setreg(RTC_DAYKEY_REG_OFS, daykey_reg.reg); rtc_trigger_cset(); unl_cpu(); return ret; } static int nvt_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) { struct rtc_time *tm = &alrm->time; uint32_t days, alarm_days, months, years, month_days; union RTC_PWRALM_REG pwralarm_reg; union RTC_DAYKEY_REG daykey_reg; loc_cpu(); /*wait for cset done*/ if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { dev_err(dev, "RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); unl_cpu(); return -ETXTBSY; } complete(&cset_completion); unl_cpu(); pwralarm_reg.reg = rtc_getreg(RTC_PWRALM_REG_OFS); daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); alarm_days = pwralarm_reg.bit.day; days = daykey_reg.bit.day; if ((days & RTC_INT_MAX_PWRALARM_DAY) > alarm_days) { days = ((days & ~RTC_INT_MAX_PWRALARM_DAY) | alarm_days) \ + (1 << RTC_INT_PRWALARM_DAY_SHIFT); } else days = ((days & ~RTC_INT_MAX_PWRALARM_DAY) | alarm_days); for (years = 0; days >= rtc_ydays[is_leap_year(years + 1900)][12]; \ years++) { days -= rtc_ydays[is_leap_year(years + 1900)][12]; } for (months = 1; months < 13; months++) { if (days <= rtc_ydays[is_leap_year(years + 1900)][months]) { days -= rtc_ydays[is_leap_year(years + 1900)][months-1]; months--; break; } } month_days = rtc_ydays[is_leap_year(years + 1900)][months+1] - \ rtc_ydays[is_leap_year(years + 1900)][months]; if (days == month_days) { months++; days = 1; } else days++; /*Align linux time format*/ tm->tm_sec = pwralarm_reg.bit.sec; tm->tm_min = pwralarm_reg.bit.min; tm->tm_hour = pwralarm_reg.bit.hour; tm->tm_mday = days; tm->tm_mon = months; tm->tm_year = years; pr_debug("read alarm time: sec = %d, min = %d, hour = %d, mday = %d," \ "mon = %d, year = %d, wday = %d, yday = %d," \ "\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \ tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday); return rtc_valid_tm(tm); } static int nvt_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) { struct rtc_time *tm = &alrm->time; int year_looper, ret; uint32_t current_days = 0, alarm_days = 0; union RTC_DAYKEY_REG daykey_reg; union RTC_CTRL_REG ctrl_reg; union RTC_PWRALM_REG pwralarm_reg; union RTC_PWBC_REG pwbc_reg; union RTC_OSCAN_REG oscan_reg; daykey_reg.reg = rtc_getreg(RTC_DAYKEY_REG_OFS); current_days = daykey_reg.bit.day; pr_debug("set alarm time: sec = %d, min = %d, hour = %d, mday = %d," \ "mon = %d, year = %d, wday = %d, yday = %d," \ "\n", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, \ tm->tm_mon, tm->tm_year, tm->tm_wday, tm->tm_yday); ret = rtc_valid_tm(tm); if (ret < 0) return ret; for (year_looper = 0; year_looper < tm->tm_year; year_looper++) alarm_days += rtc_ydays[is_leap_year(year_looper + 1900)][12]; alarm_days += rtc_ydays[is_leap_year(year_looper + 1900)][tm->tm_mon]; tm->tm_mday--; /*subtract the day which is not ended*/ alarm_days += tm->tm_mday; /*Check date parameter for maximum register setting*/ if ((alarm_days < current_days) || ((alarm_days - current_days) > \ RTC_INT_MAX_PWRALARM_DAY)) { pr_err("Invalid parameter!\n"); return E_PAR; } if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { dev_err(dev, "RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); return -ETXTBSY; } ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); ctrl_reg.bit.cset = 0; ctrl_reg.bit.pwralarmtime_sel = 1; ctrl_reg.bit.pwralarmday_sel = 1; ctrl_reg.bit.alarm_inten = 1; rtc_setreg(RTC_CTRL_REG_OFS, ctrl_reg.reg); pwralarm_reg.reg = 0; pwralarm_reg.bit.sec = tm->tm_sec; pwralarm_reg.bit.min = tm->tm_min; pwralarm_reg.bit.hour = tm->tm_hour; pwralarm_reg.bit.day = alarm_days; rtc_setreg(RTC_PWRALM_REG_OFS, pwralarm_reg.reg); /*Set OSC analog parameter*/ oscan_reg.reg = rtc_getreg(RTC_OSCAN_REG_OFS); oscan_reg.bit.osc_analogcfg = RTC_INT_OSC_ANALOG_CFG; rtc_setreg(RTC_OSCAN_REG_OFS, oscan_reg.reg); rtc_trigger_cset(); rtc_wait_pwr_alarm_done(); pwbc_reg.reg = rtc_getreg(RTC_PWBC_REG_OFS); pwbc_reg.bit.pwralarm_en = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_reg.reg); return ret; } static int pwbc_init(void) { #if (RTC_PWBC_SDT_ISR_DISABLE == 0) union RTC_PWBC_REG pwbc_ctrl_reg; pwbc_ctrl_reg.reg = rtc_getreg(RTC_PWBC_REG_OFS); pwbc_ctrl_reg.bit.pwr_sw1_inten = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_ctrl_reg.reg); #endif return 0; } /* PWBC related */ static int pwbc_proc_show(struct seq_file *seq, void *v) { union RTC_PWBCSTS_REG pwbcsts_reg; pwbcsts_reg.reg = rtc_getreg(RTC_PWBCSTS_REG_OFS); seq_printf(seq, "power switch 1 value %d\n", pwbcsts_reg.bit.pwr_sw1_pin); seq_printf(seq, "power switch 2 value %d\n", pwbcsts_reg.bit.pwr_sw2_pin); seq_printf(seq, "power switch 3 value %d\n", pwbcsts_reg.bit.pwr_sw3_pin); seq_printf(seq, "power switch 4 value %d\n", pwbcsts_reg.bit.pwr_sw4_pin); seq_printf(seq, "power on source "); if (pwbcsts_reg.bit.pwronsrc_sw1) { seq_printf(seq, "SW1\r\n"); } else if (pwbcsts_reg.bit.pwronsrc_sw2) { seq_printf(seq, "SW2\r\n"); } else if (pwbcsts_reg.bit.pwronsrc_sw3) { seq_printf(seq, "SW3\r\n"); } else if (pwbcsts_reg.bit.pwronsrc_sw4) { seq_printf(seq, "SW4\r\n"); } else { seq_printf(seq, "None\r\n"); } seq_printf(seq, "Power lost: %s\r\n", (is_power_lost?"Yes":"No")); return 0; } static int pwbc_proc_open(struct inode *inode, struct file *file) { return single_open(file, pwbc_proc_show, NULL); } #define PWBC_MAX_CMD_LENGTH 30 static ssize_t pwbc_proc_cmd_write(struct file *file, const char __user *buf, size_t size, loff_t *off) { int val; int len = size; char cmd_line[PWBC_MAX_CMD_LENGTH]; char *cmdstr = cmd_line; const char delimiters[] = {' ', 0x0A, 0x0D, '\0'}; char *p_arg; union RTC_PWBC_REG pwbc_reg; // check command length if (len > (PWBC_MAX_CMD_LENGTH - 1)) { printk("%s: Command length %d is too long\n", __func__, len); return -EINVAL; } // copy command string from user space if (copy_from_user(cmd_line, buf, len)) { return -EINVAL; } if (len == 0) cmd_line[0] = '\0'; else cmd_line[len - 1] = '\0'; p_arg = strsep(&cmdstr, delimiters); sscanf(p_arg, "%x", &val); loc_cpu(); pwbc_reg.reg = rtc_getreg(RTC_PWBC_REG_OFS); // reset shutdown timer to prevent SW1 shutdown system if (pwbc_reg.bit.reset_sdt_timer == 0) { pwbc_reg.bit.reset_sdt_timer = 1; rtc_setreg(RTC_PWBC_REG_OFS, pwbc_reg.reg); } unl_cpu(); return size; } static struct file_operations proc_pwbc_fops = { .owner = THIS_MODULE, .open = pwbc_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = pwbc_proc_cmd_write, }; static const struct rtc_class_ops nvt_rtc_ops = { .ioctl = nvt_rtc_ioctl, .proc = nvt_rtc_proc, .read_time = nvt_rtc_read_time, .set_time = nvt_rtc_set_time, .read_alarm = nvt_rtc_read_alarm, .set_alarm = nvt_rtc_set_alarm, }; /* User data related */ static int data_proc_show(struct seq_file *seq, void *v) { union RTC_DATA_REG data_reg; loc_cpu(); /*wait for cset done*/ if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { printk("RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); unl_cpu(); return -ETXTBSY; } complete(&cset_completion); unl_cpu(); data_reg.reg = rtc_getreg(RTC_DATA_REG_OFS); seq_printf(seq, "0x%x\n", data_reg.bit.data0); return 0; } static int data_proc_open(struct inode *inode, struct file *file) { return single_open(file, data_proc_show, NULL); } #define MAX_CMD_LENGTH 30 static ssize_t data_proc_cmd_write(struct file *file, const char __user *buf, size_t size, loff_t *off) { int val; int len = size; char cmd_line[MAX_CMD_LENGTH]; char *cmdstr = cmd_line; const char delimiters[] = {' ', 0x0A, 0x0D, '\0'}; char *p_arg; union RTC_CTRL_REG ctrl_reg; union RTC_DATA_REG data_reg; // check command length if (len > (MAX_CMD_LENGTH - 1)) { printk("%s: Command length %d is too long\n", __func__, len); return -EINVAL; } // copy command string from user space if (copy_from_user(cmd_line, buf, len)) { return -EINVAL; } if (len == 0) cmd_line[0] = '\0'; else cmd_line[len - 1] = '\0'; p_arg = strsep(&cmdstr, delimiters); sscanf(p_arg, "%x", &val); loc_cpu(); /*wait for cset done*/ if (!wait_for_completion_timeout(&cset_completion, msecs_to_jiffies(1000))) { printk("RTC completion timeout, plz check RTC 32K osc or PWR_SW or VCC_VBAT\n"); unl_cpu(); return -ETXTBSY; } ctrl_reg.reg = rtc_getreg(RTC_CTRL_REG_OFS); ctrl_reg.bit.cset = 0; ctrl_reg.bit.data0_sel = 1; rtc_setreg(RTC_CTRL_REG_OFS, ctrl_reg.reg); data_reg.reg = rtc_getreg(RTC_DATA_REG_OFS); data_reg.bit.data0 = val; rtc_setreg(RTC_DATA_REG_OFS, data_reg.reg); rtc_trigger_cset(); unl_cpu(); return size; } static struct file_operations proc_data_fops = { .owner = THIS_MODULE, .open = data_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = data_proc_cmd_write, }; static int nvt_rtc_probe(struct platform_device *pdev) { struct rtc_device *rtc; struct nvt_rtc_priv *priv; struct resource *memres = NULL; struct proc_dir_entry *pentry = NULL; int ret = 0, irq = 0; /* setup resource */ memres = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (unlikely(!memres)) { dev_err(&pdev->dev, "failed to get resource\n"); return -ENXIO; } _REGIOBASE = (u32) devm_ioremap_resource(&pdev->dev, memres);; if (unlikely(_REGIOBASE == 0)) { dev_err(&pdev->dev, "failed to get io memory\n"); goto out; } priv = kzalloc(sizeof(struct nvt_rtc_priv), GFP_KERNEL); if (!priv) return -ENOMEM; INIT_DELAYED_WORK(&priv->cset_work, rtc_cset_work); is_power_lost = 0; platform_set_drvdata(pdev, priv); device_init_wakeup(&pdev->dev, 1); pwbc_init(); /*Set default HW configuration*/ /*Define what data type will be used, RCW_DEF or manual define*/ irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "No IRQ resource!\n"); goto out; } ret = devm_request_irq(&pdev->dev, irq, rtc_update_handler, 0, pdev->name, priv); if (ret < 0) { dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n", irq, ret); return ret; } init_completion(&cset_completion); sema_init(&rtc_sem, 1); complete(&cset_completion); nvt_rtc_chk_power(&pdev->dev); pentry = proc_create("pwbc", S_IRUGO | S_IXUGO, NULL, &proc_pwbc_fops); if (pentry == NULL) { dev_err(&pdev->dev, "failed to create pwbc proc!\n"); ret = -EINVAL; goto out; } priv->pproc_pwbc = pentry; pentry = proc_create("rtc_data", S_IRUGO | S_IXUGO, NULL, &proc_data_fops); if (pentry == NULL) { dev_err(&pdev->dev, "failed to create data proc!\n"); ret = -EINVAL; goto out_proc_pwbc; } priv->pproc_data = pentry; rtc = rtc_device_register("nvt_rtc", &pdev->dev, &nvt_rtc_ops, THIS_MODULE); if (IS_ERR(rtc)) { ret = PTR_ERR(rtc); goto out_proc_data; } priv->rtc = rtc; return 0; out_proc_data: if (priv->pproc_data) { proc_remove(priv->pproc_data); } out_proc_pwbc: if (priv->pproc_pwbc) { proc_remove(priv->pproc_pwbc); } out: if (priv->rtc) rtc_device_unregister(priv->rtc); kfree(priv); return ret; } static int nvt_rtc_remove(struct platform_device *pdev) { struct nvt_rtc_priv *priv = platform_get_drvdata(pdev); if (priv->pproc_data) { proc_remove(priv->pproc_data); } if (priv->pproc_pwbc) { proc_remove(priv->pproc_pwbc); } rtc_device_unregister(priv->rtc); kfree(priv); return 0; } #ifdef CONFIG_OF static const struct of_device_id nvt_rtc_of_dt_ids[] = { { .compatible = "nvt,nvt_rtc", }, {}, }; #endif static struct platform_driver nvt_rtc_platform_driver = { .driver = { .name = "nvt_rtc", .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = nvt_rtc_of_dt_ids, #endif }, .probe = nvt_rtc_probe, .remove = nvt_rtc_remove, }; static int __init nvt_rtc_init(void) { int ret; ret = platform_driver_register(&nvt_rtc_platform_driver); return ret; } static void __exit nvt_rtc_exit(void) { platform_driver_unregister(&nvt_rtc_platform_driver); } MODULE_AUTHOR("Novatek"); MODULE_DESCRIPTION("nvt RTC driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); MODULE_ALIAS("platform:rtc-nvt"); module_init(nvt_rtc_init); module_exit(nvt_rtc_exit);