// SPDX-License-Identifier: GPL-2.0 /* * ARM APMT table support. * Design document number: ARM DEN0117. * * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. * */ #define pr_fmt(fmt) "ACPI: APMT: " fmt #include <linux/acpi.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include "init.h" #define DEV_NAME "arm-cs-arch-pmu" /* There can be up to 3 resources: page 0 and 1 address, and interrupt. */ #define DEV_MAX_RESOURCE_COUNT 3 /* Root pointer to the mapped APMT table */ static struct acpi_table_header *apmt_table; static int __init apmt_init_resources(struct resource *res, struct acpi_apmt_node *node) { int irq, trigger; int num_res = 0; res[num_res].start = node->base_address0; res[num_res].end = node->base_address0 + SZ_4K - 1; res[num_res].flags = IORESOURCE_MEM; num_res++; if (node->flags & ACPI_APMT_FLAGS_DUAL_PAGE) { res[num_res].start = node->base_address1; res[num_res].end = node->base_address1 + SZ_4K - 1; res[num_res].flags = IORESOURCE_MEM; num_res++; } if (node->ovflw_irq != 0) { trigger = (node->ovflw_irq_flags & ACPI_APMT_OVFLW_IRQ_FLAGS_MODE); trigger = (trigger == ACPI_APMT_OVFLW_IRQ_FLAGS_MODE_LEVEL) ? ACPI_LEVEL_SENSITIVE : ACPI_EDGE_SENSITIVE; irq = acpi_register_gsi(NULL, node->ovflw_irq, trigger, ACPI_ACTIVE_HIGH); if (irq <= 0) { pr_warn("APMT could not register gsi hwirq %d\n", irq); return num_res; } res[num_res].start = irq; res[num_res].end = irq; res[num_res].flags = IORESOURCE_IRQ; num_res++; } return num_res; } /** * apmt_add_platform_device() - Allocate a platform device for APMT node * @node: Pointer to device ACPI APMT node * @fwnode: fwnode associated with the APMT node * * Returns: 0 on success, <0 failure */ static int __init apmt_add_platform_device(struct acpi_apmt_node *node, struct fwnode_handle *fwnode) { struct platform_device *pdev; int ret, count; struct resource res[DEV_MAX_RESOURCE_COUNT]; pdev = platform_device_alloc(DEV_NAME, PLATFORM_DEVID_AUTO); if (!pdev) return -ENOMEM; memset(res, 0, sizeof(res)); count = apmt_init_resources(res, node); ret = platform_device_add_resources(pdev, res, count); if (ret) goto dev_put; /* * Add a copy of APMT node pointer to platform_data to be used to * retrieve APMT data information. */ ret = platform_device_add_data(pdev, &node, sizeof(node)); if (ret) goto dev_put; pdev->dev.fwnode = fwnode; ret = platform_device_add(pdev); if (ret) goto dev_put; return 0; dev_put: platform_device_put(pdev); return ret; } static int __init apmt_init_platform_devices(void) { struct acpi_apmt_node *apmt_node; struct acpi_table_apmt *apmt; struct fwnode_handle *fwnode; u64 offset, end; int ret; /* * apmt_table and apmt both point to the start of APMT table, but * have different struct types */ apmt = (struct acpi_table_apmt *)apmt_table; offset = sizeof(*apmt); end = apmt->header.length; while (offset < end) { apmt_node = ACPI_ADD_PTR(struct acpi_apmt_node, apmt, offset); fwnode = acpi_alloc_fwnode_static(); if (!fwnode) return -ENOMEM; ret = apmt_add_platform_device(apmt_node, fwnode); if (ret) { acpi_free_fwnode_static(fwnode); return ret; } offset += apmt_node->length; } return 0; } void __init acpi_apmt_init(void) { acpi_status status; int ret; /** * APMT table nodes will be used at runtime after the apmt init, * so we don't need to call acpi_put_table() to release * the APMT table mapping. */ status = acpi_get_table(ACPI_SIG_APMT, 0, &apmt_table); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) { const char *msg = acpi_format_exception(status); pr_err("Failed to get APMT table, %s\n", msg); } return; } ret = apmt_init_platform_devices(); if (ret) { pr_err("Failed to initialize APMT platform devices, ret: %d\n", ret); acpi_put_table(apmt_table); } }