Эх сурвалжийг харах

rtc: abx80x: add basic watchdog support

The abx804 and abx805 chips have support for a simple watchdog
function that can trigger an external reset.

Signed-off-by: Jeremy Gebben <jgebben@sweptlaser.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Jeremy Gebben 7 жил өмнө
parent
commit
749e36d0a0

+ 1 - 0
drivers/rtc/Kconfig

@@ -187,6 +187,7 @@ config RTC_DRV_ABB5ZES3
 
 
 config RTC_DRV_ABX80X
 config RTC_DRV_ABX80X
 	tristate "Abracon ABx80x"
 	tristate "Abracon ABx80x"
+	select WATCHDOG_CORE if WATCHDOG
 	help
 	help
 	  If you say yes here you get support for Abracon AB080X and AB180X
 	  If you say yes here you get support for Abracon AB080X and AB180X
 	  families of ultra-low-power  battery- and capacitor-backed real-time
 	  families of ultra-low-power  battery- and capacitor-backed real-time

+ 112 - 4
drivers/rtc/rtc-abx80x.c

@@ -17,6 +17,7 @@
 #include <linux/i2c.h>
 #include <linux/i2c.h>
 #include <linux/module.h>
 #include <linux/module.h>
 #include <linux/rtc.h>
 #include <linux/rtc.h>
+#include <linux/watchdog.h>
 
 
 #define ABX8XX_REG_HTH		0x00
 #define ABX8XX_REG_HTH		0x00
 #define ABX8XX_REG_SC		0x01
 #define ABX8XX_REG_SC		0x01
@@ -37,6 +38,7 @@
 
 
 #define ABX8XX_REG_STATUS	0x0f
 #define ABX8XX_REG_STATUS	0x0f
 #define ABX8XX_STATUS_AF	BIT(2)
 #define ABX8XX_STATUS_AF	BIT(2)
+#define ABX8XX_STATUS_WDT	BIT(6)
 
 
 #define ABX8XX_REG_CTRL1	0x10
 #define ABX8XX_REG_CTRL1	0x10
 #define ABX8XX_CTRL_WRITE	BIT(0)
 #define ABX8XX_CTRL_WRITE	BIT(0)
@@ -61,6 +63,14 @@
 #define ABX8XX_OSS_OF		BIT(1)
 #define ABX8XX_OSS_OF		BIT(1)
 #define ABX8XX_OSS_OMODE	BIT(4)
 #define ABX8XX_OSS_OMODE	BIT(4)
 
 
+#define ABX8XX_REG_WDT		0x1b
+#define ABX8XX_WDT_WDS		BIT(7)
+#define ABX8XX_WDT_BMB_MASK	0x7c
+#define ABX8XX_WDT_BMB_SHIFT	2
+#define ABX8XX_WDT_MAX_TIME	(ABX8XX_WDT_BMB_MASK >> ABX8XX_WDT_BMB_SHIFT)
+#define ABX8XX_WDT_WRB_MASK	0x03
+#define ABX8XX_WDT_WRB_1HZ	0x02
+
 #define ABX8XX_REG_CFG_KEY	0x1f
 #define ABX8XX_REG_CFG_KEY	0x1f
 #define ABX8XX_CFG_KEY_OSC	0xa1
 #define ABX8XX_CFG_KEY_OSC	0xa1
 #define ABX8XX_CFG_KEY_MISC	0x9d
 #define ABX8XX_CFG_KEY_MISC	0x9d
@@ -80,23 +90,25 @@ enum abx80x_chip {AB0801, AB0803, AB0804, AB0805,
 struct abx80x_cap {
 struct abx80x_cap {
 	u16 pn;
 	u16 pn;
 	bool has_tc;
 	bool has_tc;
+	bool has_wdog;
 };
 };
 
 
 static struct abx80x_cap abx80x_caps[] = {
 static struct abx80x_cap abx80x_caps[] = {
 	[AB0801] = {.pn = 0x0801},
 	[AB0801] = {.pn = 0x0801},
 	[AB0803] = {.pn = 0x0803},
 	[AB0803] = {.pn = 0x0803},
-	[AB0804] = {.pn = 0x0804, .has_tc = true},
-	[AB0805] = {.pn = 0x0805, .has_tc = true},
+	[AB0804] = {.pn = 0x0804, .has_tc = true, .has_wdog = true},
+	[AB0805] = {.pn = 0x0805, .has_tc = true, .has_wdog = true},
 	[AB1801] = {.pn = 0x1801},
 	[AB1801] = {.pn = 0x1801},
 	[AB1803] = {.pn = 0x1803},
 	[AB1803] = {.pn = 0x1803},
-	[AB1804] = {.pn = 0x1804, .has_tc = true},
-	[AB1805] = {.pn = 0x1805, .has_tc = true},
+	[AB1804] = {.pn = 0x1804, .has_tc = true, .has_wdog = true},
+	[AB1805] = {.pn = 0x1805, .has_tc = true, .has_wdog = true},
 	[ABX80X] = {.pn = 0}
 	[ABX80X] = {.pn = 0}
 };
 };
 
 
 struct abx80x_priv {
 struct abx80x_priv {
 	struct rtc_device *rtc;
 	struct rtc_device *rtc;
 	struct i2c_client *client;
 	struct i2c_client *client;
+	struct watchdog_device wdog;
 };
 };
 
 
 static int abx80x_is_rc_mode(struct i2c_client *client)
 static int abx80x_is_rc_mode(struct i2c_client *client)
@@ -234,6 +246,13 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id)
 	if (status & ABX8XX_STATUS_AF)
 	if (status & ABX8XX_STATUS_AF)
 		rtc_update_irq(rtc, 1, RTC_AF | RTC_IRQF);
 		rtc_update_irq(rtc, 1, RTC_AF | RTC_IRQF);
 
 
