aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/rtc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc')
-rw-r--r--drivers/rtc/Kconfig11
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-ds3232.c7
-rw-r--r--drivers/rtc/rtc-pcf2123.c1
-rw-r--r--drivers/rtc/rtc-pcf8523.c30
-rw-r--r--drivers/rtc/rtc-rpi.c277
-rw-r--r--drivers/rtc/rtc-rv3028.c24
7 files changed, 344 insertions, 7 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 64f6e9756aff..a30a70131a92 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -233,6 +233,17 @@ config RTC_DRV_AC100
This driver can also be built as a module. If so, the module
will be called rtc-ac100.
+config RTC_DRV_RPI
+ tristate "Raspberry Pi RTC"
+ depends on ARCH_BRCMSTB || COMPILE_TEST
+ default ARCH_BRCMSTB
+ help
+ If you say yes here you get support for the RTC found on
+ Raspberry Pi devices.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-rpi.
+
config RTC_DRV_BRCMSTB
tristate "Broadcom STB wake-timer"
depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 789bddfea99d..0a00853c8cd9 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -145,6 +145,7 @@ obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o
obj-$(CONFIG_RTC_DRV_RC5T619) += rtc-rc5t619.o
obj-$(CONFIG_RTC_DRV_RK808) += rtc-rk808.o
obj-$(CONFIG_RTC_DRV_RP5C01) += rtc-rp5c01.o
+obj-$(CONFIG_RTC_DRV_RPI) += rtc-rpi.o
obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o
obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o
obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o
diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c
index 18f35823b4b5..a39fc08ce529 100644
--- a/drivers/rtc/rtc-ds3232.c
+++ b/drivers/rtc/rtc-ds3232.c
@@ -681,9 +681,16 @@ static int ds3234_probe(struct spi_device *spi)
return ds3232_probe(&spi->dev, regmap, spi->irq, "ds3234");
}
+static const __maybe_unused struct of_device_id ds3234_of_match[] = {
+ { .compatible = "dallas,ds3234" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ds3234_of_match);
+
static struct spi_driver ds3234_driver = {
.driver = {
.name = "ds3234",
+ .of_match_table = of_match_ptr(ds3234_of_match),
},
.probe = ds3234_probe,
};
diff --git a/drivers/rtc/rtc-pcf2123.c b/drivers/rtc/rtc-pcf2123.c
index e714661e61a9..89cda4dea7f8 100644
--- a/drivers/rtc/rtc-pcf2123.c
+++ b/drivers/rtc/rtc-pcf2123.c
@@ -479,3 +479,4 @@ module_spi_driver(pcf2123_driver);
MODULE_AUTHOR("Chris Verges <[email protected]>");
MODULE_DESCRIPTION("NXP PCF2123 RTC driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:rtc-pcf2123");
diff --git a/drivers/rtc/rtc-pcf8523.c b/drivers/rtc/rtc-pcf8523.c
index 2c63c0ffd05a..0125000fa722 100644
--- a/drivers/rtc/rtc-pcf8523.c
+++ b/drivers/rtc/rtc-pcf8523.c
@@ -100,6 +100,7 @@ static int pcf8523_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct pcf8523 *pcf8523 = dev_get_drvdata(dev);
u8 regs[10];
+ u32 value;
int err;
err = regmap_bulk_read(pcf8523->regmap, PCF8523_REG_CONTROL1, regs,
@@ -107,9 +108,36 @@ static int pcf8523_rtc_read_time(struct device *dev, struct rtc_time *tm)
if (err < 0)
return err;
- if ((regs[0] & PCF8523_CONTROL1_STOP) || (regs[3] & PCF8523_SECONDS_OS))
+ if (regs[PCF8523_REG_CONTROL1] & PCF8523_CONTROL1_STOP)
return -EINVAL;
+ if (regs[PCF8523_REG_SECONDS] & PCF8523_SECONDS_OS) {
+ /*
+ * If the oscillator was stopped, try to clear the flag. Upon
+ * power-up the flag is always set, but if we cannot clear it
+ * the oscillator isn't running properly for some reason. The
+ * sensible thing therefore is to return an error, signalling
+ * that the clock cannot be assumed to be correct.
+ */
+
+ regs[PCF8523_REG_SECONDS] &= ~PCF8523_SECONDS_OS;
+
+ err = regmap_write(pcf8523->regmap, PCF8523_REG_SECONDS,
+ regs[PCF8523_REG_SECONDS]);
+ if (err < 0)
+ return err;
+
+ err = regmap_read(pcf8523->regmap, PCF8523_REG_SECONDS,
+ &value);
+ if (err < 0)
+ return err;
+
+ if (value & PCF8523_SECONDS_OS)
+ return -EAGAIN;
+
+ regs[PCF8523_REG_SECONDS] = value;
+ }
+
tm->tm_sec = bcd2bin(regs[3] & 0x7f);
tm->tm_min = bcd2bin(regs[4] & 0x7f);
tm->tm_hour = bcd2bin(regs[5] & 0x3f);
diff --git a/drivers/rtc/rtc-rpi.c b/drivers/rtc/rtc-rpi.c
new file mode 100644
index 000000000000..006012333e78
--- /dev/null
+++ b/drivers/rtc/rtc-rpi.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/**
+ * rtc-rpi.c
+ *
+ * RTC driver using firmware mailbox
+ * Supports battery backed RTC and wake alarms
+ *
+ * Based on rtc-meson-vrtc by Neil Armstrong
+ *
+ * Copyright (c) 2023, Raspberry Pi Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/of.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+struct rpi_rtc_data {
+ struct rtc_device *rtc;
+ struct rpi_firmware *fw;
+ u32 bbat_vchg_microvolts;
+};
+
+#define RPI_FIRMWARE_GET_RTC_REG 0x00030087
+#define RPI_FIRMWARE_SET_RTC_REG 0x00038087
+
+enum {
+ RTC_TIME,
+ RTC_ALARM,
+ RTC_ALARM_PENDING,
+ RTC_ALARM_ENABLE,
+ RTC_BBAT_CHG_VOLTS,
+ RTC_BBAT_CHG_VOLTS_MIN,
+ RTC_BBAT_CHG_VOLTS_MAX,
+ RTC_BBAT_VOLTS
+};
+
+static int rpi_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_TIME};
+ int err;
+
+ err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+ &data, sizeof(data));
+ rtc_time64_to_tm(data[1], tm);
+ return err;
+}
+
+static int rpi_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_TIME, rtc_tm_to_time64(tm)};
+
+ return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+ &data, sizeof(data));
+}
+
+static int rpi_rtc_alarm_irq_is_enabled(struct device *dev, unsigned char *enabled)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_ALARM_ENABLE};
+ s32 err = 0;
+
+ err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+ &data, sizeof(data));
+ *enabled = data[1] & 0x1;
+ return err;
+}
+
+static int rpi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_ALARM_ENABLE, enabled};
+
+ return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+ &data, sizeof(data));
+}
+
+static int rpi_rtc_alarm_clear_pending(struct device *dev)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_ALARM_PENDING, 1};
+
+ return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+ &data, sizeof(data));
+}
+
+static int rpi_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_ALARM};
+ s32 err = 0;
+
+ err = rpi_rtc_alarm_irq_is_enabled(dev, &alarm->enabled);
+ if (!err)
+ err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+ &data, sizeof(data));
+ rtc_time64_to_tm(data[1], &alarm->time);
+
+ return err;
+}
+
+static int rpi_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_ALARM, rtc_tm_to_time64(&alarm->time)};
+ int err;
+
+ err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+ &data, sizeof(data));
+
+ if (err == 0)
+ err = rpi_rtc_alarm_irq_enable(dev, alarm->enabled);
+
+ return err;
+}
+
+static const struct rtc_class_ops rpi_rtc_ops = {
+ .read_time = rpi_rtc_read_time,
+ .set_time = rpi_rtc_set_time,
+ .read_alarm = rpi_rtc_read_alarm,
+ .set_alarm = rpi_rtc_set_alarm,
+ .alarm_irq_enable = rpi_rtc_alarm_irq_enable,
+};
+
+static int rpi_rtc_set_charge_voltage(struct device *dev)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+ u32 data[2] = {RTC_BBAT_CHG_VOLTS, vrtc->bbat_vchg_microvolts};
+ int err;
+
+ err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+ &data, sizeof(data));
+
+ if (err)
+ dev_err(dev, "failed to set trickle charge voltage to %uuV: %d\n",
+ vrtc->bbat_vchg_microvolts, err);
+ else if (vrtc->bbat_vchg_microvolts)
+ dev_info(dev, "trickle charging enabled at %uuV\n",
+ vrtc->bbat_vchg_microvolts);
+
+ return err;
+}
+
+static ssize_t rpi_rtc_print_uint_reg(struct device *dev, char *buf, u32 reg)
+{
+ struct rpi_rtc_data *vrtc = dev_get_drvdata(dev->parent);
+ u32 data[2] = {reg, 0};
+ int ret = 0;
+
+ ret = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+ &data, sizeof(data));
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%u\n", data[1]);
+}
+
+static ssize_t charging_voltage_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS);
+}
+static DEVICE_ATTR_RO(charging_voltage);
+
+static ssize_t charging_voltage_min_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS_MIN);
+}
+static DEVICE_ATTR_RO(charging_voltage_min);
+
+static ssize_t charging_voltage_max_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS_MAX);
+}
+static DEVICE_ATTR_RO(charging_voltage_max);
+
+static ssize_t battery_voltage_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_VOLTS);
+}
+static DEVICE_ATTR_RO(battery_voltage);
+
+static struct attribute *rpi_rtc_attrs[] = {
+ &dev_attr_charging_voltage.attr,
+ &dev_attr_charging_voltage_min.attr,
+ &dev_attr_charging_voltage_max.attr,
+ &dev_attr_battery_voltage.attr,
+ NULL
+};
+
+static const struct attribute_group rpi_rtc_sysfs_files = {
+ .attrs = rpi_rtc_attrs,
+};
+
+static int rpi_rtc_probe(struct platform_device *pdev)
+{
+ struct rpi_rtc_data *vrtc;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *fw_node;
+ struct rpi_firmware *fw;
+ int ret;
+
+ fw_node = of_parse_phandle(np, "firmware", 0);
+ if (!fw_node) {
+ dev_err(dev, "Missing firmware node\n");
+ return -ENOENT;
+ }
+
+ fw = rpi_firmware_get(fw_node);
+ if (!fw)
+ return -EPROBE_DEFER;
+
+ vrtc = devm_kzalloc(&pdev->dev, sizeof(*vrtc), GFP_KERNEL);
+ if (!vrtc)
+ return -ENOMEM;
+
+ vrtc->fw = fw;
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ platform_set_drvdata(pdev, vrtc);
+
+ vrtc->rtc = devm_rtc_allocate_device(&pdev->dev);
+ if (IS_ERR(vrtc->rtc))
+ return PTR_ERR(vrtc->rtc);
+
+ set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, vrtc->rtc->features);
+ clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, vrtc->rtc->features);
+
+ vrtc->rtc->ops = &rpi_rtc_ops;
+ ret = rtc_add_group(vrtc->rtc, &rpi_rtc_sysfs_files);
+ if (ret)
+ return ret;
+
+ rpi_rtc_alarm_clear_pending(dev);
+
+ /*
+ * Optionally enable trickle charging - if the property isn't
+ * present (or set to zero), trickle charging is disabled.
+ */
+ of_property_read_u32(np, "trickle-charge-microvolt",
+ &vrtc->bbat_vchg_microvolts);
+
+ rpi_rtc_set_charge_voltage(dev);
+
+ return devm_rtc_register_device(vrtc->rtc);
+}
+
+static const struct of_device_id rpi_rtc_dt_match[] = {
+ { .compatible = "raspberrypi,rpi-rtc"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, rpi_rtc_dt_match);
+
+static struct platform_driver rpi_rtc_driver = {
+ .probe = rpi_rtc_probe,
+ .driver = {
+ .name = "rpi-rtc",
+ .of_match_table = rpi_rtc_dt_match,
+ },
+};
+
+module_platform_driver(rpi_rtc_driver);
+
+MODULE_DESCRIPTION("Raspberry Pi RTC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-rv3028.c b/drivers/rtc/rtc-rv3028.c
index c2a531f0e125..874ebd4f6938 100644
--- a/drivers/rtc/rtc-rv3028.c
+++ b/drivers/rtc/rtc-rv3028.c
@@ -865,16 +865,17 @@ static const struct regmap_config regmap_config = {
static u8 rv3028_set_trickle_charger(struct rv3028_data *rv3028,
struct i2c_client *client)
{
- int ret, val_old, val;
+ int ret, val_old, val, val_mask;
u32 ohms, chargeable;
+ u32 bsm;
ret = regmap_read(rv3028->regmap, RV3028_BACKUP, &val_old);
if (ret < 0)
return ret;
/* mask out only trickle charger bits */
- val_old = val_old & (RV3028_BACKUP_TCE | RV3028_BACKUP_TCR_MASK);
- val = val_old;
+ val_mask = RV3028_BACKUP_TCE | RV3028_BACKUP_TCR_MASK;
+ val = val_old & val_mask;
/* setup trickle charger */
if (!device_property_read_u32(&client->dev, "trickle-resistor-ohms",
@@ -909,10 +910,21 @@ static u8 rv3028_set_trickle_charger(struct rv3028_data *rv3028,
}
}
+ /* setup backup switchover mode */
+ if (!device_property_read_u32(&client->dev,
+ "backup-switchover-mode",
+ &bsm)) {
+ if (bsm <= 3) {
+ val_mask |= RV3028_BACKUP_BSM;
+ val |= (u8)(bsm << 2);
+ } else {
+ dev_warn(&client->dev, "invalid backup switchover mode value\n");
+ }
+ }
+
/* only update EEPROM if changes are necessary */
- if (val_old != val) {
- ret = rv3028_update_cfg(rv3028, RV3028_BACKUP, RV3028_BACKUP_TCE |
- RV3028_BACKUP_TCR_MASK, val);
+ if ((val_old & val_mask) != val) {
+ ret = rv3028_update_cfg(rv3028, RV3028_BACKUP, val_mask, val);
if (ret)
return ret;
}