// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
 */

#define pr_fmt(fmt) "AXI: %s(): " fmt, __func__

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/msm-bus.h>
#include <linux/msm-bus-board.h>
#include "msm_bus_core.h"

static const char * const hw_sel_name[] = {"RPM", "NoC", "BIMC", NULL};
static const char * const mode_sel_name[] = {"Fixed", "Limiter", "Bypass",
						"Regulator", NULL};

static int get_num(const char *const str[], const char *name)
{
	int i = 0;

	do {
		if (!strcmp(name, str[i]))
			return i;

		i++;
	} while (str[i] != NULL);

	pr_err("Error: string %s not found\n", name);
	return -EINVAL;
}

static struct msm_bus_scale_pdata *get_pdata(struct device *dev,
	struct device_node *of_node)
{
	struct msm_bus_scale_pdata *pdata = NULL;
	struct msm_bus_paths *usecase = NULL;
	struct msm_bus_lat_vectors *usecase_lat = NULL;
	int i = 0, j, ret, num_usecases = 0, num_paths, len;
	const uint32_t *vec_arr = NULL;
	bool mem_err = false;

	if (!dev) {
		pr_err("Error: Null device\n");
		return NULL;
	}

	pdata = devm_kzalloc(dev, sizeof(struct msm_bus_scale_pdata),
		GFP_KERNEL);
	if (!pdata) {
		mem_err = true;
		goto err;
	}

	ret = of_property_read_string(of_node, "qcom,msm-bus,name",
		&pdata->name);
	if (ret) {
		pr_err("Error: Client name not found\n");
		goto err;
	}

	ret = of_property_read_u32(of_node, "qcom,msm-bus,num-cases",
		&num_usecases);
	if (ret) {
		pr_err("Error: num-usecases not found\n");
		goto err;
	}

	pdata->num_usecases = num_usecases;

	if (of_property_read_bool(of_node, "qcom,msm-bus,active-only"))
		pdata->active_only = 1;
	else {
		pr_debug("active_only flag absent.\n");
		pr_debug("Using dual context by default\n");
	}

	pdata->alc = of_property_read_bool(of_node, "qcom,msm-bus,alc-voter");

	if (pdata->alc) {
		usecase_lat = devm_kzalloc(dev,
				(sizeof(struct msm_bus_lat_vectors) *
				pdata->num_usecases), GFP_KERNEL);
		if (!usecase_lat) {
			mem_err = true;
			goto err;
		}

		vec_arr = of_get_property(of_node,
					"qcom,msm-bus,vectors-alc", &len);
		if (vec_arr == NULL) {
			pr_err("Error: Lat vector array not found\n");
			goto err;
		}

		if (len != num_usecases * sizeof(uint32_t) * 2) {
			pr_err("Error: Length-error on getting vectors\n");
			goto err;
		}

		for (i = 0; i < num_usecases; i++) {
			int index = i * 2;

			usecase_lat[i].fal_ns = (uint64_t)
				be32_to_cpu(vec_arr[index]);
			usecase_lat[i].idle_t_ns = (uint64_t)
				be32_to_cpu(vec_arr[index + 1]);
		}

		pdata->usecase_lat = usecase_lat;
		return pdata;
	}

	usecase = devm_kzalloc(dev, (sizeof(struct msm_bus_paths) *
		pdata->num_usecases), GFP_KERNEL);
	if (!usecase) {
		mem_err = true;
		goto err;
	}

	ret = of_property_read_u32(of_node, "qcom,msm-bus,num-paths",
		&num_paths);
	if (ret) {
		pr_err("Error: num_paths not found\n");
		goto err;
	}

	vec_arr = of_get_property(of_node, "qcom,msm-bus,vectors-KBps", &len);
	if (vec_arr == NULL) {
		pr_err("Error: Vector array not found\n");
		goto err;
	}

	if (len != num_usecases * num_paths * sizeof(uint32_t) * 4) {
		pr_err("Error: Length-error on getting vectors\n");
		goto err;
	}

	for (i = 0; i < num_usecases; i++) {
		usecase[i].num_paths = num_paths;
		usecase[i].vectors = devm_kzalloc(dev, num_paths *
			sizeof(struct msm_bus_vectors), GFP_KERNEL);
		if (!usecase[i].vectors) {
			mem_err = true;
			pr_err("Error: Mem alloc failure in vectors\n");
			goto err;
		}

		for (j = 0; j < num_paths; j++) {
			int index = ((i * num_paths) + j) * 4;

			usecase[i].vectors[j].src = be32_to_cpu(vec_arr[index]);
			usecase[i].vectors[j].dst =
				be32_to_cpu(vec_arr[index + 1]);
			usecase[i].vectors[j].ab = (uint64_t)
				KBTOB(be32_to_cpu(vec_arr[index + 2]));
			usecase[i].vectors[j].ib = (uint64_t)
				KBTOB(be32_to_cpu(vec_arr[index + 3]));
		}
	}

