Message ID | 20250429-rtc-rx8111-events-v1-1-7e2a61126042@axis.com |
---|---|
State | New |
Headers | show |
Series | rtc: rx8111: Add NVMEM and timestamp functionality | expand |
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 --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[] = {
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,