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