// SPDX-License-Identifier: GPL-2.0
/*
 * Zynq UltraScale+ MPSoC Clock Controller
 *
 * Copyright (C) 2019 Pengutronix, Michael Tretter <m.tretter@pengutronix.de>
 *
 * Based on the Linux driver in drivers/clk/zynqmp/
 *
 * Copyright (C) 2016-2018 Xilinx
 */

#include <common.h>
#include <init.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <mach/firmware-zynqmp.h>

#include "clk-zynqmp.h"

#define MAX_PARENT			100
#define MAX_NODES			6
#define MAX_NAME_LEN			50

#define CLK_TYPE_FIELD_MASK		GENMASK(3, 0)
#define CLK_FLAG_FIELD_MASK		GENMASK(21, 8)
#define CLK_TYPE_FLAG_FIELD_MASK	GENMASK(31, 24)

#define CLK_PARENTS_ID_MASK		GENMASK(15, 0)
#define CLK_PARENTS_TYPE_MASK		GENMASK(31, 16)

#define CLK_GET_ATTR_VALID		BIT(0)
#define CLK_GET_ATTR_TYPE		BIT(2)

#define CLK_NAME_RESERVED		""
#define CLK_NAME_DUMMY			"dummy_name"

#define CLK_GET_NAME_RESP_LEN		16
#define CLK_GET_TOPOLOGY_RESP_WORDS	3
#define CLK_GET_PARENTS_RESP_WORDS	3

#define CLK_GET_PARENTS_NONE		(-1)
#define CLK_GET_PARENTS_DUMMY		(-2)

enum clk_type {
	CLK_TYPE_INVALID,
	CLK_TYPE_OUTPUT,
	CLK_TYPE_EXTERNAL,
};

enum parent_type {
	PARENT_CLK_SELF,
	PARENT_CLK_NODE1,
	PARENT_CLK_NODE2,
	PARENT_CLK_NODE3,
	PARENT_CLK_NODE4,
	PARENT_CLK_EXTERNAL,
};

struct clock_parent {
	char name[MAX_NAME_LEN];
	enum parent_type type;
};

struct zynqmp_clock_info {
	char clk_name[MAX_NAME_LEN];
	bool valid;
	enum clk_type type;
	struct clock_topology node[MAX_NODES];
	unsigned int num_nodes;
	struct clock_parent parent[MAX_PARENT];
	unsigned int num_parents;
};

static const char clk_type_postfix[][10] = {
	[TYPE_INVALID] = "",
	[TYPE_MUX] = "_mux",
	[TYPE_GATE] = "",
	[TYPE_DIV1] = "_div1",
	[TYPE_DIV2] = "_div2",
	[TYPE_FIXEDFACTOR] = "_ff",
	[TYPE_PLL] = ""
};

static struct clk *(*const clk_topology[]) (const char *name,
				            unsigned int clk_id,
					    const char **parents,
					    unsigned int num_parents,
					    const struct clock_topology *nodes)
					= {
	[TYPE_INVALID] = NULL,
	[TYPE_MUX] = zynqmp_clk_register_mux,
	[TYPE_PLL] = zynqmp_clk_register_pll,
	[TYPE_FIXEDFACTOR] = zynqmp_clk_register_fixed_factor,
	[TYPE_DIV1] = zynqmp_clk_register_divider,
	[TYPE_DIV2] = zynqmp_clk_register_divider,
	[TYPE_GATE] = zynqmp_clk_register_gate
};

static struct zynqmp_clock_info *clock_info;
static const struct zynqmp_eemi_ops *eemi_ops;

static inline bool zynqmp_is_valid_clock(unsigned int id)
{
	return clock_info[id].valid;
}

static char *zynqmp_get_clock_name(unsigned int id)
{
	if (!zynqmp_is_valid_clock(id))
		return ERR_PTR(-EINVAL);

	return clock_info[id].clk_name;
}

static enum clk_type zynqmp_get_clock_type(unsigned int id)
{
	if (!zynqmp_is_valid_clock(id))
		return CLK_TYPE_INVALID;

	return clock_info[id].type;
}

