/*
 * 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_PULL_UP);
		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)
