rtc: pcf2123: add alarm support
authorDylan Howey <Dylan.Howey@tennantco.com>
Fri, 3 May 2019 19:52:12 +0000 (19:52 +0000)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Wed, 19 Jun 2019 14:20:28 +0000 (16:20 +0200)
Allows alarm to be controlled using, e.g., the RTC_WKALM_SET ioctl.

Signed-off-by: Dylan Howey <Dylan.Howey@tennantco.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/rtc/rtc-pcf2123.c

index 2f87da47bc9adaebe005de1121872984b9527e86..3b314ce991e5aad4261ef39c2fb896d15bc8c2c3 100644 (file)
@@ -242,6 +242,99 @@ static int pcf2123_rtc_set_time(struct device *dev, struct rtc_time *tm)
        return 0;
 }
 
+static int pcf2123_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+       struct pcf2123_plat_data *pdata = dev_get_platdata(dev);
+       u8 rxbuf[4];
+       int ret;
+       unsigned int val = 0;
+
+       ret = regmap_bulk_read(pdata->map, PCF2123_REG_ALRM_MN, rxbuf,
+                               sizeof(rxbuf));
+       if (ret)
+               return ret;
+
+       alm->time.tm_min = bcd2bin(rxbuf[0] & 0x7F);
+       alm->time.tm_hour = bcd2bin(rxbuf[1] & 0x3F);
+       alm->time.tm_mday = bcd2bin(rxbuf[2] & 0x3F);
+       alm->time.tm_wday = bcd2bin(rxbuf[3] & 0x07);
+
+       dev_dbg(dev, "%s: alm is %ptR\n", __func__, &alm->time);
+
+       ret = regmap_read(pdata->map, PCF2123_REG_CTRL2, &val);
+       if (ret)
+               return ret;
+
+       alm->enabled = !!(val & CTRL2_AIE);
+
+       return 0;
+}
+
+static int pcf2123_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+       struct pcf2123_plat_data *pdata = dev_get_platdata(dev);
+       u8 txbuf[4];
+       int ret;
+
+       dev_dbg(dev, "%s: alm is %ptR\n", __func__, &alm->time);
+
+       /* Ensure alarm flag is clear */
+       ret = regmap_update_bits(pdata->map, PCF2123_REG_CTRL2, CTRL2_AF, 0);
+       if (ret)
+               return ret;
+
+       /* Disable alarm interrupt */
+       ret = regmap_update_bits(pdata->map, PCF2123_REG_CTRL2, CTRL2_AIE, 0);
+       if (ret)
+               return ret;
+
+       /* Set new alarm */
+       txbuf[0] = bin2bcd(alm->time.tm_min & 0x7F);
+       txbuf[1] = bin2bcd(alm->time.tm_hour & 0x3F);
+       txbuf[2] = bin2bcd(alm->time.tm_mday & 0x3F);
+       txbuf[3] = bin2bcd(alm->time.tm_wday & 0x07);
+
+       ret = regmap_bulk_write(pdata->map, PCF2123_REG_ALRM_MN, txbuf,
+                               sizeof(txbuf));
+       if (ret)
+               return ret;
+
+       /* Enable alarm interrupt */
+       if (alm->enabled)       {
+               ret = regmap_update_bits(pdata->map, PCF2123_REG_CTRL2,
+                                               CTRL2_AIE, CTRL2_AIE);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static irqreturn_t pcf2123_rtc_irq(int irq, void *dev)
+{
+       struct pcf2123_plat_data *pdata = dev_get_platdata(dev);
+       struct mutex *lock = &pdata->rtc->ops_lock;
+       unsigned int val = 0;
+       int ret = IRQ_NONE;
+
+       mutex_lock(lock);
+       regmap_read(pdata->map, PCF2123_REG_CTRL2, &val);
+
+       /* Alarm? */
+       if (val & CTRL2_AF) {
+               ret = IRQ_HANDLED;
+
+               /* Clear alarm flag */
+               regmap_update_bits(pdata->map, PCF2123_REG_CTRL2, CTRL2_AF, 0);
+
+               rtc_update_irq(pdata->rtc, 1, RTC_IRQF | RTC_AF);
+       }
+
+       mutex_unlock(lock);
+
+       return ret;
+}
+
 static int pcf2123_reset(struct device *dev)
 {
        struct pcf2123_plat_data *pdata = dev_get_platdata(dev);
@@ -281,7 +374,8 @@ static const struct rtc_class_ops pcf2123_rtc_ops = {
        .set_time       = pcf2123_rtc_set_time,
        .read_offset    = pcf2123_read_offset,
        .set_offset     = pcf2123_set_offset,
-
+       .read_alarm     = pcf2123_rtc_read_alarm,
+       .set_alarm      = pcf2123_rtc_set_alarm,
 };
 
 static int pcf2123_probe(struct spi_device *spi)
@@ -289,7 +383,7 @@ static int pcf2123_probe(struct spi_device *spi)
        struct rtc_device *rtc;
        struct rtc_time tm;
        struct pcf2123_plat_data *pdata;
-       int ret;
+       int ret = 0;
 
        pdata = devm_kzalloc(&spi->dev, sizeof(struct pcf2123_plat_data),
                                GFP_KERNEL);
@@ -328,6 +422,24 @@ static int pcf2123_probe(struct spi_device *spi)
 
        pdata->rtc = rtc;
 
+       /* Register alarm irq */
+       if (spi->irq > 0) {
+               ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL,
+                               pcf2123_rtc_irq,
+                               IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                               pcf2123_driver.driver.name, &spi->dev);
+               if (!ret)
+                       device_init_wakeup(&spi->dev, true);
+               else
+                       dev_err(&spi->dev, "could not request irq.\n");
+       }
+
+       /* The PCF2123's alarm only has minute accuracy. Must add timer
+        * support to this driver to generate interrupts more than once
+        * per minute.
+        */
+       pdata->rtc->uie_unsupported = 1;
+
        return 0;
 
 kfree_exit: