1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter.git synced 2025-03-09 15:40:20 +00:00
openmptcprouter/6.12/target/linux/bcm27xx/patches-6.12/950-0383-rtc-rtc-rpi-Add-simple-RTC-driver-for-Raspberry-Pi.patch
Ycarus (Yannick Chabanois) bdb9b0046f Add bcm27xx 6.12 test support
2024-12-20 14:17:26 +01:00

348 lines
9.4 KiB
Diff

From 3ea70bc24807265abe59eaef3d5d10020b638c61 Mon Sep 17 00:00:00 2001
From: Dom Cobley <popcornmix@gmail.com>
Date: Fri, 7 Jul 2023 20:00:45 +0100
Subject: [PATCH 383/697] rtc: rtc-rpi: Add simple RTC driver for Raspberry Pi
This supports setting and reading the real time clock
and supports wakeup alarms.
To support wake up alarms you want this bootloader config:
POWER_OFF_ON_HALT=1
WAKE_ON_GPIO=0
You can test with:
echo +600 | sudo tee /sys/class/rtc/rtc0/wakealarm
sudo halt
That will halt (in an almost no power state),
then wake and restart after 10 minutes.
Signed-off-by: Dom Cobley <popcornmix@gmail.com>
drivers: rtc-rpi: add battery charge circuit control and readback
Parse devicetree for a charger voltage and apply it. If nonzero and a
valid voltage, the firmware will enable charging, otherwise the charger
circuit is disabled.
Add sysfs attributes to read back the supported charge voltage range,
the measured battery voltage, and the charger setpoint.
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
---
drivers/rtc/Kconfig | 11 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-rpi.c | 277 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 289 insertions(+)
create mode 100644 drivers/rtc/rtc-rpi.c
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -223,6 +223,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
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -143,6 +143,7 @@ obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5
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
--- /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");