summaryrefslogtreecommitdiff
path: root/drivers/acpi/arm64/apmt.c
blob: bb010f6164e528acd802f65afd80d65a57f5844f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// 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);
	}
}