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@xxxxxxxxxxx> --- 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 diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index b8de25118ad0..81aaa43ac8a0 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -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 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index aab22bc63432..30b429733c70 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -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 diff --git a/drivers/rtc/rtc-pcf2131.c b/drivers/rtc/rtc-pcf2131.c new file mode 100644 index 000000000000..38fabbc49030 --- /dev/null +++ b/drivers/rtc/rtc-pcf2131.c @@ -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@xxxxxxxxxxx> + * + * 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@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("NXP PCF2131 RTC driver"); +MODULE_LICENSE("GPL"); -- 2.37.3