/* * AMBA Connector Resource for ACPI * * Copyright (C) 2013 Advanced Micro Devices, Inc. * * Author: Brandon Anderson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #ifdef CONFIG_ACPI #include #include #include #include #include #include struct acpi_amba_bus_info { struct platform_device *pdev; struct clk *clk; char *clk_name; }; static int acpi_amba_add_resource(struct acpi_resource *ares, void *data) { struct amba_device *dev = data; struct resource r; int irq_idx; switch (ares->type) { case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: if (!acpi_dev_resource_memory(ares, &dev->res)) pr_err("failed to map memory resource\n"); break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: for (irq_idx = 0; irq_idx < AMBA_NR_IRQS; irq_idx++) { if (acpi_dev_resource_interrupt(ares, irq_idx, &r)) dev->irq[irq_idx] = r.start; else break; } break; case ACPI_RESOURCE_TYPE_END_TAG: /* ignore the end tag */ break; default: /* log an error, but proceed with driver probe */ pr_err("unhandled acpi resource type= %d\n", ares->type); break; } return 1; /* Tell ACPI core that this resource has been handled */ } static struct clk *acpi_amba_get_clk(acpi_handle handle, int index, char **clk_name) { acpi_handle clk_handle; struct acpi_device *adev, *clk_adev; const char **clk_paths; struct clk *clk = NULL; int ret; if (acpi_bus_get_device(handle, &adev)) { pr_err("cannot get device from handle\n"); return NULL; } /* Get number of entries */ ret = acpi_dev_get_property_array_string(adev, "clocks", NULL, 0); if ((ret < 0) || (index >= ret)) return NULL; clk_paths = kzalloc(sizeof(*clk_paths) * ret, GFP_KERNEL); /* look under the clock device for the clock that matches the entry */ ret = acpi_dev_get_property_array_string(adev, "clocks", clk_paths, ret); /* Locate the acpi_device from the device name */ acpi_get_handle(NULL, (acpi_string)clk_paths[index], &clk_handle); if (!clk_handle) goto error; acpi_bus_get_device(clk_handle, &clk_adev); if (!clk_adev) goto error; clk = clk_get_sys(dev_name(&clk_adev->dev), NULL); error: kfree(clk_paths); return clk; } static void acpi_amba_register_clocks(struct acpi_device *adev, struct clk *default_clk, const char *default_clk_name) { struct clk *clk; int i; char *clk_name; for (i = 0;; i++) { clk_name = NULL; clk = acpi_amba_get_clk(ACPI_HANDLE(&adev->dev), i, &clk_name); if (!clk) break; clk_register_clkdev(clk, clk_name, dev_name(&adev->dev)); kfree(clk_name); } if (default_clk) { /* for amba_get_enable_pclk() ... */ clk_register_clkdev(default_clk, default_clk_name, dev_name(&adev->dev)); /* for devm_clk_get() ... */ clk_register_clkdev(default_clk, NULL, dev_name(&adev->dev)); } } /* acpi_amba_add_device() * * ACPI equivalent to of_amba_device_create() */ static acpi_status acpi_amba_add_device(acpi_handle handle, u32 lvl_not_used, void *data, void **not_used) { struct list_head resource_list; struct acpi_device *adev; struct amba_device *dev; int ret; struct acpi_amba_bus_info *bus_info = data; struct platform_device *bus_pdev = bus_info->pdev; if (acpi_bus_get_device(handle, &adev)) { pr_err("%s: acpi_bus_get_device failed\n", __func__); return AE_OK; } pr_debug("Creating amba device %s\n", dev_name(&adev->dev)); dev = amba_device_alloc(NULL, 0, 0); if (!dev) { pr_err("%s(): amba_device_alloc() failed for %s\n", __func__, dev_name(&adev->dev)); return AE_CTRL_TERMINATE; } /* setup generic device info */ dev->dev.coherent_dma_mask = ~0; dev->dev.parent = &bus_pdev->dev; dev_set_name(&dev->dev, "%s", dev_name(&adev->dev)); /* setup amba-specific device info */ ACPI_COMPANION_SET(&dev->dev, adev); ACPI_COMPANION_SET(&adev->dev, adev); INIT_LIST_HEAD(&resource_list); acpi_dev_get_resources(adev, &resource_list, acpi_amba_add_resource, dev); acpi_dev_free_resource_list(&resource_list); /* Add clocks */ acpi_amba_register_clocks(adev, bus_info->clk, bus_info->clk_name); /* Read AMBA hardware ID and add device to system. If a driver matching * hardware ID has already been registered, bind this device to it. * Otherwise, the platform subsystem will match up the hardware ID * when the matching driver is registered. */ ret = amba_device_add(dev, &iomem_resource); if (ret) { pr_err("%s(): amba_device_add() failed (%d) for %s\n", __func__, ret, dev_name(&adev->dev)); goto err_free; } return AE_OK; err_free: amba_device_put(dev); return AE_OK; /* don't prevent other devices from being probed */ } static int acpi_amba_bus_probe(struct platform_device *pdev) { struct acpi_amba_bus_info bus_info; bus_info.clk_name = NULL; /* see if there's a top-level clock to use as default for sub-devices */ bus_info.clk = acpi_amba_get_clk(ACPI_HANDLE(&pdev->dev), 0, &bus_info.clk_name); /* probe each device */ bus_info.pdev = pdev; acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_HANDLE(&pdev->dev), 1, acpi_amba_add_device, NULL, &bus_info, NULL); kfree(bus_info.clk_name); return 0; } static const struct acpi_device_id amba_bus_acpi_match[] = { { "LNRO001A", 0 }, { }, }; static struct platform_driver amba_bus_acpi_driver = { .driver = { .name = "amba-acpi", .owner = THIS_MODULE, .acpi_match_table = ACPI_PTR(amba_bus_acpi_match), }, .probe = acpi_amba_bus_probe, }; static int __init acpi_amba_bus_init(void) { return platform_driver_register(&amba_bus_acpi_driver); } postcore_initcall(acpi_amba_bus_init); MODULE_AUTHOR("Brandon Anderson "); MODULE_DESCRIPTION("ACPI Connector Resource for AMBA bus"); MODULE_LICENSE("GPLv2"); MODULE_ALIAS("platform:amba-acpi"); #endif /* CONFIG_ACPI */