static int zynqmp_get_parent_names(struct device_node *np,
				   unsigned int clk_id,
				   const char **parent_names)
{
	int i;
	struct clock_topology *clk_nodes;
	struct clock_parent *parents;
	int ret;

	clk_nodes = clock_info[clk_id].node;
	parents = clock_info[clk_id].parent;

	for (i = 0; i < clock_info[clk_id].num_parents; i++) {
		switch (parents[i].type) {
		case PARENT_CLK_SELF:
			break;
		case PARENT_CLK_EXTERNAL:
			ret = of_property_match_string(np, "clock-names",
						       parents[i].name);
			if (ret < 0)
				strcpy(parents[i].name, CLK_NAME_DUMMY);
			break;
		default:
			strcat(parents[i].name,
			       clk_type_postfix[clk_nodes[parents[i].type - 1].type]);
			break;
		}
		parent_names[i] = parents[i].name;
	}

	return clock_info[clk_id].num_parents;
}

static int zynqmp_pm_clock_query_num_clocks(void)
{
	struct zynqmp_pm_query_data qdata = {0};
	u32 ret_payload[PAYLOAD_ARG_CNT];
	int ret;

	qdata.qid = PM_QID_CLOCK_GET_NUM_CLOCKS;

	ret = eemi_ops->query_data(qdata, ret_payload);
	if (ret)
		return ret;

	return ret_payload[1];
}

static int zynqmp_pm_clock_query_name(unsigned int id, char *response)
{
	struct zynqmp_pm_query_data qdata = {0};
	u32 ret_payload[PAYLOAD_ARG_CNT];
	int ret;

	qdata.qid = PM_QID_CLOCK_GET_NAME;
	qdata.arg1 = id;

	ret = eemi_ops->query_data(qdata, ret_payload);
	if (ret)
		return ret;

	memcpy(response, ret_payload, CLK_GET_NAME_RESP_LEN);

	return 0;
}

struct zynqmp_pm_query_fixed_factor {
	unsigned int mul;
	unsigned int div;
};

static int zynqmp_pm_clock_query_fixed_factor(unsigned int id,
		struct zynqmp_pm_query_fixed_factor *response)
{
	struct zynqmp_pm_query_data qdata = {0};
	u32 ret_payload[PAYLOAD_ARG_CNT];
	int ret;

	qdata.qid = PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS;
	qdata.arg1 = id;

	ret = eemi_ops->query_data(qdata, ret_payload);
	if (ret)
		return ret;

	response->mul = ret_payload[1];
	response->div = ret_payload[2];

	return 0;
}

struct zynqmp_pm_query_topology {
	unsigned int node[CLK_GET_TOPOLOGY_RESP_WORDS];
};

static int zynqmp_pm_clock_query_topology(unsigned int id, unsigned int index,
		struct zynqmp_pm_query_topology *response)
{
	struct zynqmp_pm_query_data qdata;
	u32 ret_payload[PAYLOAD_ARG_CNT];
	int ret;

	memset(&qdata, 0, sizeof(qdata));
	qdata.qid = PM_QID_CLOCK_GET_TOPOLOGY;
	qdata.arg1 = id;
	qdata.arg2 = index;

	ret = eemi_ops->query_data(qdata, ret_payload);
	if (ret)
		return ret;

	memcpy(response, &ret_payload[1], sizeof(*response));

	return 0;
}

struct zynqmp_pm_query_parents {
	unsigned int parent[CLK_GET_PARENTS_RESP_WORDS];
};

static int zynqmp_pm_clock_query_parents(unsigned int id, unsigned int index,
		struct zynqmp_pm_query_parents *response)
{
	struct zynqmp_pm_query_data qdata;
	u32 ret_payload[PAYLOAD_ARG_CNT];
	int ret;

	memset(&qdata, 0, sizeof(qdata));
	qdata.qid = PM_QID_CLOCK_GET_PARENTS;
	qdata.arg1 = id;
	qdata.arg2 = index;

	ret = eemi_ops->query_data(qdata, ret_payload);
	if (ret)
		return ret;

	memcpy(response, &ret_payload[1], sizeof(*response));

	return 0;
}

static int zynqmp_pm_clock_query_attributes(unsigned int id, unsigned int *attr)
{
	struct zynqmp_pm_query_data qdata;
	u32 ret_payload[PAYLOAD_ARG_CNT];
	int ret;

	memset(&qdata, 0, sizeof(qdata));
	qdata.qid = PM_QID_CLOCK_GET_ATTRIBUTES;
	qdata.arg1 = id;

	ret = eemi_ops->query_data(qdata, ret_payload);
	if (ret)
		return ret;

	*attr = ret_payload[1];

	return 0;
}

