summaryrefslogtreecommitdiff
path: root/drivers/rtc/rtc-rv8803.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc/rtc-rv8803.c')
-rw-r--r--drivers/rtc/rtc-rv8803.c209
1 files changed, 172 insertions, 37 deletions
diff --git a/drivers/rtc/rtc-rv8803.c b/drivers/rtc/rtc-rv8803.c
index f69e0b1137cd..1327251e527c 100644
--- a/drivers/rtc/rtc-rv8803.c
+++ b/drivers/rtc/rtc-rv8803.c
@@ -9,13 +9,15 @@
#include <linux/bcd.h>
#include <linux/bitops.h>
+#include <linux/bitfield.h>
#include <linux/log2.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/of.h>
#include <linux/rtc.h>
+#include <linux/pm_wakeirq.h>
#define RV8803_I2C_TRY_COUNT 4
@@ -33,6 +35,7 @@
#define RV8803_EXT 0x0D
#define RV8803_FLAG 0x0E
#define RV8803_CTRL 0x0F
+#define RV8803_OSC_OFFSET 0x2C
#define RV8803_EXT_WADA BIT(6)
@@ -49,12 +52,15 @@
#define RV8803_CTRL_TIE BIT(4)
#define RV8803_CTRL_UIE BIT(5)
+#define RX8803_CTRL_CSEL GENMASK(7, 6)
+
#define RX8900_BACKUP_CTRL 0x18
#define RX8900_FLAG_SWOFF BIT(2)
#define RX8900_FLAG_VDETOFF BIT(3)
enum rv8803_type {
rv_8803,
+ rx_8803,
rx_8804,
rx_8900
};
@@ -64,6 +70,8 @@ struct rv8803_data {
struct rtc_device *rtc;
struct mutex flags_lock;
u8 ctrl;
+ u8 backup;
+ u8 alarm_invalid:1;
enum rv8803_type type;
};
@@ -136,6 +144,44 @@ static int rv8803_write_regs(const struct i2c_client *client,
return ret;
}
+static int rv8803_regs_init(struct rv8803_data *rv8803)
+{
+ int ret;
+
+ ret = rv8803_write_reg(rv8803->client, RV8803_OSC_OFFSET, 0x00);
+ if (ret)
+ return ret;
+
+ ret = rv8803_write_reg(rv8803->client, RV8803_CTRL,
+ FIELD_PREP(RX8803_CTRL_CSEL, 1)); /* 2s */
+ if (ret)
+ return ret;
+
+ ret = rv8803_write_regs(rv8803->client, RV8803_ALARM_MIN, 3,
+ (u8[]){ 0, 0, 0 });
+ if (ret)
+ return ret;
+
+ return rv8803_write_reg(rv8803->client, RV8803_RAM, 0x00);
+}
+
+static int rv8803_regs_configure(struct rv8803_data *rv8803);
+
+static int rv8803_regs_reset(struct rv8803_data *rv8803, bool full)
+{
+ /*
+ * The RV-8803 resets all registers to POR defaults after voltage-loss,
+ * the Epson RTCs don't, so we manually reset the remainder here.
+ */
+ if (full || rv8803->type == rx_8803 || rv8803->type == rx_8900) {
+ int ret = rv8803_regs_init(rv8803);
+ if (ret)
+ return ret;
+ }
+
+ return rv8803_regs_configure(rv8803);
+}
+
static irqreturn_t rv8803_handle_irq(int irq, void *dev_id)
{
struct i2c_client *client = dev_id;
@@ -194,6 +240,11 @@ static int rv8803_get_time(struct device *dev, struct rtc_time *tm)
u8 *date = date1;
int ret, flags;
+ if (rv8803->alarm_invalid) {
+ dev_warn(dev, "Corruption detected, data may be invalid.\n");
+ return -EINVAL;
+ }
+
flags = rv8803_read_reg(rv8803->client, RV8803_FLAG);
if (flags < 0)
return flags;
@@ -269,6 +320,21 @@ static int rv8803_set_time(struct device *dev, struct rtc_time *tm)
return flags;
}
+ if ((flags & RV8803_FLAG_V2F) || rv8803->alarm_invalid) {
+ /*
+ * If we sense corruption in the alarm registers, but see no
+ * voltage loss flag, we can't rely on other registers having
+ * sensible values. Reset them fully.
+ */
+ ret = rv8803_regs_reset(rv8803, rv8803->alarm_invalid);
+ if (ret) {
+ mutex_unlock(&rv8803->flags_lock);
+ return ret;
+ }
+
+ rv8803->alarm_invalid = false;
+ }
+
ret = rv8803_write_reg(rv8803->client, RV8803_FLAG,
flags & ~(RV8803_FLAG_V1F | RV8803_FLAG_V2F));
@@ -292,15 +358,33 @@ static int rv8803_get_alarm(struct device *dev, struct rtc_wkalrm *alrm)
if (flags < 0)
return flags;
+ alarmvals[0] &= 0x7f;
+ alarmvals[1] &= 0x3f;
+ alarmvals[2] &= 0x3f;
+
+ if (!bcd_is_valid(alarmvals[0]) ||
+ !bcd_is_valid(alarmvals[1]) ||
+ !bcd_is_valid(alarmvals[2]))
+ goto err_invalid;
+
alrm->time.tm_sec = 0;
- alrm->time.tm_min = bcd2bin(alarmvals[0] & 0x7f);
- alrm->time.tm_hour = bcd2bin(alarmvals[1] & 0x3f);
- alrm->time.tm_mday = bcd2bin(alarmvals[2] & 0x3f);
+ alrm->time.tm_min = bcd2bin(alarmvals[0]);
+ alrm->time.tm_hour = bcd2bin(alarmvals[1]);
+ alrm->time.tm_mday = bcd2bin(alarmvals[2]);
alrm->enabled = !!(rv8803->ctrl & RV8803_CTRL_AIE);
alrm->pending = (flags & RV8803_FLAG_AF) && alrm->enabled;
+ if ((unsigned int)alrm->time.tm_mday > 31 ||
+ (unsigned int)alrm->time.tm_hour >= 24 ||
+ (unsigned int)alrm->time.tm_min >= 60)
+ goto err_invalid;
+
return 0;
+
+err_invalid:
+ rv8803->alarm_invalid = true;
+ return -EINVAL;
}
static int rv8803_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
@@ -498,20 +582,64 @@ static int rx8900_trickle_charger_init(struct rv8803_data *rv8803)
if (err < 0)
return err;
- flags = ~(RX8900_FLAG_VDETOFF | RX8900_FLAG_SWOFF) & (u8)err;
-
- if (of_property_read_bool(node, "epson,vdet-disable"))
- flags |= RX8900_FLAG_VDETOFF;
-
- if (of_property_read_bool(node, "trickle-diode-disable"))
- flags |= RX8900_FLAG_SWOFF;
+ flags = (u8)err;
+ flags &= ~(RX8900_FLAG_VDETOFF | RX8900_FLAG_SWOFF);
+ flags |= rv8803->backup;
return i2c_smbus_write_byte_data(rv8803->client, RX8900_BACKUP_CTRL,
flags);
}
-static int rv8803_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+/* configure registers with values different than the Power-On reset defaults */
+static int rv8803_regs_configure(struct rv8803_data *rv8803)
+{
+ int err;
+
+ err = rv8803_write_reg(rv8803->client, RV8803_EXT, RV8803_EXT_WADA);
+ if (err)
+ return err;
+
+ err = rx8900_trickle_charger_init(rv8803);
+ if (err) {
+ dev_err(&rv8803->client->dev, "failed to init charger\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int rv8803_resume(struct device *dev)
+{
+ struct rv8803_data *rv8803 = dev_get_drvdata(dev);
+
+ if (rv8803->client->irq > 0 && device_may_wakeup(dev))
+ disable_irq_wake(rv8803->client->irq);
+
+ return 0;
+}
+
+static int rv8803_suspend(struct device *dev)
+{
+ struct rv8803_data *rv8803 = dev_get_drvdata(dev);
+
+ if (rv8803->client->irq > 0 && device_may_wakeup(dev))
+ enable_irq_wake(rv8803->client->irq);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(rv8803_pm_ops, rv8803_suspend, rv8803_resume);
+
+static const struct i2c_device_id rv8803_id[] = {
+ { "rv8803", rv_8803 },
+ { "rv8804", rx_8804 },
+ { "rx8803", rx_8803 },
+ { "rx8900", rx_8900 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, rv8803_id);
+
+static int rv8803_probe(struct i2c_client *client)
{
struct i2c_adapter *adapter = client->adapter;
struct rv8803_data *rv8803;
@@ -539,11 +667,13 @@ static int rv8803_probe(struct i2c_client *client,
mutex_init(&rv8803->flags_lock);
rv8803->client = client;
- if (client->dev.of_node)
- rv8803->type = (enum rv8803_type)
- of_device_get_match_data(&client->dev);
- else
+ if (client->dev.of_node) {
+ rv8803->type = (uintptr_t)of_device_get_match_data(&client->dev);
+ } else {
+ const struct i2c_device_id *id = i2c_match_id(rv8803_id, client);
+
rv8803->type = id->driver_data;
+ }
i2c_set_clientdata(client, rv8803);
flags = rv8803_read_reg(client, RV8803_FLAG);
@@ -564,27 +694,40 @@ static int rv8803_probe(struct i2c_client *client,
return PTR_ERR(rv8803->rtc);
if (client->irq > 0) {
+ unsigned long irqflags = IRQF_TRIGGER_LOW;
+
+ if (dev_fwnode(&client->dev))
+ irqflags = 0;
+
err = devm_request_threaded_irq(&client->dev, client->irq,
NULL, rv8803_handle_irq,
- IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ irqflags | IRQF_ONESHOT,
"rv8803", client);
if (err) {
dev_warn(&client->dev, "unable to request IRQ, alarms disabled\n");
client->irq = 0;
+ } else {
+ device_init_wakeup(&client->dev, true);
+ err = dev_pm_set_wake_irq(&client->dev, client->irq);
+ if (err)
+ dev_err(&client->dev, "failed to set wake IRQ\n");
}
+ } else {
+ if (device_property_read_bool(&client->dev, "wakeup-source"))
+ device_init_wakeup(&client->dev, true);
+ else
+ clear_bit(RTC_FEATURE_ALARM, rv8803->rtc->features);
}
- if (!client->irq)
- clear_bit(RTC_FEATURE_ALARM, rv8803->rtc->features);
- err = rv8803_write_reg(rv8803->client, RV8803_EXT, RV8803_EXT_WADA);
- if (err)
- return err;
+ if (of_property_read_bool(client->dev.of_node, "epson,vdet-disable"))
+ rv8803->backup |= RX8900_FLAG_VDETOFF;
- err = rx8900_trickle_charger_init(rv8803);
- if (err) {
- dev_err(&client->dev, "failed to init charger\n");
+ if (of_property_read_bool(client->dev.of_node, "trickle-diode-disable"))
+ rv8803->backup |= RX8900_FLAG_SWOFF;
+
+ err = rv8803_regs_configure(rv8803);
+ if (err)
return err;
- }
rv8803->rtc->ops = &rv8803_rtc_ops;
rv8803->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
@@ -600,15 +743,6 @@ static int rv8803_probe(struct i2c_client *client,
return 0;
}
-static const struct i2c_device_id rv8803_id[] = {
- { "rv8803", rv_8803 },
- { "rv8804", rx_8804 },
- { "rx8803", rv_8803 },
- { "rx8900", rx_8900 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, rv8803_id);
-
static const __maybe_unused struct of_device_id rv8803_of_match[] = {
{
.compatible = "microcrystal,rv8803",
@@ -616,7 +750,7 @@ static const __maybe_unused struct of_device_id rv8803_of_match[] = {
},
{
.compatible = "epson,rx8803",
- .data = (void *)rv_8803
+ .data = (void *)rx_8803
},
{
.compatible = "epson,rx8804",
@@ -634,6 +768,7 @@ static struct i2c_driver rv8803_driver = {
.driver = {
.name = "rtc-rv8803",
.of_match_table = of_match_ptr(rv8803_of_match),
+ .pm = &rv8803_pm_ops,
},
.probe = rv8803_probe,
.id_table = rv8803_id,