329 lines
8.0 KiB
C
329 lines
8.0 KiB
C
/*
|
|
* Copyright (c) 2023 Hudson C. Dalpra
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT zephyr_w1_gpio
|
|
|
|
/**
|
|
* @brief 1-Wire Bus Master driver using Zephyr GPIO interface.
|
|
*
|
|
* This file contains the implementation of the 1-Wire Bus Master driver using
|
|
* the Zephyr GPIO interface. The driver is based on GPIO bit-banging and
|
|
* follows the timing specifications for 1-Wire communication.
|
|
*
|
|
* The driver supports both standard speed and overdrive speed modes.
|
|
*
|
|
* This driver is heavily based on the w1_zephyr_serial.c driver and the
|
|
* technical documentation from Maxim Integrated.
|
|
*
|
|
* - w1_zephyr_serial.c: drivers/w1/w1_zephyr_serial.c
|
|
* - Maxim Integrated 1-Wire Communication Through Software:
|
|
* https://www.analog.com/en/technical-articles/1wire-communication-through-software.html
|
|
*/
|
|
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/w1.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/kernel.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(w1_gpio, CONFIG_W1_LOG_LEVEL);
|
|
|
|
/*
|
|
* The time critical sections are used to ensure that the timing
|
|
* between communication operations is correct.
|
|
*/
|
|
#if defined(CONFIG_W1_ZEPHYR_GPIO_TIME_CRITICAL)
|
|
#define W1_GPIO_ENTER_CRITICAL() irq_lock()
|
|
#define W1_GPIO_EXIT_CRITICAL(key) irq_unlock(key)
|
|
#define W1_GPIO_WAIT_US(us) k_busy_wait(us)
|
|
#else
|
|
#define W1_GPIO_ENTER_CRITICAL() 0u
|
|
#define W1_GPIO_EXIT_CRITICAL(key) (void)key
|
|
#define W1_GPIO_WAIT_US(us) k_usleep(us)
|
|
#endif
|
|
|
|
/*
|
|
* Standard timing between communication operations:
|
|
*/
|
|
#define W1_GPIO_TIMING_STD_A 6u
|
|
#define W1_GPIO_TIMING_STD_B 64u
|
|
#define W1_GPIO_TIMING_STD_C 60u
|
|
#define W1_GPIO_TIMING_STD_D 10u
|
|
#define W1_GPIO_TIMING_STD_E 9u
|
|
#define W1_GPIO_TIMING_STD_F 55u
|
|
#define W1_GPIO_TIMING_STD_G 0u
|
|
#define W1_GPIO_TIMING_STD_H 480u
|
|
#define W1_GPIO_TIMING_STD_I 70u
|
|
#define W1_GPIO_TIMING_STD_J 410u
|
|
|
|
/*
|
|
* Overdrive timing between communication operations:
|
|
*
|
|
* Not completely correct since the overdrive communication requires
|
|
* delays of 2.5us, 7.5us and 8.5us.
|
|
* The delays are approximated by flooring the values.
|
|
*/
|
|
#define W1_GPIO_TIMING_OD_A 1u
|
|
#define W1_GPIO_TIMING_OD_B 7u
|
|
#define W1_GPIO_TIMING_OD_C 7u
|
|
#define W1_GPIO_TIMING_OD_D 2u
|
|
#define W1_GPIO_TIMING_OD_E 1u
|
|
#define W1_GPIO_TIMING_OD_F 7u
|
|
#define W1_GPIO_TIMING_OD_G 2u
|
|
#define W1_GPIO_TIMING_OD_H 70u
|
|
#define W1_GPIO_TIMING_OD_I 8u
|
|
#define W1_GPIO_TIMING_OD_J 40u
|
|
|
|
struct w1_gpio_timing {
|
|
uint16_t a;
|
|
uint16_t b;
|
|
uint16_t c;
|
|
uint16_t d;
|
|
uint16_t e;
|
|
uint16_t f;
|
|
uint16_t g;
|
|
uint16_t h;
|
|
uint16_t i;
|
|
uint16_t j;
|
|
};
|
|
|
|
struct w1_gpio_config {
|
|
/** w1 master config, common to all drivers */
|
|
struct w1_master_config master_config;
|
|
/** GPIO device used for 1-Wire communication */
|
|
const struct gpio_dt_spec spec;
|
|
};
|
|
|
|
struct w1_gpio_data {
|
|
/** w1 master data, common to all drivers */
|
|
struct w1_master_data master_data;
|
|
/** timing parameters for 1-Wire communication */
|
|
const struct w1_gpio_timing *timing;
|
|
/** overdrive speed mode active */
|
|
bool overdrive_active;
|
|
};
|
|
|
|
static const struct w1_gpio_timing std = {
|
|
.a = W1_GPIO_TIMING_STD_A,
|
|
.b = W1_GPIO_TIMING_STD_B,
|
|
.c = W1_GPIO_TIMING_STD_C,
|
|
.d = W1_GPIO_TIMING_STD_D,
|
|
.e = W1_GPIO_TIMING_STD_E,
|
|
.f = W1_GPIO_TIMING_STD_F,
|
|
.g = W1_GPIO_TIMING_STD_G,
|
|
.h = W1_GPIO_TIMING_STD_H,
|
|
.i = W1_GPIO_TIMING_STD_I,
|
|
.j = W1_GPIO_TIMING_STD_J,
|
|
};
|
|
|
|
static const struct w1_gpio_timing od = {
|
|
.a = W1_GPIO_TIMING_OD_A,
|
|
.b = W1_GPIO_TIMING_OD_B,
|
|
.c = W1_GPIO_TIMING_OD_C,
|
|
.d = W1_GPIO_TIMING_OD_D,
|
|
.e = W1_GPIO_TIMING_OD_E,
|
|
.f = W1_GPIO_TIMING_OD_F,
|
|
.g = W1_GPIO_TIMING_OD_G,
|
|
.h = W1_GPIO_TIMING_OD_H,
|
|
.i = W1_GPIO_TIMING_OD_I,
|
|
.j = W1_GPIO_TIMING_OD_J,
|
|
};
|
|
|
|
static int w1_gpio_reset_bus(const struct device *dev)
|
|
{
|
|
const struct w1_gpio_config *cfg = dev->config;
|
|
const struct w1_gpio_data *data = dev->data;
|
|
|
|
const struct gpio_dt_spec *spec = &cfg->spec;
|
|
const struct w1_gpio_timing *timing = data->timing;
|
|
|
|
int ret = 0;
|
|
unsigned int key = W1_GPIO_ENTER_CRITICAL();
|
|
|
|
W1_GPIO_WAIT_US(timing->g);
|
|
ret = gpio_pin_set_dt(spec, 0);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
W1_GPIO_WAIT_US(timing->h);
|
|
ret = gpio_pin_set_dt(spec, 1);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
W1_GPIO_WAIT_US(timing->i);
|
|
ret = gpio_pin_get_dt(spec);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
ret ^= 0x01;
|
|
|
|
W1_GPIO_WAIT_US(timing->j);
|
|
out:
|
|
W1_GPIO_EXIT_CRITICAL(key);
|
|
return ret;
|
|
}
|
|
|
|
static int w1_gpio_read_bit(const struct device *dev)
|
|
{
|
|
const struct w1_gpio_config *cfg = dev->config;
|
|
const struct w1_gpio_data *data = dev->data;
|
|
|
|
const struct gpio_dt_spec *spec = &cfg->spec;
|
|
const struct w1_gpio_timing *timing = data->timing;
|
|
|
|
int ret = 0;
|
|
unsigned int key = W1_GPIO_ENTER_CRITICAL();
|
|
|
|
ret = gpio_pin_set_dt(spec, 0);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
W1_GPIO_WAIT_US(timing->a);
|
|
ret = gpio_pin_set_dt(spec, 1);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
W1_GPIO_WAIT_US(timing->e);
|
|
ret = gpio_pin_get_dt(spec);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
ret &= 0x01;
|
|
|
|
W1_GPIO_WAIT_US(timing->f);
|
|
out:
|
|
W1_GPIO_EXIT_CRITICAL(key);
|
|
return ret;
|
|
}
|
|
|
|
static int w1_gpio_write_bit(const struct device *dev, const bool bit)
|
|
{
|
|
const struct w1_gpio_config *cfg = dev->config;
|
|
const struct w1_gpio_data *data = dev->data;
|
|
|
|
const struct gpio_dt_spec *spec = &cfg->spec;
|
|
const struct w1_gpio_timing *timing = data->timing;
|
|
|
|
int ret = 0;
|
|
unsigned int key = W1_GPIO_ENTER_CRITICAL();
|
|
|
|
ret = gpio_pin_set_dt(spec, 0);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
W1_GPIO_WAIT_US(bit ? timing->a : timing->c);
|
|
ret = gpio_pin_set_dt(spec, 1);
|
|
if (ret < 0) {
|
|
goto out;
|
|
}
|
|
|
|
W1_GPIO_WAIT_US(bit ? timing->b : timing->d);
|
|
out:
|
|
W1_GPIO_EXIT_CRITICAL(key);
|
|
return ret;
|
|
}
|
|
|
|
static int w1_gpio_read_byte(const struct device *dev)
|
|
{
|
|
int ret = 0;
|
|
int byte = 0x00;
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
ret = w1_gpio_read_bit(dev);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
byte >>= 1;
|
|
if (ret) {
|
|
byte |= 0x80;
|
|
}
|
|
}
|
|
|
|
return byte;
|
|
}
|
|
|
|
static int w1_gpio_write_byte(const struct device *dev, const uint8_t byte)
|
|
{
|
|
int ret = 0;
|
|
uint8_t write = byte;
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
ret = w1_gpio_write_bit(dev, write & 0x01);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
write >>= 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int w1_gpio_configure(const struct device *dev, enum w1_settings_type type, uint32_t value)
|
|
{
|
|
struct w1_gpio_data *data = dev->data;
|
|
|
|
switch (type) {
|
|
case W1_SETTING_SPEED:
|
|
data->overdrive_active = (value != 0);
|
|
data->timing = data->overdrive_active ? &od : &std;
|
|
return 0;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
static int w1_gpio_init(const struct device *dev)
|
|
{
|
|
const struct w1_gpio_config *cfg = dev->config;
|
|
const struct gpio_dt_spec *spec = &cfg->spec;
|
|
struct w1_gpio_data *data = dev->data;
|
|
|
|
if (gpio_is_ready_dt(spec)) {
|
|
int ret = gpio_pin_configure_dt(spec, GPIO_OUTPUT_INACTIVE | GPIO_OPEN_DRAIN |
|
|
GPIO_INPUT);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to configure GPIO port %s pin %d", spec->port->name,
|
|
spec->pin);
|
|
return ret;
|
|
}
|
|
} else {
|
|
LOG_ERR("GPIO port %s is not ready", spec->port->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
data->timing = &std;
|
|
data->overdrive_active = false;
|
|
|
|
LOG_DBG("w1-gpio initialized, with %d slave devices", cfg->master_config.slave_count);
|
|
return 0;
|
|
}
|
|
|
|
static const struct w1_driver_api w1_gpio_driver_api = {
|
|
.reset_bus = w1_gpio_reset_bus,
|
|
.read_bit = w1_gpio_read_bit,
|
|
.write_bit = w1_gpio_write_bit,
|
|
.read_byte = w1_gpio_read_byte,
|
|
.write_byte = w1_gpio_write_byte,
|
|
.configure = w1_gpio_configure,
|
|
};
|
|
|
|
#define W1_ZEPHYR_GPIO_INIT(inst) \
|
|
static const struct w1_gpio_config w1_gpio_cfg_##inst = { \
|
|
.master_config.slave_count = W1_INST_SLAVE_COUNT(inst), \
|
|
.spec = GPIO_DT_SPEC_INST_GET(inst, gpios)}; \
|
|
static struct w1_gpio_data w1_gpio_data_##inst = {}; \
|
|
DEVICE_DT_INST_DEFINE(inst, &w1_gpio_init, NULL, &w1_gpio_data_##inst, \
|
|
&w1_gpio_cfg_##inst, POST_KERNEL, CONFIG_W1_INIT_PRIORITY, \
|
|
&w1_gpio_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(W1_ZEPHYR_GPIO_INIT)
|