+	/*
+	 * It is unclear if we'll get an interrupt before the external
+	 * reset kicks in.
+	 */
+	if (status & ABX8XX_STATUS_WDT)
+		dev_alert(&client->dev, "watchdog timeout interrupt.\n");
+
 	i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, 0);
 	i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, 0);
 
 
 	return IRQ_HANDLED;
 	return IRQ_HANDLED;
@@ -535,6 +554,89 @@ static void rtc_calib_remove_sysfs_group(void *_dev)
 	sysfs_remove_group(&dev->kobj, &rtc_calib_attr_group);
 	sysfs_remove_group(&dev->kobj, &rtc_calib_attr_group);
 }
 }
 
 
+#ifdef CONFIG_WATCHDOG
+
+static inline u8 timeout_bits(unsigned int timeout)
+{
+	return ((timeout << ABX8XX_WDT_BMB_SHIFT) & ABX8XX_WDT_BMB_MASK) |
+		 ABX8XX_WDT_WRB_1HZ;
+}
+
+static int __abx80x_wdog_set_timeout(struct watchdog_device *wdog,
+				     unsigned int timeout)
+{
+	struct abx80x_priv *priv = watchdog_get_drvdata(wdog);
+	u8 val = ABX8XX_WDT_WDS | timeout_bits(timeout);
+
+	/*
+	 * Writing any timeout to the WDT register resets the watchdog timer.
+	 * Writing 0 disables it.
+	 */
+	return i2c_smbus_write_byte_data(priv->client, ABX8XX_REG_WDT, val);
+}
+
+static int abx80x_wdog_set_timeout(struct watchdog_device *wdog,
+				   unsigned int new_timeout)
+{
+	int err = 0;
+
+	if (watchdog_hw_running(wdog))
+		err = __abx80x_wdog_set_timeout(wdog, new_timeout);
+
+	if (err == 0)
+		wdog->timeout = new_timeout;
+
+	return err;
+}
+
+static int abx80x_wdog_ping(struct watchdog_device *wdog)
+{
+	return __abx80x_wdog_set_timeout(wdog, wdog->timeout);
+}
+
+static int abx80x_wdog_start(struct watchdog_device *wdog)
+{
+	return __abx80x_wdog_set_timeout(wdog, wdog->timeout);
+}
+
+static int abx80x_wdog_stop(struct watchdog_device *wdog)
+{
+	return __abx80x_wdog_set_timeout(wdog, 0);
+}
+
+static const struct watchdog_info abx80x_wdog_info = {
+	.identity = "abx80x watchdog",
+	.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops abx80x_wdog_ops = {
+	.owner = THIS_MODULE,
+	.start = abx80x_wdog_start,
+	.stop = abx80x_wdog_stop,
+	.ping = abx80x_wdog_ping,
+	.set_timeout = abx80x_wdog_set_timeout,
+};
+
+static int abx80x_setup_watchdog(struct abx80x_priv *priv)
+{
+	priv->wdog.parent = &priv->client->dev;
+	priv->wdog.ops = &abx80x_wdog_ops;
+	priv->wdog.info = &abx80x_wdog_info;
+	priv->wdog.min_timeout = 1;
+	priv->wdog.max_timeout = ABX8XX_WDT_MAX_TIME;
+	priv->wdog.timeout = ABX8XX_WDT_MAX_TIME;
+
+	watchdog_set_drvdata(&priv->wdog, priv);
+
+	return devm_watchdog_register_device(&priv->client->dev, &priv->wdog);
+}
+#else
+static int abx80x_setup_watchdog(struct abx80x_priv *priv)
+{
+	return 0;
+}
+#endif
+
 static int abx80x_probe(struct i2c_client *client,
 static int abx80x_probe(struct i2c_client *client,
 			const struct i2c_device_id *id)
 			const struct i2c_device_id *id)
 {
 {
@@ -629,6 +731,12 @@ static int abx80x_probe(struct i2c_client *client,
 
 
 	i2c_set_clientdata(client, priv);
 	i2c_set_clientdata(client, priv);
 
 
+	if (abx80x_caps[part].has_wdog) {
+		err = abx80x_setup_watchdog(priv);
+		if (err)
+			return err;
+	}
+
 	if (client->irq > 0) {
 	if (client->irq > 0) {
 		dev_info(&client->dev, "IRQ %d supplied\n", client->irq);
 		dev_info(&client->dev, "IRQ %d supplied\n", client->irq);
 		err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
 		err = devm_request_threaded_irq(&client->dev, client->irq, NULL,