From patchwork Wed Jan 30 15:00:14 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexandre Belloni X-Patchwork-Id: 1033592 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=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-rtc-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=bootlin.com Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 43qRYb29bmz9s9h for ; Thu, 31 Jan 2019 02:07:31 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727555AbfA3PHa (ORCPT ); Wed, 30 Jan 2019 10:07:30 -0500 Received: from mslow2.mail.gandi.net ([217.70.178.242]:56762 "EHLO mslow2.mail.gandi.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727356AbfA3PHa (ORCPT ); Wed, 30 Jan 2019 10:07:30 -0500 Received: from relay9-d.mail.gandi.net (unknown [217.70.183.199]) by mslow2.mail.gandi.net (Postfix) with ESMTP id 285213A7DB5; Wed, 30 Jan 2019 16:00:23 +0100 (CET) X-Originating-IP: 109.213.13.254 Received: from localhost (alyon-652-1-30-254.w109-213.abo.wanadoo.fr [109.213.13.254]) (Authenticated sender: alexandre.belloni@bootlin.com) by relay9-d.mail.gandi.net (Postfix) with ESMTPSA id 3334FFF807; Wed, 30 Jan 2019 15:00:19 +0000 (UTC) From: Alexandre Belloni To: linux-rtc@vger.kernel.org Cc: Marek Vasut , linux-kernel@vger.kernel.org, Alexandre Belloni Subject: [PATCH] rtc: rv3028: new driver Date: Wed, 30 Jan 2019 16:00:14 +0100 Message-Id: <20190130150014.29853-1-alexandre.belloni@bootlin.com> X-Mailer: git-send-email 2.20.1 MIME-Version: 1.0 Sender: linux-rtc-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-rtc@vger.kernel.org Add a driver for the MicroCrystal RV-3028. Signed-off-by: Alexandre Belloni --- Documentation/devicetree/bindings/rtc/rtc.txt | 1 + drivers/rtc/Kconfig | 9 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-rv3028.c | 732 ++++++++++++++++++ 4 files changed, 743 insertions(+) create mode 100644 drivers/rtc/rtc-rv3028.c diff --git a/Documentation/devicetree/bindings/rtc/rtc.txt b/Documentation/devicetree/bindings/rtc/rtc.txt index d86e2850fe1c..3e6a215e7304 100644 --- a/Documentation/devicetree/bindings/rtc/rtc.txt +++ b/Documentation/devicetree/bindings/rtc/rtc.txt @@ -52,6 +52,7 @@ emmicro,em3027 EM Microelectronic EM3027 Real-time Clock isil,isl1208 Intersil ISL1208 Low Power RTC with Battery Backed SRAM isil,isl1218 Intersil ISL1218 Low Power RTC with Battery Backed SRAM isil,isl12022 Intersil ISL12022 Real-time Clock +microcrystal,rv3028 Real Time Clock Module with I2C-Bus microcrystal,rv3029 Real Time Clock Module with I2C-Bus microcrystal,rv8523 Real Time Clock nxp,pcf2127 Real-time clock diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index faa9ae1a3062..845c7eef548a 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -626,6 +626,15 @@ config RTC_DRV_EM3027 This driver can also be built as a module. If so, the module will be called rtc-em3027. +config RTC_DRV_RV3028 + tristate "Micro Crystal RV3028" + help + If you say yes here you get support for the Micro Crystal + RV3028. + + This driver can also be built as a module. If so, the module + will be called rtc-rv3028. + config RTC_DRV_RV8803 tristate "Micro Crystal RV8803, Epson RX8900" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index faca02109aaa..a701d68bb5ca 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -138,6 +138,7 @@ obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o obj-$(CONFIG_RTC_DRV_RTD119X) += rtc-rtd119x.o +obj-$(CONFIG_RTC_DRV_RV3028) += rtc-rv3028.o obj-$(CONFIG_RTC_DRV_RV3029C2) += rtc-rv3029c2.o obj-$(CONFIG_RTC_DRV_RV8803) += rtc-rv8803.o obj-$(CONFIG_RTC_DRV_RX4581) += rtc-rx4581.o diff --git a/drivers/rtc/rtc-rv3028.c b/drivers/rtc/rtc-rv3028.c new file mode 100644 index 000000000000..cd1600feaed1 --- /dev/null +++ b/drivers/rtc/rtc-rv3028.c @@ -0,0 +1,732 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RTC driver for the Micro Crystal RV3028 + * + * Copyright (C) 2018 Micro Crystal SA + * + * Alexandre Belloni + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RV3028_SEC 0x00 +#define RV3028_MIN 0x01 +#define RV3028_HOUR 0x02 +#define RV3028_WDAY 0x03 +#define RV3028_DAY 0x04 +#define RV3028_MONTH 0x05 +#define RV3028_YEAR 0x06 +#define RV3028_ALARM_MIN 0x07 +#define RV3028_ALARM_HOUR 0x08 +#define RV3028_ALARM_DAY 0x09 +#define RV3028_STATUS 0x0E +#define RV3028_CTRL1 0x0F +#define RV3028_CTRL2 0x10 +#define RV3028_EVT_CTRL 0x13 +#define RV3028_TS_COUNT 0x14 +#define RV3028_TS_SEC 0x15 +#define RV3028_RAM1 0x1F +#define RV3028_EEPROM_ADDR 0x25 +#define RV3028_EEPROM_DATA 0x26 +#define RV3028_EEPROM_CMD 0x27 +#define RV3028_CLKOUT 0x35 +#define RV3028_OFFSET 0x36 +#define RV3028_BACKUP 0x37 + +#define RV3028_STATUS_PORF BIT(0) +#define RV3028_STATUS_EVF BIT(1) +#define RV3028_STATUS_AF BIT(2) +#define RV3028_STATUS_TF BIT(3) +#define RV3028_STATUS_UF BIT(4) +#define RV3028_STATUS_BSF BIT(5) +#define RV3028_STATUS_CLKF BIT(6) +#define RV3028_STATUS_EEBUSY BIT(7) + +#define RV3028_CTRL1_EERD BIT(3) +#define RV3028_CTRL1_WADA BIT(5) + +#define RV3028_CTRL2_RESET BIT(0) +#define RV3028_CTRL2_12_24 BIT(1) +#define RV3028_CTRL2_EIE BIT(2) +#define RV3028_CTRL2_AIE BIT(3) +#define RV3028_CTRL2_TIE BIT(4) +#define RV3028_CTRL2_UIE BIT(5) +#define RV3028_CTRL2_TSE BIT(7) + +#define RV3028_EVT_CTRL_TSR BIT(2) + +#define RV3028_EEPROM_CMD_WRITE 0x21 +#define RV3028_EEPROM_CMD_READ 0x22 + +#define RV3028_EEBUSY_POLL 10000 +#define RV3028_EEBUSY_TIMEOUT 100000 + +#define RV3028_BACKUP_TCE BIT(5) +#define RV3028_BACKUP_TCR_MASK GENMASK(1,0) + +#define OFFSET_STEP_PPT 953674 + +enum rv3028_type { + rv_3028, +}; + +struct rv3028_data { + struct regmap *regmap; + struct rtc_device *rtc; + enum rv3028_type type; +}; + +static u32 rv3028_trickle_resistors[] = {1000, 3000, 6000, 11000}; + +static ssize_t timestamp0_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev->parent); + + regmap_update_bits(rv3028->regmap, RV3028_EVT_CTRL, RV3028_EVT_CTRL_TSR, + RV3028_EVT_CTRL_TSR); + + return count; +}; + +static ssize_t timestamp0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev->parent); + struct rtc_time tm; + int ret, count; + u8 date[6]; + + ret = regmap_read(rv3028->regmap, RV3028_TS_COUNT, &count); + if (ret) + return ret; + + if (!count) + return 0; + + ret = regmap_bulk_read(rv3028->regmap, RV3028_TS_SEC, date, + sizeof(date)); + if (ret) + return ret; + + tm.tm_sec = bcd2bin(date[0]); + tm.tm_min = bcd2bin(date[1]); + tm.tm_hour = bcd2bin(date[2]); + tm.tm_mday = bcd2bin(date[3]); + tm.tm_mon = bcd2bin(date[4]) - 1; + tm.tm_year = bcd2bin(date[5]) + 100; + + ret = rtc_valid_tm(&tm); + if (ret) + return ret; + + return sprintf(buf, "%llu\n", + (unsigned long long)rtc_tm_to_time64(&tm)); +}; + +static DEVICE_ATTR_RW(timestamp0); + +static ssize_t timestamp0_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev->parent); + int ret, count; + + ret = regmap_read(rv3028->regmap, RV3028_TS_COUNT, &count); + if (ret) + return ret; + + return sprintf(buf, "%u\n", count); +}; + +static DEVICE_ATTR_RO(timestamp0_count); + +static struct attribute *rv3028_attrs[] = { + &dev_attr_timestamp0.attr, + &dev_attr_timestamp0_count.attr, + NULL +}; + +static const struct attribute_group rv3028_attr_group = { + .attrs = rv3028_attrs, +}; + +static irqreturn_t rv3028_handle_irq(int irq, void *dev_id) +{ + struct rv3028_data *rv3028 = dev_id; + unsigned long events = 0; + u32 status = 0, ctrl = 0; + + if (regmap_read(rv3028->regmap, RV3028_STATUS, &status) < 0 || + status == 0) { + return IRQ_NONE; + } + + if (status & RV3028_STATUS_PORF) + dev_warn(&rv3028->rtc->dev, "Voltage low, data loss detected.\n"); + + if (status & RV3028_STATUS_TF) { + status |= RV3028_STATUS_TF; + ctrl |= RV3028_CTRL2_TIE; + events |= RTC_PF; + } + + if (status & RV3028_STATUS_AF) { + status |= RV3028_STATUS_AF; + ctrl |= RV3028_CTRL2_AIE; + events |= RTC_AF; + } + + if (status & RV3028_STATUS_UF) { + status |= RV3028_STATUS_UF; + ctrl |= RV3028_CTRL2_UIE; + events |= RTC_UF; + } + + if (events) { + rtc_update_irq(rv3028->rtc, 1, events); + regmap_update_bits(rv3028->regmap, RV3028_STATUS, status, 0); + regmap_update_bits(rv3028->regmap, RV3028_CTRL2, ctrl, 0); + } + + if (status & RV3028_STATUS_EVF) { + sysfs_notify(&rv3028->rtc->dev.kobj, NULL, + dev_attr_timestamp0.attr.name); + dev_warn(&rv3028->rtc->dev, "event detected"); + } + + return IRQ_HANDLED; +} + +static int rv3028_get_time(struct device *dev, struct rtc_time *tm) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + u8 date[7]; + int ret, status; + + ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status); + if (ret < 0) + return ret; + + if (status & RV3028_STATUS_PORF) { + dev_warn(dev, "Voltage low, data is invalid.\n"); + return -EINVAL; + } + + ret = regmap_bulk_read(rv3028->regmap, RV3028_SEC, date, sizeof(date)); + if (ret) + return ret; + + tm->tm_sec = bcd2bin(date[RV3028_SEC] & 0x7f); + tm->tm_min = bcd2bin(date[RV3028_MIN] & 0x7f); + tm->tm_hour = bcd2bin(date[RV3028_HOUR] & 0x3f); + tm->tm_wday = ilog2(date[RV3028_WDAY] & 0x7f); + tm->tm_mday = bcd2bin(date[RV3028_DAY] & 0x3f); + tm->tm_mon = bcd2bin(date[RV3028_MONTH] & 0x1f) - 1; + tm->tm_year = bcd2bin(date[RV3028_YEAR]) + 100; + + return 0; +} + +static int rv3028_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + u8 date[7]; + int ret; + + date[RV3028_SEC] = bin2bcd(tm->tm_sec); + date[RV3028_MIN] = bin2bcd(tm->tm_min); + date[RV3028_HOUR] = bin2bcd(tm->tm_hour); + date[RV3028_WDAY] = 1 << (tm->tm_wday); + date[RV3028_DAY] = bin2bcd(tm->tm_mday); + date[RV3028_MONTH] = bin2bcd(tm->tm_mon + 1); + date[RV3028_YEAR] = bin2bcd(tm->tm_year - 100); + + /* + * Writing to the Seconds register has the same effect as setting RESET + * bit to 1 + */ + ret = regmap_bulk_write(rv3028->regmap, RV3028_SEC, date, + sizeof(date)); + if (ret) + return ret; + + ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS, + RV3028_STATUS_PORF, 0); + + return ret; +} + +static int rv3028_get_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + u8 alarmvals[3]; + int status, ctrl, ret; + + ret = regmap_bulk_read(rv3028->regmap, RV3028_ALARM_MIN, alarmvals, + sizeof(alarmvals)); + if (ret) + return ret; + + ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status); + if (ret < 0) + return ret; + + ret = regmap_read(rv3028->regmap, RV3028_CTRL2, &ctrl); + if (ret < 0) + return ret; + + alrm->time.tm_sec = 0; + alrm->time.tm_min = bcd2bin(alarmvals[0] & 0x7f); + alrm->time.tm_hour = bcd2bin(alarmvals[1] & 0x3f); + alrm->time.tm_mday = bcd2bin(alarmvals[2] & 0x3f); + + alrm->enabled = !!(ctrl & RV3028_CTRL2_AIE); + alrm->pending = (status & RV3028_STATUS_AF) && alrm->enabled; + + return 0; +} + +static int rv3028_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + u8 alarmvals[3]; + u8 ctrl = 0; + int ret; + + /* The alarm has no seconds, round up to nearest minute */ + if (alrm->time.tm_sec) { + time64_t alarm_time = rtc_tm_to_time64(&alrm->time); + + alarm_time += 60 - alrm->time.tm_sec; + rtc_time64_to_tm(alarm_time, &alrm->time); + } + + ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2, + RV3028_CTRL2_AIE | RV3028_CTRL2_UIE, 0); + if (ret) + return ret; + + alarmvals[0] = bin2bcd(alrm->time.tm_min); + alarmvals[1] = bin2bcd(alrm->time.tm_hour); + alarmvals[2] = bin2bcd(alrm->time.tm_mday); + + ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS, + RV3028_STATUS_AF, 0); + if (ret) + return ret; + + ret = regmap_bulk_write(rv3028->regmap, RV3028_ALARM_MIN, alarmvals, + sizeof(alarmvals)); + if (ret) + return ret; + + if (alrm->enabled) { + if (rv3028->rtc->uie_rtctimer.enabled) + ctrl |= RV3028_CTRL2_UIE; + if (rv3028->rtc->aie_timer.enabled) + ctrl |= RV3028_CTRL2_AIE; + } + + ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2, + RV3028_CTRL2_UIE | RV3028_CTRL2_AIE, ctrl); + + return ret; +} + +static int rv3028_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + int ctrl = 0, ret; + + if (enabled) { + if (rv3028->rtc->uie_rtctimer.enabled) + ctrl |= RV3028_CTRL2_UIE; + if (rv3028->rtc->aie_timer.enabled) + ctrl |= RV3028_CTRL2_AIE; + } + + ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS, + RV3028_STATUS_AF | RV3028_STATUS_UF, 0); + if (ret) + return ret; + + ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2, + RV3028_CTRL2_UIE | RV3028_CTRL2_AIE, ctrl); + if (ret) + return ret; + + return 0; +} + +static int rv3028_read_offset(struct device *dev, long *offset) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + int ret, value, steps; + + ret = regmap_read(rv3028->regmap, RV3028_OFFSET, &value); + if (ret < 0) + return ret; + + steps = sign_extend32(value << 1, 8); + + ret = regmap_read(rv3028->regmap, RV3028_BACKUP, &value); + if (ret < 0) + return ret; + + steps += value >> 7; + + *offset = DIV_ROUND_CLOSEST(steps * OFFSET_STEP_PPT, 1000); + + return 0; +} + +static int rv3028_set_offset(struct device *dev, long offset) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + int ret; + + offset = clamp(offset, -244141L, 243187L) * 1000; + offset = DIV_ROUND_CLOSEST(offset, OFFSET_STEP_PPT); + + ret = regmap_write(rv3028->regmap, RV3028_OFFSET, offset >> 1); + if (ret < 0) + return ret; + + return regmap_update_bits(rv3028->regmap, RV3028_BACKUP, BIT(7), + offset << 7); +} + +static int rv3028_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) +{ + struct rv3028_data *rv3028 = dev_get_drvdata(dev); + int status, ret = 0; + + switch (cmd) { + case RTC_VL_READ: + ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status); + if (ret < 0) + return ret; + + if (status & RV3028_STATUS_PORF) + dev_warn(&rv3028->rtc->dev, "Voltage low, data loss detected.\n"); + + status &= RV3028_STATUS_PORF; + + if (copy_to_user((void __user *)arg, &status, sizeof(int))) + return -EFAULT; + + return 0; + + case RTC_VL_CLR: + ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS, + RV3028_STATUS_PORF, 0); + + return ret; + + default: + return -ENOIOCTLCMD; + } +} + +static int rv3028_nvram_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + return regmap_bulk_write(priv, RV3028_RAM1 + offset, val, bytes); +} + +static int rv3028_nvram_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + return regmap_bulk_read(priv, RV3028_RAM1 + offset, val, bytes); +} + +static int rv3028_eeprom_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + u32 status, ctrl1; + int i, ret, err; + u8 *buf = val; + + ret = regmap_read(priv, RV3028_CTRL1, &ctrl1); + if (ret) + return ret; + + if (!(ctrl1 & RV3028_CTRL1_EERD)) { + ret = regmap_update_bits(priv, RV3028_CTRL1, + RV3028_CTRL1_EERD, RV3028_CTRL1_EERD); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(priv, RV3028_STATUS, status, + !(status & RV3028_STATUS_EEBUSY), + RV3028_EEBUSY_POLL, + RV3028_EEBUSY_TIMEOUT); + if (ret) + goto restore_eerd; + } + + for (i = 0; i < bytes; i++) { + ret = regmap_write(priv, RV3028_EEPROM_ADDR, offset + i); + if (ret) + goto restore_eerd; + + ret = regmap_write(priv, RV3028_EEPROM_DATA, buf[i]); + if (ret) + goto restore_eerd; + + ret = regmap_write(priv, RV3028_EEPROM_CMD, 0x0); + if (ret) + goto restore_eerd; + + ret = regmap_write(priv, RV3028_EEPROM_CMD, + RV3028_EEPROM_CMD_WRITE); + if (ret) + goto restore_eerd; + + usleep_range(RV3028_EEBUSY_POLL, RV3028_EEBUSY_TIMEOUT); + + ret = regmap_read_poll_timeout(priv, RV3028_STATUS, status, + !(status & RV3028_STATUS_EEBUSY), + RV3028_EEBUSY_POLL, + RV3028_EEBUSY_TIMEOUT); + if (ret) + goto restore_eerd; + } + +restore_eerd: + if (!(ctrl1 & RV3028_CTRL1_EERD)) + { + err = regmap_update_bits(priv, RV3028_CTRL1, RV3028_CTRL1_EERD, + 0); + if (err && !ret) + ret = err; + } + + return ret; +} + +static int rv3028_eeprom_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + u32 status, ctrl1, data; + int i, ret, err; + u8 *buf = val; + + ret = regmap_read(priv, RV3028_CTRL1, &ctrl1); + if (ret) + return ret; + + if (!(ctrl1 & RV3028_CTRL1_EERD)) { + ret = regmap_update_bits(priv, RV3028_CTRL1, + RV3028_CTRL1_EERD, RV3028_CTRL1_EERD); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(priv, RV3028_STATUS, status, + !(status & RV3028_STATUS_EEBUSY), + RV3028_EEBUSY_POLL, + RV3028_EEBUSY_TIMEOUT); + if (ret) + goto restore_eerd; + } + + for (i = 0; i < bytes; i++) { + ret = regmap_write(priv, RV3028_EEPROM_ADDR, offset + i); + if (ret) + goto restore_eerd; + + ret = regmap_write(priv, RV3028_EEPROM_CMD, 0x0); + if (ret) + goto restore_eerd; + + ret = regmap_write(priv, RV3028_EEPROM_CMD, + RV3028_EEPROM_CMD_READ); + if (ret) + goto restore_eerd; + + ret = regmap_read_poll_timeout(priv, RV3028_STATUS, status, + !(status & RV3028_STATUS_EEBUSY), + RV3028_EEBUSY_POLL, + RV3028_EEBUSY_TIMEOUT); + if (ret) + goto restore_eerd; + + ret = regmap_read(priv, RV3028_EEPROM_DATA, &data); + if (ret) + goto restore_eerd; + buf[i] = data; + } + +restore_eerd: + if (!(ctrl1 & RV3028_CTRL1_EERD)) + { + err = regmap_update_bits(priv, RV3028_CTRL1, RV3028_CTRL1_EERD, + 0); + if (err && !ret) + ret = err; + } + + return ret; +} + +static struct rtc_class_ops rv3028_rtc_ops = { + .read_time = rv3028_get_time, + .set_time = rv3028_set_time, + .read_offset = rv3028_read_offset, + .set_offset = rv3028_set_offset, + .ioctl = rv3028_ioctl, +}; + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x37, +}; + +static int rv3028_probe(struct i2c_client *client) +{ + struct rv3028_data *rv3028; + int ret, status; + u32 ohms; + struct nvmem_config nvmem_cfg = { + .name = "rv3028_nvram", + .word_size = 1, + .stride = 1, + .size = 2, + .type = NVMEM_TYPE_BATTERY_BACKED, + .reg_read = rv3028_nvram_read, + .reg_write = rv3028_nvram_write, + }; + struct nvmem_config eeprom_cfg = { + .name = "rv3028_eeprom", + .word_size = 1, + .stride = 1, + .size = 43, + .type = NVMEM_TYPE_EEPROM, + .reg_read = rv3028_eeprom_read, + .reg_write = rv3028_eeprom_write, + }; + + rv3028 = devm_kzalloc(&client->dev, sizeof(struct rv3028_data), + GFP_KERNEL); + if (!rv3028) + return -ENOMEM; + + rv3028->regmap = devm_regmap_init_i2c(client, ®map_config); + + i2c_set_clientdata(client, rv3028); + + ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status); + if (ret < 0) + return ret; + + if (status & RV3028_STATUS_PORF) + dev_warn(&client->dev, "Voltage low, data loss detected.\n"); + + if (status & RV3028_STATUS_AF) + dev_warn(&client->dev, "An alarm may have been missed.\n"); + + rv3028->rtc = devm_rtc_allocate_device(&client->dev); + if (IS_ERR(rv3028->rtc)) { + return PTR_ERR(rv3028->rtc); + } + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, rv3028_handle_irq, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "rv3028", rv3028); + if (ret) { + dev_warn(&client->dev, "unable to request IRQ, alarms disabled\n"); + client->irq = 0; + } else { + rv3028_rtc_ops.read_alarm = rv3028_get_alarm; + rv3028_rtc_ops.set_alarm = rv3028_set_alarm; + rv3028_rtc_ops.alarm_irq_enable = rv3028_alarm_irq_enable; + } + } + + ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL1, + RV3028_CTRL1_WADA, RV3028_CTRL1_WADA); + if (ret) + return ret; + + /* setup timestamping */ + ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2, + RV3028_CTRL2_EIE | RV3028_CTRL2_TSE, + RV3028_CTRL2_EIE | RV3028_CTRL2_TSE); + if (ret) + return ret; + + /* setup trickle charger */ + if (!device_property_read_u32(&client->dev, "trickle-resistor-ohms", + &ohms)) { + int i; + + for (i = 0; i < ARRAY_SIZE(rv3028_trickle_resistors); i++) + if (ohms == rv3028_trickle_resistors[i]) + break; + + if (i < ARRAY_SIZE(rv3028_trickle_resistors)) { + ret = regmap_update_bits(rv3028->regmap, RV3028_BACKUP, + RV3028_BACKUP_TCE | + RV3028_BACKUP_TCR_MASK, + RV3028_BACKUP_TCE | i); + if (ret) + return ret; + } else { + dev_warn(&client->dev, "invalid trickle resistor value\n"); + } + } + + ret = rtc_add_group(rv3028->rtc, &rv3028_attr_group); + if (ret) + return ret; + + rv3028->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; + rv3028->rtc->range_max = RTC_TIMESTAMP_END_2099; + rv3028->rtc->ops = &rv3028_rtc_ops; + ret = rtc_register_device(rv3028->rtc); + if (ret) + return ret; + + nvmem_cfg.priv = rv3028->regmap; + rtc_nvmem_register(rv3028->rtc, &nvmem_cfg); + eeprom_cfg.priv = rv3028->regmap; + rtc_nvmem_register(rv3028->rtc, &eeprom_cfg); + + rv3028->rtc->max_user_freq = 1; + + return 0; +} + +static const struct of_device_id rv3028_of_match[] = { + { .compatible = "microcrystal,rv3028", }, + { } +}; +MODULE_DEVICE_TABLE(of, rv3028_of_match); + +static struct i2c_driver rv3028_driver = { + .driver = { + .name = "rtc-rv3028", + .of_match_table = of_match_ptr(rv3028_of_match), + }, + .probe_new = rv3028_probe, +}; +module_i2c_driver(rv3028_driver); + +MODULE_AUTHOR("Alexandre Belloni "); +MODULE_DESCRIPTION("Micro Crystal RV3028 RTC driver"); +MODULE_LICENSE("GPL v2");