summaryrefslogtreecommitdiffstats
path: root/drivers/rtc/rtc-pcf2127.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc/rtc-pcf2127.c')
-rw-r--r--drivers/rtc/rtc-pcf2127.c192
1 files changed, 133 insertions, 59 deletions
diff --git a/drivers/rtc/rtc-pcf2127.c b/drivers/rtc/rtc-pcf2127.c
index 48ce1e85deb1..56c58b055dff 100644
--- a/drivers/rtc/rtc-pcf2127.c
+++ b/drivers/rtc/rtc-pcf2127.c
@@ -94,10 +94,20 @@
#define PCF2127_WD_VAL_MAX 255
#define PCF2127_WD_VAL_DEFAULT 60
+/* Mask for currently enabled interrupts */
+#define PCF2127_CTRL1_IRQ_MASK (PCF2127_BIT_CTRL1_TSF1)
+#define PCF2127_CTRL2_IRQ_MASK ( \
+ PCF2127_BIT_CTRL2_AF | \
+ PCF2127_BIT_CTRL2_WDTF | \
+ PCF2127_BIT_CTRL2_TSF2)
+
struct pcf2127 {
struct rtc_device *rtc;
struct watchdog_device wdd;
struct regmap *regmap;
+ time64_t ts;
+ bool ts_valid;
+ bool irq_enabled;
};
/*
@@ -434,23 +444,96 @@ static int pcf2127_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
return pcf2127_rtc_alarm_irq_enable(dev, alrm->enabled);
}
+/*
+ * This function reads ctrl2 register, caller is responsible for calling
+ * pcf2127_wdt_active_ping()
+ */
+static int pcf2127_rtc_ts_read(struct device *dev, time64_t *ts)
+{
+ struct pcf2127 *pcf2127 = dev_get_drvdata(dev);
+ struct rtc_time tm;
+ int ret;
+ unsigned char data[25];
+
+ ret = regmap_bulk_read(pcf2127->regmap, PCF2127_REG_CTRL1, data,
+ sizeof(data));
+ if (ret) {
+ dev_err(dev, "%s: read error ret=%d\n", __func__, ret);
+ return ret;
+ }
+
+ dev_dbg(dev,
+ "%s: raw data is cr1=%02x, cr2=%02x, cr3=%02x, ts_sc=%02x, ts_mn=%02x, ts_hr=%02x, ts_dm=%02x, ts_mo=%02x, ts_yr=%02x\n",
+ __func__, data[PCF2127_REG_CTRL1], data[PCF2127_REG_CTRL2],
+ data[PCF2127_REG_CTRL3], data[PCF2127_REG_TS_SC],
+ data[PCF2127_REG_TS_MN], data[PCF2127_REG_TS_HR],
+ data[PCF2127_REG_TS_DM], data[PCF2127_REG_TS_MO],
+ data[PCF2127_REG_TS_YR]);
+
+ tm.tm_sec = bcd2bin(data[PCF2127_REG_TS_SC] & 0x7F);
+ tm.tm_min = bcd2bin(data[PCF2127_REG_TS_MN] & 0x7F);
+ tm.tm_hour = bcd2bin(data[PCF2127_REG_TS_HR] & 0x3F);
+ tm.tm_mday = bcd2bin(data[PCF2127_REG_TS_DM] & 0x3F);
+ /* TS_MO register (month) value range: 1-12 */
+ tm.tm_mon = bcd2bin(data[PCF2127_REG_TS_MO] & 0x1F) - 1;
+ tm.tm_year = bcd2bin(data[PCF2127_REG_TS_YR]);
+ if (tm.tm_year < 70)
+ tm.tm_year += 100; /* assume we are in 1970...2069 */
+
+ ret = rtc_valid_tm(&tm);
+ if (ret) {
+ dev_err(dev, "Invalid timestamp. ret=%d\n", ret);
+ return ret;
+ }
+
+ *ts = rtc_tm_to_time64(&tm);
+ return 0;
+};
+
+static void pcf2127_rtc_ts_snapshot(struct device *dev)
+{
+ struct pcf2127 *pcf2127 = dev_get_drvdata(dev);
+ int ret;
+
+ /* Let userspace read the first timestamp */
+ if (pcf2127->ts_valid)
+ return;
+
+ ret = pcf2127_rtc_ts_read(dev, &pcf2127->ts);
+ if (!ret)
+ pcf2127->ts_valid = true;
+}
+
static irqreturn_t pcf2127_rtc_irq(int irq, void *dev)
{
struct pcf2127 *pcf2127 = dev_get_drvdata(dev);
- unsigned int ctrl2 = 0;
+ unsigned int ctrl1, ctrl2;
int ret = 0;
+ ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL1, &ctrl1);
+ if (ret)
+ return IRQ_NONE;
+
ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL2, &ctrl2);
if (ret)
return IRQ_NONE;
- if (!(ctrl2 & PCF2127_BIT_CTRL2_AF))
+ if (!(ctrl1 & PCF2127_CTRL1_IRQ_MASK || ctrl2 & PCF2127_CTRL2_IRQ_MASK))
return IRQ_NONE;
- regmap_write(pcf2127->regmap, PCF2127_REG_CTRL2,
- ctrl2 & ~(PCF2127_BIT_CTRL2_AF | PCF2127_BIT_CTRL2_WDTF));
+ if (ctrl1 & PCF2127_BIT_CTRL1_TSF1 || ctrl2 & PCF2127_BIT_CTRL2_TSF2)
+ pcf2127_rtc_ts_snapshot(dev);
+
+ if (ctrl1 & PCF2127_CTRL1_IRQ_MASK)
+ regmap_write(pcf2127->regmap, PCF2127_REG_CTRL1,
+ ctrl1 & ~PCF2127_CTRL1_IRQ_MASK);
+
+ if (ctrl2 & PCF2127_CTRL2_IRQ_MASK)
+ regmap_write(pcf2127->regmap, PCF2127_REG_CTRL2,
+ ctrl2 & ~PCF2127_CTRL2_IRQ_MASK);
- rtc_update_irq(pcf2127->rtc, 1, RTC_IRQF | RTC_AF);
+ if (ctrl2 & PCF2127_BIT_CTRL2_AF)
+ rtc_update_irq(pcf2127->rtc, 1, RTC_IRQF | RTC_AF);
pcf2127_wdt_active_ping(&pcf2127->wdd);
@@ -475,23 +558,27 @@ static ssize_t timestamp0_store(struct device *dev,
struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent);
int ret;
- ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL1,
- PCF2127_BIT_CTRL1_TSF1, 0);
- if (ret) {
- dev_err(dev, "%s: update ctrl1 ret=%d\n", __func__, ret);
- return ret;
- }
+ if (pcf2127->irq_enabled) {
+ pcf2127->ts_valid = false;
+ } else {
+ ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL1,
+ PCF2127_BIT_CTRL1_TSF1, 0);
+ if (ret) {
+ dev_err(dev, "%s: update ctrl1 ret=%d\n", __func__, ret);
+ return ret;
+ }
- ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2,
- PCF2127_BIT_CTRL2_TSF2, 0);
- if (ret) {
- dev_err(dev, "%s: update ctrl2 ret=%d\n", __func__, ret);
- return ret;
- }
+ ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2,
+ PCF2127_BIT_CTRL2_TSF2, 0);
+ if (ret) {
+ dev_err(dev, "%s: update ctrl2 ret=%d\n", __func__, ret);
+ return ret;
+ }
- ret = pcf2127_wdt_active_ping(&pcf2127->wdd);
- if (ret)
- return ret;
+ ret = pcf2127_wdt_active_ping(&pcf2127->wdd);
+ if (ret)
+ return ret;
+ }
return count;
};
@@ -500,50 +587,36 @@ static ssize_t timestamp0_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent);
- struct rtc_time tm;
+ unsigned int ctrl1, ctrl2;
int ret;
- unsigned char data[25];
-
- ret = regmap_bulk_read(pcf2127->regmap, PCF2127_REG_CTRL1, data,
- sizeof(data));
- if (ret) {
- dev_err(dev, "%s: read error ret=%d\n", __func__, ret);
- return ret;
- }
-
- dev_dbg(dev,
- "%s: raw data is cr1=%02x, cr2=%02x, cr3=%02x, ts_sc=%02x, "
- "ts_mn=%02x, ts_hr=%02x, ts_dm=%02x, ts_mo=%02x, ts_yr=%02x\n",
- __func__, data[PCF2127_REG_CTRL1], data[PCF2127_REG_CTRL2],
- data[PCF2127_REG_CTRL3], data[PCF2127_REG_TS_SC],
- data[PCF2127_REG_TS_MN], data[PCF2127_REG_TS_HR],
- data[PCF2127_REG_TS_DM], data[PCF2127_REG_TS_MO],
- data[PCF2127_REG_TS_YR]);
+ time64_t ts;
+
+ if (pcf2127->irq_enabled) {
+ if (!pcf2127->ts_valid)
+ return 0;
+ ts = pcf2127->ts;
+ } else {
+ ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL1, &ctrl1);
+ if (ret)
+ return 0;
- ret = pcf2127_wdt_active_ping(&pcf2127->wdd);
- if (ret)
- return ret;
+ ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL2, &ctrl2);
+ if (ret)
+ return 0;
- if (!(data[PCF2127_REG_CTRL1] & PCF2127_BIT_CTRL1_TSF1) &&
- !(data[PCF2127_REG_CTRL2] & PCF2127_BIT_CTRL2_TSF2))
- return 0;
+ if (!(ctrl1 & PCF2127_BIT_CTRL1_TSF1) &&
+ !(ctrl2 & PCF2127_BIT_CTRL2_TSF2))
+ return 0;
- tm.tm_sec = bcd2bin(data[PCF2127_REG_TS_SC] & 0x7F);
- tm.tm_min = bcd2bin(data[PCF2127_REG_TS_MN] & 0x7F);
- tm.tm_hour = bcd2bin(data[PCF2127_REG_TS_HR] & 0x3F);
- tm.tm_mday = bcd2bin(data[PCF2127_REG_TS_DM] & 0x3F);
- /* TS_MO register (month) value range: 1-12 */
- tm.tm_mon = bcd2bin(data[PCF2127_REG_TS_MO] & 0x1F) - 1;
- tm.tm_year = bcd2bin(data[PCF2127_REG_TS_YR]);
- if (tm.tm_year < 70)
- tm.tm_year += 100; /* assume we are in 1970...2069 */
-
- ret = rtc_valid_tm(&tm);
- if (ret)
- return ret;
+ ret = pcf2127_rtc_ts_read(dev->parent, &ts);
+ if (ret)
+ return 0;
- return sprintf(buf, "%llu\n",
- (unsigned long long)rtc_tm_to_time64(&tm));
+ ret = pcf2127_wdt_active_ping(&pcf2127->wdd);
+ if (ret)
+ return ret;
+ }
+ return sprintf(buf, "%llu\n", (unsigned long long)ts);
};
static DEVICE_ATTR_RW(timestamp0);
@@ -594,6 +667,7 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap,
dev_err(dev, "failed to request alarm irq\n");
return ret;
}
+ pcf2127->irq_enabled = true;
}
if (alarm_irq > 0 || device_property_read_bool(dev, "wakeup-source")) {