On Friday 26 February 2021 10:30:19 Marek Behún wrote: > Add RTC driver for Armada 38x, based on Linux' driver. > For now implement only `marvell,armada-380-rtc` compatible. > > Signed-off-by: Marek Behún <marek.be...@nic.cz> > Reviewed-by: Stefan Roese <s...@denx.de> > Cc: Pali Rohár <p...@kernel.org>
Acked-by: Pali Rohár <p...@kernel.org> > Cc: Baruch Siach <bar...@tkos.co.il> > Cc: Chris Packham <judge.pack...@gmail.com> > Cc: Simon Glass <s...@chromium.org> > --- > drivers/rtc/Kconfig | 7 ++ > drivers/rtc/Makefile | 1 + > drivers/rtc/armada38x.c | 184 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 192 insertions(+) > create mode 100644 drivers/rtc/armada38x.c > > diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig > index aa6d90158c..dafba35279 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 > diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile > index 6a45a9c874..15609e7b18 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 > diff --git a/drivers/rtc/armada38x.c b/drivers/rtc/armada38x.c > new file mode 100644 > index 0000000000..2d264acf77 > --- /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.be...@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, > +}; > -- > 2.26.2 >