diff mbox series

rtc: rx8111: Add NVMEM and timestamp functionality

Message ID 20250429-rtc-rx8111-events-v1-1-7e2a61126042@axis.com
State New
Headers show
Series rtc: rx8111: Add NVMEM and timestamp functionality | expand

Commit Message

Anders Sandahl April 29, 2025, 1:59 p.m. UTC
Introduce support for saving a timestamp triggered by an external
event via the EVIN pin. After an event detection, the timestamp can be
retrieved from timestamp0 in sysfs.

Also add a sysfs control timestamp0_write_nvmem to enable the RX8111
feature that stores timestamps in NVMEM as a circular buffer.

Signed-off-by: Anders Sandahl <anders.sandahl@axis.com>
---
The Epson RX8111 device has support for saving a time stamp when a 
hardware trigger occurs. It also has a register area that can be used 
as non-volatile memory.

Datasheet: https://download.epsondevice.com/td/pdf/app/RX8111CE_en.pdf

Timestamp is made available in the same manner as in "rtc-rv3028.c"
through sys-fs. NVMEM is made available using the framework functions.
---
 drivers/rtc/rtc-rx8111.c | 351 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 349 insertions(+), 2 deletions(-)


---
base-commit: 38fec10eb60d687e30c8c6b5420d86e8149f7557
change-id: 20250424-rtc-rx8111-events-df9a35810a73

Best regards,

Comments

Anders Sandahl June 16, 2025, 2:29 p.m. UTC | #1
Hello,

Please let me know if I need to do something more?

BR Anders

