1// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
2/*
3 * SoundWire AMD Manager Initialize routines
4 *
5 * Initializes and creates SDW devices based on ACPI and Hardware values
6 *
7 * Copyright 2024 Advanced Micro Devices, Inc.
8 */
9
10#include <linux/acpi.h>
11#include <linux/export.h>
12#include <linux/io.h>
13#include <linux/module.h>
14#include <linux/platform_device.h>
15
16#include "amd_init.h"
17
18#define ACP_PAD_PULLDOWN_CTRL 0x0001448
19#define ACP_SW_PAD_KEEPER_EN 0x0001454
20#define AMD_SDW_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7f9a
21#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7f9f
22#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7ffa
23#define AMD_SDW0_PAD_EN_MASK 1
24#define AMD_SDW1_PAD_EN_MASK 0x10
25#define AMD_SDW_PAD_EN_MASK (AMD_SDW0_PAD_EN_MASK | AMD_SDW1_PAD_EN_MASK)
26
27static int amd_enable_sdw_pads(void __iomem *mmio, u32 link_mask, struct device *dev)
28{
29 u32 val;
30 u32 pad_keeper_en_mask, pad_pulldown_ctrl_mask;
31
32 switch (link_mask) {
33 case 1:
34 pad_keeper_en_mask = AMD_SDW0_PAD_EN_MASK;
35 pad_pulldown_ctrl_mask = AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK;
36 break;
37 case 2:
38 pad_keeper_en_mask = AMD_SDW1_PAD_EN_MASK;
39 pad_pulldown_ctrl_mask = AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK;
40 break;
41 case 3:
42 pad_keeper_en_mask = AMD_SDW_PAD_EN_MASK;
43 pad_pulldown_ctrl_mask = AMD_SDW_PAD_PULLDOWN_CTRL_ENABLE_MASK;
44 break;
45 default:
46 dev_err(dev, "No SDW Links are enabled\n");
47 return -ENODEV;
48 }
49
50 val = readl(addr: mmio + ACP_SW_PAD_KEEPER_EN);
51 val |= pad_keeper_en_mask;
52 writel(val, addr: mmio + ACP_SW_PAD_KEEPER_EN);
53 val = readl(addr: mmio + ACP_PAD_PULLDOWN_CTRL);
54 val &= pad_pulldown_ctrl_mask;
55 writel(val, addr: mmio + ACP_PAD_PULLDOWN_CTRL);
56 return 0;
57}
58
59static int sdw_amd_cleanup(struct sdw_amd_ctx *ctx)
60{
61 int i;
62
63 for (i = 0; i < ctx->count; i++) {
64 if (!(ctx->link_mask & BIT(i)))
65 continue;
66 platform_device_unregister(ctx->pdev[i]);
67 }
68
69 return 0;
70}
71
72static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res)
73{
74 struct sdw_amd_ctx *ctx;
75 struct acpi_device *adev;
76 struct resource *sdw_res;
77 struct acp_sdw_pdata sdw_pdata[2];
78 struct platform_device_info pdevinfo[2];
79 u32 link_mask;
80 int count, index;
81 int ret;
82
83 if (!res)
84 return NULL;
85
86 adev = acpi_fetch_acpi_dev(handle: res->handle);
87 if (!adev)
88 return NULL;
89
90 if (!res->count)
91 return NULL;
92
93 count = res->count;
94 dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count);
95 ret = amd_enable_sdw_pads(mmio: res->mmio_base, link_mask: res->link_mask, dev: res->parent);
96 if (ret)
97 return NULL;
98
99 /*
100 * we need to alloc/free memory manually and can't use devm:
101 * this routine may be called from a workqueue, and not from
102 * the parent .probe.
103 * If devm_ was used, the memory might never be freed on errors.
104 */
105 ctx = kzalloc(size: sizeof(*ctx), GFP_KERNEL);
106 if (!ctx)
107 return NULL;
108
109 ctx->count = count;
110 ctx->link_mask = res->link_mask;
111 sdw_res = kzalloc(size: sizeof(*sdw_res), GFP_KERNEL);
112 if (!sdw_res) {
113 kfree(objp: ctx);
114 return NULL;
115 }
116 sdw_res->flags = IORESOURCE_MEM;
117 sdw_res->start = res->addr;
118 sdw_res->end = res->addr + res->reg_range;
119 memset(&pdevinfo, 0, sizeof(pdevinfo));
120 link_mask = ctx->link_mask;
121 for (index = 0; index < count; index++) {
122 if (!(link_mask & BIT(index)))
123 continue;
124
125 sdw_pdata[index].instance = index;
126 sdw_pdata[index].acp_sdw_lock = res->acp_lock;
127 pdevinfo[index].name = "amd_sdw_manager";
128 pdevinfo[index].id = index;
129 pdevinfo[index].parent = res->parent;
130 pdevinfo[index].num_res = 1;
131 pdevinfo[index].res = sdw_res;
132 pdevinfo[index].data = &sdw_pdata[index];
133 pdevinfo[index].size_data = sizeof(struct acp_sdw_pdata);
134 pdevinfo[index].fwnode = acpi_fwnode_handle(adev);
135 ctx->pdev[index] = platform_device_register_full(pdevinfo: &pdevinfo[index]);
136 if (IS_ERR(ptr: ctx->pdev[index]))
137 goto err;
138 }
139 kfree(objp: sdw_res);
140 return ctx;
141err:
142 while (index--) {
143 if (!(link_mask & BIT(index)))
144 continue;
145
146 platform_device_unregister(ctx->pdev[index]);
147 }
148
149 kfree(objp: sdw_res);
150 kfree(objp: ctx);
151 return NULL;
152}
153
154static int sdw_amd_startup(struct sdw_amd_ctx *ctx)
155{
156 struct amd_sdw_manager *amd_manager;
157 int i, ret;
158
159 /* Startup SDW Manager devices */
160 for (i = 0; i < ctx->count; i++) {
161 if (!(ctx->link_mask & BIT(i)))
162 continue;
163 amd_manager = dev_get_drvdata(dev: &ctx->pdev[i]->dev);
164 ret = amd_sdw_manager_start(amd_manager);
165 if (ret)
166 return ret;
167 }
168
169 return 0;
170}
171
172int sdw_amd_probe(struct sdw_amd_res *res, struct sdw_amd_ctx **sdw_ctx)
173{
174 *sdw_ctx = sdw_amd_probe_controller(res);
175 if (!*sdw_ctx)
176 return -ENODEV;
177
178 return sdw_amd_startup(ctx: *sdw_ctx);
179}
180EXPORT_SYMBOL_NS(sdw_amd_probe, SOUNDWIRE_AMD_INIT);
181
182void sdw_amd_exit(struct sdw_amd_ctx *ctx)
183{
184 sdw_amd_cleanup(ctx);
185 kfree(objp: ctx->ids);
186 kfree(objp: ctx);
187}
188EXPORT_SYMBOL_NS(sdw_amd_exit, SOUNDWIRE_AMD_INIT);
189
190int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx)
191{
192 struct amd_sdw_manager *amd_manager;
193 struct sdw_bus *bus;
194 struct sdw_slave *slave;
195 struct list_head *node;
196 int index;
197 int i = 0;
198 int num_slaves = 0;
199
200 for (index = 0; index < ctx->count; index++) {
201 if (!(ctx->link_mask & BIT(index)))
202 continue;
203 amd_manager = dev_get_drvdata(dev: &ctx->pdev[index]->dev);
204 if (!amd_manager)
205 return -ENODEV;
206 bus = &amd_manager->bus;
207 /* Calculate number of slaves */
208 list_for_each(node, &bus->slaves)
209 num_slaves++;
210 }
211
212 ctx->ids = kcalloc(n: num_slaves, size: sizeof(*ctx->ids), GFP_KERNEL);
213 if (!ctx->ids)
214 return -ENOMEM;
215 ctx->num_slaves = num_slaves;
216 for (index = 0; index < ctx->count; index++) {
217 if (!(ctx->link_mask & BIT(index)))
218 continue;
219 amd_manager = dev_get_drvdata(dev: &ctx->pdev[index]->dev);
220 if (amd_manager) {
221 bus = &amd_manager->bus;
222 list_for_each_entry(slave, &bus->slaves, node) {
223 ctx->ids[i].id = slave->id;
224 ctx->ids[i].link_id = bus->link_id;
225 i++;
226 }
227 }
228 }
229 return 0;
230}
231EXPORT_SYMBOL_NS(sdw_amd_get_slave_info, SOUNDWIRE_AMD_INIT);
232
233MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
234MODULE_DESCRIPTION("AMD SoundWire Init Library");
235MODULE_LICENSE("Dual BSD/GPL");
236

source code of linux/drivers/soundwire/amd_init.c