diff options
Diffstat (limited to 'sound/firewire/cmp.c')
-rw-r--r-- | sound/firewire/cmp.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c new file mode 100644 index 000000000000..4a37f3a6fab9 --- /dev/null +++ b/sound/firewire/cmp.c | |||
@@ -0,0 +1,308 @@ | |||
1 | /* | ||
2 | * Connection Management Procedures (IEC 61883-1) helper functions | ||
3 | * | ||
4 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> | ||
5 | * Licensed under the terms of the GNU General Public License, version 2. | ||
6 | */ | ||
7 | |||
8 | #include <linux/device.h> | ||
9 | #include <linux/firewire.h> | ||
10 | #include <linux/firewire-constants.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/sched.h> | ||
13 | #include "lib.h" | ||
14 | #include "iso-resources.h" | ||
15 | #include "cmp.h" | ||
16 | |||
17 | #define IMPR_SPEED_MASK 0xc0000000 | ||
18 | #define IMPR_SPEED_SHIFT 30 | ||
19 | #define IMPR_XSPEED_MASK 0x00000060 | ||
20 | #define IMPR_XSPEED_SHIFT 5 | ||
21 | #define IMPR_PLUGS_MASK 0x0000001f | ||
22 | |||
23 | #define IPCR_ONLINE 0x80000000 | ||
24 | #define IPCR_BCAST_CONN 0x40000000 | ||
25 | #define IPCR_P2P_CONN_MASK 0x3f000000 | ||
26 | #define IPCR_P2P_CONN_SHIFT 24 | ||
27 | #define IPCR_CHANNEL_MASK 0x003f0000 | ||
28 | #define IPCR_CHANNEL_SHIFT 16 | ||
29 | |||
30 | enum bus_reset_handling { | ||
31 | ABORT_ON_BUS_RESET, | ||
32 | SUCCEED_ON_BUS_RESET, | ||
33 | }; | ||
34 | |||
35 | static __attribute__((format(printf, 2, 3))) | ||
36 | void cmp_error(struct cmp_connection *c, const char *fmt, ...) | ||
37 | { | ||
38 | va_list va; | ||
39 | |||
40 | va_start(va, fmt); | ||
41 | dev_err(&c->resources.unit->device, "%cPCR%u: %pV", | ||
42 | 'i', c->pcr_index, &(struct va_format){ fmt, &va }); | ||
43 | va_end(va); | ||
44 | } | ||
45 | |||
46 | static int pcr_modify(struct cmp_connection *c, | ||
47 | __be32 (*modify)(struct cmp_connection *c, __be32 old), | ||
48 | int (*check)(struct cmp_connection *c, __be32 pcr), | ||
49 | enum bus_reset_handling bus_reset_handling) | ||
50 | { | ||
51 | struct fw_device *device = fw_parent_device(c->resources.unit); | ||
52 | __be32 *buffer = c->resources.buffer; | ||
53 | int generation = c->resources.generation; | ||
54 | int rcode, errors = 0; | ||
55 | __be32 old_arg; | ||
56 | int err; | ||
57 | |||
58 | buffer[0] = c->last_pcr_value; | ||
59 | for (;;) { | ||
60 | old_arg = buffer[0]; | ||
61 | buffer[1] = modify(c, buffer[0]); | ||
62 | |||
63 | rcode = fw_run_transaction( | ||
64 | device->card, TCODE_LOCK_COMPARE_SWAP, | ||
65 | device->node_id, generation, device->max_speed, | ||
66 | CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index), | ||
67 | buffer, 8); | ||
68 | |||
69 | if (rcode == RCODE_COMPLETE) { | ||
70 | if (buffer[0] == old_arg) /* success? */ | ||
71 | break; | ||
72 | |||
73 | if (check) { | ||
74 | err = check(c, buffer[0]); | ||
75 | if (err < 0) | ||
76 | return err; | ||
77 | } | ||
78 | } else if (rcode == RCODE_GENERATION) | ||
79 | goto bus_reset; | ||
80 | else if (rcode_is_permanent_error(rcode) || ++errors >= 3) | ||
81 | goto io_error; | ||
82 | } | ||
83 | c->last_pcr_value = buffer[1]; | ||
84 | |||
85 | return 0; | ||
86 | |||
87 | io_error: | ||
88 | cmp_error(c, "transaction failed: %s\n", rcode_string(rcode)); | ||
89 | return -EIO; | ||
90 | |||
91 | bus_reset: | ||
92 | return bus_reset_handling == ABORT_ON_BUS_RESET ? -EAGAIN : 0; | ||
93 | } | ||
94 | |||
95 | |||
96 | /** | ||
97 | * cmp_connection_init - initializes a connection manager | ||
98 | * @c: the connection manager to initialize | ||
99 | * @unit: a unit of the target device | ||
100 | * @ipcr_index: the index of the iPCR on the target device | ||
101 | */ | ||
102 | int cmp_connection_init(struct cmp_connection *c, | ||
103 | struct fw_unit *unit, | ||
104 | unsigned int ipcr_index) | ||
105 | { | ||
106 | __be32 impr_be; | ||
107 | u32 impr; | ||
108 | int err; | ||
109 | |||
110 | err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, | ||
111 | CSR_REGISTER_BASE + CSR_IMPR, | ||
112 | &impr_be, 4); | ||
113 | if (err < 0) | ||
114 | return err; | ||
115 | impr = be32_to_cpu(impr_be); | ||
116 | |||
117 | if (ipcr_index >= (impr & IMPR_PLUGS_MASK)) | ||
118 | return -EINVAL; | ||
119 | |||
120 | err = fw_iso_resources_init(&c->resources, unit); | ||
121 | if (err < 0) | ||
122 | return err; | ||
123 | |||
124 | c->connected = false; | ||
125 | mutex_init(&c->mutex); | ||
126 | c->last_pcr_value = cpu_to_be32(0x80000000); | ||
127 | c->pcr_index = ipcr_index; | ||
128 | c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT; | ||
129 | if (c->max_speed == SCODE_BETA) | ||
130 | c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; | ||
131 | |||
132 | return 0; | ||
133 | } | ||
134 | EXPORT_SYMBOL(cmp_connection_init); | ||
135 | |||
136 | /** | ||
137 | * cmp_connection_destroy - free connection manager resources | ||
138 | * @c: the connection manager | ||
139 | */ | ||
140 | void cmp_connection_destroy(struct cmp_connection *c) | ||
141 | { | ||
142 | WARN_ON(c->connected); | ||
143 | mutex_destroy(&c->mutex); | ||
144 | fw_iso_resources_destroy(&c->resources); | ||
145 | } | ||
146 | EXPORT_SYMBOL(cmp_connection_destroy); | ||
147 | |||
148 | |||
149 | static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) | ||
150 | { | ||
151 | ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN | | ||
152 | IPCR_P2P_CONN_MASK | | ||
153 | IPCR_CHANNEL_MASK); | ||
154 | ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT); | ||
155 | ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); | ||
156 | |||
157 | return ipcr; | ||
158 | } | ||
159 | |||
160 | static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) | ||
161 | { | ||
162 | if (ipcr & cpu_to_be32(IPCR_BCAST_CONN | | ||
163 | IPCR_P2P_CONN_MASK)) { | ||
164 | cmp_error(c, "plug is already in use\n"); | ||
165 | return -EBUSY; | ||
166 | } | ||
167 | if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) { | ||
168 | cmp_error(c, "plug is not on-line\n"); | ||
169 | return -ECONNREFUSED; | ||
170 | } | ||
171 | |||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * cmp_connection_establish - establish a connection to the target | ||
177 | * @c: the connection manager | ||
178 | * @max_payload_bytes: the amount of data (including CIP headers) per packet | ||
179 | * | ||
180 | * This function establishes a point-to-point connection from the local | ||
181 | * computer to the target by allocating isochronous resources (channel and | ||
182 | * bandwidth) and setting the target's input plug control register. When this | ||
183 | * function succeeds, the caller is responsible for starting transmitting | ||
184 | * packets. | ||
185 | */ | ||
186 | int cmp_connection_establish(struct cmp_connection *c, | ||
187 | unsigned int max_payload_bytes) | ||
188 | { | ||
189 | int err; | ||
190 | |||
191 | if (WARN_ON(c->connected)) | ||
192 | return -EISCONN; | ||
193 | |||
194 | c->speed = min(c->max_speed, | ||
195 | fw_parent_device(c->resources.unit)->max_speed); | ||
196 | |||
197 | mutex_lock(&c->mutex); | ||
198 | |||
199 | retry_after_bus_reset: | ||
200 | err = fw_iso_resources_allocate(&c->resources, | ||
201 | max_payload_bytes, c->speed); | ||
202 | if (err < 0) | ||
203 | goto err_mutex; | ||
204 | |||
205 | err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, | ||
206 | ABORT_ON_BUS_RESET); | ||
207 | if (err == -EAGAIN) { | ||
208 | fw_iso_resources_free(&c->resources); | ||
209 | goto retry_after_bus_reset; | ||
210 | } | ||
211 | if (err < 0) | ||
212 | goto err_resources; | ||
213 | |||
214 | c->connected = true; | ||
215 | |||
216 | mutex_unlock(&c->mutex); | ||
217 | |||
218 | return 0; | ||
219 | |||
220 | err_resources: | ||
221 | fw_iso_resources_free(&c->resources); | ||
222 | err_mutex: | ||
223 | mutex_unlock(&c->mutex); | ||
224 | |||
225 | return err; | ||
226 | } | ||
227 | EXPORT_SYMBOL(cmp_connection_establish); | ||
228 | |||
229 | /** | ||
230 | * cmp_connection_update - update the connection after a bus reset | ||
231 | * @c: the connection manager | ||
232 | * | ||
233 | * This function must be called from the driver's .update handler to reestablish | ||
234 | * any connection that might have been active. | ||
235 | * | ||
236 | * Returns zero on success, or a negative error code. On an error, the | ||
237 | * connection is broken and the caller must stop transmitting iso packets. | ||
238 | */ | ||
239 | int cmp_connection_update(struct cmp_connection *c) | ||
240 | { | ||
241 | int err; | ||
242 | |||
243 | mutex_lock(&c->mutex); | ||
244 | |||
245 | if (!c->connected) { | ||
246 | mutex_unlock(&c->mutex); | ||
247 | return 0; | ||
248 | } | ||
249 | |||
250 | err = fw_iso_resources_update(&c->resources); | ||
251 | if (err < 0) | ||
252 | goto err_unconnect; | ||
253 | |||
254 | err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, | ||
255 | SUCCEED_ON_BUS_RESET); | ||
256 | if (err < 0) | ||
257 | goto err_resources; | ||
258 | |||
259 | mutex_unlock(&c->mutex); | ||
260 | |||
261 | return 0; | ||
262 | |||
263 | err_resources: | ||
264 | fw_iso_resources_free(&c->resources); | ||
265 | err_unconnect: | ||
266 | c->connected = false; | ||
267 | mutex_unlock(&c->mutex); | ||
268 | |||
269 | return err; | ||
270 | } | ||
271 | EXPORT_SYMBOL(cmp_connection_update); | ||
272 | |||
273 | |||
274 | static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr) | ||
275 | { | ||
276 | return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK); | ||
277 | } | ||
278 | |||
279 | /** | ||
280 | * cmp_connection_break - break the connection to the target | ||
281 | * @c: the connection manager | ||
282 | * | ||
283 | * This function deactives the connection in the target's input plug control | ||
284 | * register, and frees the isochronous resources of the connection. Before | ||
285 | * calling this function, the caller should cease transmitting packets. | ||
286 | */ | ||
287 | void cmp_connection_break(struct cmp_connection *c) | ||
288 | { | ||
289 | int err; | ||
290 | |||
291 | mutex_lock(&c->mutex); | ||
292 | |||
293 | if (!c->connected) { | ||
294 | mutex_unlock(&c->mutex); | ||
295 | return; | ||
296 | } | ||
297 | |||
298 | err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); | ||
299 | if (err < 0) | ||
300 | cmp_error(c, "plug is still connected\n"); | ||
301 | |||
302 | fw_iso_resources_free(&c->resources); | ||
303 | |||
304 | c->connected = false; | ||
305 | |||
306 | mutex_unlock(&c->mutex); | ||
307 | } | ||
308 | EXPORT_SYMBOL(cmp_connection_break); | ||