	pdata->usecase = usecase;
	return pdata;
err:
	if (mem_err) {
		for (; i > 0; i--)
			kfree(usecase[i-1].vectors);
	}

	return NULL;
}

/**
 * msm_bus_cl_get_pdata() - Generate bus client data from device tree
 * provided by clients.
 *
 * of_node: Device tree node to extract information from
 *
 * The function returns a valid pointer to the allocated bus-scale-pdata
 * if the vectors were correctly read from the client's device node.
 * Any error in reading or parsing the device node will return NULL
 * to the caller.
 */
struct msm_bus_scale_pdata *msm_bus_cl_get_pdata(struct platform_device *pdev)
{
	struct device_node *of_node;
	struct msm_bus_scale_pdata *pdata = NULL;

	if (!pdev) {
		pr_err("Error: Null Platform device\n");
		return NULL;
	}

	of_node = pdev->dev.of_node;
	pdata = get_pdata(&pdev->dev, of_node);
	if (!pdata) {
		pr_err("client has to provide missing entry for successful registration\n");
		return NULL;
	}

	return pdata;
}
EXPORT_SYMBOL(msm_bus_cl_get_pdata);

/**
 * msm_bus_cl_get_pdata_from_dev() - Generate bus client data from device tree
 * provided by clients.
 *
 * of_node: Device tree node to extract information from
 *
 * The function returns a valid pointer to the allocated bus-scale-pdata
 * if the vectors were correctly read from the client's device node.
 * Any error in reading or parsing the device node will return NULL
 * to the caller.
 */
struct msm_bus_scale_pdata *msm_bus_cl_get_pdata_from_dev(struct device *dev)
{
	struct device_node *of_node;
	struct msm_bus_scale_pdata *pdata = NULL;

	of_node = dev->of_node;

	if (!of_node)
		return NULL;

	pdata = get_pdata(dev, of_node);
	if (!pdata) {
		pr_err("client has to provide missing entry for successful registration\n");
		return NULL;
	}

	return pdata;
}
EXPORT_SYMBOL(msm_bus_cl_get_pdata_from_dev);

/**
 * msm_bus_cl_pdata_from_node() - Generate bus client data from device tree
 * node provided by clients. This function should be used when a client
 * driver needs to register multiple bus-clients from a single device-tree
 * node associated with the platform-device.
 *
 * of_node: The subnode containing information about the bus scaling
 * data
 *
 * pdev: Platform device associated with the device-tree node
 *
 * The function returns a valid pointer to the allocated bus-scale-pdata
 * if the vectors were correctly read from the client's device node.
 * Any error in reading or parsing the device node will return NULL
 * to the caller.
 */
struct msm_bus_scale_pdata *msm_bus_pdata_from_node(
		struct platform_device *pdev, struct device_node *of_node)
{
	struct msm_bus_scale_pdata *pdata = NULL;

	if (!pdev) {
		pr_err("Error: Null Platform device\n");
		return NULL;
	}

	if (!of_node) {
		pr_err("Error: Null of_node passed to bus driver\n");
		return NULL;
	}

	pdata = get_pdata(&pdev->dev, of_node);
	if (!pdata) {
		pr_err("client has to provide missing entry for successful registration\n");
		return NULL;
	}

	return pdata;
}
EXPORT_SYMBOL(msm_bus_pdata_from_node);

static int *get_arr(struct platform_device *pdev,
		const struct device_node *node, const char *prop,
		int *nports)
{
	int size = 0, ret;
	int *arr = NULL;

	if (of_get_property(node, prop, &size)) {
		*nports = size / sizeof(int);
	} else {
		pr_debug("Property %s not available\n", prop);
		*nports = 0;
		return NULL;
	}

	if (!size) {
		*nports = 0;
		return NULL;
	}

	arr = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
	if (ZERO_OR_NULL_PTR(arr)) {
		pr_err("Error: Failed to alloc mem for %s\n", prop);
		return NULL;
	}

	ret = of_property_read_u32_array(node, prop, (u32 *)arr, *nports);
	if (ret) {
		pr_err("Error in reading property: %s\n", prop);
		goto err;
	}

	return arr;
err:
	devm_kfree(&pdev->dev, arr);
	return NULL;
}