On 4/29/25 15:59, Anders Sandahl wrote:
> Introduce support for saving a timestamp triggered by an external
> event via the EVIN pin. After an event detection, the timestamp can be
> retrieved from timestamp0 in sysfs.
>
> Also add a sysfs control timestamp0_write_nvmem to enable the RX8111
> feature that stores timestamps in NVMEM as a circular buffer.
>
> Signed-off-by: Anders Sandahl <anders.sandahl@axis.com>
> ---
> The Epson RX8111 device has support for saving a time stamp when a
> hardware trigger occurs. It also has a register area that can be used
> as non-volatile memory.
>
> Datasheet: https://download.epsondevice.com/td/pdf/app/RX8111CE_en.pdf
>
> Timestamp is made available in the same manner as in "rtc-rv3028.c"
> through sys-fs. NVMEM is made available using the framework functions.
> ---
>   drivers/rtc/rtc-rx8111.c | 351 ++++++++++++++++++++++++++++++++++++++++++++++-
>   1 file changed, 349 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/rtc/rtc-rx8111.c b/drivers/rtc/rtc-rx8111.c
> index 8450d9f0b566c63bca04988d892ea4db585ac526..4f428eb98f4858b4c70b3a5709d8204a77d0a1ea 100644
> --- a/drivers/rtc/rtc-rx8111.c
> +++ b/drivers/rtc/rtc-rx8111.c
> @@ -58,7 +58,14 @@
>   #define RX8111_FLAG_XST_BIT BIT(0)
>   #define RX8111_FLAG_VLF_BIT BIT(1)
>   
> +#define RX8111_REG_TS_RAM_START		0x40	/* Timestamp RAM area start. */
> +#define RX8111_REG_TS_RAM_END		0x7f	/* Timestamp RAM area end. */
> +
> +#define RX8111_BIT_EVIN_SETTING_OVW	BIT(1)	/* Enable overwrite timestamp RAM. */
> +#define RX8111_BIT_EVIN_SETTING_PU1	BIT(3)	/* Pull up select 1. */
> +
>   #define RX8111_TIME_BUF_SZ (RX8111_REG_YEAR - RX8111_REG_SEC + 1)
> +#define RX8111_TS_BUF_SZ (RX8111_REG_TS_YEAR - RX8111_REG_TS_SEC + 1)
>   
>   enum rx8111_regfield {
>   	/* RX8111_REG_EXT. */
> @@ -98,6 +105,11 @@ enum rx8111_regfield {
>   	/* RX8111_REG_STATUS_MON. */
>   	RX8111_REGF_VLOW,
>   
> +	/* RX8111_REG_TS_CTRL1. */
> +	RX8111_REGF_TSRAM,
> +	RX8111_REGF_TSCLR,
> +	RX8111_REGF_EISEL,
> +
>   	/* Sentinel value. */
>   	RX8111_REGF_MAX
>   };
> @@ -134,12 +146,16 @@ static const struct reg_field rx8111_regfields[] = {
>   	[RX8111_REGF_CHGEN]  = REG_FIELD(RX8111_REG_PWR_SWITCH_CTRL, 7, 7),
>   
>   	[RX8111_REGF_VLOW]  = REG_FIELD(RX8111_REG_STATUS_MON, 1, 1),
> +
> +	[RX8111_REGF_TSRAM]  = REG_FIELD(RX8111_REG_TS_CTRL1, 0, 0),
> +	[RX8111_REGF_TSCLR]  = REG_FIELD(RX8111_REG_TS_CTRL1, 1, 1),
> +	[RX8111_REGF_EISEL]  = REG_FIELD(RX8111_REG_TS_CTRL1, 2, 2),
>   };
>   
>   static const struct regmap_config rx8111_regmap_config = {
>   	.reg_bits = 8,
>   	.val_bits = 8,
> -	.max_register = RX8111_REG_TS_CTRL3,
> +	.max_register = RX8111_REG_TS_RAM_END,
>   };
>   
>   struct rx8111_data {
> @@ -147,8 +163,224 @@ struct rx8111_data {
>   	struct regmap_field *regfields[RX8111_REGF_MAX];
>   	struct device *dev;
>   	struct rtc_device *rtc;
> +	spinlock_t ts_lock;	/* Don't allow poll of ETS bit when it's temporarily disabled. */
>   };
>   
> +static ssize_t timestamp0_store(struct device *dev,
> +				struct device_attribute *attr, const char *buf,
> +				size_t count)
> +{
> +	struct rx8111_data *data = dev_get_drvdata(dev);
> +	int ret, etsval;
> +
> +	/*
> +	 * Clear event only if events are enabled. This is to protect
> +	 * us from losing events in the future if events have been disabled
> +	 * by mistake (error in read function).
> +	 */
> +	spin_lock(&data->ts_lock);
> +	ret = regmap_field_read(data->regfields[RX8111_REGF_ETS], &etsval);
> +	spin_unlock(&data->ts_lock);
> +
> +	if (ret) {
> +		dev_dbg(dev, "Could not read ETS (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	if (!etsval)
> +		return -EINVAL;
> +
> +	ret = regmap_field_write(data->regfields[RX8111_REGF_EVF], 0);
> +	if (ret) {
> +		dev_dbg(dev, "Could not write EVF bit (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_field_write(data->regfields[RX8111_REGF_TSCLR], 1);
> +	if (ret) {
> +		dev_dbg(dev, "Could not write TSCLR bit (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	return count;
> +}
> +
> +static ssize_t timestamp0_show(struct device *dev,
> +			       struct device_attribute *attr, char *buf)
> +{
> +	struct rx8111_data *data = dev_get_drvdata(dev);
> +
> +	struct rtc_time tm;
> +	int ret, evfval;
> +	u8 date[RX8111_TS_BUF_SZ];
> +
> +	/* Read out timestamp values only when an event has occurred. */
> +	ret = regmap_field_read(data->regfields[RX8111_REGF_EVF], &evfval);
> +	if (ret) {
> +		dev_dbg(dev, "Could not read EVF (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	if (!evfval)
> +		return 0;
> +
> +	spin_lock(&data->ts_lock);
> +
> +	/* Disable timestamp during readout to avoid unreliable data. */
> +	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 0);
> +	if (ret) {
> +		dev_dbg(dev, "Could not disable timestamp function (%d)\n",
> +			ret);
> +		goto err_out;
> +	}
> +
> +	ret = regmap_bulk_read(data->regmap, RX8111_REG_TS_SEC, date,
> +			       sizeof(date));
> +	if (ret) {
> +		dev_dbg(dev, "Could not read timestamp data (%d)\n", ret);
> +		goto err_out;
> +	}
> +
> +	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 1);
> +	if (ret) {
> +		dev_dbg(dev, "Could not enable timestamp function (%d)\n", ret);
> +		goto err_out;
> +	}
> +
> +	spin_unlock(&data->ts_lock);
> +
> +	tm.tm_sec = bcd2bin(date[0]);
> +	tm.tm_min = bcd2bin(date[1]);
> +	tm.tm_hour = bcd2bin(date[2]);
> +	tm.tm_mday = bcd2bin(date[4]);
> +	tm.tm_mon = bcd2bin(date[5]) - 1;
> +	tm.tm_year = bcd2bin(date[6]) + 100;
> +
> +	ret = rtc_valid_tm(&tm);
> +	if (ret)
> +		return ret;
> +
> +	return sprintf(buf, "%llu\n",
> +		       (unsigned long long)rtc_tm_to_time64(&tm));
> +
> +err_out:
> +	spin_unlock(&data->ts_lock);
> +	return ret;
> +}
> +
> +static DEVICE_ATTR_RW(timestamp0);
> +
> +static ssize_t timestamp0_write_nvmem_store(struct device *dev,
> +					    struct device_attribute *attr,
> +					    const char *buf, size_t count)
> +{
> +	struct rx8111_data *data = dev_get_drvdata(dev);
> +	bool enable;
> +	int ret;
> +
> +	if (count < 1)
> +		return -EINVAL;
> +
> +	ret = kstrtobool(buf, &enable);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_field_write(data->regfields[RX8111_REGF_TSRAM],
> +				 enable ? 1 : 0);
> +	if (ret) {
> +		dev_dbg(dev, "Could not set TSRAM bit (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	return count;
> +}
> +
> +static ssize_t timestamp0_write_nvmem_show(struct device *dev,
> +					   struct device_attribute *attr,
> +					   char *buf)
> +{
> +	struct rx8111_data *data = dev_get_drvdata(dev);
> +	int enable;
> +	int ret;
> +
> +	ret = regmap_field_read(data->regfields[RX8111_REGF_TSRAM], &enable);
> +	if (ret) {
> +		dev_dbg(dev, "Could not read TSRAM bit (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	return sprintf(buf, "%s\n", enable ? "1" : "0");
> +}
> +
> +static DEVICE_ATTR_RW(timestamp0_write_nvmem);
> +
> +static int rx8111_sysfs_register(struct device *dev)
> +{
> +	int ret;
> +
> +	ret = device_create_file(dev, &dev_attr_timestamp0);
> +	if (ret)
> +		return ret;
> +
> +	ret = device_create_file(dev, &dev_attr_timestamp0_write_nvmem);
> +	if (ret)
> +		device_remove_file(dev, &dev_attr_timestamp0);
> +
> +	return ret;
> +}
> +
> +static void rx8111_sysfs_unregister(void *data)
> +{
> +	struct device *dev = (struct device *)data;
> +
> +	device_remove_file(dev, &dev_attr_timestamp0);
> +	device_remove_file(dev, &dev_attr_timestamp0_write_nvmem);
> +}
> +
> +static int rx8111_setup(struct rx8111_data *data)
> +{
> +	int ret;
> +
> +	/* Disable multiple timestamps; area is used for nvmem as default. */
> +	ret = regmap_write(data->regmap, RX8111_REG_TS_CTRL2, 0);
> +	if (ret) {
> +		dev_dbg(data->dev, "Could not setup TS_CTRL2 (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_write(data->regmap, RX8111_REG_TS_CTRL3, 0);
> +	if (ret) {
> +		dev_dbg(data->dev, "Could not setup TS_CTRL3 (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	/* Configure EVIN pin, trigger on low level. PU = 1M Ohm. */
> +	ret = regmap_write(data->regmap, RX8111_REG_EVIN_SETTING,
> +			   RX8111_BIT_EVIN_SETTING_PU1 |
> +				   RX8111_BIT_EVIN_SETTING_OVW);
> +	if (ret) {
> +		dev_dbg(data->dev, "Could not setup EVIN (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	/* Enable timestamp triggered by EVIN pin. */
> +	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 1);
> +	if (ret) {
> +		dev_dbg(data->dev, "Could not enable timestamp function (%d)\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	/* Disable all interrupts. */
> +	ret = regmap_write(data->regmap, RX8111_REG_CTRL, 0);
> +	if (ret) {
> +		dev_dbg(data->dev, "Could not disable interrupts (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
>   static int rx8111_read_vl_flag(struct rx8111_data *data, unsigned int *vlval)
>   {
>   	int ret;
> @@ -160,6 +392,17 @@ static int rx8111_read_vl_flag(struct rx8111_data *data, unsigned int *vlval)
>   	return ret;
>   }
>   
> +static int rx8111_clear_vl_flag(struct rx8111_data *data)
> +{
> +	int ret;
> +
> +	ret = regmap_field_write(data->regfields[RX8111_REGF_VLF], 0);
> +	if (ret)
> +		dev_dbg(data->dev, "Could not write VL flag (%d)", ret);
> +
> +	return ret;
> +}
> +
>   static int rx8111_read_time(struct device *dev, struct rtc_time *tm)
>   {
>   	struct rx8111_data *data = dev_get_drvdata(dev);
> @@ -289,11 +532,69 @@ static int rx8111_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
>   		vlval |= regval ? RTC_VL_BACKUP_LOW : 0;
>   
>   		return put_user(vlval, (typeof(vlval) __user *)arg);
> +	case RTC_VL_CLR:
> +		return rx8111_clear_vl_flag(data);
>   	default:
>   		return -ENOIOCTLCMD;
>   	}
>   }
>   
> +static int rx8111_nvram_write(void *priv, unsigned int offset, void *val,
> +			      size_t bytes)
> +{
> +	struct rx8111_data *data = priv;
> +	int ret, len;
> +
> +	/*
> +	 * The RX8111 device can only handle transfers with repeated start
> +	 * within the same 16 bytes aligned block.
> +	 */
> +	while (bytes > 0) {
> +		len = ((offset % 15) + bytes > 16) ? 16 - (offset % 16) : bytes;
> +		ret = regmap_bulk_write(data->regmap,
> +					RX8111_REG_TS_RAM_START + offset, val,
> +					len);
> +		if (ret) {
> +			dev_dbg(data->dev, "Could not write nvmem (%d)\n", ret);
> +			return ret;
> +		}
> +
> +		val += len;
> +		offset += len;
> +		bytes -= len;
> +	}
> +
> +	return 0;
> +}
> +
> +static int rx8111_nvram_read(void *priv, unsigned int offset, void *val,
> +			     size_t bytes)
> +{
> +	struct rx8111_data *data = priv;
> +	int ret, len;
> +
> +	/*
> +	 * The RX8111 device can only handle transfers with repeated start
> +	 * within the same 16 bytes aligned block.
> +	 */
> +	while (bytes > 0) {
> +		len = ((offset % 15) + bytes > 16) ? 16 - (offset % 16) : bytes;
> +		ret = regmap_bulk_read(data->regmap,
> +				       RX8111_REG_TS_RAM_START + offset, val,
> +				       len);
> +		if (ret) {
> +			dev_dbg(data->dev, "Could not read nvmem (%d)\n", ret);
> +			return ret;
> +		}
> +
> +		val += len;
> +		offset += len;
> +		bytes -= len;
> +	}
> +
> +	return 0;
> +}
> +
>   static const struct rtc_class_ops rx8111_rtc_ops = {
>   	.read_time = rx8111_read_time,
>   	.set_time = rx8111_set_time,
> @@ -305,6 +606,16 @@ static int rx8111_probe(struct i2c_client *client)
>   	struct rx8111_data *data;
>   	struct rtc_device *rtc;
>   	size_t i;
> +	int ret;
> +	struct nvmem_config nvmem_cfg = {
> +		.name = "rx8111_nvram",
> +		.word_size = 1,
> +		.stride = 1,
> +		.size = RX8111_REG_TS_RAM_END - RX8111_REG_TS_RAM_START + 1,
> +		.type = NVMEM_TYPE_BATTERY_BACKED,
> +		.reg_read = rx8111_nvram_read,
> +		.reg_write = rx8111_nvram_write,
> +	};
>   
>   	data = devm_kmalloc(&client->dev, sizeof(*data), GFP_KERNEL);
>   	if (!data) {
> @@ -312,6 +623,8 @@ static int rx8111_probe(struct i2c_client *client)
>   		return -ENOMEM;
>   	}
>   
> +	spin_lock_init(&data->ts_lock);
> +
>   	data->dev = &client->dev;
>   	dev_set_drvdata(data->dev, data);
>   
> @@ -331,6 +644,10 @@ static int rx8111_probe(struct i2c_client *client)
>   		}
>   	}
>   
> +	ret = rx8111_setup(data);
> +	if (ret)
> +		return ret;
> +
>   	rtc = devm_rtc_allocate_device(data->dev);
>   	if (IS_ERR(rtc)) {
>   		dev_dbg(data->dev, "Could not allocate rtc device\n");
> @@ -343,7 +660,37 @@ static int rx8111_probe(struct i2c_client *client)
>   
>   	clear_bit(RTC_FEATURE_ALARM, rtc->features);
>   
> -	return devm_rtc_register_device(rtc);
> +	ret = devm_rtc_register_device(rtc);
> +	if (ret) {
> +		dev_dbg(data->dev,
> +			"Could not register rtc device (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = rx8111_sysfs_register(data->dev);
> +	if (ret) {
> +		dev_dbg(data->dev,
> +			"Could not create sysfs entry (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	ret = devm_add_action_or_reset(data->dev, &rx8111_sysfs_unregister,
> +				       data->dev);
> +	if (ret) {
> +		dev_dbg(data->dev,
> +			"Could not add sysfs unregister devres action (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	nvmem_cfg.priv = data;
> +	ret = devm_rtc_nvmem_register(rtc, &nvmem_cfg);
> +	if (ret) {
> +		dev_dbg(data->dev,
> +			"Could not register rtc nvmem device (%d)\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
>   }
>   
>   static const struct of_device_id rx8111_of_match[] = {
>
> ---
> base-commit: 38fec10eb60d687e30c8c6b5420d86e8149f7557
> change-id: 20250424-rtc-rx8111-events-df9a35810a73
>
> Best regards,
diff mbox series

Patch

diff --git a/drivers/rtc/rtc-rx8111.c b/drivers/rtc/rtc-rx8111.c
index 8450d9f0b566c63bca04988d892ea4db585ac526..4f428eb98f4858b4c70b3a5709d8204a77d0a1ea 100644
--- a/drivers/rtc/rtc-rx8111.c
+++ b/drivers/rtc/rtc-rx8111.c
@@ -58,7 +58,14 @@ 
 #define RX8111_FLAG_XST_BIT BIT(0)
 #define RX8111_FLAG_VLF_BIT BIT(1)
 
+#define RX8111_REG_TS_RAM_START		0x40	/* Timestamp RAM area start. */
+#define RX8111_REG_TS_RAM_END		0x7f	/* Timestamp RAM area end. */
+
+#define RX8111_BIT_EVIN_SETTING_OVW	BIT(1)	/* Enable overwrite timestamp RAM. */
+#define RX8111_BIT_EVIN_SETTING_PU1	BIT(3)	/* Pull up select 1. */
+
 #define RX8111_TIME_BUF_SZ (RX8111_REG_YEAR - RX8111_REG_SEC + 1)
+#define RX8111_TS_BUF_SZ (RX8111_REG_TS_YEAR - RX8111_REG_TS_SEC + 1)
 
 enum rx8111_regfield {
 	/* RX8111_REG_EXT. */
@@ -98,6 +105,11 @@  enum rx8111_regfield {
 	/* RX8111_REG_STATUS_MON. */
 	RX8111_REGF_VLOW,
 
+	/* RX8111_REG_TS_CTRL1. */
+	RX8111_REGF_TSRAM,
+	RX8111_REGF_TSCLR,
+	RX8111_REGF_EISEL,
+
 	/* Sentinel value. */
 	RX8111_REGF_MAX
 };
@@ -134,12 +146,16 @@  static const struct reg_field rx8111_regfields[] = {
 	[RX8111_REGF_CHGEN]  = REG_FIELD(RX8111_REG_PWR_SWITCH_CTRL, 7, 7),
 
 	[RX8111_REGF_VLOW]  = REG_FIELD(RX8111_REG_STATUS_MON, 1, 1),
+
+	[RX8111_REGF_TSRAM]  = REG_FIELD(RX8111_REG_TS_CTRL1, 0, 0),
+	[RX8111_REGF_TSCLR]  = REG_FIELD(RX8111_REG_TS_CTRL1, 1, 1),
+	[RX8111_REGF_EISEL]  = REG_FIELD(RX8111_REG_TS_CTRL1, 2, 2),
 };
 
 static const struct regmap_config rx8111_regmap_config = {
 	.reg_bits = 8,
 	.val_bits = 8,
-	.max_register = RX8111_REG_TS_CTRL3,
+	.max_register = RX8111_REG_TS_RAM_END,
 };
 
 struct rx8111_data {
@@ -147,8 +163,224 @@  struct rx8111_data {
 	struct regmap_field *regfields[RX8111_REGF_MAX];
 	struct device *dev;
 	struct rtc_device *rtc;
+	spinlock_t ts_lock;	/* Don't allow poll of ETS bit when it's temporarily disabled. */
 };
 
+static ssize_t timestamp0_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count)
+{
+	struct rx8111_data *data = dev_get_drvdata(dev);
+	int ret, etsval;
+
+	/*
+	 * Clear event only if events are enabled. This is to protect
+	 * us from losing events in the future if events have been disabled
+	 * by mistake (error in read function).
+	 */
+	spin_lock(&data->ts_lock);
+	ret = regmap_field_read(data->regfields[RX8111_REGF_ETS], &etsval);
+	spin_unlock(&data->ts_lock);
+
+	if (ret) {
+		dev_dbg(dev, "Could not read ETS (%d)\n", ret);
+		return ret;
+	}
+
+	if (!etsval)
+		return -EINVAL;
+
+	ret = regmap_field_write(data->regfields[RX8111_REGF_EVF], 0);
+	if (ret) {
+		dev_dbg(dev, "Could not write EVF bit (%d)\n", ret);
+		return ret;
+	}
+
+	ret = regmap_field_write(data->regfields[RX8111_REGF_TSCLR], 1);
+	if (ret) {
+		dev_dbg(dev, "Could not write TSCLR bit (%d)\n", ret);
+		return ret;
+	}
+
+	return count;
+}
+
+static ssize_t timestamp0_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct rx8111_data *data = dev_get_drvdata(dev);
+
+	struct rtc_time tm;
+	int ret, evfval;
+	u8 date[RX8111_TS_BUF_SZ];
+
+	/* Read out timestamp values only when an event has occurred. */
+	ret = regmap_field_read(data->regfields[RX8111_REGF_EVF], &evfval);
+	if (ret) {
+		dev_dbg(dev, "Could not read EVF (%d)\n", ret);
+		return ret;
+	}
+
+	if (!evfval)
+		return 0;
+
+	spin_lock(&data->ts_lock);
+
+	/* Disable timestamp during readout to avoid unreliable data. */
+	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 0);
+	if (ret) {
+		dev_dbg(dev, "Could not disable timestamp function (%d)\n",
+			ret);
+		goto err_out;
+	}
+
+	ret = regmap_bulk_read(data->regmap, RX8111_REG_TS_SEC, date,
+			       sizeof(date));
+	if (ret) {
+		dev_dbg(dev, "Could not read timestamp data (%d)\n", ret);
+		goto err_out;
+	}
+
+	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 1);
+	if (ret) {
+		dev_dbg(dev, "Could not enable timestamp function (%d)\n", ret);
+		goto err_out;
+	}
+
+	spin_unlock(&data->ts_lock);
+
+	tm.tm_sec = bcd2bin(date[0]);
+	tm.tm_min = bcd2bin(date[1]);
+	tm.tm_hour = bcd2bin(date[2]);
+	tm.tm_mday = bcd2bin(date[4]);
+	tm.tm_mon = bcd2bin(date[5]) - 1;
+	tm.tm_year = bcd2bin(date[6]) + 100;
+
+	ret = rtc_valid_tm(&tm);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%llu\n",
+		       (unsigned long long)rtc_tm_to_time64(&tm));
+
+err_out:
+	spin_unlock(&data->ts_lock);
+	return ret;
+}
+
+static DEVICE_ATTR_RW(timestamp0);
+
+static ssize_t timestamp0_write_nvmem_store(struct device *dev,
+					    struct device_attribute *attr,
+					    const char *buf, size_t count)
+{
+	struct rx8111_data *data = dev_get_drvdata(dev);
+	bool enable;
+	int ret;
+
+	if (count < 1)
+		return -EINVAL;
+
+	ret = kstrtobool(buf, &enable);
+	if (ret)
+		return ret;
+
+	ret = regmap_field_write(data->regfields[RX8111_REGF_TSRAM],
+				 enable ? 1 : 0);
+	if (ret) {
+		dev_dbg(dev, "Could not set TSRAM bit (%d)\n", ret);
+		return ret;
+	}
+
+	return count;
+}
+
+static ssize_t timestamp0_write_nvmem_show(struct device *dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct rx8111_data *data = dev_get_drvdata(dev);
+	int enable;
+	int ret;
+
+	ret = regmap_field_read(data->regfields[RX8111_REGF_TSRAM], &enable);
+	if (ret) {
+		dev_dbg(dev, "Could not read TSRAM bit (%d)\n", ret);
+		return ret;
+	}
+
+	return sprintf(buf, "%s\n", enable ? "1" : "0");
+}
+
+static DEVICE_ATTR_RW(timestamp0_write_nvmem);
+
+static int rx8111_sysfs_register(struct device *dev)
+{
+	int ret;
+
+	ret = device_create_file(dev, &dev_attr_timestamp0);
+	if (ret)
+		return ret;
+
+	ret = device_create_file(dev, &dev_attr_timestamp0_write_nvmem);
+	if (ret)
+		device_remove_file(dev, &dev_attr_timestamp0);
+
+	return ret;
+}
+
+static void rx8111_sysfs_unregister(void *data)
+{
+	struct device *dev = (struct device *)data;
+
+	device_remove_file(dev, &dev_attr_timestamp0);
+	device_remove_file(dev, &dev_attr_timestamp0_write_nvmem);
+}
+
+static int rx8111_setup(struct rx8111_data *data)
+{
+	int ret;
+
+	/* Disable multiple timestamps; area is used for nvmem as default. */
+	ret = regmap_write(data->regmap, RX8111_REG_TS_CTRL2, 0);
+	if (ret) {
+		dev_dbg(data->dev, "Could not setup TS_CTRL2 (%d)\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(data->regmap, RX8111_REG_TS_CTRL3, 0);
+	if (ret) {
+		dev_dbg(data->dev, "Could not setup TS_CTRL3 (%d)\n", ret);
+		return ret;
+	}
+
+	/* Configure EVIN pin, trigger on low level. PU = 1M Ohm. */
+	ret = regmap_write(data->regmap, RX8111_REG_EVIN_SETTING,
+			   RX8111_BIT_EVIN_SETTING_PU1 |
+				   RX8111_BIT_EVIN_SETTING_OVW);
+	if (ret) {
+		dev_dbg(data->dev, "Could not setup EVIN (%d)\n", ret);
+		return ret;
+	}
+
+	/* Enable timestamp triggered by EVIN pin. */
+	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 1);
+	if (ret) {
+		dev_dbg(data->dev, "Could not enable timestamp function (%d)\n",
+			ret);
+		return ret;
+	}
+
+	/* Disable all interrupts. */
+	ret = regmap_write(data->regmap, RX8111_REG_CTRL, 0);
+	if (ret) {
+		dev_dbg(data->dev, "Could not disable interrupts (%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
 static int rx8111_read_vl_flag(struct rx8111_data *data, unsigned int *vlval)
 {
 	int ret;
@@ -160,6 +392,17 @@  static int rx8111_read_vl_flag(struct rx8111_data *data, unsigned int *vlval)
 	return ret;
 }
 
+static int rx8111_clear_vl_flag(struct rx8111_data *data)
+{
+	int ret;
+
+	ret = regmap_field_write(data->regfields[RX8111_REGF_VLF], 0);
+	if (ret)
+		dev_dbg(data->dev, "Could not write VL flag (%d)", ret);
+
+	return ret;
+}
+
 static int rx8111_read_time(struct device *dev, struct rtc_time *tm)
 {
 	struct rx8111_data *data = dev_get_drvdata(dev);
@@ -289,11 +532,69 @@  static int rx8111_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
 		vlval |= regval ? RTC_VL_BACKUP_LOW : 0;
 
 		return put_user(vlval, (typeof(vlval) __user *)arg);
+	case RTC_VL_CLR:
+		return rx8111_clear_vl_flag(data);
 	default:
 		return -ENOIOCTLCMD;
 	}
 }
 
+static int rx8111_nvram_write(void *priv, unsigned int offset, void *val,
+			      size_t bytes)
+{
+	struct rx8111_data *data = priv;
+	int ret, len;
+
+	/*
+	 * The RX8111 device can only handle transfers with repeated start
+	 * within the same 16 bytes aligned block.
+	 */
+	while (bytes > 0) {
+		len = ((offset % 15) + bytes > 16) ? 16 - (offset % 16) : bytes;
+		ret = regmap_bulk_write(data->regmap,
+					RX8111_REG_TS_RAM_START + offset, val,
+					len);
+		if (ret) {
+			dev_dbg(data->dev, "Could not write nvmem (%d)\n", ret);
+			return ret;
+		}
+
+		val += len;
+		offset += len;
+		bytes -= len;
+	}
+
+	return 0;
+}
+
+static int rx8111_nvram_read(void *priv, unsigned int offset, void *val,
+			     size_t bytes)
+{
+	struct rx8111_data *data = priv;
+	int ret, len;
+
+	/*
+	 * The RX8111 device can only handle transfers with repeated start
+	 * within the same 16 bytes aligned block.
+	 */
+	while (bytes > 0) {
+		len = ((offset % 15) + bytes > 16) ? 16 - (offset % 16) : bytes;
+		ret = regmap_bulk_read(data->regmap,
+				       RX8111_REG_TS_RAM_START + offset, val,
+				       len);
+		if (ret) {
+			dev_dbg(data->dev, "Could not read nvmem (%d)\n", ret);
+			return ret;
+		}
+
+		val += len;
+		offset += len;
+		bytes -= len;
+	}
+
+	return 0;
+}
+
 static const struct rtc_class_ops rx8111_rtc_ops = {
 	.read_time = rx8111_read_time,
 	.set_time = rx8111_set_time,
@@ -305,6 +606,16 @@  static int rx8111_probe(struct i2c_client *client)
 	struct rx8111_data *data;
 	struct rtc_device *rtc;
 	size_t i;
+	int ret;
+	struct nvmem_config nvmem_cfg = {
+		.name = "rx8111_nvram",
+		.word_size = 1,
+		.stride = 1,
+		.size = RX8111_REG_TS_RAM_END - RX8111_REG_TS_RAM_START + 1,
+		.type = NVMEM_TYPE_BATTERY_BACKED,
+		.reg_read = rx8111_nvram_read,
+		.reg_write = rx8111_nvram_write,
+	};
 
 	data = devm_kmalloc(&client->dev, sizeof(*data), GFP_KERNEL);
 	if (!data) {
@@ -312,6 +623,8 @@  static int rx8111_probe(struct i2c_client *client)
 		return -ENOMEM;
 	}
 
+	spin_lock_init(&data->ts_lock);
+
 	data->dev = &client->dev;
 	dev_set_drvdata(data->dev, data);
 
@@ -331,6 +644,10 @@  static int rx8111_probe(struct i2c_client *client)
 		}
 	}
 
+	ret = rx8111_setup(data);
+	if (ret)
+		return ret;
+
 	rtc = devm_rtc_allocate_device(data->dev);
 	if (IS_ERR(rtc)) {
 		dev_dbg(data->dev, "Could not allocate rtc device\n");
@@ -343,7 +660,37 @@  static int rx8111_probe(struct i2c_client *client)
 
 	clear_bit(RTC_FEATURE_ALARM, rtc->features);
 
-	return devm_rtc_register_device(rtc);
+	ret = devm_rtc_register_device(rtc);
+	if (ret) {
+		dev_dbg(data->dev,
+			"Could not register rtc device (%d)\n", ret);
+		return ret;
+	}
+
+	ret = rx8111_sysfs_register(data->dev);
+	if (ret) {
+		dev_dbg(data->dev,
+			"Could not create sysfs entry (%d)\n", ret);
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(data->dev, &rx8111_sysfs_unregister,
+				       data->dev);
+	if (ret) {
+		dev_dbg(data->dev,
+			"Could not add sysfs unregister devres action (%d)\n", ret);
+		return ret;
+	}
+
+	nvmem_cfg.priv = data;
+	ret = devm_rtc_nvmem_register(rtc, &nvmem_cfg);
+	if (ret) {
+		dev_dbg(data->dev,
+			"Could not register rtc nvmem device (%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
 }
 
 static const struct of_device_id rx8111_of_match[] = {