diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/w1/slaves | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/w1/slaves')
-rw-r--r-- | drivers/w1/slaves/Kconfig | 170 | ||||
-rw-r--r-- | drivers/w1/slaves/Makefile | 22 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2405.c | 218 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2406.c | 154 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2408.c | 353 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2413.c | 159 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2423.c | 133 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2430.c | 295 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2431.c | 294 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2433.c | 306 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2438.c | 518 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds250x.c | 290 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2780.c | 159 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2780.h | 125 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2781.c | 156 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2781.h | 130 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2805.c | 298 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds28e04.c | 414 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds28e17.c | 755 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_smem.c | 59 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_therm.c | 2224 |
21 files changed, 7232 insertions, 0 deletions
diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig new file mode 100644 index 000000000..687753889 --- /dev/null +++ b/drivers/w1/slaves/Kconfig @@ -0,0 +1,170 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# 1-wire slaves configuration +# + +menu "1-wire Slaves" + +config W1_SLAVE_THERM + tristate "Thermal family implementation" + help + Say Y here if you want to connect 1-wire thermal sensors to your + wire. + +config W1_SLAVE_SMEM + tristate "Simple 64bit memory family implementation" + help + Say Y here if you want to connect 1-wire + simple 64bit memory rom(ds2401/ds2411/ds1990*) to your wire. + +config W1_SLAVE_DS2405 + tristate "DS2405 Addressable Switch" + help + Say Y or M here if you want to use a DS2405 1-wire + single-channel addressable switch. + This device can also work as a single-channel + binary remote sensor. + +config W1_SLAVE_DS2408 + tristate "8-Channel Addressable Switch (IO Expander) 0x29 family support (DS2408)" + help + Say Y here if you want to use a 1-wire + DS2408 8-Channel Addressable Switch device support + +config W1_SLAVE_DS2408_READBACK + bool "Read-back values written to DS2408's output register" + depends on W1_SLAVE_DS2408 + default y + help + Enabling this will cause the driver to read back the values written + to the chip's output register in order to detect errors. + + This is slower but useful when debugging chips and/or busses. + +config W1_SLAVE_DS2413 + tristate "Dual Channel Addressable Switch 0x3a family support (DS2413)" + help + Say Y here if you want to use a 1-wire + DS2413 Dual Channel Addressable Switch device support + +config W1_SLAVE_DS2406 + tristate "Dual Channel Addressable Switch 0x12 family support (DS2406)" + select CRC16 + help + Say Y or M here if you want to use a 1-wire + DS2406 Dual Channel Addressable Switch. EPROM read/write + support for these devices is not implemented. + +config W1_SLAVE_DS2423 + tristate "Counter 1-wire device (DS2423)" + select CRC16 + help + If you enable this you can read the counter values available + in the DS2423 chipset from the w1_slave file under the + sys file system. + + Say Y here if you want to use a 1-wire + counter family device (DS2423). + +config W1_SLAVE_DS2805 + tristate "112-byte EEPROM support (DS28E05)" + help + Say Y here if you want to use a 1-wire + is a 112-byte user-programmable EEPROM is + organized as 7 pages of 16 bytes each with 64bit + unique number. Requires OverDrive Speed to talk to. + +config W1_SLAVE_DS2430 + tristate "256b EEPROM family support (DS2430)" + help + Say Y here if you want to use a 1-wire 256bit EEPROM + family device (DS2430). + This EEPROM is organized as one page of 32 bytes for random + access. + +config W1_SLAVE_DS2431 + tristate "1kb EEPROM family support (DS2431)" + help + Say Y here if you want to use a 1-wire + 1kb EEPROM family device (DS2431) + +config W1_SLAVE_DS2433 + tristate "4kb EEPROM family support (DS2433)" + help + Say Y here if you want to use a 1-wire + 4kb EEPROM family device (DS2433). + +config W1_SLAVE_DS2433_CRC + bool "Protect DS2433 data with a CRC16" + depends on W1_SLAVE_DS2433 + select CRC16 + help + Say Y here to protect DS2433 data with a CRC16. + Each block has 30 bytes of data and a two byte CRC16. + Full block writes are only allowed if the CRC is valid. + +config W1_SLAVE_DS2438 + tristate "DS2438 Smart Battery Monitor 0x26 family support" + help + Say Y here if you want to use a 1-wire + DS2438 Smart Battery Monitor device support + +config W1_SLAVE_DS250X + tristate "512b/1kb/16kb EPROM family support" + select CRC16 + help + Say Y here if you want to use a 1-wire + 512b/1kb/16kb EPROM family device (DS250x). + +config W1_SLAVE_DS2780 + tristate "Dallas 2780 battery monitor chip" + help + If you enable this you will have the DS2780 battery monitor + chip support. + + The battery monitor chip is used in many batteries/devices + as the one who is responsible for charging/discharging/monitoring + Li+ batteries. + + If you are unsure, say N. + +config W1_SLAVE_DS2781 + tristate "Dallas 2781 battery monitor chip" + help + If you enable this you will have the DS2781 battery monitor + chip support. + + The battery monitor chip is used in many batteries/devices + as the one who is responsible for charging/discharging/monitoring + Li+ batteries. + + If you are unsure, say N. + +config W1_SLAVE_DS28E04 + tristate "4096-Bit Addressable 1-Wire EEPROM with PIO (DS28E04-100)" + select CRC16 + help + If you enable this you will have the DS28E04-100 + chip support. + + Say Y here if you want to use a 1-wire + 4kb EEPROM with PIO family device (DS28E04). + + If you are unsure, say N. + +config W1_SLAVE_DS28E17 + tristate "1-wire-to-I2C master bridge (DS28E17)" + select CRC16 + depends on I2C + help + Say Y here if you want to use the DS28E17 1-wire-to-I2C master bridge. + For each DS28E17 detected, a new I2C adapter is created within the + kernel. I2C devices on that bus can be configured to be used by the + kernel and userspace tools as on any other "native" I2C bus. + + This driver is also available as a module. If so, the module + will be called w1_ds28e17. + + If you are unsure, say N. + +endmenu diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile new file mode 100644 index 000000000..278bcf2a9 --- /dev/null +++ b/drivers/w1/slaves/Makefile @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Dallas's 1-wire slaves. +# + +obj-$(CONFIG_W1_SLAVE_THERM) += w1_therm.o +obj-$(CONFIG_W1_SLAVE_SMEM) += w1_smem.o +obj-$(CONFIG_W1_SLAVE_DS2405) += w1_ds2405.o +obj-$(CONFIG_W1_SLAVE_DS2408) += w1_ds2408.o +obj-$(CONFIG_W1_SLAVE_DS2413) += w1_ds2413.o +obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o +obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o +obj-$(CONFIG_W1_SLAVE_DS2430) += w1_ds2430.o +obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o +obj-$(CONFIG_W1_SLAVE_DS2805) += w1_ds2805.o +obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o +obj-$(CONFIG_W1_SLAVE_DS2438) += w1_ds2438.o +obj-$(CONFIG_W1_SLAVE_DS250X) += w1_ds250x.o +obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o +obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o +obj-$(CONFIG_W1_SLAVE_DS28E04) += w1_ds28e04.o +obj-$(CONFIG_W1_SLAVE_DS28E17) += w1_ds28e17.o diff --git a/drivers/w1/slaves/w1_ds2405.c b/drivers/w1/slaves/w1_ds2405.c new file mode 100644 index 000000000..1d9a1183e --- /dev/null +++ b/drivers/w1/slaves/w1_ds2405.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * w1_ds2405.c + * + * Copyright (c) 2017 Maciej S. Szmigiero <mail@maciej.szmigiero.name> + * Based on w1_therm.c copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2405 0x05 + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas DS2405 PIO."); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2405)); + +static int w1_ds2405_select(struct w1_slave *sl, bool only_active) +{ + struct w1_master *dev = sl->master; + + u64 dev_addr = le64_to_cpu(*(u64 *)&sl->reg_num); + unsigned int bit_ctr; + + if (w1_reset_bus(dev) != 0) + return 0; + + /* + * We cannot use a normal Match ROM command + * since doing so would toggle PIO state + */ + w1_write_8(dev, only_active ? W1_ALARM_SEARCH : W1_SEARCH); + + for (bit_ctr = 0; bit_ctr < 64; bit_ctr++) { + int bit2send = !!(dev_addr & BIT(bit_ctr)); + u8 ret; + + ret = w1_triplet(dev, bit2send); + + if ((ret & (BIT(0) | BIT(1))) == + (BIT(0) | BIT(1))) /* no devices found */ + return 0; + + if (!!(ret & BIT(2)) != bit2send) + /* wrong direction taken - no such device */ + return 0; + } + + return 1; +} + +static int w1_ds2405_read_pio(struct w1_slave *sl) +{ + if (w1_ds2405_select(sl, true)) + return 0; /* "active" means PIO is low */ + + if (w1_ds2405_select(sl, false)) + return 1; + + return -ENODEV; +} + +static ssize_t state_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + + int ret; + ssize_t f_retval; + u8 state; + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret) + return ret; + + if (!w1_ds2405_select(sl, false)) { + f_retval = -ENODEV; + goto out_unlock; + } + + state = w1_read_8(dev); + if (state != 0 && + state != 0xff) { + dev_err(device, "non-consistent state %x\n", state); + f_retval = -EIO; + goto out_unlock; + } + + *buf = state ? '1' : '0'; + f_retval = 1; + +out_unlock: + w1_reset_bus(dev); + mutex_unlock(&dev->bus_mutex); + + return f_retval; +} + +static ssize_t output_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + + int ret; + ssize_t f_retval; + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret) + return ret; + + ret = w1_ds2405_read_pio(sl); + if (ret < 0) { + f_retval = ret; + goto out_unlock; + } + + *buf = ret ? '1' : '0'; + f_retval = 1; + +out_unlock: + w1_reset_bus(dev); + mutex_unlock(&dev->bus_mutex); + + return f_retval; +} + +static ssize_t output_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + + int ret, current_pio; + unsigned int val; + ssize_t f_retval; + + if (count < 1) + return -EINVAL; + + if (sscanf(buf, " %u%n", &val, &ret) < 1) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + f_retval = ret; + + ret = mutex_lock_interruptible(&dev->bus_mutex); + if (ret) + return ret; + + current_pio = w1_ds2405_read_pio(sl); + if (current_pio < 0) { + f_retval = current_pio; + goto out_unlock; + } + + if (current_pio == val) + goto out_unlock; + + if (w1_reset_bus(dev) != 0) { + f_retval = -ENODEV; + goto out_unlock; + } + + /* + * can't use w1_reset_select_slave() here since it uses Skip ROM if + * there is only one device on bus + */ + do { + u64 dev_addr = le64_to_cpu(*(u64 *)&sl->reg_num); + u8 cmd[9]; + + cmd[0] = W1_MATCH_ROM; + memcpy(&cmd[1], &dev_addr, sizeof(dev_addr)); + + w1_write_block(dev, cmd, sizeof(cmd)); + } while (0); + +out_unlock: + w1_reset_bus(dev); + mutex_unlock(&dev->bus_mutex); + + return f_retval; +} + +static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RW(output); + +static struct attribute *w1_ds2405_attrs[] = { + &dev_attr_state.attr, + &dev_attr_output.attr, + NULL +}; + +ATTRIBUTE_GROUPS(w1_ds2405); + +static const struct w1_family_ops w1_ds2405_fops = { + .groups = w1_ds2405_groups +}; + +static struct w1_family w1_family_ds2405 = { + .fid = W1_FAMILY_DS2405, + .fops = &w1_ds2405_fops +}; + +module_w1_family(w1_family_ds2405); diff --git a/drivers/w1/slaves/w1_ds2406.c b/drivers/w1/slaves/w1_ds2406.c new file mode 100644 index 000000000..6c269af73 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2406.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds2406.c - w1 family 12 (DS2406) driver + * based on w1_ds2413.c by Mariusz Bialonczyk <manio@skyboo.net> + * + * Copyright (c) 2014 Scott Alfter <scott@alfter.us> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/crc16.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2406 0x12 + +#define W1_F12_FUNC_READ_STATUS 0xAA +#define W1_F12_FUNC_WRITE_STATUS 0x55 + +static ssize_t w1_f12_read_state( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + u8 w1_buf[6]={W1_F12_FUNC_READ_STATUS, 7, 0, 0, 0, 0}; + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u16 crc=0; + int i; + ssize_t rtnval=1; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->bus_mutex); + return -EIO; + } + + w1_write_block(sl->master, w1_buf, 3); + w1_read_block(sl->master, w1_buf+3, 3); + for (i=0; i<6; i++) + crc=crc16_byte(crc, w1_buf[i]); + if (crc==0xb001) /* good read? */ + *buf=((w1_buf[3]>>5)&3)|0x30; + else + rtnval=-EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return rtnval; +} + +static ssize_t w1_f12_write_output( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 w1_buf[6]={W1_F12_FUNC_WRITE_STATUS, 7, 0, 0, 0, 0}; + u16 crc=0; + int i; + ssize_t rtnval=1; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->bus_mutex); + return -EIO; + } + + w1_buf[3] = (((*buf)&3)<<5)|0x1F; + w1_write_block(sl->master, w1_buf, 4); + w1_read_block(sl->master, w1_buf+4, 2); + for (i=0; i<6; i++) + crc=crc16_byte(crc, w1_buf[i]); + if (crc==0xb001) /* good read? */ + w1_write_8(sl->master, 0xFF); + else + rtnval=-EIO; + + mutex_unlock(&sl->master->bus_mutex); + return rtnval; +} + +#define NB_SYSFS_BIN_FILES 2 +static struct bin_attribute w1_f12_sysfs_bin_files[NB_SYSFS_BIN_FILES] = { + { + .attr = { + .name = "state", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f12_read_state, + }, + { + .attr = { + .name = "output", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = 1, + .write = w1_f12_write_output, + } +}; + +static int w1_f12_add_slave(struct w1_slave *sl) +{ + int err = 0; + int i; + + for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) + err = sysfs_create_bin_file( + &sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); + if (err) + while (--i >= 0) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); + return err; +} + +static void w1_f12_remove_slave(struct w1_slave *sl) +{ + int i; + for (i = NB_SYSFS_BIN_FILES - 1; i >= 0; --i) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); +} + +static const struct w1_family_ops w1_f12_fops = { + .add_slave = w1_f12_add_slave, + .remove_slave = w1_f12_remove_slave, +}; + +static struct w1_family w1_family_12 = { + .fid = W1_FAMILY_DS2406, + .fops = &w1_f12_fops, +}; +module_w1_family(w1_family_12); + +MODULE_AUTHOR("Scott Alfter <scott@alfter.us>"); +MODULE_DESCRIPTION("w1 family 12 driver for DS2406 2 Pin IO"); +MODULE_LICENSE("GPL"); diff --git a/drivers/w1/slaves/w1_ds2408.c b/drivers/w1/slaves/w1_ds2408.c new file mode 100644 index 000000000..ad102c577 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2408.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds2408.c - w1 family 29 (DS2408) driver + * + * Copyright (c) 2010 Jean-Francois Dagenais <dagenaisj@sonatest.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2408 0x29 + +#define W1_F29_RETRIES 3 + +#define W1_F29_REG_LOGIG_STATE 0x88 /* R */ +#define W1_F29_REG_OUTPUT_LATCH_STATE 0x89 /* R */ +#define W1_F29_REG_ACTIVITY_LATCH_STATE 0x8A /* R */ +#define W1_F29_REG_COND_SEARCH_SELECT_MASK 0x8B /* RW */ +#define W1_F29_REG_COND_SEARCH_POL_SELECT 0x8C /* RW */ +#define W1_F29_REG_CONTROL_AND_STATUS 0x8D /* RW */ + +#define W1_F29_FUNC_READ_PIO_REGS 0xF0 +#define W1_F29_FUNC_CHANN_ACCESS_READ 0xF5 +#define W1_F29_FUNC_CHANN_ACCESS_WRITE 0x5A +/* also used to write the control/status reg (0x8D): */ +#define W1_F29_FUNC_WRITE_COND_SEARCH_REG 0xCC +#define W1_F29_FUNC_RESET_ACTIVITY_LATCHES 0xC3 + +#define W1_F29_SUCCESS_CONFIRM_BYTE 0xAA + +static int _read_reg(struct w1_slave *sl, u8 address, unsigned char* buf) +{ + u8 wrbuf[3]; + dev_dbg(&sl->dev, + "Reading with slave: %p, reg addr: %0#4x, buff addr: %p", + sl, (unsigned int)address, buf); + + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex locked"); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->bus_mutex); + return -EIO; + } + + wrbuf[0] = W1_F29_FUNC_READ_PIO_REGS; + wrbuf[1] = address; + wrbuf[2] = 0; + w1_write_block(sl->master, wrbuf, 3); + *buf = w1_read_8(sl->master); + + mutex_unlock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex unlocked"); + return 1; +} + +static ssize_t state_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, + size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), W1_F29_REG_LOGIG_STATE, buf); +} + +static ssize_t output_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_OUTPUT_LATCH_STATE, buf); +} + +static ssize_t activity_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_ACTIVITY_LATCH_STATE, buf); +} + +static ssize_t cond_search_mask_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + dev_dbg(&kobj_to_w1_slave(kobj)->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_COND_SEARCH_SELECT_MASK, buf); +} + +static ssize_t cond_search_polarity_read(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_COND_SEARCH_POL_SELECT, buf); +} + +static ssize_t status_control_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + if (count != 1 || off != 0) + return -EFAULT; + return _read_reg(kobj_to_w1_slave(kobj), + W1_F29_REG_CONTROL_AND_STATUS, buf); +} + +#ifdef CONFIG_W1_SLAVE_DS2408_READBACK +static bool optional_read_back_valid(struct w1_slave *sl, u8 expected) +{ + u8 w1_buf[3]; + + if (w1_reset_resume_command(sl->master)) + return false; + + w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS; + w1_buf[1] = W1_F29_REG_OUTPUT_LATCH_STATE; + w1_buf[2] = 0; + + w1_write_block(sl->master, w1_buf, 3); + + return (w1_read_8(sl->master) == expected); +} +#else +static bool optional_read_back_valid(struct w1_slave *sl, u8 expected) +{ + return true; +} +#endif + +static ssize_t output_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 w1_buf[3]; + unsigned int retries = W1_F29_RETRIES; + ssize_t bytes_written = -EIO; + + if (count != 1 || off != 0) + return -EFAULT; + + dev_dbg(&sl->dev, "locking mutex for write_output"); + mutex_lock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex locked"); + + if (w1_reset_select_slave(sl)) + goto out; + + do { + w1_buf[0] = W1_F29_FUNC_CHANN_ACCESS_WRITE; + w1_buf[1] = *buf; + w1_buf[2] = ~(*buf); + + w1_write_block(sl->master, w1_buf, 3); + + if (w1_read_8(sl->master) == W1_F29_SUCCESS_CONFIRM_BYTE && + optional_read_back_valid(sl, *buf)) { + bytes_written = 1; + goto out; + } + + if (w1_reset_resume_command(sl->master)) + goto out; /* unrecoverable error */ + /* try again, the slave is ready for a command */ + } while (--retries); + +out: + mutex_unlock(&sl->master->bus_mutex); + + dev_dbg(&sl->dev, "%s, mutex unlocked retries:%d\n", + (bytes_written > 0) ? "succeeded" : "error", retries); + + return bytes_written; +} + + +/** + * Writing to the activity file resets the activity latches. + */ +static ssize_t activity_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) + goto error; + + while (retries--) { + w1_write_8(sl->master, W1_F29_FUNC_RESET_ACTIVITY_LATCHES); + if (w1_read_8(sl->master) == W1_F29_SUCCESS_CONFIRM_BYTE) { + mutex_unlock(&sl->master->bus_mutex); + return 1; + } + if (w1_reset_resume_command(sl->master)) + goto error; + } + +error: + mutex_unlock(&sl->master->bus_mutex); + return -EIO; +} + +static ssize_t status_control_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 w1_buf[4]; + unsigned int retries = W1_F29_RETRIES; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) + goto error; + + while (retries--) { + w1_buf[0] = W1_F29_FUNC_WRITE_COND_SEARCH_REG; + w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS; + w1_buf[2] = 0; + w1_buf[3] = *buf; + + w1_write_block(sl->master, w1_buf, 4); + if (w1_reset_resume_command(sl->master)) + goto error; + + w1_buf[0] = W1_F29_FUNC_READ_PIO_REGS; + w1_buf[1] = W1_F29_REG_CONTROL_AND_STATUS; + w1_buf[2] = 0; + + w1_write_block(sl->master, w1_buf, 3); + if (w1_read_8(sl->master) == *buf) { + /* success! */ + mutex_unlock(&sl->master->bus_mutex); + return 1; + } + } +error: + mutex_unlock(&sl->master->bus_mutex); + + return -EIO; +} + +/* + * This is a special sequence we must do to ensure the P0 output is not stuck + * in test mode. This is described in rev 2 of the ds2408's datasheet + * (http://datasheets.maximintegrated.com/en/ds/DS2408.pdf) under + * "APPLICATION INFORMATION/Power-up timing". + */ +static int w1_f29_disable_test_mode(struct w1_slave *sl) +{ + int res; + u8 magic[10] = {0x96, }; + u64 rn = le64_to_cpu(*((u64*)&sl->reg_num)); + + memcpy(&magic[1], &rn, 8); + magic[9] = 0x3C; + + mutex_lock(&sl->master->bus_mutex); + + res = w1_reset_bus(sl->master); + if (res) + goto out; + w1_write_block(sl->master, magic, ARRAY_SIZE(magic)); + + res = w1_reset_bus(sl->master); +out: + mutex_unlock(&sl->master->bus_mutex); + return res; +} + +static BIN_ATTR_RO(state, 1); +static BIN_ATTR_RW(output, 1); +static BIN_ATTR_RW(activity, 1); +static BIN_ATTR_RO(cond_search_mask, 1); +static BIN_ATTR_RO(cond_search_polarity, 1); +static BIN_ATTR_RW(status_control, 1); + +static struct bin_attribute *w1_f29_bin_attrs[] = { + &bin_attr_state, + &bin_attr_output, + &bin_attr_activity, + &bin_attr_cond_search_mask, + &bin_attr_cond_search_polarity, + &bin_attr_status_control, + NULL, +}; + +static const struct attribute_group w1_f29_group = { + .bin_attrs = w1_f29_bin_attrs, +}; + +static const struct attribute_group *w1_f29_groups[] = { + &w1_f29_group, + NULL, +}; + +static const struct w1_family_ops w1_f29_fops = { + .add_slave = w1_f29_disable_test_mode, + .groups = w1_f29_groups, +}; + +static struct w1_family w1_family_29 = { + .fid = W1_FAMILY_DS2408, + .fops = &w1_f29_fops, +}; +module_w1_family(w1_family_29); + +MODULE_AUTHOR("Jean-Francois Dagenais <dagenaisj@sonatest.com>"); +MODULE_DESCRIPTION("w1 family 29 driver for DS2408 8 Pin IO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2408)); diff --git a/drivers/w1/slaves/w1_ds2413.c b/drivers/w1/slaves/w1_ds2413.c new file mode 100644 index 000000000..c8cfac555 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2413.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds2413.c - w1 family 3a (DS2413) driver + * based on w1_ds2408.c by Jean-Francois Dagenais <dagenaisj@sonatest.com> + * + * Copyright (c) 2013 Mariusz Bialonczyk <manio@skyboo.net> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2413 0x3A + +#define W1_F3A_RETRIES 3 +#define W1_F3A_FUNC_PIO_ACCESS_READ 0xF5 +#define W1_F3A_FUNC_PIO_ACCESS_WRITE 0x5A +#define W1_F3A_SUCCESS_CONFIRM_BYTE 0xAA +#define W1_F3A_INVALID_PIO_STATE 0xFF + +static ssize_t state_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, + size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + unsigned int retries = W1_F3A_RETRIES; + ssize_t bytes_read = -EIO; + u8 state; + + dev_dbg(&sl->dev, + "Reading %s kobj: %p, off: %0#10x, count: %zu, buff addr: %p", + bin_attr->attr.name, kobj, (unsigned int)off, count, buf); + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex locked"); + +next: + if (w1_reset_select_slave(sl)) + goto out; + + while (retries--) { + w1_write_8(sl->master, W1_F3A_FUNC_PIO_ACCESS_READ); + + state = w1_read_8(sl->master); + if ((state & 0x0F) == ((~state >> 4) & 0x0F)) { + /* complement is correct */ + *buf = state; + bytes_read = 1; + goto out; + } else if (state == W1_F3A_INVALID_PIO_STATE) { + /* slave didn't respond, try to select it again */ + dev_warn(&sl->dev, "slave device did not respond to PIO_ACCESS_READ, " \ + "reselecting, retries left: %d\n", retries); + goto next; + } + + if (w1_reset_resume_command(sl->master)) + goto out; /* unrecoverable error */ + + dev_warn(&sl->dev, "PIO_ACCESS_READ error, retries left: %d\n", retries); + } + +out: + mutex_unlock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "%s, mutex unlocked, retries: %d\n", + (bytes_read > 0) ? "succeeded" : "error", retries); + return bytes_read; +} + +static BIN_ATTR_RO(state, 1); + +static ssize_t output_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 w1_buf[3]; + unsigned int retries = W1_F3A_RETRIES; + ssize_t bytes_written = -EIO; + + if (count != 1 || off != 0) + return -EFAULT; + + dev_dbg(&sl->dev, "locking mutex for write_output"); + mutex_lock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "mutex locked"); + + if (w1_reset_select_slave(sl)) + goto out; + + /* according to the DS2413 datasheet the most significant 6 bits + should be set to "1"s, so do it now */ + *buf = *buf | 0xFC; + + while (retries--) { + w1_buf[0] = W1_F3A_FUNC_PIO_ACCESS_WRITE; + w1_buf[1] = *buf; + w1_buf[2] = ~(*buf); + w1_write_block(sl->master, w1_buf, 3); + + if (w1_read_8(sl->master) == W1_F3A_SUCCESS_CONFIRM_BYTE) { + bytes_written = 1; + goto out; + } + if (w1_reset_resume_command(sl->master)) + goto out; /* unrecoverable error */ + + dev_warn(&sl->dev, "PIO_ACCESS_WRITE error, retries left: %d\n", retries); + } + +out: + mutex_unlock(&sl->master->bus_mutex); + dev_dbg(&sl->dev, "%s, mutex unlocked, retries: %d\n", + (bytes_written > 0) ? "succeeded" : "error", retries); + return bytes_written; +} + +static BIN_ATTR(output, S_IRUGO | S_IWUSR | S_IWGRP, NULL, output_write, 1); + +static struct bin_attribute *w1_f3a_bin_attrs[] = { + &bin_attr_state, + &bin_attr_output, + NULL, +}; + +static const struct attribute_group w1_f3a_group = { + .bin_attrs = w1_f3a_bin_attrs, +}; + +static const struct attribute_group *w1_f3a_groups[] = { + &w1_f3a_group, + NULL, +}; + +static const struct w1_family_ops w1_f3a_fops = { + .groups = w1_f3a_groups, +}; + +static struct w1_family w1_family_3a = { + .fid = W1_FAMILY_DS2413, + .fops = &w1_f3a_fops, +}; +module_w1_family(w1_family_3a); + +MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>"); +MODULE_DESCRIPTION("w1 family 3a driver for DS2413 2 Pin IO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2413)); diff --git a/drivers/w1/slaves/w1_ds2423.c b/drivers/w1/slaves/w1_ds2423.c new file mode 100644 index 000000000..b6bd18d5b --- /dev/null +++ b/drivers/w1/slaves/w1_ds2423.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * w1_ds2423.c + * + * Copyright (c) 2010 Mika Laitio <lamikr@pilppa.org> + * + * This driver will read and write the value of 4 counters to w1_slave file in + * sys filesystem. + * Inspired by the w1_therm and w1_ds2431 drivers. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/crc16.h> + +#include <linux/w1.h> + +#define W1_COUNTER_DS2423 0x1D + +#define CRC16_VALID 0xb001 +#define CRC16_INIT 0 + +#define COUNTER_COUNT 4 +#define READ_BYTE_COUNT 42 + +static ssize_t w1_slave_show(struct device *device, + struct device_attribute *attr, char *out_buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct w1_master *dev = sl->master; + u8 rbuf[COUNTER_COUNT * READ_BYTE_COUNT]; + u8 wrbuf[3]; + int rom_addr; + int read_byte_count; + int result; + ssize_t c; + int ii; + int p; + int crc; + + c = PAGE_SIZE; + rom_addr = (12 << 5) + 31; + wrbuf[0] = 0xA5; + wrbuf[1] = rom_addr & 0xFF; + wrbuf[2] = rom_addr >> 8; + mutex_lock(&dev->bus_mutex); + if (!w1_reset_select_slave(sl)) { + w1_write_block(dev, wrbuf, 3); + read_byte_count = 0; + for (p = 0; p < 4; p++) { + /* + * 1 byte for first bytes in ram page read + * 4 bytes for counter + * 4 bytes for zero bits + * 2 bytes for crc + * 31 remaining bytes from the ram page + */ + read_byte_count += w1_read_block(dev, + rbuf + (p * READ_BYTE_COUNT), READ_BYTE_COUNT); + for (ii = 0; ii < READ_BYTE_COUNT; ++ii) + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "%02x ", + rbuf[(p * READ_BYTE_COUNT) + ii]); + if (read_byte_count != (p + 1) * READ_BYTE_COUNT) { + dev_warn(device, + "w1_counter_read() returned %u bytes " + "instead of %d bytes wanted.\n", + read_byte_count, + READ_BYTE_COUNT); + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "crc=NO\n"); + } else { + if (p == 0) { + crc = crc16(CRC16_INIT, wrbuf, 3); + crc = crc16(crc, rbuf, 11); + } else { + /* + * DS2423 calculates crc from all bytes + * read after the previous crc bytes. + */ + crc = crc16(CRC16_INIT, + (rbuf + 11) + + ((p - 1) * READ_BYTE_COUNT), + READ_BYTE_COUNT); + } + if (crc == CRC16_VALID) { + result = 0; + for (ii = 4; ii > 0; ii--) { + result <<= 8; + result |= rbuf[(p * + READ_BYTE_COUNT) + ii]; + } + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "crc=YES c=%d\n", result); + } else { + c -= snprintf(out_buf + PAGE_SIZE - c, + c, "crc=NO\n"); + } + } + } + } else { + c -= snprintf(out_buf + PAGE_SIZE - c, c, "Connection error"); + } + mutex_unlock(&dev->bus_mutex); + return PAGE_SIZE - c; +} + +static DEVICE_ATTR_RO(w1_slave); + +static struct attribute *w1_f1d_attrs[] = { + &dev_attr_w1_slave.attr, + NULL, +}; +ATTRIBUTE_GROUPS(w1_f1d); + +static const struct w1_family_ops w1_f1d_fops = { + .groups = w1_f1d_groups, +}; + +static struct w1_family w1_family_1d = { + .fid = W1_COUNTER_DS2423, + .fops = &w1_f1d_fops, +}; +module_w1_family(w1_family_1d); + +MODULE_AUTHOR("Mika Laitio <lamikr@pilppa.org>"); +MODULE_DESCRIPTION("w1 family 1d driver for DS2423, 4 counters and 4kb ram"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_COUNTER_DS2423)); diff --git a/drivers/w1/slaves/w1_ds2430.c b/drivers/w1/slaves/w1_ds2430.c new file mode 100644 index 000000000..0ea7d779d --- /dev/null +++ b/drivers/w1/slaves/w1_ds2430.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds2430.c - w1 family 14 (DS2430) driver + ** + * Copyright (c) 2019 Angelo Dureghello <angelo.dureghello@timesys.com> + * + * Cloned and modified from ds2431 + * Copyright (c) 2008 Bernhard Weirich <bernhard.weirich@riedel.net> + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> + +#include <linux/w1.h> + +#define W1_EEPROM_DS2430 0x14 + +#define W1_F14_EEPROM_SIZE 32 +#define W1_F14_PAGE_COUNT 1 +#define W1_F14_PAGE_BITS 5 +#define W1_F14_PAGE_SIZE (1 << W1_F14_PAGE_BITS) +#define W1_F14_PAGE_MASK 0x1F + +#define W1_F14_SCRATCH_BITS 5 +#define W1_F14_SCRATCH_SIZE (1 << W1_F14_SCRATCH_BITS) +#define W1_F14_SCRATCH_MASK (W1_F14_SCRATCH_SIZE-1) + +#define W1_F14_READ_EEPROM 0xF0 +#define W1_F14_WRITE_SCRATCH 0x0F +#define W1_F14_READ_SCRATCH 0xAA +#define W1_F14_COPY_SCRATCH 0x55 +#define W1_F14_VALIDATION_KEY 0xa5 + +#define W1_F14_TPROG_MS 11 +#define W1_F14_READ_RETRIES 10 +#define W1_F14_READ_MAXLEN W1_F14_SCRATCH_SIZE + +/* + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f14_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +/* + * Read a block from W1 ROM two times and compares the results. + * If they are equal they are returned, otherwise the read + * is repeated W1_F14_READ_RETRIES times. + * + * count must not exceed W1_F14_READ_MAXLEN. + */ +static int w1_f14_readblock(struct w1_slave *sl, int off, int count, char *buf) +{ + u8 wrbuf[2]; + u8 cmp[W1_F14_READ_MAXLEN]; + int tries = W1_F14_READ_RETRIES; + + do { + wrbuf[0] = W1_F14_READ_EEPROM; + wrbuf[1] = off & 0xff; + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, 2); + w1_read_block(sl->master, buf, count); + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, 2); + w1_read_block(sl->master, cmp, count); + + if (!memcmp(cmp, buf, count)) + return 0; + } while (--tries); + + dev_err(&sl->dev, "proof reading failed %d times\n", + W1_F14_READ_RETRIES); + + return -1; +} + +static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int todo = count; + + count = w1_f14_fix_count(off, count, W1_F14_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + + /* read directly from the EEPROM in chunks of W1_F14_READ_MAXLEN */ + while (todo > 0) { + int block_read; + + if (todo >= W1_F14_READ_MAXLEN) + block_read = W1_F14_READ_MAXLEN; + else + block_read = todo; + + if (w1_f14_readblock(sl, off, block_read, buf) < 0) + count = -EIO; + + todo -= W1_F14_READ_MAXLEN; + buf += W1_F14_READ_MAXLEN; + off += W1_F14_READ_MAXLEN; + } + + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +/* + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be aligned at W1_F14_SCRATCH_SIZE bytes and + * must be W1_F14_SCRATCH_SIZE bytes long. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_F14_PAGE_SIZE - (addr & W1_F14_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f14_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + int tries = W1_F14_READ_RETRIES; + u8 wrbuf[2]; + u8 rdbuf[W1_F14_SCRATCH_SIZE + 3]; + +retry: + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F14_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + + w1_write_block(sl->master, wrbuf, 2); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F14_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 2); + + /* + * Compare what was read against the data written + * Note: on read scratchpad, device returns 2 bulk 0xff bytes, + * to be discarded. + */ + if ((memcmp(data, &rdbuf[2], len) != 0)) { + + if (--tries) + goto retry; + + dev_err(&sl->dev, + "could not write to eeprom, scratchpad compare failed %d times\n", + W1_F14_READ_RETRIES); + + return -1; + } + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F14_COPY_SCRATCH; + wrbuf[1] = W1_F14_VALIDATION_KEY; + w1_write_block(sl->master, wrbuf, 2); + + /* Sleep for tprog ms to wait for the write to complete */ + msleep(W1_F14_TPROG_MS); + + /* Reset the bus to wake up the EEPROM */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t eeprom_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr, len; + int copy; + + count = w1_f14_fix_count(off, count, W1_F14_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + + /* Can only write data in blocks of the size of the scratchpad */ + addr = off; + len = count; + while (len > 0) { + + /* if len too short or addr not aligned */ + if (len < W1_F14_SCRATCH_SIZE || addr & W1_F14_SCRATCH_MASK) { + char tmp[W1_F14_SCRATCH_SIZE]; + + /* read the block and update the parts to be written */ + if (w1_f14_readblock(sl, addr & ~W1_F14_SCRATCH_MASK, + W1_F14_SCRATCH_SIZE, tmp)) { + count = -EIO; + goto out_up; + } + + /* copy at most to the boundary of the PAGE or len */ + copy = W1_F14_SCRATCH_SIZE - + (addr & W1_F14_SCRATCH_MASK); + + if (copy > len) + copy = len; + + memcpy(&tmp[addr & W1_F14_SCRATCH_MASK], buf, copy); + if (w1_f14_write(sl, addr & ~W1_F14_SCRATCH_MASK, + W1_F14_SCRATCH_SIZE, tmp) < 0) { + count = -EIO; + goto out_up; + } + } else { + + copy = W1_F14_SCRATCH_SIZE; + if (w1_f14_write(sl, addr, copy, buf) < 0) { + count = -EIO; + goto out_up; + } + } + buf += copy; + addr += copy; + len -= copy; + } + +out_up: + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +static BIN_ATTR_RW(eeprom, W1_F14_EEPROM_SIZE); + +static struct bin_attribute *w1_f14_bin_attrs[] = { + &bin_attr_eeprom, + NULL, +}; + +static const struct attribute_group w1_f14_group = { + .bin_attrs = w1_f14_bin_attrs, +}; + +static const struct attribute_group *w1_f14_groups[] = { + &w1_f14_group, + NULL, +}; + +static const struct w1_family_ops w1_f14_fops = { + .groups = w1_f14_groups, +}; + +static struct w1_family w1_family_14 = { + .fid = W1_EEPROM_DS2430, + .fops = &w1_f14_fops, +}; +module_w1_family(w1_family_14); + +MODULE_AUTHOR("Angelo Dureghello <angelo.dureghello@timesys.com>"); +MODULE_DESCRIPTION("w1 family 14 driver for DS2430, 256b EEPROM"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2430)); diff --git a/drivers/w1/slaves/w1_ds2431.c b/drivers/w1/slaves/w1_ds2431.c new file mode 100644 index 000000000..6856b1c29 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2431.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds2431.c - w1 family 2d (DS2431) driver + * + * Copyright (c) 2008 Bernhard Weirich <bernhard.weirich@riedel.net> + * + * Heavily inspired by w1_DS2433 driver from Ben Gardner <bgardner@wabtec.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> + +#include <linux/w1.h> + +#define W1_EEPROM_DS2431 0x2D + +#define W1_F2D_EEPROM_SIZE 128 +#define W1_F2D_PAGE_COUNT 4 +#define W1_F2D_PAGE_BITS 5 +#define W1_F2D_PAGE_SIZE (1<<W1_F2D_PAGE_BITS) +#define W1_F2D_PAGE_MASK 0x1F + +#define W1_F2D_SCRATCH_BITS 3 +#define W1_F2D_SCRATCH_SIZE (1<<W1_F2D_SCRATCH_BITS) +#define W1_F2D_SCRATCH_MASK (W1_F2D_SCRATCH_SIZE-1) + +#define W1_F2D_READ_EEPROM 0xF0 +#define W1_F2D_WRITE_SCRATCH 0x0F +#define W1_F2D_READ_SCRATCH 0xAA +#define W1_F2D_COPY_SCRATCH 0x55 + + +#define W1_F2D_TPROG_MS 11 + +#define W1_F2D_READ_RETRIES 10 +#define W1_F2D_READ_MAXLEN 8 + +/* + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f2d_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +/* + * Read a block from W1 ROM two times and compares the results. + * If they are equal they are returned, otherwise the read + * is repeated W1_F2D_READ_RETRIES times. + * + * count must not exceed W1_F2D_READ_MAXLEN. + */ +static int w1_f2d_readblock(struct w1_slave *sl, int off, int count, char *buf) +{ + u8 wrbuf[3]; + u8 cmp[W1_F2D_READ_MAXLEN]; + int tries = W1_F2D_READ_RETRIES; + + do { + wrbuf[0] = W1_F2D_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, buf, count); + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, cmp, count); + + if (!memcmp(cmp, buf, count)) + return 0; + } while (--tries); + + dev_err(&sl->dev, "proof reading failed %d times\n", + W1_F2D_READ_RETRIES); + + return -1; +} + +static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int todo = count; + + count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + + /* read directly from the EEPROM in chunks of W1_F2D_READ_MAXLEN */ + while (todo > 0) { + int block_read; + + if (todo >= W1_F2D_READ_MAXLEN) + block_read = W1_F2D_READ_MAXLEN; + else + block_read = todo; + + if (w1_f2d_readblock(sl, off, block_read, buf) < 0) + count = -EIO; + + todo -= W1_F2D_READ_MAXLEN; + buf += W1_F2D_READ_MAXLEN; + off += W1_F2D_READ_MAXLEN; + } + + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +/* + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be aligned at W1_F2D_SCRATCH_SIZE bytes and + * must be W1_F2D_SCRATCH_SIZE bytes long. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_F2D_PAGE_SIZE - (addr & W1_F2D_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f2d_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + int tries = W1_F2D_READ_RETRIES; + u8 wrbuf[4]; + u8 rdbuf[W1_F2D_SCRATCH_SIZE + 3]; + u8 es = (addr + len - 1) % W1_F2D_SCRATCH_SIZE; + +retry: + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F2D_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F2D_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) { + + if (--tries) + goto retry; + + dev_err(&sl->dev, + "could not write to eeprom, scratchpad compare failed %d times\n", + W1_F2D_READ_RETRIES); + + return -1; + } + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F2D_COPY_SCRATCH; + wrbuf[3] = es; + w1_write_block(sl->master, wrbuf, 4); + + /* Sleep for tprog ms to wait for the write to complete */ + msleep(W1_F2D_TPROG_MS); + + /* Reset the bus to wake up the EEPROM */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t eeprom_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr, len; + int copy; + + count = w1_f2d_fix_count(off, count, W1_F2D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + + /* Can only write data in blocks of the size of the scratchpad */ + addr = off; + len = count; + while (len > 0) { + + /* if len too short or addr not aligned */ + if (len < W1_F2D_SCRATCH_SIZE || addr & W1_F2D_SCRATCH_MASK) { + char tmp[W1_F2D_SCRATCH_SIZE]; + + /* read the block and update the parts to be written */ + if (w1_f2d_readblock(sl, addr & ~W1_F2D_SCRATCH_MASK, + W1_F2D_SCRATCH_SIZE, tmp)) { + count = -EIO; + goto out_up; + } + + /* copy at most to the boundary of the PAGE or len */ + copy = W1_F2D_SCRATCH_SIZE - + (addr & W1_F2D_SCRATCH_MASK); + + if (copy > len) + copy = len; + + memcpy(&tmp[addr & W1_F2D_SCRATCH_MASK], buf, copy); + if (w1_f2d_write(sl, addr & ~W1_F2D_SCRATCH_MASK, + W1_F2D_SCRATCH_SIZE, tmp) < 0) { + count = -EIO; + goto out_up; + } + } else { + + copy = W1_F2D_SCRATCH_SIZE; + if (w1_f2d_write(sl, addr, copy, buf) < 0) { + count = -EIO; + goto out_up; + } + } + buf += copy; + addr += copy; + len -= copy; + } + +out_up: + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +static BIN_ATTR_RW(eeprom, W1_F2D_EEPROM_SIZE); + +static struct bin_attribute *w1_f2d_bin_attrs[] = { + &bin_attr_eeprom, + NULL, +}; + +static const struct attribute_group w1_f2d_group = { + .bin_attrs = w1_f2d_bin_attrs, +}; + +static const struct attribute_group *w1_f2d_groups[] = { + &w1_f2d_group, + NULL, +}; + +static const struct w1_family_ops w1_f2d_fops = { + .groups = w1_f2d_groups, +}; + +static struct w1_family w1_family_2d = { + .fid = W1_EEPROM_DS2431, + .fops = &w1_f2d_fops, +}; +module_w1_family(w1_family_2d); + +MODULE_AUTHOR("Bernhard Weirich <bernhard.weirich@riedel.net>"); +MODULE_DESCRIPTION("w1 family 2d driver for DS2431, 1kb EEPROM"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2431)); diff --git a/drivers/w1/slaves/w1_ds2433.c b/drivers/w1/slaves/w1_ds2433.c new file mode 100644 index 000000000..0f72df15a --- /dev/null +++ b/drivers/w1/slaves/w1_ds2433.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds2433.c - w1 family 23 (DS2433) driver + * + * Copyright (c) 2005 Ben Gardner <bgardner@wabtec.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#ifdef CONFIG_W1_SLAVE_DS2433_CRC +#include <linux/crc16.h> + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#endif + +#include <linux/w1.h> + +#define W1_EEPROM_DS2433 0x23 + +#define W1_EEPROM_SIZE 512 +#define W1_PAGE_COUNT 16 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F23_TIME 300 + +#define W1_F23_READ_EEPROM 0xF0 +#define W1_F23_WRITE_SCRATCH 0x0F +#define W1_F23_READ_SCRATCH 0xAA +#define W1_F23_COPY_SCRATCH 0x55 + +struct w1_f23_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f23_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return (size - off); + + return count; +} + +#ifdef CONFIG_W1_SLAVE_DS2433_CRC +static int w1_f23_refresh_block(struct w1_slave *sl, struct w1_f23_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + + wrbuf[0] = W1_F23_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + +static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + struct w1_f23_data *data = sl->family_data; + int i, min_page, max_page; +#else + u8 wrbuf[3]; +#endif + + if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + + mutex_lock(&sl->master->bus_mutex); + +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f23_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + +#else /* CONFIG_W1_SLAVE_DS2433_CRC */ + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) { + count = -EIO; + goto out_up; + } + + wrbuf[0] = W1_F23_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, buf, count); + +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + +out_up: + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f23_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + struct w1_f23_data *f23 = sl->family_data; +#endif + u8 wrbuf[4]; + u8 rdbuf[W1_PAGE_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F23_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F23_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) + return -1; + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F23_COPY_SCRATCH; + wrbuf[3] = es; + w1_write_block(sl->master, wrbuf, 4); + + /* Sleep for 5 ms to wait for the write to complete */ + msleep(5); + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + f23->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); +#endif + return 0; +} + +static ssize_t eeprom_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr, len, idx; + + if ((count = w1_f23_fix_count(off, count, W1_EEPROM_SIZE)) == 0) + return 0; + +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", (int)off); + return -EINVAL; + } + } +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + + mutex_lock(&sl->master->bus_mutex); + + /* Can only write data to one page at a time */ + idx = 0; + while (idx < count) { + addr = off + idx; + len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); + if (len > (count - idx)) + len = count - idx; + + if (w1_f23_write(sl, addr, len, &buf[idx]) < 0) { + count = -EIO; + goto out_up; + } + idx += len; + } + +out_up: + mutex_unlock(&sl->master->bus_mutex); + + return count; +} + +static BIN_ATTR_RW(eeprom, W1_EEPROM_SIZE); + +static struct bin_attribute *w1_f23_bin_attributes[] = { + &bin_attr_eeprom, + NULL, +}; + +static const struct attribute_group w1_f23_group = { + .bin_attrs = w1_f23_bin_attributes, +}; + +static const struct attribute_group *w1_f23_groups[] = { + &w1_f23_group, + NULL, +}; + +static int w1_f23_add_slave(struct w1_slave *sl) +{ +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + struct w1_f23_data *data; + + data = kzalloc(sizeof(struct w1_f23_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ + return 0; +} + +static void w1_f23_remove_slave(struct w1_slave *sl) +{ +#ifdef CONFIG_W1_SLAVE_DS2433_CRC + kfree(sl->family_data); + sl->family_data = NULL; +#endif /* CONFIG_W1_SLAVE_DS2433_CRC */ +} + +static const struct w1_family_ops w1_f23_fops = { + .add_slave = w1_f23_add_slave, + .remove_slave = w1_f23_remove_slave, + .groups = w1_f23_groups, +}; + +static struct w1_family w1_family_23 = { + .fid = W1_EEPROM_DS2433, + .fops = &w1_f23_fops, +}; +module_w1_family(w1_family_23); + +MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); +MODULE_DESCRIPTION("w1 family 23 driver for DS2433, 4kb EEPROM"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_EEPROM_DS2433)); diff --git a/drivers/w1/slaves/w1_ds2438.c b/drivers/w1/slaves/w1_ds2438.c new file mode 100644 index 000000000..ca64f99c8 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2438.c @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 1-Wire implementation for the ds2438 chip + * + * Copyright (c) 2017 Mariusz Bialonczyk <manio@skyboo.net> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> + +#include <linux/w1.h> + +#define W1_FAMILY_DS2438 0x26 + +#define W1_DS2438_RETRIES 3 + +/* Memory commands */ +#define W1_DS2438_READ_SCRATCH 0xBE +#define W1_DS2438_WRITE_SCRATCH 0x4E +#define W1_DS2438_COPY_SCRATCH 0x48 +#define W1_DS2438_RECALL_MEMORY 0xB8 +/* Register commands */ +#define W1_DS2438_CONVERT_TEMP 0x44 +#define W1_DS2438_CONVERT_VOLTAGE 0xB4 + +#define DS2438_PAGE_SIZE 8 +#define DS2438_ADC_INPUT_VAD 0 +#define DS2438_ADC_INPUT_VDD 1 +#define DS2438_MAX_CONVERSION_TIME 10 /* ms */ + +/* Page #0 definitions */ +#define DS2438_STATUS_REG 0x00 /* Status/Configuration Register */ +#define DS2438_STATUS_IAD (1 << 0) /* Current A/D Control Bit */ +#define DS2438_STATUS_CA (1 << 1) /* Current Accumulator Configuration */ +#define DS2438_STATUS_EE (1 << 2) /* Current Accumulator Shadow Selector bit */ +#define DS2438_STATUS_AD (1 << 3) /* Voltage A/D Input Select Bit */ +#define DS2438_STATUS_TB (1 << 4) /* Temperature Busy Flag */ +#define DS2438_STATUS_NVB (1 << 5) /* Nonvolatile Memory Busy Flag */ +#define DS2438_STATUS_ADB (1 << 6) /* A/D Converter Busy Flag */ + +#define DS2438_TEMP_LSB 0x01 +#define DS2438_TEMP_MSB 0x02 +#define DS2438_VOLTAGE_LSB 0x03 +#define DS2438_VOLTAGE_MSB 0x04 +#define DS2438_CURRENT_LSB 0x05 +#define DS2438_CURRENT_MSB 0x06 +#define DS2438_THRESHOLD 0x07 + +/* Page #1 definitions */ +#define DS2438_ETM_0 0x00 +#define DS2438_ETM_1 0x01 +#define DS2438_ETM_2 0x02 +#define DS2438_ETM_3 0x03 +#define DS2438_ICA 0x04 +#define DS2438_OFFSET_LSB 0x05 +#define DS2438_OFFSET_MSB 0x06 + +static int w1_ds2438_get_page(struct w1_slave *sl, int pageno, u8 *buf) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[2]; + u8 crc; + size_t count; + + while (retries--) { + crc = 0; + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = (u8)pageno; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = (u8)pageno; + w1_write_block(sl->master, w1_buf, 2); + + count = w1_read_block(sl->master, buf, DS2438_PAGE_SIZE + 1); + if (count == DS2438_PAGE_SIZE + 1) { + crc = w1_calc_crc8(buf, DS2438_PAGE_SIZE); + + /* check for correct CRC */ + if ((u8)buf[DS2438_PAGE_SIZE] == crc) + return 0; + } + } + return -1; +} + +static int w1_ds2438_get_temperature(struct w1_slave *sl, int16_t *temperature) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_TEMP); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *temperature = (((int16_t) w1_buf[DS2438_TEMP_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_TEMP_LSB]); + ret = 0; + } else + ret = -1; + + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +static int w1_ds2438_change_config_bit(struct w1_slave *sl, u8 mask, u8 value) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[3]; + u8 status; + int perform_write = 0; + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_RECALL_MEMORY; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_READ_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + /* reading one byte of result */ + status = w1_read_8(sl->master); + + /* if bit0=1, set a value to a mask for easy compare */ + if (value) + value = mask; + + if ((status & mask) == value) + return 0; /* already set as requested */ + + /* changing bit */ + status ^= mask; + perform_write = 1; + + break; + } + + if (perform_write) { + retries = W1_DS2438_RETRIES; + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_WRITE_SCRATCH; + w1_buf[1] = 0x00; + w1_buf[2] = status; + w1_write_block(sl->master, w1_buf, 3); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_COPY_SCRATCH; + w1_buf[1] = 0x00; + w1_write_block(sl->master, w1_buf, 2); + + return 0; + } + } + return -1; +} + +static int w1_ds2438_change_offset_register(struct w1_slave *sl, u8 *value) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[9]; + u8 w1_page1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + + if (w1_ds2438_get_page(sl, 1, w1_page1_buf) == 0) { + memcpy(&w1_buf[2], w1_page1_buf, DS2438_PAGE_SIZE - 1); /* last register reserved */ + w1_buf[7] = value[0]; /* change only offset register */ + w1_buf[8] = value[1]; + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_WRITE_SCRATCH; + w1_buf[1] = 0x01; /* write to page 1 */ + w1_write_block(sl->master, w1_buf, 9); + + if (w1_reset_select_slave(sl)) + continue; + w1_buf[0] = W1_DS2438_COPY_SCRATCH; + w1_buf[1] = 0x01; + w1_write_block(sl->master, w1_buf, 2); + return 0; + } + } + return -1; +} + +static int w1_ds2438_get_voltage(struct w1_slave *sl, + int adc_input, uint16_t *voltage) +{ + unsigned int retries = W1_DS2438_RETRIES; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + unsigned int tm = DS2438_MAX_CONVERSION_TIME; + unsigned long sleep_rem; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_AD, adc_input)) { + ret = -1; + goto pre_unlock; + } + + while (retries--) { + if (w1_reset_select_slave(sl)) + continue; + w1_write_8(sl->master, W1_DS2438_CONVERT_VOLTAGE); + + mutex_unlock(&sl->master->bus_mutex); + sleep_rem = msleep_interruptible(tm); + if (sleep_rem != 0) { + ret = -1; + goto post_unlock; + } + + if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) { + ret = -1; + goto post_unlock; + } + + break; + } + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + *voltage = (((uint16_t) w1_buf[DS2438_VOLTAGE_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_VOLTAGE_LSB]); + ret = 0; + } else + ret = -1; + +pre_unlock: + mutex_unlock(&sl->master->bus_mutex); + +post_unlock: + return ret; +} + +static int w1_ds2438_get_current(struct w1_slave *sl, int16_t *voltage) +{ + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + int ret; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + /* The voltage measured across current sense resistor RSENS. */ + *voltage = (((int16_t) w1_buf[DS2438_CURRENT_MSB]) << 8) | ((int16_t) w1_buf[DS2438_CURRENT_LSB]); + ret = 0; + } else + ret = -1; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t iad_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_IAD, *buf & 0x01) == 0) + ret = 1; + else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t iad_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + int16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_current(sl, &voltage) == 0) + ret = snprintf(buf, count, "%i\n", voltage); + else + ret = -EIO; + + return ret; +} + +static ssize_t page0_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + /* Read no more than page0 size */ + if (count > DS2438_PAGE_SIZE) + count = DS2438_PAGE_SIZE; + + if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) { + memcpy(buf, &w1_buf, count); + ret = count; + } else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t page1_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/]; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + /* Read no more than page1 size */ + if (count > DS2438_PAGE_SIZE) + count = DS2438_PAGE_SIZE; + + if (w1_ds2438_get_page(sl, 1, w1_buf) == 0) { + memcpy(buf, &w1_buf, count); + ret = count; + } else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t offset_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_ds2438_change_offset_register(sl, buf) == 0) + ret = count; + else + ret = -EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} + +static ssize_t temperature_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + int16_t temp; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_temperature(sl, &temp) == 0) + ret = snprintf(buf, count, "%i\n", temp); + else + ret = -EIO; + + return ret; +} + +static ssize_t vad_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VAD, &voltage) == 0) + ret = snprintf(buf, count, "%u\n", voltage); + else + ret = -EIO; + + return ret; +} + +static ssize_t vdd_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + uint16_t voltage; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VDD, &voltage) == 0) + ret = snprintf(buf, count, "%u\n", voltage); + else + ret = -EIO; + + return ret; +} + +static BIN_ATTR_RW(iad, 0); +static BIN_ATTR_RO(page0, DS2438_PAGE_SIZE); +static BIN_ATTR_RO(page1, DS2438_PAGE_SIZE); +static BIN_ATTR_WO(offset, 2); +static BIN_ATTR_RO(temperature, 0/* real length varies */); +static BIN_ATTR_RO(vad, 0/* real length varies */); +static BIN_ATTR_RO(vdd, 0/* real length varies */); + +static struct bin_attribute *w1_ds2438_bin_attrs[] = { + &bin_attr_iad, + &bin_attr_page0, + &bin_attr_page1, + &bin_attr_offset, + &bin_attr_temperature, + &bin_attr_vad, + &bin_attr_vdd, + NULL, +}; + +static const struct attribute_group w1_ds2438_group = { + .bin_attrs = w1_ds2438_bin_attrs, +}; + +static const struct attribute_group *w1_ds2438_groups[] = { + &w1_ds2438_group, + NULL, +}; + +static const struct w1_family_ops w1_ds2438_fops = { + .groups = w1_ds2438_groups, +}; + +static struct w1_family w1_ds2438_family = { + .fid = W1_FAMILY_DS2438, + .fops = &w1_ds2438_fops, +}; +module_w1_family(w1_ds2438_family); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>"); +MODULE_DESCRIPTION("1-wire driver for Maxim/Dallas DS2438 Smart Battery Monitor"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2438)); diff --git a/drivers/w1/slaves/w1_ds250x.c b/drivers/w1/slaves/w1_ds250x.c new file mode 100644 index 000000000..7592c7050 --- /dev/null +++ b/drivers/w1/slaves/w1_ds250x.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * w1_ds250x.c - w1 family 09/0b/89/91 (DS250x) driver + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/crc16.h> + +#include <linux/w1.h> +#include <linux/nvmem-provider.h> + +#define W1_DS2501_UNW_FAMILY 0x91 +#define W1_DS2501_SIZE 64 + +#define W1_DS2502_FAMILY 0x09 +#define W1_DS2502_UNW_FAMILY 0x89 +#define W1_DS2502_SIZE 128 + +#define W1_DS2505_FAMILY 0x0b +#define W1_DS2505_SIZE 2048 + +#define W1_PAGE_SIZE 32 + +#define W1_EXT_READ_MEMORY 0xA5 +#define W1_READ_DATA_CRC 0xC3 + +#define OFF2PG(off) ((off) / W1_PAGE_SIZE) + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +struct w1_eprom_data { + size_t size; + int (*read)(struct w1_slave *sl, int pageno); + u8 eprom[W1_DS2505_SIZE]; + DECLARE_BITMAP(page_present, W1_DS2505_SIZE / W1_PAGE_SIZE); + char nvmem_name[64]; +}; + +static int w1_ds2502_read_page(struct w1_slave *sl, int pageno) +{ + struct w1_eprom_data *data = sl->family_data; + int pgoff = pageno * W1_PAGE_SIZE; + int ret = -EIO; + u8 buf[3]; + u8 crc8; + + if (test_bit(pageno, data->page_present)) + return 0; /* page already present */ + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) + goto err; + + buf[0] = W1_READ_DATA_CRC; + buf[1] = pgoff & 0xff; + buf[2] = pgoff >> 8; + w1_write_block(sl->master, buf, 3); + + crc8 = w1_read_8(sl->master); + if (w1_calc_crc8(buf, 3) != crc8) + goto err; + + w1_read_block(sl->master, &data->eprom[pgoff], W1_PAGE_SIZE); + + crc8 = w1_read_8(sl->master); + if (w1_calc_crc8(&data->eprom[pgoff], W1_PAGE_SIZE) != crc8) + goto err; + + set_bit(pageno, data->page_present); /* mark page present */ + ret = 0; +err: + mutex_unlock(&sl->master->bus_mutex); + return ret; +} + +static int w1_ds2505_read_page(struct w1_slave *sl, int pageno) +{ + struct w1_eprom_data *data = sl->family_data; + int redir_retries = 16; + int pgoff, epoff; + int ret = -EIO; + u8 buf[6]; + u8 redir; + u16 crc; + + if (test_bit(pageno, data->page_present)) + return 0; /* page already present */ + + epoff = pgoff = pageno * W1_PAGE_SIZE; + mutex_lock(&sl->master->bus_mutex); + +retry: + if (w1_reset_select_slave(sl)) + goto err; + + buf[0] = W1_EXT_READ_MEMORY; + buf[1] = pgoff & 0xff; + buf[2] = pgoff >> 8; + w1_write_block(sl->master, buf, 3); + w1_read_block(sl->master, buf + 3, 3); /* redir, crc16 */ + redir = buf[3]; + crc = crc16(CRC16_INIT, buf, 6); + + if (crc != CRC16_VALID) + goto err; + + + if (redir != 0xff) { + redir_retries--; + if (redir_retries < 0) + goto err; + + pgoff = (redir ^ 0xff) * W1_PAGE_SIZE; + goto retry; + } + + w1_read_block(sl->master, &data->eprom[epoff], W1_PAGE_SIZE); + w1_read_block(sl->master, buf, 2); /* crc16 */ + crc = crc16(CRC16_INIT, &data->eprom[epoff], W1_PAGE_SIZE); + crc = crc16(crc, buf, 2); + + if (crc != CRC16_VALID) + goto err; + + set_bit(pageno, data->page_present); + ret = 0; +err: + mutex_unlock(&sl->master->bus_mutex); + return ret; +} + +static int w1_nvmem_read(void *priv, unsigned int off, void *buf, size_t count) +{ + struct w1_slave *sl = priv; + struct w1_eprom_data *data = sl->family_data; + size_t eprom_size = data->size; + int ret; + int i; + + if (off > eprom_size) + return -EINVAL; + + if ((off + count) > eprom_size) + count = eprom_size - off; + + i = OFF2PG(off); + do { + ret = data->read(sl, i++); + if (ret < 0) + return ret; + } while (i < OFF2PG(off + count)); + + memcpy(buf, &data->eprom[off], count); + return 0; +} + +static int w1_eprom_add_slave(struct w1_slave *sl) +{ + struct w1_eprom_data *data; + struct nvmem_device *nvmem; + struct nvmem_config nvmem_cfg = { + .dev = &sl->dev, + .reg_read = w1_nvmem_read, + .type = NVMEM_TYPE_OTP, + .read_only = true, + .word_size = 1, + .priv = sl, + .id = -1 + }; + + data = devm_kzalloc(&sl->dev, sizeof(struct w1_eprom_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + sl->family_data = data; + switch (sl->family->fid) { + case W1_DS2501_UNW_FAMILY: + data->size = W1_DS2501_SIZE; + data->read = w1_ds2502_read_page; + break; + case W1_DS2502_FAMILY: + case W1_DS2502_UNW_FAMILY: + data->size = W1_DS2502_SIZE; + data->read = w1_ds2502_read_page; + break; + case W1_DS2505_FAMILY: + data->size = W1_DS2505_SIZE; + data->read = w1_ds2505_read_page; + break; + } + + if (sl->master->bus_master->dev_id) + snprintf(data->nvmem_name, sizeof(data->nvmem_name), + "%s-%02x-%012llx", + sl->master->bus_master->dev_id, sl->reg_num.family, + (unsigned long long)sl->reg_num.id); + else + snprintf(data->nvmem_name, sizeof(data->nvmem_name), + "%02x-%012llx", + sl->reg_num.family, + (unsigned long long)sl->reg_num.id); + + nvmem_cfg.name = data->nvmem_name; + nvmem_cfg.size = data->size; + + nvmem = devm_nvmem_register(&sl->dev, &nvmem_cfg); + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct w1_family_ops w1_eprom_fops = { + .add_slave = w1_eprom_add_slave, +}; + +static struct w1_family w1_family_09 = { + .fid = W1_DS2502_FAMILY, + .fops = &w1_eprom_fops, +}; + +static struct w1_family w1_family_0b = { + .fid = W1_DS2505_FAMILY, + .fops = &w1_eprom_fops, +}; + +static struct w1_family w1_family_89 = { + .fid = W1_DS2502_UNW_FAMILY, + .fops = &w1_eprom_fops, +}; + +static struct w1_family w1_family_91 = { + .fid = W1_DS2501_UNW_FAMILY, + .fops = &w1_eprom_fops, +}; + +static int __init w1_ds250x_init(void) +{ + int err; + + err = w1_register_family(&w1_family_09); + if (err) + return err; + + err = w1_register_family(&w1_family_0b); + if (err) + goto err_0b; + + err = w1_register_family(&w1_family_89); + if (err) + goto err_89; + + err = w1_register_family(&w1_family_91); + if (err) + goto err_91; + + return 0; + +err_91: + w1_unregister_family(&w1_family_89); +err_89: + w1_unregister_family(&w1_family_0b); +err_0b: + w1_unregister_family(&w1_family_09); + return err; +} + +static void __exit w1_ds250x_exit(void) +{ + w1_unregister_family(&w1_family_09); + w1_unregister_family(&w1_family_0b); + w1_unregister_family(&w1_family_89); + w1_unregister_family(&w1_family_91); +} + +module_init(w1_ds250x_init); +module_exit(w1_ds250x_exit); + +MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfe@suse.de>"); +MODULE_DESCRIPTION("w1 family driver for DS250x Add Only Memory"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_DS2502_FAMILY)); +MODULE_ALIAS("w1-family-" __stringify(W1_DS2505_FAMILY)); +MODULE_ALIAS("w1-family-" __stringify(W1_DS2501_UNW_FAMILY)); +MODULE_ALIAS("w1-family-" __stringify(W1_DS2502_UNW_FAMILY)); diff --git a/drivers/w1/slaves/w1_ds2780.c b/drivers/w1/slaves/w1_ds2780.c new file mode 100644 index 000000000..9dcb5a54f --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 1-Wire implementation for the ds2780 chip + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * Based on w1-ds2760 driver + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/idr.h> + +#include <linux/w1.h> + +#include "w1_ds2780.h" + +#define W1_FAMILY_DS2780 0x32 + +static int w1_ds2780_do_io(struct device *dev, char *buf, int addr, + size_t count, int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (addr > DS2780_DATA_SIZE || addr < 0) + return 0; + + count = min_t(int, count, DS2780_DATA_SIZE - addr); + + if (w1_reset_select_slave(sl) == 0) { + if (io) { + w1_write_8(sl->master, W1_DS2780_WRITE_DATA); + w1_write_8(sl->master, addr); + w1_write_block(sl->master, buf, count); + } else { + w1_write_8(sl->master, W1_DS2780_READ_DATA); + w1_write_8(sl->master, addr); + count = w1_read_block(sl->master, buf, count); + } + } + + return count; +} + +int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, + int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + int ret; + + if (!dev) + return -ENODEV; + + mutex_lock(&sl->master->bus_mutex); + + ret = w1_ds2780_do_io(dev, buf, addr, count, io); + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} +EXPORT_SYMBOL(w1_ds2780_io); + +int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->bus_mutex); + return 0; +} +EXPORT_SYMBOL(w1_ds2780_eeprom_cmd); + +static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + return w1_ds2780_io(dev, buf, off, count, 0); +} + +static BIN_ATTR_RO(w1_slave, DS2780_DATA_SIZE); + +static struct bin_attribute *w1_ds2780_bin_attrs[] = { + &bin_attr_w1_slave, + NULL, +}; + +static const struct attribute_group w1_ds2780_group = { + .bin_attrs = w1_ds2780_bin_attrs, +}; + +static const struct attribute_group *w1_ds2780_groups[] = { + &w1_ds2780_group, + NULL, +}; + +static int w1_ds2780_add_slave(struct w1_slave *sl) +{ + int ret; + struct platform_device *pdev; + + pdev = platform_device_alloc("ds2780-battery", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + dev_set_drvdata(&sl->dev, pdev); + + return 0; + +pdev_add_failed: + platform_device_put(pdev); + + return ret; +} + +static void w1_ds2780_remove_slave(struct w1_slave *sl) +{ + struct platform_device *pdev = dev_get_drvdata(&sl->dev); + + platform_device_unregister(pdev); +} + +static const struct w1_family_ops w1_ds2780_fops = { + .add_slave = w1_ds2780_add_slave, + .remove_slave = w1_ds2780_remove_slave, + .groups = w1_ds2780_groups, +}; + +static struct w1_family w1_ds2780_family = { + .fid = W1_FAMILY_DS2780, + .fops = &w1_ds2780_fops, +}; +module_w1_family(w1_ds2780_family); + +MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>"); +MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2780)); diff --git a/drivers/w1/slaves/w1_ds2780.h b/drivers/w1/slaves/w1_ds2780.h new file mode 100644 index 000000000..99e38ed78 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2780.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * 1-Wire implementation for the ds2780 chip + * + * Copyright (C) 2010 Indesign, LLC + * + * Author: Clifton Barnes <cabarnes@indesign-llc.com> + * + * Based on w1-ds2760 driver + */ + +#ifndef _W1_DS2780_H +#define _W1_DS2780_H + +/* Function commands */ +#define W1_DS2780_READ_DATA 0x69 +#define W1_DS2780_WRITE_DATA 0x6C +#define W1_DS2780_COPY_DATA 0x48 +#define W1_DS2780_RECALL_DATA 0xB8 +#define W1_DS2780_LOCK 0x6A + +/* Register map */ +/* Register 0x00 Reserved */ +#define DS2780_STATUS_REG 0x01 +#define DS2780_RAAC_MSB_REG 0x02 +#define DS2780_RAAC_LSB_REG 0x03 +#define DS2780_RSAC_MSB_REG 0x04 +#define DS2780_RSAC_LSB_REG 0x05 +#define DS2780_RARC_REG 0x06 +#define DS2780_RSRC_REG 0x07 +#define DS2780_IAVG_MSB_REG 0x08 +#define DS2780_IAVG_LSB_REG 0x09 +#define DS2780_TEMP_MSB_REG 0x0A +#define DS2780_TEMP_LSB_REG 0x0B +#define DS2780_VOLT_MSB_REG 0x0C +#define DS2780_VOLT_LSB_REG 0x0D +#define DS2780_CURRENT_MSB_REG 0x0E +#define DS2780_CURRENT_LSB_REG 0x0F +#define DS2780_ACR_MSB_REG 0x10 +#define DS2780_ACR_LSB_REG 0x11 +#define DS2780_ACRL_MSB_REG 0x12 +#define DS2780_ACRL_LSB_REG 0x13 +#define DS2780_AS_REG 0x14 +#define DS2780_SFR_REG 0x15 +#define DS2780_FULL_MSB_REG 0x16 +#define DS2780_FULL_LSB_REG 0x17 +#define DS2780_AE_MSB_REG 0x18 +#define DS2780_AE_LSB_REG 0x19 +#define DS2780_SE_MSB_REG 0x1A +#define DS2780_SE_LSB_REG 0x1B +/* Register 0x1C - 0x1E Reserved */ +#define DS2780_EEPROM_REG 0x1F +#define DS2780_EEPROM_BLOCK0_START 0x20 +/* Register 0x20 - 0x2F User EEPROM */ +#define DS2780_EEPROM_BLOCK0_END 0x2F +/* Register 0x30 - 0x5F Reserved */ +#define DS2780_EEPROM_BLOCK1_START 0x60 +#define DS2780_CONTROL_REG 0x60 +#define DS2780_AB_REG 0x61 +#define DS2780_AC_MSB_REG 0x62 +#define DS2780_AC_LSB_REG 0x63 +#define DS2780_VCHG_REG 0x64 +#define DS2780_IMIN_REG 0x65 +#define DS2780_VAE_REG 0x66 +#define DS2780_IAE_REG 0x67 +#define DS2780_AE_40_REG 0x68 +#define DS2780_RSNSP_REG 0x69 +#define DS2780_FULL_40_MSB_REG 0x6A +#define DS2780_FULL_40_LSB_REG 0x6B +#define DS2780_FULL_3040_SLOPE_REG 0x6C +#define DS2780_FULL_2030_SLOPE_REG 0x6D +#define DS2780_FULL_1020_SLOPE_REG 0x6E +#define DS2780_FULL_0010_SLOPE_REG 0x6F +#define DS2780_AE_3040_SLOPE_REG 0x70 +#define DS2780_AE_2030_SLOPE_REG 0x71 +#define DS2780_AE_1020_SLOPE_REG 0x72 +#define DS2780_AE_0010_SLOPE_REG 0x73 +#define DS2780_SE_3040_SLOPE_REG 0x74 +#define DS2780_SE_2030_SLOPE_REG 0x75 +#define DS2780_SE_1020_SLOPE_REG 0x76 +#define DS2780_SE_0010_SLOPE_REG 0x77 +#define DS2780_RSGAIN_MSB_REG 0x78 +#define DS2780_RSGAIN_LSB_REG 0x79 +#define DS2780_RSTC_REG 0x7A +#define DS2780_FRSGAIN_MSB_REG 0x7B +#define DS2780_FRSGAIN_LSB_REG 0x7C +#define DS2780_EEPROM_BLOCK1_END 0x7C +/* Register 0x7D - 0xFF Reserved */ + +/* Number of valid register addresses */ +#define DS2780_DATA_SIZE 0x80 + +/* Status register bits */ +#define DS2780_STATUS_REG_CHGTF (1 << 7) +#define DS2780_STATUS_REG_AEF (1 << 6) +#define DS2780_STATUS_REG_SEF (1 << 5) +#define DS2780_STATUS_REG_LEARNF (1 << 4) +/* Bit 3 Reserved */ +#define DS2780_STATUS_REG_UVF (1 << 2) +#define DS2780_STATUS_REG_PORF (1 << 1) +/* Bit 0 Reserved */ + +/* Control register bits */ +/* Bit 7 Reserved */ +#define DS2780_CONTROL_REG_UVEN (1 << 6) +#define DS2780_CONTROL_REG_PMOD (1 << 5) +#define DS2780_CONTROL_REG_RNAOP (1 << 4) +/* Bit 0 - 3 Reserved */ + +/* Special feature register bits */ +/* Bit 1 - 7 Reserved */ +#define DS2780_SFR_REG_PIOSC (1 << 0) + +/* EEPROM register bits */ +#define DS2780_EEPROM_REG_EEC (1 << 7) +#define DS2780_EEPROM_REG_LOCK (1 << 6) +/* Bit 2 - 6 Reserved */ +#define DS2780_EEPROM_REG_BL1 (1 << 1) +#define DS2780_EEPROM_REG_BL0 (1 << 0) + +extern int w1_ds2780_io(struct device *dev, char *buf, int addr, size_t count, + int io); +extern int w1_ds2780_eeprom_cmd(struct device *dev, int addr, int cmd); + +#endif /* !_W1_DS2780_H */ diff --git a/drivers/w1/slaves/w1_ds2781.c b/drivers/w1/slaves/w1_ds2781.c new file mode 100644 index 000000000..2cb7c020b --- /dev/null +++ b/drivers/w1/slaves/w1_ds2781.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 1-Wire implementation for the ds2781 chip + * + * Author: Renata Sayakhova <renata@oktetlabs.ru> + * + * Based on w1-ds2780 driver + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> + +#include <linux/w1.h> + +#include "w1_ds2781.h" + +#define W1_FAMILY_DS2781 0x3D + +static int w1_ds2781_do_io(struct device *dev, char *buf, int addr, + size_t count, int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (addr > DS2781_DATA_SIZE || addr < 0) + return 0; + + count = min_t(int, count, DS2781_DATA_SIZE - addr); + + if (w1_reset_select_slave(sl) == 0) { + if (io) { + w1_write_8(sl->master, W1_DS2781_WRITE_DATA); + w1_write_8(sl->master, addr); + w1_write_block(sl->master, buf, count); + } else { + w1_write_8(sl->master, W1_DS2781_READ_DATA); + w1_write_8(sl->master, addr); + count = w1_read_block(sl->master, buf, count); + } + } + + return count; +} + +int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count, + int io) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + int ret; + + if (!dev) + return -ENODEV; + + mutex_lock(&sl->master->bus_mutex); + + ret = w1_ds2781_do_io(dev, buf, addr, count, io); + + mutex_unlock(&sl->master->bus_mutex); + + return ret; +} +EXPORT_SYMBOL(w1_ds2781_io); + +int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd) +{ + struct w1_slave *sl = container_of(dev, struct w1_slave, dev); + + if (!dev) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl) == 0) { + w1_write_8(sl->master, cmd); + w1_write_8(sl->master, addr); + } + + mutex_unlock(&sl->master->bus_mutex); + return 0; +} +EXPORT_SYMBOL(w1_ds2781_eeprom_cmd); + +static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + return w1_ds2781_io(dev, buf, off, count, 0); +} + +static BIN_ATTR_RO(w1_slave, DS2781_DATA_SIZE); + +static struct bin_attribute *w1_ds2781_bin_attrs[] = { + &bin_attr_w1_slave, + NULL, +}; + +static const struct attribute_group w1_ds2781_group = { + .bin_attrs = w1_ds2781_bin_attrs, +}; + +static const struct attribute_group *w1_ds2781_groups[] = { + &w1_ds2781_group, + NULL, +}; + +static int w1_ds2781_add_slave(struct w1_slave *sl) +{ + int ret; + struct platform_device *pdev; + + pdev = platform_device_alloc("ds2781-battery", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + pdev->dev.parent = &sl->dev; + + ret = platform_device_add(pdev); + if (ret) + goto pdev_add_failed; + + dev_set_drvdata(&sl->dev, pdev); + + return 0; + +pdev_add_failed: + platform_device_put(pdev); + + return ret; +} + +static void w1_ds2781_remove_slave(struct w1_slave *sl) +{ + struct platform_device *pdev = dev_get_drvdata(&sl->dev); + + platform_device_unregister(pdev); +} + +static const struct w1_family_ops w1_ds2781_fops = { + .add_slave = w1_ds2781_add_slave, + .remove_slave = w1_ds2781_remove_slave, + .groups = w1_ds2781_groups, +}; + +static struct w1_family w1_ds2781_family = { + .fid = W1_FAMILY_DS2781, + .fops = &w1_ds2781_fops, +}; +module_w1_family(w1_ds2781_family); + +MODULE_AUTHOR("Renata Sayakhova <renata@oktetlabs.ru>"); +MODULE_DESCRIPTION("1-wire Driver for Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2781)); diff --git a/drivers/w1/slaves/w1_ds2781.h b/drivers/w1/slaves/w1_ds2781.h new file mode 100644 index 000000000..fa902dfa0 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2781.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * 1-Wire implementation for the ds2780 chip + * + * Author: Renata Sayakhova <renata@oktetlabs.ru> + * + * Based on w1-ds2760 driver + */ + +#ifndef _W1_DS2781_H +#define _W1_DS2781_H + +/* Function commands */ +#define W1_DS2781_READ_DATA 0x69 +#define W1_DS2781_WRITE_DATA 0x6C +#define W1_DS2781_COPY_DATA 0x48 +#define W1_DS2781_RECALL_DATA 0xB8 +#define W1_DS2781_LOCK 0x6A + +/* Register map */ +/* Register 0x00 Reserved */ +#define DS2781_STATUS 0x01 +#define DS2781_RAAC_MSB 0x02 +#define DS2781_RAAC_LSB 0x03 +#define DS2781_RSAC_MSB 0x04 +#define DS2781_RSAC_LSB 0x05 +#define DS2781_RARC 0x06 +#define DS2781_RSRC 0x07 +#define DS2781_IAVG_MSB 0x08 +#define DS2781_IAVG_LSB 0x09 +#define DS2781_TEMP_MSB 0x0A +#define DS2781_TEMP_LSB 0x0B +#define DS2781_VOLT_MSB 0x0C +#define DS2781_VOLT_LSB 0x0D +#define DS2781_CURRENT_MSB 0x0E +#define DS2781_CURRENT_LSB 0x0F +#define DS2781_ACR_MSB 0x10 +#define DS2781_ACR_LSB 0x11 +#define DS2781_ACRL_MSB 0x12 +#define DS2781_ACRL_LSB 0x13 +#define DS2781_AS 0x14 +#define DS2781_SFR 0x15 +#define DS2781_FULL_MSB 0x16 +#define DS2781_FULL_LSB 0x17 +#define DS2781_AE_MSB 0x18 +#define DS2781_AE_LSB 0x19 +#define DS2781_SE_MSB 0x1A +#define DS2781_SE_LSB 0x1B +/* Register 0x1C - 0x1E Reserved */ +#define DS2781_EEPROM 0x1F +#define DS2781_EEPROM_BLOCK0_START 0x20 +/* Register 0x20 - 0x2F User EEPROM */ +#define DS2781_EEPROM_BLOCK0_END 0x2F +/* Register 0x30 - 0x5F Reserved */ +#define DS2781_EEPROM_BLOCK1_START 0x60 +#define DS2781_CONTROL 0x60 +#define DS2781_AB 0x61 +#define DS2781_AC_MSB 0x62 +#define DS2781_AC_LSB 0x63 +#define DS2781_VCHG 0x64 +#define DS2781_IMIN 0x65 +#define DS2781_VAE 0x66 +#define DS2781_IAE 0x67 +#define DS2781_AE_40 0x68 +#define DS2781_RSNSP 0x69 +#define DS2781_FULL_40_MSB 0x6A +#define DS2781_FULL_40_LSB 0x6B +#define DS2781_FULL_4_SLOPE 0x6C +#define DS2781_FULL_3_SLOPE 0x6D +#define DS2781_FULL_2_SLOPE 0x6E +#define DS2781_FULL_1_SLOPE 0x6F +#define DS2781_AE_4_SLOPE 0x70 +#define DS2781_AE_3_SLOPE 0x71 +#define DS2781_AE_2_SLOPE 0x72 +#define DS2781_AE_1_SLOPE 0x73 +#define DS2781_SE_4_SLOPE 0x74 +#define DS2781_SE_3_SLOPE 0x75 +#define DS2781_SE_2_SLOPE 0x76 +#define DS2781_SE_1_SLOPE 0x77 +#define DS2781_RSGAIN_MSB 0x78 +#define DS2781_RSGAIN_LSB 0x79 +#define DS2781_RSTC 0x7A +#define DS2781_COB 0x7B +#define DS2781_TBP34 0x7C +#define DS2781_TBP23 0x7D +#define DS2781_TBP12 0x7E +#define DS2781_EEPROM_BLOCK1_END 0x7F +/* Register 0x7D - 0xFF Reserved */ + +#define DS2781_FSGAIN_MSB 0xB0 +#define DS2781_FSGAIN_LSB 0xB1 + +/* Number of valid register addresses */ +#define DS2781_DATA_SIZE 0xB2 + +/* Status register bits */ +#define DS2781_STATUS_CHGTF (1 << 7) +#define DS2781_STATUS_AEF (1 << 6) +#define DS2781_STATUS_SEF (1 << 5) +#define DS2781_STATUS_LEARNF (1 << 4) +/* Bit 3 Reserved */ +#define DS2781_STATUS_UVF (1 << 2) +#define DS2781_STATUS_PORF (1 << 1) +/* Bit 0 Reserved */ + +/* Control register bits */ +/* Bit 7 Reserved */ +#define DS2781_CONTROL_NBEN (1 << 7) +#define DS2781_CONTROL_UVEN (1 << 6) +#define DS2781_CONTROL_PMOD (1 << 5) +#define DS2781_CONTROL_RNAOP (1 << 4) +#define DS1781_CONTROL_UVTH (1 << 3) +/* Bit 0 - 2 Reserved */ + +/* Special feature register bits */ +/* Bit 1 - 7 Reserved */ +#define DS2781_SFR_PIOSC (1 << 0) + +/* EEPROM register bits */ +#define DS2781_EEPROM_EEC (1 << 7) +#define DS2781_EEPROM_LOCK (1 << 6) +/* Bit 2 - 6 Reserved */ +#define DS2781_EEPROM_BL1 (1 << 1) +#define DS2781_EEPROM_BL0 (1 << 0) + +extern int w1_ds2781_io(struct device *dev, char *buf, int addr, size_t count, + int io); +extern int w1_ds2781_eeprom_cmd(struct device *dev, int addr, int cmd); + +#endif /* !_W1_DS2781_H */ diff --git a/drivers/w1/slaves/w1_ds2805.c b/drivers/w1/slaves/w1_ds2805.c new file mode 100644 index 000000000..6b5d12ba1 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2805.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds2805 - w1 family 0d (DS28E05) driver + * + * Copyright (c) 2016 Andrew Worsley amworsley@gmail.com + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> + +#include <linux/w1.h> + +#define W1_EEPROM_DS2805 0x0D + +#define W1_F0D_EEPROM_SIZE 128 +#define W1_F0D_PAGE_BITS 3 +#define W1_F0D_PAGE_SIZE (1<<W1_F0D_PAGE_BITS) +#define W1_F0D_PAGE_MASK 0x0F + +#define W1_F0D_SCRATCH_BITS 1 +#define W1_F0D_SCRATCH_SIZE (1<<W1_F0D_SCRATCH_BITS) +#define W1_F0D_SCRATCH_MASK (W1_F0D_SCRATCH_SIZE-1) + +#define W1_F0D_READ_EEPROM 0xF0 +#define W1_F0D_WRITE_EEPROM 0x55 +#define W1_F0D_RELEASE 0xFF + +#define W1_F0D_CS_OK 0xAA /* Chip Status Ok */ + +#define W1_F0D_TPROG_MS 16 + +#define W1_F0D_READ_RETRIES 10 +#define W1_F0D_READ_MAXLEN W1_F0D_EEPROM_SIZE + +/* + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f0d_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +/* + * Read a block from W1 ROM two times and compares the results. + * If they are equal they are returned, otherwise the read + * is repeated W1_F0D_READ_RETRIES times. + * + * count must not exceed W1_F0D_READ_MAXLEN. + */ +static int w1_f0d_readblock(struct w1_slave *sl, int off, int count, char *buf) +{ + u8 wrbuf[3]; + u8 cmp[W1_F0D_READ_MAXLEN]; + int tries = W1_F0D_READ_RETRIES; + + do { + wrbuf[0] = W1_F0D_READ_EEPROM; + wrbuf[1] = off & 0x7f; + wrbuf[2] = 0; + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + w1_read_block(sl->master, buf, count); + + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + w1_read_block(sl->master, cmp, count); + + if (!memcmp(cmp, buf, count)) + return 0; + } while (--tries); + + dev_err(&sl->dev, "proof reading failed %d times\n", + W1_F0D_READ_RETRIES); + + return -1; +} + +static ssize_t w1_f0d_read_bin(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int todo = count; + + count = w1_f0d_fix_count(off, count, W1_F0D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + /* read directly from the EEPROM in chunks of W1_F0D_READ_MAXLEN */ + while (todo > 0) { + int block_read; + + if (todo >= W1_F0D_READ_MAXLEN) + block_read = W1_F0D_READ_MAXLEN; + else + block_read = todo; + + if (w1_f0d_readblock(sl, off, block_read, buf) < 0) { + count = -EIO; + break; + } + + todo -= W1_F0D_READ_MAXLEN; + buf += W1_F0D_READ_MAXLEN; + off += W1_F0D_READ_MAXLEN; + } + + mutex_unlock(&sl->master->mutex); + + return count; +} + +/* + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be aligned at W1_F0D_SCRATCH_SIZE bytes and + * must be W1_F0D_SCRATCH_SIZE bytes long. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_F0D_PAGE_SIZE - (addr & W1_F0D_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f0d_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + int tries = W1_F0D_READ_RETRIES; + u8 wrbuf[3]; + u8 rdbuf[W1_F0D_SCRATCH_SIZE]; + u8 cs; + + if ((addr & 1) || (len != 2)) { + dev_err(&sl->dev, "%s: bad addr/len - addr=%#x len=%d\n", + __func__, addr, len); + return -1; + } + +retry: + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F0D_WRITE_EEPROM; + wrbuf[1] = addr & 0xff; + wrbuf[2] = 0xff; /* ?? from Example */ + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + w1_write_block(sl->master, data, len); + + w1_read_block(sl->master, rdbuf, sizeof(rdbuf)); + /* Compare what was read against the data written */ + if ((rdbuf[0] != data[0]) || (rdbuf[1] != data[1])) { + + if (--tries) + goto retry; + + dev_err(&sl->dev, + "could not write to eeprom, scratchpad compare failed %d times\n", + W1_F0D_READ_RETRIES); + pr_info("%s: rdbuf = %#x %#x data = %#x %#x\n", + __func__, rdbuf[0], rdbuf[1], data[0], data[1]); + + return -1; + } + + /* Trigger write out to EEPROM */ + w1_write_8(sl->master, W1_F0D_RELEASE); + + /* Sleep for tprog ms to wait for the write to complete */ + msleep(W1_F0D_TPROG_MS); + + /* Check CS (Command Status) == 0xAA ? */ + cs = w1_read_8(sl->master); + if (cs != W1_F0D_CS_OK) { + dev_err(&sl->dev, "save to eeprom failed = CS=%#x\n", cs); + return -1; + } + + return 0; +} + +static ssize_t w1_f0d_write_bin(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr, len; + int copy; + + count = w1_f0d_fix_count(off, count, W1_F0D_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + /* Can only write data in blocks of the size of the scratchpad */ + addr = off; + len = count; + while (len > 0) { + + /* if len too short or addr not aligned */ + if (len < W1_F0D_SCRATCH_SIZE || addr & W1_F0D_SCRATCH_MASK) { + char tmp[W1_F0D_SCRATCH_SIZE]; + + /* read the block and update the parts to be written */ + if (w1_f0d_readblock(sl, addr & ~W1_F0D_SCRATCH_MASK, + W1_F0D_SCRATCH_SIZE, tmp)) { + count = -EIO; + goto out_up; + } + + /* copy at most to the boundary of the PAGE or len */ + copy = W1_F0D_SCRATCH_SIZE - + (addr & W1_F0D_SCRATCH_MASK); + + if (copy > len) + copy = len; + + memcpy(&tmp[addr & W1_F0D_SCRATCH_MASK], buf, copy); + if (w1_f0d_write(sl, addr & ~W1_F0D_SCRATCH_MASK, + W1_F0D_SCRATCH_SIZE, tmp) < 0) { + count = -EIO; + goto out_up; + } + } else { + + copy = W1_F0D_SCRATCH_SIZE; + if (w1_f0d_write(sl, addr, copy, buf) < 0) { + count = -EIO; + goto out_up; + } + } + buf += copy; + addr += copy; + len -= copy; + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static struct bin_attribute w1_f0d_bin_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUSR, + }, + .size = W1_F0D_EEPROM_SIZE, + .read = w1_f0d_read_bin, + .write = w1_f0d_write_bin, +}; + +static int w1_f0d_add_slave(struct w1_slave *sl) +{ + return sysfs_create_bin_file(&sl->dev.kobj, &w1_f0d_bin_attr); +} + +static void w1_f0d_remove_slave(struct w1_slave *sl) +{ + sysfs_remove_bin_file(&sl->dev.kobj, &w1_f0d_bin_attr); +} + +static const struct w1_family_ops w1_f0d_fops = { + .add_slave = w1_f0d_add_slave, + .remove_slave = w1_f0d_remove_slave, +}; + +static struct w1_family w1_family_0d = { + .fid = W1_EEPROM_DS2805, + .fops = &w1_f0d_fops, +}; + +module_w1_family(w1_family_0d); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrew Worsley amworsley@gmail.com"); +MODULE_DESCRIPTION("w1 family 0d driver for DS2805, 1kb EEPROM"); diff --git a/drivers/w1/slaves/w1_ds28e04.c b/drivers/w1/slaves/w1_ds28e04.c new file mode 100644 index 000000000..6cef6e2ed --- /dev/null +++ b/drivers/w1/slaves/w1_ds28e04.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds28e04.c - w1 family 1C (DS28E04) driver + * + * Copyright (c) 2012 Markus Franke <franke.m@sebakmt.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/crc16.h> +#include <linux/uaccess.h> + +#define CRC16_INIT 0 +#define CRC16_VALID 0xb001 + +#include <linux/w1.h> + +#define W1_FAMILY_DS28E04 0x1C + +/* Allow the strong pullup to be disabled, but default to enabled. + * If it was disabled a parasite powered device might not get the required + * current to copy the data from the scratchpad to EEPROM. If it is enabled + * parasite powered devices have a better chance of getting the current + * required. + */ +static int w1_strong_pullup = 1; +module_param_named(strong_pullup, w1_strong_pullup, int, 0); + +/* enable/disable CRC checking on DS28E04-100 memory accesses */ +static bool w1_enable_crccheck = true; + +#define W1_EEPROM_SIZE 512 +#define W1_PAGE_COUNT 16 +#define W1_PAGE_SIZE 32 +#define W1_PAGE_BITS 5 +#define W1_PAGE_MASK 0x1F + +#define W1_F1C_READ_EEPROM 0xF0 +#define W1_F1C_WRITE_SCRATCH 0x0F +#define W1_F1C_READ_SCRATCH 0xAA +#define W1_F1C_COPY_SCRATCH 0x55 +#define W1_F1C_ACCESS_WRITE 0x5A + +#define W1_1C_REG_LOGIC_STATE 0x220 + +struct w1_f1C_data { + u8 memory[W1_EEPROM_SIZE]; + u32 validcrc; +}; + +/** + * Check the file size bounds and adjusts count as needed. + * This would not be needed if the file size didn't reset to 0 after a write. + */ +static inline size_t w1_f1C_fix_count(loff_t off, size_t count, size_t size) +{ + if (off > size) + return 0; + + if ((off + count) > size) + return size - off; + + return count; +} + +static int w1_f1C_refresh_block(struct w1_slave *sl, struct w1_f1C_data *data, + int block) +{ + u8 wrbuf[3]; + int off = block * W1_PAGE_SIZE; + + if (data->validcrc & (1 << block)) + return 0; + + if (w1_reset_select_slave(sl)) { + data->validcrc = 0; + return -EIO; + } + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = off & 0xff; + wrbuf[2] = off >> 8; + w1_write_block(sl->master, wrbuf, 3); + w1_read_block(sl->master, &data->memory[off], W1_PAGE_SIZE); + + /* cache the block if the CRC is valid */ + if (crc16(CRC16_INIT, &data->memory[off], W1_PAGE_SIZE) == CRC16_VALID) + data->validcrc |= (1 << block); + + return 0; +} + +static int w1_f1C_read(struct w1_slave *sl, int addr, int len, char *data) +{ + u8 wrbuf[3]; + + /* read directly from the EEPROM */ + if (w1_reset_select_slave(sl)) + return -EIO; + + wrbuf[0] = W1_F1C_READ_EEPROM; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, sizeof(wrbuf)); + return w1_read_block(sl->master, data, len); +} + +static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + struct w1_f1C_data *data = sl->family_data; + int i, min_page, max_page; + + count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE); + if (count == 0) + return 0; + + mutex_lock(&sl->master->mutex); + + if (w1_enable_crccheck) { + min_page = (off >> W1_PAGE_BITS); + max_page = (off + count - 1) >> W1_PAGE_BITS; + for (i = min_page; i <= max_page; i++) { + if (w1_f1C_refresh_block(sl, data, i)) { + count = -EIO; + goto out_up; + } + } + memcpy(buf, &data->memory[off], count); + } else { + count = w1_f1C_read(sl, off, count, buf); + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +/** + * Writes to the scratchpad and reads it back for verification. + * Then copies the scratchpad to EEPROM. + * The data must be on one page. + * The master must be locked. + * + * @param sl The slave structure + * @param addr Address for the write + * @param len length must be <= (W1_PAGE_SIZE - (addr & W1_PAGE_MASK)) + * @param data The data to write + * @return 0=Success -1=failure + */ +static int w1_f1C_write(struct w1_slave *sl, int addr, int len, const u8 *data) +{ + u8 wrbuf[4]; + u8 rdbuf[W1_PAGE_SIZE + 3]; + u8 es = (addr + len - 1) & 0x1f; + unsigned int tm = 10; + int i; + struct w1_f1C_data *f1C = sl->family_data; + + /* Write the data to the scratchpad */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_WRITE_SCRATCH; + wrbuf[1] = addr & 0xff; + wrbuf[2] = addr >> 8; + + w1_write_block(sl->master, wrbuf, 3); + w1_write_block(sl->master, data, len); + + /* Read the scratchpad and verify */ + if (w1_reset_select_slave(sl)) + return -1; + + w1_write_8(sl->master, W1_F1C_READ_SCRATCH); + w1_read_block(sl->master, rdbuf, len + 3); + + /* Compare what was read against the data written */ + if ((rdbuf[0] != wrbuf[1]) || (rdbuf[1] != wrbuf[2]) || + (rdbuf[2] != es) || (memcmp(data, &rdbuf[3], len) != 0)) + return -1; + + /* Copy the scratchpad to EEPROM */ + if (w1_reset_select_slave(sl)) + return -1; + + wrbuf[0] = W1_F1C_COPY_SCRATCH; + wrbuf[3] = es; + + for (i = 0; i < sizeof(wrbuf); ++i) { + /* issue 10ms strong pullup (or delay) on the last byte + for writing the data from the scratchpad to EEPROM */ + if (w1_strong_pullup && i == sizeof(wrbuf)-1) + w1_next_pullup(sl->master, tm); + + w1_write_8(sl->master, wrbuf[i]); + } + + if (!w1_strong_pullup) + msleep(tm); + + if (w1_enable_crccheck) { + /* invalidate cached data */ + f1C->validcrc &= ~(1 << (addr >> W1_PAGE_BITS)); + } + + /* Reset the bus to wake up the EEPROM (this may not be needed) */ + w1_reset_bus(sl->master); + + return 0; +} + +static ssize_t eeprom_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int addr, len, idx; + + count = w1_f1C_fix_count(off, count, W1_EEPROM_SIZE); + if (count == 0) + return 0; + + if (w1_enable_crccheck) { + /* can only write full blocks in cached mode */ + if ((off & W1_PAGE_MASK) || (count & W1_PAGE_MASK)) { + dev_err(&sl->dev, "invalid offset/count off=%d cnt=%zd\n", + (int)off, count); + return -EINVAL; + } + + /* make sure the block CRCs are valid */ + for (idx = 0; idx < count; idx += W1_PAGE_SIZE) { + if (crc16(CRC16_INIT, &buf[idx], W1_PAGE_SIZE) + != CRC16_VALID) { + dev_err(&sl->dev, "bad CRC at offset %d\n", + (int)off); + return -EINVAL; + } + } + } + + mutex_lock(&sl->master->mutex); + + /* Can only write data to one page at a time */ + idx = 0; + while (idx < count) { + addr = off + idx; + len = W1_PAGE_SIZE - (addr & W1_PAGE_MASK); + if (len > (count - idx)) + len = count - idx; + + if (w1_f1C_write(sl, addr, len, &buf[idx]) < 0) { + count = -EIO; + goto out_up; + } + idx += len; + } + +out_up: + mutex_unlock(&sl->master->mutex); + + return count; +} + +static BIN_ATTR_RW(eeprom, W1_EEPROM_SIZE); + +static ssize_t pio_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, + size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + int ret; + + /* check arguments */ + if (off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + ret = w1_f1C_read(sl, W1_1C_REG_LOGIC_STATE, count, buf); + mutex_unlock(&sl->master->mutex); + + return ret; +} + +static ssize_t pio_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, char *buf, loff_t off, + size_t count) + +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 wrbuf[3]; + u8 ack; + + /* check arguments */ + if (off != 0 || count != 1 || buf == NULL) + return -EINVAL; + + mutex_lock(&sl->master->mutex); + + /* Write the PIO data */ + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->mutex); + return -1; + } + + /* set bit 7..2 to value '1' */ + *buf = *buf | 0xFC; + + wrbuf[0] = W1_F1C_ACCESS_WRITE; + wrbuf[1] = *buf; + wrbuf[2] = ~(*buf); + w1_write_block(sl->master, wrbuf, 3); + + w1_read_block(sl->master, &ack, sizeof(ack)); + + mutex_unlock(&sl->master->mutex); + + /* check for acknowledgement */ + if (ack != 0xAA) + return -EIO; + + return count; +} + +static BIN_ATTR_RW(pio, 1); + +static ssize_t crccheck_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", w1_enable_crccheck); +} + +static ssize_t crccheck_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err = kstrtobool(buf, &w1_enable_crccheck); + + if (err) + return err; + + return count; +} + +static DEVICE_ATTR_RW(crccheck); + +static struct attribute *w1_f1C_attrs[] = { + &dev_attr_crccheck.attr, + NULL, +}; + +static struct bin_attribute *w1_f1C_bin_attrs[] = { + &bin_attr_eeprom, + &bin_attr_pio, + NULL, +}; + +static const struct attribute_group w1_f1C_group = { + .attrs = w1_f1C_attrs, + .bin_attrs = w1_f1C_bin_attrs, +}; + +static const struct attribute_group *w1_f1C_groups[] = { + &w1_f1C_group, + NULL, +}; + +static int w1_f1C_add_slave(struct w1_slave *sl) +{ + struct w1_f1C_data *data = NULL; + + if (w1_enable_crccheck) { + data = kzalloc(sizeof(struct w1_f1C_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + } + + return 0; +} + +static void w1_f1C_remove_slave(struct w1_slave *sl) +{ + kfree(sl->family_data); + sl->family_data = NULL; +} + +static const struct w1_family_ops w1_f1C_fops = { + .add_slave = w1_f1C_add_slave, + .remove_slave = w1_f1C_remove_slave, + .groups = w1_f1C_groups, +}; + +static struct w1_family w1_family_1C = { + .fid = W1_FAMILY_DS28E04, + .fops = &w1_f1C_fops, +}; +module_w1_family(w1_family_1C); + +MODULE_AUTHOR("Markus Franke <franke.m@sebakmt.com>, <franm@hrz.tu-chemnitz.de>"); +MODULE_DESCRIPTION("w1 family 1C driver for DS28E04, 4kb EEPROM and PIO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS28E04)); diff --git a/drivers/w1/slaves/w1_ds28e17.c b/drivers/w1/slaves/w1_ds28e17.c new file mode 100644 index 000000000..aed10b72f --- /dev/null +++ b/drivers/w1/slaves/w1_ds28e17.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * w1_ds28e17.c - w1 family 19 (DS28E17) driver + * + * Copyright (c) 2016 Jan Kandziora <jjj@gmx.de> + */ + +#include <linux/crc16.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#define CRC16_INIT 0 + +#include <linux/w1.h> + +#define W1_FAMILY_DS28E17 0x19 + +/* Module setup. */ +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jan Kandziora <jjj@gmx.de>"); +MODULE_DESCRIPTION("w1 family 19 driver for DS28E17, 1-wire to I2C master bridge"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS28E17)); + + +/* Default I2C speed to be set when a DS28E17 is detected. */ +static int i2c_speed = 100; +module_param_named(speed, i2c_speed, int, (S_IRUSR | S_IWUSR)); +MODULE_PARM_DESC(speed, "Default I2C speed to be set when a DS28E17 is detected"); + +/* Default I2C stretch value to be set when a DS28E17 is detected. */ +static char i2c_stretch = 1; +module_param_named(stretch, i2c_stretch, byte, (S_IRUSR | S_IWUSR)); +MODULE_PARM_DESC(stretch, "Default I2C stretch value to be set when a DS28E17 is detected"); + +/* DS28E17 device command codes. */ +#define W1_F19_WRITE_DATA_WITH_STOP 0x4B +#define W1_F19_WRITE_DATA_NO_STOP 0x5A +#define W1_F19_WRITE_DATA_ONLY 0x69 +#define W1_F19_WRITE_DATA_ONLY_WITH_STOP 0x78 +#define W1_F19_READ_DATA_WITH_STOP 0x87 +#define W1_F19_WRITE_READ_DATA_WITH_STOP 0x2D +#define W1_F19_WRITE_CONFIGURATION 0xD2 +#define W1_F19_READ_CONFIGURATION 0xE1 +#define W1_F19_ENABLE_SLEEP_MODE 0x1E +#define W1_F19_READ_DEVICE_REVISION 0xC4 + +/* DS28E17 status bits */ +#define W1_F19_STATUS_CRC 0x01 +#define W1_F19_STATUS_ADDRESS 0x02 +#define W1_F19_STATUS_START 0x08 + +/* + * Maximum number of I2C bytes to transfer within one CRC16 protected onewire + * command. + * */ +#define W1_F19_WRITE_DATA_LIMIT 255 + +/* Maximum number of I2C bytes to read with one onewire command. */ +#define W1_F19_READ_DATA_LIMIT 255 + +/* Constants for calculating the busy sleep. */ +#define W1_F19_BUSY_TIMEBASES { 90, 23, 10 } +#define W1_F19_BUSY_GRATUITY 1000 + +/* Number of checks for the busy flag before timeout. */ +#define W1_F19_BUSY_CHECKS 1000 + + +/* Slave specific data. */ +struct w1_f19_data { + u8 speed; + u8 stretch; + struct i2c_adapter adapter; +}; + + +/* Wait a while until the busy flag clears. */ +static int w1_f19_i2c_busy_wait(struct w1_slave *sl, size_t count) +{ + const unsigned long timebases[3] = W1_F19_BUSY_TIMEBASES; + struct w1_f19_data *data = sl->family_data; + unsigned int checks; + + /* Check the busy flag first in any case.*/ + if (w1_touch_bit(sl->master, 1) == 0) + return 0; + + /* + * Do a generously long sleep in the beginning, + * as we have to wait at least this time for all + * the I2C bytes at the given speed to be transferred. + */ + usleep_range(timebases[data->speed] * (data->stretch) * count, + timebases[data->speed] * (data->stretch) * count + + W1_F19_BUSY_GRATUITY); + + /* Now continusly check the busy flag sent by the DS28E17. */ + checks = W1_F19_BUSY_CHECKS; + while ((checks--) > 0) { + /* Return success if the busy flag is cleared. */ + if (w1_touch_bit(sl->master, 1) == 0) + return 0; + + /* Wait one non-streched byte timeslot. */ + udelay(timebases[data->speed]); + } + + /* Timeout. */ + dev_warn(&sl->dev, "busy timeout\n"); + return -ETIMEDOUT; +} + + +/* Utility function: result. */ +static size_t w1_f19_error(struct w1_slave *sl, u8 w1_buf[]) +{ + /* Warnings. */ + if (w1_buf[0] & W1_F19_STATUS_CRC) + dev_warn(&sl->dev, "crc16 mismatch\n"); + if (w1_buf[0] & W1_F19_STATUS_ADDRESS) + dev_warn(&sl->dev, "i2c device not responding\n"); + if ((w1_buf[0] & (W1_F19_STATUS_CRC | W1_F19_STATUS_ADDRESS)) == 0 + && w1_buf[1] != 0) { + dev_warn(&sl->dev, "i2c short write, %d bytes not acknowledged\n", + w1_buf[1]); + } + + /* Check error conditions. */ + if (w1_buf[0] & W1_F19_STATUS_ADDRESS) + return -ENXIO; + if (w1_buf[0] & W1_F19_STATUS_START) + return -EAGAIN; + if (w1_buf[0] != 0 || w1_buf[1] != 0) + return -EIO; + + /* All ok. */ + return 0; +} + + +/* Utility function: write data to I2C slave, single chunk. */ +static int __w1_f19_i2c_write(struct w1_slave *sl, + const u8 *command, size_t command_count, + const u8 *buffer, size_t count) +{ + u16 crc; + int error; + u8 w1_buf[2]; + + /* Send command and I2C data to DS28E17. */ + crc = crc16(CRC16_INIT, command, command_count); + w1_write_block(sl->master, command, command_count); + + w1_buf[0] = count; + crc = crc16(crc, w1_buf, 1); + w1_write_8(sl->master, w1_buf[0]); + + crc = crc16(crc, buffer, count); + w1_write_block(sl->master, buffer, count); + + w1_buf[0] = ~(crc & 0xFF); + w1_buf[1] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 2); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, count + 1) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_read_block(sl->master, w1_buf, 2); + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Return number of bytes written. */ + return count; +} + + +/* Write data to I2C slave. */ +static int w1_f19_i2c_write(struct w1_slave *sl, u16 i2c_address, + const u8 *buffer, size_t count, bool stop) +{ + int result; + int remaining = count; + const u8 *p; + u8 command[2]; + + /* Check input. */ + if (count == 0) + return -EOPNOTSUPP; + + /* Check whether we need multiple commands. */ + if (count <= W1_F19_WRITE_DATA_LIMIT) { + /* + * Small data amount. Data can be sent with + * a single onewire command. + */ + + /* Send all data to DS28E17. */ + command[0] = (stop ? W1_F19_WRITE_DATA_WITH_STOP + : W1_F19_WRITE_DATA_NO_STOP); + command[1] = i2c_address << 1; + result = __w1_f19_i2c_write(sl, command, 2, buffer, count); + } else { + /* Large data amount. Data has to be sent in multiple chunks. */ + + /* Send first chunk to DS28E17. */ + p = buffer; + command[0] = W1_F19_WRITE_DATA_NO_STOP; + command[1] = i2c_address << 1; + result = __w1_f19_i2c_write(sl, command, 2, p, + W1_F19_WRITE_DATA_LIMIT); + if (result < 0) + return result; + + /* Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) + return -EIO; + + /* Next data chunk. */ + p += W1_F19_WRITE_DATA_LIMIT; + remaining -= W1_F19_WRITE_DATA_LIMIT; + + while (remaining > W1_F19_WRITE_DATA_LIMIT) { + /* Send intermediate chunk to DS28E17. */ + command[0] = W1_F19_WRITE_DATA_ONLY; + result = __w1_f19_i2c_write(sl, command, 1, p, + W1_F19_WRITE_DATA_LIMIT); + if (result < 0) + return result; + + /* Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) + return -EIO; + + /* Next data chunk. */ + p += W1_F19_WRITE_DATA_LIMIT; + remaining -= W1_F19_WRITE_DATA_LIMIT; + } + + /* Send final chunk to DS28E17. */ + command[0] = (stop ? W1_F19_WRITE_DATA_ONLY_WITH_STOP + : W1_F19_WRITE_DATA_ONLY); + result = __w1_f19_i2c_write(sl, command, 1, p, remaining); + } + + return result; +} + + +/* Read data from I2C slave. */ +static int w1_f19_i2c_read(struct w1_slave *sl, u16 i2c_address, + u8 *buffer, size_t count) +{ + u16 crc; + int error; + u8 w1_buf[5]; + + /* Check input. */ + if (count == 0) + return -EOPNOTSUPP; + + /* Send command to DS28E17. */ + w1_buf[0] = W1_F19_READ_DATA_WITH_STOP; + w1_buf[1] = i2c_address << 1 | 0x01; + w1_buf[2] = count; + crc = crc16(CRC16_INIT, w1_buf, 3); + w1_buf[3] = ~(crc & 0xFF); + w1_buf[4] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 5); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, count + 1) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_buf[0] = w1_read_8(sl->master); + w1_buf[1] = 0; + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Read received I2C data from DS28E17. */ + return w1_read_block(sl->master, buffer, count); +} + + +/* Write to, then read data from I2C slave. */ +static int w1_f19_i2c_write_read(struct w1_slave *sl, u16 i2c_address, + const u8 *wbuffer, size_t wcount, u8 *rbuffer, size_t rcount) +{ + u16 crc; + int error; + u8 w1_buf[3]; + + /* Check input. */ + if (wcount == 0 || rcount == 0) + return -EOPNOTSUPP; + + /* Send command and I2C data to DS28E17. */ + w1_buf[0] = W1_F19_WRITE_READ_DATA_WITH_STOP; + w1_buf[1] = i2c_address << 1; + w1_buf[2] = wcount; + crc = crc16(CRC16_INIT, w1_buf, 3); + w1_write_block(sl->master, w1_buf, 3); + + crc = crc16(crc, wbuffer, wcount); + w1_write_block(sl->master, wbuffer, wcount); + + w1_buf[0] = rcount; + crc = crc16(crc, w1_buf, 1); + w1_buf[1] = ~(crc & 0xFF); + w1_buf[2] = ~((crc >> 8) & 0xFF); + w1_write_block(sl->master, w1_buf, 3); + + /* Wait until busy flag clears (or timeout). */ + if (w1_f19_i2c_busy_wait(sl, wcount + rcount + 2) < 0) + return -ETIMEDOUT; + + /* Read status from DS28E17. */ + w1_read_block(sl->master, w1_buf, 2); + + /* Check error conditions. */ + error = w1_f19_error(sl, w1_buf); + if (error < 0) + return error; + + /* Read received I2C data from DS28E17. */ + return w1_read_block(sl->master, rbuffer, rcount); +} + + +/* Do an I2C master transfer. */ +static int w1_f19_i2c_master_transfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct w1_slave *sl = (struct w1_slave *) adapter->algo_data; + int i = 0; + int result = 0; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Select DS28E17. */ + if (w1_reset_select_slave(sl)) { + i = -EIO; + goto error; + } + + /* Loop while there are still messages to transfer. */ + while (i < num) { + /* + * Check for special case: Small write followed + * by read to same I2C device. + */ + if (i < (num-1) + && msgs[i].addr == msgs[i+1].addr + && !(msgs[i].flags & I2C_M_RD) + && (msgs[i+1].flags & I2C_M_RD) + && (msgs[i].len <= W1_F19_WRITE_DATA_LIMIT)) { + /* + * The DS28E17 has a combined transfer + * for small write+read. + */ + result = w1_f19_i2c_write_read(sl, msgs[i].addr, + msgs[i].buf, msgs[i].len, + msgs[i+1].buf, msgs[i+1].len); + if (result < 0) { + i = result; + goto error; + } + + /* + * Check if we should interpret the read data + * as a length byte. The DS28E17 unfortunately + * has no read without stop, so we can just do + * another simple read in that case. + */ + if (msgs[i+1].flags & I2C_M_RECV_LEN) { + result = w1_f19_i2c_read(sl, msgs[i+1].addr, + &(msgs[i+1].buf[1]), msgs[i+1].buf[0]); + if (result < 0) { + i = result; + goto error; + } + } + + /* Eat up read message, too. */ + i++; + } else if (msgs[i].flags & I2C_M_RD) { + /* Read transfer. */ + result = w1_f19_i2c_read(sl, msgs[i].addr, + msgs[i].buf, msgs[i].len); + if (result < 0) { + i = result; + goto error; + } + + /* + * Check if we should interpret the read data + * as a length byte. The DS28E17 unfortunately + * has no read without stop, so we can just do + * another simple read in that case. + */ + if (msgs[i].flags & I2C_M_RECV_LEN) { + result = w1_f19_i2c_read(sl, + msgs[i].addr, + &(msgs[i].buf[1]), + msgs[i].buf[0]); + if (result < 0) { + i = result; + goto error; + } + } + } else { + /* + * Write transfer. + * Stop condition only for last + * transfer. + */ + result = w1_f19_i2c_write(sl, + msgs[i].addr, + msgs[i].buf, + msgs[i].len, + i == (num-1)); + if (result < 0) { + i = result; + goto error; + } + } + + /* Next message. */ + i++; + + /* Are there still messages to send/receive? */ + if (i < num) { + /* Yes. Resume to same DS28E17. */ + if (w1_reset_resume_command(sl->master)) { + i = -EIO; + goto error; + } + } + } + +error: + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + /* Return number of messages processed or error. */ + return i; +} + + +/* Get I2C adapter functionality. */ +static u32 w1_f19_i2c_functionality(struct i2c_adapter *adapter) +{ + /* + * Plain I2C functions only. + * SMBus is emulated by the kernel's I2C layer. + * No "I2C_FUNC_SMBUS_QUICK" + * No "I2C_FUNC_SMBUS_READ_BLOCK_DATA" + * No "I2C_FUNC_SMBUS_BLOCK_PROC_CALL" + */ + return I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_PROC_CALL | + I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK | + I2C_FUNC_SMBUS_PEC; +} + + +/* I2C adapter quirks. */ +static const struct i2c_adapter_quirks w1_f19_i2c_adapter_quirks = { + .max_read_len = W1_F19_READ_DATA_LIMIT, +}; + +/* I2C algorithm. */ +static const struct i2c_algorithm w1_f19_i2c_algorithm = { + .master_xfer = w1_f19_i2c_master_transfer, + .functionality = w1_f19_i2c_functionality, +}; + + +/* Read I2C speed from DS28E17. */ +static int w1_f19_get_i2c_speed(struct w1_slave *sl) +{ + struct w1_f19_data *data = sl->family_data; + int result = -EIO; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Select slave. */ + if (w1_reset_select_slave(sl)) + goto error; + + /* Read slave configuration byte. */ + w1_write_8(sl->master, W1_F19_READ_CONFIGURATION); + result = w1_read_8(sl->master); + if (result < 0 || result > 2) { + result = -EIO; + goto error; + } + + /* Update speed in slave specific data. */ + data->speed = result; + +error: + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + return result; +} + + +/* Set I2C speed on DS28E17. */ +static int __w1_f19_set_i2c_speed(struct w1_slave *sl, u8 speed) +{ + struct w1_f19_data *data = sl->family_data; + const int i2c_speeds[3] = { 100, 400, 900 }; + u8 w1_buf[2]; + + /* Select slave. */ + if (w1_reset_select_slave(sl)) + return -EIO; + + w1_buf[0] = W1_F19_WRITE_CONFIGURATION; + w1_buf[1] = speed; + w1_write_block(sl->master, w1_buf, 2); + + /* Update speed in slave specific data. */ + data->speed = speed; + + dev_info(&sl->dev, "i2c speed set to %d kBaud\n", i2c_speeds[speed]); + + return 0; +} + +static int w1_f19_set_i2c_speed(struct w1_slave *sl, u8 speed) +{ + int result; + + /* Start onewire transaction. */ + mutex_lock(&sl->master->bus_mutex); + + /* Set I2C speed on DS28E17. */ + result = __w1_f19_set_i2c_speed(sl, speed); + + /* End onewire transaction. */ + mutex_unlock(&sl->master->bus_mutex); + + return result; +} + + +/* Sysfs attributes. */ + +/* I2C speed attribute for a single chip. */ +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + int result; + + /* Read current speed from slave. Updates data->speed. */ + result = w1_f19_get_i2c_speed(sl); + if (result < 0) + return result; + + /* Return current speed value. */ + return sprintf(buf, "%d\n", result); +} + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + int error; + + /* Valid values are: "100", "400", "900" */ + if (count < 3 || count > 4 || !buf) + return -EINVAL; + if (count == 4 && buf[3] != '\n') + return -EINVAL; + if (buf[1] != '0' || buf[2] != '0') + return -EINVAL; + + /* Set speed on slave. */ + switch (buf[0]) { + case '1': + error = w1_f19_set_i2c_speed(sl, 0); + break; + case '4': + error = w1_f19_set_i2c_speed(sl, 1); + break; + case '9': + error = w1_f19_set_i2c_speed(sl, 2); + break; + default: + return -EINVAL; + } + + if (error < 0) + return error; + + /* Return bytes written. */ + return count; +} + +static DEVICE_ATTR_RW(speed); + + +/* Busy stretch attribute for a single chip. */ +static ssize_t stretch_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + struct w1_f19_data *data = sl->family_data; + + /* Return current stretch value. */ + return sprintf(buf, "%d\n", data->stretch); +} + +static ssize_t stretch_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct w1_slave *sl = dev_to_w1_slave(dev); + struct w1_f19_data *data = sl->family_data; + + /* Valid values are '1' to '9' */ + if (count < 1 || count > 2 || !buf) + return -EINVAL; + if (count == 2 && buf[1] != '\n') + return -EINVAL; + if (buf[0] < '1' || buf[0] > '9') + return -EINVAL; + + /* Set busy stretch value. */ + data->stretch = buf[0] & 0x0F; + + /* Return bytes written. */ + return count; +} + +static DEVICE_ATTR_RW(stretch); + + +/* All attributes. */ +static struct attribute *w1_f19_attrs[] = { + &dev_attr_speed.attr, + &dev_attr_stretch.attr, + NULL, +}; + +static const struct attribute_group w1_f19_group = { + .attrs = w1_f19_attrs, +}; + +static const struct attribute_group *w1_f19_groups[] = { + &w1_f19_group, + NULL, +}; + + +/* Slave add and remove functions. */ +static int w1_f19_add_slave(struct w1_slave *sl) +{ + struct w1_f19_data *data = NULL; + + /* Allocate memory for slave specific data. */ + data = devm_kzalloc(&sl->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + sl->family_data = data; + + /* Setup default I2C speed on slave. */ + switch (i2c_speed) { + case 100: + __w1_f19_set_i2c_speed(sl, 0); + break; + case 400: + __w1_f19_set_i2c_speed(sl, 1); + break; + case 900: + __w1_f19_set_i2c_speed(sl, 2); + break; + default: + /* + * A i2c_speed module parameter of anything else + * than 100, 400, 900 means not to touch the + * speed of the DS28E17. + * We assume 400kBaud, the power-on value. + */ + data->speed = 1; + } + + /* + * Setup default busy stretch + * configuration for the DS28E17. + */ + data->stretch = i2c_stretch; + + /* Setup I2C adapter. */ + data->adapter.owner = THIS_MODULE; + data->adapter.algo = &w1_f19_i2c_algorithm; + data->adapter.algo_data = sl; + strcpy(data->adapter.name, "w1-"); + strcat(data->adapter.name, sl->name); + data->adapter.dev.parent = &sl->dev; + data->adapter.quirks = &w1_f19_i2c_adapter_quirks; + + return i2c_add_adapter(&data->adapter); +} + +static void w1_f19_remove_slave(struct w1_slave *sl) +{ + struct w1_f19_data *family_data = sl->family_data; + + /* Delete I2C adapter. */ + i2c_del_adapter(&family_data->adapter); + + /* Free slave specific data. */ + devm_kfree(&sl->dev, family_data); + sl->family_data = NULL; +} + + +/* Declarations within the w1 subsystem. */ +static const struct w1_family_ops w1_f19_fops = { + .add_slave = w1_f19_add_slave, + .remove_slave = w1_f19_remove_slave, + .groups = w1_f19_groups, +}; + +static struct w1_family w1_family_19 = { + .fid = W1_FAMILY_DS28E17, + .fops = &w1_f19_fops, +}; + +module_w1_family(w1_family_19); diff --git a/drivers/w1/slaves/w1_smem.c b/drivers/w1/slaves/w1_smem.c new file mode 100644 index 000000000..09afcf2a4 --- /dev/null +++ b/drivers/w1/slaves/w1_smem.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * w1_smem.c + * + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + */ + +#include <asm/types.h> + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/types.h> + +#include <linux/w1.h> + +#define W1_FAMILY_SMEM_01 0x01 +#define W1_FAMILY_SMEM_81 0x81 + +static struct w1_family w1_smem_family_01 = { + .fid = W1_FAMILY_SMEM_01, +}; + +static struct w1_family w1_smem_family_81 = { + .fid = W1_FAMILY_SMEM_81, +}; + +static int __init w1_smem_init(void) +{ + int err; + + err = w1_register_family(&w1_smem_family_01); + if (err) + return err; + + err = w1_register_family(&w1_smem_family_81); + if (err) { + w1_unregister_family(&w1_smem_family_01); + return err; + } + + return 0; +} + +static void __exit w1_smem_fini(void) +{ + w1_unregister_family(&w1_smem_family_01); + w1_unregister_family(&w1_smem_family_81); +} + +module_init(w1_smem_init); +module_exit(w1_smem_fini); + +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, 64bit memory family."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_SMEM_01)); +MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_SMEM_81)); diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c new file mode 100644 index 000000000..99c58bd9d --- /dev/null +++ b/drivers/w1/slaves/w1_therm.c @@ -0,0 +1,2224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * w1_therm.c + * + * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> + */ + +#include <asm/types.h> + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hwmon.h> +#include <linux/string.h> +#include <linux/jiffies.h> + +#include <linux/w1.h> + +#define W1_THERM_DS18S20 0x10 +#define W1_THERM_DS1822 0x22 +#define W1_THERM_DS18B20 0x28 +#define W1_THERM_DS1825 0x3B +#define W1_THERM_DS28EA00 0x42 + +/* + * Allow the strong pullup to be disabled, but default to enabled. + * If it was disabled a parasite powered device might not get the require + * current to do a temperature conversion. If it is enabled parasite powered + * devices have a better chance of getting the current required. + * In case the parasite power-detection is not working (seems to be the case + * for some DS18S20) the strong pullup can also be forced, regardless of the + * power state of the devices. + * + * Summary of options: + * - strong_pullup = 0 Disable strong pullup completely + * - strong_pullup = 1 Enable automatic strong pullup detection + * - strong_pullup = 2 Force strong pullup + */ +static int w1_strong_pullup = 1; +module_param_named(strong_pullup, w1_strong_pullup, int, 0); + +/* Counter for devices supporting bulk reading */ +static u16 bulk_read_device_counter; /* =0 as per C standard */ + +/* This command should be in public header w1.h but is not */ +#define W1_RECALL_EEPROM 0xB8 + +/* Nb of try for an operation */ +#define W1_THERM_MAX_TRY 5 + +/* ms delay to retry bus mutex */ +#define W1_THERM_RETRY_DELAY 20 + +/* delay in ms to write in EEPROM */ +#define W1_THERM_EEPROM_WRITE_DELAY 10 + +#define EEPROM_CMD_WRITE "save" /* cmd for write eeprom sysfs */ +#define EEPROM_CMD_READ "restore" /* cmd for read eeprom sysfs */ +#define BULK_TRIGGER_CMD "trigger" /* cmd to trigger a bulk read */ + +#define MIN_TEMP -55 /* min temperature that can be measured */ +#define MAX_TEMP 125 /* max temperature that can be measured */ + +/* Allowed values for sysfs conv_time attribute */ +#define CONV_TIME_DEFAULT 0 +#define CONV_TIME_MEASURE 1 + +/* Bits in sysfs "features" value */ +#define W1_THERM_CHECK_RESULT 1 /* Enable conversion success check */ +#define W1_THERM_POLL_COMPLETION 2 /* Poll for conversion completion */ +#define W1_THERM_FEATURES_MASK 3 /* All values mask */ + +/* Poll period in milliseconds. Should be less then a shortest operation on the device */ +#define W1_POLL_PERIOD 32 +#define W1_POLL_CONVERT_TEMP 2000 /* Timeout for W1_CONVERT_TEMP, ms */ +#define W1_POLL_RECALL_EEPROM 500 /* Timeout for W1_RECALL_EEPROM, ms*/ + +/* Masks for resolution functions, work with all devices */ +/* Bit mask for config register for all devices, bits 7,6,5 */ +#define W1_THERM_RESOLUTION_MASK 0xE0 +/* Bit offset of resolution in config register for all devices */ +#define W1_THERM_RESOLUTION_SHIFT 5 +/* Bit offset of resolution in config register for all devices */ +#define W1_THERM_RESOLUTION_SHIFT 5 +/* Add this to bit value to get resolution */ +#define W1_THERM_RESOLUTION_MIN 9 +/* Maximum allowed value */ +#define W1_THERM_RESOLUTION_MAX 14 + +/* Helpers Macros */ + +/* + * return a pointer on the slave w1_therm_family_converter struct: + * always test family data existence before using this macro + */ +#define SLAVE_SPECIFIC_FUNC(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->specific_functions) + +/* + * return the power mode of the sl slave : 1-ext, 0-parasite, <0 unknown + * always test family data existence before using this macro + */ +#define SLAVE_POWERMODE(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->external_powered) + +/* + * return the resolution in bit of the sl slave : <0 unknown + * always test family data existence before using this macro + */ +#define SLAVE_RESOLUTION(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->resolution) + +/* + * return the conv_time_override of the sl slave + * always test family data existence before using this macro + */ + #define SLAVE_CONV_TIME_OVERRIDE(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->conv_time_override) + +/* + * return the features of the sl slave + * always test family data existence before using this macro + */ + #define SLAVE_FEATURES(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->features) + +/* + * return whether or not a converT command has been issued to the slave + * * 0: no bulk read is pending + * * -1: conversion is in progress + * * 1: conversion done, result to be read + */ +#define SLAVE_CONVERT_TRIGGERED(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->convert_triggered) + +/* return the address of the refcnt in the family data */ +#define THERM_REFCNT(family_data) \ + (&((struct w1_therm_family_data *)family_data)->refcnt) + +/* Structs definition */ + +/** + * struct w1_therm_family_converter - bind device specific functions + * @broken: flag for non-registred families + * @reserved: not used here + * @f: pointer to the device binding structure + * @convert: pointer to the device conversion function + * @get_conversion_time: pointer to the device conversion time function + * @set_resolution: pointer to the device set_resolution function + * @get_resolution: pointer to the device get_resolution function + * @write_data: pointer to the device writing function (2 or 3 bytes) + * @bulk_read: true if device family support bulk read, false otherwise + */ +struct w1_therm_family_converter { + u8 broken; + u16 reserved; + struct w1_family *f; + int (*convert)(u8 rom[9]); + int (*get_conversion_time)(struct w1_slave *sl); + int (*set_resolution)(struct w1_slave *sl, int val); + int (*get_resolution)(struct w1_slave *sl); + int (*write_data)(struct w1_slave *sl, const u8 *data); + bool bulk_read; +}; + +/** + * struct w1_therm_family_data - device data + * @rom: ROM device id (64bit Lasered ROM code + 1 CRC byte) + * @refcnt: ref count + * @external_powered: 1 device powered externally, + * 0 device parasite powered, + * -x error or undefined + * @resolution: current device resolution + * @convert_triggered: conversion state of the device + * @conv_time_override: user selected conversion time or CONV_TIME_DEFAULT + * @features: bit mask - enable temperature validity check, poll for completion + * @specific_functions: pointer to struct of device specific function + */ +struct w1_therm_family_data { + uint8_t rom[9]; + atomic_t refcnt; + int external_powered; + int resolution; + int convert_triggered; + int conv_time_override; + unsigned int features; + struct w1_therm_family_converter *specific_functions; +}; + +/** + * struct therm_info - store temperature reading + * @rom: read device data (8 data bytes + 1 CRC byte) + * @crc: computed crc from rom + * @verdict: 1 crc checked, 0 crc not matching + */ +struct therm_info { + u8 rom[9]; + u8 crc; + u8 verdict; +}; + +/* Hardware Functions declaration */ + +/** + * reset_select_slave() - reset and select a slave + * @sl: the slave to select + * + * Resets the bus and select the slave by sending a ROM MATCH cmd + * w1_reset_select_slave() from w1_io.c could not be used here because + * it sent a SKIP ROM command if only one device is on the line. + * At the beginning of the such process, sl->master->slave_count is 1 even if + * more devices are on the line, causing collision on the line. + * + * Context: The w1 master lock must be held. + * + * Return: 0 if success, negative kernel error code otherwise. + */ +static int reset_select_slave(struct w1_slave *sl); + +/** + * convert_t() - Query the device for temperature conversion and read + * @sl: pointer to the slave to read + * @info: pointer to a structure to store the read results + * + * Return: 0 if success, -kernel error code otherwise + */ +static int convert_t(struct w1_slave *sl, struct therm_info *info); + +/** + * read_scratchpad() - read the data in device RAM + * @sl: pointer to the slave to read + * @info: pointer to a structure to store the read results + * + * Return: 0 if success, -kernel error code otherwise + */ +static int read_scratchpad(struct w1_slave *sl, struct therm_info *info); + +/** + * write_scratchpad() - write nb_bytes in the device RAM + * @sl: pointer to the slave to write in + * @data: pointer to an array of 3 bytes, as 3 bytes MUST be written + * @nb_bytes: number of bytes to be written (2 for DS18S20, 3 otherwise) + * + * Return: 0 if success, -kernel error code otherwise + */ +static int write_scratchpad(struct w1_slave *sl, const u8 *data, u8 nb_bytes); + +/** + * copy_scratchpad() - Copy the content of scratchpad in device EEPROM + * @sl: slave involved + * + * Return: 0 if success, -kernel error code otherwise + */ +static int copy_scratchpad(struct w1_slave *sl); + +/** + * recall_eeprom() - Restore EEPROM data to device RAM + * @sl: slave involved + * + * Return: 0 if success, -kernel error code otherwise + */ +static int recall_eeprom(struct w1_slave *sl); + +/** + * read_powermode() - Query the power mode of the slave + * @sl: slave to retrieve the power mode + * + * Ask the device to get its power mode (external or parasite) + * and store the power status in the &struct w1_therm_family_data. + * + * Return: + * * 0 parasite powered device + * * 1 externally powered device + * * <0 kernel error code + */ +static int read_powermode(struct w1_slave *sl); + +/** + * trigger_bulk_read() - function to trigger a bulk read on the bus + * @dev_master: the device master of the bus + * + * Send a SKIP ROM follow by a CONVERT T commmand on the bus. + * It also set the status flag in each slave &struct w1_therm_family_data + * to signal that a conversion is in progress. + * + * Return: 0 if success, -kernel error code otherwise + */ +static int trigger_bulk_read(struct w1_master *dev_master); + +/* Sysfs interface declaration */ + +static ssize_t w1_slave_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t w1_slave_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size); + +static ssize_t w1_seq_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t temperature_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t ext_power_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t resolution_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t resolution_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size); + +static ssize_t eeprom_cmd_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size); + +static ssize_t alarms_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size); + +static ssize_t alarms_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t therm_bulk_read_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size); + +static ssize_t therm_bulk_read_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t conv_time_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t conv_time_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t size); + +static ssize_t features_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t features_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t size); +/* Attributes declarations */ + +static DEVICE_ATTR_RW(w1_slave); +static DEVICE_ATTR_RO(w1_seq); +static DEVICE_ATTR_RO(temperature); +static DEVICE_ATTR_RO(ext_power); +static DEVICE_ATTR_RW(resolution); +static DEVICE_ATTR_WO(eeprom_cmd); +static DEVICE_ATTR_RW(alarms); +static DEVICE_ATTR_RW(conv_time); +static DEVICE_ATTR_RW(features); + +static DEVICE_ATTR_RW(therm_bulk_read); /* attribut at master level */ + +/* Interface Functions declaration */ + +/** + * w1_therm_add_slave() - Called when a new slave is discovered + * @sl: slave just discovered by the master. + * + * Called by the master when the slave is discovered on the bus. Used to + * initialize slave state before the beginning of any communication. + * + * Return: 0 - If success, negative kernel code otherwise + */ +static int w1_therm_add_slave(struct w1_slave *sl); + +/** + * w1_therm_remove_slave() - Called when a slave is removed + * @sl: slave to be removed. + * + * Called by the master when the slave is considered not to be on the bus + * anymore. Used to free memory. + */ +static void w1_therm_remove_slave(struct w1_slave *sl); + +/* Family attributes */ + +static struct attribute *w1_therm_attrs[] = { + &dev_attr_w1_slave.attr, + &dev_attr_temperature.attr, + &dev_attr_ext_power.attr, + &dev_attr_resolution.attr, + &dev_attr_eeprom_cmd.attr, + &dev_attr_alarms.attr, + &dev_attr_conv_time.attr, + &dev_attr_features.attr, + NULL, +}; + +static struct attribute *w1_ds18s20_attrs[] = { + &dev_attr_w1_slave.attr, + &dev_attr_temperature.attr, + &dev_attr_ext_power.attr, + &dev_attr_eeprom_cmd.attr, + &dev_attr_alarms.attr, + &dev_attr_conv_time.attr, + &dev_attr_features.attr, + NULL, +}; + +static struct attribute *w1_ds28ea00_attrs[] = { + &dev_attr_w1_slave.attr, + &dev_attr_w1_seq.attr, + &dev_attr_temperature.attr, + &dev_attr_ext_power.attr, + &dev_attr_resolution.attr, + &dev_attr_eeprom_cmd.attr, + &dev_attr_alarms.attr, + &dev_attr_conv_time.attr, + &dev_attr_features.attr, + NULL, +}; + +/* Attribute groups */ + +ATTRIBUTE_GROUPS(w1_therm); +ATTRIBUTE_GROUPS(w1_ds18s20); +ATTRIBUTE_GROUPS(w1_ds28ea00); + +#if IS_REACHABLE(CONFIG_HWMON) +static int w1_read_temp(struct device *dev, u32 attr, int channel, + long *val); + +static umode_t w1_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return attr == hwmon_temp_input ? 0444 : 0; +} + +static int w1_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_temp: + return w1_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static const u32 w1_temp_config[] = { + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info w1_temp = { + .type = hwmon_temp, + .config = w1_temp_config, +}; + +static const struct hwmon_channel_info *w1_info[] = { + &w1_temp, + NULL +}; + +static const struct hwmon_ops w1_hwmon_ops = { + .is_visible = w1_is_visible, + .read = w1_read, +}; + +static const struct hwmon_chip_info w1_chip_info = { + .ops = &w1_hwmon_ops, + .info = w1_info, +}; +#define W1_CHIPINFO (&w1_chip_info) +#else +#define W1_CHIPINFO NULL +#endif + +/* Family operations */ + +static const struct w1_family_ops w1_therm_fops = { + .add_slave = w1_therm_add_slave, + .remove_slave = w1_therm_remove_slave, + .groups = w1_therm_groups, + .chip_info = W1_CHIPINFO, +}; + +static const struct w1_family_ops w1_ds18s20_fops = { + .add_slave = w1_therm_add_slave, + .remove_slave = w1_therm_remove_slave, + .groups = w1_ds18s20_groups, + .chip_info = W1_CHIPINFO, +}; + +static const struct w1_family_ops w1_ds28ea00_fops = { + .add_slave = w1_therm_add_slave, + .remove_slave = w1_therm_remove_slave, + .groups = w1_ds28ea00_groups, + .chip_info = W1_CHIPINFO, +}; + +/* Family binding operations struct */ + +static struct w1_family w1_therm_family_DS18S20 = { + .fid = W1_THERM_DS18S20, + .fops = &w1_ds18s20_fops, +}; + +static struct w1_family w1_therm_family_DS18B20 = { + .fid = W1_THERM_DS18B20, + .fops = &w1_therm_fops, +}; + +static struct w1_family w1_therm_family_DS1822 = { + .fid = W1_THERM_DS1822, + .fops = &w1_therm_fops, +}; + +static struct w1_family w1_therm_family_DS28EA00 = { + .fid = W1_THERM_DS28EA00, + .fops = &w1_ds28ea00_fops, +}; + +static struct w1_family w1_therm_family_DS1825 = { + .fid = W1_THERM_DS1825, + .fops = &w1_therm_fops, +}; + +/* Device dependent func */ + +static inline int w1_DS18B20_convert_time(struct w1_slave *sl) +{ + int ret; + + if (!sl->family_data) + return -ENODEV; /* device unknown */ + + if (SLAVE_CONV_TIME_OVERRIDE(sl) != CONV_TIME_DEFAULT) + return SLAVE_CONV_TIME_OVERRIDE(sl); + + /* Return the conversion time, depending on resolution, + * select maximum conversion time among all compatible devices + */ + switch (SLAVE_RESOLUTION(sl)) { + case 9: + ret = 95; + break; + case 10: + ret = 190; + break; + case 11: + ret = 375; + break; + case 12: + ret = 750; + break; + case 13: + ret = 850; /* GX20MH01 only. Datasheet says 500ms, but that's not enough. */ + break; + case 14: + ret = 1600; /* GX20MH01 only. Datasheet says 1000ms - not enough */ + break; + default: + ret = 750; + } + return ret; +} + +static inline int w1_DS18S20_convert_time(struct w1_slave *sl) +{ + if (!sl->family_data) + return -ENODEV; /* device unknown */ + + if (SLAVE_CONV_TIME_OVERRIDE(sl) == CONV_TIME_DEFAULT) + return 750; /* default for DS18S20 */ + else + return SLAVE_CONV_TIME_OVERRIDE(sl); +} + +static inline int w1_DS1825_convert_time(struct w1_slave *sl) +{ + int ret; + + if (!sl->family_data) + return -ENODEV; /* device unknown */ + + if (SLAVE_CONV_TIME_OVERRIDE(sl) != CONV_TIME_DEFAULT) + return SLAVE_CONV_TIME_OVERRIDE(sl); + + /* Return the conversion time, depending on resolution, + * select maximum conversion time among all compatible devices + */ + switch (SLAVE_RESOLUTION(sl)) { + case 9: + ret = 95; + break; + case 10: + ret = 190; + break; + case 11: + ret = 375; + break; + case 12: + ret = 750; + break; + case 14: + ret = 100; /* MAX31850 only. Datasheet says 100ms */ + break; + default: + ret = 750; + } + return ret; +} + +static inline int w1_DS18B20_write_data(struct w1_slave *sl, + const u8 *data) +{ + return write_scratchpad(sl, data, 3); +} + +static inline int w1_DS18S20_write_data(struct w1_slave *sl, + const u8 *data) +{ + /* No config register */ + return write_scratchpad(sl, data, 2); +} + +static inline int w1_DS18B20_set_resolution(struct w1_slave *sl, int val) +{ + int ret; + struct therm_info info, info2; + + /* DS18B20 resolution is 9 to 12 bits */ + /* GX20MH01 resolution is 9 to 14 bits */ + /* MAX31850 resolution is fixed 14 bits */ + if (val < W1_THERM_RESOLUTION_MIN || val > W1_THERM_RESOLUTION_MAX) + return -EINVAL; + + /* Calc bit value from resolution */ + val = (val - W1_THERM_RESOLUTION_MIN) << W1_THERM_RESOLUTION_SHIFT; + + /* + * Read the scratchpad to change only the required bits + * (bit5 & bit 6 from byte 4) + */ + ret = read_scratchpad(sl, &info); + + if (ret) + return ret; + + + info.rom[4] &= ~W1_THERM_RESOLUTION_MASK; + info.rom[4] |= val; + + /* Write data in the device RAM */ + ret = w1_DS18B20_write_data(sl, info.rom + 2); + if (ret) + return ret; + + /* Have to read back the resolution to verify an actual value + * GX20MH01 and DS18B20 are indistinguishable by family number, but resolutions differ + * Some DS18B20 clones don't support resolution change + */ + ret = read_scratchpad(sl, &info2); + if (ret) + /* Scratchpad read fail */ + return ret; + + if ((info2.rom[4] & W1_THERM_RESOLUTION_MASK) == (info.rom[4] & W1_THERM_RESOLUTION_MASK)) + return 0; + + /* Resolution verify error */ + return -EIO; +} + +static inline int w1_DS18B20_get_resolution(struct w1_slave *sl) +{ + int ret; + int resolution; + struct therm_info info; + + ret = read_scratchpad(sl, &info); + + if (ret) + return ret; + + resolution = ((info.rom[4] & W1_THERM_RESOLUTION_MASK) >> W1_THERM_RESOLUTION_SHIFT) + + W1_THERM_RESOLUTION_MIN; + /* GX20MH01 has one special case: + * >=14 means 14 bits when getting resolution from bit value. + * MAX31850 delivers fixed 15 and has 14 bits. + * Other devices have no more then 12 bits. + */ + if (resolution > W1_THERM_RESOLUTION_MAX) + resolution = W1_THERM_RESOLUTION_MAX; + + return resolution; +} + +/** + * w1_DS18B20_convert_temp() - temperature computation for DS18B20 + * @rom: data read from device RAM (8 data bytes + 1 CRC byte) + * + * Can be called for any DS18B20 compliant device. + * + * Return: value in millidegrees Celsius. + */ +static inline int w1_DS18B20_convert_temp(u8 rom[9]) +{ + u16 bv; + s16 t; + + /* Signed 16-bit value to unsigned, cpu order */ + bv = le16_to_cpup((__le16 *)rom); + + /* Config register bit R2 = 1 - GX20MH01 in 13 or 14 bit resolution mode */ + if (rom[4] & 0x80) { + /* Insert two temperature bits from config register */ + /* Avoid arithmetic shift of signed value */ + bv = (bv << 2) | (rom[4] & 3); + t = (s16) bv; /* Degrees, lowest bit is 2^-6 */ + return (int)t * 1000 / 64; /* Sign-extend to int; millidegrees */ + } + t = (s16)bv; /* Degrees, lowest bit is 2^-4 */ + return (int)t * 1000 / 16; /* Sign-extend to int; millidegrees */ +} + +/** + * w1_DS18S20_convert_temp() - temperature computation for DS18S20 + * @rom: data read from device RAM (8 data bytes + 1 CRC byte) + * + * Can be called for any DS18S20 compliant device. + * + * Return: value in millidegrees Celsius. + */ +static inline int w1_DS18S20_convert_temp(u8 rom[9]) +{ + int t, h; + + if (!rom[7]) { + pr_debug("%s: Invalid argument for conversion\n", __func__); + return 0; + } + + if (rom[1] == 0) + t = ((s32)rom[0] >> 1)*1000; + else + t = 1000*(-1*(s32)(0x100-rom[0]) >> 1); + + t -= 250; + h = 1000*((s32)rom[7] - (s32)rom[6]); + h /= (s32)rom[7]; + t += h; + + return t; +} + +/** + * w1_DS1825_convert_temp() - temperature computation for DS1825 + * @rom: data read from device RAM (8 data bytes + 1 CRC byte) + * + * Can be called for any DS1825 compliant device. + * Is used by MAX31850, too + * + * Return: value in millidegrees Celsius. + */ + +static inline int w1_DS1825_convert_temp(u8 rom[9]) +{ + u16 bv; + s16 t; + + /* Signed 16-bit value to unsigned, cpu order */ + bv = le16_to_cpup((__le16 *)rom); + + /* Config register bit 7 = 1 - MA31850 found, 14 bit resolution */ + if (rom[4] & 0x80) { + /* Mask out bits 0 (Fault) and 1 (Reserved) */ + /* Avoid arithmetic shift of signed value */ + bv = (bv & 0xFFFC); /* Degrees, lowest 4 bits are 2^-1, 2^-2 and 2 zero bits */ + } + t = (s16)bv; /* Degrees, lowest bit is 2^-4 */ + return (int)t * 1000 / 16; /* Sign-extend to int; millidegrees */ +} + +/* Device capability description */ +/* GX20MH01 device shares family number and structure with DS18B20 */ + +static struct w1_therm_family_converter w1_therm_families[] = { + { + .f = &w1_therm_family_DS18S20, + .convert = w1_DS18S20_convert_temp, + .get_conversion_time = w1_DS18S20_convert_time, + .set_resolution = NULL, /* no config register */ + .get_resolution = NULL, /* no config register */ + .write_data = w1_DS18S20_write_data, + .bulk_read = true + }, + { + .f = &w1_therm_family_DS1822, + .convert = w1_DS18B20_convert_temp, + .get_conversion_time = w1_DS18B20_convert_time, + .set_resolution = w1_DS18B20_set_resolution, + .get_resolution = w1_DS18B20_get_resolution, + .write_data = w1_DS18B20_write_data, + .bulk_read = true + }, + { + /* Also used for GX20MH01 */ + .f = &w1_therm_family_DS18B20, + .convert = w1_DS18B20_convert_temp, + .get_conversion_time = w1_DS18B20_convert_time, + .set_resolution = w1_DS18B20_set_resolution, + .get_resolution = w1_DS18B20_get_resolution, + .write_data = w1_DS18B20_write_data, + .bulk_read = true + }, + { + .f = &w1_therm_family_DS28EA00, + .convert = w1_DS18B20_convert_temp, + .get_conversion_time = w1_DS18B20_convert_time, + .set_resolution = w1_DS18B20_set_resolution, + .get_resolution = w1_DS18B20_get_resolution, + .write_data = w1_DS18B20_write_data, + .bulk_read = false + }, + { + /* Also used for MAX31850 */ + .f = &w1_therm_family_DS1825, + .convert = w1_DS1825_convert_temp, + .get_conversion_time = w1_DS1825_convert_time, + .set_resolution = w1_DS18B20_set_resolution, + .get_resolution = w1_DS18B20_get_resolution, + .write_data = w1_DS18B20_write_data, + .bulk_read = true + } +}; + +/* Helpers Functions */ + +/** + * device_family() - Retrieve a pointer on &struct w1_therm_family_converter + * @sl: slave to retrieve the device specific structure + * + * Return: pointer to the slaves's family converter, NULL if not known + */ +static struct w1_therm_family_converter *device_family(struct w1_slave *sl) +{ + struct w1_therm_family_converter *ret = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) { + if (w1_therm_families[i].f->fid == sl->family->fid) { + ret = &w1_therm_families[i]; + break; + } + } + return ret; +} + +/** + * bus_mutex_lock() - Acquire the mutex + * @lock: w1 bus mutex to acquire + * + * It try to acquire the mutex W1_THERM_MAX_TRY times and wait + * W1_THERM_RETRY_DELAY between 2 attempts. + * + * Return: true is mutex is acquired and lock, false otherwise + */ +static inline bool bus_mutex_lock(struct mutex *lock) +{ + int max_trying = W1_THERM_MAX_TRY; + + /* try to acquire the mutex, if not, sleep retry_delay before retry) */ + while (mutex_lock_interruptible(lock) != 0 && max_trying > 0) { + unsigned long sleep_rem; + + sleep_rem = msleep_interruptible(W1_THERM_RETRY_DELAY); + if (!sleep_rem) + max_trying--; + } + + if (!max_trying) + return false; /* Didn't acquire the bus mutex */ + + return true; +} + +/** + * check_family_data() - Check if family data and specific functions are present + * @sl: W1 device data + * + * Return: 0 - OK, negative value - error + */ +static int check_family_data(struct w1_slave *sl) +{ + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(&sl->dev, + "%s: Device is not supported by the driver\n", __func__); + return -EINVAL; /* No device family */ + } + return 0; +} + +/** + * bulk_read_support() - check if slave support bulk read + * @sl: device to check the ability + * + * Return: true if bulk read is supported, false if not or error + */ +static inline bool bulk_read_support(struct w1_slave *sl) +{ + if (SLAVE_SPECIFIC_FUNC(sl)) + return SLAVE_SPECIFIC_FUNC(sl)->bulk_read; + + dev_info(&sl->dev, + "%s: Device not supported by the driver\n", __func__); + + return false; /* No device family */ +} + +/** + * conversion_time() - get the Tconv for the slave + * @sl: device to get the conversion time + * + * On device supporting resolution settings, conversion time depend + * on the resolution setting. This helper function get the slave timing, + * depending on its current setting. + * + * Return: conversion time in ms, negative values are kernel error code + */ +static inline int conversion_time(struct w1_slave *sl) +{ + if (SLAVE_SPECIFIC_FUNC(sl)) + return SLAVE_SPECIFIC_FUNC(sl)->get_conversion_time(sl); + + dev_info(&sl->dev, + "%s: Device not supported by the driver\n", __func__); + + return -ENODEV; /* No device family */ +} + +/** + * temperature_from_RAM() - Convert the read info to temperature + * @sl: device that sent the RAM data + * @rom: read value on the slave device RAM + * + * Device dependent, the function bind the correct computation method. + * + * Return: temperature in 1/1000degC, 0 on error. + */ +static inline int temperature_from_RAM(struct w1_slave *sl, u8 rom[9]) +{ + if (SLAVE_SPECIFIC_FUNC(sl)) + return SLAVE_SPECIFIC_FUNC(sl)->convert(rom); + + dev_info(&sl->dev, + "%s: Device not supported by the driver\n", __func__); + + return 0; /* No device family */ +} + +/** + * int_to_short() - Safe casting of int to short + * + * @i: integer to be converted to short + * + * Device register use 1 byte to store signed integer. + * This helper function convert the int in a signed short, + * using the min/max values that device can measure as limits. + * min/max values are defined by macro. + * + * Return: a short in the range of min/max value + */ +static inline s8 int_to_short(int i) +{ + /* Prepare to cast to short by eliminating out of range values */ + i = clamp(i, MIN_TEMP, MAX_TEMP); + return (s8) i; +} + +/* Interface Functions */ + +static int w1_therm_add_slave(struct w1_slave *sl) +{ + struct w1_therm_family_converter *sl_family_conv; + + /* Allocate memory */ + sl->family_data = kzalloc(sizeof(struct w1_therm_family_data), + GFP_KERNEL); + if (!sl->family_data) + return -ENOMEM; + + atomic_set(THERM_REFCNT(sl->family_data), 1); + + /* Get a pointer to the device specific function struct */ + sl_family_conv = device_family(sl); + if (!sl_family_conv) { + kfree(sl->family_data); + return -ENODEV; + } + /* save this pointer to the device structure */ + SLAVE_SPECIFIC_FUNC(sl) = sl_family_conv; + + if (bulk_read_support(sl)) { + /* + * add the sys entry to trigger bulk_read + * at master level only the 1st time + */ + if (!bulk_read_device_counter) { + int err = device_create_file(&sl->master->dev, + &dev_attr_therm_bulk_read); + + if (err) + dev_warn(&sl->dev, + "%s: Device has been added, but bulk read is unavailable. err=%d\n", + __func__, err); + } + /* Increment the counter */ + bulk_read_device_counter++; + } + + /* Getting the power mode of the device {external, parasite} */ + SLAVE_POWERMODE(sl) = read_powermode(sl); + + if (SLAVE_POWERMODE(sl) < 0) { + /* no error returned as device has been added */ + dev_warn(&sl->dev, + "%s: Device has been added, but power_mode may be corrupted. err=%d\n", + __func__, SLAVE_POWERMODE(sl)); + } + + /* Getting the resolution of the device */ + if (SLAVE_SPECIFIC_FUNC(sl)->get_resolution) { + SLAVE_RESOLUTION(sl) = + SLAVE_SPECIFIC_FUNC(sl)->get_resolution(sl); + if (SLAVE_RESOLUTION(sl) < 0) { + /* no error returned as device has been added */ + dev_warn(&sl->dev, + "%s:Device has been added, but resolution may be corrupted. err=%d\n", + __func__, SLAVE_RESOLUTION(sl)); + } + } + + /* Finally initialize convert_triggered flag */ + SLAVE_CONVERT_TRIGGERED(sl) = 0; + + return 0; +} + +static void w1_therm_remove_slave(struct w1_slave *sl) +{ + int refcnt = atomic_sub_return(1, THERM_REFCNT(sl->family_data)); + + if (bulk_read_support(sl)) { + bulk_read_device_counter--; + /* Delete the entry if no more device support the feature */ + if (!bulk_read_device_counter) + device_remove_file(&sl->master->dev, + &dev_attr_therm_bulk_read); + } + + while (refcnt) { + msleep(1000); + refcnt = atomic_read(THERM_REFCNT(sl->family_data)); + } + kfree(sl->family_data); + sl->family_data = NULL; +} + +/* Hardware Functions */ + +/* Safe version of reset_select_slave - avoid using the one in w_io.c */ +static int reset_select_slave(struct w1_slave *sl) +{ + u8 match[9] = { W1_MATCH_ROM, }; + u64 rn = le64_to_cpu(*((u64 *)&sl->reg_num)); + + if (w1_reset_bus(sl->master)) + return -ENODEV; + + memcpy(&match[1], &rn, 8); + w1_write_block(sl->master, match, 9); + + return 0; +} + +/** + * w1_poll_completion - Poll for operation completion, with timeout + * @dev_master: the device master of the bus + * @tout_ms: timeout in milliseconds + * + * The device is answering 0's while an operation is in progress and 1's after it completes + * Timeout may happen if the previous command was not recognised due to a line noise + * + * Return: 0 - OK, negative error - timeout + */ +static int w1_poll_completion(struct w1_master *dev_master, int tout_ms) +{ + int i; + + for (i = 0; i < tout_ms/W1_POLL_PERIOD; i++) { + /* Delay is before poll, for device to recognize a command */ + msleep(W1_POLL_PERIOD); + + /* Compare all 8 bits to mitigate a noise on the bus */ + if (w1_read_8(dev_master) == 0xFF) + break; + } + if (i == tout_ms/W1_POLL_PERIOD) + return -EIO; + + return 0; +} + +static int convert_t(struct w1_slave *sl, struct therm_info *info) +{ + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int t_conv; + int ret = -ENODEV; + bool strong_pullup; + + if (!sl->family_data) + goto error; + + strong_pullup = (w1_strong_pullup == 2 || + (!SLAVE_POWERMODE(sl) && + w1_strong_pullup)); + + if (strong_pullup && SLAVE_FEATURES(sl) & W1_THERM_POLL_COMPLETION) { + dev_warn(&sl->dev, + "%s: Disabling W1_THERM_POLL_COMPLETION in parasite power mode.\n", + __func__); + SLAVE_FEATURES(sl) &= ~W1_THERM_POLL_COMPLETION; + } + + /* get conversion duration device and id dependent */ + t_conv = conversion_time(sl); + + memset(info->rom, 0, sizeof(info->rom)); + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while (max_trying-- && ret) { /* ret should be 0 */ + + info->verdict = 0; + info->crc = 0; + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + unsigned long sleep_rem; + + /* 750ms strong pullup (or delay) after the convert */ + if (strong_pullup) + w1_next_pullup(dev_master, t_conv); + + w1_write_8(dev_master, W1_CONVERT_TEMP); + + if (SLAVE_FEATURES(sl) & W1_THERM_POLL_COMPLETION) { + ret = w1_poll_completion(dev_master, W1_POLL_CONVERT_TEMP); + if (ret) { + dev_dbg(&sl->dev, "%s: Timeout\n", __func__); + goto mt_unlock; + } + mutex_unlock(&dev_master->bus_mutex); + } else if (!strong_pullup) { /*no device need pullup */ + sleep_rem = msleep_interruptible(t_conv); + if (sleep_rem != 0) { + ret = -EINTR; + goto mt_unlock; + } + mutex_unlock(&dev_master->bus_mutex); + } else { /*some device need pullup */ + mutex_unlock(&dev_master->bus_mutex); + sleep_rem = msleep_interruptible(t_conv); + if (sleep_rem != 0) { + ret = -EINTR; + goto dec_refcnt; + } + } + ret = read_scratchpad(sl, info); + + /* If enabled, check for conversion success */ + if ((SLAVE_FEATURES(sl) & W1_THERM_CHECK_RESULT) && + (info->rom[6] == 0xC) && + ((info->rom[1] == 0x5 && info->rom[0] == 0x50) || + (info->rom[1] == 0x7 && info->rom[0] == 0xFF)) + ) { + /* Invalid reading (scratchpad byte 6 = 0xC) + * due to insufficient conversion time + * or power failure. + */ + ret = -EIO; + } + + goto dec_refcnt; + } + + } + +mt_unlock: + mutex_unlock(&dev_master->bus_mutex); +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + +static int conv_time_measure(struct w1_slave *sl, int *conv_time) +{ + struct therm_info inf, + *info = &inf; + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int ret = -ENODEV; + bool strong_pullup; + + if (!sl->family_data) + goto error; + + strong_pullup = (w1_strong_pullup == 2 || + (!SLAVE_POWERMODE(sl) && + w1_strong_pullup)); + + if (strong_pullup) { + pr_info("%s: Measure with strong_pullup is not supported.\n", __func__); + return -EINVAL; + } + + memset(info->rom, 0, sizeof(info->rom)); + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while (max_trying-- && ret) { /* ret should be 0 */ + info->verdict = 0; + info->crc = 0; + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + int j_start, j_end; + + /*no device need pullup */ + w1_write_8(dev_master, W1_CONVERT_TEMP); + + j_start = jiffies; + ret = w1_poll_completion(dev_master, W1_POLL_CONVERT_TEMP); + if (ret) { + dev_dbg(&sl->dev, "%s: Timeout\n", __func__); + goto mt_unlock; + } + j_end = jiffies; + /* 1.2x increase for variation and changes over temperature range */ + *conv_time = jiffies_to_msecs(j_end-j_start)*12/10; + pr_debug("W1 Measure complete, conv_time = %d, HZ=%d.\n", + *conv_time, HZ); + if (*conv_time <= CONV_TIME_MEASURE) { + ret = -EIO; + goto mt_unlock; + } + mutex_unlock(&dev_master->bus_mutex); + ret = read_scratchpad(sl, info); + goto dec_refcnt; + } + + } +mt_unlock: + mutex_unlock(&dev_master->bus_mutex); +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + +static int read_scratchpad(struct w1_slave *sl, struct therm_info *info) +{ + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int ret = -ENODEV; + + info->verdict = 0; + + if (!sl->family_data) + goto error; + + memset(info->rom, 0, sizeof(info->rom)); + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while (max_trying-- && ret) { /* ret should be 0 */ + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + u8 nb_bytes_read; + + w1_write_8(dev_master, W1_READ_SCRATCHPAD); + + nb_bytes_read = w1_read_block(dev_master, info->rom, 9); + if (nb_bytes_read != 9) { + dev_warn(&sl->dev, + "w1_read_block(): returned %u instead of 9.\n", + nb_bytes_read); + ret = -EIO; + } + + info->crc = w1_calc_crc8(info->rom, 8); + + if (info->rom[8] == info->crc) { + info->verdict = 1; + ret = 0; + } else + ret = -EIO; /* CRC not checked */ + } + + } + mutex_unlock(&dev_master->bus_mutex); + +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + +static int write_scratchpad(struct w1_slave *sl, const u8 *data, u8 nb_bytes) +{ + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int ret = -ENODEV; + + if (!sl->family_data) + goto error; + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while (max_trying-- && ret) { /* ret should be 0 */ + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + w1_write_8(dev_master, W1_WRITE_SCRATCHPAD); + w1_write_block(dev_master, data, nb_bytes); + ret = 0; + } + } + mutex_unlock(&dev_master->bus_mutex); + +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + +static int copy_scratchpad(struct w1_slave *sl) +{ + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int t_write, ret = -ENODEV; + bool strong_pullup; + + if (!sl->family_data) + goto error; + + t_write = W1_THERM_EEPROM_WRITE_DELAY; + strong_pullup = (w1_strong_pullup == 2 || + (!SLAVE_POWERMODE(sl) && + w1_strong_pullup)); + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while (max_trying-- && ret) { /* ret should be 0 */ + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + unsigned long sleep_rem; + + /* 10ms strong pullup (or delay) after the convert */ + if (strong_pullup) + w1_next_pullup(dev_master, t_write); + + w1_write_8(dev_master, W1_COPY_SCRATCHPAD); + + if (strong_pullup) { + sleep_rem = msleep_interruptible(t_write); + if (sleep_rem != 0) { + ret = -EINTR; + goto mt_unlock; + } + } + ret = 0; + } + + } + +mt_unlock: + mutex_unlock(&dev_master->bus_mutex); +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + +static int recall_eeprom(struct w1_slave *sl) +{ + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int ret = -ENODEV; + + if (!sl->family_data) + goto error; + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while (max_trying-- && ret) { /* ret should be 0 */ + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + + w1_write_8(dev_master, W1_RECALL_EEPROM); + ret = w1_poll_completion(dev_master, W1_POLL_RECALL_EEPROM); + } + + } + + mutex_unlock(&dev_master->bus_mutex); + +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + +static int read_powermode(struct w1_slave *sl) +{ + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int ret = -ENODEV; + + if (!sl->family_data) + goto error; + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while ((max_trying--) && (ret < 0)) { + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + w1_write_8(dev_master, W1_READ_PSUPPLY); + /* + * Emit a read time slot and read only one bit, + * 1 is externally powered, + * 0 is parasite powered + */ + ret = w1_touch_bit(dev_master, 1); + /* ret should be either 1 either 0 */ + } + } + mutex_unlock(&dev_master->bus_mutex); + +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + +static int trigger_bulk_read(struct w1_master *dev_master) +{ + struct w1_slave *sl = NULL; /* used to iterate through slaves */ + int max_trying = W1_THERM_MAX_TRY; + int t_conv = 0; + int ret = -ENODEV; + bool strong_pullup = false; + + /* + * Check whether there are parasite powered device on the bus, + * and compute duration of conversion for these devices + * so we can apply a strong pullup if required + */ + list_for_each_entry(sl, &dev_master->slist, w1_slave_entry) { + if (!sl->family_data) + goto error; + if (bulk_read_support(sl)) { + int t_cur = conversion_time(sl); + + t_conv = t_cur > t_conv ? t_cur : t_conv; + strong_pullup = strong_pullup || + (w1_strong_pullup == 2 || + (!SLAVE_POWERMODE(sl) && + w1_strong_pullup)); + } + } + + /* + * t_conv is the max conversion time required on the bus + * If its 0, no device support the bulk read feature + */ + if (!t_conv) + goto error; + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto error; + } + + while ((max_trying--) && (ret < 0)) { /* ret should be either 0 */ + + if (!w1_reset_bus(dev_master)) { /* Just reset the bus */ + unsigned long sleep_rem; + + w1_write_8(dev_master, W1_SKIP_ROM); + + if (strong_pullup) /* Apply pullup if required */ + w1_next_pullup(dev_master, t_conv); + + w1_write_8(dev_master, W1_CONVERT_TEMP); + + /* set a flag to instruct that converT pending */ + list_for_each_entry(sl, + &dev_master->slist, w1_slave_entry) { + if (bulk_read_support(sl)) + SLAVE_CONVERT_TRIGGERED(sl) = -1; + } + + if (strong_pullup) { /* some device need pullup */ + sleep_rem = msleep_interruptible(t_conv); + if (sleep_rem != 0) { + ret = -EINTR; + goto mt_unlock; + } + mutex_unlock(&dev_master->bus_mutex); + } else { + mutex_unlock(&dev_master->bus_mutex); + sleep_rem = msleep_interruptible(t_conv); + if (sleep_rem != 0) { + ret = -EINTR; + goto set_flag; + } + } + ret = 0; + goto set_flag; + } + } + +mt_unlock: + mutex_unlock(&dev_master->bus_mutex); +set_flag: + /* set a flag to register convsersion is done */ + list_for_each_entry(sl, &dev_master->slist, w1_slave_entry) { + if (bulk_read_support(sl)) + SLAVE_CONVERT_TRIGGERED(sl) = 1; + } +error: + return ret; +} + +/* Sysfs Interface definition */ + +static ssize_t w1_slave_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct therm_info info; + u8 *family_data = sl->family_data; + int ret, i; + ssize_t c = PAGE_SIZE; + + if (bulk_read_support(sl)) { + if (SLAVE_CONVERT_TRIGGERED(sl) < 0) { + dev_dbg(device, + "%s: Conversion in progress, retry later\n", + __func__); + return 0; + } else if (SLAVE_CONVERT_TRIGGERED(sl) > 0) { + /* A bulk read has been issued, read the device RAM */ + ret = read_scratchpad(sl, &info); + SLAVE_CONVERT_TRIGGERED(sl) = 0; + } else + ret = convert_t(sl, &info); + } else + ret = convert_t(sl, &info); + + if (ret < 0) { + dev_dbg(device, + "%s: Temperature data may be corrupted. err=%d\n", + __func__, ret); + return 0; + } + + for (i = 0; i < 9; ++i) + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", info.rom[i]); + c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n", + info.crc, (info.verdict) ? "YES" : "NO"); + + if (info.verdict) + memcpy(family_data, info.rom, sizeof(info.rom)); + else + dev_warn(device, "%s:Read failed CRC check\n", __func__); + + for (i = 0; i < 9; ++i) + c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", + ((u8 *)family_data)[i]); + + c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n", + temperature_from_RAM(sl, info.rom)); + + ret = PAGE_SIZE - c; + return ret; +} + +static ssize_t w1_slave_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t size) +{ + int val, ret = 0; + struct w1_slave *sl = dev_to_w1_slave(device); + + ret = kstrtoint(buf, 10, &val); /* converting user entry to int */ + + if (ret) { /* conversion error */ + dev_info(device, + "%s: conversion error. err= %d\n", __func__, ret); + return size; /* return size to avoid call back again */ + } + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device not supported by the driver\n", __func__); + return size; /* No device family */ + } + + if (val == 0) /* val=0 : trigger a EEPROM save */ + ret = copy_scratchpad(sl); + else { + if (SLAVE_SPECIFIC_FUNC(sl)->set_resolution) + ret = SLAVE_SPECIFIC_FUNC(sl)->set_resolution(sl, val); + } + + if (ret) { + dev_warn(device, "%s: Set resolution - error %d\n", __func__, ret); + /* Propagate error to userspace */ + return ret; + } + SLAVE_RESOLUTION(sl) = val; + /* Reset the conversion time to default - it depends on resolution */ + SLAVE_CONV_TIME_OVERRIDE(sl) = CONV_TIME_DEFAULT; + + return size; /* always return size to avoid infinite calling */ +} + +static ssize_t temperature_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct therm_info info; + int ret = 0; + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device not supported by the driver\n", __func__); + return 0; /* No device family */ + } + + if (bulk_read_support(sl)) { + if (SLAVE_CONVERT_TRIGGERED(sl) < 0) { + dev_dbg(device, + "%s: Conversion in progress, retry later\n", + __func__); + return 0; + } else if (SLAVE_CONVERT_TRIGGERED(sl) > 0) { + /* A bulk read has been issued, read the device RAM */ + ret = read_scratchpad(sl, &info); + SLAVE_CONVERT_TRIGGERED(sl) = 0; + } else + ret = convert_t(sl, &info); + } else + ret = convert_t(sl, &info); + + if (ret < 0) { + dev_dbg(device, + "%s: Temperature data may be corrupted. err=%d\n", + __func__, ret); + return 0; + } + + return sprintf(buf, "%d\n", temperature_from_RAM(sl, info.rom)); +} + +static ssize_t ext_power_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + + if (!sl->family_data) { + dev_info(device, + "%s: Device not supported by the driver\n", __func__); + return 0; /* No device family */ + } + + /* Getting the power mode of the device {external, parasite} */ + SLAVE_POWERMODE(sl) = read_powermode(sl); + + if (SLAVE_POWERMODE(sl) < 0) { + dev_dbg(device, + "%s: Power_mode may be corrupted. err=%d\n", + __func__, SLAVE_POWERMODE(sl)); + } + return sprintf(buf, "%d\n", SLAVE_POWERMODE(sl)); +} + +static ssize_t resolution_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device not supported by the driver\n", __func__); + return 0; /* No device family */ + } + + /* get the correct function depending on the device */ + SLAVE_RESOLUTION(sl) = SLAVE_SPECIFIC_FUNC(sl)->get_resolution(sl); + if (SLAVE_RESOLUTION(sl) < 0) { + dev_dbg(device, + "%s: Resolution may be corrupted. err=%d\n", + __func__, SLAVE_RESOLUTION(sl)); + } + + return sprintf(buf, "%d\n", SLAVE_RESOLUTION(sl)); +} + +static ssize_t resolution_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + int val; + int ret = 0; + + ret = kstrtoint(buf, 10, &val); /* converting user entry to int */ + + if (ret) { /* conversion error */ + dev_info(device, + "%s: conversion error. err= %d\n", __func__, ret); + return size; /* return size to avoid call back again */ + } + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device not supported by the driver\n", __func__); + return size; /* No device family */ + } + + /* + * Don't deal with the val enterd by user, + * only device knows what is correct or not + */ + + /* get the correct function depending on the device */ + ret = SLAVE_SPECIFIC_FUNC(sl)->set_resolution(sl, val); + + if (ret) + return ret; + + SLAVE_RESOLUTION(sl) = val; + /* Reset the conversion time to default because it depends on resolution */ + SLAVE_CONV_TIME_OVERRIDE(sl) = CONV_TIME_DEFAULT; + + return size; +} + +static ssize_t eeprom_cmd_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + int ret = -EINVAL; /* Invalid argument */ + + if (size == sizeof(EEPROM_CMD_WRITE)) { + if (!strncmp(buf, EEPROM_CMD_WRITE, sizeof(EEPROM_CMD_WRITE)-1)) + ret = copy_scratchpad(sl); + } else if (size == sizeof(EEPROM_CMD_READ)) { + if (!strncmp(buf, EEPROM_CMD_READ, sizeof(EEPROM_CMD_READ)-1)) + ret = recall_eeprom(sl); + } + + if (ret) + dev_info(device, "%s: error in process %d\n", __func__, ret); + + return size; +} + +static ssize_t alarms_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + int ret; + s8 th = 0, tl = 0; + struct therm_info scratchpad; + + ret = read_scratchpad(sl, &scratchpad); + + if (!ret) { + th = scratchpad.rom[2]; /* TH is byte 2 */ + tl = scratchpad.rom[3]; /* TL is byte 3 */ + } else { + dev_info(device, + "%s: error reading alarms register %d\n", + __func__, ret); + } + + return sprintf(buf, "%hd %hd\n", tl, th); +} + +static ssize_t alarms_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + struct therm_info info; + u8 new_config_register[3]; /* array of data to be written */ + int temp, ret; + char *token = NULL; + s8 tl, th; /* 1 byte per value + temp ring order */ + char *p_args, *orig; + + p_args = orig = kmalloc(size, GFP_KERNEL); + /* Safe string copys as buf is const */ + if (!p_args) { + dev_warn(device, + "%s: error unable to allocate memory %d\n", + __func__, -ENOMEM); + return size; + } + strcpy(p_args, buf); + + /* Split string using space char */ + token = strsep(&p_args, " "); + + if (!token) { + dev_info(device, + "%s: error parsing args %d\n", __func__, -EINVAL); + goto free_m; + } + + /* Convert 1st entry to int */ + ret = kstrtoint (token, 10, &temp); + if (ret) { + dev_info(device, + "%s: error parsing args %d\n", __func__, ret); + goto free_m; + } + + tl = int_to_short(temp); + + /* Split string using space char */ + token = strsep(&p_args, " "); + if (!token) { + dev_info(device, + "%s: error parsing args %d\n", __func__, -EINVAL); + goto free_m; + } + /* Convert 2nd entry to int */ + ret = kstrtoint (token, 10, &temp); + if (ret) { + dev_info(device, + "%s: error parsing args %d\n", __func__, ret); + goto free_m; + } + + /* Prepare to cast to short by eliminating out of range values */ + th = int_to_short(temp); + + /* Reorder if required th and tl */ + if (tl > th) + swap(tl, th); + + /* + * Read the scratchpad to change only the required bits + * (th : byte 2 - tl: byte 3) + */ + ret = read_scratchpad(sl, &info); + if (!ret) { + new_config_register[0] = th; /* Byte 2 */ + new_config_register[1] = tl; /* Byte 3 */ + new_config_register[2] = info.rom[4];/* Byte 4 */ + } else { + dev_info(device, + "%s: error reading from the slave device %d\n", + __func__, ret); + goto free_m; + } + + /* Write data in the device RAM */ + if (!SLAVE_SPECIFIC_FUNC(sl)) { + dev_info(device, + "%s: Device not supported by the driver %d\n", + __func__, -ENODEV); + goto free_m; + } + + ret = SLAVE_SPECIFIC_FUNC(sl)->write_data(sl, new_config_register); + if (ret) + dev_info(device, + "%s: error writing to the slave device %d\n", + __func__, ret); + +free_m: + /* free allocated memory */ + kfree(orig); + + return size; +} + +static ssize_t therm_bulk_read_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct w1_master *dev_master = dev_to_w1_master(device); + int ret = -EINVAL; /* Invalid argument */ + + if (size == sizeof(BULK_TRIGGER_CMD)) + if (!strncmp(buf, BULK_TRIGGER_CMD, + sizeof(BULK_TRIGGER_CMD)-1)) + ret = trigger_bulk_read(dev_master); + + if (ret) + dev_info(device, + "%s: unable to trigger a bulk read on the bus. err=%d\n", + __func__, ret); + + return size; +} + +static ssize_t therm_bulk_read_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_master *dev_master = dev_to_w1_master(device); + struct w1_slave *sl = NULL; + int ret = 0; + + list_for_each_entry(sl, &dev_master->slist, w1_slave_entry) { + if (sl->family_data) { + if (bulk_read_support(sl)) { + if (SLAVE_CONVERT_TRIGGERED(sl) == -1) { + ret = -1; + goto show_result; + } + if (SLAVE_CONVERT_TRIGGERED(sl) == 1) + /* continue to check other slaves */ + ret = 1; + } + } + } +show_result: + return sprintf(buf, "%d\n", ret); +} + +static ssize_t conv_time_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device is not supported by the driver\n", __func__); + return 0; /* No device family */ + } + return sprintf(buf, "%d\n", conversion_time(sl)); +} + +static ssize_t conv_time_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + int val, ret = 0; + struct w1_slave *sl = dev_to_w1_slave(device); + + if (kstrtoint(buf, 10, &val)) /* converting user entry to int */ + return -EINVAL; + + if (check_family_data(sl)) + return -ENODEV; + + if (val != CONV_TIME_MEASURE) { + if (val >= CONV_TIME_DEFAULT) + SLAVE_CONV_TIME_OVERRIDE(sl) = val; + else + return -EINVAL; + + } else { + int conv_time; + + ret = conv_time_measure(sl, &conv_time); + if (ret) + return -EIO; + SLAVE_CONV_TIME_OVERRIDE(sl) = conv_time; + } + return size; +} + +static ssize_t features_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device not supported by the driver\n", __func__); + return 0; /* No device family */ + } + return sprintf(buf, "%u\n", SLAVE_FEATURES(sl)); +} + +static ssize_t features_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + int val, ret = 0; + bool strong_pullup; + struct w1_slave *sl = dev_to_w1_slave(device); + + ret = kstrtouint(buf, 10, &val); /* converting user entry to int */ + if (ret) + return -EINVAL; /* invalid number */ + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, "%s: Device not supported by the driver\n", __func__); + return -ENODEV; + } + + if ((val & W1_THERM_FEATURES_MASK) != val) + return -EINVAL; + + SLAVE_FEATURES(sl) = val; + + strong_pullup = (w1_strong_pullup == 2 || + (!SLAVE_POWERMODE(sl) && + w1_strong_pullup)); + + if (strong_pullup && SLAVE_FEATURES(sl) & W1_THERM_POLL_COMPLETION) { + dev_warn(&sl->dev, + "%s: W1_THERM_POLL_COMPLETION disabled in parasite power mode.\n", + __func__); + SLAVE_FEATURES(sl) &= ~W1_THERM_POLL_COMPLETION; + } + + return size; +} + +#if IS_REACHABLE(CONFIG_HWMON) +static int w1_read_temp(struct device *device, u32 attr, int channel, + long *val) +{ + struct w1_slave *sl = dev_get_drvdata(device); + struct therm_info info; + int ret; + + switch (attr) { + case hwmon_temp_input: + ret = convert_t(sl, &info); + if (ret) + return ret; + + if (!info.verdict) { + ret = -EIO; + return ret; + } + + *val = temperature_from_RAM(sl, info.rom); + ret = 0; + break; + default: + ret = -EOPNOTSUPP; + break; + } + + return ret; +} +#endif + +#define W1_42_CHAIN 0x99 +#define W1_42_CHAIN_OFF 0x3C +#define W1_42_CHAIN_OFF_INV 0xC3 +#define W1_42_CHAIN_ON 0x5A +#define W1_42_CHAIN_ON_INV 0xA5 +#define W1_42_CHAIN_DONE 0x96 +#define W1_42_CHAIN_DONE_INV 0x69 +#define W1_42_COND_READ 0x0F +#define W1_42_SUCCESS_CONFIRM_BYTE 0xAA +#define W1_42_FINISHED_BYTE 0xFF +static ssize_t w1_seq_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + ssize_t c = PAGE_SIZE; + int i; + u8 ack; + u64 rn; + struct w1_reg_num *reg_num; + int seq = 0; + + mutex_lock(&sl->master->bus_mutex); + /* Place all devices in CHAIN state */ + if (w1_reset_bus(sl->master)) + goto error; + w1_write_8(sl->master, W1_SKIP_ROM); + w1_write_8(sl->master, W1_42_CHAIN); + w1_write_8(sl->master, W1_42_CHAIN_ON); + w1_write_8(sl->master, W1_42_CHAIN_ON_INV); + msleep(sl->master->pullup_duration); + + /* check for acknowledgment */ + ack = w1_read_8(sl->master); + if (ack != W1_42_SUCCESS_CONFIRM_BYTE) + goto error; + + /* In case the bus fails to send 0xFF, limit */ + for (i = 0; i <= 64; i++) { + if (w1_reset_bus(sl->master)) + goto error; + + w1_write_8(sl->master, W1_42_COND_READ); + w1_read_block(sl->master, (u8 *)&rn, 8); + reg_num = (struct w1_reg_num *) &rn; + if (reg_num->family == W1_42_FINISHED_BYTE) + break; + if (sl->reg_num.id == reg_num->id) + seq = i; + + if (w1_reset_bus(sl->master)) + goto error; + + /* Put the device into chain DONE state */ + w1_write_8(sl->master, W1_MATCH_ROM); + w1_write_block(sl->master, (u8 *)&rn, 8); + w1_write_8(sl->master, W1_42_CHAIN); + w1_write_8(sl->master, W1_42_CHAIN_DONE); + w1_write_8(sl->master, W1_42_CHAIN_DONE_INV); + + /* check for acknowledgment */ + ack = w1_read_8(sl->master); + if (ack != W1_42_SUCCESS_CONFIRM_BYTE) + goto error; + } + + /* Exit from CHAIN state */ + if (w1_reset_bus(sl->master)) + goto error; + w1_write_8(sl->master, W1_SKIP_ROM); + w1_write_8(sl->master, W1_42_CHAIN); + w1_write_8(sl->master, W1_42_CHAIN_OFF); + w1_write_8(sl->master, W1_42_CHAIN_OFF_INV); + + /* check for acknowledgment */ + ack = w1_read_8(sl->master); + if (ack != W1_42_SUCCESS_CONFIRM_BYTE) + goto error; + mutex_unlock(&sl->master->bus_mutex); + + c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", seq); + return PAGE_SIZE - c; +error: + mutex_unlock(&sl->master->bus_mutex); + return -EIO; +} + +static int __init w1_therm_init(void) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) { + err = w1_register_family(w1_therm_families[i].f); + if (err) + w1_therm_families[i].broken = 1; + } + + return 0; +} + +static void __exit w1_therm_fini(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) + if (!w1_therm_families[i].broken) + w1_unregister_family(w1_therm_families[i].f); +} + +module_init(w1_therm_init); +module_exit(w1_therm_fini); + +MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); +MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, temperature family."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18S20)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1822)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18B20)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1825)); +MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS28EA00)); |