1 /*
2  * Copyright (c) 2022 Thomas Stranger
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * @brief 1-Wire network related functions.
9  *
10  * The following procedures wrap basic w1 syscalls, they should be callable
11  * from user mode as well as supervisor mode, therefore _ZEPHYR_SUPERVISOR__
12  * is not defined for this file such that inline macros do not skip
13  * the arch_is_user_context() check.
14  */
15 
16 #include <zephyr/logging/log.h>
17 #include <zephyr/drivers/w1.h>
18 
19 LOG_MODULE_REGISTER(w1, CONFIG_W1_LOG_LEVEL);
20 
21 #define W1_SEARCH_DISCREPANCY_INIT 0
22 #define W1_SEARCH_LAST_SLAVE	   65
23 #define W1_SEARCH_NO_SLAVE	   66
24 
25 /* @brief Search bus for next slave.
26  *
27  * This function searches the next 1-Wire slave on the bus.
28  * It sets the found ROM and the last discrepancy in case more than one
29  * slave took part in the search.
30  * In case only one slave took part in the search, the discrepancy is set to
31  * W1_SEARCH_LAST_SLAVE, and in case no slave participated in the search,
32  * the discrepancy is set to W1_SEARCH_NO_SLAVE.
33  *
34  * The implementation is similar to that suggested in the Maxim Integrated
35  * application note 187.
36  * @see https://www.analog.com/media/en/technical-documentation/app-notes/1wire-search-algorithm.pdf
37  * The master reads the first ROM bit and its complementary value of all slaves.
38  * Due to physical characteristics, the value received is a
39  * logical AND of all slaves' 1st bit. Slaves only continue to
40  * participate in the search procedure if the next bit the master sends matches
41  * their own addresses' bit. This allows the master to branch through 64-bit
42  * addresses in order to detect all slaves.
43 
44  * The 1st bit received is stored in bit 1 of rom_inv_64, the 2nd in bit 2 and so
45  * on, until bit 64.
46  * As a result, each byte of the ROM has the correct bit order, but the received
47  * bytes (big-endian) stored in rom_inv_64 are in inverse byte order.
48  *
49  * Note: Filtering by families is currently not supported.
50  *
51  * @param dev              Pointer to the device structure for the w1 instance.
52  * @param command          Command to chose between normal and alarm search.
53  * @param family           This parameter is currently not supported.
54  * @param last_discrepancy This must be set to W1_SEARCH_DISCREPANCY_INIT before
55  *                         the first call, it carries the search progress for
56  *                         further calls.
57  * @param rom_inv_64       The found ROM: It must be set to zero before first
58  *                         call and carries the last found ROM for furter calls.
59  *                         The ROM is stored in inverse byte order.
60  *
61  * @retval 0      If successful.
62  * @retval -errno Negative error code in case of 1-wire read/write error.
63  */
search_slave(const struct device * dev,uint8_t command,uint8_t family,size_t * last_discrepancy,uint64_t * rom_inv_64)64 static int search_slave(const struct device *dev, uint8_t command,
65 			 uint8_t family, size_t *last_discrepancy,
66 			 uint64_t *rom_inv_64)
67 {
68 	int ret;
69 	size_t next_discrepancy;
70 	bool last_id_bit;
71 	bool last_complement_id_bit;
72 
73 	ARG_UNUSED(family);
74 	__ASSERT_NO_MSG(command == W1_CMD_SEARCH_ROM ||
75 			command == W1_CMD_SEARCH_ALARM);
76 
77 	ret = w1_reset_bus(dev);
78 	if (ret < 0) {
79 		return ret;
80 	}
81 	if (ret == 0) {
82 		*last_discrepancy = W1_SEARCH_NO_SLAVE;
83 		return 0;
84 	}
85 
86 	ret = w1_write_byte(dev, command);
87 	if (ret < 0) {
88 		return ret;
89 	}
90 	next_discrepancy = W1_SEARCH_LAST_SLAVE;
91 
92 	for (size_t id_bit_nr = 1; id_bit_nr < W1_SEARCH_LAST_SLAVE; id_bit_nr++) {
93 		ret = w1_read_bit(dev);
94 		if (ret < 0) {
95 			return ret;
96 		}
97 		last_id_bit = (bool)ret;
98 		ret = w1_read_bit(dev);
99 		if (ret < 0) {
100 			return ret;
101 		}
102 		last_complement_id_bit = (bool)ret;
103 
104 		if (last_id_bit && last_complement_id_bit) {
105 			/*
106 			 * No slave participating:
107 			 * We can stop following the branch.
108 			 */
109 			LOG_DBG("No slave paricipating");
110 			*last_discrepancy = W1_SEARCH_NO_SLAVE;
111 			return 0;
112 		} else if (last_id_bit != last_complement_id_bit) {
113 			/*
114 			 * All slaves connected have same ROM bit value:
115 			 * We can directly follow last_id_bit branch.
116 			 */
117 		} else {
118 			/*
119 			 * Discrepancy detected: bit value at id_bit_nr does
120 			 * not match for all slaves on the bus.
121 			 */
122 			if ((id_bit_nr > *last_discrepancy) ||
123 			    ((id_bit_nr < *last_discrepancy) &&
124 			     (*rom_inv_64 & BIT64(id_bit_nr - 1)))) {
125 				/*
126 				 * - id_bit_nr > last_discrepancy:
127 				 *     Start always w/ branch of 1s
128 				 * - id_bit_nr < last_discrepancy:
129 				 *     Follow same branch as before
130 				 */
131 				last_id_bit = true;
132 				next_discrepancy = id_bit_nr;
133 			} else {
134 				/*
135 				 * - id_bit_nr == last_discrepancy:
136 				 *     1-path already done, therefore go 0 path
137 				 * - id_bit_nr < last_discrepancy:
138 				 *     Follow same branch as before
139 				 */
140 			}
141 		}
142 
143 		/*
144 		 * Send and store the chosen bit: all not matching slaves will
145 		 * no longer participate in this search until they are reset.
146 		 */
147 		ret = w1_write_bit(dev, last_id_bit);
148 		if (ret < 0) {
149 			return ret;
150 		}
151 		*rom_inv_64 &= ~BIT64(id_bit_nr - 1);
152 		*rom_inv_64 |= last_id_bit ? BIT64(id_bit_nr - 1) : 0;
153 	}
154 
155 	*last_discrepancy = next_discrepancy;
156 	return 0;
157 }
158 
z_impl_w1_search_bus(const struct device * dev,uint8_t command,uint8_t family,w1_search_callback_t callback,void * user_data)159 int z_impl_w1_search_bus(const struct device *dev, uint8_t command,
160 			 uint8_t family, w1_search_callback_t callback,
161 			 void *user_data)
162 {
163 	size_t last_discrepancy = W1_SEARCH_DISCREPANCY_INIT;
164 	uint64_t found_rom_inv_64 = 0;
165 	struct w1_rom found_rom = { 0 };
166 	int found_cnt = 0;
167 	int ret;
168 
169 	(void)w1_lock_bus(dev);
170 
171 	do {
172 		ret = search_slave(dev, command, family, &last_discrepancy,
173 				    &found_rom_inv_64);
174 		if (ret < 0) {
175 			found_cnt = ret;
176 			break;
177 		}
178 		if (last_discrepancy == W1_SEARCH_NO_SLAVE) {
179 			break;
180 		}
181 
182 		found_cnt++;
183 		/*
184 		 * ROM is stored in found_rom_inv_64 in "inverse byte order" =>
185 		 * Only big-endian targets need to swap, such that struct's
186 		 * bytes are stored in big-endian byte order.
187 		 */
188 		if (IS_ENABLED(CONFIG_BIG_ENDIAN)) {
189 			sys_memcpy_swap(&found_rom, &found_rom_inv_64, 8);
190 		} else {
191 			*(uint64_t *)&found_rom = found_rom_inv_64;
192 		}
193 		LOG_DBG("ROM found: nr %u, %016llx", found_cnt,
194 			w1_rom_to_uint64(&found_rom));
195 
196 		if (callback != NULL) {
197 			callback(found_rom, user_data);
198 		}
199 
200 	} while (last_discrepancy != W1_SEARCH_LAST_SLAVE);
201 
202 	(void)w1_unlock_bus(dev);
203 	return found_cnt;
204 }
205 
w1_read_rom(const struct device * dev,struct w1_rom * rom)206 int w1_read_rom(const struct device *dev, struct w1_rom *rom)
207 {
208 	int ret;
209 
210 	(void)w1_lock_bus(dev);
211 	ret = w1_reset_bus(dev);
212 	if (ret == 0) {
213 		ret = -ENODEV;
214 		goto out;
215 	}
216 	if (ret < 0) {
217 		goto out;
218 	}
219 
220 	ret = w1_write_byte(dev, W1_CMD_READ_ROM);
221 	if (ret < 0) {
222 		goto out;
223 	}
224 	ret = w1_read_block(dev, (uint8_t *)rom, sizeof(struct w1_rom));
225 	if (ret < 0) {
226 		goto out;
227 	}
228 	if (w1_crc8((uint8_t *)rom, sizeof(struct w1_rom)) != 0) {
229 		ret = -EIO;
230 	}
231 
232 out:
233 	(void)w1_unlock_bus(dev);
234 	return ret;
235 };
236 
match_rom(const struct device * dev,const struct w1_slave_config * config)237 static int match_rom(const struct device *dev, const struct w1_slave_config *config)
238 {
239 	int ret;
240 	uint8_t cmd;
241 
242 	if (!config->overdrive) {
243 		if (w1_configure(dev, W1_SETTING_SPEED, 0) < 0) {
244 			return -EIO;
245 		}
246 	}
247 
248 	ret = w1_reset_bus(dev);
249 	if (ret == 0) {
250 		return -ENODEV;
251 	}
252 	if (ret < 0) {
253 		return ret;
254 	}
255 
256 	cmd = config->overdrive ? W1_CMD_OVERDRIVE_MATCH_ROM : W1_CMD_MATCH_ROM;
257 	ret = w1_write_byte(dev, cmd);
258 	if (ret < 0) {
259 		return ret;
260 	}
261 	ret = w1_write_block(dev, (uint8_t *)&config->rom, 8);
262 	if (ret < 0) {
263 		return ret;
264 	}
265 
266 	if (config->overdrive) {
267 		if (w1_configure(dev, W1_SETTING_SPEED, 1) < 0) {
268 			return -EIO;
269 		}
270 	}
271 
272 	return 0;
273 };
274 
w1_match_rom(const struct device * dev,const struct w1_slave_config * config)275 int w1_match_rom(const struct device *dev, const struct w1_slave_config *config)
276 {
277 	int ret;
278 
279 	(void)w1_lock_bus(dev);
280 	ret = match_rom(dev, config);
281 	(void)w1_unlock_bus(dev);
282 	return ret;
283 }
284 
w1_resume_command(const struct device * dev)285 int w1_resume_command(const struct device *dev)
286 {
287 	int ret;
288 
289 	(void)w1_lock_bus(dev);
290 	ret = w1_reset_bus(dev);
291 	if (ret == 0) {
292 		ret = -ENODEV;
293 		goto out;
294 	}
295 	if (ret < 0) {
296 		goto out;
297 	}
298 
299 	ret = w1_write_byte(dev, W1_CMD_RESUME);
300 out:
301 	(void)w1_unlock_bus(dev);
302 	return ret;
303 }
304 
skip_rom(const struct device * dev,const struct w1_slave_config * config)305 static int skip_rom(const struct device *dev, const struct w1_slave_config *config)
306 {
307 	int ret;
308 	uint8_t cmd;
309 
310 	if (!config->overdrive) {
311 		if (w1_configure(dev, W1_SETTING_SPEED, 0) < 0) {
312 			return -EIO;
313 		}
314 	}
315 
316 	ret = w1_reset_bus(dev);
317 	if (ret == 0) {
318 		return -ENODEV;
319 	}
320 	if (ret < 0) {
321 		return ret;
322 	}
323 
324 	cmd = config->overdrive ? W1_CMD_OVERDRIVE_SKIP_ROM : W1_CMD_SKIP_ROM;
325 	ret = w1_write_byte(dev, cmd);
326 	if (ret < 0) {
327 		return ret;
328 	}
329 
330 	if (config->overdrive) {
331 		if (w1_configure(dev, W1_SETTING_SPEED, 1) < 0) {
332 			return -EIO;
333 		}
334 	}
335 
336 	return 0;
337 }
338 
w1_skip_rom(const struct device * dev,const struct w1_slave_config * config)339 int w1_skip_rom(const struct device *dev, const struct w1_slave_config *config)
340 {
341 	int ret;
342 
343 	(void)w1_lock_bus(dev);
344 	ret = skip_rom(dev, config);
345 	(void)w1_unlock_bus(dev);
346 	return ret;
347 }
348 
reset_select(const struct device * dev,const struct w1_slave_config * config)349 static int reset_select(const struct device *dev, const struct w1_slave_config *config)
350 {
351 	if (IS_ENABLED(CONFIG_W1_NET_FORCE_MULTIDROP_ADDRESSING) || w1_get_slave_count(dev) > 1) {
352 		return match_rom(dev, config);
353 	}
354 
355 	return skip_rom(dev, config);
356 }
357 
w1_reset_select(const struct device * dev,const struct w1_slave_config * config)358 int w1_reset_select(const struct device *dev, const struct w1_slave_config *config)
359 {
360 	int ret;
361 
362 	(void)w1_lock_bus(dev);
363 	ret = reset_select(dev, config);
364 	(void)w1_unlock_bus(dev);
365 	return ret;
366 }
367 
write_read(const struct device * dev,const struct w1_slave_config * config,const uint8_t * write_buf,size_t write_len,uint8_t * read_buf,size_t read_len)368 static int write_read(const struct device *dev, const struct w1_slave_config *config,
369 		      const uint8_t *write_buf, size_t write_len,
370 		      uint8_t *read_buf, size_t read_len)
371 {
372 	int ret;
373 
374 	ret = reset_select(dev, config);
375 	if (ret != 0) {
376 		return ret;
377 	}
378 
379 	ret = w1_write_block(dev, write_buf, write_len);
380 	if (ret < 0) {
381 		return ret;
382 	}
383 
384 	if (read_buf == NULL && read_len > 0) {
385 		return -EIO;
386 	}
387 	return w1_read_block(dev, read_buf, read_len);
388 };
389 
w1_write_read(const struct device * dev,const struct w1_slave_config * config,const uint8_t * write_buf,size_t write_len,uint8_t * read_buf,size_t read_len)390 int w1_write_read(const struct device *dev, const struct w1_slave_config *config,
391 		  const uint8_t *write_buf, size_t write_len,
392 		  uint8_t *read_buf, size_t read_len)
393 {
394 	int ret;
395 
396 	(void)w1_lock_bus(dev);
397 	ret = write_read(dev, config, write_buf, write_len, read_buf, read_len);
398 	(void)w1_unlock_bus(dev);
399 	return ret;
400 };
401