static u64 *get_th_params(struct platform_device *pdev,
		const struct device_node *node, const char *prop,
		int *nports)
{
	int size = 0, ret;
	u64 *ret_arr = NULL;
	int *arr = NULL;
	int i;

	if (of_get_property(node, prop, &size)) {
		*nports = size / sizeof(int);
	} else {
		pr_debug("Property %s not available\n", prop);
		*nports = 0;
		return NULL;
	}

	if (!size) {
		*nports = 0;
		return NULL;
	}

	ret_arr = devm_kzalloc(&pdev->dev, (*nports * sizeof(u64)),
							GFP_KERNEL);
	if (ZERO_OR_NULL_PTR(ret_arr)) {
		pr_err("Error: Failed to alloc mem for ret arr %s\n", prop);
		return NULL;
	}

	arr = kzalloc(size, GFP_KERNEL);
	if ((ZERO_OR_NULL_PTR(arr))) {
		pr_err("Error: Failed to alloc temp mem for %s\n", prop);
		return NULL;
	}

	ret = of_property_read_u32_array(node, prop, (u32 *)arr, *nports);
	if (ret) {
		pr_err("Error in reading property: %s\n", prop);
		goto err;
	}

	for (i = 0; i < *nports; i++)
		ret_arr[i] = (uint64_t)KBTOB(arr[i]);

	MSM_BUS_DBG("%s: num entries %d prop %s", __func__, *nports, prop);

	for (i = 0; i < *nports; i++)
		MSM_BUS_DBG("Th %d val %llu", i, ret_arr[i]);

	kfree(arr);
	return ret_arr;
err:
	kfree(arr);
	devm_kfree(&pdev->dev, ret_arr);
	return NULL;
}

static struct msm_bus_node_info *get_nodes(struct device_node *of_node,
	struct platform_device *pdev,
	struct msm_bus_fabric_registration *pdata)
{
	struct msm_bus_node_info *info;
	struct device_node *child_node = NULL;
	int i = 0, ret;
	int num_bw = 0;
	u32 temp;

	for_each_child_of_node(of_node, child_node) {
		i++;
	}

	pdata->len = i;
	info = devm_kzalloc(&pdev->dev, sizeof(struct msm_bus_node_info) *
			pdata->len, GFP_KERNEL);
	if (ZERO_OR_NULL_PTR(info)) {
		pr_err("Failed to alloc memory for nodes: %d\n", pdata->len);
		goto err;
	}

