diff --git a/src/drivers/i2c/rv3028c7/Kconfig b/src/drivers/i2c/rv3028c7/Kconfig new file mode 100644 index 0000000000..543b24c9f2 --- /dev/null +++ b/src/drivers/i2c/rv3028c7/Kconfig @@ -0,0 +1,5 @@ +config DRIVERS_I2C_RV3028C7 + bool + default n + help + Enable support for external RTC chip RV-3028-C7 diff --git a/src/drivers/i2c/rv3028c7/Makefile.inc b/src/drivers/i2c/rv3028c7/Makefile.inc new file mode 100644 index 0000000000..57eddf39c5 --- /dev/null +++ b/src/drivers/i2c/rv3028c7/Makefile.inc @@ -0,0 +1 @@ +ramstage-$(CONFIG_DRIVERS_I2C_RV3028C7) += rv3028c7.c diff --git a/src/drivers/i2c/rv3028c7/chip.h b/src/drivers/i2c/rv3028c7/chip.h new file mode 100644 index 0000000000..0ac400531b --- /dev/null +++ b/src/drivers/i2c/rv3028c7/chip.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __DRIVERS_I2C_RV3028C7_CHIP_H__ +#define __DRIVERS_I2C_RV3028C7_CHIP_H__ + +#include "rv3028c7.h" + +/* + * The RTC has three different modes in how the backup voltage is used: + * - OFF: Backup voltage not used + * - DIRECT: Switch to backup voltage if it is higher than VDD + * - LEVEL: Switch to backup voltage if VDD is < 2 V and VBACKUP > 2 V + */ +enum sw_mode { + BACKUP_SW_DIRECT = 1, + BACKUP_SW_OFF, + BACKUP_SW_LEVEL +}; + +/* + * The RTC can be used to charge a capacitor on VBACKUP. + * There are the following modes: + * - OFF: Do not charge the backup capacitor via VDD + * - VIA_3K: Connect the backup capacitor via 3 k resistor with VDD + * - VIA_5K: Connect the backup capacitor via 5 k resistor with VDD + * - VIA_9K: Connect the backup capacitor via 9 k resistor with VDD + * - VIA_15K: Connect the backup capacitor via 15 k resistor with VDD + */ +enum charge_mode { + CHARGE_OFF = 0, + CHARGE_VIA_3K, + CHARGE_VIA_5K, + CHARGE_VIA_9K, + CHARGE_VIA_15K +}; + +struct drivers_i2c_rv3028c7_config { + unsigned char user_weekday; /* User day of the week to set */ + unsigned char user_day; /* User day to set */ + unsigned char user_month; /* User month to set */ + unsigned char user_year; /* User year to set (2-digit) */ + unsigned char set_user_date; /* Use user date from devicetree */ + enum sw_mode bckup_sw_mode; /* Mode for switching between VDD and VBACKUP */ + enum charge_mode cap_charge; /* Mode for capacitor charging */ +}; + +#endif /* __DRIVERS_I2C_RV3028C7_CHIP_H__ */ diff --git a/src/drivers/i2c/rv3028c7/rv3028c7.c b/src/drivers/i2c/rv3028c7/rv3028c7.c new file mode 100644 index 0000000000..e7e3b47ece --- /dev/null +++ b/src/drivers/i2c/rv3028c7/rv3028c7.c @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "chip.h" +#include "rv3028c7.h" + +static enum cb_err rtc_eep_wait_ready(struct device *dev) +{ + struct stopwatch sw; + uint8_t status; + + stopwatch_init_msecs_expire(&sw, EEP_SYNC_TIMEOUT_MS); + do { + status = (uint8_t)i2c_dev_readb_at(dev, STATUS_REG); + mdelay(1); + } while ((status & EE_BUSY_BIT) && !stopwatch_expired(&sw)); + + if (status & EE_BUSY_BIT) { + return CB_ERR; + } else { + return CB_SUCCESS; + } +} + +static enum cb_err rtc_eep_auto_refresh(struct device *dev, uint8_t state) +{ + uint8_t reg; + + reg = (uint8_t)i2c_dev_readb_at(dev, CTRL1_REG); + reg &= ~EERD_BIT; + if (state == EEP_REFRESH_DIS) + reg |= EERD_BIT; + i2c_dev_writeb_at(dev, CTRL1_REG, reg); + /* Wait until the EEPROM has finished a possible running operation. */ + if (rtc_eep_wait_ready(dev) != CB_SUCCESS) { + printk(BIOS_ERR, "%s: EEPROM access timed out (%d ms)!\n", + dev->chip_ops->name, EEP_SYNC_TIMEOUT_MS); + return CB_ERR; + } + return CB_SUCCESS; +} + +static enum cb_err rtc_eep_start_update(struct device *dev) +{ + /* Disable EEPROM auto refresh before writing RAM to EEPROM + to avoid race conditions. */ + if (rtc_eep_auto_refresh(dev, EEP_REFRESH_DIS)) + return CB_ERR; + + /* Now start the update cycle.*/ + i2c_dev_writeb_at(dev, EEP_CMD_REG, EEP_CMD_PREFIX); + i2c_dev_writeb_at(dev, EEP_CMD_REG, EEP_CMD_UPDATE); + return CB_SUCCESS; +} + +static void rtc_set_time_date(struct device *dev) +{ + struct drivers_i2c_rv3028c7_config *config = dev->chip_info; + uint8_t buf[7]; + + /* The buffer contains the seconds through years of the new time and date. + Whenever a new date is set, the time is set to 00:00:00. */ + buf[0] = 0; /* Entry for seconds. */ + buf[1] = 0; /* Entry for minutes. */ + buf[2] = 0; /* Entry for hours. */ + if (config->set_user_date) { + buf[3] = config->user_weekday; + buf[4] = bin2bcd(config->user_day); + buf[5] = bin2bcd(config->user_month); + buf[6] = bin2bcd(config->user_year); + printk(BIOS_DEBUG, "%s: Set to user date\n", dev->chip_ops->name); + } else { + buf[3] = coreboot_build_date.weekday; + buf[4] = coreboot_build_date.day; + buf[5] = coreboot_build_date.month; + buf[6] = coreboot_build_date.year; + printk(BIOS_DEBUG, "%s: Set to coreboot build date\n", dev->chip_ops->name); + } + /* According to the datasheet, date and time should be transferred in "one go" + in order to avoid value corruption. */ + if (i2c_dev_write_at(dev, buf, sizeof(buf), 0) != sizeof(buf)) { + printk(BIOS_ERR, "%s: Not able to set date and time!\n", dev->chip_ops->name); + } +} + +static void rtc_final(struct device *dev) +{ + uint8_t buf[7]; + + /* Read back current RTC date and time and print it to the console. + Date and time are read in "one go", the buffer contains seconds (byte 0) + through years (byte 6) after this read. */ + if (i2c_dev_read_at(dev, buf, sizeof(buf), 0) != sizeof(buf)) { + printk(BIOS_ERR, "%s: Not able to read current date and time!\n", + dev->chip_ops->name); + } else { + printk(BIOS_INFO, "%s: Current date %02d.%02d.%02d %02d:%02d:%02d\n", + dev->chip_ops->name, bcd2bin(buf[5]), bcd2bin(buf[4]), + bcd2bin(buf[6]), bcd2bin(buf[2]), bcd2bin(buf[1]), + bcd2bin(buf[0])); + } + /* Make sure the EEPROM automatic refresh is enabled. */ + if (rtc_eep_auto_refresh(dev, EEP_REFRESH_EN) != CB_SUCCESS) { + printk(BIOS_ERR, "%s: Not able to enable EEPROM auto refresh!\n", + dev->chip_ops->name); + } +} + +static void rtc_init(struct device *dev) +{ + struct drivers_i2c_rv3028c7_config *config = dev->chip_info; + uint8_t reg, backup_reg, eep_update_needed = 0; + + /* On every startup, the RTC synchronizes the internal EEPROM with RAM. + * During this time no operation shall modify RAM registers. Ensure this + * sync is finished before starting the initialization. */ + if (rtc_eep_wait_ready(dev) != CB_SUCCESS) { + printk(BIOS_WARNING, "%s: Timeout on EEPROM sync after power on!\n", + dev->chip_ops->name); + return; + } + reg = backup_reg = (uint8_t)i2c_dev_readb_at(dev, EEP_BACKUP_REG); + /* Configure the switch-over setting according to devicetree. */ + if (config->bckup_sw_mode) { + reg &= ~BSM_MASK; + reg |= config->bckup_sw_mode << BSM_BIT; + } + /* Configure the VBACKUP charging mode. */ + if (config->cap_charge) { + reg &= ~TCR_MASK; + reg |= ((config->cap_charge - 1) << TCR_BIT); + reg |= TCE_BIT; + } else { + reg &= ~TCE_BIT; + } + /* According to the datasheet the Fast Edge Detection Enable (FEDE) bit + should always be set. */ + reg |= FEDE_BIT; + if (reg != backup_reg) { + /* Write new register value into shadow RAM and request an EEPROM update. */ + i2c_dev_writeb_at(dev, EEP_BACKUP_REG, reg); + eep_update_needed = 1; + } + /* Make sure the hour register is in 24h format.*/ + reg = (uint8_t)i2c_dev_readb_at(dev, CTRL2_REG); + if (reg & HOUR_12_24_BIT) { + reg &= ~HOUR_12_24_BIT; + i2c_dev_writeb_at(dev, CTRL2_REG, reg); + } + /* Check for a possible voltage drop event. */ + reg = (uint8_t)i2c_dev_readb_at(dev, STATUS_REG); + if (reg & PORF_BIT) { + /* Voltage drop was detected, date and time needs to be set properly. */ + rtc_set_time_date(dev); + /* Clear the PORF bit to mark that the event was handled. */ + reg &= ~PORF_BIT; + i2c_dev_writeb_at(dev, STATUS_REG, reg); + } + /* + * Finally, trigger the EEPROM update procedure if needed. + * According to the datasheet, this update will consume ~63 ms. + * In order to not block the boot process here waiting for this update being finished, + * trigger the update now and check for readiness in the final hook. + */ + if (eep_update_needed && rtc_eep_start_update(dev) != CB_SUCCESS) { + printk(BIOS_ERR, "%s: Not able to trigger EEPROM update!\n", + dev->chip_ops->name); + } +} + +static struct device_operations rv3028c7_ops = { + .read_resources = noop_read_resources, + .set_resources = noop_set_resources, + .init = rtc_init, + .final = rtc_final, +}; + +static void rtc_enable(struct device *dev) +{ + dev->ops = &rv3028c7_ops; +} + +struct chip_operations drivers_i2c_rv3028c7_ops = { + CHIP_NAME("RV-3028-C7") + .enable_dev = rtc_enable +}; diff --git a/src/drivers/i2c/rv3028c7/rv3028c7.h b/src/drivers/i2c/rv3028c7/rv3028c7.h new file mode 100644 index 0000000000..55617a724f --- /dev/null +++ b/src/drivers/i2c/rv3028c7/rv3028c7.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _I2C_RV3028C7_H_ +#define _I2C_RV3028C7_H_ + +/* Register layout */ +#define SECOND_REG 0x00 +#define MINUTE_REG 0x01 +#define HOUR_REG 0x02 +#define WEEK_REG 0x03 +#define DAY_REG 0x04 +#define MONTH_REG 0x05 +#define YEAR_REG 0x06 +#define STATUS_REG 0x0e +#define PORF_BIT (1 << 0) +#define EE_BUSY_BIT (1 << 7) +#define CTRL1_REG 0x0f +#define EERD_BIT (1 << 3) +#define CTRL2_REG 0x10 +#define HOUR_12_24_BIT (1 << 1) +/* Registers for the internal EEPROM */ +#define EEP_ADR_REG 0x25 +#define EEP_DATA_REG 0x26 +#define EEP_CMD_REG 0x27 +#define EEP_CMD_PREFIX 0x00 +#define EEP_CMD_UPDATE 0x11 +#define EEP_BACKUP_REG 0x37 +#define FEDE_BIT (1 << 4) +#define BSM_BIT 2 +#define BSM_MASK (3 << BSM_BIT) +#define TCR_BIT 0 +#define TCR_MASK (3 << TCR_BIT) +#define TCE_BIT (1 << 5) + +#define EEP_REFRESH_EN 1 +#define EEP_REFRESH_DIS 0 + +/* The longest mentioned timeout in the datasheet is 63 ms, + round up to 70 ms to be on the safe side. */ +#define EEP_SYNC_TIMEOUT_MS 70 + +#endif /* _I2C_RV3028C7_H_ */