static int zynqmp_clock_parse_topology(struct clock_topology *topology,
				       struct zynqmp_pm_query_topology *response,
				       size_t max_nodes)
{
	int i;
	enum topology_type type;

	for (i = 0; i < max_nodes && i < ARRAY_SIZE(response->node); i++) {
		type = FIELD_GET(CLK_TYPE_FIELD_MASK, response->node[i]);
		if (type == TYPE_INVALID)
			break;

		topology[i].type = type;
		topology[i].flag =
			FIELD_GET(CLK_FLAG_FIELD_MASK, response->node[i]);
		topology[i].type_flag =
			FIELD_GET(CLK_TYPE_FLAG_FIELD_MASK, response->node[i]);
	}

	return i;
}

static int zynqmp_clock_parse_parents(struct clock_parent *parents,
				      struct zynqmp_pm_query_parents *response,
				      size_t max_parents)
{
	int i;
	struct clock_parent *parent;
	char *parent_name;
	int parent_id;

	for (i = 0; i < max_parents && i < ARRAY_SIZE(response->parent); i++) {
		if (response->parent[i] == CLK_GET_PARENTS_NONE)
			break;

		parent = &parents[i];
		if (response->parent[i] == CLK_GET_PARENTS_DUMMY) {
			parent->type = PARENT_CLK_SELF;
			strncpy(parent->name, CLK_NAME_DUMMY, MAX_NAME_LEN);
			continue;
		}

		parent_id = FIELD_GET(CLK_PARENTS_ID_MASK, response->parent[i]);
		parent_name = zynqmp_get_clock_name(parent_id);
		if (IS_ERR(parent_name))
			continue;

		strncpy(parent->name, parent_name, MAX_NAME_LEN);
		parent->type = FIELD_GET(CLK_PARENTS_TYPE_MASK, response->parent[i]);
	}

	return i;
}

static int zynqmp_clock_get_topology(unsigned int id,
				     struct clock_topology *topology,
				     size_t max_nodes)
{
	int ret;
	int i;
	struct zynqmp_pm_query_topology response;

	for (i = 0; i < max_nodes; i += ret) {
		ret = zynqmp_pm_clock_query_topology(id, i, &response);
		if (ret)
			return ret;

		ret = zynqmp_clock_parse_topology(&topology[i], &response,
						  max_nodes - i);
		if (ret == 0)
			break;
	}

	return i;
}

static int zynqmp_clock_get_parents(unsigned int clk_id,
				    struct clock_parent *parents,
				    size_t max_parents)
{
	int ret;
	int i;
	struct zynqmp_pm_query_parents response;

	for (i = 0; i < max_parents; i += ret) {
		ret = zynqmp_pm_clock_query_parents(clk_id, i, &response);
		if (ret)
			return ret;

		ret = zynqmp_clock_parse_parents(&parents[i], &response,
						 max_parents - i);
		if (ret == 0)
			break;
	}

	return i;
}

struct clk *zynqmp_clk_register_fixed_factor(const char *name,
					     unsigned int clk_id,
					     const char **parents,
					     unsigned int num_parents,
					     const struct clock_topology *topology)
{
	unsigned int err;
	struct zynqmp_pm_query_fixed_factor response = {0};
	unsigned flags;

	err = zynqmp_pm_clock_query_fixed_factor(clk_id, &response);
	if (err)
		return ERR_PTR(err);

	flags = topology->flag;

	return clk_fixed_factor(strdup(name), strdup(parents[0]),
			        response.mul, response.div, flags);
}

static struct clk *zynqmp_register_clk_topology(char *clk_name,
					        int clk_id,
						int num_parents,
						const char **parent_names)
{
	int i;
	unsigned int num_nodes;
	char tmp_name[MAX_NAME_LEN];
	char parent_name[MAX_NAME_LEN];
	struct clock_topology *nodes;
	struct clk *clk = NULL;

	nodes = clock_info[clk_id].node;
	num_nodes = clock_info[clk_id].num_nodes;

	for (i = 0; i < num_nodes; i++) {
		if (!clk_topology[nodes[i].type])
			continue;

		/*
		 * Register only the last sub-node in the chain with the name of the
		 * original clock, but postfix other sub-node inside the chain with
		 * their type.
		 */
		snprintf(tmp_name, MAX_NAME_LEN, "%s%s", clk_name,
			(i == num_nodes - 1) ? "" : clk_type_postfix[nodes[i].type]);

		clk = (*clk_topology[nodes[i].type])(tmp_name, clk_id,
						     parent_names,
						     num_parents,
						     &nodes[i]);
		if (IS_ERR(clk))
			pr_warn("failed to register node %s of clock %s\n",
				tmp_name, clk_name);

		/*
		 * Only link the first sub-node to the original (first)
		 * parent, but link every other sub-node with their preceeding
		 * sub-clock via the first parent.
		 */
		parent_names[0] = parent_name;
		strncpy(parent_name, tmp_name, MAX_NAME_LEN);
	}

	return clk;
}

