Skip to contentSkip to conversation

rtc: Abracon AB-RTCMC-32.768kHz-B5ZE-S3 I2C RTC chip support

PATCH New Arnaud Ebalard 1 participant 4 messages Jan 15, 2015

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover rtc: Abracon AB-RTCMC-32.768kHz-B5ZE-S3 I2C RTC chip support

Hi Andrew,

I fixed unhandled error case for devm_request_threaded_irq() and came w/ a simple solution to get a single return path in probe function:

   err:
   	if (ret && data && data->irq)
   		device_init_wakeup(dev, false);
   	return ret;
   }

Changes since v0:
- fixed unhandled error case for devm_request_threaded_irq()
- enforced a common error return path in _probe()

For those who missed v0:

This series adds support for Abracon AB-RTCMC-32.768kHz-B5ZE-S3 I2C RTC chip. Unlike many RTC chips, it includes an internal oscillator which spares room on the PCB. It also has some interesting features, like battery low detection (which the driver in this series supports). The only small "limitation" (mainly due to what RTC subsystem expects from RTC chips) is the fact that its alarm is accurate to the second. This series provides a solution (described below) for that limitation using another mechanism of the chip.

I decided to split support between three different patches for this v0:

- Patch 1/3: it simply references Abracon Corporation in vendor-prefixes documentation file. As Abracon has no NASDAQ ticker symbol; I have decided to use "abcn" (I initially started my work w/ "ab" but later changed for "abcn" which looked more meaningful)
- Patch 2/3: it adds initial support for the chip and provides the ability to read/write time and also read/write alarm. As the alarm the chip provides is accurate to the minute, the support provided by this patch also has this limitation (e.g. UIE mode is not supported).
- Patch 3/3: the chip supports a watchdog timer which can be used to extend the alarm mechanism in patch 2/3 in order to provide support for alarms under one minute (e.g. support UIE mode). In practice, the logic I implemented is to use the watchdog timer for alarms which are at most 4 minutes in the future and use the common alarm mechanism for alarms which are set to larger values. With that additional patch the device fully passes the rtctest.c program.

I decided to split the driver between two patches (2 and 3 of 3) in order to ease review: patch 2 should be pretty straightforward to read for someone familiar w/ RTC subsystem. Patch 3 only extends what is in patch 2 regarding alarms. If you prefer a single patch in a v1, do not hesitate to ask.

The set currently applies on top of 3.19-rc4 but I can rebase it against something else if you prefer.

Arnaud Ebalard (3):
of: add vendor prefix for Abracon Corporation
rtc: Add support for Abracon AB-RTCMC-32.768kHz-B5ZE-S3 I2C RTC chip
rtc: rtc-ab-b5ze-s3: Add sub-minute alarm support

 .../devicetree/bindings/i2c/trivial-devices.txt    |    1 +
 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 drivers/rtc/Kconfig                                |   11 +
 drivers/rtc/Makefile                               |    1 +
 drivers/rtc/rtc-ab-b5ze-s3.c                       | 1035 ++++++++++++++++++++
 5 files changed, 1049 insertions(+)
 create mode 100644 drivers/rtc/rtc-ab-b5ze-s3.c
Signature · 1 line
2.1.1
#1 lore raw

PATCH 1/3 of: add vendor prefix for Abracon Corporation

---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)
Documentation/devicetree/bindings/vendor-prefixes.txt +1 -0
index b1df0ad1306c..8275258cc8c5 100644
@@ -4,6 +4,7 @@ This isn't an exhaustive list, but you should add new prefixes to it before
 using them to avoid name-space collisions.
 
 abilis	Abilis Systems
+abracon	Abracon Corporation
 active-semi	Active-Semi International Inc
 ad	Avionic Design GmbH
 adapteva	Adapteva, Inc.
Signature · 1 line
2.1.1
#2 lore raw

PATCH 2/3 rtc: Add support for Abracon AB-RTCMC-32.768kHz-B5ZE-S3 I2C RTC chip

In reply to Arnaud Ebalard

This patch adds support for Abracon AB-RTCMC-32.768kHz-B5ZE-S3 RTC/Calendar module w/ I2C interface.

