18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Dallas Semiconductor DS1682 Elapsed Time Recorder device driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Written by: Grant Likely <grant.likely@secretlab.ca>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (C) 2007 Secret Lab Technologies Ltd.
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci/*
118c2ecf20Sopenharmony_ci * The DS1682 elapsed timer recorder is a simple device that implements
128c2ecf20Sopenharmony_ci * one elapsed time counter, one event counter, an alarm signal and 10
138c2ecf20Sopenharmony_ci * bytes of general purpose EEPROM.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * This driver provides access to the DS1682 counters and user data via
168c2ecf20Sopenharmony_ci * the sysfs.  The following attributes are added to the device node:
178c2ecf20Sopenharmony_ci *     elapsed_time (u32): Total elapsed event time in ms resolution
188c2ecf20Sopenharmony_ci *     alarm_time (u32): When elapsed time exceeds the value in alarm_time,
198c2ecf20Sopenharmony_ci *                       then the alarm pin is asserted.
208c2ecf20Sopenharmony_ci *     event_count (u16): number of times the event pin has gone low.
218c2ecf20Sopenharmony_ci *     eeprom (u8[10]): general purpose EEPROM
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * Counter registers and user data are both read/write unless the device
248c2ecf20Sopenharmony_ci * has been write protected.  This driver does not support turning off write
258c2ecf20Sopenharmony_ci * protection.  Once write protection is turned on, it is impossible to
268c2ecf20Sopenharmony_ci * turn it off again, so I have left the feature out of this driver to avoid
278c2ecf20Sopenharmony_ci * accidental enabling, but it is trivial to add write protect support.
288c2ecf20Sopenharmony_ci *
298c2ecf20Sopenharmony_ci */
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#include <linux/module.h>
328c2ecf20Sopenharmony_ci#include <linux/i2c.h>
338c2ecf20Sopenharmony_ci#include <linux/string.h>
348c2ecf20Sopenharmony_ci#include <linux/list.h>
358c2ecf20Sopenharmony_ci#include <linux/sysfs.h>
368c2ecf20Sopenharmony_ci#include <linux/ctype.h>
378c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h>
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci/* Device registers */
408c2ecf20Sopenharmony_ci#define DS1682_REG_CONFIG		0x00
418c2ecf20Sopenharmony_ci#define DS1682_REG_ALARM		0x01
428c2ecf20Sopenharmony_ci#define DS1682_REG_ELAPSED		0x05
438c2ecf20Sopenharmony_ci#define DS1682_REG_EVT_CNTR		0x09
448c2ecf20Sopenharmony_ci#define DS1682_REG_EEPROM		0x0b
458c2ecf20Sopenharmony_ci#define DS1682_REG_RESET		0x1d
468c2ecf20Sopenharmony_ci#define DS1682_REG_WRITE_DISABLE	0x1e
478c2ecf20Sopenharmony_ci#define DS1682_REG_WRITE_MEM_DISABLE	0x1f
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci#define DS1682_EEPROM_SIZE		10
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci/*
528c2ecf20Sopenharmony_ci * Generic counter attributes
538c2ecf20Sopenharmony_ci */
548c2ecf20Sopenharmony_cistatic ssize_t ds1682_show(struct device *dev, struct device_attribute *attr,
558c2ecf20Sopenharmony_ci			   char *buf)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
588c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
598c2ecf20Sopenharmony_ci	unsigned long long val, check;
608c2ecf20Sopenharmony_ci	__le32 val_le = 0;
618c2ecf20Sopenharmony_ci	int rc;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	dev_dbg(dev, "ds1682_show() called on %s\n", attr->attr.name);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	/* Read the register */
668c2ecf20Sopenharmony_ci	rc = i2c_smbus_read_i2c_block_data(client, sattr->index, sattr->nr,
678c2ecf20Sopenharmony_ci					   (u8 *)&val_le);
688c2ecf20Sopenharmony_ci	if (rc < 0)
698c2ecf20Sopenharmony_ci		return -EIO;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	val = le32_to_cpu(val_le);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	if (sattr->index == DS1682_REG_ELAPSED) {
748c2ecf20Sopenharmony_ci		int retries = 5;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci		/* Detect and retry when a tick occurs mid-read */
778c2ecf20Sopenharmony_ci		do {
788c2ecf20Sopenharmony_ci			rc = i2c_smbus_read_i2c_block_data(client, sattr->index,
798c2ecf20Sopenharmony_ci							   sattr->nr,
808c2ecf20Sopenharmony_ci							   (u8 *)&val_le);
818c2ecf20Sopenharmony_ci			if (rc < 0 || retries <= 0)
828c2ecf20Sopenharmony_ci				return -EIO;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci			check = val;
858c2ecf20Sopenharmony_ci			val = le32_to_cpu(val_le);
868c2ecf20Sopenharmony_ci			retries--;
878c2ecf20Sopenharmony_ci		} while (val != check && val != (check + 1));
888c2ecf20Sopenharmony_ci	}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	/* Format the output string and return # of bytes
918c2ecf20Sopenharmony_ci	 * Special case: the 32 bit regs are time values with 1/4s
928c2ecf20Sopenharmony_ci	 * resolution, scale them up to milliseconds
938c2ecf20Sopenharmony_ci	 */
948c2ecf20Sopenharmony_ci	return sprintf(buf, "%llu\n", (sattr->nr == 4) ? (val * 250) : val);
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic ssize_t ds1682_store(struct device *dev, struct device_attribute *attr,
988c2ecf20Sopenharmony_ci			    const char *buf, size_t count)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
1018c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
1028c2ecf20Sopenharmony_ci	u64 val;
1038c2ecf20Sopenharmony_ci	__le32 val_le;
1048c2ecf20Sopenharmony_ci	int rc;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	dev_dbg(dev, "ds1682_store() called on %s\n", attr->attr.name);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	/* Decode input */
1098c2ecf20Sopenharmony_ci	rc = kstrtoull(buf, 0, &val);
1108c2ecf20Sopenharmony_ci	if (rc < 0) {
1118c2ecf20Sopenharmony_ci		dev_dbg(dev, "input string not a number\n");
1128c2ecf20Sopenharmony_ci		return -EINVAL;
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	/* Special case: the 32 bit regs are time values with 1/4s
1168c2ecf20Sopenharmony_ci	 * resolution, scale input down to quarter-seconds */
1178c2ecf20Sopenharmony_ci	if (sattr->nr == 4)
1188c2ecf20Sopenharmony_ci		do_div(val, 250);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	/* write out the value */
1218c2ecf20Sopenharmony_ci	val_le = cpu_to_le32(val);
1228c2ecf20Sopenharmony_ci	rc = i2c_smbus_write_i2c_block_data(client, sattr->index, sattr->nr,
1238c2ecf20Sopenharmony_ci					    (u8 *) & val_le);
1248c2ecf20Sopenharmony_ci	if (rc < 0) {
1258c2ecf20Sopenharmony_ci		dev_err(dev, "register write failed; reg=0x%x, size=%i\n",
1268c2ecf20Sopenharmony_ci			sattr->index, sattr->nr);
1278c2ecf20Sopenharmony_ci		return -EIO;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	return count;
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci/*
1348c2ecf20Sopenharmony_ci * Simple register attributes
1358c2ecf20Sopenharmony_ci */
1368c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_2(elapsed_time, S_IRUGO | S_IWUSR, ds1682_show,
1378c2ecf20Sopenharmony_ci			    ds1682_store, 4, DS1682_REG_ELAPSED);
1388c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_2(alarm_time, S_IRUGO | S_IWUSR, ds1682_show,
1398c2ecf20Sopenharmony_ci			    ds1682_store, 4, DS1682_REG_ALARM);
1408c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_2(event_count, S_IRUGO | S_IWUSR, ds1682_show,
1418c2ecf20Sopenharmony_ci			    ds1682_store, 2, DS1682_REG_EVT_CNTR);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic const struct attribute_group ds1682_group = {
1448c2ecf20Sopenharmony_ci	.attrs = (struct attribute *[]) {
1458c2ecf20Sopenharmony_ci		&sensor_dev_attr_elapsed_time.dev_attr.attr,
1468c2ecf20Sopenharmony_ci		&sensor_dev_attr_alarm_time.dev_attr.attr,
1478c2ecf20Sopenharmony_ci		&sensor_dev_attr_event_count.dev_attr.attr,
1488c2ecf20Sopenharmony_ci		NULL,
1498c2ecf20Sopenharmony_ci	},
1508c2ecf20Sopenharmony_ci};
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci/*
1538c2ecf20Sopenharmony_ci * User data attribute
1548c2ecf20Sopenharmony_ci */
1558c2ecf20Sopenharmony_cistatic ssize_t ds1682_eeprom_read(struct file *filp, struct kobject *kobj,
1568c2ecf20Sopenharmony_ci				  struct bin_attribute *attr,
1578c2ecf20Sopenharmony_ci				  char *buf, loff_t off, size_t count)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct i2c_client *client = kobj_to_i2c_client(kobj);
1608c2ecf20Sopenharmony_ci	int rc;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	dev_dbg(&client->dev, "ds1682_eeprom_read(p=%p, off=%lli, c=%zi)\n",
1638c2ecf20Sopenharmony_ci		buf, off, count);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	rc = i2c_smbus_read_i2c_block_data(client, DS1682_REG_EEPROM + off,
1668c2ecf20Sopenharmony_ci					   count, buf);
1678c2ecf20Sopenharmony_ci	if (rc < 0)
1688c2ecf20Sopenharmony_ci		return -EIO;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	return count;
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic ssize_t ds1682_eeprom_write(struct file *filp, struct kobject *kobj,
1748c2ecf20Sopenharmony_ci				   struct bin_attribute *attr,
1758c2ecf20Sopenharmony_ci				   char *buf, loff_t off, size_t count)
1768c2ecf20Sopenharmony_ci{
1778c2ecf20Sopenharmony_ci	struct i2c_client *client = kobj_to_i2c_client(kobj);
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	dev_dbg(&client->dev, "ds1682_eeprom_write(p=%p, off=%lli, c=%zi)\n",
1808c2ecf20Sopenharmony_ci		buf, off, count);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	/* Write out to the device */
1838c2ecf20Sopenharmony_ci	if (i2c_smbus_write_i2c_block_data(client, DS1682_REG_EEPROM + off,
1848c2ecf20Sopenharmony_ci					   count, buf) < 0)
1858c2ecf20Sopenharmony_ci		return -EIO;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	return count;
1888c2ecf20Sopenharmony_ci}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cistatic const struct bin_attribute ds1682_eeprom_attr = {
1918c2ecf20Sopenharmony_ci	.attr = {
1928c2ecf20Sopenharmony_ci		.name = "eeprom",
1938c2ecf20Sopenharmony_ci		.mode = S_IRUGO | S_IWUSR,
1948c2ecf20Sopenharmony_ci	},
1958c2ecf20Sopenharmony_ci	.size = DS1682_EEPROM_SIZE,
1968c2ecf20Sopenharmony_ci	.read = ds1682_eeprom_read,
1978c2ecf20Sopenharmony_ci	.write = ds1682_eeprom_write,
1988c2ecf20Sopenharmony_ci};
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci/*
2018c2ecf20Sopenharmony_ci * Called when a ds1682 device is matched with this driver
2028c2ecf20Sopenharmony_ci */
2038c2ecf20Sopenharmony_cistatic int ds1682_probe(struct i2c_client *client,
2048c2ecf20Sopenharmony_ci			const struct i2c_device_id *id)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	int rc;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(client->adapter,
2098c2ecf20Sopenharmony_ci				     I2C_FUNC_SMBUS_I2C_BLOCK)) {
2108c2ecf20Sopenharmony_ci		dev_err(&client->dev, "i2c bus does not support the ds1682\n");
2118c2ecf20Sopenharmony_ci		rc = -ENODEV;
2128c2ecf20Sopenharmony_ci		goto exit;
2138c2ecf20Sopenharmony_ci	}
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	rc = sysfs_create_group(&client->dev.kobj, &ds1682_group);
2168c2ecf20Sopenharmony_ci	if (rc)
2178c2ecf20Sopenharmony_ci		goto exit;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	rc = sysfs_create_bin_file(&client->dev.kobj, &ds1682_eeprom_attr);
2208c2ecf20Sopenharmony_ci	if (rc)
2218c2ecf20Sopenharmony_ci		goto exit_bin_attr;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	return 0;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci exit_bin_attr:
2268c2ecf20Sopenharmony_ci	sysfs_remove_group(&client->dev.kobj, &ds1682_group);
2278c2ecf20Sopenharmony_ci exit:
2288c2ecf20Sopenharmony_ci	return rc;
2298c2ecf20Sopenharmony_ci}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_cistatic int ds1682_remove(struct i2c_client *client)
2328c2ecf20Sopenharmony_ci{
2338c2ecf20Sopenharmony_ci	sysfs_remove_bin_file(&client->dev.kobj, &ds1682_eeprom_attr);
2348c2ecf20Sopenharmony_ci	sysfs_remove_group(&client->dev.kobj, &ds1682_group);
2358c2ecf20Sopenharmony_ci	return 0;
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic const struct i2c_device_id ds1682_id[] = {
2398c2ecf20Sopenharmony_ci	{ "ds1682", 0 },
2408c2ecf20Sopenharmony_ci	{ }
2418c2ecf20Sopenharmony_ci};
2428c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ds1682_id);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic const struct of_device_id ds1682_of_match[] = {
2458c2ecf20Sopenharmony_ci	{ .compatible = "dallas,ds1682", },
2468c2ecf20Sopenharmony_ci	{}
2478c2ecf20Sopenharmony_ci};
2488c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ds1682_of_match);
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_cistatic struct i2c_driver ds1682_driver = {
2518c2ecf20Sopenharmony_ci	.driver = {
2528c2ecf20Sopenharmony_ci		.name = "ds1682",
2538c2ecf20Sopenharmony_ci		.of_match_table = ds1682_of_match,
2548c2ecf20Sopenharmony_ci	},
2558c2ecf20Sopenharmony_ci	.probe = ds1682_probe,
2568c2ecf20Sopenharmony_ci	.remove = ds1682_remove,
2578c2ecf20Sopenharmony_ci	.id_table = ds1682_id,
2588c2ecf20Sopenharmony_ci};
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_cimodule_i2c_driver(ds1682_driver);
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ciMODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
2638c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("DS1682 Elapsed Time Indicator driver");
2648c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
265