// SPDX-License-Identifier: GPL-2.0
/*
 * PRU-ICSS platform driver for various TI SoCs
 *
 * Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com/
 */

#include <dm.h>
#include <dm/of_access.h>
#include <errno.h>
#include <clk.h>
#include <reset.h>
#include <regmap.h>
#include <syscon.h>
#include <asm/io.h>
#include <power-domain.h>
#include <linux/pruss_driver.h>
#include <dm/device_compat.h>

#define PRUSS_CFG_IEPCLK	0x30
#define ICSSG_CFG_CORE_SYNC	0x3c

#define ICSSG_TASK_MGR_OFFSET	0x2a000

/* PRUSS_IEPCLK register bits */
#define PRUSS_IEPCLK_IEP_OCP_CLK_EN		BIT(0)

/* ICSSG CORE_SYNC register bits */
#define ICSSG_CORE_VBUSP_SYNC_EN		BIT(0)

/*
 * pruss_request_tm_region() - Request pruss for task manager region
 * @dev:	corresponding k3 device
 * @loc:	the task manager physical address
 *
 * Return: 0 if all goes good, else appropriate error message.
 */
int pruss_request_tm_region(struct udevice *dev, phys_addr_t *loc)
{
	struct pruss *priv;

	priv = dev_get_priv(dev);
	if (!priv || !priv->mem_regions[PRUSS_MEM_DRAM0].pa)
		return -EINVAL;

	*loc = priv->mem_regions[PRUSS_MEM_DRAM0].pa + ICSSG_TASK_MGR_OFFSET;

	return 0;
}

/**
 * pruss_request_mem_region() - request a memory resource
 * @dev: the pruss device
 * @mem_id: the memory resource id
 * @region: pointer to memory region structure to be filled in
 *
 * This function allows a client driver to request a memory resource,
 * and if successful, will let the client driver own the particular
 * memory region until released using the pruss_release_mem_region()
 * API.
 *
 * Returns the memory region if requested resource is available, an
 * error otherwise
 */
int pruss_request_mem_region(struct udevice *dev, enum pruss_mem mem_id,
			     struct pruss_mem_region *region)
{
	struct pruss *pruss;

	pruss = dev_get_priv(dev);
	if (!pruss || !region)
		return -EINVAL;

	if (mem_id >= PRUSS_MEM_MAX)
		return -EINVAL;

	if (pruss->mem_in_use[mem_id])
		return -EBUSY;

	*region = pruss->mem_regions[mem_id];
	pruss->mem_in_use[mem_id] = region;

	return 0;
}

/**
 * pruss_release_mem_region() - release a memory resource
 * @dev: the pruss device
 * @region: the memory region to release
 *
 * This function is the complimentary function to
 * pruss_request_mem_region(), and allows the client drivers to
 * release back a memory resource.
 *
 * Returns 0 on success, an error code otherwise
 */
int pruss_release_mem_region(struct udevice *dev,
			     struct pruss_mem_region *region)
{
	struct pruss *pruss;
	int id;

	pruss = dev_get_priv(dev);
	if (!pruss || !region)
		return -EINVAL;

	/* find out the memory region being released */
	for (id = 0; id < PRUSS_MEM_MAX; id++) {
		if (pruss->mem_in_use[id] == region)
			break;
	}

	if (id == PRUSS_MEM_MAX)
		return -EINVAL;

	pruss->mem_in_use[id] = NULL;

	return 0;
}

/**
 * pruss_cfg_update() - configure a PRUSS CFG sub-module register
 * @dev: the pruss device
 * @reg: register offset within the CFG sub-module
 * @mask: bit mask to use for programming the @val
 * @val: value to write
 *
 * Programs a given register within the PRUSS CFG sub-module
 *
 * Returns 0 on success, or an error code otherwise
 */
int pruss_cfg_update(struct udevice *dev, unsigned int reg,
		     unsigned int mask, unsigned int val)
{
	struct pruss *pruss;

	pruss = dev_get_priv(dev);
	if (IS_ERR_OR_NULL(pruss))
		return -EINVAL;

	return regmap_update_bits(pruss->cfg, reg, mask, val);
}

/**
 * pruss_probe() - Basic probe
 * @dev:	corresponding k3 device
 *
 * Return: 0 if all goes good, else appropriate error message.
 */
static int pruss_probe(struct udevice *dev)
{
	const char *mem_names[PRUSS_MEM_MAX] = { "dram0", "dram1", "shrdram2" };
	ofnode sub_node, node, memories;
	struct udevice *syscon;
	struct pruss *priv;
	int ret, idx, i;

	priv = dev_get_priv(dev);
	node = dev_ofnode(dev);
	priv->dev = dev;
	memories = ofnode_find_subnode(node, "memories");

	for (i = 0; i < ARRAY_SIZE(mem_names); i++) {
		idx = ofnode_stringlist_search(memories, "reg-names", mem_names[i]);
		priv->mem_regions[i].pa = ofnode_get_addr_size_index(memories, idx,
						       (fdt_size_t *)&priv->mem_regions[i].size);
	}

	sub_node = ofnode_find_subnode(node, "cfg");
	ret = uclass_get_device_by_ofnode(UCLASS_SYSCON, sub_node,
					  &syscon);

	priv->cfg = syscon_get_regmap(syscon);
	if (IS_ERR(priv->cfg)) {
		dev_err(dev, "unable to get cfg regmap (%ld)\n",
			PTR_ERR(priv->cfg));
		return -ENODEV;
	}

	/*
	 * ToDo: To be modelled as clocks.
	 * The CORE block uses two multiplexers to allow software to
	 * select one of three source clocks (ICSSGn_CORE_CLK, ICSSGn_ICLK or
	 * ICSSGn_IEP_CLK) for the final clock source of the CORE block.
	 * The user needs to configure ICSSG_CORE_SYNC_REG[0] CORE_VBUSP_SYNC_EN
	 * bit & ICSSG_IEPCLK_REG[0] IEP_OCP_CLK_EN bit in order to select the
	 * clock source to the CORE block.
	 */
	ret = regmap_update_bits(priv->cfg, ICSSG_CFG_CORE_SYNC,
				 ICSSG_CORE_VBUSP_SYNC_EN,
				 ICSSG_CORE_VBUSP_SYNC_EN);
	if (ret)
		return ret;
	ret = regmap_update_bits(priv->cfg, PRUSS_CFG_IEPCLK,
				 PRUSS_IEPCLK_IEP_OCP_CLK_EN,
				 PRUSS_IEPCLK_IEP_OCP_CLK_EN);
	if (ret)
		return ret;

	dev_dbg(dev, "pruss successfully probed %s\n", dev->name);

	return 0;
}

static const struct udevice_id pruss_ids[] = {
	{ .compatible = "ti,am654-icssg"},
	{ .compatible = "ti,am642-icssg"},
	{}
};

U_BOOT_DRIVER(pruss) = {
	.name = "pruss",
	.of_match = pruss_ids,
	.id = UCLASS_MISC,
	.probe = pruss_probe,
	.priv_auto = sizeof(struct pruss),
};
