diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-24 08:46:03 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-24 08:46:03 -0700 |
| commit | cf9610f9116d55c5a66ef9f1faecacabd93a9322 (patch) | |
| tree | 757a3d280760c654767631cab5ad8af2d86d1848 /drivers/base/platform.c | |
| download | linux-stable-cf9610f9116d55c5a66ef9f1faecacabd93a9322.tar.gz linux-stable-cf9610f9116d55c5a66ef9f1faecacabd93a9322.zip | |
Merge tag 'pinctrl-v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-pinctrlgrafted
Pull pin control updates from Linus Walleij:
"Core changes:
- Use the non-blocking mux_state_try_select() in the generic MUX pin
control back-end
- Free pin maps on pinctrl_generic_to_map() failure in the core
helpers
New hardware pin control:
- Qualcomm Maili TLMM SoC, PMG1110 PMIC, and Eliza LPASS LPI TLMM SoC
(this is for the low power audio, LPASS = Low Power Audio SubSystem
portions)
- Upboard support on top of the Intel pin controller
- Apple T6030 and T6031 support (just compatible strings)
- Samsung Exynos 8855 SoC
- Mediatek MT6858 SoC
- Rockchip RK3308B and RV1106 SoCs
- Airoha EN7523 and AN7563 SoCs
Improvements:
- Qualcomm TLMM GPIOs are unconditionally marked as wakeup capable
- Qualcomm improvements on top of the Qualcomm PDC (power domain
controller) irqchip improvements from tglx:s tree
- Qualcomm IPQ806x and IPQ9650 non-urgent pin grop fixups
- The NPCM8xx driver has been put into shape fixing many pin and
group definitions
- Some non-urgent Tegra 264 pin assignments are fixed up
- Some non-urgent fixups to Airoha AN7581 and AN7583 registers and
pin assignments, missing features etc"
* tag 'pinctrl-v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-pinctrl: (130 commits)
dt-bindings: pinctrl: Convert TI DA850 pupd to DT schema
pinctrl: meson: sync some modify from A4
pinctrl: meson: a4: Add input enable pin configuration
pinctrl: realtek: rtd1625: remove unused group name spdif_sel
pinctrl: realtek: rtd1625: fix base_bit for VE4 GPIO 13
dt-bindings: pinctrl: microchip,pic32mzda-pinctrl: Convert to DT schema
Revert "Merge branch 'ib-rsk7204' into devel"
pinctrl: rockchip: Add RV1106 pinctrl support
dt-bindings: pinctrl: rockchip: Add RV1106 compatible
pinctrl: rockchip: Decode drive strength in the get function
pinctrl: fix PINCTRL_GENERIC_MUX not always being selectable
pinctrl: airoha: add support of an7563 SoC
dt-bindings: pinctrl: airoha: add support of an7563 pin controller
pinctrl: airoha: try to find chip scu node by phandle first
pinctrl: airoha: add support of en7523 SoC
dt-bindings: pinctrl: airoha: add support of en7523 pin controller
pinctrl: airoha: an7583: add support for olt pinmux
pinctrl: airoha: an7583: add support for pon_alt pinmux
pinctrl: airoha: an7583: add support for npu_uart pinmux
dt-bindings: pinctrl: airoha: an7583: add missed features
...
Diffstat (limited to 'drivers/base/platform.c')
| -rw-r--r-- | drivers/base/platform.c | 1629 |
1 files changed, 1629 insertions, 0 deletions
diff --git a/drivers/base/platform.c b/drivers/base/platform.c new file mode 100644 index 000000000..66f9ec73d --- /dev/null +++ b/drivers/base/platform.c @@ -0,0 +1,1629 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * platform.c - platform 'pseudo' bus for legacy devices + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * + * Please see Documentation/driver-api/driver-model/platform.rst for more + * information. + */ + +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/dma-mapping.h> +#include <linux/memblock.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <linux/pm_domain.h> +#include <linux/idr.h> +#include <linux/acpi.h> +#include <linux/clk/clk-conf.h> +#include <linux/limits.h> +#include <linux/property.h> +#include <linux/kmemleak.h> +#include <linux/types.h> +#include <linux/iommu.h> +#include <linux/dma-map-ops.h> + +#include "base.h" +#include "power/power.h" + +/* For automatically allocated device IDs */ +static DEFINE_IDA(platform_devid_ida); + +struct device platform_bus = { + .init_name = "platform", +}; +EXPORT_SYMBOL_GPL(platform_bus); + +/** + * platform_get_resource - get a resource for a device + * @dev: platform device + * @type: resource type + * @num: resource index + * + * Return: a pointer to the resource or NULL on failure. + */ +struct resource *platform_get_resource(struct platform_device *dev, + unsigned int type, unsigned int num) +{ + u32 i; + + for (i = 0; i < dev->num_resources; i++) { + struct resource *r = &dev->resource[i]; + + if (type == resource_type(r) && num-- == 0) + return r; + } + return NULL; +} +EXPORT_SYMBOL_GPL(platform_get_resource); + +struct resource *platform_get_mem_or_io(struct platform_device *dev, + unsigned int num) +{ + u32 i; + + for (i = 0; i < dev->num_resources; i++) { + struct resource *r = &dev->resource[i]; + + if ((resource_type(r) & (IORESOURCE_MEM | IORESOURCE_IO)) && num-- == 0) + return r; + } + return NULL; +} +EXPORT_SYMBOL_GPL(platform_get_mem_or_io); + +#ifdef CONFIG_HAS_IOMEM +/** + * devm_platform_get_and_ioremap_resource - call devm_ioremap_resource() for a + * platform device and get resource + * + * @pdev: platform device to use both for memory resource lookup as well as + * resource management + * @index: resource index + * @res: optional output parameter to store a pointer to the obtained resource. + * + * Return: a pointer to the remapped memory or an ERR_PTR() encoded error code + * on failure. + */ +void __iomem * +devm_platform_get_and_ioremap_resource(struct platform_device *pdev, + unsigned int index, struct resource **res) +{ + struct resource *r; + + r = platform_get_resource(pdev, IORESOURCE_MEM, index); + if (res) + *res = r; + return devm_ioremap_resource(&pdev->dev, r); +} +EXPORT_SYMBOL_GPL(devm_platform_get_and_ioremap_resource); + +/** + * devm_platform_ioremap_resource - call devm_ioremap_resource() for a platform + * device + * + * @pdev: platform device to use both for memory resource lookup as well as + * resource management + * @index: resource index + * + * Return: a pointer to the remapped memory or an ERR_PTR() encoded error code + * on failure. + */ +void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev, + unsigned int index) +{ + return devm_platform_get_and_ioremap_resource(pdev, index, NULL); +} +EXPORT_SYMBOL_GPL(devm_platform_ioremap_resource); + +/** + * devm_platform_ioremap_resource_byname - call devm_ioremap_resource for + * a platform device, retrieve the + * resource by name + * + * @pdev: platform device to use both for memory resource lookup as well as + * resource management + * @name: name of the resource + * + * Return: a pointer to the remapped memory or an ERR_PTR() encoded error code + * on failure. + */ +void __iomem * +devm_platform_ioremap_resource_byname(struct platform_device *pdev, + const char *name) +{ + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + return devm_ioremap_resource(&pdev->dev, res); +} +EXPORT_SYMBOL_GPL(devm_platform_ioremap_resource_byname); +#endif /* CONFIG_HAS_IOMEM */ + +static const struct cpumask *get_irq_affinity(struct platform_device *dev, + unsigned int num) +{ + const struct cpumask *mask = NULL; +#ifndef CONFIG_SPARC + struct fwnode_handle *fwnode = dev_fwnode(&dev->dev); + + if (is_of_node(fwnode)) + mask = of_irq_get_affinity(to_of_node(fwnode), num); + else if (is_acpi_device_node(fwnode)) + mask = acpi_irq_get_affinity(ACPI_HANDLE_FWNODE(fwnode), num); +#endif + + return mask ?: cpu_possible_mask; +} + +/** + * platform_get_irq_affinity - get an optional IRQ and its affinity for a device + * @dev: platform device + * @num: interrupt number index + * @affinity: optional cpumask pointer to get the affinity of a per-cpu interrupt + * + * Gets an interrupt for a platform device. Device drivers should check the + * return value for errors so as to not pass a negative integer value to + * the request_irq() APIs. Optional affinity information is provided in the + * affinity pointer if available, and NULL otherwise. + * + * Return: non-zero interrupt number on success, negative error number on failure. + */ +int platform_get_irq_affinity(struct platform_device *dev, unsigned int num, + const struct cpumask **affinity) +{ + int ret; +#ifdef CONFIG_SPARC + /* sparc does not have irqs represented as IORESOURCE_IRQ resources */ + if (!dev || num >= dev->archdata.num_irqs) + goto out_not_found; + ret = dev->archdata.irqs[num]; + goto out; +#else + struct fwnode_handle *fwnode = dev_fwnode(&dev->dev); + struct resource *r; + + if (is_of_node(fwnode)) { + ret = of_irq_get(to_of_node(fwnode), num); + if (ret > 0 || ret == -EPROBE_DEFER) + goto out; + } + + r = platform_get_resource(dev, IORESOURCE_IRQ, num); + if (is_acpi_device_node(fwnode)) { + if (r && r->flags & IORESOURCE_DISABLED) { + ret = acpi_irq_get(ACPI_HANDLE_FWNODE(fwnode), num, r); + if (ret) + goto out; + } + } + + /* + * The resources may pass trigger flags to the irqs that need + * to be set up. It so happens that the trigger flags for + * IORESOURCE_BITS correspond 1-to-1 to the IRQF_TRIGGER* + * settings. + */ + if (r && r->flags & IORESOURCE_BITS) { + struct irq_data *irqd; + + irqd = irq_get_irq_data(r->start); + if (!irqd) + goto out_not_found; + irqd_set_trigger_type(irqd, r->flags & IORESOURCE_BITS); + } + + if (r) { + ret = r->start; + goto out; + } + + /* + * For the index 0 interrupt, allow falling back to GpioInt + * resources. While a device could have both Interrupt and GpioInt + * resources, making this fallback ambiguous, in many common cases + * the device will only expose one IRQ, and this fallback + * allows a common code path across either kind of resource. + */ + if (num == 0 && is_acpi_device_node(fwnode)) { + ret = acpi_dev_gpio_irq_get(to_acpi_device_node(fwnode), num); + /* Our callers expect -ENXIO for missing IRQs. */ + if (ret >= 0 || ret == -EPROBE_DEFER) + goto out; + } + +#endif +out_not_found: + ret = -ENXIO; +out: + if (WARN(!ret, "0 is an invalid IRQ number\n")) + return -EINVAL; + + if (ret > 0 && affinity) + *affinity = get_irq_affinity(dev, num); + + return ret; +} +EXPORT_SYMBOL_GPL(platform_get_irq_affinity); + +/** + * platform_get_irq_optional - get an optional interrupt for a device + * @dev: platform device + * @num: interrupt number index + * + * Gets an interrupt for a platform device. Device drivers should check the + * return value for errors so as to not pass a negative integer value to + * the request_irq() APIs. This is the same as platform_get_irq(), except + * that it does not print an error message if an interrupt can not be + * obtained. + * + * For example:: + * + * int irq = platform_get_irq_optional(pdev, 0); + * if (irq < 0) + * return irq; + * + * Return: non-zero interrupt number on success, negative error number on failure. + */ +int platform_get_irq_optional(struct platform_device *dev, unsigned int num) +{ + return platform_get_irq_affinity(dev, num, NULL); +} +EXPORT_SYMBOL_GPL(platform_get_irq_optional); + +/** + * platform_get_irq - get an IRQ for a device + * @dev: platform device + * @num: IRQ number index + * + * Gets an IRQ for a platform device and prints an error message if finding the + * IRQ fails. Device drivers should check the return value for errors so as to + * not pass a negative integer value to the request_irq() APIs. + * + * For example:: + * + * int irq = platform_get_irq(pdev, 0); + * if (irq < 0) + * return irq; + * + * Return: non-zero IRQ number on success, negative error number on failure. + */ +int platform_get_irq(struct platform_device *dev, unsigned int num) +{ + int ret; + + ret = platform_get_irq_optional(dev, num); + if (ret < 0) + return dev_err_probe(&dev->dev, ret, + "IRQ index %u not found\n", num); + + return ret; +} +EXPORT_SYMBOL_GPL(platform_get_irq); + +/** + * platform_irq_count - Count the number of IRQs a platform device uses + * @dev: platform device + * + * Return: Number of IRQs a platform device uses or EPROBE_DEFER + */ +int platform_irq_count(struct platform_device *dev) +{ + int ret, nr = 0; + + while ((ret = platform_get_irq_optional(dev, nr)) >= 0) + nr++; + + if (ret == -EPROBE_DEFER) + return ret; + + return nr; +} +EXPORT_SYMBOL_GPL(platform_irq_count); + +struct irq_affinity_devres { + unsigned int count; + unsigned int irq[] __counted_by(count); +}; + +static void platform_disable_acpi_irq(struct platform_device *pdev, int index) +{ + struct resource *r; + + r = platform_get_resource(pdev, IORESOURCE_IRQ, index); + if (r) + irqresource_disabled(r, 0); +} + +static void devm_platform_get_irqs_affinity_release(struct device *dev, + void *res) +{ + struct irq_affinity_devres *ptr = res; + int i; + + for (i = 0; i < ptr->count; i++) { + irq_dispose_mapping(ptr->irq[i]); + + if (is_acpi_device_node(dev_fwnode(dev))) + platform_disable_acpi_irq(to_platform_device(dev), i); + } +} + +/** + * devm_platform_get_irqs_affinity - devm method to get a set of IRQs for a + * device using an interrupt affinity descriptor + * @dev: platform device pointer + * @affd: affinity descriptor + * @minvec: minimum count of interrupt vectors + * @maxvec: maximum count of interrupt vectors + * @irqs: pointer holder for IRQ numbers + * + * Gets a set of IRQs for a platform device, and updates IRQ afffinty according + * to the passed affinity descriptor + * + * Return: Number of vectors on success, negative error number on failure. + */ +int devm_platform_get_irqs_affinity(struct platform_device *dev, + struct irq_affinity *affd, + unsigned int minvec, + unsigned int maxvec, + int **irqs) +{ + struct irq_affinity_devres *ptr; + struct irq_affinity_desc *desc; + size_t size; + int i, ret, nvec; + + if (!affd) + return -EPERM; + + if (maxvec < minvec) + return -ERANGE; + + nvec = platform_irq_count(dev); + if (nvec < 0) + return nvec; + + if (nvec < minvec) + return -ENOSPC; + + nvec = irq_calc_affinity_vectors(minvec, nvec, affd); + if (nvec < minvec) + return -ENOSPC; + + if (nvec > maxvec) + nvec = maxvec; + + size = sizeof(*ptr) + sizeof(unsigned int) * nvec; + ptr = devres_alloc(devm_platform_get_irqs_affinity_release, size, + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ptr->count = nvec; + + for (i = 0; i < nvec; i++) { + int irq = platform_get_irq(dev, i); + if (irq < 0) { + ret = irq; + goto err_free_devres; + } + ptr->irq[i] = irq; + } + + desc = irq_create_affinity_masks(nvec, affd); + if (!desc) { + ret = -ENOMEM; + goto err_free_devres; + } + + for (i = 0; i < nvec; i++) { + ret = irq_update_affinity_desc(ptr->irq[i], &desc[i]); + if (ret) { + dev_err(&dev->dev, "failed to update irq%d affinity descriptor (%d)\n", + ptr->irq[i], ret); + goto err_free_desc; + } + } + + devres_add(&dev->dev, ptr); + + kfree(desc); + + *irqs = ptr->irq; + + return nvec; + +err_free_desc: + kfree(desc); +err_free_devres: + devres_free(ptr); + return ret; +} +EXPORT_SYMBOL_GPL(devm_platform_get_irqs_affinity); + +/** + * platform_get_resource_byname - get a resource for a device by name + * @dev: platform device + * @type: resource type + * @name: resource name + */ +struct resource *platform_get_resource_byname(struct platform_device *dev, + unsigned int type, + const char *name) +{ + u32 i; + + for (i = 0; i < dev->num_resources; i++) { + struct resource *r = &dev->resource[i]; + + if (unlikely(!r->name)) + continue; + + if (type == resource_type(r) && !strcmp(r->name, name)) + return r; + } + return NULL; +} +EXPORT_SYMBOL_GPL(platform_get_resource_byname); + +static int __platform_get_irq_byname(struct platform_device *dev, + const char *name) +{ + struct resource *r; + int ret; + + ret = fwnode_irq_get_byname(dev_fwnode(&dev->dev), name); + if (ret > 0 || ret == -EPROBE_DEFER) + return ret; + + r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name); + if (r) { + if (WARN(!r->start, "0 is an invalid IRQ number\n")) + return -EINVAL; + return r->start; + } + + return -ENXIO; +} + +/** + * platform_get_irq_byname - get an IRQ for a device by name + * @dev: platform device + * @name: IRQ name + * + * Get an IRQ like platform_get_irq(), but then by name rather then by index. + * + * Return: non-zero IRQ number on success, negative error number on failure. + */ +int platform_get_irq_byname(struct platform_device *dev, const char *name) +{ + int ret; + + ret = __platform_get_irq_byname(dev, name); + if (ret < 0) + return dev_err_probe(&dev->dev, ret, "IRQ %s not found\n", + name); + return ret; +} +EXPORT_SYMBOL_GPL(platform_get_irq_byname); + +/** + * platform_get_irq_byname_optional - get an optional IRQ for a device by name + * @dev: platform device + * @name: IRQ name + * + * Get an optional IRQ by name like platform_get_irq_byname(). Except that it + * does not print an error message if an IRQ can not be obtained. + * + * Return: non-zero IRQ number on success, negative error number on failure. + */ +int platform_get_irq_byname_optional(struct platform_device *dev, + const char *name) +{ + return __platform_get_irq_byname(dev, name); +} +EXPORT_SYMBOL_GPL(platform_get_irq_byname_optional); + +/** + * platform_add_devices - add a numbers of platform devices + * @devs: array of platform devices to add + * @num: number of platform devices in array + * + * Return: 0 on success, negative error number on failure. + */ +int platform_add_devices(struct platform_device **devs, int num) +{ + int i, ret = 0; + + for (i = 0; i < num; i++) { + ret = platform_device_register(devs[i]); + if (ret) { + while (--i >= 0) + platform_device_unregister(devs[i]); + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(platform_add_devices); + +struct platform_object { + struct platform_device pdev; + char name[]; +}; + +/* + * Set up default DMA mask for platform devices if the they weren't + * previously set by the architecture / DT. + */ +static void setup_pdev_dma_masks(struct platform_device *pdev) +{ + pdev->dev.dma_parms = &pdev->dma_parms; + + if (!pdev->dev.coherent_dma_mask) + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + if (!pdev->dev.dma_mask) { + pdev->platform_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &pdev->platform_dma_mask; + } +}; + +/** + * platform_device_put - destroy a platform device + * @pdev: platform device to free + * + * Free all memory associated with a platform device. This function must + * _only_ be externally called in error cases. All other usage is a bug. + */ +void platform_device_put(struct platform_device *pdev) +{ + if (!IS_ERR_OR_NULL(pdev)) + put_device(&pdev->dev); +} +EXPORT_SYMBOL_GPL(platform_device_put); + +static void platform_device_release(struct device *dev) +{ + struct platform_object *pa = container_of(dev, struct platform_object, + pdev.dev); + + device_remove_software_node(dev); + /* + * If the primary firmware node is a software node, its reference count + * was already decreased by the call to device_remove_software_node(). + */ + if (!is_software_node(dev_fwnode(dev))) + fwnode_handle_put(pa->pdev.dev.fwnode); + kfree(pa->pdev.dev.platform_data); + kfree(pa->pdev.mfd_cell); + kfree(pa->pdev.resource); + kfree(pa); +} + +/** + * platform_device_alloc - create a platform device + * @name: base name of the device we're adding + * @id: instance id + * + * Create a platform device object which can have other objects attached + * to it, and which will have attached objects freed when it is released. + * + * The following fields of the dynamically allocated platform device must not + * be modified manually: resource, num_resources, dev.platform_data, + * dev.of_node and dev.fwnode. Users wishing to do the split platform device + * registration with platform_device_alloc() + platform_device_add() are + * required to use dedicated helpers for adding resources, platform data or + * assigning firmware nodes. + */ +struct platform_device *platform_device_alloc(const char *name, int id) +{ + struct platform_object *pa; + + pa = kzalloc(sizeof(*pa) + strlen(name) + 1, GFP_KERNEL); + if (pa) { + strcpy(pa->name, name); + pa->pdev.name = pa->name; + pa->pdev.id = id; + device_initialize(&pa->pdev.dev); + pa->pdev.dev.release = platform_device_release; + setup_pdev_dma_masks(&pa->pdev); + } + + return pa ? &pa->pdev : NULL; +} +EXPORT_SYMBOL_GPL(platform_device_alloc); + +/** + * platform_device_add_resources - add resources to a platform device + * @pdev: platform device allocated by platform_device_alloc to add resources to + * @res: set of resources that needs to be allocated for the device + * @num: number of resources + * + * Add a copy of the resources to the platform device. The memory + * associated with the resources will be freed when the platform device is + * released. + */ +int platform_device_add_resources(struct platform_device *pdev, + const struct resource *res, unsigned int num) +{ + struct resource *r = NULL; + + if (res) { + r = kmemdup_array(res, num, sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + } + + kfree(pdev->resource); + pdev->resource = r; + pdev->num_resources = num; + return 0; +} +EXPORT_SYMBOL_GPL(platform_device_add_resources); + +/** + * platform_device_add_data - add platform-specific data to a platform device + * @pdev: platform device allocated by platform_device_alloc to add resources to + * @data: platform specific data for this platform device + * @size: size of platform specific data + * + * Add a copy of platform specific data to the platform device's + * platform_data pointer. The memory associated with the platform data + * will be freed when the platform device is released. + */ +int platform_device_add_data(struct platform_device *pdev, const void *data, + size_t size) +{ + void *d = NULL; + + if (data) { + d = kmemdup(data, size, GFP_KERNEL); + if (!d) + return -ENOMEM; + } + + kfree(pdev->dev.platform_data); + pdev->dev.platform_data = d; + return 0; +} +EXPORT_SYMBOL_GPL(platform_device_add_data); + +/** + * platform_device_set_of_node - assign an OF node to device + * @pdev: platform device to add the node for + * @np: new device node + * + * Assign an OF node to this platform device. Internally keep track of the + * reference count. Devices created with platform_device_alloc() must use this + * function instead of assigning the node manually. This function must not be + * called for a platform device that already has a software node as its primary + * firmware node assigned. + */ +void platform_device_set_of_node(struct platform_device *pdev, + struct device_node *np) +{ + platform_device_set_fwnode(pdev, of_fwnode_handle(np)); +} +EXPORT_SYMBOL_GPL(platform_device_set_of_node); + +/** + * platform_device_set_fwnode - assign a firmware node to device + * @pdev: platform device to set the node for + * @fwnode: new firmware node + * + * Assign a firmware node to this platform device. Internally keep track of the + * reference count. Devices created with platform_device_alloc() must use this + * function instead of assigning the node manually. This function must not be + * called for a platform device that already has a software node as its primary + * firmware node assigned. + */ +void platform_device_set_fwnode(struct platform_device *pdev, + struct fwnode_handle *fwnode) +{ + /* + * If we call this function for a platform device whose primary + * firmware node is a software node, we'll never end up calling the + * symmetric software_node_notify_remove(). There are no users for this + * right now in the tree so just disallow it. + */ + WARN_ON(is_software_node(dev_fwnode(&pdev->dev))); + fwnode_handle_put(pdev->dev.fwnode); + device_set_node(&pdev->dev, fwnode_handle_get(fwnode)); +} +EXPORT_SYMBOL_GPL(platform_device_set_fwnode); + +/** + * platform_device_set_of_node_from_dev - reuse OF node of another device + * @pdev: platform device to set the node for + * @dev2: device whose OF node to reuse + * + * Reuses the OF node of another device in this platform device while + * internally keeping track of reference counting. This function must not be + * called for a platform device that already has a software node as its primary + * firmware node assigned. + */ +void platform_device_set_of_node_from_dev(struct platform_device *pdev, + const struct device *dev2) +{ + /* See platform_device_set_fwnode(). */ + WARN_ON(is_software_node(dev_fwnode(&pdev->dev))); + device_set_of_node_from_dev(&pdev->dev, dev2); + pdev->dev.fwnode = of_fwnode_handle(pdev->dev.of_node); +} +EXPORT_SYMBOL_GPL(platform_device_set_of_node_from_dev); + +/** + * platform_device_add - add a platform device to device hierarchy + * @pdev: platform device we're adding + * + * This is part 2 of platform_device_register(), though may be called + * separately _iff_ pdev was allocated by platform_device_alloc(). + */ +int platform_device_add(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + u32 i; + int ret; + + if (!dev->parent) + dev->parent = &platform_bus; + + dev->bus = &platform_bus_type; + + switch (pdev->id) { + default: + dev_set_name(dev, "%s.%d", pdev->name, pdev->id); + break; + case PLATFORM_DEVID_NONE: + dev_set_name(dev, "%s", pdev->name); + break; + case PLATFORM_DEVID_AUTO: + /* + * Automatically allocated device ID. We mark it as such so + * that we remember it must be freed, and we append a suffix + * to avoid namespace collision with explicit IDs. + */ + ret = ida_alloc(&platform_devid_ida, GFP_KERNEL); + if (ret < 0) + return ret; + pdev->id = ret; + pdev->id_auto = true; + dev_set_name(dev, "%s.%d.auto", pdev->name, pdev->id); + break; + } + + for (i = 0; i < pdev->num_resources; i++) { + struct resource *p, *r = &pdev->resource[i]; + + if (r->name == NULL) + r->name = dev_name(dev); + + p = r->parent; + if (!p) { + if (resource_type(r) == IORESOURCE_MEM) + p = &iomem_resource; + else if (resource_type(r) == IORESOURCE_IO) + p = &ioport_resource; + } + + if (p) { + ret = insert_resource(p, r); + if (ret) { + dev_err(dev, "failed to claim resource %d: %pR\n", i, r); + goto failed; + } + } + } + + pr_debug("Registering platform device '%s'. Parent at %s\n", dev_name(dev), + dev_name(dev->parent)); + + ret = device_add(dev); + if (ret) + goto failed; + + return 0; + + failed: + if (pdev->id_auto) { + ida_free(&platform_devid_ida, pdev->id); + pdev->id = PLATFORM_DEVID_AUTO; + } + + while (i--) { + struct resource *r = &pdev->resource[i]; + if (r->parent) + release_resource(r); + } + + return ret; +} +EXPORT_SYMBOL_GPL(platform_device_add); + +/** + * platform_device_del - remove a platform-level device + * @pdev: platform device we're removing + * + * Note that this function will also release all memory- and port-based + * resources owned by the device (@dev->resource). This function must + * _only_ be externally called in error cases. All other usage is a bug. + */ +void platform_device_del(struct platform_device *pdev) +{ + u32 i; + + if (!IS_ERR_OR_NULL(pdev)) { + device_del(&pdev->dev); + + if (pdev->id_auto) { + ida_free(&platform_devid_ida, pdev->id); + pdev->id = PLATFORM_DEVID_AUTO; + } + + for (i = 0; i < pdev->num_resources; i++) { + struct resource *r = &pdev->resource[i]; + if (r->parent) + release_resource(r); + } + } +} +EXPORT_SYMBOL_GPL(platform_device_del); + +/** + * platform_device_register - add a platform-level device + * @pdev: platform device we're adding + * + * NOTE: _Never_ directly free @pdev after calling this function, even if it + * returned an error! Always use platform_device_put() to give up the + * reference initialised in this function instead. + */ +int platform_device_register(struct platform_device *pdev) +{ + device_initialize(&pdev->dev); + setup_pdev_dma_masks(pdev); + return platform_device_add(pdev); +} +EXPORT_SYMBOL_GPL(platform_device_register); + +/** + * platform_device_unregister - unregister a platform-level device + * @pdev: platform device we're unregistering + * + * Unregistration is done in 2 steps. First we release all resources + * and remove it from the subsystem, then we drop reference count by + * calling platform_device_put(). + */ +void platform_device_unregister(struct platform_device *pdev) +{ + platform_device_del(pdev); + platform_device_put(pdev); +} +EXPORT_SYMBOL_GPL(platform_device_unregister); + +/** + * platform_device_register_full - add a platform-level device with + * resources and platform-specific data + * + * @pdevinfo: data used to create device + * + * Returns &struct platform_device pointer on success, or ERR_PTR() on error. + */ +struct platform_device *platform_device_register_full(const struct platform_device_info *pdevinfo) +{ + int ret; + struct platform_device *pdev; + + /* + * Only one software node per device is allowed. Make sure we don't + * accept or create two. + */ + if ((pdevinfo->swnode && pdevinfo->properties) || + (pdevinfo->swnode && is_software_node(pdevinfo->fwnode)) || + (pdevinfo->properties && is_software_node(pdevinfo->fwnode))) + return ERR_PTR(-EINVAL); + + pdev = platform_device_alloc(pdevinfo->name, pdevinfo->id); + if (!pdev) + return ERR_PTR(-ENOMEM); + + pdev->dev.parent = pdevinfo->parent; + device_set_node(&pdev->dev, fwnode_handle_get(pdevinfo->fwnode)); + dev_assign_of_node_reused(&pdev->dev, pdevinfo->of_node_reused); + + if (pdevinfo->dma_mask) { + pdev->platform_dma_mask = pdevinfo->dma_mask; + pdev->dev.dma_mask = &pdev->platform_dma_mask; + pdev->dev.coherent_dma_mask = pdevinfo->dma_mask; + } + + ret = platform_device_add_resources(pdev, pdevinfo->res, pdevinfo->num_res); + if (ret) + goto err; + + ret = platform_device_add_data(pdev, pdevinfo->data, pdevinfo->size_data); + if (ret) + goto err; + + if (pdevinfo->swnode) { + ret = device_add_software_node(&pdev->dev, pdevinfo->swnode); + if (ret) + goto err; + } else if (pdevinfo->properties) { + ret = device_create_managed_software_node(&pdev->dev, + pdevinfo->properties, NULL); + if (ret) + goto err; + } + + ret = platform_device_add(pdev); + if (ret) { +err: + ACPI_COMPANION_SET(&pdev->dev, NULL); + platform_device_put(pdev); + return ERR_PTR(ret); + } + + return pdev; +} +EXPORT_SYMBOL_GPL(platform_device_register_full); + +/** + * __platform_driver_register - register a driver for platform-level devices + * @drv: platform driver structure + * @owner: owning module/driver + * @mod_name: module name string + */ +int __platform_driver_register(struct platform_driver *drv, struct module *owner, + const char *mod_name) +{ + drv->driver.owner = owner; + drv->driver.bus = &platform_bus_type; + drv->driver.mod_name = mod_name; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__platform_driver_register); + +/** + * platform_driver_unregister - unregister a driver for platform-level devices + * @drv: platform driver structure + */ +void platform_driver_unregister(struct platform_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(platform_driver_unregister); + +static int platform_probe_fail(struct platform_device *pdev) +{ + return -ENXIO; +} + +static int is_bound_to_driver(struct device *dev, void *driver) +{ + if (dev->driver == driver) + return 1; + return 0; +} + +/** + * __platform_driver_probe - register driver for non-hotpluggable device + * @drv: platform driver structure + * @probe: the driver probe routine, probably from an __init section + * @module: module which will be the owner of the driver + * @mod_name: module name string + * + * Use this instead of platform_driver_register() when you know the device + * is not hotpluggable and has already been registered, and you want to + * remove its run-once probe() infrastructure from memory after the driver + * has bound to the device. + * + * One typical use for this would be with drivers for controllers integrated + * into system-on-chip processors, where the controller devices have been + * configured as part of board setup. + * + * Note that this is incompatible with deferred probing. + * + * Returns zero if the driver registered and bound to a device, else returns + * a negative error code and with the driver not registered. + */ +int __init_or_module __platform_driver_probe(struct platform_driver *drv, + int (*probe)(struct platform_device *), + struct module *module, + const char *mod_name) +{ + int retval; + + if (drv->driver.probe_type == PROBE_PREFER_ASYNCHRONOUS) { + pr_err("%s: drivers registered with %s can not be probed asynchronously\n", + drv->driver.name, __func__); + return -EINVAL; + } + + /* + * We have to run our probes synchronously because we check if + * we find any devices to bind to and exit with error if there + * are any. + */ + drv->driver.probe_type = PROBE_FORCE_SYNCHRONOUS; + + /* + * Prevent driver from requesting probe deferral to avoid further + * futile probe attempts. + */ + drv->prevent_deferred_probe = true; + + /* make sure driver won't have bind/unbind attributes */ + drv->driver.suppress_bind_attrs = true; + + /* temporary section violation during probe() */ + drv->probe = probe; + retval = __platform_driver_register(drv, module, mod_name); + if (retval) + return retval; + + /* Force all new probes of this driver to fail */ + drv->probe = platform_probe_fail; + + /* Walk all platform devices and see if any actually bound to this driver. + * If not, return an error as the device should have done so by now. + */ + if (!bus_for_each_dev(&platform_bus_type, NULL, &drv->driver, is_bound_to_driver)) { + retval = -ENODEV; + platform_driver_unregister(drv); + } + + return retval; +} +EXPORT_SYMBOL_GPL(__platform_driver_probe); + +/** + * __platform_create_bundle - register driver and create corresponding device + * @driver: platform driver structure + * @probe: the driver probe routine, probably from an __init section + * @res: set of resources that needs to be allocated for the device + * @n_res: number of resources + * @data: platform specific data for this platform device + * @size: size of platform specific data + * @module: module which will be the owner of the driver + * @mod_name: module name string + * + * Use this in legacy-style modules that probe hardware directly and + * register a single platform device and corresponding platform driver. + * + * Returns &struct platform_device pointer on success, or ERR_PTR() on error. + */ +struct platform_device * __init_or_module +__platform_create_bundle(struct platform_driver *driver, + int (*probe)(struct platform_device *), + struct resource *res, unsigned int n_res, + const void *data, size_t size, struct module *module, const char *mod_name) +{ + struct platform_device *pdev; + int error; + + pdev = platform_device_alloc(driver->driver.name, PLATFORM_DEVID_NONE); + if (!pdev) { + error = -ENOMEM; + goto err_out; + } + + error = platform_device_add_resources(pdev, res, n_res); + if (error) + goto err_pdev_put; + + error = platform_device_add_data(pdev, data, size); + if (error) + goto err_pdev_put; + + error = platform_device_add(pdev); + if (error) + goto err_pdev_put; + + error = __platform_driver_probe(driver, probe, module, mod_name); + if (error) + goto err_pdev_del; + + return pdev; + +err_pdev_del: + platform_device_del(pdev); +err_pdev_put: + platform_device_put(pdev); +err_out: + return ERR_PTR(error); +} +EXPORT_SYMBOL_GPL(__platform_create_bundle); + +/** + * __platform_register_drivers - register an array of platform drivers + * @drivers: an array of drivers to register + * @count: the number of drivers to register + * @owner: module owning the drivers + * @mod_name: module name string + * + * Registers platform drivers specified by an array. On failure to register a + * driver, all previously registered drivers will be unregistered. Callers of + * this API should use platform_unregister_drivers() to unregister drivers in + * the reverse order. + * + * Returns: 0 on success or a negative error code on failure. + */ +int __platform_register_drivers(struct platform_driver * const *drivers, + unsigned int count, struct module *owner, const char *mod_name) +{ + unsigned int i; + int err; + + for (i = 0; i < count; i++) { + pr_debug("registering platform driver %ps\n", drivers[i]); + + err = __platform_driver_register(drivers[i], owner, mod_name); + if (err < 0) { + pr_err("failed to register platform driver %ps: %d\n", + drivers[i], err); + goto error; + } + } + + return 0; + +error: + while (i--) { + pr_debug("unregistering platform driver %ps\n", drivers[i]); + platform_driver_unregister(drivers[i]); + } + + return err; +} +EXPORT_SYMBOL_GPL(__platform_register_drivers); + +/** + * platform_unregister_drivers - unregister an array of platform drivers + * @drivers: an array of drivers to unregister + * @count: the number of drivers to unregister + * + * Unregisters platform drivers specified by an array. This is typically used + * to complement an earlier call to platform_register_drivers(). Drivers are + * unregistered in the reverse order in which they were registered. + */ +void platform_unregister_drivers(struct platform_driver * const *drivers, + unsigned int count) +{ + while (count--) { + pr_debug("unregistering platform driver %ps\n", drivers[count]); + platform_driver_unregister(drivers[count]); + } +} +EXPORT_SYMBOL_GPL(platform_unregister_drivers); + +static const struct platform_device_id * +platform_match_id(const struct platform_device_id *id, struct platform_device *pdev) +{ + while (id->name[0]) { + if (strcmp(pdev->name, id->name) == 0) { + pdev->id_entry = id; + return id; + } + id++; + } + return NULL; +} + +#ifdef CONFIG_PM_SLEEP + +static int platform_legacy_suspend(struct device *dev, pm_message_t mesg) +{ + struct platform_driver *pdrv = to_platform_driver(dev->driver); + struct platform_device *pdev = to_platform_device(dev); + int ret = 0; + + if (dev->driver && pdrv->suspend) + ret = pdrv->suspend(pdev, mesg); + + return ret; +} + +static int platform_legacy_resume(struct device *dev) +{ + struct platform_driver *pdrv = to_platform_driver(dev->driver); + struct platform_device *pdev = to_platform_device(dev); + int ret = 0; + + if (dev->driver && pdrv->resume) + ret = pdrv->resume(pdev); + + return ret; +} + +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_SUSPEND + +int platform_pm_suspend(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->suspend) + ret = drv->pm->suspend(dev); + } else { + ret = platform_legacy_suspend(dev, PMSG_SUSPEND); + } + + return ret; +} + +int platform_pm_resume(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->resume) + ret = drv->pm->resume(dev); + } else { + ret = platform_legacy_resume(dev); + } + + return ret; +} + +#endif /* CONFIG_SUSPEND */ + +#ifdef CONFIG_HIBERNATE_CALLBACKS + +int platform_pm_freeze(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->freeze) + ret = drv->pm->freeze(dev); + } else { + ret = platform_legacy_suspend(dev, PMSG_FREEZE); + } + + return ret; +} + +int platform_pm_thaw(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->thaw) + ret = drv->pm->thaw(dev); + } else { + ret = platform_legacy_resume(dev); + } + + return ret; +} + +int platform_pm_poweroff(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->poweroff) + ret = drv->pm->poweroff(dev); + } else { + ret = platform_legacy_suspend(dev, PMSG_HIBERNATE); + } + + return ret; +} + +int platform_pm_restore(struct device *dev) +{ + const struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->restore) + ret = drv->pm->restore(dev); + } else { + ret = platform_legacy_resume(dev); + } + + return ret; +} + +#endif /* CONFIG_HIBERNATE_CALLBACKS */ + +/* modalias support enables more hands-off userspace setup: + * (a) environment variable lets new-style hotplug events work once system is + * fully running: "modprobe $MODALIAS" + * (b) sysfs attribute lets new-style coldplug recover from hotplug events + * mishandled before system is fully running: "modprobe $(cat modalias)" + */ +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + int len; + + len = of_device_modalias(dev, buf, PAGE_SIZE); + if (len != -ENODEV) + return len; + + len = acpi_device_modalias(dev, buf, PAGE_SIZE - 1); + if (len != -ENODEV) + return len; + + return sysfs_emit(buf, "platform:%s\n", pdev->name); +} +static DEVICE_ATTR_RO(modalias); + +static ssize_t numa_node_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", dev_to_node(dev)); +} +static DEVICE_ATTR_RO(numa_node); + +static struct attribute *platform_dev_attrs[] = { + &dev_attr_modalias.attr, + &dev_attr_numa_node.attr, + NULL, +}; + +static umode_t platform_dev_attrs_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, typeof(*dev), kobj); + + if (a == &dev_attr_numa_node.attr && dev_to_node(dev) == NUMA_NO_NODE) + return 0; + + return a->mode; +} + +static const struct attribute_group platform_dev_group = { + .attrs = platform_dev_attrs, + .is_visible = platform_dev_attrs_visible, +}; +__ATTRIBUTE_GROUPS(platform_dev); + +/** + * platform_match - bind platform device to platform driver. + * @dev: device. + * @drv: driver. + * + * Platform device IDs are assumed to be encoded like this: + * "<name><instance>", where <name> is a short description of the type of + * device, like "pci" or "floppy", and <instance> is the enumerated + * instance of the device, like '0' or '42'. Driver IDs are simply + * "<name>". So, extract the <name> from the platform_device structure, + * and compare it against the name of the driver. Return whether they match + * or not. + */ +static int platform_match(struct device *dev, const struct device_driver *drv) +{ + struct platform_device *pdev = to_platform_device(dev); + struct platform_driver *pdrv = to_platform_driver(drv); + int ret; + + /* When driver_override is set, only bind to the matching driver */ + ret = device_match_driver_override(dev, drv); + if (ret >= 0) + return ret; + + /* Attempt an OF style match first */ + if (of_driver_match_device(dev, drv)) + return 1; + + /* Then try ACPI style match */ + if (acpi_driver_match_device(dev, drv)) + return 1; + + /* Then try to match against the id table */ + if (pdrv->id_table) + return platform_match_id(pdrv->id_table, pdev) != NULL; + + /* fall-back to driver name match */ + return (strcmp(pdev->name, drv->name) == 0); +} + +static int platform_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + const struct platform_device *pdev = to_platform_device(dev); + int rc; + + /* Some devices have extra OF data and an OF-style MODALIAS */ + rc = of_device_uevent_modalias(dev, env); + if (rc != -ENODEV) + return rc; + + rc = acpi_device_uevent_modalias(dev, env); + if (rc != -ENODEV) + return rc; + + add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX, pdev->name); + return 0; +} + +static int platform_probe(struct device *_dev) +{ + struct platform_driver *drv = to_platform_driver(_dev->driver); + struct platform_device *dev = to_platform_device(_dev); + int ret; + + /* + * A driver registered using platform_driver_probe() cannot be bound + * again later because the probe function usually lives in __init code + * and so is gone. For these drivers .probe is set to + * platform_probe_fail in __platform_driver_probe(). Don't even prepare + * clocks and PM domains for these to match the traditional behaviour. + */ + if (unlikely(drv->probe == platform_probe_fail)) + return -ENXIO; + + ret = of_clk_set_defaults(_dev->of_node, false); + if (ret < 0) + return ret; + + ret = dev_pm_domain_attach(_dev, PD_FLAG_ATTACH_POWER_ON | + PD_FLAG_DETACH_POWER_OFF); + if (ret) + goto out; + + if (drv->probe) + ret = drv->probe(dev); + +out: + if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) { + dev_warn(_dev, "probe deferral not supported\n"); + ret = -ENXIO; + } + + return ret; +} + +static void platform_remove(struct device *_dev) +{ + struct platform_driver *drv = to_platform_driver(_dev->driver); + struct platform_device *dev = to_platform_device(_dev); + + if (drv->remove) + drv->remove(dev); +} + +static void platform_shutdown(struct device *_dev) +{ + struct platform_device *dev = to_platform_device(_dev); + struct platform_driver *drv; + + if (!_dev->driver) + return; + + drv = to_platform_driver(_dev->driver); + if (drv->shutdown) + drv->shutdown(dev); +} + +static int platform_dma_configure(struct device *dev) +{ + struct device_driver *drv = READ_ONCE(dev->driver); + struct fwnode_handle *fwnode = dev_fwnode(dev); + enum dev_dma_attr attr; + int ret = 0; + + if (is_of_node(fwnode)) { + ret = of_dma_configure(dev, to_of_node(fwnode), true); + } else if (is_acpi_device_node(fwnode)) { + attr = acpi_get_dma_attr(to_acpi_device_node(fwnode)); + ret = acpi_dma_configure(dev, attr); + } + /* @dev->driver may not be valid when we're called from the IOMMU layer */ + if (ret || !drv || to_platform_driver(drv)->driver_managed_dma) + return ret; + + ret = iommu_device_use_default_domain(dev); + if (ret) + arch_teardown_dma_ops(dev); + + return ret; +} + +static void platform_dma_cleanup(struct device *dev) +{ + struct platform_driver *drv = to_platform_driver(dev->driver); + + if (!drv->driver_managed_dma) + iommu_device_unuse_default_domain(dev); +} + +static const struct dev_pm_ops platform_dev_pm_ops = { + SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, pm_generic_runtime_resume, NULL) + USE_PLATFORM_PM_SLEEP_OPS +}; + +const struct bus_type platform_bus_type = { + .name = "platform", + .dev_groups = platform_dev_groups, + .driver_override = true, + .match = platform_match, + .uevent = platform_uevent, + .probe = platform_probe, + .remove = platform_remove, + .shutdown = platform_shutdown, + .dma_configure = platform_dma_configure, + .dma_cleanup = platform_dma_cleanup, + .pm = &platform_dev_pm_ops, +}; +EXPORT_SYMBOL_GPL(platform_bus_type); + +static inline int __platform_match(struct device *dev, const void *drv) +{ + return platform_match(dev, (struct device_driver *)drv); +} + +/** + * platform_find_device_by_driver - Find a platform device with a given + * driver. + * @start: The device to start the search from. + * @drv: The device driver to look for. + */ +struct device *platform_find_device_by_driver(struct device *start, + const struct device_driver *drv) +{ + return bus_find_device(&platform_bus_type, start, drv, + __platform_match); +} +EXPORT_SYMBOL_GPL(platform_find_device_by_driver); + +void __weak __init early_platform_cleanup(void) { } + +int __init platform_bus_init(void) +{ + int error; + + early_platform_cleanup(); + + error = device_register(&platform_bus); + if (error) { + put_device(&platform_bus); + return error; + } + error = bus_register(&platform_bus_type); + if (error) + device_unregister(&platform_bus); + + return error; +} |
