// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2022-2025 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can access it online at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 *
 */

#include <linux/atomic.h>
#include <linux/coresight.h>
#include <linux/dma-mapping.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <linux/of_platform.h>
#if KERNEL_VERSION(6, 12, 0) <= LINUX_VERSION_CODE
#include <linux/of.h>
#endif

#include <linux/mali_kbase_debug_coresight_csf.h>
#include <coresight-priv.h>
#include "sources/coresight_mali_sources.h"

static int coresight_mali_source_trace_id(struct coresight_device *csdev)
{
	struct coresight_mali_source_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);

	return drvdata->trcid;
}

#if KERNEL_VERSION(6, 12, 0) <= LINUX_VERSION_CODE
#ifndef CSTD_UNUSED
#define CSTD_UNUSED(x) ((void)(x))
#endif

static int coresight_mali_enable_source(struct coresight_device *csdev, struct perf_event *event,
					u32 mode, struct coresight_trace_id_map *id_map)
{
	/* id_map is used by Perf sessions and since mali sources work in SYSFS mode (CS_MODE_SYSFS)
	 * id_map will not be utilized at all
	 */
	CSTD_UNUSED(id_map);
	return coresight_mali_enable_component(csdev, mode);
}
#else
static int coresight_mali_enable_source(struct coresight_device *csdev, struct perf_event *event,
					u32 mode)
{
	return coresight_mali_enable_component(csdev, mode);
}
#endif

static void coresight_mali_disable_source(struct coresight_device *csdev, struct perf_event *event)
{
	coresight_mali_disable_component(csdev);
}

static const struct coresight_ops_source coresight_mali_source_ops = {
#if KERNEL_VERSION(6, 3, 0) <= LINUX_VERSION_CODE
	.cpu_id = coresight_mali_source_trace_id,
#else
	.trace_id = coresight_mali_source_trace_id,
#endif
	.enable = coresight_mali_enable_source,
	.disable = coresight_mali_disable_source
};

static const struct coresight_ops mali_cs_ops = {
	.source_ops = &coresight_mali_source_ops,
};

int coresight_mali_sources_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct coresight_platform_data *pdata = NULL;
	struct coresight_mali_source_drvdata *drvdata = NULL;
	struct coresight_desc desc = { 0 };
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct platform_device *gpu_pdev = NULL;
	struct device_node *gpu_node = NULL;

	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
	if (!drvdata)
		return -ENOMEM;

	dev_set_drvdata(dev, drvdata);
	drvdata->base.dev = dev;

#if KERNEL_VERSION(5, 3, 0) <= LINUX_VERSION_CODE
	pdata = coresight_get_platform_data(dev);
#else
	if (np)
		pdata = of_get_coresight_platform_data(dev, np);
#endif
	if (IS_ERR(pdata)) {
		dev_err(drvdata->base.dev, "Failed to get platform data\n");
		ret = PTR_ERR(pdata);
		goto devm_kfree_drvdata;
	}

	dev->platform_data = pdata;

	gpu_node = of_parse_phandle(np, "gpu", 0);
	if (!gpu_node) {
		dev_err(drvdata->base.dev, "GPU node not available\n");
		goto devm_kfree_drvdata;
	}
	gpu_pdev = of_find_device_by_node(gpu_node);
	if (gpu_pdev == NULL) {
		dev_err(drvdata->base.dev, "Couldn't find GPU device from node\n");
		goto devm_kfree_drvdata;
	}

	drvdata->base.gpu_dev = platform_get_drvdata(gpu_pdev);
	if (!drvdata->base.gpu_dev) {
		dev_err(drvdata->base.dev, "GPU dev not available\n");
		goto devm_kfree_drvdata;
	}

	ret = coresight_mali_sources_init_drvdata(drvdata);
	if (ret) {
		dev_err(drvdata->base.dev, "Failed to init source driver data\n");
		goto kbase_client_unregister;
	}

	desc.type = CORESIGHT_DEV_TYPE_SOURCE;
	desc.subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE;
	desc.ops = &mali_cs_ops;
	desc.pdata = pdata;
	desc.dev = dev;
	desc.groups = coresight_mali_source_groups_get();

#if KERNEL_VERSION(5, 3, 0) <= LINUX_VERSION_CODE
	desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s", drvdata->type_name);
	if (!desc.name) {
		ret = -ENOMEM;
		goto devm_kfree_drvdata;
	}
#endif
	drvdata->base.csdev = coresight_register(&desc);
	if (IS_ERR(drvdata->base.csdev)) {
		dev_err(drvdata->base.dev, "Failed to register coresight device\n");
		ret = PTR_ERR(drvdata->base.csdev);
		goto devm_kfree_drvdata;
	}

	return ret;

kbase_client_unregister:
	if (drvdata->base.csdev != NULL)
		coresight_unregister(drvdata->base.csdev);

	coresight_mali_sources_deinit_drvdata(drvdata);

devm_kfree_drvdata:
	devm_kfree(dev, drvdata);

	return ret;
}

int coresight_mali_sources_remove(struct platform_device *pdev)
{
	struct coresight_mali_source_drvdata *drvdata = dev_get_drvdata(&pdev->dev);

	if (drvdata->base.csdev != NULL)
		coresight_unregister(drvdata->base.csdev);

	coresight_mali_sources_deinit_drvdata(drvdata);

	devm_kfree(&pdev->dev, drvdata);

	return 0;
}

MODULE_AUTHOR("ARM Ltd.");
MODULE_DESCRIPTION("Arm Coresight Mali source");
MODULE_LICENSE("GPL");
