diff options
author | Chris Packham <judge.packham@gmail.com> | 2023-03-20 10:23:44 +1300 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2023-03-30 15:09:59 -0400 |
commit | 0798a1ce0f6439dfb80d310e11bc8993e09baf08 (patch) | |
tree | fbababa9e40e67408baa71cfa4b9a065bf19763e | |
parent | b5f045e12fea0a64a3749c4da1bdfd579530b67e (diff) | |
download | u-boot-0798a1ce0f6439dfb80d310e11bc8993e09baf08.zip u-boot-0798a1ce0f6439dfb80d310e11bc8993e09baf08.tar.gz u-boot-0798a1ce0f6439dfb80d310e11bc8993e09baf08.tar.bz2 |
drivers: rtc: add max313xx series rtc driver
Adding support for Analog Devices MAX313XX series RTCs.
This is ported from the Linux driver and adapted for use in u-boot.
Notable differences are
- handling of tm_year and tm_mon differ
- clock source support is omitted
- hwmon support for the MAX31328 and MAX31343 is omitted
- rtc_ops->reset is added
Signed-off-by: Chris Packham <judge.packham@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
-rw-r--r-- | configs/sandbox_defconfig | 1 | ||||
-rw-r--r-- | drivers/rtc/Kconfig | 13 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/max313xx.c | 459 |
4 files changed, 474 insertions, 0 deletions
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 2141c3d..cbace25 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -269,6 +269,7 @@ CONFIG_SANDBOX_RESET=y CONFIG_RESET_SYSCON=y CONFIG_RESET_SCMI=y CONFIG_DM_RTC=y +CONFIG_RTC_MAX313XX=y CONFIG_RTC_RV8803=y CONFIG_RTC_HT1380=y CONFIG_SCSI=y diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index fcfda28..2317313 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -134,6 +134,19 @@ config RTC_ISL1208 This driver supports reading and writing the RTC/calendar and detects total power failures. +config RTC_MAX313XX + bool "Analog Devices MAX313XX RTC driver" + depends on DM_RTC + depends on DM_I2C + help + If you say yes here you will get support for the + Analog Devices MAX313XX series RTC family. + + Chip features not currently supported: + - Timestamp registers as SRAM + - Temperature sensor + - CLKOUT generation + config RTC_PCF8563 tristate "Philips PCF8563" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index b6c9029..308fab8 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_RTC_HT1380) += ht1380.o obj-$(CONFIG_$(SPL_TPL_)RTC_SANDBOX) += i2c_rtc_emul.o obj-$(CONFIG_RTC_ISL1208) += isl1208.o obj-$(CONFIG_RTC_M41T62) += m41t62.o +obj-$(CONFIG_RTC_MAX313XX) += max313xx.o obj-$(CONFIG_RTC_MC13XXX) += mc13xxx-rtc.o obj-$(CONFIG_RTC_MC146818) += mc146818.o obj-$(CONFIG_MCFRTC) += mcfrtc.o diff --git a/drivers/rtc/max313xx.c b/drivers/rtc/max313xx.c new file mode 100644 index 0000000..748f3c4 --- /dev/null +++ b/drivers/rtc/max313xx.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices MAX313XX series I2C RTC driver + * + * Copyright 2022 Analog Devices Inc. + */ +#include <bcd.h> +#include <dm.h> +#include <i2c.h> +#include <rtc.h> +#include <dm/device_compat.h> +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/kernel.h> + +/* common registers */ +#define MAX313XX_INT_ALARM1 BIT(0) +#define MAX313XX_INT_ALARM2 BIT(1) +#define MAX313XX_HRS_F_12_24 BIT(6) +#define MAX313XX_HRS_F_AM_PM BIT(5) +#define MAX313XX_MONTH_CENTURY BIT(7) + +#define MAX313XX_TMR_CFG_ENABLE BIT(4) +#define MAX313XX_TMR_CFG_FREQ_MASK GENMASK(1, 0) +#define MAX313XX_TMR_CFG_FREQ_16HZ 0x03 + +#define MAX313XX_REG_MINUTE 0x01 +#define MAX313XX_REG_HOUR 0x02 + +#define MAX313XX_TIME_SIZE 0x07 + +/* device specific registers */ +#define MAX3134X_CFG2_REG 0x01 +#define MAX3134X_CFG2_SET_RTC BIT(1) + +#define MAX31341_TRICKLE_RES_MASK GENMASK(1, 0) +#define MAX31341_TRICKLE_DIODE_EN BIT(2) +#define MAX31341_TRICKLE_ENABLE_BIT BIT(3) +#define MAX31341_POWER_MGMT_REG 0x56 +#define MAX31341_POWER_MGMT_TRICKLE_BIT BIT(0) + +#define MAX3133X_TRICKLE_RES_MASK GENMASK(2, 1) +#define MAX3133X_TRICKLE_DIODE_EN BIT(3) +#define MAX3133X_TRICKLE_ENABLE_BIT BIT(0) + +#define MAX31329_TRICKLE_ENABLE_BIT BIT(7) +#define MAX31343_TRICKLE_ENABLE_MASK GENMASK(7, 4) +#define MAX31343_TRICKLE_ENABLE_CODE 5 +#define MAX31329_43_TRICKLE_RES_MASK GENMASK(1, 0) +#define MAX31329_43_TRICKLE_DIODE_EN BIT(2) + +#define MAX31329_CONFIG2_REG 0x04 +#define MAX31329_CONFIG2_CLKIN_EN BIT(2) +#define MAX31329_CONFIG2_CLKIN_FREQ GENMASK(1, 0) + +#define MAX31341_42_CONFIG1_REG 0x00 +#define MAX31341_42_CONFIG1_CLKIN_EN BIT(7) +#define MAX31341_42_CONFIG1_CLKIN_FREQ GENMASK(5, 4) +#define MAX31341_42_CONFIG1_OSC_DISABLE BIT(3) +#define MAX31341_42_CONFIG1_SWRST BIT(0) + +enum max313xx_ids { + ID_MAX31328, + ID_MAX31329, + ID_MAX31331, + ID_MAX31334, + ID_MAX31341, + ID_MAX31342, + ID_MAX31343, + MAX313XX_ID_NR +}; + +/** + * struct chip_desc - descriptor for MAX313xx variants + * @sec_reg: Offset to seconds register. Used to denote the start of the + * current time registers. + * @alarm1_sec_reg: Offset to Alarm1 seconds register. Used to denote the + * start of the alarm registers. + * @int_en_reg: Offset to the interrupt enable register. + * @int_status_reg: Offset to the interrupt status register. + * @ram_reg: Offset to the timestamp RAM (which can be used as SRAM). + * @ram_size: Size of the timestamp RAM. + * @temp_reg: Offset to the temperature register (or 0 if temperature + * sensor is not supported). + * @trickle_reg: Offset to the trickle charger configuration register (or + * 0 if trickle charger is not supported). + * @rst_reg: Offset to the reset register. + * @rst_bit: Bit within the reset register for the software reset. + */ +struct chip_desc { + u8 sec_reg; + u8 alarm1_sec_reg; + + u8 int_en_reg; + u8 int_status_reg; + + u8 ram_reg; + u8 ram_size; + + u8 temp_reg; + + u8 trickle_reg; + + u8 rst_reg; + u8 rst_bit; +}; + +struct max313xx_priv { + enum max313xx_ids id; + const struct chip_desc *chip; +}; + +static const struct chip_desc chip[MAX313XX_ID_NR] = { + [ID_MAX31328] = { + .int_en_reg = 0x0E, + .int_status_reg = 0x0F, + .sec_reg = 0x00, + .alarm1_sec_reg = 0x07, + .temp_reg = 0x11, + }, + [ID_MAX31329] = { + .int_en_reg = 0x01, + .int_status_reg = 0x00, + .sec_reg = 0x06, + .alarm1_sec_reg = 0x0D, + .ram_reg = 0x22, + .ram_size = 64, + .trickle_reg = 0x19, + .rst_reg = 0x02, + .rst_bit = BIT(0), + }, + [ID_MAX31331] = { + .int_en_reg = 0x01, + .int_status_reg = 0x00, + .sec_reg = 0x08, + .alarm1_sec_reg = 0x0F, + .ram_reg = 0x20, + .ram_size = 32, + .trickle_reg = 0x1B, + .rst_reg = 0x02, + .rst_bit = BIT(0), + }, + [ID_MAX31334] = { + .int_en_reg = 0x01, + .int_status_reg = 0x00, + .sec_reg = 0x09, + .alarm1_sec_reg = 0x10, + .ram_reg = 0x30, + .ram_size = 32, + .trickle_reg = 0x1E, + .rst_reg = 0x02, + .rst_bit = BIT(0), + }, + [ID_MAX31341] = { + .int_en_reg = 0x04, + .int_status_reg = 0x05, + .sec_reg = 0x06, + .alarm1_sec_reg = 0x0D, + .ram_reg = 0x16, + .ram_size = 64, + .trickle_reg = 0x57, + .rst_reg = 0x00, + .rst_bit = BIT(0), + }, + [ID_MAX31342] = { + .int_en_reg = 0x04, + .int_status_reg = 0x05, + .sec_reg = 0x06, + .alarm1_sec_reg = 0x0D, + .rst_reg = 0x00, + .rst_bit = BIT(0), + }, + [ID_MAX31343] = { + .int_en_reg = 0x01, + .int_status_reg = 0x00, + .sec_reg = 0x06, + .alarm1_sec_reg = 0x0D, + .ram_reg = 0x22, + .ram_size = 64, + .temp_reg = 0x1A, + .trickle_reg = 0x19, + .rst_reg = 0x02, + .rst_bit = BIT(0), + }, +}; + +static const u32 max313xx_trickle_ohms[] = { 3000, 6000, 11000 }; + +static int max313xx_set_bits(struct udevice *dev, unsigned int reg, unsigned int bits) +{ + int ret; + + ret = dm_i2c_reg_read(dev, reg); + if (ret < 0) + return ret; + + return dm_i2c_reg_write(dev, reg, ret | bits); +} + +static int max313xx_clear_bits(struct udevice *dev, unsigned int reg, unsigned int bits) +{ + int ret; + + ret = dm_i2c_reg_read(dev, reg); + if (ret < 0) + return ret; + + return dm_i2c_reg_write(dev, reg, ret & ~bits); +} + +static int max313xx_get_hour(u8 hour_reg) +{ + int hour; + + /* 24Hr mode */ + if (!FIELD_GET(MAX313XX_HRS_F_12_24, hour_reg)) + return bcd2bin(hour_reg & 0x3f); + + /* 12Hr mode */ + hour = bcd2bin(hour_reg & 0x1f); + if (hour == 12) + hour = 0; + + if (FIELD_GET(MAX313XX_HRS_F_AM_PM, hour_reg)) + hour += 12; + + return hour; +} + +static int max313xx_read_time(struct udevice *dev, struct rtc_time *t) +{ + struct max313xx_priv *rtc = dev_get_priv(dev); + u8 regs[7]; + int ret; + + ret = dm_i2c_read(dev, rtc->chip->sec_reg, regs, 7); + if (ret) + return ret; + + t->tm_sec = bcd2bin(regs[0] & 0x7f); + t->tm_min = bcd2bin(regs[1] & 0x7f); + t->tm_hour = max313xx_get_hour(regs[2]); + t->tm_wday = bcd2bin(regs[3] & 0x07) - 1; + t->tm_mday = bcd2bin(regs[4] & 0x3f); + t->tm_mon = bcd2bin(regs[5] & 0x1f); + t->tm_year = bcd2bin(regs[6]) + 2000; + + if (FIELD_GET(MAX313XX_MONTH_CENTURY, regs[5])) + t->tm_year += 100; + + dev_dbg(dev, "read %4d-%02d-%02d (wday=%d) %2d:%02d:%02d\n", + t->tm_year, t->tm_mon, t->tm_mday, + t->tm_wday, t->tm_hour, t->tm_min, t->tm_sec); + + return 0; +} + +static int max313xx_set_time(struct udevice *dev, const struct rtc_time *t) +{ + struct max313xx_priv *rtc = dev_get_priv(dev); + u8 regs[7]; + int ret; + + dev_dbg(dev, "set %4d-%02d-%02d (wday=%d) %2d:%02d:%02d\n", + t->tm_year, t->tm_mon, t->tm_mday, + t->tm_wday, t->tm_hour, t->tm_min, t->tm_sec); + + if (t->tm_year < 2000) { + dev_err(dev, "year %d (before 2000) not supported\n", + t->tm_year); + return -EINVAL; + } + + if (rtc->chip->rst_bit) { + ret = max313xx_clear_bits(dev, rtc->chip->rst_reg, rtc->chip->rst_bit); + if (ret) + return ret; + } + + regs[0] = bin2bcd(t->tm_sec); + regs[1] = bin2bcd(t->tm_min); + regs[2] = bin2bcd(t->tm_hour); + regs[3] = bin2bcd(t->tm_wday + 1); + regs[4] = bin2bcd(t->tm_mday); + regs[5] = bin2bcd(t->tm_mon); + regs[6] = bin2bcd((t->tm_year - 2000) % 100); + + if ((t->tm_year - 2000) >= 200) + regs[5] |= FIELD_PREP(MAX313XX_MONTH_CENTURY, 1); + + ret = dm_i2c_write(dev, rtc->chip->sec_reg, regs, 7); + if (ret) + return ret; + + switch (rtc->id) { + case ID_MAX31341: + case ID_MAX31342: + ret = max313xx_set_bits(dev, MAX3134X_CFG2_REG, + MAX3134X_CFG2_SET_RTC); + if (ret) + return ret; + + udelay(10000); + + ret = max313xx_clear_bits(dev, MAX3134X_CFG2_REG, + MAX3134X_CFG2_SET_RTC); + if (ret) + return ret; + + break; + default: + break; + } + + return ret; +} + +static int max313xx_reset(struct udevice *dev) +{ + struct max313xx_priv *rtc = dev_get_priv(dev); + int ret = -EINVAL; + + if (rtc->chip->rst_bit) + ret = max313xx_set_bits(dev, rtc->chip->rst_reg, rtc->chip->rst_bit); + + return ret; +} + +static const struct rtc_ops max3133x_rtc_ops = { + .get = max313xx_read_time, + .set = max313xx_set_time, + .reset = max313xx_reset, +}; + +static int max313xx_init(struct udevice *dev) +{ + struct max313xx_priv *rtc = dev_get_priv(dev); + int ret; + + switch (rtc->id) { + case ID_MAX31341: + case ID_MAX31342: + ret = max313xx_clear_bits(dev, MAX31341_42_CONFIG1_REG, + MAX31341_42_CONFIG1_OSC_DISABLE); + if (ret) + return ret; + + return max313xx_set_bits(dev, MAX31341_42_CONFIG1_REG, + MAX31341_42_CONFIG1_SWRST); + default: + return 0; + } +} + +static int max313xx_trickle_charger_setup(struct udevice *dev) +{ + struct max313xx_priv *rtc = dev_get_priv(dev); + bool diode; + int index, reg; + u32 ohms; + u32 chargeable; + int ret; + + if (dev_read_u32(dev, "trickle-resistor-ohms", &ohms) || + dev_read_u32(dev, "aux-voltage-chargeable", &chargeable)) + return 0; + + switch (chargeable) { + case 0: + diode = false; + break; + case 1: + diode = true; + break; + default: + dev_dbg(dev, "unsupported aux-voltage-chargeable value\n"); + return -EINVAL; + } + + if (!rtc->chip->trickle_reg) { + dev_warn(dev, "device does not have trickle charger\n"); + return -ENOTSUPP; + } + + index = find_closest(ohms, max313xx_trickle_ohms, + ARRAY_SIZE(max313xx_trickle_ohms)) + 1; + + switch (rtc->id) { + case ID_MAX31329: + reg = FIELD_PREP(MAX31329_TRICKLE_ENABLE_BIT, 1) | + FIELD_PREP(MAX31329_43_TRICKLE_RES_MASK, index) | + FIELD_PREP(MAX31329_43_TRICKLE_DIODE_EN, diode); + break; + case ID_MAX31331: + case ID_MAX31334: + reg = FIELD_PREP(MAX3133X_TRICKLE_ENABLE_BIT, 1) | + FIELD_PREP(MAX3133X_TRICKLE_DIODE_EN, diode) | + FIELD_PREP(MAX3133X_TRICKLE_RES_MASK, index); + break; + case ID_MAX31341: + if (index == 1) + index = 0; + reg = FIELD_PREP(MAX31341_TRICKLE_ENABLE_BIT, 1) | + FIELD_PREP(MAX31341_TRICKLE_DIODE_EN, diode) | + FIELD_PREP(MAX31341_TRICKLE_RES_MASK, index); + + ret = max313xx_set_bits(dev, MAX31341_POWER_MGMT_REG, + MAX31341_POWER_MGMT_TRICKLE_BIT); + if (ret) + return ret; + + break; + case ID_MAX31343: + reg = FIELD_PREP(MAX31329_43_TRICKLE_RES_MASK, index) | + FIELD_PREP(MAX31329_43_TRICKLE_DIODE_EN, diode) | + FIELD_PREP(MAX31343_TRICKLE_ENABLE_MASK, + MAX31343_TRICKLE_ENABLE_CODE); + break; + default: + return -EOPNOTSUPP; + } + + return dm_i2c_reg_write(dev, rtc->chip->trickle_reg, reg); +} + +static int max313xx_probe(struct udevice *dev) +{ + struct max313xx_priv *max313xx = dev_get_priv(dev); + int ret; + + max313xx->id = dev_get_driver_data(dev); + max313xx->chip = &chip[max313xx->id]; + + ret = max313xx_init(dev); + if (ret) + return ret; + + return max313xx_trickle_charger_setup(dev); +} + +static const struct udevice_id max313xx_of_id[] = { + { .compatible = "adi,max31328", .data = ID_MAX31328 }, + { .compatible = "adi,max31329", .data = ID_MAX31329 }, + { .compatible = "adi,max31331", .data = ID_MAX31331 }, + { .compatible = "adi,max31334", .data = ID_MAX31334 }, + { .compatible = "adi,max31341", .data = ID_MAX31341 }, + { .compatible = "adi,max31342", .data = ID_MAX31342 }, + { .compatible = "adi,max31343", .data = ID_MAX31343 }, + { } +}; + +U_BOOT_DRIVER(rtc_max313xx) = { + .name = "rtc-max313xx", + .id = UCLASS_RTC, + .probe = max313xx_probe, + .of_match = max313xx_of_id, + .priv_auto = sizeof(struct max313xx_priv), + .ops = &max3133x_rtc_ops, +}; |