1d62a7d41SVinod Koul // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
2d62a7d41SVinod Koul // Copyright(c) 2015-17 Intel Corporation.
3d62a7d41SVinod Koul 
4d62a7d41SVinod Koul /*
5d62a7d41SVinod Koul  * SDW Intel Init Routines
6d62a7d41SVinod Koul  *
7d62a7d41SVinod Koul  * Initializes and creates SDW devices based on ACPI and Hardware values
8d62a7d41SVinod Koul  */
9d62a7d41SVinod Koul 
10d62a7d41SVinod Koul #include <linux/acpi.h>
114abbd783SPaul Gortmaker #include <linux/export.h>
123fc40449SVinod Koul #include <linux/io.h>
134abbd783SPaul Gortmaker #include <linux/module.h>
14d62a7d41SVinod Koul #include <linux/platform_device.h>
15d62a7d41SVinod Koul #include <linux/soundwire/sdw_intel.h>
16d62a7d41SVinod Koul #include "intel.h"
17d62a7d41SVinod Koul 
186f11586fSPierre-Louis Bossart #define SDW_LINK_TYPE		4 /* from Intel ACPI documentation */
19d62a7d41SVinod Koul #define SDW_MAX_LINKS		4
20d62a7d41SVinod Koul #define SDW_SHIM_LCAP		0x0
21d62a7d41SVinod Koul #define SDW_SHIM_BASE		0x2C000
22d62a7d41SVinod Koul #define SDW_ALH_BASE		0x2C800
23d62a7d41SVinod Koul #define SDW_LINK_BASE		0x30000
24d62a7d41SVinod Koul #define SDW_LINK_SIZE		0x10000
25d62a7d41SVinod Koul 
2650302fc7SPierre-Louis Bossart static int link_mask;
2750302fc7SPierre-Louis Bossart module_param_named(sdw_link_mask, link_mask, int, 0444);
2850302fc7SPierre-Louis Bossart MODULE_PARM_DESC(sdw_link_mask, "Intel link mask (one bit per link)");
2950302fc7SPierre-Louis Bossart 
30d62a7d41SVinod Koul static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx)
31d62a7d41SVinod Koul {
32f98f690fSPierre-Louis Bossart 	struct sdw_intel_link_res *link = ctx->links;
33d62a7d41SVinod Koul 	int i;
34d62a7d41SVinod Koul 
35d62a7d41SVinod Koul 	if (!link)
36d62a7d41SVinod Koul 		return 0;
37d62a7d41SVinod Koul 
38d62a7d41SVinod Koul 	for (i = 0; i < ctx->count; i++) {
39d62a7d41SVinod Koul 		if (link->pdev)
40d62a7d41SVinod Koul 			platform_device_unregister(link->pdev);
41d62a7d41SVinod Koul 		link++;
42d62a7d41SVinod Koul 	}
43d62a7d41SVinod Koul 
44d62a7d41SVinod Koul 	kfree(ctx->links);
45d62a7d41SVinod Koul 	ctx->links = NULL;
46d62a7d41SVinod Koul 
47d62a7d41SVinod Koul 	return 0;
48d62a7d41SVinod Koul }
49d62a7d41SVinod Koul 
50d62a7d41SVinod Koul static struct sdw_intel_ctx
51d62a7d41SVinod Koul *sdw_intel_add_controller(struct sdw_intel_res *res)
52d62a7d41SVinod Koul {
53d62a7d41SVinod Koul 	struct platform_device_info pdevinfo;
54d62a7d41SVinod Koul 	struct platform_device *pdev;
55f98f690fSPierre-Louis Bossart 	struct sdw_intel_link_res *link;
56d62a7d41SVinod Koul 	struct sdw_intel_ctx *ctx;
57d62a7d41SVinod Koul 	struct acpi_device *adev;
58d62a7d41SVinod Koul 	int ret, i;
59d62a7d41SVinod Koul 	u8 count;
60d62a7d41SVinod Koul 	u32 caps;
61d62a7d41SVinod Koul 
62d62a7d41SVinod Koul 	if (acpi_bus_get_device(res->handle, &adev))
63d62a7d41SVinod Koul 		return NULL;
64d62a7d41SVinod Koul 
65d62a7d41SVinod Koul 	/* Found controller, find links supported */
66d62a7d41SVinod Koul 	count = 0;
67d62a7d41SVinod Koul 	ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev),
68d62a7d41SVinod Koul 					    "mipi-sdw-master-count", &count, 1);
69d62a7d41SVinod Koul 
70d62a7d41SVinod Koul 	/* Don't fail on error, continue and use hw value */
71d62a7d41SVinod Koul 	if (ret) {
72d62a7d41SVinod Koul 		dev_err(&adev->dev,
73d62a7d41SVinod Koul 			"Failed to read mipi-sdw-master-count: %d\n", ret);
74d62a7d41SVinod Koul 		count = SDW_MAX_LINKS;
75d62a7d41SVinod Koul 	}
76d62a7d41SVinod Koul 
77d62a7d41SVinod Koul 	/* Check SNDWLCAP.LCOUNT */
78d62a7d41SVinod Koul 	caps = ioread32(res->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP);
79432732b8SPierre-Louis Bossart 	caps &= GENMASK(2, 0);
80d62a7d41SVinod Koul 
81d62a7d41SVinod Koul 	/* Check HW supported vs property value and use min of two */
82d62a7d41SVinod Koul 	count = min_t(u8, caps, count);
83d62a7d41SVinod Koul 
84d62a7d41SVinod Koul 	/* Check count is within bounds */
85d62a7d41SVinod Koul 	if (count > SDW_MAX_LINKS) {
86d62a7d41SVinod Koul 		dev_err(&adev->dev, "Link count %d exceeds max %d\n",
87d62a7d41SVinod Koul 			count, SDW_MAX_LINKS);
88d62a7d41SVinod Koul 		return NULL;
896f7219feSGuennadi Liakhovetski 	}
906f7219feSGuennadi Liakhovetski 
916f7219feSGuennadi Liakhovetski 	if (!count) {
92432732b8SPierre-Louis Bossart 		dev_warn(&adev->dev, "No SoundWire links detected\n");
93432732b8SPierre-Louis Bossart 		return NULL;
94d62a7d41SVinod Koul 	}
95d62a7d41SVinod Koul 
96d62a7d41SVinod Koul 	dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count);
97d62a7d41SVinod Koul 
98d62a7d41SVinod Koul 	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
99d62a7d41SVinod Koul 	if (!ctx)
100d62a7d41SVinod Koul 		return NULL;
101d62a7d41SVinod Koul 
102d62a7d41SVinod Koul 	ctx->count = count;
103d62a7d41SVinod Koul 	ctx->links = kcalloc(ctx->count, sizeof(*ctx->links), GFP_KERNEL);
104d62a7d41SVinod Koul 	if (!ctx->links)
105d62a7d41SVinod Koul 		goto link_err;
106d62a7d41SVinod Koul 
107d62a7d41SVinod Koul 	link = ctx->links;
108d62a7d41SVinod Koul 
109d62a7d41SVinod Koul 	/* Create SDW Master devices */
110d62a7d41SVinod Koul 	for (i = 0; i < count; i++) {
11150302fc7SPierre-Louis Bossart 		if (link_mask && !(link_mask & BIT(i))) {
11250302fc7SPierre-Louis Bossart 			dev_dbg(&adev->dev,
11350302fc7SPierre-Louis Bossart 				"Link %d masked, will not be enabled\n", i);
11450302fc7SPierre-Louis Bossart 			link++;
11550302fc7SPierre-Louis Bossart 			continue;
11650302fc7SPierre-Louis Bossart 		}
11750302fc7SPierre-Louis Bossart 
118f98f690fSPierre-Louis Bossart 		link->registers = res->mmio_base + SDW_LINK_BASE
119d62a7d41SVinod Koul 					+ (SDW_LINK_SIZE * i);
120f98f690fSPierre-Louis Bossart 		link->shim = res->mmio_base + SDW_SHIM_BASE;
121f98f690fSPierre-Louis Bossart 		link->alh = res->mmio_base + SDW_ALH_BASE;
122d62a7d41SVinod Koul 
123f98f690fSPierre-Louis Bossart 		link->ops = res->ops;
1244b206d34SRander Wang 		link->dev = res->dev;
125c46302ecSVinod Koul 
126d62a7d41SVinod Koul 		memset(&pdevinfo, 0, sizeof(pdevinfo));
127d62a7d41SVinod Koul 
128d62a7d41SVinod Koul 		pdevinfo.parent = res->parent;
129d62a7d41SVinod Koul 		pdevinfo.name = "int-sdw";
130d62a7d41SVinod Koul 		pdevinfo.id = i;
131d62a7d41SVinod Koul 		pdevinfo.fwnode = acpi_fwnode_handle(adev);
132d62a7d41SVinod Koul 
133d62a7d41SVinod Koul 		pdev = platform_device_register_full(&pdevinfo);
134d62a7d41SVinod Koul 		if (IS_ERR(pdev)) {
135d62a7d41SVinod Koul 			dev_err(&adev->dev,
136d62a7d41SVinod Koul 				"platform device creation failed: %ld\n",
137d62a7d41SVinod Koul 				PTR_ERR(pdev));
138d62a7d41SVinod Koul 			goto pdev_err;
139d62a7d41SVinod Koul 		}
140d62a7d41SVinod Koul 
141d62a7d41SVinod Koul 		link->pdev = pdev;
142d62a7d41SVinod Koul 		link++;
143d62a7d41SVinod Koul 	}
144d62a7d41SVinod Koul 
145d62a7d41SVinod Koul 	return ctx;
146d62a7d41SVinod Koul 
147d62a7d41SVinod Koul pdev_err:
148d62a7d41SVinod Koul 	sdw_intel_cleanup_pdev(ctx);
149d62a7d41SVinod Koul link_err:
150d62a7d41SVinod Koul 	kfree(ctx);
151d62a7d41SVinod Koul 	return NULL;
152d62a7d41SVinod Koul }
153d62a7d41SVinod Koul 
154d62a7d41SVinod Koul static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level,
155d62a7d41SVinod Koul 				     void *cdata, void **return_value)
156d62a7d41SVinod Koul {
157d62a7d41SVinod Koul 	struct sdw_intel_res *res = cdata;
158d62a7d41SVinod Koul 	struct acpi_device *adev;
1596f11586fSPierre-Louis Bossart 	acpi_status status;
1606f11586fSPierre-Louis Bossart 	u64 adr;
1616f11586fSPierre-Louis Bossart 
1626f11586fSPierre-Louis Bossart 	status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr);
1636f11586fSPierre-Louis Bossart 	if (ACPI_FAILURE(status))
1646f11586fSPierre-Louis Bossart 		return AE_OK; /* keep going */
165d62a7d41SVinod Koul 
166d62a7d41SVinod Koul 	if (acpi_bus_get_device(handle, &adev)) {
167e1c815f4SVinod Koul 		pr_err("%s: Couldn't find ACPI handle\n", __func__);
168d62a7d41SVinod Koul 		return AE_NOT_FOUND;
169d62a7d41SVinod Koul 	}
170d62a7d41SVinod Koul 
171d62a7d41SVinod Koul 	res->handle = handle;
1726f11586fSPierre-Louis Bossart 
1736f11586fSPierre-Louis Bossart 	/*
1746f11586fSPierre-Louis Bossart 	 * On some Intel platforms, multiple children of the HDAS
1756f11586fSPierre-Louis Bossart 	 * device can be found, but only one of them is the SoundWire
1766f11586fSPierre-Louis Bossart 	 * controller. The SNDW device is always exposed with
1776f11586fSPierre-Louis Bossart 	 * Name(_ADR, 0x40000000), with bits 31..28 representing the
1786f11586fSPierre-Louis Bossart 	 * SoundWire link so filter accordingly
1796f11586fSPierre-Louis Bossart 	 */
1806f11586fSPierre-Louis Bossart 	if ((adr & GENMASK(31, 28)) >> 28 != SDW_LINK_TYPE)
1816f11586fSPierre-Louis Bossart 		return AE_OK; /* keep going */
1826f11586fSPierre-Louis Bossart 
1836f11586fSPierre-Louis Bossart 	/* device found, stop namespace walk */
1846f11586fSPierre-Louis Bossart 	return AE_CTRL_TERMINATE;
185d62a7d41SVinod Koul }
186d62a7d41SVinod Koul 
187d62a7d41SVinod Koul /**
188d62a7d41SVinod Koul  * sdw_intel_init() - SoundWire Intel init routine
189d62a7d41SVinod Koul  * @parent_handle: ACPI parent handle
190d62a7d41SVinod Koul  * @res: resource data
191d62a7d41SVinod Koul  *
192d62a7d41SVinod Koul  * This scans the namespace and creates SoundWire link controller devices
193d62a7d41SVinod Koul  * based on the info queried.
194d62a7d41SVinod Koul  */
195d62a7d41SVinod Koul void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res)
196d62a7d41SVinod Koul {
197d62a7d41SVinod Koul 	acpi_status status;
198d62a7d41SVinod Koul 
199d62a7d41SVinod Koul 	status = acpi_walk_namespace(ACPI_TYPE_DEVICE,
200d62a7d41SVinod Koul 				     parent_handle, 1,
201d62a7d41SVinod Koul 				     sdw_intel_acpi_cb,
202d62a7d41SVinod Koul 				     NULL, res, NULL);
203d62a7d41SVinod Koul 	if (ACPI_FAILURE(status))
204d62a7d41SVinod Koul 		return NULL;
205d62a7d41SVinod Koul 
206d62a7d41SVinod Koul 	return sdw_intel_add_controller(res);
207d62a7d41SVinod Koul }
208d62a7d41SVinod Koul 
209d62a7d41SVinod Koul /**
210d62a7d41SVinod Koul  * sdw_intel_exit() - SoundWire Intel exit
211d62a7d41SVinod Koul  * @arg: callback context
212d62a7d41SVinod Koul  *
213d62a7d41SVinod Koul  * Delete the controller instances created and cleanup
214d62a7d41SVinod Koul  */
215f98f690fSPierre-Louis Bossart void sdw_intel_exit(struct sdw_intel_ctx *ctx)
216d62a7d41SVinod Koul {
217d62a7d41SVinod Koul 	sdw_intel_cleanup_pdev(ctx);
218d62a7d41SVinod Koul 	kfree(ctx);
219d62a7d41SVinod Koul }
220d62a7d41SVinod Koul EXPORT_SYMBOL(sdw_intel_exit);
221d62a7d41SVinod Koul 
222d62a7d41SVinod Koul MODULE_LICENSE("Dual BSD/GPL");
223d62a7d41SVinod Koul MODULE_DESCRIPTION("Intel Soundwire Init Library");
224