u-boot/drivers/rtc/armada38x.c

185 lines
4.1 KiB
C
Raw Normal View History

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