diff options
author | Esben Haabendal <esben@geanix.com> | 2024-09-13 12:29:13 +0200 |
---|---|---|
committer | Alexandre Belloni <alexandre.belloni@bootlin.com> | 2024-11-11 00:06:46 +0100 |
commit | c62d658e5253a872180e77f4e1674bd94a989927 (patch) | |
tree | 5dcb7fa512125bd46faba1986e33b91bc5405676 /drivers/rtc | |
parent | d4a6161f242b70dde08a631b16ca3671aa1b2ed2 (diff) |
rtc: isl12022: Add alarm support
The ISL12022 RTC has a combined INT/fOUT pin, which can be used for alarm
interrupt when frequency output is not enabled.
The device-tree bindings should ensure that interrupt and clock output is
not enabled at the same time.
Signed-off-by: Esben Haabendal <esben@geanix.com>
Link: https://lore.kernel.org/r/20240913-rtc-isl12022-alarm-irq-v2-2-37309d939723@geanix.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/rtc-isl12022.c | 234 |
1 files changed, 227 insertions, 7 deletions
diff --git a/drivers/rtc/rtc-isl12022.c b/drivers/rtc/rtc-isl12022.c index d82278fdc29b..8001e3c5da76 100644 --- a/drivers/rtc/rtc-isl12022.c +++ b/drivers/rtc/rtc-isl12022.c @@ -21,7 +21,7 @@ #include <asm/byteorder.h> -/* ISL register offsets */ +/* RTC - Real time clock registers */ #define ISL12022_REG_SC 0x00 #define ISL12022_REG_MN 0x01 #define ISL12022_REG_HR 0x02 @@ -30,21 +30,36 @@ #define ISL12022_REG_YR 0x05 #define ISL12022_REG_DW 0x06 +/* CSR - Control and status registers */ #define ISL12022_REG_SR 0x07 #define ISL12022_REG_INT 0x08 - #define ISL12022_REG_PWR_VBAT 0x0a - #define ISL12022_REG_BETA 0x0d + +/* ALARM - Alarm registers */ +#define ISL12022_REG_SCA0 0x10 +#define ISL12022_REG_MNA0 0x11 +#define ISL12022_REG_HRA0 0x12 +#define ISL12022_REG_DTA0 0x13 +#define ISL12022_REG_MOA0 0x14 +#define ISL12022_REG_DWA0 0x15 +#define ISL12022_ALARM ISL12022_REG_SCA0 +#define ISL12022_ALARM_LEN (ISL12022_REG_DWA0 - ISL12022_REG_SCA0 + 1) + +/* TEMP - Temperature sensor registers */ #define ISL12022_REG_TEMP_L 0x28 /* ISL register bits */ #define ISL12022_HR_MIL (1 << 7) /* military or 24 hour time */ +#define ISL12022_SR_ALM (1 << 4) #define ISL12022_SR_LBAT85 (1 << 2) #define ISL12022_SR_LBAT75 (1 << 1) +#define ISL12022_INT_ARST (1 << 7) #define ISL12022_INT_WRTC (1 << 6) +#define ISL12022_INT_IM (1 << 5) +#define ISL12022_INT_FOBATB (1 << 4) #define ISL12022_INT_FO_MASK GENMASK(3, 0) #define ISL12022_INT_FO_OFF 0x0 #define ISL12022_INT_FO_32K 0x1 @@ -52,10 +67,17 @@ #define ISL12022_REG_VB85_MASK GENMASK(5, 3) #define ISL12022_REG_VB75_MASK GENMASK(2, 0) +#define ISL12022_ALARM_ENABLE (1 << 7) /* for all ALARM registers */ + #define ISL12022_BETA_TSE (1 << 7) +static struct i2c_driver isl12022_driver; + struct isl12022 { + struct rtc_device *rtc; struct regmap *regmap; + int irq; + bool irq_enabled; }; static umode_t isl12022_hwmon_is_visible(const void *data, @@ -215,6 +237,194 @@ static int isl12022_rtc_set_time(struct device *dev, struct rtc_time *tm) return regmap_bulk_write(regmap, ISL12022_REG_SC, buf, sizeof(buf)); } +static int isl12022_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rtc_time *tm = &alarm->time; + struct isl12022 *isl12022 = dev_get_drvdata(dev); + struct regmap *regmap = isl12022->regmap; + u8 buf[ISL12022_ALARM_LEN]; + unsigned int i, yr; + int ret; + + ret = regmap_bulk_read(regmap, ISL12022_ALARM, buf, sizeof(buf)); + if (ret) { + dev_dbg(dev, "%s: reading ALARM registers failed\n", + __func__); + return ret; + } + + /* The alarm doesn't store the year so get it from the rtc section */ + ret = regmap_read(regmap, ISL12022_REG_YR, &yr); + if (ret) { + dev_dbg(dev, "%s: reading YR register failed\n", __func__); + return ret; + } + + dev_dbg(dev, + "%s: sc=%02x, mn=%02x, hr=%02x, dt=%02x, mo=%02x, dw=%02x yr=%u\n", + __func__, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], yr); + + tm->tm_sec = bcd2bin(buf[ISL12022_REG_SCA0 - ISL12022_ALARM] & 0x7F); + tm->tm_min = bcd2bin(buf[ISL12022_REG_MNA0 - ISL12022_ALARM] & 0x7F); + tm->tm_hour = bcd2bin(buf[ISL12022_REG_HRA0 - ISL12022_ALARM] & 0x3F); + tm->tm_mday = bcd2bin(buf[ISL12022_REG_DTA0 - ISL12022_ALARM] & 0x3F); + tm->tm_mon = bcd2bin(buf[ISL12022_REG_MOA0 - ISL12022_ALARM] & 0x1F) - 1; + tm->tm_wday = buf[ISL12022_REG_DWA0 - ISL12022_ALARM] & 0x07; + tm->tm_year = bcd2bin(yr) + 100; + + for (i = 0; i < ISL12022_ALARM_LEN; i++) { + if (buf[i] & ISL12022_ALARM_ENABLE) { + alarm->enabled = 1; + break; + } + } + + dev_dbg(dev, "%s: %ptR\n", __func__, tm); + + return 0; +} + +static int isl12022_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rtc_time *alarm_tm = &alarm->time; + struct isl12022 *isl12022 = dev_get_drvdata(dev); + struct regmap *regmap = isl12022->regmap; + u8 regs[ISL12022_ALARM_LEN] = { 0, }; + struct rtc_time rtc_tm; + int ret, enable, dw; + + ret = isl12022_rtc_read_time(dev, &rtc_tm); + if (ret) + return ret; + + /* If the alarm time is before the current time disable the alarm */ + if (!alarm->enabled || rtc_tm_sub(alarm_tm, &rtc_tm) <= 0) + enable = 0; + else + enable = ISL12022_ALARM_ENABLE; + + /* + * Set non-matching day of the week to safeguard against early false + * matching while setting all the alarm registers (this rtc lacks a + * general alarm/irq enable/disable bit). + */ + ret = regmap_read(regmap, ISL12022_REG_DW, &dw); + if (ret) { + dev_dbg(dev, "%s: reading DW failed\n", __func__); + return ret; + } + /* ~4 days into the future should be enough to avoid match */ + dw = ((dw + 4) % 7) | ISL12022_ALARM_ENABLE; + ret = regmap_write(regmap, ISL12022_REG_DWA0, dw); + if (ret) { + dev_dbg(dev, "%s: writing DWA0 failed\n", __func__); + return ret; + } + + /* Program the alarm and enable it for each setting */ + regs[ISL12022_REG_SCA0 - ISL12022_ALARM] = bin2bcd(alarm_tm->tm_sec) | enable; + regs[ISL12022_REG_MNA0 - ISL12022_ALARM] = bin2bcd(alarm_tm->tm_min) | enable; + regs[ISL12022_REG_HRA0 - ISL12022_ALARM] = bin2bcd(alarm_tm->tm_hour) | enable; + regs[ISL12022_REG_DTA0 - ISL12022_ALARM] = bin2bcd(alarm_tm->tm_mday) | enable; + regs[ISL12022_REG_MOA0 - ISL12022_ALARM] = bin2bcd(alarm_tm->tm_mon + 1) | enable; + regs[ISL12022_REG_DWA0 - ISL12022_ALARM] = bin2bcd(alarm_tm->tm_wday & 7) | enable; + + /* write ALARM registers */ + ret = regmap_bulk_write(regmap, ISL12022_ALARM, ®s, sizeof(regs)); + if (ret) { + dev_dbg(dev, "%s: writing ALARM registers failed\n", __func__); + return ret; + } + + return 0; +} + +static irqreturn_t isl12022_rtc_interrupt(int irq, void *data) +{ + struct isl12022 *isl12022 = data; + struct rtc_device *rtc = isl12022->rtc; + struct device *dev = &rtc->dev; + struct regmap *regmap = isl12022->regmap; + u32 val = 0; + unsigned long events = 0; + int ret; + + ret = regmap_read(regmap, ISL12022_REG_SR, &val); + if (ret) { + dev_dbg(dev, "%s: reading SR failed\n", __func__); + return IRQ_HANDLED; + } + + if (val & ISL12022_SR_ALM) + events |= RTC_IRQF | RTC_AF; + + if (events & RTC_AF) + dev_dbg(dev, "alarm!\n"); + + if (!events) + return IRQ_NONE; + + rtc_update_irq(rtc, 1, events); + return IRQ_HANDLED; +} + +static int isl12022_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct isl12022 *isl12022 = dev_get_drvdata(dev); + + /* Make sure enabled is 0 or 1 */ + enabled = !!enabled; + + if (isl12022->irq_enabled == enabled) + return 0; + + if (enabled) + enable_irq(isl12022->irq); + else + disable_irq(isl12022->irq); + + isl12022->irq_enabled = enabled; + + return 0; +} + +static int isl12022_setup_irq(struct device *dev, int irq) +{ + struct isl12022 *isl12022 = dev_get_drvdata(dev); + struct regmap *regmap = isl12022->regmap; + unsigned int reg_mask, reg_val; + u8 buf[ISL12022_ALARM_LEN] = { 0, }; + int ret; + + /* Clear and disable all alarm registers */ + ret = regmap_bulk_write(regmap, ISL12022_ALARM, buf, sizeof(buf)); + if (ret) + return ret; + + /* + * Enable automatic reset of ALM bit and enable single event interrupt + * mode. + */ + reg_mask = ISL12022_INT_ARST | ISL12022_INT_IM | ISL12022_INT_FO_MASK; + reg_val = ISL12022_INT_ARST | ISL12022_INT_FO_OFF; + ret = regmap_write_bits(regmap, ISL12022_REG_INT, + reg_mask, reg_val); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, + isl12022_rtc_interrupt, + IRQF_SHARED | IRQF_ONESHOT, + isl12022_driver.driver.name, + isl12022); + if (ret) + return dev_err_probe(dev, ret, "Unable to request irq %d\n", irq); + + isl12022->irq = irq; + return 0; +} + static int isl12022_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) { struct isl12022 *isl12022 = dev_get_drvdata(dev); @@ -246,6 +456,9 @@ static const struct rtc_class_ops isl12022_rtc_ops = { .ioctl = isl12022_rtc_ioctl, .read_time = isl12022_rtc_read_time, .set_time = isl12022_rtc_set_time, + .read_alarm = isl12022_rtc_read_alarm, + .set_alarm = isl12022_rtc_set_alarm, + .alarm_irq_enable = isl12022_rtc_alarm_irq_enable, }; static const struct regmap_config regmap_config = { @@ -349,10 +562,8 @@ static int isl12022_probe(struct i2c_client *client) return -ENOMEM; regmap = devm_regmap_init_i2c(client, ®map_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "regmap allocation failed\n"); - return PTR_ERR(regmap); - } + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), "regmap allocation failed\n"); isl12022->regmap = regmap; dev_set_drvdata(&client->dev, isl12022); @@ -367,11 +578,20 @@ static int isl12022_probe(struct i2c_client *client) rtc = devm_rtc_allocate_device(&client->dev); if (IS_ERR(rtc)) return PTR_ERR(rtc); + isl12022->rtc = rtc; rtc->ops = &isl12022_rtc_ops; rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; rtc->range_max = RTC_TIMESTAMP_END_2099; + if (client->irq > 0) { + ret = isl12022_setup_irq(&client->dev, client->irq); + if (ret) + return ret; + } else { + clear_bit(RTC_FEATURE_ALARM, rtc->features); + } + return devm_rtc_register_device(rtc); } |