/* * Copyright (c) 2022 Thomas Stranger * * SPDX-License-Identifier: Apache-2.0 */ /** * @brief 1-Wire network related functions. * * The following procedures wrap basic w1 syscalls, they should be callable * from user mode as well as supervisor mode, therefore _ZEPHYR_SUPERVISOR__ * is not defined for this file such that inline macros do not skip * the arch_is_user_context() check. */ #include #include LOG_MODULE_REGISTER(w1, CONFIG_W1_LOG_LEVEL); #define W1_SEARCH_DISCREPANCY_INIT 0 #define W1_SEARCH_LAST_SLAVE 65 #define W1_SEARCH_NO_SLAVE 66 /* @brief Search bus for next slave. * * This function searches the next 1-Wire slave on the bus. * It sets the found ROM and the last discrepancy in case more than one * slave took part in the search. * In case only one slave took part in the search, the discrepancy is set to * W1_SEARCH_LAST_SLAVE, and in case no slave participated in the search, * the discrepancy is set to W1_SEARCH_NO_SLAVE. * * The implementation is similar to suggested in the maxim application note 187. * The master reads the first ROM bit and its complementary value of all slaves. * Due to physical characteristics, the value received is a * logical AND of all slaves' 1st bit. Slaves only continue to * participate in the search procedure if the next bit the master sends matches * their own addresses' bit. This allows the master to branch through 64-bit * addresses in order to detect all slaves. * The 1st bit received is stored in bit 1 of rom_inv_64, the 2nd in bit 2 and so * on, until bit 64. * As a result, each byte of the ROM has the correct bit order, but the received * bytes (big-endian) stored in rom_inv_64 are in inverse byte order. * * Note: Filtering by families is currently not supported. * * @param dev Pointer to the device structure for the w1 instance. * @param command Command to chose between normal and alarm search. * @param family This parameter is currently not supported. * @param last_discrepancy This must be set to W1_SEARCH_DISCREPANCY_INIT before * the first call, it carries the search progress for * further calls. * @param rom_inv_64 The found ROM: It must be set to zero before first * call and carries the last found ROM for furter calls. * The ROM is stored in inverse byte order. * * @retval 0 If successful. * @retval -errno Negative error code in case of 1-wire read/write error. */ static int search_slave(const struct device *dev, uint8_t command, uint8_t family, size_t *last_discrepancy, uint64_t *rom_inv_64) { int ret; size_t next_discrepancy; bool last_id_bit; bool last_complement_id_bit; ARG_UNUSED(family); __ASSERT_NO_MSG(command == W1_CMD_SEARCH_ROM || command == W1_CMD_SEARCH_ALARM); ret = w1_reset_bus(dev); if (ret < 0) { return ret; } if (ret == 0) { *last_discrepancy = W1_SEARCH_NO_SLAVE; return 0; } ret = w1_write_byte(dev, command); if (ret < 0) { return ret; } next_discrepancy = W1_SEARCH_LAST_SLAVE; for (size_t id_bit_nr = 1; id_bit_nr < W1_SEARCH_LAST_SLAVE; id_bit_nr++) { ret = w1_read_bit(dev); if (ret < 0) { return ret; } last_id_bit = (bool)ret; ret = w1_read_bit(dev); if (ret < 0) { return ret; } last_complement_id_bit = (bool)ret; if (last_id_bit && last_complement_id_bit) { /* * No slave participating: * We can stop following the branch. */ LOG_DBG("No slave paricipating"); *last_discrepancy = W1_SEARCH_NO_SLAVE; return 0; } else if (last_id_bit != last_complement_id_bit) { /* * All slaves connected have same ROM bit value: * We can directly follow last_id_bit branch. */ } else { /* * Discrepancy detected: bit value at id_bit_nr does * not match for all slaves on the bus. */ if ((id_bit_nr > *last_discrepancy) || ((id_bit_nr < *last_discrepancy) && (*rom_inv_64 & BIT64(id_bit_nr - 1)))) { /* * - id_bit_nr > last_discrepancy: * Start always w/ branch of 1s * - id_bit_nr < last_discrepancy: * Follow same branch as before */ last_id_bit = true; next_discrepancy = id_bit_nr; } else { /* * - id_bit_nr == last_discrepancy: * 1-path already done, therefore go 0 path * - id_bit_nr < last_discrepancy: * Follow same branch as before */ } } /* * Send and store the chosen bit: all not matching slaves will * no longer participate in this search until they are reset. */ ret = w1_write_bit(dev, last_id_bit); if (ret < 0) { return ret; } *rom_inv_64 &= ~BIT64(id_bit_nr - 1); *rom_inv_64 |= last_id_bit ? BIT64(id_bit_nr - 1) : 0; } *last_discrepancy = next_discrepancy; return 0; } int z_impl_w1_search_bus(const struct device *dev, uint8_t command, uint8_t family, w1_search_callback_t callback, void *user_data) { size_t last_discrepancy = W1_SEARCH_DISCREPANCY_INIT; uint64_t found_rom_inv_64 = 0; struct w1_rom found_rom = { 0 }; int found_cnt = 0; int ret; (void)w1_lock_bus(dev); do { ret = search_slave(dev, command, family, &last_discrepancy, &found_rom_inv_64); if (ret < 0) { found_cnt = ret; break; } if (last_discrepancy == W1_SEARCH_NO_SLAVE) { break; } found_cnt++; /* * ROM is stored in found_rom_inv_64 in "inverse byte order" => * Only big-endian targets need to swap, such that struct's * bytes are stored in big-endian byte order. */ if (IS_ENABLED(CONFIG_BIG_ENDIAN)) { sys_memcpy_swap(&found_rom, &found_rom_inv_64, 8); } else { *(uint64_t *)&found_rom = found_rom_inv_64; } LOG_DBG("ROM found: nr %u, %016llx", found_cnt, w1_rom_to_uint64(&found_rom)); if (callback != NULL) { callback(found_rom, user_data); } } while (last_discrepancy != W1_SEARCH_LAST_SLAVE); (void)w1_unlock_bus(dev); return found_cnt; } int w1_read_rom(const struct device *dev, struct w1_rom *rom) { int ret; (void)w1_lock_bus(dev); ret = w1_reset_bus(dev); if (ret == 0) { ret = -ENODEV; goto out; } if (ret < 0) { goto out; } ret = w1_write_byte(dev, W1_CMD_READ_ROM); if (ret < 0) { goto out; } ret = w1_read_block(dev, (uint8_t *)rom, sizeof(struct w1_rom)); if (ret < 0) { goto out; } if (w1_crc8((uint8_t *)rom, sizeof(struct w1_rom)) != 0) { ret = -EIO; } out: (void)w1_unlock_bus(dev); return ret; }; static int match_rom(const struct device *dev, const struct w1_slave_config *config) { int ret; uint8_t cmd; if (!config->overdrive) { if (w1_configure(dev, W1_SETTING_SPEED, 0) < 0) { return -EIO; } } ret = w1_reset_bus(dev); if (ret == 0) { return -ENODEV; } if (ret < 0) { return ret; } cmd = config->overdrive ? W1_CMD_OVERDRIVE_MATCH_ROM : W1_CMD_MATCH_ROM; ret = w1_write_byte(dev, cmd); if (ret < 0) { return ret; } ret = w1_write_block(dev, (uint8_t *)&config->rom, 8); if (ret < 0) { return ret; } if (config->overdrive) { if (w1_configure(dev, W1_SETTING_SPEED, 1) < 0) { return -EIO; } } return 0; }; int w1_match_rom(const struct device *dev, const struct w1_slave_config *config) { int ret; (void)w1_lock_bus(dev); ret = match_rom(dev, config); (void)w1_unlock_bus(dev); return ret; } int w1_resume_command(const struct device *dev) { int ret; (void)w1_lock_bus(dev); ret = w1_reset_bus(dev); if (ret == 0) { ret = -ENODEV; goto out; } if (ret < 0) { goto out; } ret = w1_write_byte(dev, W1_CMD_RESUME); out: (void)w1_unlock_bus(dev); return ret; } static int skip_rom(const struct device *dev, const struct w1_slave_config *config) { int ret; uint8_t cmd; if (!config->overdrive) { if (w1_configure(dev, W1_SETTING_SPEED, 0) < 0) { return -EIO; } } ret = w1_reset_bus(dev); if (ret == 0) { return -ENODEV; } if (ret < 0) { return ret; } cmd = config->overdrive ? W1_CMD_OVERDRIVE_SKIP_ROM : W1_CMD_SKIP_ROM; ret = w1_write_byte(dev, cmd); if (ret < 0) { return ret; } if (config->overdrive) { if (w1_configure(dev, W1_SETTING_SPEED, 1) < 0) { return -EIO; } } return 0; } int w1_skip_rom(const struct device *dev, const struct w1_slave_config *config) { int ret; (void)w1_lock_bus(dev); ret = skip_rom(dev, config); (void)w1_unlock_bus(dev); return ret; } static int reset_select(const struct device *dev, const struct w1_slave_config *config) { if (IS_ENABLED(CONFIG_W1_NET_FORCE_MULTIDROP_ADDRESSING) || w1_get_slave_count(dev) > 1) { return match_rom(dev, config); } return skip_rom(dev, config); } int w1_reset_select(const struct device *dev, const struct w1_slave_config *config) { int ret; (void)w1_lock_bus(dev); ret = reset_select(dev, config); (void)w1_unlock_bus(dev); return ret; } static int 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) { int ret; ret = reset_select(dev, config); if (ret != 0) { return ret; } ret = w1_write_block(dev, write_buf, write_len); if (ret < 0) { return ret; } if (read_buf == NULL && read_len > 0) { return -EIO; } return w1_read_block(dev, read_buf, read_len); }; int 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) { int ret; (void)w1_lock_bus(dev); ret = write_read(dev, config, write_buf, write_len, read_buf, read_len); (void)w1_unlock_bus(dev); return ret; };