zephyr/drivers/w1/w1_zephyr_gpio.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 article from Analog Devices.
*
* - w1_zephyr_serial.c: drivers/w1/w1_zephyr_serial.c
* - Analog Devices 1-Wire Communication Through Software:
* https://www.analog.com/en/resources/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)