1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * w1_ds2405.c |
4 | * |
5 | * Copyright (c) 2017 Maciej S. Szmigiero <mail@maciej.szmigiero.name> |
6 | * Based on w1_therm.c copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/moduleparam.h> |
13 | #include <linux/mutex.h> |
14 | #include <linux/string.h> |
15 | #include <linux/types.h> |
16 | |
17 | #include <linux/w1.h> |
18 | |
19 | #define W1_FAMILY_DS2405 0x05 |
20 | |
21 | MODULE_LICENSE("GPL" ); |
22 | MODULE_AUTHOR("Maciej S. Szmigiero <mail@maciej.szmigiero.name>" ); |
23 | MODULE_DESCRIPTION("Driver for 1-wire Dallas DS2405 PIO." ); |
24 | MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2405)); |
25 | |
26 | static int w1_ds2405_select(struct w1_slave *sl, bool only_active) |
27 | { |
28 | struct w1_master *dev = sl->master; |
29 | |
30 | u64 dev_addr = le64_to_cpu(*(u64 *)&sl->reg_num); |
31 | unsigned int bit_ctr; |
32 | |
33 | if (w1_reset_bus(dev) != 0) |
34 | return 0; |
35 | |
36 | /* |
37 | * We cannot use a normal Match ROM command |
38 | * since doing so would toggle PIO state |
39 | */ |
40 | w1_write_8(dev, only_active ? W1_ALARM_SEARCH : W1_SEARCH); |
41 | |
42 | for (bit_ctr = 0; bit_ctr < 64; bit_ctr++) { |
43 | int bit2send = !!(dev_addr & BIT(bit_ctr)); |
44 | u8 ret; |
45 | |
46 | ret = w1_triplet(dev, bdir: bit2send); |
47 | |
48 | if ((ret & (BIT(0) | BIT(1))) == |
49 | (BIT(0) | BIT(1))) /* no devices found */ |
50 | return 0; |
51 | |
52 | if (!!(ret & BIT(2)) != bit2send) |
53 | /* wrong direction taken - no such device */ |
54 | return 0; |
55 | } |
56 | |
57 | return 1; |
58 | } |
59 | |
60 | static int w1_ds2405_read_pio(struct w1_slave *sl) |
61 | { |
62 | if (w1_ds2405_select(sl, only_active: true)) |
63 | return 0; /* "active" means PIO is low */ |
64 | |
65 | if (w1_ds2405_select(sl, only_active: false)) |
66 | return 1; |
67 | |
68 | return -ENODEV; |
69 | } |
70 | |
71 | static ssize_t state_show(struct device *device, |
72 | struct device_attribute *attr, char *buf) |
73 | { |
74 | struct w1_slave *sl = dev_to_w1_slave(dev: device); |
75 | struct w1_master *dev = sl->master; |
76 | |
77 | int ret; |
78 | ssize_t f_retval; |
79 | u8 state; |
80 | |
81 | ret = mutex_lock_interruptible(&dev->bus_mutex); |
82 | if (ret) |
83 | return ret; |
84 | |
85 | if (!w1_ds2405_select(sl, only_active: false)) { |
86 | f_retval = -ENODEV; |
87 | goto out_unlock; |
88 | } |
89 | |
90 | state = w1_read_8(dev); |
91 | if (state != 0 && |
92 | state != 0xff) { |
93 | dev_err(device, "non-consistent state %x\n" , state); |
94 | f_retval = -EIO; |
95 | goto out_unlock; |
96 | } |
97 | |
98 | *buf = state ? '1' : '0'; |
99 | f_retval = 1; |
100 | |
101 | out_unlock: |
102 | w1_reset_bus(dev); |
103 | mutex_unlock(lock: &dev->bus_mutex); |
104 | |
105 | return f_retval; |
106 | } |
107 | |
108 | static ssize_t output_show(struct device *device, |
109 | struct device_attribute *attr, char *buf) |
110 | { |
111 | struct w1_slave *sl = dev_to_w1_slave(dev: device); |
112 | struct w1_master *dev = sl->master; |
113 | |
114 | int ret; |
115 | ssize_t f_retval; |
116 | |
117 | ret = mutex_lock_interruptible(&dev->bus_mutex); |
118 | if (ret) |
119 | return ret; |
120 | |
121 | ret = w1_ds2405_read_pio(sl); |
122 | if (ret < 0) { |
123 | f_retval = ret; |
124 | goto out_unlock; |
125 | } |
126 | |
127 | *buf = ret ? '1' : '0'; |
128 | f_retval = 1; |
129 | |
130 | out_unlock: |
131 | w1_reset_bus(dev); |
132 | mutex_unlock(lock: &dev->bus_mutex); |
133 | |
134 | return f_retval; |
135 | } |
136 | |
137 | static ssize_t output_store(struct device *device, |
138 | struct device_attribute *attr, |
139 | const char *buf, size_t count) |
140 | { |
141 | struct w1_slave *sl = dev_to_w1_slave(dev: device); |
142 | struct w1_master *dev = sl->master; |
143 | |
144 | int ret, current_pio; |
145 | unsigned int val; |
146 | ssize_t f_retval; |
147 | |
148 | if (count < 1) |
149 | return -EINVAL; |
150 | |
151 | if (sscanf(buf, " %u%n" , &val, &ret) < 1) |
152 | return -EINVAL; |
153 | |
154 | if (val != 0 && val != 1) |
155 | return -EINVAL; |
156 | |
157 | f_retval = ret; |
158 | |
159 | ret = mutex_lock_interruptible(&dev->bus_mutex); |
160 | if (ret) |
161 | return ret; |
162 | |
163 | current_pio = w1_ds2405_read_pio(sl); |
164 | if (current_pio < 0) { |
165 | f_retval = current_pio; |
166 | goto out_unlock; |
167 | } |
168 | |
169 | if (current_pio == val) |
170 | goto out_unlock; |
171 | |
172 | if (w1_reset_bus(dev) != 0) { |
173 | f_retval = -ENODEV; |
174 | goto out_unlock; |
175 | } |
176 | |
177 | /* |
178 | * can't use w1_reset_select_slave() here since it uses Skip ROM if |
179 | * there is only one device on bus |
180 | */ |
181 | do { |
182 | u64 dev_addr = le64_to_cpu(*(u64 *)&sl->reg_num); |
183 | u8 cmd[9]; |
184 | |
185 | cmd[0] = W1_MATCH_ROM; |
186 | memcpy(&cmd[1], &dev_addr, sizeof(dev_addr)); |
187 | |
188 | w1_write_block(dev, cmd, sizeof(cmd)); |
189 | } while (0); |
190 | |
191 | out_unlock: |
192 | w1_reset_bus(dev); |
193 | mutex_unlock(lock: &dev->bus_mutex); |
194 | |
195 | return f_retval; |
196 | } |
197 | |
198 | static DEVICE_ATTR_RO(state); |
199 | static DEVICE_ATTR_RW(output); |
200 | |
201 | static struct attribute *w1_ds2405_attrs[] = { |
202 | &dev_attr_state.attr, |
203 | &dev_attr_output.attr, |
204 | NULL |
205 | }; |
206 | |
207 | ATTRIBUTE_GROUPS(w1_ds2405); |
208 | |
209 | static const struct w1_family_ops w1_ds2405_fops = { |
210 | .groups = w1_ds2405_groups |
211 | }; |
212 | |
213 | static struct w1_family w1_family_ds2405 = { |
214 | .fid = W1_FAMILY_DS2405, |
215 | .fops = &w1_ds2405_fops |
216 | }; |
217 | |
218 | module_w1_family(w1_family_ds2405); |
219 | |