| 1 | |
| 2 | |
| 3 | * ACPI support for platform bus type.
|
| 4 | *
|
| 5 | * Copyright (C) 2012, Intel Corporation
|
| 6 | * Authors: Mika Westerberg <[email protected]>
|
| 7 | * Mathias Nyman <[email protected]>
|
| 8 | * Rafael J. Wysocki <[email protected]>
|
| 9 |
|
| 10 |
|
| 11 | #include <linux/acpi.h>
|
| 12 | #include <linux/bits.h>
|
| 13 | #include <linux/device.h>
|
| 14 | #include <linux/err.h>
|
| 15 | #include <linux/ioport.h>
|
| 16 | #include <linux/kernel.h>
|
| 17 | #include <linux/module.h>
|
| 18 | #include <linux/dma-mapping.h>
|
| 19 | #include <linux/pci.h>
|
| 20 | #include <linux/platform_device.h>
|
| 21 |
|
| 22 | #include "internal.h"
|
| 23 |
|
| 24 |
|
| 25 | #define ACPI_ALLOW_WO_RESOURCES BIT(0)
|
| 26 |
|
| 27 | static const struct acpi_device_id forbidden_id_list[] = {
|
| 28 | {"ACPI0009", 0},
|
| 29 | {"ACPI000A", 0},
|
| 30 | {"PNP0000", 0},
|
| 31 | {"PNP0100", 0},
|
| 32 | {"PNP0200", 0},
|
| 33 | {ACPI_SMBUS_MS_HID, ACPI_ALLOW_WO_RESOURCES},
|
| 34 | { }
|
| 35 | };
|
| 36 |
|
| 37 | static struct platform_device *acpi_platform_device_find_by_companion(struct acpi_device *adev)
|
| 38 | {
|
| 39 | struct device *dev;
|
| 40 |
|
| 41 | dev = bus_find_device_by_acpi_dev(&platform_bus_type, adev);
|
| 42 | return dev ? to_platform_device(dev) : NULL;
|
| 43 | }
|
| 44 |
|
| 45 | static int acpi_platform_device_remove_notify(struct notifier_block *nb,
|
| 46 | unsigned long value, void *arg)
|
| 47 | {
|
| 48 | struct acpi_device *adev = arg;
|
| 49 | struct platform_device *pdev;
|
| 50 |
|
| 51 | switch (value) {
|
| 52 | case ACPI_RECONFIG_DEVICE_ADD:
|
| 53 |
|
| 54 | break;
|
| 55 | case ACPI_RECONFIG_DEVICE_REMOVE:
|
| 56 | if (!acpi_device_enumerated(adev))
|
| 57 | break;
|
| 58 |
|
| 59 | pdev = acpi_platform_device_find_by_companion(adev);
|
| 60 | if (!pdev)
|
| 61 | break;
|
| 62 |
|
| 63 | platform_device_unregister(pdev);
|
| 64 | put_device(&pdev->dev);
|
| 65 | break;
|
| 66 | }
|
| 67 |
|
| 68 | return NOTIFY_OK;
|
| 69 | }
|
| 70 |
|
| 71 | static struct notifier_block acpi_platform_notifier = {
|
| 72 | .notifier_call = acpi_platform_device_remove_notify,
|
| 73 | };
|
| 74 |
|
| 75 | static unsigned int acpi_platform_adjust_resources(struct acpi_device *adev,
|
| 76 | struct resource *new_res,
|
| 77 | struct resource *resources,
|
| 78 | unsigned int count)
|
| 79 | {
|
| 80 | unsigned int i;
|
| 81 |
|
| 82 | if (!(new_res->flags & (IORESOURCE_IO | IORESOURCE_MEM)))
|
| 83 | return count;
|
| 84 |
|
| 85 | for (i = 0; i < count; ) {
|
| 86 | struct resource *res = &resources[i];
|
| 87 |
|
| 88 | |
| 89 | * Look for overlaps of resources of the same type that would
|
| 90 | * cause resource insertion to fail down the road.
|
| 91 |
|
| 92 | if (__resource_contains_unbound(res, new_res) ||
|
| 93 | __resource_contains_unbound(new_res, res) ||
|
| 94 | resource_type(new_res) != resource_type(res) ||
|
| 95 | !resource_union(new_res, res, new_res)) {
|
| 96 | i++;
|
| 97 | continue;
|
| 98 | }
|
| 99 |
|
| 100 | dev_info(&adev->dev, "%pR expanded due to overlap\n", new_res);
|
| 101 | |
| 102 | * Eliminate the previously processed resource that overlapped
|
| 103 | * with the new one because it is not necessary any more.
|
| 104 |
|
| 105 | memmove(res, res + 1, (--count - i) * sizeof(*res));
|
| 106 | }
|
| 107 |
|
| 108 | return count;
|
| 109 | }
|
| 110 |
|
| 111 | static void acpi_platform_fill_resource(struct device *parent,
|
| 112 | const struct resource *src,
|
| 113 | struct resource *dest)
|
| 114 | {
|
| 115 | *dest = *src;
|
| 116 | |
| 117 | * If the device has parent we need to take its resources into
|
| 118 | * account as well because this device might consume part of those.
|
| 119 |
|
| 120 | if (parent && dev_is_pci(parent))
|
| 121 | dest->parent = pci_find_resource(to_pci_dev(parent), dest);
|
| 122 | }
|
| 123 |
|
| 124 | static unsigned int acpi_platform_resource_count(struct acpi_resource *ares, void *data)
|
| 125 | {
|
| 126 | bool *has_resources = data;
|
| 127 |
|
| 128 | *has_resources = true;
|
| 129 |
|
| 130 | return AE_CTRL_TERMINATE;
|
| 131 | }
|
| 132 |
|
| 133 | |
| 134 | * acpi_create_platform_device - Create platform device for ACPI device node
|
| 135 | * @adev: ACPI device node to create a platform device for.
|
| 136 | * @properties: Optional collection of build-in properties.
|
| 137 | *
|
| 138 | * Check if the given @adev can be represented as a platform device and, if
|
| 139 | * that's the case, create and register a platform device, populate its common
|
| 140 | * resources and returns a pointer to it. Otherwise, return %NULL.
|
| 141 | *
|
| 142 | * Name of the platform device will be the same as @adev's.
|
| 143 |
|
| 144 | struct platform_device *acpi_create_platform_device(struct acpi_device *adev,
|
| 145 | const struct property_entry *properties)
|
| 146 | {
|
| 147 | struct acpi_device *p = acpi_dev_parent(adev);
|
| 148 | struct device *parent __free(put_device) = acpi_bus_get_primary_device(p);
|
| 149 | struct platform_device *pdev = NULL;
|
| 150 | struct platform_device_info pdevinfo;
|
| 151 | const struct acpi_device_id *match;
|
| 152 | struct resource *resources = NULL;
|
| 153 | int count = 0;
|
| 154 |
|
| 155 |
|
| 156 | if (adev->physical_node_count && !adev->pnp.type.backlight)
|
| 157 | return NULL;
|
| 158 |
|
| 159 | match = acpi_match_acpi_device(forbidden_id_list, adev);
|
| 160 | if (match) {
|
| 161 | if (match->driver_data & ACPI_ALLOW_WO_RESOURCES) {
|
| 162 | bool has_resources = false;
|
| 163 |
|
| 164 | acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
|
| 165 | acpi_platform_resource_count, &has_resources);
|
| 166 | if (has_resources)
|
| 167 | return ERR_PTR(-EINVAL);
|
| 168 | } else {
|
| 169 | return ERR_PTR(-EINVAL);
|
| 170 | }
|
| 171 | }
|
| 172 |
|
| 173 | if (adev->device_type == ACPI_BUS_TYPE_DEVICE) {
|
| 174 | LIST_HEAD(resource_list);
|
| 175 |
|
| 176 | count = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);
|
| 177 | if (count < 0)
|
| 178 | return ERR_PTR(-ENODATA);
|
| 179 |
|
| 180 | if (count > 0) {
|
| 181 | struct resource_entry *rentry;
|
| 182 |
|
| 183 | resources = kzalloc_objs(*resources, count);
|
| 184 | if (!resources) {
|
| 185 | acpi_dev_free_resource_list(&resource_list);
|
| 186 | return ERR_PTR(-ENOMEM);
|
| 187 | }
|
| 188 | count = 0;
|
| 189 | list_for_each_entry(rentry, &resource_list, node) {
|
| 190 | count = acpi_platform_adjust_resources(adev,
|
| 191 | rentry->res,
|
| 192 | resources,
|
| 193 | count);
|
| 194 | acpi_platform_fill_resource(parent, rentry->res,
|
| 195 | &resources[count++]);
|
| 196 | }
|
| 197 | acpi_dev_free_resource_list(&resource_list);
|
| 198 | }
|
| 199 | }
|
| 200 |
|
| 201 | memset(&pdevinfo, 0, sizeof(pdevinfo));
|
| 202 | |
| 203 | * If the ACPI node has a parent and that parent has a physical device
|
| 204 | * attached to it, that physical device should be the parent of the
|
| 205 | * platform device we are about to create.
|
| 206 |
|
| 207 | pdevinfo.parent = parent;
|
| 208 | pdevinfo.name = dev_name(&adev->dev);
|
| 209 | pdevinfo.id = PLATFORM_DEVID_NONE;
|
| 210 | pdevinfo.res = resources;
|
| 211 | pdevinfo.num_res = count;
|
| 212 | pdevinfo.fwnode = acpi_fwnode_handle(adev);
|
| 213 | pdevinfo.properties = properties;
|
| 214 |
|
| 215 | if (acpi_dma_supported(adev))
|
| 216 | pdevinfo.dma_mask = DMA_BIT_MASK(32);
|
| 217 | else
|
| 218 | pdevinfo.dma_mask = 0;
|
| 219 |
|
| 220 | pdev = platform_device_register_full(&pdevinfo);
|
| 221 | if (IS_ERR(pdev))
|
| 222 | dev_err(&adev->dev, "platform device creation failed: %ld\n",
|
| 223 | PTR_ERR(pdev));
|
| 224 | else {
|
| 225 | set_dev_node(&pdev->dev, acpi_get_node(adev->handle));
|
| 226 | dev_dbg(&adev->dev, "created platform device %s\n",
|
| 227 | dev_name(&pdev->dev));
|
| 228 | }
|
| 229 |
|
| 230 | kfree(resources);
|
| 231 |
|
| 232 | return pdev;
|
| 233 | }
|
| 234 | EXPORT_SYMBOL_GPL(acpi_create_platform_device);
|
| 235 |
|
| 236 | void __init acpi_platform_init(void)
|
| 237 | {
|
| 238 | acpi_reconfig_notifier_register(&acpi_platform_notifier);
|
| 239 | }
|