@@ -920,6 +920,21 @@ config RTC_DRV_PCF2127
This driver can also be built as a module. If so, the module
will be called rtc-pcf2127.
+config RTC_DRV_PCF2131
+ tristate "NXP PCF2131"
+ depends on RTC_I2C
+ select REGMAP_I2C if I2C
+ select WATCHDOG_CORE if WATCHDOG
+ help
+ If you say yes here you enable support for the NXP PCF2131 RTC
+ chip, a CMOS RTC and calendar with an integrated temperature
+ compensated crystal (Xtal) oscillator (TCXO) and a 32.768 kHz
+ quartz crystal optimized for very high accuracy and ultra-low
+ power consumption.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-pcf2131.
+
config RTC_DRV_RV3029C2
tristate "Micro Crystal RV3029/3049"
depends on RTC_I2C_AND_SPI
@@ -121,6 +121,7 @@ obj-$(CONFIG_RTC_DRV_PALMAS) += rtc-palmas.o
obj-$(CONFIG_RTC_DRV_PCAP) += rtc-pcap.o
obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o
obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o
+obj-$(CONFIG_RTC_DRV_PCF2131) += rtc-pcf2131.o
obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o
new file mode 100644
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * An I2C and SPI driver for the NXP PCF2131 RTC
+ * Copyright 2022 Timesys Corp.
+ *
+ * Author: Angelo Dureghello <angelo.dureghello@timesys.com>
+ *
+ * Based on the other drivers in this same directory.
+ *
+ */
+
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <linux/bcd.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+#define PCF2131_REG_CTRL1 0x00
+#define PCF2131_BIT_CTRL1_STOP BIT(5)
+
+#define PCF2131_REG_CTRL2 0x01
+
+/* Control register 3 */
+#define PCF2131_REG_CTRL3 0x02
+#define PCF2131_BIT_CTRL3_BLIE BIT(0)
+#define PCF2131_BIT_CTRL3_BIE BIT(1)
+#define PCF2131_BIT_CTRL3_BLF BIT(2)
+#define PCF2131_BIT_CTRL3_BF BIT(3)
+#define PCF2131_BIT_CTRL3_BTSE BIT(4)
+
+#define PCF2131_REG_CTRL4 0x03
+#define PCF2131_BIT_CTRL4_TSF4 BIT(4)
+#define PCF2131_BIT_CTRL4_TSF3 BIT(5)
+#define PCF2131_BIT_CTRL4_TSF2 BIT(6)
+#define PCF2131_BIT_CTRL4_TSF1 BIT(7)
+
+#define PCF2131_REG_CTRL5 0x04
+#define PCF2131_BIT_CTRL5_TSIE4 BIT(4)
+#define PCF2131_BIT_CTRL5_TSIE3 BIT(5)
+#define PCF2131_BIT_CTRL5_TSIE2 BIT(6)
+#define PCF2131_BIT_CTRL5_TSIE1 BIT(7)
+
+#define PCF2131_REG_SW_RST 0x05
+#define PCF2131_BIT_SW_RST_CPR BIT(7)
+
+/* Time and date registers */
+#define PCF2131_REG_SC 0x07
+#define PCF2131_BIT_SC_OSF BIT(7)
+#define PCF2131_REG_MN 0x08
+#define PCF2131_REG_HR 0x09
+#define PCF2131_REG_DM 0x0a
+#define PCF2131_REG_DW 0x0b
+#define PCF2131_REG_MO 0x0c
+#define PCF2131_REG_YR 0x0d
+
+/* Timestamps registers */
+#define PCF2131_TIMESTAMPS 4
+#define PCF2131_REG_TS_SIZE 7
+#define PCF2131_REG_TS1_CTRL 0x14
+#define PCF2131_REG_TS2_CTRL 0x1b
+#define PCF2131_REG_TS3_CTRL 0x22
+#define PCF2131_REG_TS4_CTRL 0x29
+#define PCF2131_BIT_TS_CTRL_TSOFF BIT(6)
+#define PCF2131_BIT_TS_CTRL_TSM BIT(7)
+
+/* Watchdog registers */
+#define PCF2131_REG_WD_CTL 0x35
+#define PCF2131_BIT_WD_CTL_TF0 BIT(0)
+#define PCF2131_BIT_WD_CTL_TF1 BIT(1)
+#define PCF2131_BIT_WD_CTL_TI_TP BIT(5)
+#define PCF2131_BIT_WD_CTL_CD BIT(7)
+#define PCF2131_REG_WD_VAL 0x36
+
+/* Watchdog timer value constants */
+#define PCF2131_WD_VAL_STOP 0
+#define PCF2131_WD_VAL_MIN 2
+#define PCF2131_WD_VAL_MAX 255
+#define PCF2131_WD_VAL_DEFAULT 60
+
+struct pcf2131 {
+ struct rtc_device *rtc;
+ struct regmap *regmap;
+ struct watchdog_device wdd;
+};
+
+static int pcf2131_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf2131 *pcf2131 = dev_get_drvdata(dev);
+ u8 buf[16];
+ int ret;
+
+ ret = regmap_bulk_read(pcf2131->regmap, PCF2131_REG_CTRL1,
+ buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ if (buf[PCF2131_REG_CTRL3] & PCF2131_BIT_CTRL3_BLF)
+ dev_info(dev, "low voltage, check/replace RTC battery.\n");
+
+ if (buf[PCF2131_REG_SC] & PCF2131_BIT_SC_OSF) {
+ dev_err(dev, "oscillator stop detected, time not reliable.\n");
+
+ return -EINVAL;
+ }
+
+ tm->tm_sec = bcd2bin(buf[PCF2131_REG_SC] & 0x7F);
+ tm->tm_min = bcd2bin(buf[PCF2131_REG_MN] & 0x7F);
+ tm->tm_hour = bcd2bin(buf[PCF2131_REG_HR] & 0x3F);
+ tm->tm_mday = bcd2bin(buf[PCF2131_REG_DM] & 0x3F);
+ tm->tm_wday = bcd2bin(buf[PCF2131_REG_DW] & 0x07);
+ tm->tm_mon = bcd2bin(buf[PCF2131_REG_MO] & 0x1F) - 1;
+ tm->tm_year = bcd2bin(buf[PCF2131_REG_YR]);
+ if (tm->tm_year < 70)
+ tm->tm_year += 100;
+
+ return 0;
+}
+
+static int pcf2131_rtc_stop(struct regmap *regmap)
+{
+ return regmap_update_bits(regmap, PCF2131_REG_CTRL1,
+ PCF2131_BIT_CTRL1_STOP,
+ PCF2131_BIT_CTRL1_STOP);
+}
+
+static int pcf2131_start_rtc(struct regmap *regmap)
+{
+ return regmap_update_bits(regmap, PCF2131_REG_CTRL1,
+ PCF2131_BIT_CTRL1_STOP,
+ 0);
+}
+
+static int pcf2131_rtc_clear_prescaler(struct regmap *regmap)
+{
+ return regmap_update_bits(regmap, PCF2131_REG_SW_RST,
+ PCF2131_BIT_SW_RST_CPR,
+ PCF2131_BIT_SW_RST_CPR);
+}
+
+static int pcf2131_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf2131 *pcf2131 = dev_get_drvdata(dev);
+ unsigned char buf[7];
+ int i = 0, err;
+
+ if (tm->tm_year < 100 || tm->tm_year >= 200)
+ return -EINVAL;
+
+ err = pcf2131_rtc_stop(pcf2131->regmap);
+ if (err)
+ goto ret_err;
+
+ err = pcf2131_rtc_clear_prescaler(pcf2131->regmap);
+ if (err)
+ goto ret_err;
+
+ /* this will also clear OSF flag */
+ buf[i++] = bin2bcd(tm->tm_sec);
+ buf[i++] = bin2bcd(tm->tm_min);
+ buf[i++] = bin2bcd(tm->tm_hour);
+ buf[i++] = bin2bcd(tm->tm_mday);
+ buf[i++] = bin2bcd(tm->tm_wday) & 0x07;
+ buf[i++] = bin2bcd(tm->tm_mon + 1);
+ buf[i++] = bin2bcd(tm->tm_year % 100);
+
+ err = regmap_bulk_write(pcf2131->regmap, PCF2131_REG_SC, buf, i);
+ if (err < 0) {
+ pcf2131_start_rtc(pcf2131->regmap);
+ goto ret_err;
+ }
+
+ if (pcf2131_start_rtc(pcf2131->regmap))
+ goto ret_err;
+
+ return 0;
+
+ret_err:
+ dev_err(dev, "%s: err=%d", __func__, err);
+
+ return err;
+}
+
+#ifdef CONFIG_RTC_INTF_DEV
+static int pcf2131_rtc_ioctl(struct device *dev,
+ unsigned int cmd, unsigned long arg)
+{
+ struct pcf2131 *pcf2131 = dev_get_drvdata(dev);
+ int ret, touser;
+
+ switch (cmd) {
+ case RTC_VL_READ:
+ ret = regmap_read(pcf2131->regmap, PCF2131_REG_CTRL3, &touser);
+ if (ret < 0)
+ return ret;
+
+ touser = touser & PCF2131_BIT_CTRL3_BLF ? 1 : 0;
+
+ if (copy_to_user((void __user *)arg, &touser, sizeof(int)))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+#else
+#define pcf2131_rtc_ioctl NULL
+#endif
+
+static const struct rtc_class_ops pcf2131_rtc_ops = {
+ .ioctl = pcf2131_rtc_ioctl,
+ .read_time = pcf2131_rtc_read_time,
+ .set_time = pcf2131_rtc_set_time,
+};
+
+/* watchdog driver */
+
+static int pcf2131_wdt_ping(struct watchdog_device *wdd)
+{
+ struct pcf2131 *pcf2131 = watchdog_get_drvdata(wdd);
+
+ return regmap_write(pcf2131->regmap, PCF2131_REG_WD_VAL, wdd->timeout);
+}
+
+/*
+ * Restart watchdog timer if feature is active.
+ *
+ * Note: Reading CTRL2 register causes watchdog to stop which is unfortunate,
+ * since register also contain control/status flags for other features.
+ * Always call this function after reading CTRL2 register.
+ */
+static int pcf2131_wdt_active_ping(struct watchdog_device *wdd)
+{
+ int ret = 0;
+
+ if (watchdog_active(wdd)) {
+ ret = pcf2131_wdt_ping(wdd);
+ if (ret)
+ dev_err(wdd->parent,
+ "%s: watchdog restart failed, ret=%d\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static int pcf2131_wdt_start(struct watchdog_device *wdd)
+{
+ return pcf2131_wdt_ping(wdd);
+}
+
+static int pcf2131_wdt_stop(struct watchdog_device *wdd)
+{
+ struct pcf2131 *pcf2131 = watchdog_get_drvdata(wdd);
+
+ return regmap_write(pcf2131->regmap, PCF2131_REG_WD_VAL,
+ PCF2131_WD_VAL_STOP);
+}
+
+static int pcf2131_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int new_timeout)
+{
+ dev_dbg(wdd->parent, "new watchdog timeout: %is (old: %is)\n",
+ new_timeout, wdd->timeout);
+
+ wdd->timeout = new_timeout;
+
+ return pcf2131_wdt_active_ping(wdd);
+}
+
+static const struct watchdog_info pcf2131_wdt_info = {
+ .identity = "NXP PCF2131 Watchdog",
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+};
+
+static const struct watchdog_ops pcf2131_watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = pcf2131_wdt_start,
+ .stop = pcf2131_wdt_stop,
+ .ping = pcf2131_wdt_ping,
+ .set_timeout = pcf2131_wdt_set_timeout,
+};
+
+/* sysfs interface */
+
+static ssize_t __timestamp_store(int idx,
+ struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcf2131 *pcf2131 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_update_bits(pcf2131->regmap,
+ PCF2131_REG_CTRL4,
+ PCF2131_BIT_CTRL4_TSF1 >> idx, 0);
+ if (ret < 0) {
+ dev_err(dev, "%s: update ctrl1 ret=%d\n", __func__, ret);
+ return ret;
+ }
+
+ ret = pcf2131_wdt_active_ping(&pcf2131->wdd);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t timestamp0_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return __timestamp_store(0, dev, attr, buf, count);
+}
+
+static ssize_t timestamp1_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return __timestamp_store(1, dev, attr, buf, count);
+}
+
+static ssize_t timestamp2_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return __timestamp_store(2, dev, attr, buf, count);
+}
+
+static ssize_t timestamp3_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return __timestamp_store(3, dev, attr, buf, count);
+}
+
+static ssize_t __timestamp_show(int idx, struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pcf2131 *pcf2131 = dev_get_drvdata(dev);
+ struct rtc_time tm;
+ int ret;
+ int offs;
+ unsigned char data[48];
+
+ ret = regmap_bulk_read(pcf2131->regmap, PCF2131_REG_CTRL1,
+ data, sizeof(data));
+ if (ret < 0)
+ return ret;
+
+ offs = PCF2131_REG_TS1_CTRL + (idx * PCF2131_REG_TS_SIZE);
+
+ ret = pcf2131_wdt_active_ping(&pcf2131->wdd);
+ if (ret)
+ return ret;
+
+ if (!(data[PCF2131_REG_CTRL4] & (PCF2131_BIT_CTRL4_TSF1 >> idx)))
+ return 0;
+
+ tm.tm_sec = bcd2bin(data[offs + 1] & 0x7F);
+ tm.tm_min = bcd2bin(data[offs + 2] & 0x7F);
+ tm.tm_hour = bcd2bin(data[offs + 3] & 0x3F);
+ tm.tm_mday = bcd2bin(data[offs + 4] & 0x3F);
+ tm.tm_mon = bcd2bin(data[offs + 5] & 0x1F) - 1;
+ tm.tm_year = bcd2bin(data[offs + 6]);
+ if (tm.tm_year < 70)
+ tm.tm_year += 100;
+
+ ret = rtc_valid_tm(&tm);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%llu\n",
+ (unsigned long long)rtc_tm_to_time64(&tm));
+};
+
+static ssize_t timestamp0_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return __timestamp_show(0, dev, attr, buf);
+}
+
+static ssize_t timestamp1_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return __timestamp_show(1, dev, attr, buf);
+}
+static ssize_t timestamp2_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return __timestamp_show(2, dev, attr, buf);
+}
+
+static ssize_t timestamp3_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return __timestamp_show(3, dev, attr, buf);
+}
+
+static DEVICE_ATTR_RW(timestamp0);
+static DEVICE_ATTR_RW(timestamp1);
+static DEVICE_ATTR_RW(timestamp2);
+static DEVICE_ATTR_RW(timestamp3);
+
+static struct attribute *pcf2131_attrs[] = {
+ &dev_attr_timestamp0.attr,
+ &dev_attr_timestamp1.attr,
+ &dev_attr_timestamp2.attr,
+ &dev_attr_timestamp3.attr,
+ NULL
+};
+
+static const struct attribute_group pcf2131_attr_group = {
+ .attrs = pcf2131_attrs,
+};
+
+/*
+ * This device does not support bulk transferts.
+ */
+static const struct regmap_config regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x36,
+ .use_single_read = 1,
+ .use_single_write = 1,
+};
+
+static int pcf2131_rtc_probe(struct i2c_client *client)
+{
+ struct pcf2131 *pcf2131;
+ struct rtc_device *rtc;
+ int i, ret = 0;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -ENODEV;
+
+ pcf2131 = devm_kzalloc(&client->dev,
+ sizeof(struct pcf2131), GFP_KERNEL);
+ if (!pcf2131)
+ return -ENOMEM;
+
+ pcf2131->regmap = devm_regmap_init_i2c(client, ®map_config);
+ if (IS_ERR(pcf2131->regmap))
+ return PTR_ERR(pcf2131->regmap);
+
+ i2c_set_clientdata(client, pcf2131);
+
+ rtc = devm_rtc_allocate_device(&client->dev);
+ if (IS_ERR(rtc))
+ return PTR_ERR(rtc);
+ pcf2131->rtc = rtc;
+
+ pcf2131->rtc->ops = &pcf2131_rtc_ops;
+
+ pcf2131->wdd.parent = &client->dev;
+ pcf2131->wdd.info = &pcf2131_wdt_info;
+ pcf2131->wdd.ops = &pcf2131_watchdog_ops;
+ pcf2131->wdd.min_timeout = PCF2131_WD_VAL_MIN;
+ pcf2131->wdd.max_timeout = PCF2131_WD_VAL_MAX;
+ pcf2131->wdd.timeout = PCF2131_WD_VAL_DEFAULT;
+ pcf2131->wdd.min_hw_heartbeat_ms = 500;
+
+ watchdog_set_drvdata(&pcf2131->wdd, pcf2131);
+
+ /*
+ * Watchdog timer enabled and int pins /INTA/B activated when timed out.
+ * Select 4Hz clock source for watchdog timer.
+ * Timer is not started until WD_VAL is loaded with a valid value.
+ * Note: countdown timer not available.
+ */
+ ret = regmap_update_bits(pcf2131->regmap, PCF2131_REG_WD_CTL,
+ PCF2131_BIT_WD_CTL_CD |
+ PCF2131_BIT_WD_CTL_TI_TP |
+ PCF2131_BIT_WD_CTL_TF1 |
+ PCF2131_BIT_WD_CTL_TF0,
+ PCF2131_BIT_WD_CTL_CD |
+ PCF2131_BIT_WD_CTL_TF0);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: watchdog config failed, err %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+#ifdef CONFIG_WATCHDOG
+ ret = devm_watchdog_register_device(&client->dev, &pcf2131->wdd);
+ if (ret)
+ return ret;
+#endif /* CONFIG_WATCHDOG */
+
+ /*
+ * Disable battery low/switch-over timestamp and interrupts.
+ * Clear battery interrupt flags which can block new trigger events.
+ * Note: This is the default chip behaviour but added to ensure
+ * correct tamper timestamp and interrupt function.
+ */
+ ret = regmap_update_bits(pcf2131->regmap, PCF2131_REG_CTRL3,
+ PCF2131_BIT_CTRL3_BTSE |
+ PCF2131_BIT_CTRL3_BF |
+ PCF2131_BIT_CTRL3_BIE |
+ PCF2131_BIT_CTRL3_BLIE, 0);
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: interrupt config (ctrl3) failed\n",
+ __func__);
+ return ret;
+ }
+
+ for (i = 0; i < PCF2131_TIMESTAMPS; i++) {
+ int reg = PCF2131_REG_TS1_CTRL + i * PCF2131_REG_TS_SIZE;
+
+ ret = regmap_update_bits(pcf2131->regmap, reg,
+ PCF2131_BIT_TS_CTRL_TSOFF |
+ PCF2131_BIT_TS_CTRL_TSM,
+ PCF2131_BIT_TS_CTRL_TSM);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s: tamper detection %d config failed\n",
+ __func__, i);
+ return ret;
+ }
+
+ ret = regmap_update_bits(pcf2131->regmap, PCF2131_REG_CTRL5,
+ PCF2131_BIT_CTRL5_TSIE1 >> i,
+ PCF2131_BIT_CTRL5_TSIE1 >> i);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s: tamper interrupt config %d failed\n",
+ __func__, i);
+ return ret;
+ }
+ }
+
+ ret = rtc_add_group(rtc, &pcf2131_attr_group);
+ if (ret) {
+ dev_err(&client->dev, "%s: tamper sysfs registering failed\n",
+ __func__);
+ return ret;
+ }
+
+ return devm_rtc_register_device(rtc);
+}
+
+static const struct i2c_device_id pcf2131_id[] = {
+ { "pcf2131", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pcf2131_id);
+
+static const struct of_device_id pcf2131_rtc_of_match[] = {
+ { .compatible = "nxp,pcf2131" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pcf2131_rtc_of_match);
+
+static struct i2c_driver pcf2131_rtc_driver = {
+ .driver = {
+ .name = "rtc-pcf2131-i2c",
+ .of_match_table = of_match_ptr(pcf2131_rtc_of_match),
+ },
+ .probe_new = pcf2131_rtc_probe,
+ .id_table = pcf2131_id,
+};
+module_i2c_driver(pcf2131_rtc_driver);
+
+MODULE_AUTHOR("Angelo Dureghello <angelo.dureghello@timesys.com>");
+MODULE_DESCRIPTION("NXP PCF2131 RTC driver");
+MODULE_LICENSE("GPL");
Started up this driver from similar rtc pcf2127. Actually only i2c communication has been tested, spi part can be easily added as needed in a later time. Signed-off-by: Angelo Dureghello <angelo.dureghello@timesys.com> --- drivers/rtc/Kconfig | 15 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-pcf2131.c | 569 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 585 insertions(+) create mode 100644 drivers/rtc/rtc-pcf2131.c