This supports includes RTC time reading and setting, Alarm (1 minute accuracy) reading and setting, and battery low detection. The device also supports frequency adjustment and two timers but those features are currently not implemented in this driver. Due to alarm accuracy limitation (and current lack of timer support in the driver), UIE mode is not supported.

---
 .../devicetree/bindings/i2c/trivial-devices.txt    |   1 +
 drivers/rtc/Kconfig                                |  11 +
 drivers/rtc/Makefile                               |   1 +
 drivers/rtc/rtc-ab-b5ze-s3.c                       | 802 +++++++++++++++++++++
 4 files changed, 815 insertions(+)
 create mode 100644 drivers/rtc/rtc-ab-b5ze-s3.c
Documentation/devicetree/bindings/i2c/trivial-devices.txt +1 -0
index 9f4e3824e71e..7b2923bc52b8 100644
@@ -9,6 +9,7 @@ document for it just like any other devices.
 
 Compatible		Vendor / Chip
 ==========		=============
+abracon,abb5zes3		AB-RTCMC-32.768kHz-B5ZE-S3: Real Time Clock/Calendar Module with I2C Interface
 ad,ad7414		SMBus/I2C Digital Temperature Sensor in 6-Pin SOT with SMBus Alert and Over Temperature Pin
 ad,adm9240		ADM9240:  Complete System Hardware Monitor for uProcessor-Based Systems
 adi,adt7461		+/-1C TDM Extended Temp Range I.C
drivers/rtc/Kconfig +11 -0
index f15cddfeb897..1b19f327f35f 100644
@@ -153,6 +153,17 @@ config RTC_DRV_88PM80X
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-88pm80x.
 
+config RTC_DRV_ABB5ZES3
+       depends on I2C
+       select REGMAP_I2C
+       tristate "Abracon AB-RTCMC-32.768kHz-B5ZE-S3"
+       help
+	  If you say yes here you get support for the Abracon
+	  AB-RTCMC-32.768kHz-B5ZE-S3 I2C RTC chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-ab-b5ze-s3.
+
 config RTC_DRV_AS3722
 	tristate "ams AS3722 RTC driver"
 	depends on MFD_AS3722
drivers/rtc/Makefile +1 -0
index c8ef3e1e6ccd..855c4e364058 100644
@@ -24,6 +24,7 @@ obj-$(CONFIG_RTC_DRV_88PM860X)  += rtc-88pm860x.o
 obj-$(CONFIG_RTC_DRV_88PM80X)	+= rtc-88pm80x.o
 obj-$(CONFIG_RTC_DRV_AB3100)	+= rtc-ab3100.o
 obj-$(CONFIG_RTC_DRV_AB8500)	+= rtc-ab8500.o
+obj-$(CONFIG_RTC_DRV_ABB5ZES3)	+= rtc-ab-b5ze-s3.o
 obj-$(CONFIG_RTC_DRV_AS3722)	+= rtc-as3722.o
 obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o
 obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
drivers/rtc/rtc-ab-b5ze-s3.c new file +802 -0

802 lines not on this page · raw message

Signature · 1 line
2.1.1
#3 lore raw

PATCH 3/3 rtc: rtc-ab-b5ze-s3: Add sub-minute alarm support

In reply to Arnaud Ebalard

Abracon AB-RTCMC-32.768kHz-B5ZE-S3 alarm is only accurate to the minute. For that reason, UIE mode is currently not supported by the driver. But the device provides a watchdog timer which can be coupled with the alarm mechanism to extend support and provide sub-minute alarm capability.

This patch implements that extension. More precisely, it makes use of the watchdog timer for alarms which are less that four minutes in the future (with second accuracy) and use standard alarm mechanism for other alarms (with minute accuracy).

---
 drivers/rtc/rtc-ab-b5ze-s3.c | 291 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 262 insertions(+), 29 deletions(-)
drivers/rtc/rtc-ab-b5ze-s3.c +262 -29
index bbbf06f55e17..cfc2ef98d393 100644
@@ -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, &reg);
+	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, &regs[0], &regs[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);
Signature · 1 line
2.1.1