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