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 | |
27 | static 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 | |
59 | static 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 | |
72 | static 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; |
141 | err: |
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 | |
154 | static 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 | |
172 | int 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 | } |
180 | EXPORT_SYMBOL_NS(sdw_amd_probe, SOUNDWIRE_AMD_INIT); |
181 | |
182 | void sdw_amd_exit(struct sdw_amd_ctx *ctx) |
183 | { |
184 | sdw_amd_cleanup(ctx); |
185 | kfree(objp: ctx->ids); |
186 | kfree(objp: ctx); |
187 | } |
188 | EXPORT_SYMBOL_NS(sdw_amd_exit, SOUNDWIRE_AMD_INIT); |
189 | |
190 | int 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 | } |
231 | EXPORT_SYMBOL_NS(sdw_amd_get_slave_info, SOUNDWIRE_AMD_INIT); |
232 | |
233 | MODULE_AUTHOR("Vijendar.Mukunda@amd.com" ); |
234 | MODULE_DESCRIPTION("AMD SoundWire Init Library" ); |
235 | MODULE_LICENSE("Dual BSD/GPL" ); |
236 | |