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:
parent
2c789782ad
commit
5fb66adc32
|
@ -0,0 +1,5 @@
|
|||
config DRIVERS_I2C_RV3028C7
|
||||
bool
|
||||
default n
|
||||
help
|
||||
Enable support for external RTC chip RV-3028-C7
|
|
@ -0,0 +1 @@
|
|||
ramstage-$(CONFIG_DRIVERS_I2C_RV3028C7) += rv3028c7.c
|
|
@ -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__ */
|
|
@ -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
|
||||
};
|
|
@ -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_ */
|
Loading…
Reference in New Issue