/*
 * Copyright (c) 2022-2023 Vestas Wind Systems A/S
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/init.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(gpio_hogs, CONFIG_GPIO_LOG_LEVEL);

struct gpio_hog_dt_spec {
	gpio_pin_t pin;
	gpio_flags_t flags;
};

struct gpio_hogs {
	const struct device *port;
	const struct gpio_hog_dt_spec *specs;
	size_t num_specs;
};

/* Static initializer for a struct gpio_hog_dt_spec */
#define GPIO_HOG_DT_SPEC_GET_BY_IDX(node_id, idx)						\
	{											\
		.pin = DT_GPIO_HOG_PIN_BY_IDX(node_id, idx),					\
		.flags = DT_GPIO_HOG_FLAGS_BY_IDX(node_id, idx) |				\
			 COND_CODE_1(DT_PROP(node_id, input), (GPIO_INPUT),			\
				     (COND_CODE_1(DT_PROP(node_id, output_low),			\
						 (GPIO_OUTPUT_INACTIVE),			\
						 (COND_CODE_1(DT_PROP(node_id, output_high),	\
							     (GPIO_OUTPUT_ACTIVE), (0)))))),	\
	}

/* Expands to 1 if node_id is a GPIO controller, 0 otherwise */
#define GPIO_HOGS_NODE_IS_GPIO_CTLR(node_id)			\
	DT_PROP_OR(node_id, gpio_controller, 0)

/* Expands to to 1 if node_id is a GPIO hog, empty otherwise */
#define GPIO_HOGS_NODE_IS_GPIO_HOG(node_id)			\
	IF_ENABLED(DT_PROP_OR(node_id, gpio_hog, 0), 1)

/* Expands to 1 if GPIO controller node_id has GPIO hog children, 0 otherwise */
#define GPIO_HOGS_GPIO_CTLR_HAS_HOGS(node_id)			\
	COND_CODE_0(						\
		IS_EMPTY(DT_FOREACH_CHILD_STATUS_OKAY(node_id,	\
			GPIO_HOGS_NODE_IS_GPIO_HOG)),		\
		(1), (0))

/* Called for GPIO hog indexes */
#define GPIO_HOGS_INIT_GPIO_HOG_BY_IDX(idx, node_id)		\
	GPIO_HOG_DT_SPEC_GET_BY_IDX(node_id, idx)

/* Called for GPIO hog dts nodes */
#define GPIO_HOGS_INIT_GPIO_HOGS(node_id)			\
	LISTIFY(DT_NUM_GPIO_HOGS(node_id),			\
		GPIO_HOGS_INIT_GPIO_HOG_BY_IDX, (,), node_id),

/* Called for GPIO controller dts node children */
#define GPIO_HOGS_COND_INIT_GPIO_HOGS(node_id)			\
	COND_CODE_0(IS_EMPTY(GPIO_HOGS_NODE_IS_GPIO_HOG(node_id)),	\
		    (GPIO_HOGS_INIT_GPIO_HOGS(node_id)), ())

/* Called for each GPIO controller dts node which has GPIO hog children */
#define GPIO_HOGS_INIT_GPIO_CTLR(node_id)				\
	{								\
		.port = DEVICE_DT_GET(node_id),				\
		.specs = (const struct gpio_hog_dt_spec []) {		\
			DT_FOREACH_CHILD_STATUS_OKAY(node_id,		\
				GPIO_HOGS_COND_INIT_GPIO_HOGS)		\
		},							\
		.num_specs =						\
			DT_FOREACH_CHILD_STATUS_OKAY_SEP(node_id,	\
				DT_NUM_GPIO_HOGS, (+)),			\
	},

/* Called for each GPIO controller dts node */
#define GPIO_HOGS_COND_INIT_GPIO_CTLR(node_id)			\
	IF_ENABLED(GPIO_HOGS_GPIO_CTLR_HAS_HOGS(node_id),	\
		   (GPIO_HOGS_INIT_GPIO_CTLR(node_id)))

/* Called for each dts node */
#define GPIO_HOGS_COND_INIT(node_id)				\
	IF_ENABLED(GPIO_HOGS_NODE_IS_GPIO_CTLR(node_id),	\
		   (GPIO_HOGS_COND_INIT_GPIO_CTLR(node_id)))

static const struct gpio_hogs gpio_hogs[] = {
	DT_FOREACH_STATUS_OKAY_NODE(GPIO_HOGS_COND_INIT)
};

static int gpio_hogs_init(void)
{
	const struct gpio_hogs *hogs;
	const struct gpio_hog_dt_spec *spec;
	int err;
	int i;
	int j;


	for (i = 0; i < ARRAY_SIZE(gpio_hogs); i++) {
		hogs = &gpio_hogs[i];

		if (!device_is_ready(hogs->port)) {
			LOG_ERR("GPIO port %s not ready", hogs->port->name);
			return -ENODEV;
		}

		for (j = 0; j < hogs->num_specs; j++) {
			spec = &hogs->specs[j];

			err = gpio_pin_configure(hogs->port, spec->pin, spec->flags);
			if (err < 0) {
				LOG_ERR("failed to configure GPIO hog for port %s pin %u (err %d)",
					hogs->port->name, spec->pin, err);
				return err;
			}
		}
	}

	return 0;
}

SYS_INIT(gpio_hogs_init, POST_KERNEL, CONFIG_GPIO_HOGS_INIT_PRIORITY);