static int zynqmp_register_clocks(struct device_d *dev,
				  struct clk **clks, size_t num_clocks)
{
	unsigned int i;
	const char *parent_names[MAX_PARENT];
	char *name;
	struct device_node *node = dev->device_node;
	int num_parents;

	for (i = 0; i < num_clocks; i++) {
		if (zynqmp_get_clock_type(i) != CLK_TYPE_OUTPUT)
			continue;
		name = zynqmp_get_clock_name(i);
		if (IS_ERR(name))
			continue;

		num_parents = zynqmp_get_parent_names(node, i, parent_names);
		if (num_parents < 0) {
			dev_warn(dev, "failed to find parents for %s\n", name);
			continue;
		}

		clks[i] = zynqmp_register_clk_topology(name, i, num_parents,
						       parent_names);
		if (IS_ERR_OR_NULL(clks[i]))
			dev_warn(dev, "failed to register clock %s: %ld\n",
				 name, PTR_ERR(clks[i]));
	}

	return 0;
}

static void zynqmp_fill_clock_info(struct zynqmp_clock_info *clock_info,
				   size_t num_clocks)
{
	int i;
	int ret;
	unsigned int attr;

	for (i = 0; i < num_clocks; i++) {
		zynqmp_pm_clock_query_name(i, clock_info[i].clk_name);
		if (!strcmp(clock_info[i].clk_name, CLK_NAME_RESERVED))
			continue;

		ret = zynqmp_pm_clock_query_attributes(i, &attr);
		if (ret)
			continue;

		clock_info[i].valid = attr & CLK_GET_ATTR_VALID;
		clock_info[i].type = attr & CLK_GET_ATTR_TYPE ?
			CLK_TYPE_EXTERNAL : CLK_TYPE_OUTPUT;
	}

	for (i = 0; i < num_clocks; i++) {
		if (zynqmp_get_clock_type(i) != CLK_TYPE_OUTPUT)
			continue;
		clock_info[i].num_nodes =
			zynqmp_clock_get_topology(i, clock_info[i].node,
						  ARRAY_SIZE(clock_info[i].node));
	}

	for (i = 0; i < num_clocks; i++) {
		if (zynqmp_get_clock_type(i) != CLK_TYPE_OUTPUT)
			continue;
		ret = zynqmp_clock_get_parents(i, clock_info[i].parent,
					       ARRAY_SIZE(clock_info[i].parent));
		if (ret < 0)
			continue;
		clock_info[i].num_parents = ret;
	}
}

static int zynqmp_clock_probe(struct device_d *dev)
{
	int err;
	u32 api_version;
	int num_clocks;
	struct clk_onecell_data *clk_data;

	eemi_ops = zynqmp_pm_get_eemi_ops();
	if (!eemi_ops)
		return -ENODEV;

	/* Check version to make sure firmware is available */
	err = eemi_ops->get_api_version(&api_version);
	if (err) {
		dev_err(dev, "Firmware not available\n");
		return err;
	}

	num_clocks = zynqmp_pm_clock_query_num_clocks();
	if (num_clocks < 1)
		return num_clocks;

	clock_info = kcalloc(num_clocks, sizeof(*clock_info), GFP_KERNEL);
	if (!clock_info)
		return -ENOMEM;

	zynqmp_fill_clock_info(clock_info, num_clocks);

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

	clk_data->clks = kcalloc(num_clocks, sizeof(*clk_data->clks), GFP_KERNEL);
	if (!clk_data->clks) {
		kfree(clk_data);
		return -ENOMEM;
	}

	zynqmp_register_clocks(dev, clk_data->clks, num_clocks);
	clk_data->clk_num = num_clocks;
	of_clk_add_provider(dev->device_node, of_clk_src_onecell_get, clk_data);

	/*
	 * We can free clock_info now, as is only used to store clock info
	 * from firmware for registering the clocks.
	 */
	kfree(clock_info);

	return 0;
}

static struct of_device_id zynqmp_clock_of_match[] = {
	{.compatible = "xlnx,zynqmp-clk"},
	{},
};

static struct driver_d zynqmp_clock_driver = {
	.probe	= zynqmp_clock_probe,
	.name	= "zynqmp_clock",
	.of_compatible = DRV_OF_COMPAT(zynqmp_clock_of_match),
};
postcore_platform_driver(zynqmp_clock_driver);
