drivers/i2c: Add a new RTC RV-3028-C7 from Micro Crystal

This patch adds a driver for a new RTC from Micro Crystal. Supported
features are:
 * configure backup voltage switchover via devicetree
 * configure backup capacitor charging mode via devicetree
 * set date if a voltage drop on backup voltage was detected
   to either a user definable (devicetree) or coreboot build date

Change-Id: I37176ea726e50e4e74d409488981d7618ecff8bb
Signed-off-by: Werner Zeh <werner.zeh@siemens.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/67099
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Mario Scheithauer <mario.scheithauer@siemens.com>
This commit is contained in:
Werner Zeh 2022-08-26 13:40:47 +02:00 committed by Felix Held
parent 2c789782ad
commit 5fb66adc32
5 changed files with 289 additions and 0 deletions

View File

@ -0,0 +1,5 @@
config DRIVERS_I2C_RV3028C7
bool
default n
help
Enable support for external RTC chip RV-3028-C7

View File

@ -0,0 +1 @@
ramstage-$(CONFIG_DRIVERS_I2C_RV3028C7) += rv3028c7.c

View File

@ -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__ */

View File

@ -0,0 +1,194 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <commonlib/bsd/bcd.h>
#include <console/console.h>
#include <delay.h>
#include <device/device.h>
#include <device/i2c.h>
#include <device/i2c_bus.h>
#include <timer.h>
#include <types.h>
#include <version.h>
#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
};

View File

@ -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_ */