// SPDX-License-Identifier: GPL-2.0+
/*
 *  (C) Copyright 2023
 *  Svyatoslav Ryhel <clamor95@gmail.com>
 */

#include <dm.h>
#include <dm/device_compat.h>
#include <dm/pinctrl.h>
#include <stdlib.h>

#include <asm/arch/pinmux.h>

static void tegra_pinctrl_set_pin(struct udevice *config)
{
	int i, count, pin_id, ret;
	int pull, tristate;
	const char **pins;

	ret = dev_read_u32(config, "nvidia,pull", &pull);
	if (ret)
		pull = ret;

	ret = dev_read_u32(config, "nvidia,tristate", &tristate);
	if (ret)
		tristate = ret;

	count = dev_read_string_list(config, "nvidia,pins", &pins);
	if (count < 0) {
		log_debug("%s: could not parse property nvidia,pins\n", __func__);
		return;
	}

	for (i = 0; i < count; i++) {
		for (pin_id = 0; pin_id < PMUX_PINGRP_COUNT; pin_id++)
			if (tegra_pinctrl_to_pingrp[pin_id])
				if (!strcmp(pins[i], tegra_pinctrl_to_pingrp[pin_id]))
					break;

		if (pin_id == PMUX_PINGRP_COUNT) {
			log_debug("%s: %s(%d) is not valid\n", __func__, pins[i], pin_id);
			continue;
		}

		if (pull >= 0)
			pinmux_set_pullupdown(pin_id, pull);

		if (tristate >= 0) {
			if (!tristate)
				pinmux_tristate_disable(pin_id);
			else
				pinmux_tristate_enable(pin_id);
		}
	}

	free(pins);
}

static void tegra_pinctrl_set_func(struct udevice *config)
{
	int i, count, func_id, pin_id;
	const char *function;
	const char **pins;

	function = dev_read_string(config, "nvidia,function");
	if (function) {
		for (i = 0; i < PMUX_FUNC_COUNT; i++)
			if (tegra_pinctrl_to_func[i])
				if (!strcmp(function, tegra_pinctrl_to_func[i]))
					break;

		func_id = i;
	} else {
		func_id = PMUX_FUNC_COUNT;
	}

	count = dev_read_string_list(config, "nvidia,pins", &pins);
	if (count < 0) {
		log_debug("%s: could not parse property nvidia,pins\n", __func__);
		return;
	}

	for (i = 0; i < count; i++) {
		for (pin_id = 0; pin_id < PMUX_PINGRP_COUNT; pin_id++)
			if (tegra_pinctrl_to_pingrp[pin_id])
				if (!strcmp(pins[i], tegra_pinctrl_to_pingrp[pin_id]))
					break;

		if (func_id == PMUX_FUNC_COUNT || pin_id == PMUX_PINGRP_COUNT) {
			log_debug("%s: pin %s(%d) or function %s(%d) is not valid\n",
				  __func__, pins[i], pin_id, function, func_id);
			continue;
		}

		debug("%s(%d) muxed to %s(%d)\n", pins[i], pin_id, function, func_id);

		pinmux_set_func(pin_id, func_id);
	}

	free(pins);
}

static int tegra_pinctrl_set_state(struct udevice *dev, struct udevice *config)
{
	struct udevice *child;

	device_foreach_child(child, config) {
		/*
		 * Tegra20 pinmux is set differently then any other
		 * Tegra SOC. Nodes are arranged by function muxing,
		 * then actual pins setup (with node name prefix
		 * conf_*) and then drive setup.
		 */
		if (!strncmp(child->name, "conf", 4))
			tegra_pinctrl_set_pin(child);
		else if (!strncmp(child->name, "drive", 5))
			debug("%s: drive configuration is not supported\n", __func__);
		else
			tegra_pinctrl_set_func(child);
	}

	return 0;
}

static int tegra_pinctrl_get_pins_count(struct udevice *dev)
{
	return PMUX_PINGRP_COUNT;
}

static const char *tegra_pinctrl_get_pin_name(struct udevice *dev,
					      unsigned int selector)
{
	return tegra_pinctrl_to_pingrp[selector];
}

static int tegra_pinctrl_get_groups_count(struct udevice *dev)
{
	return PMUX_DRVGRP_COUNT;
}

static const char *tegra_pinctrl_get_group_name(struct udevice *dev,
						unsigned int selector)
{
	return tegra_pinctrl_to_drvgrp[selector];
}

static int tegra_pinctrl_get_functions_count(struct udevice *dev)
{
	return PMUX_FUNC_COUNT;
}

static const char *tegra_pinctrl_get_function_name(struct udevice *dev,
						   unsigned int selector)
{
	return tegra_pinctrl_to_func[selector];
}

const struct pinctrl_ops tegra_pinctrl_ops = {
	.get_pins_count = tegra_pinctrl_get_pins_count,
	.get_pin_name = tegra_pinctrl_get_pin_name,
	.get_groups_count = tegra_pinctrl_get_groups_count,
	.get_group_name = tegra_pinctrl_get_group_name,
	.get_functions_count = tegra_pinctrl_get_functions_count,
	.get_function_name = tegra_pinctrl_get_function_name,
	.set_state = tegra_pinctrl_set_state,
};

static int tegra_pinctrl_bind(struct udevice *dev)
{
	/*
	 * Make sure that the pinctrl driver gets probed after binding
	 * to provide initial configuration and assure that further
	 * probed devices are working correctly.
	 */
	dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);

	return 0;
}

static const struct udevice_id tegra_pinctrl_ids[] = {
	{ .compatible = "nvidia,tegra20-pinmux" },
	{ },
};

U_BOOT_DRIVER(tegra_pinctrl) = {
	.name		= "tegra_pinctrl",
	.id		= UCLASS_PINCTRL,
	.of_match	= tegra_pinctrl_ids,
	.bind		= tegra_pinctrl_bind,
	.ops		= &tegra_pinctrl_ops,
};
