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