// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause
/*
 * Copyright (C) 2022, STMicroelectronics - All Rights Reserved
 * Author: Gabriel Fernandez <gabriel.fernandez@foss.st.com> for STMicroelectronics.
 */

#define LOG_CATEGORY UCLASS_CLK

#include <clk-uclass.h>
#include <dm.h>
#include <log.h>
#include <asm/io.h>
#include <dm/device_compat.h>
#include <linux/clk-provider.h>
#include "clk-stm32-core.h"

int stm32_rcc_init(struct udevice *dev,
		   const struct stm32_clock_match_data *data)
{
	int i;
	u8 *cpt;
	struct stm32mp_rcc_priv *priv = dev_get_priv(dev);
	fdt_addr_t base = dev_read_addr(dev->parent);
	const struct clk_stm32_clock_data *clock_data = data->clock_data;

	if (base == FDT_ADDR_T_NONE)
		return -EINVAL;

	priv->base = (void __iomem *)base;

	/* allocate the counter of user for internal RCC gates, common for several user */
	cpt = kzalloc(clock_data->num_gates, GFP_KERNEL);
	if (!cpt)
		return -ENOMEM;

	priv->gate_cpt = cpt;

	priv->data = clock_data;

	for (i = 0; i < data->num_clocks; i++) {
		const struct clock_config *cfg = &data->tab_clocks[i];
		struct clk *clk = ERR_PTR(-ENOENT);

		if (data->check_security && data->check_security(dev, priv->base, cfg))
			continue;

		if (cfg->setup) {
			clk = cfg->setup(dev, cfg);
			/* set identifier of clock provider*/
			dev_clk_dm(dev, cfg->id, clk);
		} else {
			dev_err(dev, "failed to register clock %s\n", cfg->name);
			return -ENOENT;
		}
	}

	return 0;
}

ulong clk_stm32_get_rate_by_name(const char *name)
{
	struct udevice *dev;

	if (!uclass_get_device_by_name(UCLASS_CLK, name, &dev)) {
		struct clk *clk = dev_get_clk_ptr(dev);

		return clk_get_rate(clk);
	}

	return 0;
}

static const struct clk_ops *clk_dev_ops(struct udevice *dev)
{
	return (const struct clk_ops *)dev->driver->ops;
}

static int stm32_clk_endisable(struct clk *clk, bool enable)
{
	const struct clk_ops *ops;
	struct clk *c = NULL;

	if (!clk->id || clk_get_by_id(clk->id, &c))
		return -ENOENT;

	ops = clk_dev_ops(c->dev);
	if (!ops->enable || !ops->disable)
		return 0;

	return enable ? ops->enable(c) : ops->disable(c);
}

static int stm32_clk_enable(struct clk *clk)
{
	return stm32_clk_endisable(clk, true);
}

static int stm32_clk_disable(struct clk *clk)
{
	return stm32_clk_endisable(clk, false);
}

static ulong stm32_clk_get_rate(struct clk *clk)
{
	const struct clk_ops *ops;
	struct clk *c = NULL;

	if (!clk->id || clk_get_by_id(clk->id, &c))
		return -ENOENT;

	ops = clk_dev_ops(c->dev);
	if (!ops->get_rate)
		return -ENOSYS;

	return ops->get_rate(c);
}

static ulong stm32_clk_set_rate(struct clk *clk, unsigned long clk_rate)
{
	const struct clk_ops *ops;
	struct clk *c = NULL;

	if (!clk->id || clk_get_by_id(clk->id, &c))
		return -ENOENT;

	ops = clk_dev_ops(c->dev);
	if (!ops->set_rate)
		return -ENOSYS;

	return ops->set_rate(c, clk_rate);
}

const struct clk_ops stm32_clk_ops = {
	.enable = stm32_clk_enable,
	.disable = stm32_clk_disable,
	.get_rate = stm32_clk_get_rate,
	.set_rate = stm32_clk_set_rate,
};

#define RCC_MP_ENCLRR_OFFSET	4

static void clk_stm32_gate_set_state(void __iomem *base,
				     const struct clk_stm32_clock_data *data,
				     u8 *cpt, u16 gate_id, int enable)
{
	const struct stm32_gate_cfg *gate_cfg = &data->gates[gate_id];
	void __iomem *addr = base + gate_cfg->reg_off;
	u8 set_clr = gate_cfg->set_clr ? RCC_MP_ENCLRR_OFFSET : 0;

	if (enable) {
		if (cpt[gate_id]++ > 0)
			return;

		if (set_clr)
			writel(BIT(gate_cfg->bit_idx), addr);
		else
			writel(readl(addr) | BIT(gate_cfg->bit_idx), addr);
	} else {
		if (--cpt[gate_id] > 0)
			return;

		if (set_clr)
			writel(BIT(gate_cfg->bit_idx), addr + set_clr);
		else
			writel(readl(addr) & ~BIT(gate_cfg->bit_idx), addr);
	}
}

