Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753006AbbBXXAZ (ORCPT ); Tue, 24 Feb 2015 18:00:25 -0500 Received: from mail-bn1on0146.outbound.protection.outlook.com ([157.56.110.146]:27764 "EHLO na01-bn1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1752516AbbBXXAX (ORCPT ); Tue, 24 Feb 2015 18:00:23 -0500 From: "J. German Rivera" To: , , CC: , , , , , , , , , , "J. German Rivera" Subject: [PATCH v7 2/3] drivers/bus: Freescale Management Complex (fsl-mc) bus driver Date: Tue, 24 Feb 2015 16:21:53 -0600 Message-ID: <1424816514-26369-3-git-send-email-German.Rivera@freescale.com> X-Mailer: git-send-email 2.3.0 In-Reply-To: <1424816514-26369-1-git-send-email-German.Rivera@freescale.com> References: <1424816514-26369-1-git-send-email-German.Rivera@freescale.com> X-EOPAttributedMessage: 0 Authentication-Results: spf=fail (sender IP is 192.88.158.2) smtp.mailfrom=German.Rivera@freescale.com; freescale.mail.onmicrosoft.com; dkim=none (message not signed) header.d=none; X-Forefront-Antispam-Report: CIP:192.88.158.2;CTRY:US;IPV:NLI;EFV:NLI;SFV:NSPM;SFS:(10019020)(6009001)(339900001)(199003)(189002)(50986999)(76176999)(104016003)(105606002)(62966003)(77096005)(68736005)(5890100001)(77156002)(48376002)(50466002)(36756003)(87936001)(2201001)(85426001)(46102003)(47776003)(64706001)(50226001)(19580405001)(19580395003)(69596002)(86362001)(6806004)(97736003)(229853001)(2950100001)(81156004)(106466001)(92566002);DIR:OUT;SFP:1102;SCL:1;SRVR:DM2PR03MB510;H:az84smr01.freescale.net;FPR:;SPF:Fail;PTR:InfoDomainNonexistent;A:1;MX:1;LANG:en; MIME-Version: 1.0 Content-Type: text/plain X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:DM2PR03MB510; X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:; X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(601004)(5005004);SRVR:DM2PR03MB510;BCL:0;PCL:0;RULEID:;SRVR:DM2PR03MB510; X-Forefront-PRVS: 04976078F0 X-OriginatorOrg: freescale.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 24 Feb 2015 22:27:14.4454 (UTC) X-MS-Exchange-CrossTenant-Id: 710a03f5-10f6-4d38-9ff4-a80b81da590d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=710a03f5-10f6-4d38-9ff4-a80b81da590d;Ip=[192.88.158.2] X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: DM2PR03MB510 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 31330 Lines: 1130 Platform device driver that sets up the basic bus infrastructure for the fsl-mc bus type, including support for adding/removing fsl-mc devices, register/unregister of fsl-mc drivers, and bus match support to bind devices to drivers. Signed-off-by: J. German Rivera Signed-off-by: Stuart Yoder --- Changes in v7: - Refactored MC bus data structures - Removed creation of fsl_mc_io object from fsl_mc_add_device() - Addressed comments from Alex Graf: * Use the 'ranges' property for the 'fsl,qoriq-mc' node as a way to translate MC-returned addresses (bus relative addresses) to system physical addresses. Changes addressed in v6: - Fixed new checkpatch warnings Changes addressed in v5: - Addressed comments from Alex Graf: * Renamed dprc_get_container_id() call as dpmng_get_container_id(). * Added TODO comment to use the 'ranges' property of the fsl-mc DT node. Changes addressed in v4: - Addressed comments from Alex Graf: * Added missing scope limitations and more descriptive help text for the FSL_MC_BUS config option. - Fixed bug related to setting fsl_mc_bus_type.dev_root too late. Changes in v3: - Addressed changes from Kim Phillips: * Renamed files: drivers/bus/fsl-mc/fsl_mc_bus.c -> drivers/bus/fsl-mc/mc-bus.c include/linux/fsl_mc.h -> include/linux/fsl/mc.h include/linux/fsl_mc_private.h -> include/linux/fsl/mc-private.h - Addressed comments from Timur Tabi: * Changed all functions that had goto out/error when no common cleanup was done, to just have multiple return points. * Replaced error cleanup boolean flags with multiple exit points. Changes in v2: - Addressed comment from Joe Perches: * Changed pr_debug to dev_dbg in fsl_mc_bus_match - Addressed comments from Kim Phillips and Alex Graf: * Changed version check to allow the driver to run with MC firmware that has major version number greater than or equal to the driver's major version number. * Removed minor version check - Removed unused variable parent_dev in fsl_mc_device_remove drivers/bus/Kconfig | 3 + drivers/bus/Makefile | 3 + drivers/bus/fsl-mc/Kconfig | 24 ++ drivers/bus/fsl-mc/Makefile | 14 + drivers/bus/fsl-mc/mc-bus.c | 758 +++++++++++++++++++++++++++++++++++++++++ include/linux/fsl/mc-private.h | 67 ++++ include/linux/fsl/mc.h | 136 ++++++++ 7 files changed, 1005 insertions(+) create mode 100644 drivers/bus/fsl-mc/Kconfig create mode 100644 drivers/bus/fsl-mc/Makefile create mode 100644 drivers/bus/fsl-mc/mc-bus.c create mode 100644 include/linux/fsl/mc-private.h create mode 100644 include/linux/fsl/mc.h diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index b99729e..dde3ec2 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -67,4 +67,7 @@ config VEXPRESS_CONFIG help Platform configuration infrastructure for the ARM Ltd. Versatile Express. + +source "drivers/bus/fsl-mc/Kconfig" + endmenu diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 2973c18..6abcab1 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -15,3 +15,6 @@ obj-$(CONFIG_ARM_CCI) += arm-cci.o obj-$(CONFIG_ARM_CCN) += arm-ccn.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o + +# Freescale Management Complex (MC) bus drivers +obj-$(CONFIG_FSL_MC_BUS) += fsl-mc/ diff --git a/drivers/bus/fsl-mc/Kconfig b/drivers/bus/fsl-mc/Kconfig new file mode 100644 index 0000000..0d779d9 --- /dev/null +++ b/drivers/bus/fsl-mc/Kconfig @@ -0,0 +1,24 @@ +# +# Freescale Management Complex (MC) bus drivers +# +# Copyright (C) 2014 Freescale Semiconductor, Inc. +# +# This file is released under the GPLv2 +# + +config FSL_MC_BUS + tristate "Freescale Management Complex (MC) bus driver" + depends on OF && ARM64 + help + Driver to enable the bus infrastructure for the Freescale + QorIQ Management Complex (fsl-mc). The fsl-mc is a hardware + module of the QorIQ LS2 SoCs, that does resource management + for hardware building-blocks in the SoC that can be used + to dynamically create networking hardware objects such as + network interfaces (NICs), crypto accelerator instances, + or L2 switches. + + Only enable this option when building the kernel for + Freescale QorQIQ LS2xxxx SoCs. + + diff --git a/drivers/bus/fsl-mc/Makefile b/drivers/bus/fsl-mc/Makefile new file mode 100644 index 0000000..decd339 --- /dev/null +++ b/drivers/bus/fsl-mc/Makefile @@ -0,0 +1,14 @@ +# +# Freescale Management Complex (MC) bus drivers +# +# Copyright (C) 2014 Freescale Semiconductor, Inc. +# +# This file is released under the GPLv2 +# +obj-$(CONFIG_FSL_MC_BUS) += mc-bus-driver.o + +mc-bus-driver-objs := mc-bus.o \ + mc-sys.o \ + dprc.o \ + dpmng.o + diff --git a/drivers/bus/fsl-mc/mc-bus.c b/drivers/bus/fsl-mc/mc-bus.c new file mode 100644 index 0000000..01407cc --- /dev/null +++ b/drivers/bus/fsl-mc/mc-bus.c @@ -0,0 +1,758 @@ +/* + * Freescale Management Complex (MC) bus driver + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Author: German Rivera + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dprc-cmd.h" + +static struct kmem_cache *mc_dev_cache; + +/** + * fsl_mc_bus_match - device to driver matching callback + * @dev: the MC object device structure to match against + * @drv: the device driver to search for matching MC object device id + * structures + * + * Returns 1 on success, 0 otherwise. + */ +static int fsl_mc_bus_match(struct device *dev, struct device_driver *drv) +{ + const struct fsl_mc_device_match_id *id; + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(drv); + bool found = false; + + if (WARN_ON(!fsl_mc_bus_type.dev_root)) + goto out; + + if (!mc_drv->match_id_table) + goto out; + + /* + * If the object is not 'plugged' don't match. + * Only exception is the root DPRC, which is a special case. + */ + if ((mc_dev->obj_desc.state & DPRC_OBJ_STATE_PLUGGED) == 0 && + &mc_dev->dev != fsl_mc_bus_type.dev_root) + goto out; + + /* + * Traverse the match_id table of the given driver, trying to find + * a matching for the given MC object device. + */ + for (id = mc_drv->match_id_table; id->vendor != 0x0; id++) { + if (id->vendor == mc_dev->obj_desc.vendor && + strcmp(id->obj_type, mc_dev->obj_desc.type) == 0 && + id->ver_major == mc_dev->obj_desc.ver_major && + id->ver_minor == mc_dev->obj_desc.ver_minor) { + found = true; + break; + } + } + +out: + dev_dbg(dev, "%smatched\n", found ? "" : "not "); + return found; +} + +/** + * fsl_mc_bus_uevent - callback invoked when a device is added + */ +static int fsl_mc_bus_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + pr_debug("%s invoked\n", __func__); + return 0; +} + +struct bus_type fsl_mc_bus_type = { + .name = "fsl-mc", + .match = fsl_mc_bus_match, + .uevent = fsl_mc_bus_uevent, +}; +EXPORT_SYMBOL_GPL(fsl_mc_bus_type); + +static int fsl_mc_driver_probe(struct device *dev) +{ + struct fsl_mc_driver *mc_drv; + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + int error; + + if (WARN_ON(!dev->driver)) + return -EINVAL; + + mc_drv = to_fsl_mc_driver(dev->driver); + if (WARN_ON(!mc_drv->probe)) + return -EINVAL; + + error = mc_drv->probe(mc_dev); + if (error < 0) { + dev_err(dev, "MC object device probe callback failed: %d\n", + error); + return error; + } + + return 0; +} + +static int fsl_mc_driver_remove(struct device *dev) +{ + struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + int error; + + if (WARN_ON(!dev->driver)) + return -EINVAL; + + error = mc_drv->remove(mc_dev); + if (error < 0) { + dev_err(dev, + "MC object device remove callback failed: %d\n", + error); + return error; + } + + return 0; +} + +static void fsl_mc_driver_shutdown(struct device *dev) +{ + struct fsl_mc_driver *mc_drv = to_fsl_mc_driver(dev->driver); + struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); + + mc_drv->shutdown(mc_dev); +} + +/** + * __fsl_mc_driver_register - registers a child device driver with the + * MC bus + * + * This function is implicitly invoked from the registration function of + * fsl_mc device drivers, which is generated by the + * module_fsl_mc_driver() macro. + */ +int __fsl_mc_driver_register(struct fsl_mc_driver *mc_driver, + struct module *owner) +{ + int error; + + mc_driver->driver.owner = owner; + mc_driver->driver.bus = &fsl_mc_bus_type; + + /* + * Set default driver callbacks, if not set by the child driver: + */ + if (mc_driver->probe) + mc_driver->driver.probe = fsl_mc_driver_probe; + + if (mc_driver->remove) + mc_driver->driver.remove = fsl_mc_driver_remove; + + if (mc_driver->shutdown) + mc_driver->driver.shutdown = fsl_mc_driver_shutdown; + + error = driver_register(&mc_driver->driver); + if (error < 0) { + pr_err("driver_register() failed for %s: %d\n", + mc_driver->driver.name, error); + return error; + } + + pr_info("MC object device driver %s registered\n", + mc_driver->driver.name); + return 0; +} +EXPORT_SYMBOL_GPL(__fsl_mc_driver_register); + +/** + * fsl_mc_driver_unregister - unregisters a device driver from the + * MC bus + */ +void fsl_mc_driver_unregister(struct fsl_mc_driver *mc_driver) +{ + driver_unregister(&mc_driver->driver); +} +EXPORT_SYMBOL_GPL(fsl_mc_driver_unregister); + +static int get_dprc_icid(struct fsl_mc_io *mc_io, + int container_id, uint16_t *icid) +{ + uint16_t dprc_handle; + struct dprc_attributes attr; + int error; + + error = dprc_open(mc_io, container_id, &dprc_handle); + if (error < 0) { + pr_err("dprc_open() failed: %d\n", error); + return error; + } + + memset(&attr, 0, sizeof(attr)); + error = dprc_get_attributes(mc_io, dprc_handle, &attr); + if (error < 0) { + pr_err("dprc_get_attributes() failed: %d\n", error); + goto common_cleanup; + } + + *icid = attr.icid; + error = 0; + +common_cleanup: + (void)dprc_close(mc_io, dprc_handle); + return error; +} + +static int translate_mc_addr(uint64_t mc_addr, phys_addr_t *phys_addr) +{ + int i; + struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent); + + if (mc->num_translation_ranges == 0) { + /* + * Do identity mapping: + */ + *phys_addr = mc_addr; + return 0; + } + + for (i = 0; i < mc->num_translation_ranges; i++) { + struct fsl_mc_addr_translation_range *range = + &mc->translation_ranges[i]; + + if (mc_addr >= range->start_mc_addr && + mc_addr < range->end_mc_addr) { + *phys_addr = range->start_phys_addr + + (mc_addr - range->start_mc_addr); + return 0; + } + } + + return -EFAULT; +} + +static int fsl_mc_device_get_mmio_regions(struct fsl_mc_device *mc_dev, + struct fsl_mc_device *mc_bus_dev) +{ + int i; + int error; + struct resource *regions; + struct dprc_obj_desc *obj_desc = &mc_dev->obj_desc; + struct device *parent_dev = mc_dev->dev.parent; + + regions = kmalloc_array(obj_desc->region_count, + sizeof(regions[0]), GFP_KERNEL); + if (!regions) + return -ENOMEM; + + for (i = 0; i < obj_desc->region_count; i++) { + struct dprc_region_desc region_desc; + + error = dprc_get_obj_region(mc_bus_dev->mc_io, + mc_bus_dev->mc_handle, + obj_desc->type, + obj_desc->id, i, ®ion_desc); + if (error < 0) { + dev_err(parent_dev, + "dprc_get_obj_region() failed: %d\n", error); + goto error_cleanup_regions; + } + + WARN_ON(region_desc.base_paddr == 0x0); + WARN_ON(region_desc.size == 0); + error = translate_mc_addr(region_desc.base_paddr, + ®ions[i].start); + if (error < 0) { + dev_err(parent_dev, + "Invalid MC address: %#llx\n", + region_desc.base_paddr); + goto error_cleanup_regions; + } + + regions[i].end = regions[i].start + region_desc.size - 1; + regions[i].name = "fsl-mc object MMIO region"; + regions[i].flags = IORESOURCE_IO; + } + + mc_dev->regions = regions; + return 0; + +error_cleanup_regions: + kfree(regions); + return error; +} + +/** + * Add a newly discovered MC object device to be visible in Linux + */ +int fsl_mc_device_add(struct dprc_obj_desc *obj_desc, + struct fsl_mc_io *mc_io, + struct device *parent_dev, + struct fsl_mc_device **new_mc_dev) +{ + int error; + struct fsl_mc_device *mc_dev = NULL; + struct fsl_mc_bus *mc_bus = NULL; + struct fsl_mc_device *parent_mc_dev; + + if (parent_dev->bus == &fsl_mc_bus_type) + parent_mc_dev = to_fsl_mc_device(parent_dev); + else + parent_mc_dev = NULL; + + if (strcmp(obj_desc->type, "dprc") == 0) { + /* + * Allocate an MC bus device object: + */ + mc_bus = devm_kzalloc(parent_dev, sizeof(*mc_bus), GFP_KERNEL); + if (!mc_bus) + return -ENOMEM; + + mc_dev = &mc_bus->mc_dev; + } else { + /* + * Allocate a regular fsl_mc_device object: + */ + mc_dev = kmem_cache_zalloc(mc_dev_cache, GFP_KERNEL); + if (!mc_dev) + return -ENOMEM; + } + + mc_dev->obj_desc = *obj_desc; + mc_dev->mc_io = mc_io; + device_initialize(&mc_dev->dev); + mc_dev->dev.parent = parent_dev; + mc_dev->dev.bus = &fsl_mc_bus_type; + dev_set_name(&mc_dev->dev, "%s.%x", obj_desc->type, obj_desc->id); + + if (strcmp(obj_desc->type, "dprc") == 0) { + struct fsl_mc_io *mc_io2; + + mc_dev->flags |= FSL_MC_IS_DPRC; + + /* + * To get the DPRC's ICID, we need to open the DPRC + * in get_dprc_icid(). For child DPRCs, we do so using the + * parent DPRC's MC portal instead of the child DPRC's MC + * portal, in case the child DPRC is already opened with + * its own portal (e.g., the DPRC used by AIOP). + * + * NOTE: There cannot be more than one active open for a + * given MC object, using the same MC portal. + */ + if (parent_mc_dev) { + /* + * device being added is a child DPRC device + */ + mc_io2 = parent_mc_dev->mc_io; + } else { + /* + * device being added is the root DPRC device + */ + if (WARN_ON(!mc_io)) { + error = -EINVAL; + goto error_cleanup_dev; + } + + mc_io2 = mc_io; + + if (!fsl_mc_bus_type.dev_root) + fsl_mc_bus_type.dev_root = &mc_dev->dev; + } + + error = get_dprc_icid(mc_io2, obj_desc->id, &mc_dev->icid); + if (error < 0) + goto error_cleanup_dev; + } else { + /* + * A non-DPRC MC object device has to be a child of another + * MC object (specifically a DPRC object) + */ + mc_dev->icid = parent_mc_dev->icid; + mc_dev->dma_mask = FSL_MC_DEFAULT_DMA_MASK; + mc_dev->dev.dma_mask = &mc_dev->dma_mask; + } + + /* + * Get MMIO regions for the device from the MC: + * + * NOTE: the root DPRC is a special case as its MMIO region is + * obtained from the device tree + */ + if (parent_mc_dev && obj_desc->region_count != 0) { + error = fsl_mc_device_get_mmio_regions(mc_dev, + parent_mc_dev); + if (error < 0) + goto error_cleanup_dev; + } + + /* + * The device-specific probe callback will get invoked by device_add() + */ + error = device_add(&mc_dev->dev); + if (error < 0) { + dev_err(parent_dev, + "device_add() failed for device %s: %d\n", + dev_name(&mc_dev->dev), error); + goto error_cleanup_dev; + } + + (void)get_device(&mc_dev->dev); + dev_dbg(parent_dev, "Added MC object device %s\n", + dev_name(&mc_dev->dev)); + + *new_mc_dev = mc_dev; + return 0; + +error_cleanup_dev: + kfree(mc_dev->regions); + if (mc_bus) + devm_kfree(parent_dev, mc_bus); + else + kmem_cache_free(mc_dev_cache, mc_dev); + + return error; +} +EXPORT_SYMBOL_GPL(fsl_mc_device_add); + +/** + * fsl_mc_device_remove - Remove a MC object device from being visible to + * Linux + * + * @mc_dev: Pointer to a MC object device object + */ +void fsl_mc_device_remove(struct fsl_mc_device *mc_dev) +{ + struct fsl_mc_bus *mc_bus = NULL; + + kfree(mc_dev->regions); + + /* + * The device-specific remove callback will get invoked by device_del() + */ + device_del(&mc_dev->dev); + put_device(&mc_dev->dev); + + if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) { + struct fsl_mc_io *mc_io = mc_dev->mc_io; + + mc_bus = to_fsl_mc_bus(mc_dev); + fsl_destroy_mc_io(mc_io); + if (&mc_dev->dev == fsl_mc_bus_type.dev_root) + fsl_mc_bus_type.dev_root = NULL; + } + + mc_dev->mc_io = NULL; + if (mc_bus) + devm_kfree(mc_dev->dev.parent, mc_bus); + else + kmem_cache_free(mc_dev_cache, mc_dev); +} +EXPORT_SYMBOL_GPL(fsl_mc_device_remove); + +static int parse_mc_ranges(struct device *dev, + int *paddr_cells, + int *mc_addr_cells, + int *mc_size_cells, + const __be32 **ranges_start, + uint8_t *num_ranges) +{ + const __be32 *prop; + int range_tuple_cell_count; + int ranges_len; + int tuple_len; + struct device_node *mc_node = dev->of_node; + + *ranges_start = of_get_property(mc_node, "ranges", &ranges_len); + if (!(*ranges_start) || !ranges_len) { + dev_warn(dev, + "missing or empty ranges property for device tree node '%s'\n", + mc_node->name); + + *num_ranges = 0; + return 0; + } + + *paddr_cells = of_n_addr_cells(mc_node); + + prop = of_get_property(mc_node, "#address-cells", NULL); + if (prop) + *mc_addr_cells = be32_to_cpup(prop); + else + *mc_addr_cells = *paddr_cells; + + prop = of_get_property(mc_node, "#size-cells", NULL); + if (prop) + *mc_size_cells = be32_to_cpup(prop); + else + *mc_size_cells = of_n_size_cells(mc_node); + + range_tuple_cell_count = *paddr_cells + *mc_addr_cells + + *mc_size_cells; + + tuple_len = range_tuple_cell_count * sizeof(__be32); + if (ranges_len % tuple_len != 0) { + dev_err(dev, "malformed ranges property '%s'\n", mc_node->name); + return -EINVAL; + } + + *num_ranges = ranges_len / tuple_len; + return 0; +} + +static int get_mc_addr_translation_ranges(struct device *dev, + struct fsl_mc_addr_translation_range + **ranges, + uint8_t *num_ranges) +{ + int error; + int paddr_cells; + int mc_addr_cells; + int mc_size_cells; + int i; + const __be32 *ranges_start; + const __be32 *cell; + + error = parse_mc_ranges(dev, + &paddr_cells, + &mc_addr_cells, + &mc_size_cells, + &ranges_start, + num_ranges); + if (error < 0) + return error; + + if (!(*num_ranges)) { + /* + * Missing or empty ranges property ("ranges;") for the + * 'fsl,qoriq-mc' node. In this case, identity mapping + * will be used. + */ + *ranges = NULL; + return 0; + } + + *ranges = devm_kcalloc(dev, *num_ranges, + sizeof(struct fsl_mc_addr_translation_range), + GFP_KERNEL); + if (!(*ranges)) + return -ENOMEM; + + cell = ranges_start; + for (i = 0; i < *num_ranges; ++i) { + struct fsl_mc_addr_translation_range *range = &(*ranges)[i]; + + range->start_mc_addr = of_read_number(cell, mc_addr_cells); + cell += mc_addr_cells; + range->start_phys_addr = of_read_number(cell, paddr_cells); + cell += paddr_cells; + range->end_mc_addr = range->start_mc_addr + + of_read_number(cell, mc_size_cells); + + cell += mc_size_cells; + } + + return 0; +} + +/** + * fsl_mc_bus_probe - callback invoked when the root MC bus is being + * added + */ +static int fsl_mc_bus_probe(struct platform_device *pdev) +{ + struct dprc_obj_desc obj_desc; + int error; + struct fsl_mc *mc; + struct fsl_mc_device *mc_bus_dev = NULL; + struct fsl_mc_io *mc_io = NULL; + int container_id; + phys_addr_t mc_portal_phys_addr; + uint32_t mc_portal_size; + struct mc_version mc_version; + struct resource res; + + dev_info(&pdev->dev, "Root MC bus device probed"); + + mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + platform_set_drvdata(pdev, mc); + + /* + * Get physical address of MC portal for the root DPRC: + */ + error = of_address_to_resource(pdev->dev.of_node, 0, &res); + if (error < 0) { + dev_err(&pdev->dev, + "of_address_to_resource() failed for %s\n", + pdev->dev.of_node->full_name); + return error; + } + + mc_portal_phys_addr = res.start; + mc_portal_size = resource_size(&res); + error = fsl_create_mc_io(&pdev->dev, mc_portal_phys_addr, + mc_portal_size, 0, &mc_io); + if (error < 0) + return error; + + error = mc_get_version(mc_io, &mc_version); + if (error != 0) { + dev_err(&pdev->dev, + "mc_get_version() failed with error %d\n", error); + goto error_cleanup_mc_io; + } + + dev_info(&pdev->dev, + "Freescale Management Complex Firmware version: %u.%u.%u\n", + mc_version.major, mc_version.minor, mc_version.revision); + + if (mc_version.major < MC_VER_MAJOR) { + dev_err(&pdev->dev, + "ERROR: MC firmware version not supported by driver (driver version: %u.%u)\n", + MC_VER_MAJOR, MC_VER_MINOR); + error = -ENOTSUPP; + goto error_cleanup_mc_io; + } + + if (mc_version.major > MC_VER_MAJOR) { + dev_warn(&pdev->dev, + "WARNING: driver may not support newer MC firmware features (driver version: %u.%u)\n", + MC_VER_MAJOR, MC_VER_MINOR); + } + + error = get_mc_addr_translation_ranges(&pdev->dev, + &mc->translation_ranges, + &mc->num_translation_ranges); + if (error < 0) + goto error_cleanup_mc_io; + + error = dpmng_get_container_id(mc_io, &container_id); + if (error < 0) { + dev_err(&pdev->dev, + "dpmng_get_container_id() failed: %d\n", error); + goto error_cleanup_mc_io; + } + + obj_desc.vendor = FSL_MC_VENDOR_FREESCALE; + strcpy(obj_desc.type, "dprc"); + obj_desc.id = container_id; + obj_desc.ver_major = DPRC_VER_MAJOR; + obj_desc.ver_minor = DPRC_VER_MINOR; + obj_desc.region_count = 0; + + error = fsl_mc_device_add(&obj_desc, mc_io, &pdev->dev, &mc_bus_dev); + if (error < 0) + goto error_cleanup_mc_io; + + mc->root_mc_bus_dev = mc_bus_dev; + return 0; + +error_cleanup_mc_io: + fsl_destroy_mc_io(mc_io); + return error; +} + +/** + * fsl_mc_bus_remove - callback invoked when the root MC bus is being + * removed + */ +static int fsl_mc_bus_remove(struct platform_device *pdev) +{ + struct fsl_mc *mc = platform_get_drvdata(pdev); + + if (WARN_ON(&mc->root_mc_bus_dev->dev != fsl_mc_bus_type.dev_root)) + return -EINVAL; + + fsl_mc_device_remove(mc->root_mc_bus_dev); + dev_info(&pdev->dev, "Root MC bus device removed"); + return 0; +} + +static const struct of_device_id fsl_mc_bus_match_table[] = { + {.compatible = "fsl,qoriq-mc",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, fsl_mc_bus_match_table); + +static struct platform_driver fsl_mc_bus_driver = { + .driver = { + .name = "fsl_mc_bus", + .owner = THIS_MODULE, + .pm = NULL, + .of_match_table = fsl_mc_bus_match_table, + }, + .probe = fsl_mc_bus_probe, + .remove = fsl_mc_bus_remove, +}; + +static int __init fsl_mc_bus_driver_init(void) +{ + int error; + + mc_dev_cache = kmem_cache_create("fsl_mc_device", + sizeof(struct fsl_mc_device), 0, 0, + NULL); + if (!mc_dev_cache) { + pr_err("Could not create fsl_mc_device cache\n"); + return -ENOMEM; + } + + error = bus_register(&fsl_mc_bus_type); + if (error < 0) { + pr_err("fsl-mc bus type registration failed: %d\n", error); + goto error_cleanup_cache; + } + + pr_info("fsl-mc bus type registered\n"); + + error = platform_driver_register(&fsl_mc_bus_driver); + if (error < 0) { + pr_err("platform_driver_register() failed: %d\n", error); + goto error_cleanup_bus; + } + + return 0; + +error_cleanup_bus: + bus_unregister(&fsl_mc_bus_type); + +error_cleanup_cache: + kmem_cache_destroy(mc_dev_cache); + return error; +} + +postcore_initcall(fsl_mc_bus_driver_init); + +static void __exit fsl_mc_bus_driver_exit(void) +{ + if (WARN_ON(!mc_dev_cache)) + return; + + platform_driver_unregister(&fsl_mc_bus_driver); + bus_unregister(&fsl_mc_bus_type); + kmem_cache_destroy(mc_dev_cache); + pr_info("MC bus unregistered\n"); +} + +module_exit(fsl_mc_bus_driver_exit); + +MODULE_AUTHOR("Freescale Semiconductor Inc."); +MODULE_DESCRIPTION("Freescale Management Complex (MC) bus driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/fsl/mc-private.h b/include/linux/fsl/mc-private.h new file mode 100644 index 0000000..066f9d4 --- /dev/null +++ b/include/linux/fsl/mc-private.h @@ -0,0 +1,67 @@ +/* + * Freescale Management Complex (MC) bus private declarations + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Author: German Rivera + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +#ifndef _FSL_MC_PRIVATE_H_ +#define _FSL_MC_PRIVATE_H_ + +#include +#include +#include + +#define FSL_MC_DEVICE_MATCH(_mc_dev, _obj_desc) \ + (strcmp((_mc_dev)->obj_desc.type, (_obj_desc)->type) == 0 && \ + (_mc_dev)->obj_desc.id == (_obj_desc)->id) + +/** + * struct fsl_mc - Private data of a "fsl,qoriq-mc" platform device + * @root_mc_bus_dev: MC object device representing the root DPRC + * @addr_translation_ranges: array of bus to system address translation ranges + */ +struct fsl_mc { + struct fsl_mc_device *root_mc_bus_dev; + uint8_t num_translation_ranges; + struct fsl_mc_addr_translation_range *translation_ranges; +}; + +/** + * struct fsl_mc_bus - logical bus that corresponds to a physical DPRC + * @mc_dev: fsl-mc device for the bus device itself. + * @scan_mutex: Serializes bus scanning + */ +struct fsl_mc_bus { + struct fsl_mc_device mc_dev; + struct mutex scan_mutex; /* serializes bus scanning */ +}; + +#define to_fsl_mc_bus(_mc_dev) \ + container_of(_mc_dev, struct fsl_mc_bus, mc_dev) + +/** + * struct fsl_mc_addr_translation_range - bus to system address translation + * range + * @start_mc_addr: Start MC address of the range being translated + * @end_mc_addr: MC address of the first byte after the range (last MC + * address of the range is end_mc_addr - 1) + * @start_phys_addr: system physical address corresponding to start_mc_addr + */ +struct fsl_mc_addr_translation_range { + uint64_t start_mc_addr; + uint64_t end_mc_addr; + phys_addr_t start_phys_addr; +}; + +int __must_check fsl_mc_device_add(struct dprc_obj_desc *obj_desc, + struct fsl_mc_io *mc_io, + struct device *parent_dev, + struct fsl_mc_device **new_mc_dev); + +void fsl_mc_device_remove(struct fsl_mc_device *mc_dev); + +#endif /* _FSL_MC_PRIVATE_H_ */ diff --git a/include/linux/fsl/mc.h b/include/linux/fsl/mc.h new file mode 100644 index 0000000..3a62cd5 --- /dev/null +++ b/include/linux/fsl/mc.h @@ -0,0 +1,136 @@ +/* + * Freescale Management Complex (MC) bus public interface + * + * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Author: German Rivera + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +#ifndef _FSL_MC_H_ +#define _FSL_MC_H_ + +#include +#include +#include +#include + +#define FSL_MC_VENDOR_FREESCALE 0x1957 + +struct fsl_mc_device; +struct fsl_mc_io; + +/** + * struct fsl_mc_driver - MC object device driver object + * @driver: Generic device driver + * @match_id_table: table of supported device matching Ids + * @probe: Function called when a device is added + * @remove: Function called when a device is removed + * @shutdown: Function called at shutdown time to quiesce the device + * @suspend: Function called when a device is stopped + * @resume: Function called when a device is resumed + * + * Generic DPAA device driver object for device drivers that are registered + * with a DPRC bus. This structure is to be embedded in each device-specific + * driver structure. + */ +struct fsl_mc_driver { + struct device_driver driver; + const struct fsl_mc_device_match_id *match_id_table; + int (*probe)(struct fsl_mc_device *dev); + int (*remove)(struct fsl_mc_device *dev); + void (*shutdown)(struct fsl_mc_device *dev); + int (*suspend)(struct fsl_mc_device *dev, pm_message_t state); + int (*resume)(struct fsl_mc_device *dev); +}; + +#define to_fsl_mc_driver(_drv) \ + container_of(_drv, struct fsl_mc_driver, driver) + +/** + * struct fsl_mc_device_match_id - MC object device Id entry for driver matching + * @vendor: vendor ID + * @obj_type: MC object type + * @ver_major: MC object version major number + * @ver_minor: MC object version minor number + * + * Type of entries in the "device Id" table for MC object devices supported by + * a MC object device driver. The last entry of the table has vendor set to 0x0 + */ +struct fsl_mc_device_match_id { + uint16_t vendor; + const char obj_type[16]; + uint32_t ver_major; + uint32_t ver_minor; +}; + +/** + * Bit masks for a MC object device (struct fsl_mc_device) flags + */ +#define FSL_MC_IS_DPRC 0x0001 + +/** + * Default DMA mask for devices on a fsl-mc bus + */ +#define FSL_MC_DEFAULT_DMA_MASK (~0ULL) + +/** + * struct fsl_mc_device - MC object device object + * @dev: Linux driver model device object + * @dma_mask: Default DMA mask + * @flags: MC object device flags + * @icid: Isolation context ID for the device + * @mc_handle: MC handle for the corresponding MC object opened + * @mc_io: Pointer to MC IO object assigned to this device or + * NULL if none. + * @obj_desc: MC description of the DPAA device + * @regions: pointer to array of MMIO region entries + * + * Generic device object for MC object devices that are "attached" to a + * MC bus. + * + * NOTES: + * - For a non-DPRC object its icid is the same as its parent DPRC's icid. + * - The SMMU notifier callback gets invoked after device_add() has been + * called for an MC object device, but before the device-specific probe + * callback gets called. + */ +struct fsl_mc_device { + struct device dev; + uint64_t dma_mask; + uint16_t flags; + uint16_t icid; + uint16_t mc_handle; + struct fsl_mc_io *mc_io; + struct dprc_obj_desc obj_desc; + struct resource *regions; +}; + +#define to_fsl_mc_device(_dev) \ + container_of(_dev, struct fsl_mc_device, dev) + +/* + * module_fsl_mc_driver() - Helper macro for drivers that don't do + * anything special in module init/exit. This eliminates a lot of + * boilerplate. Each module may only use this macro once, and + * calling it replaces module_init() and module_exit() + */ +#define module_fsl_mc_driver(__fsl_mc_driver) \ + module_driver(__fsl_mc_driver, fsl_mc_driver_register, \ + fsl_mc_driver_unregister) + +/* + * Macro to avoid include chaining to get THIS_MODULE + */ +#define fsl_mc_driver_register(drv) \ + __fsl_mc_driver_register(drv, THIS_MODULE) + +int __must_check __fsl_mc_driver_register(struct fsl_mc_driver *fsl_mc_driver, + struct module *owner); + +void fsl_mc_driver_unregister(struct fsl_mc_driver *driver); + +extern struct bus_type fsl_mc_bus_type; + +#endif /* _FSL_MC_H_ */ -- 2.3.0 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/