From patchwork Tue Aug 14 09:51:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Philipp Tomsich X-Patchwork-Id: 957408 X-Patchwork-Delegate: philipp.tomsich@theobroma-systems.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=theobroma-systems.com Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 41qSXv4Q8Qz9s5b for ; Tue, 14 Aug 2018 19:51:27 +1000 (AEST) Received: by lists.denx.de (Postfix, from userid 105) id 2590DC21E0B; Tue, 14 Aug 2018 09:51:17 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id D7C1CC21C93; Tue, 14 Aug 2018 09:51:14 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 775E6C21C93; Tue, 14 Aug 2018 09:51:13 +0000 (UTC) Received: from mail.theobroma-systems.com (vegas.theobroma-systems.com [144.76.126.164]) by lists.denx.de (Postfix) with ESMTPS id 6AB21C21C27 for ; Tue, 14 Aug 2018 09:51:12 +0000 (UTC) Received: from [86.59.122.178] (port=50372 helo=android.lan) by mail.theobroma-systems.com with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA256:128) (Exim 4.80) (envelope-from ) id 1fpVyo-0007Pg-Si; Tue, 14 Aug 2018 11:51:11 +0200 From: Philipp Tomsich To: u-boot@lists.denx.de Date: Tue, 14 Aug 2018 11:51:00 +0200 Message-Id: <1534240262-8904-2-git-send-email-philipp.tomsich@theobroma-systems.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1534240262-8904-1-git-send-email-philipp.tomsich@theobroma-systems.com> References: <1534240262-8904-1-git-send-email-philipp.tomsich@theobroma-systems.com> Cc: Martyn Welch , Heinrich Schuchardt , Nandor Han , Sebastian Reichel , Chris Packham Subject: [U-Boot] [PATCH 1/3] rtc: rv3029: add to Kconfig X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" The MicroCrystal RV3029 driver didn't have a Kconfig entry and was not used anywhere. Add it to Kconfig to make it selectable. Signed-off-by: Philipp Tomsich Tested-by: Klaus Goger --- drivers/rtc/Kconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 5436509..8a6e796 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -42,6 +42,16 @@ config RTC_ISL1208 This driver supports reading and writing the RTC/calendar and detects total power failures. +config RTC_RV3029 + bool "Enable RV3029 driver" + depends on DM_RTC + help + The MicroCrystal RV3029 is a I2C Real Time Clock (RTC) with 8-byte + battery-backed SRAM. + + This driver supports reading and writing the RTC/calendar and the + battery-baced SRAM section. + config RTC_RX8010SJ bool "Enable RX8010SJ driver" depends on DM_RTC From patchwork Tue Aug 14 09:51:01 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Philipp Tomsich X-Patchwork-Id: 957411 X-Patchwork-Delegate: philipp.tomsich@theobroma-systems.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=theobroma-systems.com Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 41qSbK5vm0z9s5b for ; Tue, 14 Aug 2018 19:53:33 +1000 (AEST) Received: by lists.denx.de (Postfix, from userid 105) id 828C9C21E1D; Tue, 14 Aug 2018 09:52:20 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id DD6EFC21DB6; Tue, 14 Aug 2018 09:51:18 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 20A5DC21D8A; Tue, 14 Aug 2018 09:51:15 +0000 (UTC) Received: from mail.theobroma-systems.com (vegas.theobroma-systems.com [144.76.126.164]) by lists.denx.de (Postfix) with ESMTPS id D8148C21C27 for ; Tue, 14 Aug 2018 09:51:13 +0000 (UTC) Received: from [86.59.122.178] (port=50372 helo=android.lan) by mail.theobroma-systems.com with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA256:128) (Exim 4.80) (envelope-from ) id 1fpVyp-0007Pg-IN; Tue, 14 Aug 2018 11:51:11 +0200 From: Philipp Tomsich To: u-boot@lists.denx.de Date: Tue, 14 Aug 2018 11:51:01 +0200 Message-Id: <1534240262-8904-3-git-send-email-philipp.tomsich@theobroma-systems.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1534240262-8904-1-git-send-email-philipp.tomsich@theobroma-systems.com> References: <1534240262-8904-1-git-send-email-philipp.tomsich@theobroma-systems.com> Cc: Stephen Warren , Angelo Dureghello Subject: [U-Boot] [PATCH 2/3] rtc: rv3029: update to support DM and sync with Linux 4.17 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" The "Flamingo" carrier-board for the RK3399-Q7 has a RV3029 populated and the application will use the off-module RV3029 RTC including the battery backed SRAM. To support this use case, this commit includes the following changes: * updates the rv3029 driver to use DM * implements the read8/write8 operations This syncs the implementation with the Linux code (based on 4.17), porting the trickle-charger support from there (with improvements to avoid unnecessary EEPROM updates) and adheres to the Linux DTS binding. Signed-off-by: Philipp Tomsich Tested-by: Klaus Goger --- drivers/rtc/rv3029.c | 579 +++++++++++++++++++++++++++++++++---------- scripts/config_whitelist.txt | 1 - 2 files changed, 442 insertions(+), 138 deletions(-) diff --git a/drivers/rtc/rv3029.c b/drivers/rtc/rv3029.c index dd4d060..4726f23 100644 --- a/drivers/rtc/rv3029.c +++ b/drivers/rtc/rv3029.c @@ -1,189 +1,494 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * (C) Copyright 2010 - * Heiko Schocher, DENX Software Engineering, hs@denx.de + * (C) Copyright 2018 Theobroma Systems Design und Consulting GmbH + * + * Based on a the Linux rtc-rv3029c2.c driver written by: + * Gregory Hermant + * Michael Buesch */ + #include #include +#include #include #include -#define RTC_RV3029_CTRL1 0x00 -#define RTC_RV3029_CTRL1_EERE (1 << 3) - -#define RTC_RV3029_CTRL_STATUS 0x03 -#define RTC_RV3029_CTRLS_EEBUSY (1 << 7) +#define RTC_RV3029_PAGE_LEN 7 -#define RTC_RV3029_CTRL_RESET 0x04 -#define RTC_RV3029_CTRL_SYS_R (1 << 4) +/* control section */ +#define RV3029_ONOFF_CTRL 0x00 +#define RV3029_ONOFF_CTRL_WE BIT(0) +#define RV3029_ONOFF_CTRL_TE BIT(1) +#define RV3029_ONOFF_CTRL_TAR BIT(2) +#define RV3029_ONOFF_CTRL_EERE BIT(3) +#define RV3029_ONOFF_CTRL_SRON BIT(4) +#define RV3029_ONOFF_CTRL_TD0 BIT(5) +#define RV3029_ONOFF_CTRL_TD1 BIT(6) +#define RV3029_ONOFF_CTRL_CLKINT BIT(7) +#define RV3029_IRQ_CTRL 0x01 +#define RV3029_IRQ_CTRL_AIE BIT(0) +#define RV3029_IRQ_CTRL_TIE BIT(1) +#define RV3029_IRQ_CTRL_V1IE BIT(2) +#define RV3029_IRQ_CTRL_V2IE BIT(3) +#define RV3029_IRQ_CTRL_SRIE BIT(4) +#define RV3029_IRQ_FLAGS 0x02 +#define RV3029_IRQ_FLAGS_AF BIT(0) +#define RV3029_IRQ_FLAGS_TF BIT(1) +#define RV3029_IRQ_FLAGS_V1IF BIT(2) +#define RV3029_IRQ_FLAGS_V2IF BIT(3) +#define RV3029_IRQ_FLAGS_SRF BIT(4) +#define RV3029_STATUS 0x03 +#define RV3029_STATUS_VLOW1 BIT(2) +#define RV3029_STATUS_VLOW2 BIT(3) +#define RV3029_STATUS_SR BIT(4) +#define RV3029_STATUS_PON BIT(5) +#define RV3029_STATUS_EEBUSY BIT(7) +#define RV3029_RST_CTRL 0x04 +#define RV3029_RST_CTRL_SYSR BIT(4) +#define RV3029_CONTROL_SECTION_LEN 0x05 -#define RTC_RV3029_CLOCK_PAGE 0x08 -#define RTC_RV3029_PAGE_LEN 7 +/* watch section */ +#define RV3029_W_SEC 0x08 +#define RV3029_W_MINUTES 0x09 +#define RV3029_W_HOURS 0x0A +#define RV3029_REG_HR_12_24 BIT(6) /* 24h/12h mode */ +#define RV3029_REG_HR_PM BIT(5) /* PM/AM bit in 12h mode */ +#define RV3029_W_DATE 0x0B +#define RV3029_W_DAYS 0x0C +#define RV3029_W_MONTHS 0x0D +#define RV3029_W_YEARS 0x0E -#define RV3029C2_W_SECONDS 0x00 -#define RV3029C2_W_MINUTES 0x01 -#define RV3029C2_W_HOURS 0x02 -#define RV3029C2_W_DATE 0x03 -#define RV3029C2_W_DAYS 0x04 -#define RV3029C2_W_MONTHS 0x05 -#define RV3029C2_W_YEARS 0x06 +/* eeprom control section */ +#define RV3029_CONTROL_E2P_EECTRL 0x30 +#define RV3029_TRICKLE_1K BIT(4) /* 1.5K resistance */ +#define RV3029_TRICKLE_5K BIT(5) /* 5K resistance */ +#define RV3029_TRICKLE_20K BIT(6) /* 20K resistance */ +#define RV3029_TRICKLE_80K BIT(7) /* 80K resistance */ +#define RV3029_TRICKLE_MASK (RV3029_TRICKLE_1K |\ + RV3029_TRICKLE_5K |\ + RV3029_TRICKLE_20K |\ + RV3029_TRICKLE_80K) +#define RV3029_TRICKLE_SHIFT 4 -#define RV3029C2_REG_HR_12_24 (1 << 6) /* 24h/12h mode */ -#define RV3029C2_REG_HR_PM (1 << 5) /* PM/AM bit in 12h mode */ -#define RTC_RV3029_EEPROM_CTRL 0x30 -#define RTC_RV3029_TRICKLE_1K (1 << 4) -#define RTC_RV3029_TRICKLE_5K (1 << 5) -#define RTC_RV3029_TRICKLE_20K (1 << 6) -#define RTC_RV3029_TRICKLE_80K (1 << 7) - -int rtc_get( struct rtc_time *tmp ) +static int rv3029_rtc_get(struct udevice *dev, struct rtc_time *tm) { - int ret; - unsigned char buf[RTC_RV3029_PAGE_LEN]; + u8 regs[RTC_RV3029_PAGE_LEN]; + int ret; - ret = i2c_read(CONFIG_SYS_I2C_RTC_ADDR, RTC_RV3029_CLOCK_PAGE, 1, buf, \ - RTC_RV3029_PAGE_LEN); - if (ret) { + ret = dm_i2c_read(dev, RV3029_W_SEC, regs, sizeof(regs)); + if (ret < 0) { printf("%s: error reading RTC: %x\n", __func__, ret); - return -1; + return -EIO; + } + + tm->tm_sec = bcd2bin(regs[RV3029_W_SEC - RV3029_W_SEC]); + tm->tm_min = bcd2bin(regs[RV3029_W_MINUTES - RV3029_W_SEC]); + + /* HR field has a more complex interpretation */ + { + const u8 _hr = regs[RV3029_W_HOURS - RV3029_W_SEC]; + + if (_hr & RV3029_REG_HR_12_24) { + /* 12h format */ + tm->tm_hour = bcd2bin(_hr & 0x1f); + if (_hr & RV3029_REG_HR_PM) /* PM flag set */ + tm->tm_hour += 12; + } else { + /* 24h format */ + tm->tm_hour = bcd2bin(_hr & 0x3f); + } } - tmp->tm_sec = bcd2bin( buf[RV3029C2_W_SECONDS] & 0x7f); - tmp->tm_min = bcd2bin( buf[RV3029C2_W_MINUTES] & 0x7f); - if (buf[RV3029C2_W_HOURS] & RV3029C2_REG_HR_12_24) { - /* 12h format */ - tmp->tm_hour = bcd2bin(buf[RV3029C2_W_HOURS] & 0x1f); - if (buf[RV3029C2_W_HOURS] & RV3029C2_REG_HR_PM) - /* PM flag set */ - tmp->tm_hour += 12; - } else - tmp->tm_hour = bcd2bin(buf[RV3029C2_W_HOURS] & 0x3f); - - tmp->tm_mday = bcd2bin( buf[RV3029C2_W_DATE] & 0x3F ); - tmp->tm_mon = bcd2bin( buf[RV3029C2_W_MONTHS] & 0x1F ); - tmp->tm_wday = bcd2bin( buf[RV3029C2_W_DAYS] & 0x07 ); + + tm->tm_mday = bcd2bin(regs[RV3029_W_DATE - RV3029_W_SEC]); + tm->tm_mon = bcd2bin(regs[RV3029_W_MONTHS - RV3029_W_SEC]) - 1; /* RTC supports only years > 1999 */ - tmp->tm_year = bcd2bin( buf[RV3029C2_W_YEARS]) + 2000; - tmp->tm_yday = 0; - tmp->tm_isdst = 0; + tm->tm_year = bcd2bin(regs[RV3029_W_YEARS - RV3029_W_SEC]) + 2000; + tm->tm_wday = bcd2bin(regs[RV3029_W_DAYS - RV3029_W_SEC]) - 1; + + tm->tm_yday = 0; + tm->tm_isdst = 0; - debug( "Get DATE: %4d-%02d-%02d (wday=%d) TIME: %2d:%02d:%02d\n", - tmp->tm_year, tmp->tm_mon, tmp->tm_mday, tmp->tm_wday, - tmp->tm_hour, tmp->tm_min, tmp->tm_sec ); + 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; } -int rtc_set( struct rtc_time *tmp ) +static int rv3029_rtc_set(struct udevice *dev, const struct rtc_time *tm) { - int ret; - unsigned char buf[RTC_RV3029_PAGE_LEN]; + u8 regs[RTC_RV3029_PAGE_LEN]; + + 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); - debug( "Set DATE: %4d-%02d-%02d (wday=%d) TIME: %2d:%02d:%02d\n", - tmp->tm_year, tmp->tm_mon, tmp->tm_mday, tmp->tm_wday, - tmp->tm_hour, tmp->tm_min, tmp->tm_sec); - if (tmp->tm_year < 2000) { - printf("RTC: year %d < 2000 not possible\n", tmp->tm_year); - return -1; + if (tm->tm_year < 2000) { + printf("%s: year %d (before 2000) not supported\n", + __func__, tm->tm_year); + return -EINVAL; } - buf[RV3029C2_W_SECONDS] = bin2bcd(tmp->tm_sec); - buf[RV3029C2_W_MINUTES] = bin2bcd(tmp->tm_min); - buf[RV3029C2_W_HOURS] = bin2bcd(tmp->tm_hour); - /* set 24h format */ - buf[RV3029C2_W_HOURS] &= ~RV3029C2_REG_HR_12_24; - buf[RV3029C2_W_DATE] = bin2bcd(tmp->tm_mday); - buf[RV3029C2_W_DAYS] = bin2bcd(tmp->tm_wday); - buf[RV3029C2_W_MONTHS] = bin2bcd(tmp->tm_mon); - tmp->tm_year -= 2000; - buf[RV3029C2_W_YEARS] = bin2bcd(tmp->tm_year); - ret = i2c_write(CONFIG_SYS_I2C_RTC_ADDR, RTC_RV3029_CLOCK_PAGE, 1, - buf, RTC_RV3029_PAGE_LEN); - - /* give the RTC some time to update */ - udelay(1000); - return ret; + + regs[RV3029_W_SEC - RV3029_W_SEC] = bin2bcd(tm->tm_sec); + regs[RV3029_W_MINUTES - RV3029_W_SEC] = bin2bcd(tm->tm_min); + regs[RV3029_W_HOURS - RV3029_W_SEC] = bin2bcd(tm->tm_hour); + regs[RV3029_W_DATE - RV3029_W_SEC] = bin2bcd(tm->tm_mday); + regs[RV3029_W_MONTHS - RV3029_W_SEC] = bin2bcd(tm->tm_mon + 1); + regs[RV3029_W_DAYS - RV3029_W_SEC] = bin2bcd(tm->tm_wday + 1) & 0x7; + regs[RV3029_W_YEARS - RV3029_W_SEC] = bin2bcd(tm->tm_year - 2000); + + return dm_i2c_write(dev, RV3029_W_SEC, regs, sizeof(regs)); } -/* sets EERE-Bit (automatic EEPROM refresh) */ -static void set_eere_bit(int state) +static int rv3029_rtc_reset(struct udevice *dev) { - unsigned char reg_ctrl1; + u8 ctrl = RV3029_RST_CTRL_SYSR; + unsigned long start; + const unsigned long timeout_ms = 10000; + int ret; - (void)i2c_read(CONFIG_SYS_I2C_RTC_ADDR, RTC_RV3029_CTRL1, 1, - ®_ctrl1, 1); + /* trigger the system-reset */ + ret = dm_i2c_write(dev, RV3029_RST_CTRL, &ctrl, 1); + if (ret < 0) + return -EIO; - if (state) - reg_ctrl1 |= RTC_RV3029_CTRL1_EERE; - else - reg_ctrl1 &= (~RTC_RV3029_CTRL1_EERE); + /* wait for the system-reset to complete */ + start = get_timer(0); + do { + if (get_timer(start) > timeout_ms) + return -ETIMEDOUT; - (void)i2c_write(CONFIG_SYS_I2C_RTC_ADDR, RTC_RV3029_CTRL1, 1, - ®_ctrl1, 1); + ret = dm_i2c_read(dev, RV3029_RST_CTRL, &ctrl, 1); + if (ret < 0) + return -EIO; + } while (ctrl & RV3029_RST_CTRL_SYSR); + + return 0; } -/* waits until EEPROM page is no longer busy (times out after 10ms*loops) */ -static int wait_eebusy(int loops) +static int rv3029_rtc_read8(struct udevice *dev, unsigned int reg) { - int i; - unsigned char ctrl_status; + u8 data; + int ret; + + ret = dm_i2c_read(dev, reg, &data, sizeof(data)); + return ret < 0 ? ret : data; +} - for (i = 0; i < loops; i++) { - (void)i2c_read(CONFIG_SYS_I2C_RTC_ADDR, RTC_RV3029_CTRL_STATUS, - 1, &ctrl_status, 1); +static int rv3029_rtc_write8(struct udevice *dev, unsigned int reg, int val) +{ + u8 data = val; + + return dm_i2c_write(dev, reg, &data, 1); +} + +#if defined(OF_CONTROL) +static int rv3029_get_sr(struct udevice *dev, u8 *buf) +{ + int ret = dm_i2c_read(dev, RV3029_STATUS, buf, 1); + + if (ret < 0) + return -EIO; + + dev_dbg(dev, "status = 0x%.2x (%d)\n", buf[0], buf[0]); + return 0; +} - if ((ctrl_status & RTC_RV3029_CTRLS_EEBUSY) == 0) +static int rv3029_set_sr(struct udevice *dev, u8 val) +{ + int ret; + + ret = dm_i2c_read(dev, RV3029_STATUS, &val, 1); + if (ret < 0) + return -EIO; + + dev_dbg(dev, "status = 0x%.2x (%d)\n", val, val); + return 0; +} + +static int rv3029_eeprom_busywait(struct udevice *dev) +{ + int i, ret; + u8 sr; + + for (i = 100; i > 0; i--) { + ret = rv3029_get_sr(dev, &sr); + if (ret < 0) + break; + if (!(sr & RV3029_STATUS_EEBUSY)) break; udelay(10000); } - return i; + if (i <= 0) { + dev_err(dev, "EEPROM busy wait timeout.\n"); + return -ETIMEDOUT; + } + + return ret; } -void rtc_reset (void) +static int rv3029_update_bits(struct udevice *dev, u8 reg, u8 mask, u8 set) { - unsigned char buf[RTC_RV3029_PAGE_LEN]; + u8 buf; + int ret; - buf[0] = RTC_RV3029_CTRL_SYS_R; - (void)i2c_write(CONFIG_SYS_I2C_RTC_ADDR, RTC_RV3029_CTRL_RESET, 1, - buf, 1); + ret = dm_i2c_read(dev, reg, &buf, 1); + if (ret < 0) + return ret; -#if defined(CONFIG_SYS_RV3029_TCR) - /* - * because EEPROM_CTRL register is in EEPROM page it is necessary to - * disable automatic EEPROM refresh and check if EEPROM is busy - * before EEPORM_CTRL register may be accessed - */ - set_eere_bit(0); - wait_eebusy(100); - /* read current trickle charger setting */ - (void)i2c_read(CONFIG_SYS_I2C_RTC_ADDR, RTC_RV3029_EEPROM_CTRL, - 1, buf, 1); - /* enable automatic EEPROM refresh again */ - set_eere_bit(1); + if ((buf & mask) == (set && mask)) + return 0; - /* - * to minimize EEPROM access write trickle charger setting only if it - * differs from current value - */ - if ((buf[0] & 0xF0) != CONFIG_SYS_RV3029_TCR) { - buf[0] = (buf[0] & 0x0F) | CONFIG_SYS_RV3029_TCR; - /* - * write trickle charger setting (disable autom. EEPROM - * refresh and wait until EEPROM is idle) - */ - set_eere_bit(0); - wait_eebusy(100); - (void)i2c_write(CONFIG_SYS_I2C_RTC_ADDR, - RTC_RV3029_EEPROM_CTRL, 1, buf, 1); - /* - * it is necessary to wait 10ms before EEBUSY-Bit may be read - * (this is not documented in the data sheet yet, but the - * manufacturer recommends it) + buf = (buf & ~mask) | (set & mask); + ret = dm_i2c_read(dev, reg, &buf, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int rv3029_eeprom_exit(struct udevice *dev) +{ + /* Re-enable eeprom refresh */ + return rv3029_update_bits(dev, RV3029_ONOFF_CTRL, + RV3029_ONOFF_CTRL_EERE, + RV3029_ONOFF_CTRL_EERE); +} + +static int rv3029_eeprom_enter(struct udevice *dev) +{ + int ret; + u8 sr; + + /* Check whether we are in the allowed voltage range. */ + ret = rv3029_get_sr(dev, &sr); + if (ret < 0) + return ret; + if (sr & (RV3029_STATUS_VLOW1 | RV3029_STATUS_VLOW2)) { + /* We clear the bits and retry once just in case + * we had a brown out in early startup. */ + sr &= ~RV3029_STATUS_VLOW1; + sr &= ~RV3029_STATUS_VLOW2; + ret = rv3029_set_sr(dev, sr); + if (ret < 0) + return ret; udelay(10000); - /* wait until EEPROM write access is finished */ - wait_eebusy(100); - set_eere_bit(1); + ret = rv3029_get_sr(dev, &sr); + if (ret < 0) + return ret; + if (sr & (RV3029_STATUS_VLOW1 | RV3029_STATUS_VLOW2)) { + dev_err(dev, "Supply voltage is too low to safely access the EEPROM.\n"); + return -ENODEV; + } + } + + /* Disable eeprom refresh. */ + ret = rv3029_update_bits(dev, + RV3029_ONOFF_CTRL, RV3029_ONOFF_CTRL_EERE, 0); + if (ret < 0) + return ret; + + /* Wait for any previous eeprom accesses to finish. */ + ret = rv3029_eeprom_busywait(dev); + if (ret < 0) + rv3029_eeprom_exit(dev); + + return ret; +} + +static int rv3029_eeprom_read(struct udevice *dev, u8 reg, + u8 buf[], size_t len) +{ + int ret, err; + + err = rv3029_eeprom_enter(dev); + if (err < 0) + return err; + + ret = dm_i2c_read(dev, reg, buf, len); + + err = rv3029_eeprom_exit(dev); + if (err < 0) + return err; + + return ret; +} + +static int rv3029_eeprom_write(struct udevice *dev, u8 reg, + u8 const buf[], size_t len) +{ + int ret; + size_t i; + u8 tmp; + + ret = rv3029_eeprom_enter(dev); + if (ret < 0) + return ret; + + for (i = 0; i < len; i++, reg++) { + ret = dm_i2c_read(dev, reg, &tmp, 1); + if (ret < 0) + break; + if (tmp != buf[i]) { + ret = dm_i2c_write(dev, reg, &buf[i], 1); + if (ret < 0) + break; + } + ret = rv3029_eeprom_busywait(dev); + if (ret < 0) + break; } + + ret = rv3029_eeprom_exit(dev); + if (ret < 0) + return ret; + + return 0; +} + +static int rv3029_eeprom_update_bits(struct udevice *dev, + u8 reg, u8 mask, u8 set) +{ + u8 buf; + int ret; + + ret = rv3029_eeprom_read(dev, reg, &buf, 1); + if (ret < 0) + return ret; + + /* + * If the EEPROM already reads the correct bitpattern, we don't need + * to update it. + */ + if ((buf & mask) == (set & mask)) + return 0; + + buf = (buf & ~mask) | (set & mask); + ret = rv3029_eeprom_write(dev, reg, &buf, 1); + if (ret < 0) + return ret; + + return 0; +} + +static void rv3029_trickle_config(struct udevice *dev) +{ + static const struct rv3029_trickle_tab_elem { + u32 r; /* resistance in ohms */ + u8 conf; /* trickle config bits */ + } rv3029_trickle_tab[] = { + { + .r = 1076, + .conf = RV3029_TRICKLE_1K | RV3029_TRICKLE_5K | + RV3029_TRICKLE_20K | RV3029_TRICKLE_80K, + }, { + .r = 1091, + .conf = RV3029_TRICKLE_1K | RV3029_TRICKLE_5K | + RV3029_TRICKLE_20K, + }, { + .r = 1137, + .conf = RV3029_TRICKLE_1K | RV3029_TRICKLE_5K | + RV3029_TRICKLE_80K, + }, { + .r = 1154, + .conf = RV3029_TRICKLE_1K | RV3029_TRICKLE_5K, + }, { + .r = 1371, + .conf = RV3029_TRICKLE_1K | RV3029_TRICKLE_20K | + RV3029_TRICKLE_80K, + }, { + .r = 1395, + .conf = RV3029_TRICKLE_1K | RV3029_TRICKLE_20K, + }, { + .r = 1472, + .conf = RV3029_TRICKLE_1K | RV3029_TRICKLE_80K, + }, { + .r = 1500, + .conf = RV3029_TRICKLE_1K, + }, { + .r = 3810, + .conf = RV3029_TRICKLE_5K | RV3029_TRICKLE_20K | + RV3029_TRICKLE_80K, + }, { + .r = 4000, + .conf = RV3029_TRICKLE_5K | RV3029_TRICKLE_20K, + }, { + .r = 4706, + .conf = RV3029_TRICKLE_5K | RV3029_TRICKLE_80K, + }, { + .r = 5000, + .conf = RV3029_TRICKLE_5K, + }, { + .r = 16000, + .conf = RV3029_TRICKLE_20K | RV3029_TRICKLE_80K, + }, { + .r = 20000, + .conf = RV3029_TRICKLE_20K, + }, { + .r = 80000, + .conf = RV3029_TRICKLE_80K, + }, + }; + int err; + u32 ohms; + u8 trickle_set_bits = 0; + + /* Configure the trickle charger. */ + err = dev_read_u32(dev, "trickle-resistor-ohms", &ohms); + + if (!err) { + /* Find trickle-charger config */ + for (int i = 0; i < ARRAY_SIZE(rv3029_trickle_tab); i++) + if (rv3029_trickle_tab[i].r >= ohms) { + dev_dbg(dev, "trickle charger at %d ohms\n", + rv3029_trickle_tab[i].r); + trickle_set_bits = rv3029_trickle_tab[i].conf; + break; + } + } + + dev_dbg(dev, "trickle charger config 0x%x\n", trickle_set_bits); + err = rv3029_eeprom_update_bits(dev, RV3029_CONTROL_E2P_EECTRL, + RV3029_TRICKLE_MASK, + trickle_set_bits); + if (err < 0) + dev_dbg(dev, "failed to update trickle charger\n"); +} +#else +static inline void rv3029_trickle_config(struct udevice *dev) +{ +} #endif + +static int rv3029_probe(struct udevice *dev) +{ + i2c_set_chip_flags(dev, DM_I2C_CHIP_RD_ADDRESS | + DM_I2C_CHIP_WR_ADDRESS); + + rv3029_trickle_config(dev); + return 0; } + +static const struct rtc_ops rv3029_rtc_ops = { + .get = rv3029_rtc_get, + .set = rv3029_rtc_set, + .read8 = rv3029_rtc_read8, + .write8 = rv3029_rtc_write8, + .reset = rv3029_rtc_reset, +}; + +static const struct udevice_id rv3029_rtc_ids[] = { + { .compatible = "mc,rv3029" }, + { } +}; + +U_BOOT_DRIVER(rtc_rv3029) = { + .name = "rtc-rv3029", + .id = UCLASS_RTC, + .probe = rv3029_probe, + .of_match = rv3029_rtc_ids, + .ops = &rv3029_rtc_ops, +}; diff --git a/scripts/config_whitelist.txt b/scripts/config_whitelist.txt index bd264d2..2597525 100644 --- a/scripts/config_whitelist.txt +++ b/scripts/config_whitelist.txt @@ -4100,7 +4100,6 @@ CONFIG_SYS_RTC_OSCILLATOR CONFIG_SYS_RTC_PL031_BASE CONFIG_SYS_RTC_REG_BASE_ADDR CONFIG_SYS_RTC_SETUP -CONFIG_SYS_RV3029_TCR CONFIG_SYS_RX_ETH_BUFFER CONFIG_SYS_SATA CONFIG_SYS_SATA1 From patchwork Tue Aug 14 09:51:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Philipp Tomsich X-Patchwork-Id: 957410 X-Patchwork-Delegate: philipp.tomsich@theobroma-systems.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=theobroma-systems.com Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 41qSYq1Vhhz9s5b for ; Tue, 14 Aug 2018 19:52:15 +1000 (AEST) Received: by lists.denx.de (Postfix, from userid 105) id CD489C21DF9; Tue, 14 Aug 2018 09:51:56 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=none autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 89284C21D9A; Tue, 14 Aug 2018 09:51:17 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id 3D86CC21C93; Tue, 14 Aug 2018 09:51:14 +0000 (UTC) Received: from mail.theobroma-systems.com (vegas.theobroma-systems.com [144.76.126.164]) by lists.denx.de (Postfix) with ESMTPS id EA3D9C21C2F for ; Tue, 14 Aug 2018 09:51:13 +0000 (UTC) Received: from [86.59.122.178] (port=50372 helo=android.lan) by mail.theobroma-systems.com with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA256:128) (Exim 4.80) (envelope-from ) id 1fpVyq-0007Pg-5Q; Tue, 14 Aug 2018 11:51:12 +0200 From: Philipp Tomsich To: u-boot@lists.denx.de Date: Tue, 14 Aug 2018 11:51:02 +0200 Message-Id: <1534240262-8904-4-git-send-email-philipp.tomsich@theobroma-systems.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1534240262-8904-1-git-send-email-philipp.tomsich@theobroma-systems.com> References: <1534240262-8904-1-git-send-email-philipp.tomsich@theobroma-systems.com> Cc: Ian Ray Subject: [U-Boot] [PATCH 3/3] bootcount: add DM-based backing store for bootcount X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" The original bootcount methods do not provide an interface to DM and rely on a static configuration for I2C devices (e.g. bus, chip-addr, etc. are configured through defines statically). On a modern system that exposes multiple devices in a DTS-configurable way, this is less than optimal and a interface to DM-based devices will be desirable. This adds a simple driver that is DM-aware and configurable via DTS: the /chosen/u-boot,bootcount-device property is used to detect the DM device storing the bootcount and deviceclass-specific commands are used to read/write the bootcount. Initially, this provides support for the following DM devices: * RTC devices implementing the read8/write8 ops Signed-off-by: Philipp Tomsich Tested-by: Klaus Goger --- doc/device-tree-bindings/chosen.txt | 27 +++++++++++ drivers/bootcount/Kconfig | 12 +++++ drivers/bootcount/Makefile | 1 + drivers/bootcount/bootcount_dm.c | 93 +++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) create mode 100644 drivers/bootcount/bootcount_dm.c diff --git a/doc/device-tree-bindings/chosen.txt b/doc/device-tree-bindings/chosen.txt index da7b4e6..734fd15 100644 --- a/doc/device-tree-bindings/chosen.txt +++ b/doc/device-tree-bindings/chosen.txt @@ -42,6 +42,33 @@ Example }; }; +u-boot,bootcount-device property +-------------------------------- +In a DM-based system, the bootcount may be stored in a device known to +the DM framework (e.g. in a battery-backed SRAM area within a RTC +device). To identify the device to be used for the bootcount, the +u-boot,bootcount-device property needs to point to the target device. + +Further configuration in the target device's node may be required +(e.g. an offset into an I2C RTC's address space), depending on the +UCLASS of the target device. + +Example +------- +/ { + chosen { + u-boot,bootcount-device = &rv3029; + }; + + i2c2 { + rv3029: rtc@56 { + compatible = "mc,rv3029"; + reg = <0x56>; + u-boot,bootcount-offset = <0x38>; + }; + }; +}; + u-boot,spl-boot-order property ------------------------------ diff --git a/drivers/bootcount/Kconfig b/drivers/bootcount/Kconfig index d335ed1..cdde7b5 100644 --- a/drivers/bootcount/Kconfig +++ b/drivers/bootcount/Kconfig @@ -70,6 +70,18 @@ config BOOTCOUNT_AT91 bool "Boot counter for Atmel AT91SAM9XE" depends on AT91SAM9XE +config BOOTCOUNT_DM + bool "Boot counter in a device-model device" + help + Enables reading/writing the bootcount in a device-model + device referenced from the /chosen node. The type of the + device is detected from DM and any additional configuration + information (e.g. the offset into a RTC device that supports + read32/write32) is read from the device's node. + + At this time the following DM device classes are supported: + * RTC (using read32/write32) + endchoice config BOOTCOUNT_ALEN diff --git a/drivers/bootcount/Makefile b/drivers/bootcount/Makefile index 68bc006..e8ed729 100644 --- a/drivers/bootcount/Makefile +++ b/drivers/bootcount/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_BOOTCOUNT_RAM) += bootcount_ram.o obj-$(CONFIG_BOOTCOUNT_ENV) += bootcount_env.o obj-$(CONFIG_BOOTCOUNT_I2C) += bootcount_i2c.o obj-$(CONFIG_BOOTCOUNT_EXT) += bootcount_ext.o +obj-$(CONFIG_BOOTCOUNT_DM) += bootcount_dm.o diff --git a/drivers/bootcount/bootcount_dm.c b/drivers/bootcount/bootcount_dm.c new file mode 100644 index 0000000..99bdb88 --- /dev/null +++ b/drivers/bootcount/bootcount_dm.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2018 Theobroma Systems Design und Consulting GmbH + */ + +#include +#include +#include +#include + +const u8 bootcount_magic = 0xbc; + +static void bootcount_store_rtc(struct udevice *dev, ulong a) +{ +#if CONFIG_IS_ENABLED(DM_RTC) + u32 offset; + const char *offset_propname = "u-boot,bootcount-offset"; + const u16 val = bootcount_magic << 8 | (a & 0xff); + + if (dev_read_u32(dev, offset_propname, &offset) < 0) { + debug("%s: requires %s\n", __func__, offset_propname); + return; + } + + if (rtc_write16(dev, offset, val) < 0) { + debug("%s: rtc_write16 failed\n", __func__); + return; + } +#endif +} + +static u32 bootcount_load_rtc(struct udevice *dev) +{ +#if CONFIG_IS_ENABLED(DM_RTC) + u32 offset; + const char *offset_propname = "u-boot,bootcount-offset"; + u16 val; + + if (dev_read_u32(dev, offset_propname, &offset) < 0) { + debug("%s: requires %s\n", __func__, offset_propname); + goto fail; + } + + if (rtc_read16(dev, offset, &val) < 0) { + debug("%s: rtc_write16 failed\n", __func__); + goto fail; + } + + if (val >> 8 == bootcount_magic) + return val & 0xff; + + debug("%s: bootcount magic does not match on %04x\n", __func__, val); + fail: +#endif + return 0; +} + +/* Now implement the generic default functions */ +void bootcount_store(ulong a) +{ + struct udevice *dev; + ofnode node; + const char *propname = "u-boot,bootcount-device"; + + node = ofnode_get_chosen_node(propname); + if (!ofnode_valid(node)) { + debug("%s: no '%s'\n", __func__, propname); + return; + } + + /* RTC devices */ + if (!uclass_get_device_by_ofnode(UCLASS_RTC, node, &dev)) + return bootcount_store_rtc(dev, a); +} + +ulong bootcount_load(void) +{ + struct udevice *dev; + ofnode node; + const char *propname = "u-boot,bootcount-device"; + + node = ofnode_get_chosen_node(propname); + if (!ofnode_valid(node)) { + debug("%s: no '%s'\n", __func__, propname); + return 0; + } + + /* RTC devices */ + if (!uclass_get_device_by_ofnode(UCLASS_RTC, node, &dev)) + return bootcount_load_rtc(dev); + + return 0; +}