diff options
Diffstat (limited to 'drivers/rtc/rtc-ab-b5ze-s3.c')
-rw-r--r-- | drivers/rtc/rtc-ab-b5ze-s3.c | 291 |
1 files changed, 262 insertions, 29 deletions
diff --git a/drivers/rtc/rtc-ab-b5ze-s3.c b/drivers/rtc/rtc-ab-b5ze-s3.c index bbbf06f55e17..cfc2ef98d393 100644 --- a/drivers/rtc/rtc-ab-b5ze-s3.c +++ b/drivers/rtc/rtc-ab-b5ze-s3.c @@ -133,6 +133,7 @@ struct abb5zes3_rtc_data { int irq; bool battery_low; + bool timer_alarm; /* current alarm is via timer A */ }; /* @@ -192,6 +193,22 @@ static int _abb5zes3_rtc_update_alarm(struct device *dev, bool enable) return ret; } +/* Enable or disable timer (watchdog timer A interrupt generation) */ +static int _abb5zes3_rtc_update_timer(struct device *dev, bool enable) +{ + struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(data->regmap, ABB5ZES3_REG_CTRL2, + ABB5ZES3_REG_CTRL2_WTAIE, + enable ? ABB5ZES3_REG_CTRL2_WTAIE : 0); + if (ret) + dev_err(dev, "%s: writing timer INT failed (%d)\n", + __func__, ret); + + return ret; +} + /* * Note: we only read, so regmap inner lock protection is sufficient, i.e. * we do not need driver's main lock protection. @@ -277,7 +294,92 @@ static int abb5zes3_rtc_set_time(struct device *dev, struct rtc_time *tm) return ret; } -static int abb5zes3_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +/* + * Set provided TAQ and Timer A registers (TIMA_CLK and TIMA) based on + * given number of seconds. + */ +static inline void sec_to_timer_a(u8 secs, u8 *taq, u8 *timer_a) +{ + *taq = ABB5ZES3_REG_TIMA_CLK_TAQ1; /* 1Hz */ + *timer_a = secs; +} + +/* + * Return current number of seconds in Timer A. As we only use + * timer A with a 1Hz freq, this is what we expect to have. + */ +static inline int sec_from_timer_a(u8 *secs, u8 taq, u8 timer_a) +{ + if (taq != ABB5ZES3_REG_TIMA_CLK_TAQ1) /* 1Hz */ + return -EINVAL; + + *secs = timer_a; + + return 0; +} + +/* + * Read alarm currently configured via a watchdog timer using timer A. This + * is done by reading current RTC time and adding remaining timer time. + */ +static int _abb5zes3_rtc_read_timer(struct device *dev, + struct rtc_wkalrm *alarm) +{ + struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); + struct rtc_time rtc_tm, *alarm_tm = &alarm->time; + u8 regs[ABB5ZES3_TIMA_SEC_LEN + 1]; + unsigned long rtc_secs; + unsigned int reg; + u8 timer_secs; + int ret; + + /* + * Instead of doing two separate calls, because they are consecutive, + * we grab both clockout register and Timer A section. The latter is + * used to decide if timer A is enabled (as a watchdog timer). + */ + ret = regmap_bulk_read(data->regmap, ABB5ZES3_REG_TIM_CLK, regs, + ABB5ZES3_TIMA_SEC_LEN + 1); + if (ret) { + dev_err(dev, "%s: reading Timer A section failed (%d)\n", + __func__, ret); + goto err; + } + + /* get current time ... */ + ret = _abb5zes3_rtc_read_time(dev, &rtc_tm); + if (ret) + goto err; + + /* ... convert to seconds ... */ + ret = rtc_tm_to_time(&rtc_tm, &rtc_secs); + if (ret) + goto err; + + /* ... add remaining timer A time ... */ + ret = sec_from_timer_a(&timer_secs, regs[1], regs[2]); + if (ret) + goto err; + + /* ... and convert back. */ + rtc_time_to_tm(rtc_secs + timer_secs, alarm_tm); + + ret = regmap_read(data->regmap, ABB5ZES3_REG_CTRL2, ®); + if (ret) { + dev_err(dev, "%s: reading ctrl reg failed (%d)\n", + __func__, ret); + goto err; + } + + alarm->enabled = !!(reg & ABB5ZES3_REG_CTRL2_WTAIE); + +err: + return ret; +} + +/* Read alarm currently configured via a RTC alarm registers. */ +static int _abb5zes3_rtc_read_alarm(struct device *dev, + struct rtc_wkalrm *alarm) { struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); struct rtc_time rtc_tm, *alarm_tm = &alarm->time; @@ -286,7 +388,6 @@ static int abb5zes3_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) unsigned int reg; int ret; - mutex_lock(&data->lock); ret = regmap_bulk_read(data->regmap, ABB5ZES3_REG_ALRM_MN, regs, ABB5ZES3_ALRM_SEC_LEN); if (ret) { @@ -340,13 +441,39 @@ static int abb5zes3_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) alarm->enabled = !!(reg & ABB5ZES3_REG_CTRL1_AIE); err: + return ret; +} + +/* + * As the Alarm mechanism supported by the chip is only accurate to the + * minute, we use the watchdog timer mechanism provided by timer A + * (up to 256 seconds w/ a second accuracy) for low alarm values (below + * 4 minutes). Otherwise, we use the common alarm mechanism provided + * by the chip. In order for that to work, we keep track of currently + * configured timer type via 'timer_alarm' flag in our private data + * structure. + */ +static int abb5zes3_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); + int ret; + + mutex_lock(&data->lock); + if (data->timer_alarm) + ret = _abb5zes3_rtc_read_timer(dev, alarm); + else + ret = _abb5zes3_rtc_read_alarm(dev, alarm); mutex_unlock(&data->lock); return ret; } -/* ALARM is only accurate to the minute (not the second) */ -static int abb5zes3_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +/* + * Set alarm using chip alarm mechanism. It is only accurate to the + * minute (not the second). The function expects alarm interrupt to + * be disabled. + */ +static int _abb5zes3_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) { struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); struct rtc_time *alarm_tm = &alarm->time; @@ -355,7 +482,6 @@ static int abb5zes3_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) struct rtc_time rtc_tm; int ret, enable = 1; - mutex_lock(&data->lock); ret = _abb5zes3_rtc_read_time(dev, &rtc_tm); if (ret) goto err; @@ -397,18 +523,13 @@ static int abb5zes3_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) } } - /* Disable the alarm before modifying it */ - ret = _abb5zes3_rtc_update_alarm(dev, 0); - if (ret < 0) { - dev_err(dev, "%s: unable to disable the alarm (%d)\n", - __func__, ret); - goto err; - } - - /* Program alarm registers */ - regs[0] = bin2bcd(alarm_tm->tm_min) & 0x7f; /* minute */ - regs[1] = bin2bcd(alarm_tm->tm_hour) & 0x3f; /* hour */ - regs[2] = bin2bcd(alarm_tm->tm_mday) & 0x3f; /* day of the month */ + /* + * Program all alarm registers but DW one. For each register, setting + * MSB to 0 enables associated alarm. + */ + regs[0] = bin2bcd(alarm_tm->tm_min) & 0x7f; + regs[1] = bin2bcd(alarm_tm->tm_hour) & 0x3f; + regs[2] = bin2bcd(alarm_tm->tm_mday) & 0x3f; regs[3] = ABB5ZES3_REG_ALRM_DW_AE; /* do not match day of the week */ ret = regmap_bulk_write(data->regmap, ABB5ZES3_REG_ALRM_MN, regs, @@ -419,15 +540,115 @@ static int abb5zes3_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) goto err; } - /* Enable or disable alarm */ + /* Record currently configured alarm is not a timer */ + data->timer_alarm = 0; + + /* Enable or disable alarm interrupt generation */ ret = _abb5zes3_rtc_update_alarm(dev, enable); err: - mutex_unlock(&data->lock); + return ret; +} + +/* + * Set alarm using timer watchdog (via timer A) mechanism. The function expects + * timer A interrupt to be disabled. + */ +static int _abb5zes3_rtc_set_timer(struct device *dev, struct rtc_wkalrm *alarm, + u8 secs) +{ + struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); + u8 regs[ABB5ZES3_TIMA_SEC_LEN]; + u8 mask = ABB5ZES3_REG_TIM_CLK_TAC0 | ABB5ZES3_REG_TIM_CLK_TAC1; + int ret = 0; + + /* Program given number of seconds to Timer A registers */ + sec_to_timer_a(secs, ®s[0], ®s[1]); + ret = regmap_bulk_write(data->regmap, ABB5ZES3_REG_TIMA_CLK, regs, + ABB5ZES3_TIMA_SEC_LEN); + if (ret < 0) { + dev_err(dev, "%s: writing timer section failed\n", __func__); + goto err; + } + + /* Configure Timer A as a watchdog timer */ + ret = regmap_update_bits(data->regmap, ABB5ZES3_REG_TIM_CLK, + mask, ABB5ZES3_REG_TIM_CLK_TAC1); + if (ret) + dev_err(dev, "%s: failed to update timer\n", __func__); + + /* Record currently configured alarm is a timer */ + data->timer_alarm = 1; + /* Enable or disable timer interrupt generation */ + ret = _abb5zes3_rtc_update_timer(dev, alarm->enabled); + +err: return ret; } +/* + * The chip has an alarm which is only accurate to the minute. In order to + * handle alarms below that limit, we use the watchdog timer function of + * timer A. More precisely, the timer method is used for alarms below 240 + * seconds. + */ +static int abb5zes3_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); + struct rtc_time *alarm_tm = &alarm->time; + unsigned long rtc_secs, alarm_secs; + struct rtc_time rtc_tm; + int ret; + + mutex_lock(&data->lock); + ret = _abb5zes3_rtc_read_time(dev, &rtc_tm); + if (ret) + goto err; + + ret = rtc_tm_to_time(&rtc_tm, &rtc_secs); + if (ret) + goto err; + + ret = rtc_tm_to_time(alarm_tm, &alarm_secs); + if (ret) + goto err; + + /* Let's first disable both the alarm and the timer interrupts */ + ret = _abb5zes3_rtc_update_alarm(dev, false); + if (ret < 0) { + dev_err(dev, "%s: unable to disable alarm (%d)\n", __func__, + ret); + goto err; + } + ret = _abb5zes3_rtc_update_timer(dev, false); + if (ret < 0) { + dev_err(dev, "%s: unable to disable timer (%d)\n", __func__, + ret); + goto err; + } + + data->timer_alarm = 0; + + /* + * Let's now configure the alarm; if we are expected to ring in + * more than 240s, then we setup an alarm. Otherwise, a timer. + */ + if ((alarm_secs > rtc_secs) && ((alarm_secs - rtc_secs) <= 240)) + ret = _abb5zes3_rtc_set_timer(dev, alarm, + alarm_secs - rtc_secs); + else + ret = _abb5zes3_rtc_set_alarm(dev, alarm); + + err: + mutex_unlock(&data->lock); + + if (ret) + dev_err(dev, "%s: unable to configure alarm (%d)\n", __func__, + ret); + + return ret; + } /* Enable or disable battery low irq generation */ static inline int _abb5zes3_rtc_battery_low_irq_enable(struct regmap *regmap, @@ -446,7 +667,7 @@ static inline int _abb5zes3_rtc_battery_low_irq_enable(struct regmap *regmap, static int abb5zes3_rtc_check_setup(struct device *dev) { struct abb5zes3_rtc_data *data = dev_get_drvdata(dev); - struct regmap *regmap = data->regmap; + struct regmap *regmap = data->regmap; unsigned int reg; int ret; u8 mask; @@ -579,7 +800,10 @@ static int abb5zes3_rtc_alarm_irq_enable(struct device *dev, if (rtc_data->irq) { mutex_lock(&rtc_data->lock); - ret = _abb5zes3_rtc_update_alarm(dev, enable); + if (rtc_data->timer_alarm) + ret = _abb5zes3_rtc_update_timer(dev, enable); + else + ret = _abb5zes3_rtc_update_alarm(dev, enable); mutex_unlock(&rtc_data->lock); } @@ -629,6 +853,23 @@ static irqreturn_t _abb5zes3_rtc_interrupt(int irq, void *data) handled = IRQ_HANDLED; } + /* Check watchdog Timer A flag */ + if (regs[ABB5ZES3_REG_CTRL2] & ABB5ZES3_REG_CTRL2_WTAF) { + dev_dbg(dev, "RTC timer!\n"); + + rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF); + + /* + * Acknowledge and disable the alarm. Note: WTAF + * flag had been cleared when reading CTRL2 + */ + _abb5zes3_rtc_update_timer(dev, 0); + + rtc_data->timer_alarm = 0; + + handled = IRQ_HANDLED; + } + return handled; } @@ -712,14 +953,6 @@ static int abb5zes3_probe(struct i2c_client *client, goto err; } - /* - * AB-B5Z5E only supports a coarse granularity alarm (one minute - * resolution up to one month) so we cannot support UIE mode - * using the device's alarm. Note it should be feasible to support - * such a feature using one of the two timers the device provides. - */ - data->rtc->uie_unsupported = 1; - /* Enable battery low detection interrupt if battery not already low */ if (!data->battery_low && data->irq) { ret = _abb5zes3_rtc_battery_low_irq_enable(regmap, true); |