	i = 0;
	child_node = NULL;
	for_each_child_of_node(of_node, child_node) {
		const char *sel_str;

		ret = of_property_read_string(child_node, "label",
			&info[i].name);
		if (ret)
			pr_err("Error reading node label\n");

		ret = of_property_read_u32(child_node, "cell-id", &info[i].id);
		if (ret) {
			pr_err("Error reading node id\n");
			goto err;
		}

		if (of_property_read_bool(child_node, "qcom,gateway"))
			info[i].gateway = 1;

		of_property_read_u32(child_node, "qcom,mas-hw-id",
			&info[i].mas_hw_id);

		of_property_read_u32(child_node, "qcom,slv-hw-id",
			&info[i].slv_hw_id);
		info[i].masterp = get_arr(pdev, child_node,
					"qcom,masterp", &info[i].num_mports);
		/* No need to store number of qports */
		info[i].qport = get_arr(pdev, child_node,
					"qcom,qport", &ret);
		pdata->nmasters += info[i].num_mports;


		info[i].slavep = get_arr(pdev, child_node,
					"qcom,slavep", &info[i].num_sports);
		pdata->nslaves += info[i].num_sports;


		info[i].tier = get_arr(pdev, child_node,
					"qcom,tier", &info[i].num_tiers);

		if (of_property_read_bool(child_node, "qcom,ahb"))
			info[i].ahb = 1;

		ret = of_property_read_string(child_node, "qcom,hw-sel",
			&sel_str);
		if (ret)
			info[i].hw_sel = 0;
		else {
			ret =  get_num(hw_sel_name, sel_str);
			if (ret < 0) {
				pr_err("Invalid hw-sel\n");
				goto err;
			}

			info[i].hw_sel = ret;
		}

		of_property_read_u32(child_node, "qcom,buswidth",
			&info[i].buswidth);
		of_property_read_u32(child_node, "qcom,ws", &info[i].ws);

		info[i].dual_conf =
			of_property_read_bool(child_node, "qcom,dual-conf");


		info[i].th = get_th_params(pdev, child_node, "qcom,thresh",
						&info[i].num_thresh);

		info[i].bimc_bw = get_th_params(pdev, child_node,
						"qcom,bimc,bw", &num_bw);

		if (num_bw != info[i].num_thresh) {
			pr_err("%s:num_bw %d must equal num_thresh %d\n",
				__func__, num_bw, info[i].num_thresh);
			pr_err("%s:Err setting up dual conf for %s\n",
				__func__, info[i].name);
			goto err;
		}

		of_property_read_u32(child_node, "qcom,bimc,gp",
			&info[i].bimc_gp);
		of_property_read_u32(child_node, "qcom,bimc,thmp",
			&info[i].bimc_thmp);

		ret = of_property_read_string(child_node, "qcom,mode-thresh",
			&sel_str);
		if (ret)
			info[i].mode_thresh = 0;
		else {
			ret = get_num(mode_sel_name, sel_str);
			if (ret < 0) {
				pr_err("Unknown mode :%s\n", sel_str);
				goto err;
			}

			info[i].mode_thresh = ret;
			MSM_BUS_DBG("AXI: THreshold mode set: %d\n",
					info[i].mode_thresh);
		}

		ret = of_property_read_string(child_node, "qcom,mode",
				&sel_str);

		if (ret)
			info[i].mode = 0;
		else {
			ret = get_num(mode_sel_name, sel_str);
			if (ret < 0) {
				pr_err("Unknown mode :%s\n", sel_str);
				goto err;
			}

			info[i].mode = ret;
		}

		info[i].nr_lim =
			of_property_read_bool(child_node, "qcom,nr-lim");

		ret = of_property_read_u32(child_node, "qcom,ff",
							&info[i].ff);
		if (ret) {
			pr_debug("fudge factor not present %d\n", info[i].id);
			info[i].ff = 0;
		}

		ret = of_property_read_u32(child_node, "qcom,floor-bw",
						&temp);
		if (ret) {
			pr_debug("fabdev floor bw not present %d\n",
							info[i].id);
			info[i].floor_bw = 0;
		} else {
			info[i].floor_bw = KBTOB(temp);
		}

		info[i].rt_mas =
			of_property_read_bool(child_node, "qcom,rt-mas");

		ret = of_property_read_string(child_node, "qcom,perm-mode",
			&sel_str);
		if (ret)
			info[i].perm_mode = 0;
		else {
			ret = get_num(mode_sel_name, sel_str);
			if (ret < 0)
				goto err;

			info[i].perm_mode = 1 << ret;
		}

		of_property_read_u32(child_node, "qcom,prio-lvl",
			&info[i].prio_lvl);
		of_property_read_u32(child_node, "qcom,prio-rd",
			&info[i].prio_rd);
		of_property_read_u32(child_node, "qcom,prio-wr",
			&info[i].prio_wr);
		of_property_read_u32(child_node, "qcom,prio0", &info[i].prio0);
		of_property_read_u32(child_node, "qcom,prio1", &info[i].prio1);
		ret = of_property_read_string(child_node, "qcom,slaveclk-dual",
			&info[i].slaveclk[DUAL_CTX]);
		if (!ret)
			pr_debug("Got slaveclk_dual: %s\n",
				info[i].slaveclk[DUAL_CTX]);
		else
			info[i].slaveclk[DUAL_CTX] = NULL;

		ret = of_property_read_string(child_node,
			"qcom,slaveclk-active", &info[i].slaveclk[ACTIVE_CTX]);
		if (!ret)
			pr_debug("Got slaveclk_active\n");
		else
			info[i].slaveclk[ACTIVE_CTX] = NULL;

		ret = of_property_read_string(child_node, "qcom,memclk-dual",
			&info[i].memclk[DUAL_CTX]);
		if (!ret)
			pr_debug("Got memclk_dual\n");
		else
			info[i].memclk[DUAL_CTX] = NULL;

		ret = of_property_read_string(child_node, "qcom,memclk-active",
			&info[i].memclk[ACTIVE_CTX]);
		if (!ret)
			pr_debug("Got memclk_active\n");
		else
			info[i].memclk[ACTIVE_CTX] = NULL;

		ret = of_property_read_string(child_node, "qcom,iface-clk-node",
			&info[i].iface_clk_node);
		if (!ret)
			pr_debug("Got iface_clk_node\n");
		else
			info[i].iface_clk_node = NULL;

		pr_debug("Node name: %s\n", info[i].name);
		i++;
	}

	pr_debug("Bus %d added: %d masters\n", pdata->id, pdata->nmasters);
	pr_debug("Bus %d added: %d slaves\n", pdata->id, pdata->nslaves);
	return info;
err:
	return NULL;
}

