diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2011-03-15 02:53:21 -0400 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2011-03-15 03:42:22 -0400 |
commit | 31ef9134eb52636d383a7d0626cbbd345cb94f2f (patch) | |
tree | 5d994932a8773e844190cbea43ef31d67f605cf8 /sound/firewire/cmp.c | |
parent | a5abba989deceb731047425812d268daf7536575 (diff) |
ALSA: add LaCie FireWire Speakers/Griffin FireWave Surround driver
Add a driver for two playback-only FireWire devices based on the OXFW970
chip.
v2: better AMDTP API abstraction; fix fw_unit leak; small fixes
v3: cache the iPCR value
v4: FireWave constraints; fix fw_device reference counting;
fix PCR caching; small changes and fixes
v5: volume/mute support; fix crashing due to pcm stop races
v6: fix build; one-channel volume for LaCie
v7: use signed values to make volume (range checks) work; fix function
block IDs for volume/mute; always use channel 0 for LaCie volume
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Acked-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Tested-by: Jay Fenlason <fenlason@redhat.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/firewire/cmp.c')
-rw-r--r-- | sound/firewire/cmp.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c new file mode 100644 index 000000000000..c992dab4bb95 --- /dev/null +++ b/sound/firewire/cmp.c | |||
@@ -0,0 +1,305 @@ | |||
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 | c->connected = false; | ||
121 | mutex_init(&c->mutex); | ||
122 | fw_iso_resources_init(&c->resources, unit); | ||
123 | c->last_pcr_value = cpu_to_be32(0x80000000); | ||
124 | c->pcr_index = ipcr_index; | ||
125 | c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT; | ||
126 | if (c->max_speed == SCODE_BETA) | ||
127 | c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | EXPORT_SYMBOL(cmp_connection_init); | ||
132 | |||
133 | /** | ||
134 | * cmp_connection_destroy - free connection manager resources | ||
135 | * @c: the connection manager | ||
136 | */ | ||
137 | void cmp_connection_destroy(struct cmp_connection *c) | ||
138 | { | ||
139 | WARN_ON(c->connected); | ||
140 | mutex_destroy(&c->mutex); | ||
141 | fw_iso_resources_destroy(&c->resources); | ||
142 | } | ||
143 | EXPORT_SYMBOL(cmp_connection_destroy); | ||
144 | |||
145 | |||
146 | static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) | ||
147 | { | ||
148 | ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN | | ||
149 | IPCR_P2P_CONN_MASK | | ||
150 | IPCR_CHANNEL_MASK); | ||
151 | ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT); | ||
152 | ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); | ||
153 | |||
154 | return ipcr; | ||
155 | } | ||
156 | |||
157 | static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) | ||
158 | { | ||
159 | if (ipcr & cpu_to_be32(IPCR_BCAST_CONN | | ||
160 | IPCR_P2P_CONN_MASK)) { | ||
161 | cmp_error(c, "plug is already in use\n"); | ||
162 | return -EBUSY; | ||
163 | } | ||
164 | if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) { | ||
165 | cmp_error(c, "plug is not on-line\n"); | ||
166 | return -ECONNREFUSED; | ||
167 | } | ||
168 | |||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * cmp_connection_establish - establish a connection to the target | ||
174 | * @c: the connection manager | ||
175 | * @max_payload_bytes: the amount of data (including CIP headers) per packet | ||
176 | * | ||
177 | * This function establishes a point-to-point connection from the local | ||
178 | * computer to the target by allocating isochronous resources (channel and | ||
179 | * bandwidth) and setting the target's input plug control register. When this | ||
180 | * function succeeds, the caller is responsible for starting transmitting | ||
181 | * packets. | ||
182 | */ | ||
183 | int cmp_connection_establish(struct cmp_connection *c, | ||
184 | unsigned int max_payload_bytes) | ||
185 | { | ||
186 | int err; | ||
187 | |||
188 | if (WARN_ON(c->connected)) | ||
189 | return -EISCONN; | ||
190 | |||
191 | c->speed = min(c->max_speed, | ||
192 | fw_parent_device(c->resources.unit)->max_speed); | ||
193 | |||
194 | mutex_lock(&c->mutex); | ||
195 | |||
196 | retry_after_bus_reset: | ||
197 | err = fw_iso_resources_allocate(&c->resources, | ||
198 | max_payload_bytes, c->speed); | ||
199 | if (err < 0) | ||
200 | goto err_mutex; | ||
201 | |||
202 | err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, | ||
203 | ABORT_ON_BUS_RESET); | ||
204 | if (err == -EAGAIN) { | ||
205 | fw_iso_resources_free(&c->resources); | ||
206 | goto retry_after_bus_reset; | ||
207 | } | ||
208 | if (err < 0) | ||
209 | goto err_resources; | ||
210 | |||
211 | c->connected = true; | ||
212 | |||
213 | mutex_unlock(&c->mutex); | ||
214 | |||
215 | return 0; | ||
216 | |||
217 | err_resources: | ||
218 | fw_iso_resources_free(&c->resources); | ||
219 | err_mutex: | ||
220 | mutex_unlock(&c->mutex); | ||
221 | |||
222 | return err; | ||
223 | } | ||
224 | EXPORT_SYMBOL(cmp_connection_establish); | ||
225 | |||
226 | /** | ||
227 | * cmp_connection_update - update the connection after a bus reset | ||
228 | * @c: the connection manager | ||
229 | * | ||
230 | * This function must be called from the driver's .update handler to reestablish | ||
231 | * any connection that might have been active. | ||
232 | * | ||
233 | * Returns zero on success, or a negative error code. On an error, the | ||
234 | * connection is broken and the caller must stop transmitting iso packets. | ||
235 | */ | ||
236 | int cmp_connection_update(struct cmp_connection *c) | ||
237 | { | ||
238 | int err; | ||
239 | |||
240 | mutex_lock(&c->mutex); | ||
241 | |||
242 | if (!c->connected) { | ||
243 | mutex_unlock(&c->mutex); | ||
244 | return 0; | ||
245 | } | ||
246 | |||
247 | err = fw_iso_resources_update(&c->resources); | ||
248 | if (err < 0) | ||
249 | goto err_unconnect; | ||
250 | |||
251 | err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, | ||
252 | SUCCEED_ON_BUS_RESET); | ||
253 | if (err < 0) | ||
254 | goto err_resources; | ||
255 | |||
256 | mutex_unlock(&c->mutex); | ||
257 | |||
258 | return 0; | ||
259 | |||
260 | err_resources: | ||
261 | fw_iso_resources_free(&c->resources); | ||
262 | err_unconnect: | ||
263 | c->connected = false; | ||
264 | mutex_unlock(&c->mutex); | ||
265 | |||
266 | return err; | ||
267 | } | ||
268 | EXPORT_SYMBOL(cmp_connection_update); | ||
269 | |||
270 | |||
271 | static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr) | ||
272 | { | ||
273 | return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK); | ||
274 | } | ||
275 | |||
276 | /** | ||
277 | * cmp_connection_break - break the connection to the target | ||
278 | * @c: the connection manager | ||
279 | * | ||
280 | * This function deactives the connection in the target's input plug control | ||
281 | * register, and frees the isochronous resources of the connection. Before | ||
282 | * calling this function, the caller should cease transmitting packets. | ||
283 | */ | ||
284 | void cmp_connection_break(struct cmp_connection *c) | ||
285 | { | ||
286 | int err; | ||
287 | |||
288 | mutex_lock(&c->mutex); | ||
289 | |||
290 | if (!c->connected) { | ||
291 | mutex_unlock(&c->mutex); | ||
292 | return; | ||
293 | } | ||
294 | |||
295 | err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); | ||
296 | if (err < 0) | ||
297 | cmp_error(c, "plug is still connected\n"); | ||
298 | |||
299 | fw_iso_resources_free(&c->resources); | ||
300 | |||
301 | c->connected = false; | ||
302 | |||
303 | mutex_unlock(&c->mutex); | ||
304 | } | ||
305 | EXPORT_SYMBOL(cmp_connection_break); | ||