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:
parent
e9c99db778
commit
aefbc2c2a2
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
Loading…
Reference in New Issue