You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
9.6 KiB
400 lines
9.6 KiB
/* |
|
* 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 <zephyr/logging/log.h> |
|
#include <zephyr/drivers/w1.h> |
|
|
|
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 that suggested in the Maxim Integrated |
|
* application note 187. |
|
* @see https://www.analog.com/media/en/technical-documentation/app-notes/1wire-search-algorithm.pdf |
|
* 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; |
|
};
|
|
|