18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * ACPI support for platform bus type.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2012, Intel Corporation
68c2ecf20Sopenharmony_ci * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
78c2ecf20Sopenharmony_ci *          Mathias Nyman <mathias.nyman@linux.intel.com>
88c2ecf20Sopenharmony_ci *          Rafael J. Wysocki <rafael.j.wysocki@intel.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/acpi.h>
128c2ecf20Sopenharmony_ci#include <linux/device.h>
138c2ecf20Sopenharmony_ci#include <linux/err.h>
148c2ecf20Sopenharmony_ci#include <linux/kernel.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
178c2ecf20Sopenharmony_ci#include <linux/pci.h>
188c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include "internal.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic const struct acpi_device_id forbidden_id_list[] = {
238c2ecf20Sopenharmony_ci	{"PNP0000",  0},	/* PIC */
248c2ecf20Sopenharmony_ci	{"PNP0100",  0},	/* Timer */
258c2ecf20Sopenharmony_ci	{"PNP0200",  0},	/* AT DMA Controller */
268c2ecf20Sopenharmony_ci	{"ACPI0009", 0},	/* IOxAPIC */
278c2ecf20Sopenharmony_ci	{"ACPI000A", 0},	/* IOAPIC */
288c2ecf20Sopenharmony_ci	{"SMB0001",  0},	/* ACPI SMBUS virtual device */
298c2ecf20Sopenharmony_ci	{"", 0},
308c2ecf20Sopenharmony_ci};
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic struct platform_device *acpi_platform_device_find_by_companion(struct acpi_device *adev)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	struct device *dev;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev);
378c2ecf20Sopenharmony_ci	return dev ? to_platform_device(dev) : NULL;
388c2ecf20Sopenharmony_ci}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic int acpi_platform_device_remove_notify(struct notifier_block *nb,
418c2ecf20Sopenharmony_ci					      unsigned long value, void *arg)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	struct acpi_device *adev = arg;
448c2ecf20Sopenharmony_ci	struct platform_device *pdev;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	switch (value) {
478c2ecf20Sopenharmony_ci	case ACPI_RECONFIG_DEVICE_ADD:
488c2ecf20Sopenharmony_ci		/* Nothing to do here */
498c2ecf20Sopenharmony_ci		break;
508c2ecf20Sopenharmony_ci	case ACPI_RECONFIG_DEVICE_REMOVE:
518c2ecf20Sopenharmony_ci		if (!acpi_device_enumerated(adev))
528c2ecf20Sopenharmony_ci			break;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci		pdev = acpi_platform_device_find_by_companion(adev);
558c2ecf20Sopenharmony_ci		if (!pdev)
568c2ecf20Sopenharmony_ci			break;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci		platform_device_unregister(pdev);
598c2ecf20Sopenharmony_ci		put_device(&pdev->dev);
608c2ecf20Sopenharmony_ci		break;
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	return NOTIFY_OK;
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic struct notifier_block acpi_platform_notifier = {
678c2ecf20Sopenharmony_ci	.notifier_call = acpi_platform_device_remove_notify,
688c2ecf20Sopenharmony_ci};
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic void acpi_platform_fill_resource(struct acpi_device *adev,
718c2ecf20Sopenharmony_ci	const struct resource *src, struct resource *dest)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	struct device *parent;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	*dest = *src;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	/*
788c2ecf20Sopenharmony_ci	 * If the device has parent we need to take its resources into
798c2ecf20Sopenharmony_ci	 * account as well because this device might consume part of those.
808c2ecf20Sopenharmony_ci	 */
818c2ecf20Sopenharmony_ci	parent = acpi_get_first_physical_node(adev->parent);
828c2ecf20Sopenharmony_ci	if (parent && dev_is_pci(parent))
838c2ecf20Sopenharmony_ci		dest->parent = pci_find_resource(to_pci_dev(parent), dest);
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci/**
878c2ecf20Sopenharmony_ci * acpi_create_platform_device - Create platform device for ACPI device node
888c2ecf20Sopenharmony_ci * @adev: ACPI device node to create a platform device for.
898c2ecf20Sopenharmony_ci * @properties: Optional collection of build-in properties.
908c2ecf20Sopenharmony_ci *
918c2ecf20Sopenharmony_ci * Check if the given @adev can be represented as a platform device and, if
928c2ecf20Sopenharmony_ci * that's the case, create and register a platform device, populate its common
938c2ecf20Sopenharmony_ci * resources and returns a pointer to it.  Otherwise, return %NULL.
948c2ecf20Sopenharmony_ci *
958c2ecf20Sopenharmony_ci * Name of the platform device will be the same as @adev's.
968c2ecf20Sopenharmony_ci */
978c2ecf20Sopenharmony_cistruct platform_device *acpi_create_platform_device(struct acpi_device *adev,
988c2ecf20Sopenharmony_ci					struct property_entry *properties)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	struct platform_device *pdev = NULL;
1018c2ecf20Sopenharmony_ci	struct platform_device_info pdevinfo;
1028c2ecf20Sopenharmony_ci	struct resource_entry *rentry;
1038c2ecf20Sopenharmony_ci	struct list_head resource_list;
1048c2ecf20Sopenharmony_ci	struct resource *resources = NULL;
1058c2ecf20Sopenharmony_ci	int count;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	/* If the ACPI node already has a physical device attached, skip it. */
1088c2ecf20Sopenharmony_ci	if (adev->physical_node_count)
1098c2ecf20Sopenharmony_ci		return NULL;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (!acpi_match_device_ids(adev, forbidden_id_list))
1128c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&resource_list);
1158c2ecf20Sopenharmony_ci	count = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);
1168c2ecf20Sopenharmony_ci	if (count < 0) {
1178c2ecf20Sopenharmony_ci		return NULL;
1188c2ecf20Sopenharmony_ci	} else if (count > 0) {
1198c2ecf20Sopenharmony_ci		resources = kcalloc(count, sizeof(struct resource),
1208c2ecf20Sopenharmony_ci				    GFP_KERNEL);
1218c2ecf20Sopenharmony_ci		if (!resources) {
1228c2ecf20Sopenharmony_ci			dev_err(&adev->dev, "No memory for resources\n");
1238c2ecf20Sopenharmony_ci			acpi_dev_free_resource_list(&resource_list);
1248c2ecf20Sopenharmony_ci			return ERR_PTR(-ENOMEM);
1258c2ecf20Sopenharmony_ci		}
1268c2ecf20Sopenharmony_ci		count = 0;
1278c2ecf20Sopenharmony_ci		list_for_each_entry(rentry, &resource_list, node)
1288c2ecf20Sopenharmony_ci			acpi_platform_fill_resource(adev, rentry->res,
1298c2ecf20Sopenharmony_ci						    &resources[count++]);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		acpi_dev_free_resource_list(&resource_list);
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	memset(&pdevinfo, 0, sizeof(pdevinfo));
1358c2ecf20Sopenharmony_ci	/*
1368c2ecf20Sopenharmony_ci	 * If the ACPI node has a parent and that parent has a physical device
1378c2ecf20Sopenharmony_ci	 * attached to it, that physical device should be the parent of the
1388c2ecf20Sopenharmony_ci	 * platform device we are about to create.
1398c2ecf20Sopenharmony_ci	 */
1408c2ecf20Sopenharmony_ci	pdevinfo.parent = adev->parent ?
1418c2ecf20Sopenharmony_ci		acpi_get_first_physical_node(adev->parent) : NULL;
1428c2ecf20Sopenharmony_ci	pdevinfo.name = dev_name(&adev->dev);
1438c2ecf20Sopenharmony_ci	pdevinfo.id = -1;
1448c2ecf20Sopenharmony_ci	pdevinfo.res = resources;
1458c2ecf20Sopenharmony_ci	pdevinfo.num_res = count;
1468c2ecf20Sopenharmony_ci	pdevinfo.fwnode = acpi_fwnode_handle(adev);
1478c2ecf20Sopenharmony_ci	pdevinfo.properties = properties;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	if (acpi_dma_supported(adev))
1508c2ecf20Sopenharmony_ci		pdevinfo.dma_mask = DMA_BIT_MASK(32);
1518c2ecf20Sopenharmony_ci	else
1528c2ecf20Sopenharmony_ci		pdevinfo.dma_mask = 0;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	pdev = platform_device_register_full(&pdevinfo);
1558c2ecf20Sopenharmony_ci	if (IS_ERR(pdev))
1568c2ecf20Sopenharmony_ci		dev_err(&adev->dev, "platform device creation failed: %ld\n",
1578c2ecf20Sopenharmony_ci			PTR_ERR(pdev));
1588c2ecf20Sopenharmony_ci	else {
1598c2ecf20Sopenharmony_ci		set_dev_node(&pdev->dev, acpi_get_node(adev->handle));
1608c2ecf20Sopenharmony_ci		dev_dbg(&adev->dev, "created platform device %s\n",
1618c2ecf20Sopenharmony_ci			dev_name(&pdev->dev));
1628c2ecf20Sopenharmony_ci	}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	kfree(resources);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	return pdev;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_create_platform_device);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_civoid __init acpi_platform_init(void)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	acpi_reconfig_notifier_register(&acpi_platform_notifier);
1738c2ecf20Sopenharmony_ci}
174