diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/u-boot/drivers/rtc/armada38x.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/u-boot/drivers/rtc/armada38x.c')
-rw-r--r-- | roms/u-boot/drivers/rtc/armada38x.c | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/rtc/armada38x.c b/roms/u-boot/drivers/rtc/armada38x.c new file mode 100644 index 000000000..2d264acf7 --- /dev/null +++ b/roms/u-boot/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, +}; |