diff mbox series

rtc: rx8111: Add timestamp and nvmem functionality

Message ID 20231002-rx8111-add-timestamp0-v1-1-353727cf7f14@axis.com
State Changes Requested
Headers show
Series rtc: rx8111: Add timestamp and nvmem functionality | expand

Commit Message

Anders Sandahl Oct. 2, 2023, 11:14 a.m. UTC
Add timestamp by external event (EVIN pin). Also add support for nvmem.
If an event occurs the time stamp can be read out from timestamp0 in
sysfs.

Nvmem will be used as an ordinary non-volatile memory until a '1' is
written to timestamp0_write_nvmem file in sysfs. In that case time
stamp data will also be written to the register area shared with
nvmem.

Signed-off-by: Anders Sandahl <anders.sandahl@axis.com>
---
This patch adds NVMEM and timestamp functionality to the driver for
Epson RX8111 RTC chip.

The base for this patch is an unmerged patch stack:
https://lore.kernel.org/lkml/cover.1692699931.git.waqar.hameed@axis.com/
---
 drivers/rtc/rtc-rx8111.c | 299 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 290 insertions(+), 9 deletions(-)


---
base-commit: 2687773764932f57571bfaffc97290e0a63bb48d
change-id: 20230927-rx8111-add-timestamp0-b08c18aa574e

Best regards,
diff mbox series

Patch

diff --git a/drivers/rtc/rtc-rx8111.c b/drivers/rtc/rtc-rx8111.c
index 15ff776f739e..d6e4c9baa1de 100644
--- a/drivers/rtc/rtc-rx8111.c
+++ b/drivers/rtc/rtc-rx8111.c
@@ -56,13 +56,24 @@ 
 #define RX8111_REG_TS_CTRL2		0x35	/* Timestamp control 2. */
 #define RX8111_REG_TS_CTRL3		0x36	/* Timestamp control 3. */
 
+#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 TSRAM. */
+#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_TIME_BUF_IDX(reg)                                               \
+#define RX8111_BUF_IDX(reg, min, max)                                          \
 	({                                                                     \
-		BUILD_BUG_ON_MSG(reg < RX8111_REG_SEC || reg > RX8111_REG_YEAR,\
-				 "Invalid reg value");                         \
-		(reg - RX8111_REG_SEC);                                        \
+		BUILD_BUG_ON_MSG(reg < min || reg > max, "Invalid reg value"); \
+		(reg - min);                                                   \
 	})
+#define RX8111_TIME_BUF_IDX(reg) \
+	RX8111_BUF_IDX(reg, RX8111_REG_SEC, RX8111_REG_YEAR)
+
+#define RX8111_TS_BUF_SZ (RX8111_REG_TS_YEAR - RX8111_REG_TS_SEC + 1)
+#define RX8111_TS_BUF_IDX(reg) \
+	RX8111_BUF_IDX(reg, RX8111_REG_TS_SEC, RX8111_REG_TS_YEAR)
 
 enum rx8111_regfield {
 	/* RX8111_REG_EXT. */
@@ -99,6 +110,11 @@  enum rx8111_regfield {
 	RX8111_REGF_INIEN,
 	RX8111_REGF_CHGEN,
 
+	/* RX8111_REG_TS_CTRL1. */
+	RX8111_REGF_TSRAM,
+	RX8111_REGF_TSCLR,
+	RX8111_REGF_EISEL,
+
 	/* Sentinel value. */
 	RX8111_REGF_MAX
 };
@@ -133,12 +149,16 @@  static const struct reg_field rx8111_regfields[] = {
 	[RX8111_REGF_SWSEL1] = REG_FIELD(RX8111_REG_PWR_SWITCH_CTRL, 3, 3),
 	[RX8111_REGF_INIEN]  = REG_FIELD(RX8111_REG_PWR_SWITCH_CTRL, 6, 6),
 	[RX8111_REGF_CHGEN]  = REG_FIELD(RX8111_REG_PWR_SWITCH_CTRL, 7, 7),
+
+	[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 {
@@ -148,15 +168,191 @@  struct rx8111_data {
 	struct rtc_device *rtc;
 };
 
+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).
+	 */
+	ret = regmap_field_read(data->regfields[RX8111_REGF_ETS], &etsval);
+	if (ret) {
+		dev_err(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_err(dev, "Could not write EVF 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_err(dev, "Could not read EVF (%d)\n", ret);
+		return ret;
+	}
+
+	if (!evfval)
+		return 0;
+
+	/* Disable timestamp during readout to avoid unreliable data. */
+	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 0);
+	if (ret) {
+		dev_err(dev, "Could not disable timestamp function (%d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = regmap_bulk_read(data->regmap, RX8111_REG_TS_SEC, date,
+			       sizeof(date));
+	if (ret) {
+		dev_err(dev, "Could not read timestamp data (%d)\n", ret);
+		return ret;
+	}
+
+	ret = regmap_field_write(data->regfields[RX8111_REGF_ETS], 1);
+	if (ret) {
+		dev_err(dev, "Could not enable timestamp function (%d)\n", ret);
+		return ret;
+	}
+
+	tm.tm_sec = bcd2bin(date[RX8111_TS_BUF_IDX(RX8111_REG_TS_SEC)]);
+	tm.tm_min = bcd2bin(date[RX8111_TS_BUF_IDX(RX8111_REG_TS_MIN)]);
+	tm.tm_hour = bcd2bin(date[RX8111_TS_BUF_IDX(RX8111_REG_TS_HOUR)]);
+	tm.tm_mday = bcd2bin(date[RX8111_TS_BUF_IDX(RX8111_REG_TS_DAY)]);
+	tm.tm_mon = bcd2bin(date[RX8111_TS_BUF_IDX(RX8111_REG_TS_MONTH)]) - 1;
+	tm.tm_year = bcd2bin(date[RX8111_TS_BUF_IDX(RX8111_REG_TS_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 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_err(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_err(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 extended functionality (timer, events, timestamps etc.). */
-	ret = regmap_write(data->regmap, RX8111_REG_EXT, 0);
+	/* Disable multiple timestamps; area is used for nvmem as default. */
+	ret = regmap_write(data->regmap, RX8111_REG_TS_CTRL2, 0);
+	if (ret) {
+		dev_err(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_err(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_err(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_err(data->dev,
-			"Could not disable extended functionality (%d)\n", ret);
+		dev_err(data->dev, "Could not enable timestamp function (%d)\n",
+			ret);
 		return ret;
 	}
 
@@ -330,6 +526,62 @@  static int rx8111_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
 	}
 }
 
+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_err(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_err(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,
@@ -342,6 +594,15 @@  static int rx8111_probe(struct i2c_client *client)
 	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)
@@ -386,6 +647,26 @@  static int rx8111_probe(struct i2c_client *client)
 				     "Could not register rtc device (%d)\n",
 				     ret);
 
+	ret = rx8111_sysfs_register(data->dev);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "Could not create sysfs entry (%d)\n",
+				     ret);
+
+	ret = devm_add_action_or_reset(data->dev, &rx8111_sysfs_unregister,
+				       data->dev);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "Could not add sysfs unregister devres action (%d)\n",
+				     ret);
+
+	nvmem_cfg.priv = data;
+	ret = devm_rtc_nvmem_register(rtc, &nvmem_cfg);
+	if (ret)
+		return dev_err_probe(data->dev, ret,
+				     "Could not register rtc nvmem device (%d)\n",
+				     ret);
+
 	return 0;
 }