| // 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); |