Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) // Copyright(c) 2015-17 Intel Corporation. /* * SDW Intel Init Routines * * Initializes and creates SDW devices based on ACPI and Hardware values */ #include <linux/acpi.h> #include <linux/export.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/soundwire/sdw_intel.h> #include "intel.h" #define SDW_LINK_TYPE 4 /* from Intel ACPI documentation */ #define SDW_MAX_LINKS 4 #define SDW_SHIM_LCAP 0x0 #define SDW_SHIM_BASE 0x2C000 #define SDW_ALH_BASE 0x2C800 #define SDW_LINK_BASE 0x30000 #define SDW_LINK_SIZE 0x10000 struct sdw_link_data { struct sdw_intel_link_res res; struct platform_device *pdev; }; struct sdw_intel_ctx { int count; struct sdw_link_data *links; }; static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx) { struct sdw_link_data *link = ctx->links; int i; if (!link) return 0; for (i = 0; i < ctx->count; i++) { if (link->pdev) platform_device_unregister(link->pdev); link++; } kfree(ctx->links); ctx->links = NULL; return 0; } static struct sdw_intel_ctx *sdw_intel_add_controller(struct sdw_intel_res *res) { struct platform_device_info pdevinfo; struct platform_device *pdev; struct sdw_link_data *link; struct sdw_intel_ctx *ctx; struct acpi_device *adev; int ret, i; u8 count; u32 caps; if (acpi_bus_get_device(res->handle, &adev)) return NULL; /* Found controller, find links supported */ count = 0; ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev), "mipi-sdw-master-count", &count, 1); /* Don't fail on error, continue and use hw value */ if (ret) { dev_err(&adev->dev, "Failed to read mipi-sdw-master-count: %d\n", ret); count = SDW_MAX_LINKS; } /* Check SNDWLCAP.LCOUNT */ caps = ioread32(res->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP); caps &= GENMASK(2, 0); /* Check HW supported vs property value and use min of two */ count = min_t(u8, caps, count); /* Check count is within bounds */ if (count > SDW_MAX_LINKS) { dev_err(&adev->dev, "Link count %d exceeds max %d\n", count, SDW_MAX_LINKS); return NULL; } else if (!count) { dev_warn(&adev->dev, "No SoundWire links detected\n"); return NULL; } dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count); ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return NULL; ctx->count = count; ctx->links = kcalloc(ctx->count, sizeof(*ctx->links), GFP_KERNEL); if (!ctx->links) goto link_err; link = ctx->links; /* Create SDW Master devices */ for (i = 0; i < count; i++) { link->res.irq = res->irq; link->res.registers = res->mmio_base + SDW_LINK_BASE + (SDW_LINK_SIZE * i); link->res.shim = res->mmio_base + SDW_SHIM_BASE; link->res.alh = res->mmio_base + SDW_ALH_BASE; link->res.ops = res->ops; link->res.arg = res->arg; memset(&pdevinfo, 0, sizeof(pdevinfo)); pdevinfo.parent = res->parent; pdevinfo.name = "int-sdw"; pdevinfo.id = i; pdevinfo.fwnode = acpi_fwnode_handle(adev); pdevinfo.data = &link->res; pdevinfo.size_data = sizeof(link->res); pdev = platform_device_register_full(&pdevinfo); if (IS_ERR(pdev)) { dev_err(&adev->dev, "platform device creation failed: %ld\n", PTR_ERR(pdev)); goto pdev_err; } link->pdev = pdev; link++; } return ctx; pdev_err: sdw_intel_cleanup_pdev(ctx); link_err: kfree(ctx); return NULL; } static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, void *cdata, void **return_value) { struct sdw_intel_res *res = cdata; struct acpi_device *adev; acpi_status status; u64 adr; status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); if (ACPI_FAILURE(status)) return AE_OK; /* keep going */ if (acpi_bus_get_device(handle, &adev)) { pr_err("%s: Couldn't find ACPI handle\n", __func__); return AE_NOT_FOUND; } res->handle = handle; /* * On some Intel platforms, multiple children of the HDAS * device can be found, but only one of them is the SoundWire * controller. The SNDW device is always exposed with * Name(_ADR, 0x40000000), with bits 31..28 representing the * SoundWire link so filter accordingly */ if ((adr & GENMASK(31, 28)) >> 28 != SDW_LINK_TYPE) return AE_OK; /* keep going */ /* device found, stop namespace walk */ return AE_CTRL_TERMINATE; } /** * sdw_intel_init() - SoundWire Intel init routine * @parent_handle: ACPI parent handle * @res: resource data * * This scans the namespace and creates SoundWire link controller devices * based on the info queried. */ void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res) { acpi_status status; status = acpi_walk_namespace(ACPI_TYPE_DEVICE, parent_handle, 1, sdw_intel_acpi_cb, NULL, res, NULL); if (ACPI_FAILURE(status)) return NULL; return sdw_intel_add_controller(res); } EXPORT_SYMBOL(sdw_intel_init); /** * sdw_intel_exit() - SoundWire Intel exit * @arg: callback context * * Delete the controller instances created and cleanup */ void sdw_intel_exit(void *arg) { struct sdw_intel_ctx *ctx = arg; sdw_intel_cleanup_pdev(ctx); kfree(ctx); } EXPORT_SYMBOL(sdw_intel_exit); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("Intel Soundwire Init Library"); |