1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ACPI support for platform bus type. |
4 | * |
5 | * Copyright (C) 2012, Intel Corporation |
6 | * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> |
7 | * Mathias Nyman <mathias.nyman@linux.intel.com> |
8 | * Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
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/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/dma-mapping.h> |
18 | #include <linux/pci.h> |
19 | #include <linux/platform_device.h> |
20 | |
21 | #include "internal.h" |
22 | |
23 | /* Exclude devices that have no _CRS resources provided */ |
24 | #define ACPI_ALLOW_WO_RESOURCES BIT(0) |
25 | |
26 | static const struct acpi_device_id forbidden_id_list[] = { |
27 | {"ACPI0009" , 0}, /* IOxAPIC */ |
28 | {"ACPI000A" , 0}, /* IOAPIC */ |
29 | {"PNP0000" , 0}, /* PIC */ |
30 | {"PNP0100" , 0}, /* Timer */ |
31 | {"PNP0200" , 0}, /* AT DMA Controller */ |
32 | {ACPI_SMBUS_MS_HID, ACPI_ALLOW_WO_RESOURCES}, /* ACPI SMBUS virtual device */ |
33 | { } |
34 | }; |
35 | |
36 | static struct platform_device *acpi_platform_device_find_by_companion(struct acpi_device *adev) |
37 | { |
38 | struct device *dev; |
39 | |
40 | dev = bus_find_device_by_acpi_dev(bus: &platform_bus_type, adev); |
41 | return dev ? to_platform_device(dev) : NULL; |
42 | } |
43 | |
44 | static int acpi_platform_device_remove_notify(struct notifier_block *nb, |
45 | unsigned long value, void *arg) |
46 | { |
47 | struct acpi_device *adev = arg; |
48 | struct platform_device *pdev; |
49 | |
50 | switch (value) { |
51 | case ACPI_RECONFIG_DEVICE_ADD: |
52 | /* Nothing to do here */ |
53 | break; |
54 | case ACPI_RECONFIG_DEVICE_REMOVE: |
55 | if (!acpi_device_enumerated(adev)) |
56 | break; |
57 | |
58 | pdev = acpi_platform_device_find_by_companion(adev); |
59 | if (!pdev) |
60 | break; |
61 | |
62 | platform_device_unregister(pdev); |
63 | put_device(dev: &pdev->dev); |
64 | break; |
65 | } |
66 | |
67 | return NOTIFY_OK; |
68 | } |
69 | |
70 | static struct notifier_block acpi_platform_notifier = { |
71 | .notifier_call = acpi_platform_device_remove_notify, |
72 | }; |
73 | |
74 | static void acpi_platform_fill_resource(struct acpi_device *adev, |
75 | const struct resource *src, struct resource *dest) |
76 | { |
77 | struct device *parent; |
78 | |
79 | *dest = *src; |
80 | |
81 | /* |
82 | * If the device has parent we need to take its resources into |
83 | * account as well because this device might consume part of those. |
84 | */ |
85 | parent = acpi_get_first_physical_node(adev: acpi_dev_parent(adev)); |
86 | if (parent && dev_is_pci(parent)) |
87 | dest->parent = pci_find_resource(to_pci_dev(parent), res: dest); |
88 | } |
89 | |
90 | static unsigned int acpi_platform_resource_count(struct acpi_resource *ares, void *data) |
91 | { |
92 | bool *has_resources = data; |
93 | |
94 | *has_resources = true; |
95 | |
96 | return AE_CTRL_TERMINATE; |
97 | } |
98 | |
99 | /** |
100 | * acpi_create_platform_device - Create platform device for ACPI device node |
101 | * @adev: ACPI device node to create a platform device for. |
102 | * @properties: Optional collection of build-in properties. |
103 | * |
104 | * Check if the given @adev can be represented as a platform device and, if |
105 | * that's the case, create and register a platform device, populate its common |
106 | * resources and returns a pointer to it. Otherwise, return %NULL. |
107 | * |
108 | * Name of the platform device will be the same as @adev's. |
109 | */ |
110 | struct platform_device *acpi_create_platform_device(struct acpi_device *adev, |
111 | const struct property_entry *properties) |
112 | { |
113 | struct acpi_device *parent = acpi_dev_parent(adev); |
114 | struct platform_device *pdev = NULL; |
115 | struct platform_device_info pdevinfo; |
116 | const struct acpi_device_id *match; |
117 | struct resource_entry *rentry; |
118 | struct list_head resource_list; |
119 | struct resource *resources = NULL; |
120 | int count; |
121 | |
122 | /* If the ACPI node already has a physical device attached, skip it. */ |
123 | if (adev->physical_node_count) |
124 | return NULL; |
125 | |
126 | match = acpi_match_acpi_device(ids: forbidden_id_list, adev); |
127 | if (match) { |
128 | if (match->driver_data & ACPI_ALLOW_WO_RESOURCES) { |
129 | bool has_resources = false; |
130 | |
131 | acpi_walk_resources(device: adev->handle, METHOD_NAME__CRS, |
132 | user_function: acpi_platform_resource_count, context: &has_resources); |
133 | if (has_resources) |
134 | return ERR_PTR(error: -EINVAL); |
135 | } else { |
136 | return ERR_PTR(error: -EINVAL); |
137 | } |
138 | } |
139 | |
140 | INIT_LIST_HEAD(list: &resource_list); |
141 | count = acpi_dev_get_resources(adev, list: &resource_list, NULL, NULL); |
142 | if (count < 0) |
143 | return NULL; |
144 | if (count > 0) { |
145 | resources = kcalloc(n: count, size: sizeof(*resources), GFP_KERNEL); |
146 | if (!resources) { |
147 | acpi_dev_free_resource_list(list: &resource_list); |
148 | return ERR_PTR(error: -ENOMEM); |
149 | } |
150 | count = 0; |
151 | list_for_each_entry(rentry, &resource_list, node) |
152 | acpi_platform_fill_resource(adev, src: rentry->res, |
153 | dest: &resources[count++]); |
154 | |
155 | acpi_dev_free_resource_list(list: &resource_list); |
156 | } |
157 | |
158 | memset(&pdevinfo, 0, sizeof(pdevinfo)); |
159 | /* |
160 | * If the ACPI node has a parent and that parent has a physical device |
161 | * attached to it, that physical device should be the parent of the |
162 | * platform device we are about to create. |
163 | */ |
164 | pdevinfo.parent = parent ? acpi_get_first_physical_node(adev: parent) : NULL; |
165 | pdevinfo.name = dev_name(dev: &adev->dev); |
166 | pdevinfo.id = PLATFORM_DEVID_NONE; |
167 | pdevinfo.res = resources; |
168 | pdevinfo.num_res = count; |
169 | pdevinfo.fwnode = acpi_fwnode_handle(adev); |
170 | pdevinfo.properties = properties; |
171 | |
172 | if (acpi_dma_supported(adev)) |
173 | pdevinfo.dma_mask = DMA_BIT_MASK(32); |
174 | else |
175 | pdevinfo.dma_mask = 0; |
176 | |
177 | pdev = platform_device_register_full(pdevinfo: &pdevinfo); |
178 | if (IS_ERR(ptr: pdev)) |
179 | dev_err(&adev->dev, "platform device creation failed: %ld\n" , |
180 | PTR_ERR(pdev)); |
181 | else { |
182 | set_dev_node(dev: &pdev->dev, node: acpi_get_node(handle: adev->handle)); |
183 | dev_dbg(&adev->dev, "created platform device %s\n" , |
184 | dev_name(&pdev->dev)); |
185 | } |
186 | |
187 | kfree(objp: resources); |
188 | |
189 | return pdev; |
190 | } |
191 | EXPORT_SYMBOL_GPL(acpi_create_platform_device); |
192 | |
193 | void __init acpi_platform_init(void) |
194 | { |
195 | acpi_reconfig_notifier_register(nb: &acpi_platform_notifier); |
196 | } |
197 | |