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