162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ARM APMT table support.
462306a36Sopenharmony_ci * Design document number: ARM DEN0117.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define pr_fmt(fmt)	"ACPI: APMT: " fmt
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/acpi.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include "init.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define DEV_NAME "arm-cs-arch-pmu"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* There can be up to 3 resources: page 0 and 1 address, and interrupt. */
2162306a36Sopenharmony_ci#define DEV_MAX_RESOURCE_COUNT 3
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* Root pointer to the mapped APMT table */
2462306a36Sopenharmony_cistatic struct acpi_table_header *apmt_table;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic int __init apmt_init_resources(struct resource *res,
2762306a36Sopenharmony_ci				      struct acpi_apmt_node *node)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	int irq, trigger;
3062306a36Sopenharmony_ci	int num_res = 0;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	res[num_res].start = node->base_address0;
3362306a36Sopenharmony_ci	res[num_res].end = node->base_address0 + SZ_4K - 1;
3462306a36Sopenharmony_ci	res[num_res].flags = IORESOURCE_MEM;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	num_res++;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (node->flags & ACPI_APMT_FLAGS_DUAL_PAGE) {
3962306a36Sopenharmony_ci		res[num_res].start = node->base_address1;
4062306a36Sopenharmony_ci		res[num_res].end = node->base_address1 + SZ_4K - 1;
4162306a36Sopenharmony_ci		res[num_res].flags = IORESOURCE_MEM;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci		num_res++;
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	if (node->ovflw_irq != 0) {
4762306a36Sopenharmony_ci		trigger = (node->ovflw_irq_flags & ACPI_APMT_OVFLW_IRQ_FLAGS_MODE);
4862306a36Sopenharmony_ci		trigger = (trigger == ACPI_APMT_OVFLW_IRQ_FLAGS_MODE_LEVEL) ?
4962306a36Sopenharmony_ci			ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE;
5062306a36Sopenharmony_ci		irq = acpi_register_gsi(NULL, node->ovflw_irq, trigger,
5162306a36Sopenharmony_ci						ACPI_ACTIVE_HIGH);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci		if (irq <= 0) {
5462306a36Sopenharmony_ci			pr_warn("APMT could not register gsi hwirq %d\n", irq);
5562306a36Sopenharmony_ci			return num_res;
5662306a36Sopenharmony_ci		}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci		res[num_res].start = irq;
5962306a36Sopenharmony_ci		res[num_res].end = irq;
6062306a36Sopenharmony_ci		res[num_res].flags = IORESOURCE_IRQ;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci		num_res++;
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	return num_res;
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/**
6962306a36Sopenharmony_ci * apmt_add_platform_device() - Allocate a platform device for APMT node
7062306a36Sopenharmony_ci * @node: Pointer to device ACPI APMT node
7162306a36Sopenharmony_ci * @fwnode: fwnode associated with the APMT node
7262306a36Sopenharmony_ci *
7362306a36Sopenharmony_ci * Returns: 0 on success, <0 failure
7462306a36Sopenharmony_ci */
7562306a36Sopenharmony_cistatic int __init apmt_add_platform_device(struct acpi_apmt_node *node,
7662306a36Sopenharmony_ci					   struct fwnode_handle *fwnode)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct platform_device *pdev;
7962306a36Sopenharmony_ci	int ret, count;
8062306a36Sopenharmony_ci	struct resource res[DEV_MAX_RESOURCE_COUNT];
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	pdev = platform_device_alloc(DEV_NAME, PLATFORM_DEVID_AUTO);
8362306a36Sopenharmony_ci	if (!pdev)
8462306a36Sopenharmony_ci		return -ENOMEM;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	memset(res, 0, sizeof(res));
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	count = apmt_init_resources(res, node);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	ret = platform_device_add_resources(pdev, res, count);
9162306a36Sopenharmony_ci	if (ret)
9262306a36Sopenharmony_ci		goto dev_put;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	/*
9562306a36Sopenharmony_ci	 * Add a copy of APMT node pointer to platform_data to be used to
9662306a36Sopenharmony_ci	 * retrieve APMT data information.
9762306a36Sopenharmony_ci	 */
9862306a36Sopenharmony_ci	ret = platform_device_add_data(pdev, &node, sizeof(node));
9962306a36Sopenharmony_ci	if (ret)
10062306a36Sopenharmony_ci		goto dev_put;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	pdev->dev.fwnode = fwnode;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	ret = platform_device_add(pdev);
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	if (ret)
10762306a36Sopenharmony_ci		goto dev_put;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	return 0;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cidev_put:
11262306a36Sopenharmony_ci	platform_device_put(pdev);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	return ret;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic int __init apmt_init_platform_devices(void)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct acpi_apmt_node *apmt_node;
12062306a36Sopenharmony_ci	struct acpi_table_apmt *apmt;
12162306a36Sopenharmony_ci	struct fwnode_handle *fwnode;
12262306a36Sopenharmony_ci	u64 offset, end;
12362306a36Sopenharmony_ci	int ret;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/*
12662306a36Sopenharmony_ci	 * apmt_table and apmt both point to the start of APMT table, but
12762306a36Sopenharmony_ci	 * have different struct types
12862306a36Sopenharmony_ci	 */
12962306a36Sopenharmony_ci	apmt = (struct acpi_table_apmt *)apmt_table;
13062306a36Sopenharmony_ci	offset = sizeof(*apmt);
13162306a36Sopenharmony_ci	end = apmt->header.length;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	while (offset < end) {
13462306a36Sopenharmony_ci		apmt_node = ACPI_ADD_PTR(struct acpi_apmt_node, apmt,
13562306a36Sopenharmony_ci				 offset);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		fwnode = acpi_alloc_fwnode_static();
13862306a36Sopenharmony_ci		if (!fwnode)
13962306a36Sopenharmony_ci			return -ENOMEM;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		ret = apmt_add_platform_device(apmt_node, fwnode);
14262306a36Sopenharmony_ci		if (ret) {
14362306a36Sopenharmony_ci			acpi_free_fwnode_static(fwnode);
14462306a36Sopenharmony_ci			return ret;
14562306a36Sopenharmony_ci		}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci		offset += apmt_node->length;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	return 0;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_civoid __init acpi_apmt_init(void)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	acpi_status status;
15662306a36Sopenharmony_ci	int ret;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	/**
15962306a36Sopenharmony_ci	 * APMT table nodes will be used at runtime after the apmt init,
16062306a36Sopenharmony_ci	 * so we don't need to call acpi_put_table() to release
16162306a36Sopenharmony_ci	 * the APMT table mapping.
16262306a36Sopenharmony_ci	 */
16362306a36Sopenharmony_ci	status = acpi_get_table(ACPI_SIG_APMT, 0, &apmt_table);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	if (ACPI_FAILURE(status)) {
16662306a36Sopenharmony_ci		if (status != AE_NOT_FOUND) {
16762306a36Sopenharmony_ci			const char *msg = acpi_format_exception(status);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci			pr_err("Failed to get APMT table, %s\n", msg);
17062306a36Sopenharmony_ci		}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		return;
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	ret = apmt_init_platform_devices();
17662306a36Sopenharmony_ci	if (ret) {
17762306a36Sopenharmony_ci		pr_err("Failed to initialize APMT platform devices, ret: %d\n", ret);
17862306a36Sopenharmony_ci		acpi_put_table(apmt_table);
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci}
181