diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 13 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 2 | ||||
-rw-r--r-- | drivers/rtc/armada38x.c | 184 | ||||
-rw-r--r-- | drivers/rtc/i2c_rtc_emul.c | 21 | ||||
-rw-r--r-- | drivers/rtc/rv3028.c | 208 | ||||
-rw-r--r-- | drivers/rtc/sandbox_rtc.c | 13 |
6 files changed, 421 insertions, 20 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index aa6d90158cd..c84a9d2b27b 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 @@ -95,6 +102,12 @@ config RTC_PCF8563 If you say yes here you get support for the Philips PCF8563 RTC and compatible chips. +config RTC_RV3028 + bool "Enable RV3028 driver" + depends on DM_RTC + help + The MicroCrystal RV3028 is a I2C Real Time Clock (RTC) + config RTC_RV3029 bool "Enable RV3029 driver" depends on DM_RTC diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6a45a9c8749..f668cf9050c 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 @@ -46,6 +47,7 @@ obj-$(CONFIG_RTC_PCF2127) += pcf2127.o obj-$(CONFIG_RTC_PL031) += pl031.o obj-$(CONFIG_RTC_PT7C4338) += pt7c4338.o obj-$(CONFIG_RTC_RS5C372A) += rs5c372.o +obj-$(CONFIG_RTC_RV3028) += rv3028.o obj-$(CONFIG_RTC_RV3029) += rv3029.o obj-$(CONFIG_RTC_RV8803) += rv8803.o obj-$(CONFIG_RTC_RX8025) += rx8025.o diff --git a/drivers/rtc/armada38x.c b/drivers/rtc/armada38x.c new file mode 100644 index 00000000000..2d264acf779 --- /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.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, +}; diff --git a/drivers/rtc/i2c_rtc_emul.c b/drivers/rtc/i2c_rtc_emul.c index f25b976e54d..ba418c25daf 100644 --- a/drivers/rtc/i2c_rtc_emul.c +++ b/drivers/rtc/i2c_rtc_emul.c @@ -28,25 +28,6 @@ #define debug_buffer(x, ...) #endif -/** - * struct sandbox_i2c_rtc_plat_data - platform data for the RTC - * - * @base_time: Base system time when RTC device was bound - * @offset: RTC offset from current system time - * @use_system_time: true to use system time, false to use @base_time - * @reg: Register values - */ -struct sandbox_i2c_rtc_plat_data { - long base_time; - long offset; - bool use_system_time; - u8 reg[REG_COUNT]; -}; - -struct sandbox_i2c_rtc { - unsigned int offset_secs; -}; - long sandbox_i2c_rtc_set_offset(struct udevice *dev, bool use_system_time, int offset) { @@ -223,7 +204,7 @@ static int sandbox_i2c_rtc_bind(struct udevice *dev) } static const struct udevice_id sandbox_i2c_rtc_ids[] = { - { .compatible = "sandbox,i2c-rtc" }, + { .compatible = "sandbox,i2c-rtc-emul" }, { } }; diff --git a/drivers/rtc/rv3028.c b/drivers/rtc/rv3028.c new file mode 100644 index 00000000000..9f63afc14a8 --- /dev/null +++ b/drivers/rtc/rv3028.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * RTC driver for the Micro Crystal RV3028 + * + * based on linux driver from + * Copyright (C) 2019 Micro Crystal SA + * + * Alexandre Belloni <alexandre.belloni@bootlin.com> + * + */ + +#include <dm.h> +#include <i2c.h> +#include <rtc.h> + +#define RV3028_SEC 0x00 +#define RV3028_MIN 0x01 +#define RV3028_HOUR 0x02 +#define RV3028_WDAY 0x03 +#define RV3028_DAY 0x04 +#define RV3028_MONTH 0x05 +#define RV3028_YEAR 0x06 +#define RV3028_ALARM_MIN 0x07 +#define RV3028_ALARM_HOUR 0x08 +#define RV3028_ALARM_DAY 0x09 +#define RV3028_STATUS 0x0E +#define RV3028_CTRL1 0x0F +#define RV3028_CTRL2 0x10 +#define RV3028_EVT_CTRL 0x13 +#define RV3028_TS_COUNT 0x14 +#define RV3028_TS_SEC 0x15 +#define RV3028_RAM1 0x1F +#define RV3028_EEPROM_ADDR 0x25 +#define RV3028_EEPROM_DATA 0x26 +#define RV3028_EEPROM_CMD 0x27 +#define RV3028_CLKOUT 0x35 +#define RV3028_OFFSET 0x36 +#define RV3028_BACKUP 0x37 + +#define RV3028_STATUS_PORF BIT(0) +#define RV3028_STATUS_EVF BIT(1) +#define RV3028_STATUS_AF BIT(2) +#define RV3028_STATUS_TF BIT(3) +#define RV3028_STATUS_UF BIT(4) +#define RV3028_STATUS_BSF BIT(5) +#define RV3028_STATUS_CLKF BIT(6) +#define RV3028_STATUS_EEBUSY BIT(7) + +#define RV3028_CLKOUT_FD_MASK GENMASK(2, 0) +#define RV3028_CLKOUT_PORIE BIT(3) +#define RV3028_CLKOUT_CLKSY BIT(6) +#define RV3028_CLKOUT_CLKOE BIT(7) + +#define RV3028_CTRL1_EERD BIT(3) +#define RV3028_CTRL1_WADA BIT(5) + +#define RV3028_CTRL2_RESET BIT(0) +#define RV3028_CTRL2_12_24 BIT(1) +#define RV3028_CTRL2_EIE BIT(2) +#define RV3028_CTRL2_AIE BIT(3) +#define RV3028_CTRL2_TIE BIT(4) +#define RV3028_CTRL2_UIE BIT(5) +#define RV3028_CTRL2_TSE BIT(7) + +#define RV3028_EVT_CTRL_TSR BIT(2) + +#define RV3028_EEPROM_CMD_UPDATE 0x11 +#define RV3028_EEPROM_CMD_WRITE 0x21 +#define RV3028_EEPROM_CMD_READ 0x22 + +#define RV3028_EEBUSY_POLL 10000 +#define RV3028_EEBUSY_TIMEOUT 100000 + +#define RV3028_BACKUP_TCE BIT(5) +#define RV3028_BACKUP_TCR_MASK GENMASK(1, 0) + +#define OFFSET_STEP_PPT 953674 + +#define RTC_RV3028_LEN 7 + +static int rv3028_rtc_get(struct udevice *dev, struct rtc_time *tm) +{ + u8 regs[RTC_RV3028_LEN]; + u8 status; + int ret; + + ret = dm_i2c_read(dev, RV3028_STATUS, &status, 1); + if (ret < 0) { + printf("%s: error reading RTC status: %x\n", __func__, ret); + return -EIO; + } + + if (status & RV3028_STATUS_PORF) { + printf("Voltage low, data is invalid.\n"); + return -EINVAL; + } + + ret = dm_i2c_read(dev, RV3028_SEC, regs, sizeof(regs)); + if (ret < 0) { + printf("%s: error reading RTC: %x\n", __func__, ret); + return -EIO; + } + + tm->tm_sec = bcd2bin(regs[RV3028_SEC] & 0x7f); + tm->tm_min = bcd2bin(regs[RV3028_MIN] & 0x7f); + tm->tm_hour = bcd2bin(regs[RV3028_HOUR] & 0x3f); + tm->tm_wday = regs[RV3028_WDAY] & 0x7; + tm->tm_mday = bcd2bin(regs[RV3028_DAY] & 0x3f); + tm->tm_mon = bcd2bin(regs[RV3028_MONTH] & 0x1f); + tm->tm_year = bcd2bin(regs[RV3028_YEAR]) + 2000; + tm->tm_yday = 0; + tm->tm_isdst = 0; + + debug("%s: %4d-%02d-%02d (wday=%d) %2d:%02d:%02d\n", + __func__, tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + return 0; +} + +static int rv3028_rtc_set(struct udevice *dev, const struct rtc_time *tm) +{ + u8 regs[RTC_RV3028_LEN]; + u8 status; + int ret; + + debug("%s: %4d-%02d-%02d (wday=%d( %2d:%02d:%02d\n", + __func__, tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + if (tm->tm_year < 2000) { + printf("%s: year %d (before 2000) not supported\n", + __func__, tm->tm_year); + return -EINVAL; + } + + regs[RV3028_SEC] = bin2bcd(tm->tm_sec); + regs[RV3028_MIN] = bin2bcd(tm->tm_min); + regs[RV3028_HOUR] = bin2bcd(tm->tm_hour); + regs[RV3028_WDAY] = tm->tm_wday; + regs[RV3028_DAY] = bin2bcd(tm->tm_mday); + regs[RV3028_MONTH] = bin2bcd(tm->tm_mon); + regs[RV3028_YEAR] = bin2bcd(tm->tm_year - 2000); + + ret = dm_i2c_write(dev, RV3028_SEC, regs, sizeof(regs)); + if (ret) { + printf("%s: set rtc error: %d\n", __func__, ret); + return ret; + } + + ret = dm_i2c_read(dev, RV3028_STATUS, &status, 1); + if (ret < 0) { + printf("%s: error reading RTC status: %x\n", __func__, ret); + return -EIO; + } + status |= RV3028_STATUS_PORF; + return dm_i2c_write(dev, RV3028_STATUS, &status, 1); +} + +static int rv3028_rtc_reset(struct udevice *dev) +{ + return 0; +} + +static int rv3028_rtc_read8(struct udevice *dev, unsigned int reg) +{ + u8 data; + int ret; + + ret = dm_i2c_read(dev, reg, &data, sizeof(data)); + return ret < 0 ? ret : data; +} + +static int rv3028_rtc_write8(struct udevice *dev, unsigned int reg, int val) +{ + u8 data = val; + + return dm_i2c_write(dev, reg, &data, 1); +} + +static int rv3028_probe(struct udevice *dev) +{ + i2c_set_chip_flags(dev, DM_I2C_CHIP_RD_ADDRESS | + DM_I2C_CHIP_WR_ADDRESS); + + return 0; +} + +static const struct rtc_ops rv3028_rtc_ops = { + .get = rv3028_rtc_get, + .set = rv3028_rtc_set, + .read8 = rv3028_rtc_read8, + .write8 = rv3028_rtc_write8, + .reset = rv3028_rtc_reset, +}; + +static const struct udevice_id rv3028_rtc_ids[] = { + { .compatible = "microcrystal,rv3028" }, + { } +}; + +U_BOOT_DRIVER(rtc_rv3028) = { + .name = "rtc-rv3028", + .id = UCLASS_RTC, + .probe = rv3028_probe, + .of_match = rv3028_rtc_ids, + .ops = &rv3028_rtc_ops, +}; diff --git a/drivers/rtc/sandbox_rtc.c b/drivers/rtc/sandbox_rtc.c index d0864b1df97..657e5c7be2c 100644 --- a/drivers/rtc/sandbox_rtc.c +++ b/drivers/rtc/sandbox_rtc.c @@ -79,6 +79,18 @@ struct acpi_ops sandbox_rtc_acpi_ops = { }; #endif +static int sandbox_rtc_bind(struct udevice *dev) +{ +#if CONFIG_IS_ENABLED(PLATDATA) + struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev); + + /* Set up the emul_idx for i2c_emul_find() */ + i2c_emul_set_idx(dev, plat->dtplat.sandbox_emul->idx); +#endif + + return 0; +} + static const struct rtc_ops sandbox_rtc_ops = { .get = sandbox_rtc_get, .set = sandbox_rtc_set, @@ -97,5 +109,6 @@ U_BOOT_DRIVER(sandbox_rtc) = { .id = UCLASS_RTC, .of_match = sandbox_rtc_ids, .ops = &sandbox_rtc_ops, + .bind = sandbox_rtc_bind, ACPI_OPS_PTR(&sandbox_rtc_acpi_ops) }; |