blob: 1627d0e954c38efa43d9fa52c174858ef63234e3 [file] [log] [blame]
/*
* 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;
};