static int clk_stm32_gate_enable(struct clk *clk)
{
	struct clk_stm32_gate *stm32_gate = to_clk_stm32_gate(clk);
	struct stm32mp_rcc_priv *priv = stm32_gate->priv;

	clk_stm32_gate_set_state(priv->base, priv->data, priv->gate_cpt,
				 stm32_gate->gate_id, 1);

	return 0;
}

static int clk_stm32_gate_disable(struct clk *clk)
{
	struct clk_stm32_gate *stm32_gate = to_clk_stm32_gate(clk);
	struct stm32mp_rcc_priv *priv = stm32_gate->priv;

	clk_stm32_gate_set_state(priv->base, priv->data, priv->gate_cpt,
				 stm32_gate->gate_id, 0);

	return 0;
}

static const struct clk_ops clk_stm32_gate_ops = {
	.enable = clk_stm32_gate_enable,
	.disable = clk_stm32_gate_disable,
	.get_rate = clk_generic_get_rate,
};

#define UBOOT_DM_CLK_STM32_GATE "clk_stm32_gate"

U_BOOT_DRIVER(clk_stm32_gate) = {
	.name	= UBOOT_DM_CLK_STM32_GATE,
	.id	= UCLASS_CLK,
	.ops	= &clk_stm32_gate_ops,
};

struct clk *clk_stm32_gate_register(struct udevice *dev,
				    const struct clock_config *cfg)
{
	struct stm32mp_rcc_priv *priv = dev_get_priv(dev);
	struct stm32_clk_gate_cfg *clk_cfg = cfg->clock_cfg;
	struct clk_stm32_gate *stm32_gate;
	struct clk *clk;
	int ret;

	stm32_gate = kzalloc(sizeof(*stm32_gate), GFP_KERNEL);
	if (!stm32_gate)
		return ERR_PTR(-ENOMEM);

	stm32_gate->priv = priv;
	stm32_gate->gate_id = clk_cfg->gate_id;

	clk = &stm32_gate->clk;
	clk->flags = cfg->flags;

	ret = clk_register(clk, UBOOT_DM_CLK_STM32_GATE,
			   cfg->name, cfg->parent_name);
	if (ret) {
		kfree(stm32_gate);
		return ERR_PTR(ret);
	}

	return clk;
}

struct clk *
clk_stm32_register_composite(struct udevice *dev,
			     const struct clock_config *cfg)
{
	struct stm32_clk_composite_cfg *composite = cfg->clock_cfg;
	const char *const *parent_names;
	int num_parents;
	struct clk *clk = ERR_PTR(-ENOMEM);
	struct clk_mux *mux = NULL;
	struct clk_stm32_gate *gate = NULL;
	struct clk_divider *div = NULL;
	struct clk *mux_clk = NULL;
	const struct clk_ops *mux_ops = NULL;
	struct clk *gate_clk = NULL;
	const struct clk_ops *gate_ops = NULL;
	struct clk *div_clk = NULL;
	const struct clk_ops *div_ops = NULL;
	struct stm32mp_rcc_priv *priv = dev_get_priv(dev);
	const struct clk_stm32_clock_data *data = priv->data;

	if  (composite->mux_id != NO_STM32_MUX) {
		const struct stm32_mux_cfg *mux_cfg;

		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
		if (!mux)
			goto fail;

		mux_cfg = &data->muxes[composite->mux_id];

		mux->reg = priv->base + mux_cfg->reg_off;
		mux->shift = mux_cfg->shift;
		mux->mask = BIT(mux_cfg->width) - 1;
		mux->num_parents = mux_cfg->num_parents;
		mux->flags = 0;
		mux->parent_names = mux_cfg->parent_names;

		mux_clk = &mux->clk;
		mux_ops = &clk_mux_ops;

		parent_names = mux_cfg->parent_names;
		num_parents = mux_cfg->num_parents;
	} else {
		parent_names = &cfg->parent_name;
		num_parents = 1;
	}

	if  (composite->div_id != NO_STM32_DIV) {
		const struct stm32_div_cfg *div_cfg;

		div = kzalloc(sizeof(*div), GFP_KERNEL);
		if (!div)
			goto fail;

		div_cfg = &data->dividers[composite->div_id];

		div->reg = priv->base + div_cfg->reg_off;
		div->shift = div_cfg->shift;
		div->width = div_cfg->width;
		div->width = div_cfg->width;
		div->flags = div_cfg->div_flags;
		div->table = div_cfg->table;

		div_clk = &div->clk;
		div_ops = &clk_divider_ops;
	}

	if  (composite->gate_id != NO_STM32_GATE) {
		gate = kzalloc(sizeof(*gate), GFP_KERNEL);
		if (!gate)
			goto fail;

		gate->priv = priv;
		gate->gate_id = composite->gate_id;

		gate_clk = &gate->clk;
		gate_ops = &clk_stm32_gate_ops;
	}

	clk = clk_register_composite(dev, cfg->name,
				     parent_names, num_parents,
				     mux_clk, mux_ops,
				     div_clk, div_ops,
				     gate_clk, gate_ops,
				     cfg->flags);
	if (IS_ERR(clk))
		goto fail;

	return clk;

fail:
	kfree(gate);
	kfree(div);
	kfree(mux);
	return ERR_CAST(clk);
}