void msm_bus_of_get_nfab(struct platform_device *pdev,
		struct msm_bus_fabric_registration *pdata)
{
	struct device_node *of_node;
	int ret, nfab = 0;

	if (!pdev) {
		pr_err("Error: Null platform device\n");
		return;
	}

	of_node = pdev->dev.of_node;
	ret = of_property_read_u32(of_node, "qcom,nfab",
		&nfab);
	if (!ret)
		pr_debug("Fab_of: Read number of buses: %u\n", nfab);

	msm_bus_board_set_nfab(pdata, nfab);
}

struct msm_bus_fabric_registration
	*msm_bus_of_get_fab_data(struct platform_device *pdev)
{
	struct device_node *of_node;
	struct msm_bus_fabric_registration *pdata;
	bool mem_err = false;
	int ret = 0;
	const char *sel_str;
	u32 temp;

	if (!pdev) {
		pr_err("Error: Null platform device\n");
		return NULL;
	}

	of_node = pdev->dev.of_node;
	pdata = devm_kzalloc(&pdev->dev,
			sizeof(struct msm_bus_fabric_registration), GFP_KERNEL);
	if (!pdata) {
		mem_err = true;
		goto err;
	}

	ret = of_property_read_string(of_node, "label", &pdata->name);
	if (ret) {
		pr_err("Error: label not found\n");
		goto err;
	}
	pr_debug("Fab_of: Read name: %s\n", pdata->name);

	ret = of_property_read_u32(of_node, "cell-id",
		&pdata->id);
	if (ret) {
		pr_err("Error: num-usecases not found\n");
		goto err;
	}
	pr_debug("Fab_of: Read id: %u\n", pdata->id);

	if (of_property_read_bool(of_node, "qcom,ahb"))
		pdata->ahb = 1;

	ret = of_property_read_string(of_node, "qcom,fabclk-dual",
		&pdata->fabclk[DUAL_CTX]);
	if (ret) {
		pr_debug("fabclk_dual not available\n");
		pdata->fabclk[DUAL_CTX] = NULL;
	} else
		pr_debug("Fab_of: Read clk dual ctx: %s\n",
			pdata->fabclk[DUAL_CTX]);
	ret = of_property_read_string(of_node, "qcom,fabclk-active",
		&pdata->fabclk[ACTIVE_CTX]);
	if (ret) {
		pr_debug("Error: fabclk_active not available\n");
		pdata->fabclk[ACTIVE_CTX] = NULL;
	} else
		pr_debug("Fab_of: Read clk act ctx: %s\n",
			pdata->fabclk[ACTIVE_CTX]);

	ret = of_property_read_u32(of_node, "qcom,ntieredslaves",
		&pdata->ntieredslaves);
	if (ret) {
		pr_err("Error: ntieredslaves not found\n");
		goto err;
	}

	ret = of_property_read_u32(of_node, "qcom,qos-freq", &pdata->qos_freq);
	if (ret)
		pr_debug("qos_freq not available\n");

	ret = of_property_read_string(of_node, "qcom,hw-sel", &sel_str);
	if (ret) {
		pr_err("Error: hw_sel not found\n");
		goto err;
	} else {
		ret = get_num(hw_sel_name, sel_str);
		if (ret < 0)
			goto err;

		pdata->hw_sel = ret;
	}

	if (of_property_read_bool(of_node, "qcom,virt"))
		pdata->virt = true;

	ret = of_property_read_u32(of_node, "qcom,qos-baseoffset",
						&pdata->qos_baseoffset);
	if (ret)
		pr_debug("%s:qos_baseoffset not available\n", __func__);

	ret = of_property_read_u32(of_node, "qcom,qos-delta",
						&pdata->qos_delta);
	if (ret)
		pr_debug("%s:qos_delta not available\n", __func__);

	if (of_property_read_bool(of_node, "qcom,rpm-en"))
		pdata->rpm_enabled = 1;

	ret = of_property_read_u32(of_node, "qcom,nr-lim-thresh",
						&temp);

	if (ret) {
		pr_err("nr-lim threshold not specified\n");
		pdata->nr_lim_thresh = 0;
	} else {
		pdata->nr_lim_thresh = KBTOB(temp);
	}

	ret = of_property_read_u32(of_node, "qcom,eff-fact",
						&pdata->eff_fact);
	if (ret) {
		pr_err("Fab eff-factor not present\n");
		pdata->eff_fact = 0;
	}

	pdata->info = get_nodes(of_node, pdev, pdata);
	return pdata;
err:
	return NULL;
}
EXPORT_SYMBOL(msm_bus_of_get_fab_data);
