162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * OpRegion handler to allow AML to call native firmware
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
662306a36Sopenharmony_ci *	Bjorn Helgaas <bjorn.helgaas@hp.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * This driver implements HP Open Source Review Board proposal 1842,
962306a36Sopenharmony_ci * which was approved on 9/20/2006.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * For technical documentation, see the HP SPPA Firmware EAS, Appendix F.
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * ACPI does not define a mechanism for AML methods to call native firmware
1462306a36Sopenharmony_ci * interfaces such as PAL or SAL.  This OpRegion handler adds such a mechanism.
1562306a36Sopenharmony_ci * After the handler is installed, an AML method can call native firmware by
1662306a36Sopenharmony_ci * storing the arguments and firmware entry point to specific offsets in the
1762306a36Sopenharmony_ci * OpRegion.  When AML reads the "return value" offset from the OpRegion, this
1862306a36Sopenharmony_ci * handler loads up the arguments, makes the firmware call, and returns the
1962306a36Sopenharmony_ci * result.
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <linux/module.h>
2362306a36Sopenharmony_ci#include <linux/acpi.h>
2462306a36Sopenharmony_ci#include <asm/sal.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ciMODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
2762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2862306a36Sopenharmony_ciMODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic bool force_register;
3162306a36Sopenharmony_cimodule_param_named(force, force_register, bool, 0);
3262306a36Sopenharmony_ciMODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define AML_NFW_SPACE		0xA1
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistruct ia64_pdesc {
3762306a36Sopenharmony_ci	void *ip;
3862306a36Sopenharmony_ci	void *gp;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/*
4262306a36Sopenharmony_ci * N.B.  The layout of this structure is defined in the HP SPPA FW EAS, and
4362306a36Sopenharmony_ci *	 the member offsets are embedded in AML methods.
4462306a36Sopenharmony_ci */
4562306a36Sopenharmony_cistruct ia64_nfw_context {
4662306a36Sopenharmony_ci	u64 arg[8];
4762306a36Sopenharmony_ci	struct ia64_sal_retval ret;
4862306a36Sopenharmony_ci	u64 ip;
4962306a36Sopenharmony_ci	u64 gp;
5062306a36Sopenharmony_ci	u64 pad[2];
5162306a36Sopenharmony_ci};
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void *virt_map(u64 address)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	if (address & (1UL << 63))
5662306a36Sopenharmony_ci		return (void *) (__IA64_UNCACHED_OFFSET | address);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	return __va(address);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic void aml_nfw_execute(struct ia64_nfw_context *c)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	struct ia64_pdesc virt_entry;
6462306a36Sopenharmony_ci	ia64_sal_handler entry;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	virt_entry.ip = virt_map(c->ip);
6762306a36Sopenharmony_ci	virt_entry.gp = virt_map(c->gp);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	entry = (ia64_sal_handler) &virt_entry;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	IA64_FW_CALL(entry, c->ret,
7262306a36Sopenharmony_ci		     c->arg[0], c->arg[1], c->arg[2], c->arg[3],
7362306a36Sopenharmony_ci		     c->arg[4], c->arg[5], c->arg[6], c->arg[7]);
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	switch (bit_width) {
7962306a36Sopenharmony_ci	case 8:
8062306a36Sopenharmony_ci		*value = *(u8 *)offset;
8162306a36Sopenharmony_ci		break;
8262306a36Sopenharmony_ci	case 16:
8362306a36Sopenharmony_ci		*value = *(u16 *)offset;
8462306a36Sopenharmony_ci		break;
8562306a36Sopenharmony_ci	case 32:
8662306a36Sopenharmony_ci		*value = *(u32 *)offset;
8762306a36Sopenharmony_ci		break;
8862306a36Sopenharmony_ci	case 64:
8962306a36Sopenharmony_ci		*value = *(u64 *)offset;
9062306a36Sopenharmony_ci		break;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	switch (bit_width) {
9762306a36Sopenharmony_ci	case 8:
9862306a36Sopenharmony_ci		*(u8 *) offset = *value;
9962306a36Sopenharmony_ci		break;
10062306a36Sopenharmony_ci	case 16:
10162306a36Sopenharmony_ci		*(u16 *) offset = *value;
10262306a36Sopenharmony_ci		break;
10362306a36Sopenharmony_ci	case 32:
10462306a36Sopenharmony_ci		*(u32 *) offset = *value;
10562306a36Sopenharmony_ci		break;
10662306a36Sopenharmony_ci	case 64:
10762306a36Sopenharmony_ci		*(u64 *) offset = *value;
10862306a36Sopenharmony_ci		break;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
11362306a36Sopenharmony_ci	u32 bit_width, u64 *value, void *handler_context,
11462306a36Sopenharmony_ci	void *region_context)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct ia64_nfw_context *context = handler_context;
11762306a36Sopenharmony_ci	u8 *offset = (u8 *) context + address;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (bit_width !=  8 && bit_width != 16 &&
12062306a36Sopenharmony_ci	    bit_width != 32 && bit_width != 64)
12162306a36Sopenharmony_ci		return AE_BAD_PARAMETER;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context))
12462306a36Sopenharmony_ci		return AE_BAD_PARAMETER;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	switch (function) {
12762306a36Sopenharmony_ci	case ACPI_READ:
12862306a36Sopenharmony_ci		if (address == offsetof(struct ia64_nfw_context, ret))
12962306a36Sopenharmony_ci			aml_nfw_execute(context);
13062306a36Sopenharmony_ci		aml_nfw_read_arg(offset, bit_width, value);
13162306a36Sopenharmony_ci		break;
13262306a36Sopenharmony_ci	case ACPI_WRITE:
13362306a36Sopenharmony_ci		aml_nfw_write_arg(offset, bit_width, value);
13462306a36Sopenharmony_ci		break;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return AE_OK;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic struct ia64_nfw_context global_context;
14162306a36Sopenharmony_cistatic int global_handler_registered;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic int aml_nfw_add_global_handler(void)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	acpi_status status;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (global_handler_registered)
14862306a36Sopenharmony_ci		return 0;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
15162306a36Sopenharmony_ci		AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context);
15262306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
15362306a36Sopenharmony_ci		return -ENODEV;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	global_handler_registered = 1;
15662306a36Sopenharmony_ci	printk(KERN_INFO "Global 0x%02X opregion handler registered\n",
15762306a36Sopenharmony_ci		AML_NFW_SPACE);
15862306a36Sopenharmony_ci	return 0;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic int aml_nfw_remove_global_handler(void)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	acpi_status status;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	if (!global_handler_registered)
16662306a36Sopenharmony_ci		return 0;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
16962306a36Sopenharmony_ci		AML_NFW_SPACE, aml_nfw_handler);
17062306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
17162306a36Sopenharmony_ci		return -ENODEV;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	global_handler_registered = 0;
17462306a36Sopenharmony_ci	printk(KERN_INFO "Global 0x%02X opregion handler removed\n",
17562306a36Sopenharmony_ci		AML_NFW_SPACE);
17662306a36Sopenharmony_ci	return 0;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int aml_nfw_add(struct acpi_device *device)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	/*
18262306a36Sopenharmony_ci	 * We would normally allocate a new context structure and install
18362306a36Sopenharmony_ci	 * the address space handler for the specific device we found.
18462306a36Sopenharmony_ci	 * But the HP-UX implementation shares a single global context
18562306a36Sopenharmony_ci	 * and always puts the handler at the root, so we'll do the same.
18662306a36Sopenharmony_ci	 */
18762306a36Sopenharmony_ci	return aml_nfw_add_global_handler();
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic void aml_nfw_remove(struct acpi_device *device)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	aml_nfw_remove_global_handler();
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic const struct acpi_device_id aml_nfw_ids[] = {
19662306a36Sopenharmony_ci	{"HPQ5001", 0},
19762306a36Sopenharmony_ci	{"", 0}
19862306a36Sopenharmony_ci};
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic struct acpi_driver acpi_aml_nfw_driver = {
20162306a36Sopenharmony_ci	.name = "native firmware",
20262306a36Sopenharmony_ci	.ids = aml_nfw_ids,
20362306a36Sopenharmony_ci	.ops = {
20462306a36Sopenharmony_ci		.add = aml_nfw_add,
20562306a36Sopenharmony_ci		.remove = aml_nfw_remove,
20662306a36Sopenharmony_ci		},
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic int __init aml_nfw_init(void)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	int result;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (force_register)
21462306a36Sopenharmony_ci		aml_nfw_add_global_handler();
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	result = acpi_bus_register_driver(&acpi_aml_nfw_driver);
21762306a36Sopenharmony_ci	if (result < 0) {
21862306a36Sopenharmony_ci		aml_nfw_remove_global_handler();
21962306a36Sopenharmony_ci		return result;
22062306a36Sopenharmony_ci	}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return 0;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic void __exit aml_nfw_exit(void)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
22862306a36Sopenharmony_ci	aml_nfw_remove_global_handler();
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cimodule_init(aml_nfw_init);
23262306a36Sopenharmony_cimodule_exit(aml_nfw_exit);
233