Links and the manufacturer name are updated from maxim to analog for the 1-wire subsystem and the related ds18b20 sensor. After the acquisition of Maxim Integrated the documentation of these devices has been moved to the analog.com website. Redirects exist, so they are not broken yet, but we should not rely on that. Signed-off-by: Thomas Stranger <thomas.stranger@outlook.com>
400 lines
9.6 KiB
C
400 lines
9.6 KiB
C
/*
|
|
* 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;
|
|
};
|