rtc: add armada38x driver

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.behun@nic.cz>
Reviewed-by: Stefan Roese <sr@denx.de>
Cc: Pali Rohár <pali@kernel.org>
Cc: Baruch Siach <baruch@tkos.co.il>
Cc: Chris Packham <judge.packham@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
Acked-by: Pali Rohár <pali@kernel.org>
This commit is contained in:
Marek Behún 2021-02-26 10:30:19 +01:00 committed by Stefan Roese
parent e9c99db778
commit aefbc2c2a2
3 changed files with 192 additions and 0 deletions

View File

@ -38,6 +38,13 @@ config RTC_ENABLE_32KHZ_OUTPUT
Some real-time clocks support the output of 32kHz square waves (such as ds3231), 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. 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 config RTC_PCF2127
bool "Enable PCF2127 driver" bool "Enable PCF2127 driver"
depends on DM_RTC depends on DM_RTC

View File

@ -8,6 +8,7 @@ obj-$(CONFIG_$(SPL_TPL_)DM_RTC) += rtc-uclass.o
obj-$(CONFIG_RTC_AT91SAM9_RTT) += at91sam9_rtt.o obj-$(CONFIG_RTC_AT91SAM9_RTT) += at91sam9_rtt.o
obj-y += rtc-lib.o obj-y += rtc-lib.o
obj-$(CONFIG_RTC_ARMADA38X) += armada38x.o
obj-$(CONFIG_RTC_DAVINCI) += davinci.o obj-$(CONFIG_RTC_DAVINCI) += davinci.o
obj-$(CONFIG_RTC_DS1302) += ds1302.o obj-$(CONFIG_RTC_DS1302) += ds1302.o
obj-$(CONFIG_RTC_DS1306) += ds1306.o obj-$(CONFIG_RTC_DS1306) += ds1306.o

184
drivers/rtc/armada38x.c Normal file
View File

@ -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,
};