Commit | Line | Data |
---|---|---|
7c3cd189 VK |
1 | // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) |
2 | // Copyright(c) 2015-17 Intel Corporation. | |
3 | ||
4 | #include <linux/acpi.h> | |
a2e48458 | 5 | #include <linux/of.h> |
7c3cd189 VK |
6 | #include <linux/soundwire/sdw.h> |
7 | #include <linux/soundwire/sdw_type.h> | |
8 | #include "bus.h" | |
0173f525 | 9 | #include "sysfs_local.h" |
7c3cd189 VK |
10 | |
11 | static void sdw_slave_release(struct device *dev) | |
12 | { | |
13 | struct sdw_slave *slave = dev_to_sdw_dev(dev); | |
14 | ||
bd29c00e | 15 | mutex_destroy(&slave->sdw_dev_lock); |
7c3cd189 VK |
16 | kfree(slave); |
17 | } | |
18 | ||
90acca1d PLB |
19 | struct device_type sdw_slave_type = { |
20 | .name = "sdw_slave", | |
21 | .release = sdw_slave_release, | |
22 | .uevent = sdw_slave_uevent, | |
23 | }; | |
24 | ||
fcb9d730 SK |
25 | int sdw_slave_add(struct sdw_bus *bus, |
26 | struct sdw_slave_id *id, struct fwnode_handle *fwnode) | |
7c3cd189 VK |
27 | { |
28 | struct sdw_slave *slave; | |
29 | int ret; | |
60737558 | 30 | int i; |
7c3cd189 VK |
31 | |
32 | slave = kzalloc(sizeof(*slave), GFP_KERNEL); | |
33 | if (!slave) | |
34 | return -ENOMEM; | |
35 | ||
36 | /* Initialize data structure */ | |
37 | memcpy(&slave->id, id, sizeof(*id)); | |
38 | slave->dev.parent = bus->dev; | |
39 | slave->dev.fwnode = fwnode; | |
40 | ||
2e8c4ad1 PLB |
41 | if (id->unique_id == SDW_IGNORED_UNIQUE_ID) { |
42 | /* name shall be sdw:link:mfg:part:class */ | |
9c294739 | 43 | dev_set_name(&slave->dev, "sdw:%01x:%04x:%04x:%02x", |
2e8c4ad1 PLB |
44 | bus->link_id, id->mfg_id, id->part_id, |
45 | id->class_id); | |
46 | } else { | |
47 | /* name shall be sdw:link:mfg:part:class:unique */ | |
9c294739 | 48 | dev_set_name(&slave->dev, "sdw:%01x:%04x:%04x:%02x:%01x", |
2e8c4ad1 PLB |
49 | bus->link_id, id->mfg_id, id->part_id, |
50 | id->class_id, id->unique_id); | |
51 | } | |
7c3cd189 | 52 | |
7c3cd189 | 53 | slave->dev.bus = &sdw_bus_type; |
a2e48458 | 54 | slave->dev.of_node = of_node_get(to_of_node(fwnode)); |
90acca1d | 55 | slave->dev.type = &sdw_slave_type; |
0173f525 | 56 | slave->dev.groups = sdw_slave_status_attr_groups; |
7c3cd189 VK |
57 | slave->bus = bus; |
58 | slave->status = SDW_SLAVE_UNATTACHED; | |
fb9469e5 | 59 | init_completion(&slave->enumeration_complete); |
a90def06 | 60 | init_completion(&slave->initialization_complete); |
7c3cd189 | 61 | slave->dev_num = 0; |
2140b66b | 62 | slave->probed = false; |
c2819e19 | 63 | slave->first_interrupt_done = false; |
bd29c00e | 64 | mutex_init(&slave->sdw_dev_lock); |
7c3cd189 | 65 | |
60737558 PLB |
66 | for (i = 0; i < SDW_MAX_PORTS; i++) |
67 | init_completion(&slave->port_ready[i]); | |
68 | ||
7c3cd189 VK |
69 | mutex_lock(&bus->bus_lock); |
70 | list_add_tail(&slave->node, &bus->slaves); | |
71 | mutex_unlock(&bus->bus_lock); | |
72 | ||
73 | ret = device_register(&slave->dev); | |
74 | if (ret) { | |
75 | dev_err(bus->dev, "Failed to add slave: ret %d\n", ret); | |
76 | ||
77 | /* | |
78 | * On err, don't free but drop ref as this will be freed | |
79 | * when release method is invoked. | |
80 | */ | |
81 | mutex_lock(&bus->bus_lock); | |
82 | list_del(&slave->node); | |
83 | mutex_unlock(&bus->bus_lock); | |
84 | put_device(&slave->dev); | |
8893ab5e PLB |
85 | |
86 | return ret; | |
7c3cd189 | 87 | } |
bf03473d | 88 | sdw_slave_debugfs_init(slave); |
7c3cd189 VK |
89 | |
90 | return ret; | |
91 | } | |
01ad444e | 92 | EXPORT_SYMBOL(sdw_slave_add); |
7c3cd189 VK |
93 | |
94 | #if IS_ENABLED(CONFIG_ACPI) | |
de5b174b PLB |
95 | |
96 | static bool find_slave(struct sdw_bus *bus, | |
97 | struct acpi_device *adev, | |
98 | struct sdw_slave_id *id) | |
99 | { | |
6558b667 | 100 | u64 addr; |
de5b174b PLB |
101 | unsigned int link_id; |
102 | acpi_status status; | |
103 | ||
104 | status = acpi_evaluate_integer(adev->handle, | |
105 | METHOD_NAME__ADR, NULL, &addr); | |
106 | ||
107 | if (ACPI_FAILURE(status)) { | |
108 | dev_err(bus->dev, "_ADR resolution failed: %x\n", | |
109 | status); | |
110 | return false; | |
111 | } | |
112 | ||
6558b667 VK |
113 | if (bus->ops->override_adr) |
114 | addr = bus->ops->override_adr(bus, addr); | |
115 | ||
116 | if (!addr) | |
117 | return false; | |
118 | ||
de5b174b | 119 | /* Extract link id from ADR, Bit 51 to 48 (included) */ |
bd6a024f | 120 | link_id = SDW_DISCO_LINK_ID(addr); |
de5b174b PLB |
121 | |
122 | /* Check for link_id match */ | |
123 | if (link_id != bus->link_id) | |
124 | return false; | |
125 | ||
126 | sdw_extract_slave_id(bus, addr, id); | |
127 | ||
128 | return true; | |
129 | } | |
130 | ||
9089d1a4 RW |
131 | struct sdw_acpi_child_walk_data { |
132 | struct sdw_bus *bus; | |
133 | struct acpi_device *adev; | |
134 | struct sdw_slave_id id; | |
135 | bool ignore_unique_id; | |
136 | }; | |
137 | ||
138 | static int sdw_acpi_check_duplicate(struct acpi_device *adev, void *data) | |
139 | { | |
140 | struct sdw_acpi_child_walk_data *cwd = data; | |
141 | struct sdw_bus *bus = cwd->bus; | |
142 | struct sdw_slave_id id; | |
143 | ||
144 | if (adev == cwd->adev) | |
145 | return 0; | |
146 | ||
147 | if (!find_slave(bus, adev, &id)) | |
148 | return 0; | |
149 | ||
150 | if (cwd->id.sdw_version != id.sdw_version || cwd->id.mfg_id != id.mfg_id || | |
151 | cwd->id.part_id != id.part_id || cwd->id.class_id != id.class_id) | |
152 | return 0; | |
153 | ||
154 | if (cwd->id.unique_id != id.unique_id) { | |
155 | dev_dbg(bus->dev, | |
156 | "Valid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n", | |
157 | cwd->id.unique_id, id.unique_id, cwd->id.mfg_id, | |
158 | cwd->id.part_id); | |
159 | cwd->ignore_unique_id = false; | |
160 | return 0; | |
161 | } | |
162 | ||
163 | dev_err(bus->dev, | |
164 | "Invalid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n", | |
165 | cwd->id.unique_id, id.unique_id, cwd->id.mfg_id, cwd->id.part_id); | |
166 | return -ENODEV; | |
167 | } | |
168 | ||
169 | static int sdw_acpi_find_one(struct acpi_device *adev, void *data) | |
170 | { | |
171 | struct sdw_bus *bus = data; | |
172 | struct sdw_acpi_child_walk_data cwd = { | |
173 | .bus = bus, | |
174 | .adev = adev, | |
175 | .ignore_unique_id = true, | |
176 | }; | |
177 | int ret; | |
178 | ||
179 | if (!find_slave(bus, adev, &cwd.id)) | |
180 | return 0; | |
181 | ||
182 | /* Brute-force O(N^2) search for duplicates. */ | |
183 | ret = acpi_dev_for_each_child(ACPI_COMPANION(bus->dev), | |
184 | sdw_acpi_check_duplicate, &cwd); | |
185 | if (ret) | |
186 | return ret; | |
187 | ||
188 | if (cwd.ignore_unique_id) | |
189 | cwd.id.unique_id = SDW_IGNORED_UNIQUE_ID; | |
190 | ||
191 | /* Ignore errors and continue. */ | |
192 | sdw_slave_add(bus, &cwd.id, acpi_fwnode_handle(adev)); | |
193 | return 0; | |
194 | } | |
195 | ||
7c3cd189 VK |
196 | /* |
197 | * sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node | |
198 | * @bus: SDW bus instance | |
199 | * | |
200 | * Scans Master ACPI node for SDW child Slave devices and registers it. | |
201 | */ | |
202 | int sdw_acpi_find_slaves(struct sdw_bus *bus) | |
203 | { | |
9089d1a4 | 204 | struct acpi_device *parent; |
7c3cd189 VK |
205 | |
206 | parent = ACPI_COMPANION(bus->dev); | |
207 | if (!parent) { | |
208 | dev_err(bus->dev, "Can't find parent for acpi bind\n"); | |
209 | return -ENODEV; | |
210 | } | |
211 | ||
9089d1a4 | 212 | return acpi_dev_for_each_child(parent, sdw_acpi_find_one, bus); |
7c3cd189 VK |
213 | } |
214 | ||
215 | #endif | |
a2e48458 SK |
216 | |
217 | /* | |
218 | * sdw_of_find_slaves() - Find Slave devices in master device tree node | |
219 | * @bus: SDW bus instance | |
220 | * | |
221 | * Scans Master DT node for SDW child Slave devices and registers it. | |
222 | */ | |
223 | int sdw_of_find_slaves(struct sdw_bus *bus) | |
224 | { | |
225 | struct device *dev = bus->dev; | |
226 | struct device_node *node; | |
227 | ||
228 | for_each_child_of_node(bus->dev->of_node, node) { | |
7b47ad33 PLB |
229 | int link_id, ret, len; |
230 | unsigned int sdw_version; | |
a2e48458 SK |
231 | const char *compat = NULL; |
232 | struct sdw_slave_id id; | |
233 | const __be32 *addr; | |
234 | ||
235 | compat = of_get_property(node, "compatible", NULL); | |
236 | if (!compat) | |
237 | continue; | |
238 | ||
239 | ret = sscanf(compat, "sdw%01x%04hx%04hx%02hhx", &sdw_version, | |
240 | &id.mfg_id, &id.part_id, &id.class_id); | |
241 | ||
242 | if (ret != 4) { | |
243 | dev_err(dev, "Invalid compatible string found %s\n", | |
244 | compat); | |
245 | continue; | |
246 | } | |
247 | ||
248 | addr = of_get_property(node, "reg", &len); | |
249 | if (!addr || (len < 2 * sizeof(u32))) { | |
250 | dev_err(dev, "Invalid Link and Instance ID\n"); | |
251 | continue; | |
252 | } | |
253 | ||
254 | link_id = be32_to_cpup(addr++); | |
255 | id.unique_id = be32_to_cpup(addr); | |
256 | id.sdw_version = sdw_version; | |
257 | ||
258 | /* Check for link_id match */ | |
259 | if (link_id != bus->link_id) | |
260 | continue; | |
261 | ||
262 | sdw_slave_add(bus, &id, of_fwnode_handle(node)); | |
263 | } | |
264 | ||
265 | return 0; | |
266 | } |