summaryrefslogtreecommitdiff
path: root/drivers/rtc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc')
-rw-r--r--drivers/rtc/Kconfig13
-rw-r--r--drivers/rtc/Makefile2
-rw-r--r--drivers/rtc/armada38x.c184
-rw-r--r--drivers/rtc/i2c_rtc_emul.c21
-rw-r--r--drivers/rtc/rv3028.c208
-rw-r--r--drivers/rtc/sandbox_rtc.c13
6 files changed, 421 insertions, 20 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index aa6d90158cd..c84a9d2b27b 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -38,6 +38,13 @@ config RTC_ENABLE_32KHZ_OUTPUT
Some real-time clocks support the output of 32kHz square waves (such as ds3231),
the config symbol choose Real Time Clock device 32Khz output feature.
+config RTC_ARMADA38X
+ bool "Enable Armada 38x Marvell SoC RTC"
+ depends on DM_RTC && ARCH_MVEBU
+ help
+ This adds support for the in-chip RTC that can be found in the
+ Armada 38x Marvell's SoC devices.
+
config RTC_PCF2127
bool "Enable PCF2127 driver"
depends on DM_RTC
@@ -95,6 +102,12 @@ config RTC_PCF8563
If you say yes here you get support for the Philips PCF8563 RTC
and compatible chips.
+config RTC_RV3028
+ bool "Enable RV3028 driver"
+ depends on DM_RTC
+ help
+ The MicroCrystal RV3028 is a I2C Real Time Clock (RTC)
+
config RTC_RV3029
bool "Enable RV3029 driver"
depends on DM_RTC
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 6a45a9c8749..f668cf9050c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_$(SPL_TPL_)DM_RTC) += rtc-uclass.o
obj-$(CONFIG_RTC_AT91SAM9_RTT) += at91sam9_rtt.o
obj-y += rtc-lib.o
+obj-$(CONFIG_RTC_ARMADA38X) += armada38x.o
obj-$(CONFIG_RTC_DAVINCI) += davinci.o
obj-$(CONFIG_RTC_DS1302) += ds1302.o
obj-$(CONFIG_RTC_DS1306) += ds1306.o
@@ -46,6 +47,7 @@ obj-$(CONFIG_RTC_PCF2127) += pcf2127.o
obj-$(CONFIG_RTC_PL031) += pl031.o
obj-$(CONFIG_RTC_PT7C4338) += pt7c4338.o
obj-$(CONFIG_RTC_RS5C372A) += rs5c372.o
+obj-$(CONFIG_RTC_RV3028) += rv3028.o
obj-$(CONFIG_RTC_RV3029) += rv3029.o
obj-$(CONFIG_RTC_RV8803) += rv8803.o
obj-$(CONFIG_RTC_RX8025) += rx8025.o
diff --git a/drivers/rtc/armada38x.c b/drivers/rtc/armada38x.c
new file mode 100644
index 00000000000..2d264acf779
--- /dev/null
+++ b/drivers/rtc/armada38x.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * RTC driver for the Armada 38x Marvell SoCs
+ *
+ * Copyright (C) 2021 Marek Behun <marek.behun@nic.cz>
+ *
+ * Based on Linux' driver by Gregory Clement and Marvell
+ */
+
+#include <asm/io.h>
+#include <dm.h>
+#include <linux/delay.h>
+#include <rtc.h>
+
+#define RTC_STATUS 0x0
+#define RTC_TIME 0xC
+#define RTC_CONF_TEST 0x1C
+
+/* Armada38x SoC registers */
+#define RTC_38X_BRIDGE_TIMING_CTL 0x0
+#define RTC_38X_PERIOD_OFFS 0
+#define RTC_38X_PERIOD_MASK (0x3FF << RTC_38X_PERIOD_OFFS)
+#define RTC_38X_READ_DELAY_OFFS 26
+#define RTC_38X_READ_DELAY_MASK (0x1F << RTC_38X_READ_DELAY_OFFS)
+
+#define SAMPLE_NR 100
+
+struct armada38x_rtc {
+ void __iomem *regs;
+ void __iomem *regs_soc;
+};
+
+/*
+ * According to Erratum RES-3124064 we have to do some configuration in MBUS.
+ * To read an RTC register we need to read it 100 times and return the most
+ * frequent value.
+ * To write an RTC register we need to write 2x zero into STATUS register,
+ * followed by the proper write. Linux adds an 5 us delay after this, so we do
+ * it here as well.
+ */
+static void update_38x_mbus_timing_params(struct armada38x_rtc *rtc)
+{
+ u32 reg;
+
+ reg = readl(rtc->regs_soc + RTC_38X_BRIDGE_TIMING_CTL);
+ reg &= ~RTC_38X_PERIOD_MASK;
+ reg |= 0x3FF << RTC_38X_PERIOD_OFFS; /* Maximum value */
+ reg &= ~RTC_38X_READ_DELAY_MASK;
+ reg |= 0x1F << RTC_38X_READ_DELAY_OFFS; /* Maximum value */
+ writel(reg, rtc->regs_soc + RTC_38X_BRIDGE_TIMING_CTL);
+}
+
+static void armada38x_rtc_write(u32 val, struct armada38x_rtc *rtc, u8 reg)
+{
+ writel(0, rtc->regs + RTC_STATUS);
+ writel(0, rtc->regs + RTC_STATUS);
+ writel(val, rtc->regs + reg);
+ udelay(5);
+}
+
+static u32 armada38x_rtc_read(struct armada38x_rtc *rtc, u8 reg)
+{
+ u8 counts[SAMPLE_NR], max_idx;
+ u32 samples[SAMPLE_NR], max;
+ int i, j, last;
+
+ for (i = 0, last = 0; i < SAMPLE_NR; ++i) {
+ u32 sample = readl(rtc->regs + reg);
+
+ /* find if this value was already read */
+ for (j = 0; j < last; ++j) {
+ if (samples[j] == sample)
+ break;
+ }
+
+ if (j < last) {
+ /* if yes, increment count */
+ ++counts[j];
+ } else {
+ /* if not, add */
+ samples[last] = sample;
+ counts[last] = 1;
+ ++last;
+ }
+ }
+
+ /* finally find the sample that was read the most */
+ max = 0;
+ max_idx = 0;
+
+ for (i = 0; i < last; ++i) {
+ if (counts[i] > max) {
+ max = counts[i];
+ max_idx = i;
+ }
+ }
+
+ return samples[max_idx];
+}
+
+static int armada38x_rtc_get(struct udevice *dev, struct rtc_time *tm)
+{
+ struct armada38x_rtc *rtc = dev_get_priv(dev);
+ u32 time;
+
+ time = armada38x_rtc_read(rtc, RTC_TIME);
+
+ rtc_to_tm(time, tm);
+
+ return 0;
+}
+
+static int armada38x_rtc_reset(struct udevice *dev)
+{
+ struct armada38x_rtc *rtc = dev_get_priv(dev);
+ u32 reg;
+
+ reg = armada38x_rtc_read(rtc, RTC_CONF_TEST);
+
+ if (reg & 0xff) {
+ armada38x_rtc_write(0, rtc, RTC_CONF_TEST);
+ mdelay(500);
+ armada38x_rtc_write(0, rtc, RTC_TIME);
+ armada38x_rtc_write(BIT(0) | BIT(1), 0, RTC_STATUS);
+ }
+
+ return 0;
+}
+
+static int armada38x_rtc_set(struct udevice *dev, const struct rtc_time *tm)
+{
+ struct armada38x_rtc *rtc = dev_get_priv(dev);
+ unsigned long time;
+
+ time = rtc_mktime(tm);
+
+ if (time > U32_MAX)
+ printf("%s: requested time to set will overflow\n", dev->name);
+
+ armada38x_rtc_reset(dev);
+ armada38x_rtc_write(time, rtc, RTC_TIME);
+
+ return 0;
+}
+
+static int armada38x_probe(struct udevice *dev)
+{
+ struct armada38x_rtc *rtc = dev_get_priv(dev);
+
+ rtc->regs = dev_remap_addr_name(dev, "rtc");
+ if (!rtc->regs)
+ goto err;
+
+ rtc->regs_soc = dev_remap_addr_name(dev, "rtc-soc");
+ if (!rtc->regs_soc)
+ goto err;
+
+ update_38x_mbus_timing_params(rtc);
+
+ return 0;
+err:
+ printf("%s: io address missing\n", dev->name);
+ return -ENODEV;
+}
+
+static const struct rtc_ops armada38x_rtc_ops = {
+ .get = armada38x_rtc_get,
+ .set = armada38x_rtc_set,
+ .reset = armada38x_rtc_reset,
+};
+
+static const struct udevice_id armada38x_rtc_ids[] = {
+ { .compatible = "marvell,armada-380-rtc", .data = 0 },
+ { }
+};
+
+U_BOOT_DRIVER(rtc_armada38x) = {
+ .name = "rtc-armada38x",
+ .id = UCLASS_RTC,
+ .of_match = armada38x_rtc_ids,
+ .probe = armada38x_probe,
+ .priv_auto = sizeof(struct armada38x_rtc),
+ .ops = &armada38x_rtc_ops,
+};
diff --git a/drivers/rtc/i2c_rtc_emul.c b/drivers/rtc/i2c_rtc_emul.c
index f25b976e54d..ba418c25daf 100644
--- a/drivers/rtc/i2c_rtc_emul.c
+++ b/drivers/rtc/i2c_rtc_emul.c
@@ -28,25 +28,6 @@
#define debug_buffer(x, ...)
#endif
-/**
- * struct sandbox_i2c_rtc_plat_data - platform data for the RTC
- *
- * @base_time: Base system time when RTC device was bound
- * @offset: RTC offset from current system time
- * @use_system_time: true to use system time, false to use @base_time
- * @reg: Register values
- */
-struct sandbox_i2c_rtc_plat_data {
- long base_time;
- long offset;
- bool use_system_time;
- u8 reg[REG_COUNT];
-};
-
-struct sandbox_i2c_rtc {
- unsigned int offset_secs;
-};
-
long sandbox_i2c_rtc_set_offset(struct udevice *dev, bool use_system_time,
int offset)
{
@@ -223,7 +204,7 @@ static int sandbox_i2c_rtc_bind(struct udevice *dev)
}
static const struct udevice_id sandbox_i2c_rtc_ids[] = {
- { .compatible = "sandbox,i2c-rtc" },
+ { .compatible = "sandbox,i2c-rtc-emul" },
{ }
};
diff --git a/drivers/rtc/rv3028.c b/drivers/rtc/rv3028.c
new file mode 100644
index 00000000000..9f63afc14a8
--- /dev/null
+++ b/drivers/rtc/rv3028.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * RTC driver for the Micro Crystal RV3028
+ *
+ * based on linux driver from
+ * Copyright (C) 2019 Micro Crystal SA
+ *
+ * Alexandre Belloni <alexandre.belloni@bootlin.com>
+ *
+ */
+
+#include <dm.h>
+#include <i2c.h>
+#include <rtc.h>
+
+#define RV3028_SEC 0x00
+#define RV3028_MIN 0x01
+#define RV3028_HOUR 0x02
+#define RV3028_WDAY 0x03
+#define RV3028_DAY 0x04
+#define RV3028_MONTH 0x05
+#define RV3028_YEAR 0x06
+#define RV3028_ALARM_MIN 0x07
+#define RV3028_ALARM_HOUR 0x08
+#define RV3028_ALARM_DAY 0x09
+#define RV3028_STATUS 0x0E
+#define RV3028_CTRL1 0x0F
+#define RV3028_CTRL2 0x10
+#define RV3028_EVT_CTRL 0x13
+#define RV3028_TS_COUNT 0x14
+#define RV3028_TS_SEC 0x15
+#define RV3028_RAM1 0x1F
+#define RV3028_EEPROM_ADDR 0x25
+#define RV3028_EEPROM_DATA 0x26
+#define RV3028_EEPROM_CMD 0x27
+#define RV3028_CLKOUT 0x35
+#define RV3028_OFFSET 0x36
+#define RV3028_BACKUP 0x37
+
+#define RV3028_STATUS_PORF BIT(0)
+#define RV3028_STATUS_EVF BIT(1)
+#define RV3028_STATUS_AF BIT(2)
+#define RV3028_STATUS_TF BIT(3)
+#define RV3028_STATUS_UF BIT(4)
+#define RV3028_STATUS_BSF BIT(5)
+#define RV3028_STATUS_CLKF BIT(6)
+#define RV3028_STATUS_EEBUSY BIT(7)
+
+#define RV3028_CLKOUT_FD_MASK GENMASK(2, 0)
+#define RV3028_CLKOUT_PORIE BIT(3)
+#define RV3028_CLKOUT_CLKSY BIT(6)
+#define RV3028_CLKOUT_CLKOE BIT(7)
+
+#define RV3028_CTRL1_EERD BIT(3)
+#define RV3028_CTRL1_WADA BIT(5)
+
+#define RV3028_CTRL2_RESET BIT(0)
+#define RV3028_CTRL2_12_24 BIT(1)
+#define RV3028_CTRL2_EIE BIT(2)
+#define RV3028_CTRL2_AIE BIT(3)
+#define RV3028_CTRL2_TIE BIT(4)
+#define RV3028_CTRL2_UIE BIT(5)
+#define RV3028_CTRL2_TSE BIT(7)
+
+#define RV3028_EVT_CTRL_TSR BIT(2)
+
+#define RV3028_EEPROM_CMD_UPDATE 0x11
+#define RV3028_EEPROM_CMD_WRITE 0x21
+#define RV3028_EEPROM_CMD_READ 0x22
+
+#define RV3028_EEBUSY_POLL 10000
+#define RV3028_EEBUSY_TIMEOUT 100000
+
+#define RV3028_BACKUP_TCE BIT(5)
+#define RV3028_BACKUP_TCR_MASK GENMASK(1, 0)
+
+#define OFFSET_STEP_PPT 953674
+
+#define RTC_RV3028_LEN 7
+
+static int rv3028_rtc_get(struct udevice *dev, struct rtc_time *tm)
+{
+ u8 regs[RTC_RV3028_LEN];
+ u8 status;
+ int ret;
+
+ ret = dm_i2c_read(dev, RV3028_STATUS, &status, 1);
+ if (ret < 0) {
+ printf("%s: error reading RTC status: %x\n", __func__, ret);
+ return -EIO;
+ }
+
+ if (status & RV3028_STATUS_PORF) {
+ printf("Voltage low, data is invalid.\n");
+ return -EINVAL;
+ }
+
+ ret = dm_i2c_read(dev, RV3028_SEC, regs, sizeof(regs));
+ if (ret < 0) {
+ printf("%s: error reading RTC: %x\n", __func__, ret);
+ return -EIO;
+ }
+
+ tm->tm_sec = bcd2bin(regs[RV3028_SEC] & 0x7f);
+ tm->tm_min = bcd2bin(regs[RV3028_MIN] & 0x7f);
+ tm->tm_hour = bcd2bin(regs[RV3028_HOUR] & 0x3f);
+ tm->tm_wday = regs[RV3028_WDAY] & 0x7;
+ tm->tm_mday = bcd2bin(regs[RV3028_DAY] & 0x3f);
+ tm->tm_mon = bcd2bin(regs[RV3028_MONTH] & 0x1f);
+ tm->tm_year = bcd2bin(regs[RV3028_YEAR]) + 2000;
+ tm->tm_yday = 0;
+ tm->tm_isdst = 0;
+
+ debug("%s: %4d-%02d-%02d (wday=%d) %2d:%02d:%02d\n",
+ __func__, tm->tm_year, tm->tm_mon, tm->tm_mday,
+ tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+ return 0;
+}
+
+static int rv3028_rtc_set(struct udevice *dev, const struct rtc_time *tm)
+{
+ u8 regs[RTC_RV3028_LEN];
+ u8 status;
+ int ret;
+
+ debug("%s: %4d-%02d-%02d (wday=%d( %2d:%02d:%02d\n",
+ __func__, tm->tm_year, tm->tm_mon, tm->tm_mday,
+ tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+
+ if (tm->tm_year < 2000) {
+ printf("%s: year %d (before 2000) not supported\n",
+ __func__, tm->tm_year);
+ return -EINVAL;
+ }
+
+ regs[RV3028_SEC] = bin2bcd(tm->tm_sec);
+ regs[RV3028_MIN] = bin2bcd(tm->tm_min);
+ regs[RV3028_HOUR] = bin2bcd(tm->tm_hour);
+ regs[RV3028_WDAY] = tm->tm_wday;
+ regs[RV3028_DAY] = bin2bcd(tm->tm_mday);
+ regs[RV3028_MONTH] = bin2bcd(tm->tm_mon);
+ regs[RV3028_YEAR] = bin2bcd(tm->tm_year - 2000);
+
+ ret = dm_i2c_write(dev, RV3028_SEC, regs, sizeof(regs));
+ if (ret) {
+ printf("%s: set rtc error: %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = dm_i2c_read(dev, RV3028_STATUS, &status, 1);
+ if (ret < 0) {
+ printf("%s: error reading RTC status: %x\n", __func__, ret);
+ return -EIO;
+ }
+ status |= RV3028_STATUS_PORF;
+ return dm_i2c_write(dev, RV3028_STATUS, &status, 1);
+}
+
+static int rv3028_rtc_reset(struct udevice *dev)
+{
+ return 0;
+}
+
+static int rv3028_rtc_read8(struct udevice *dev, unsigned int reg)
+{
+ u8 data;
+ int ret;
+
+ ret = dm_i2c_read(dev, reg, &data, sizeof(data));
+ return ret < 0 ? ret : data;
+}
+
+static int rv3028_rtc_write8(struct udevice *dev, unsigned int reg, int val)
+{
+ u8 data = val;
+
+ return dm_i2c_write(dev, reg, &data, 1);
+}
+
+static int rv3028_probe(struct udevice *dev)
+{
+ i2c_set_chip_flags(dev, DM_I2C_CHIP_RD_ADDRESS |
+ DM_I2C_CHIP_WR_ADDRESS);
+
+ return 0;
+}
+
+static const struct rtc_ops rv3028_rtc_ops = {
+ .get = rv3028_rtc_get,
+ .set = rv3028_rtc_set,
+ .read8 = rv3028_rtc_read8,
+ .write8 = rv3028_rtc_write8,
+ .reset = rv3028_rtc_reset,
+};
+
+static const struct udevice_id rv3028_rtc_ids[] = {
+ { .compatible = "microcrystal,rv3028" },
+ { }
+};
+
+U_BOOT_DRIVER(rtc_rv3028) = {
+ .name = "rtc-rv3028",
+ .id = UCLASS_RTC,
+ .probe = rv3028_probe,
+ .of_match = rv3028_rtc_ids,
+ .ops = &rv3028_rtc_ops,
+};
diff --git a/drivers/rtc/sandbox_rtc.c b/drivers/rtc/sandbox_rtc.c
index d0864b1df97..657e5c7be2c 100644
--- a/drivers/rtc/sandbox_rtc.c
+++ b/drivers/rtc/sandbox_rtc.c
@@ -79,6 +79,18 @@ struct acpi_ops sandbox_rtc_acpi_ops = {
};
#endif
+static int sandbox_rtc_bind(struct udevice *dev)
+{
+#if CONFIG_IS_ENABLED(PLATDATA)
+ struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev);
+
+ /* Set up the emul_idx for i2c_emul_find() */
+ i2c_emul_set_idx(dev, plat->dtplat.sandbox_emul->idx);
+#endif
+
+ return 0;
+}
+
static const struct rtc_ops sandbox_rtc_ops = {
.get = sandbox_rtc_get,
.set = sandbox_rtc_set,
@@ -97,5 +109,6 @@ U_BOOT_DRIVER(sandbox_rtc) = {
.id = UCLASS_RTC,
.of_match = sandbox_rtc_ids,
.ops = &sandbox_rtc_ops,
+ .bind = sandbox_rtc_bind,
ACPI_OPS_PTR(&sandbox_rtc_acpi_ops)
};