diff options
Diffstat (limited to 'drivers/media/dvb/firewire/firedtv-fw.c')
-rw-r--r-- | drivers/media/dvb/firewire/firedtv-fw.c | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/drivers/media/dvb/firewire/firedtv-fw.c b/drivers/media/dvb/firewire/firedtv-fw.c new file mode 100644 index 000000000000..208e5b59e830 --- /dev/null +++ b/drivers/media/dvb/firewire/firedtv-fw.c | |||
@@ -0,0 +1,385 @@ | |||
1 | /* | ||
2 | * FireDTV driver -- firewire I/O backend | ||
3 | */ | ||
4 | |||
5 | #include <linux/device.h> | ||
6 | #include <linux/errno.h> | ||
7 | #include <linux/firewire.h> | ||
8 | #include <linux/firewire-constants.h> | ||
9 | #include <linux/highmem.h> | ||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/list.h> | ||
12 | #include <linux/slab.h> | ||
13 | #include <linux/spinlock.h> | ||
14 | #include <linux/types.h> | ||
15 | |||
16 | #include <asm/page.h> | ||
17 | |||
18 | #include <dvb_demux.h> | ||
19 | |||
20 | #include "firedtv.h" | ||
21 | |||
22 | static LIST_HEAD(node_list); | ||
23 | static DEFINE_SPINLOCK(node_list_lock); | ||
24 | |||
25 | static inline struct fw_device *device_of(struct firedtv *fdtv) | ||
26 | { | ||
27 | return fw_device(fdtv->device->parent); | ||
28 | } | ||
29 | |||
30 | static int node_req(struct firedtv *fdtv, u64 addr, void *data, size_t len, | ||
31 | int tcode) | ||
32 | { | ||
33 | struct fw_device *device = device_of(fdtv); | ||
34 | int rcode, generation = device->generation; | ||
35 | |||
36 | smp_rmb(); /* node_id vs. generation */ | ||
37 | |||
38 | rcode = fw_run_transaction(device->card, tcode, device->node_id, | ||
39 | generation, device->max_speed, addr, data, len); | ||
40 | |||
41 | return rcode != RCODE_COMPLETE ? -EIO : 0; | ||
42 | } | ||
43 | |||
44 | static int node_lock(struct firedtv *fdtv, u64 addr, __be32 data[]) | ||
45 | { | ||
46 | return node_req(fdtv, addr, data, 8, TCODE_LOCK_COMPARE_SWAP); | ||
47 | } | ||
48 | |||
49 | static int node_read(struct firedtv *fdtv, u64 addr, void *data, size_t len) | ||
50 | { | ||
51 | return node_req(fdtv, addr, data, len, len == 4 ? | ||
52 | TCODE_READ_QUADLET_REQUEST : TCODE_READ_BLOCK_REQUEST); | ||
53 | } | ||
54 | |||
55 | static int node_write(struct firedtv *fdtv, u64 addr, void *data, size_t len) | ||
56 | { | ||
57 | return node_req(fdtv, addr, data, len, TCODE_WRITE_BLOCK_REQUEST); | ||
58 | } | ||
59 | |||
60 | #define ISO_HEADER_SIZE 4 | ||
61 | #define CIP_HEADER_SIZE 8 | ||
62 | #define MPEG2_TS_HEADER_SIZE 4 | ||
63 | #define MPEG2_TS_SOURCE_PACKET_SIZE (4 + 188) | ||
64 | |||
65 | #define MAX_PACKET_SIZE 1024 /* 776, rounded up to 2^n */ | ||
66 | #define PACKETS_PER_PAGE (PAGE_SIZE / MAX_PACKET_SIZE) | ||
67 | #define N_PACKETS 64 /* buffer size */ | ||
68 | #define N_PAGES DIV_ROUND_UP(N_PACKETS, PACKETS_PER_PAGE) | ||
69 | #define IRQ_INTERVAL 16 | ||
70 | |||
71 | struct firedtv_receive_context { | ||
72 | struct fw_iso_context *context; | ||
73 | struct fw_iso_buffer buffer; | ||
74 | int interrupt_packet; | ||
75 | int current_packet; | ||
76 | char *packets[N_PACKETS]; | ||
77 | }; | ||
78 | |||
79 | static int queue_iso(struct firedtv_receive_context *ctx, int index) | ||
80 | { | ||
81 | struct fw_iso_packet p; | ||
82 | int err; | ||
83 | |||
84 | p.payload_length = MAX_PACKET_SIZE; | ||
85 | p.interrupt = !(ctx->interrupt_packet & (IRQ_INTERVAL - 1)); | ||
86 | p.skip = 0; | ||
87 | p.header_length = ISO_HEADER_SIZE; | ||
88 | |||
89 | err = fw_iso_context_queue(ctx->context, &p, &ctx->buffer, | ||
90 | index * MAX_PACKET_SIZE); | ||
91 | if (!err) | ||
92 | ctx->interrupt_packet++; | ||
93 | |||
94 | return err; | ||
95 | } | ||
96 | |||
97 | static void handle_iso(struct fw_iso_context *context, u32 cycle, | ||
98 | size_t header_length, void *header, void *data) | ||
99 | { | ||
100 | struct firedtv *fdtv = data; | ||
101 | struct firedtv_receive_context *ctx = fdtv->backend_data; | ||
102 | __be32 *h, *h_end; | ||
103 | int i = ctx->current_packet, length, err; | ||
104 | char *p, *p_end; | ||
105 | |||
106 | for (h = header, h_end = h + header_length / 4; h < h_end; h++) { | ||
107 | length = be32_to_cpup(h) >> 16; | ||
108 | if (unlikely(length > MAX_PACKET_SIZE)) { | ||
109 | dev_err(fdtv->device, "length = %d\n", length); | ||
110 | length = MAX_PACKET_SIZE; | ||
111 | } | ||
112 | |||
113 | p = ctx->packets[i]; | ||
114 | p_end = p + length; | ||
115 | |||
116 | for (p += CIP_HEADER_SIZE + MPEG2_TS_HEADER_SIZE; p < p_end; | ||
117 | p += MPEG2_TS_SOURCE_PACKET_SIZE) | ||
118 | dvb_dmx_swfilter_packets(&fdtv->demux, p, 1); | ||
119 | |||
120 | err = queue_iso(ctx, i); | ||
121 | if (unlikely(err)) | ||
122 | dev_err(fdtv->device, "requeue failed\n"); | ||
123 | |||
124 | i = (i + 1) & (N_PACKETS - 1); | ||
125 | } | ||
126 | ctx->current_packet = i; | ||
127 | } | ||
128 | |||
129 | static int start_iso(struct firedtv *fdtv) | ||
130 | { | ||
131 | struct firedtv_receive_context *ctx; | ||
132 | struct fw_device *device = device_of(fdtv); | ||
133 | char *p; | ||
134 | int i, j, k, err; | ||
135 | |||
136 | ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); | ||
137 | if (!ctx) | ||
138 | return -ENOMEM; | ||
139 | |||
140 | ctx->context = fw_iso_context_create(device->card, | ||
141 | FW_ISO_CONTEXT_RECEIVE, fdtv->isochannel, | ||
142 | device->max_speed, ISO_HEADER_SIZE, handle_iso, fdtv); | ||
143 | if (IS_ERR(ctx->context)) { | ||
144 | err = PTR_ERR(ctx->context); | ||
145 | goto fail_free; | ||
146 | } | ||
147 | |||
148 | err = fw_iso_buffer_init(&ctx->buffer, device->card, | ||
149 | N_PAGES, DMA_FROM_DEVICE); | ||
150 | if (err) | ||
151 | goto fail_context_destroy; | ||
152 | |||
153 | ctx->interrupt_packet = 1; | ||
154 | ctx->current_packet = 0; | ||
155 | |||
156 | for (i = 0, k = 0; k < N_PAGES; k++) { | ||
157 | p = kmap(ctx->buffer.pages[k]); | ||
158 | for (j = 0; j < PACKETS_PER_PAGE && i < N_PACKETS; j++, i++) | ||
159 | ctx->packets[i] = p + j * MAX_PACKET_SIZE; | ||
160 | } | ||
161 | |||
162 | for (i = 0; i < N_PACKETS; i++) { | ||
163 | err = queue_iso(ctx, i); | ||
164 | if (err) | ||
165 | goto fail; | ||
166 | } | ||
167 | |||
168 | err = fw_iso_context_start(ctx->context, -1, 0, | ||
169 | FW_ISO_CONTEXT_MATCH_ALL_TAGS); | ||
170 | if (err) | ||
171 | goto fail; | ||
172 | |||
173 | fdtv->backend_data = ctx; | ||
174 | |||
175 | return 0; | ||
176 | fail: | ||
177 | fw_iso_buffer_destroy(&ctx->buffer, device->card); | ||
178 | fail_context_destroy: | ||
179 | fw_iso_context_destroy(ctx->context); | ||
180 | fail_free: | ||
181 | kfree(ctx); | ||
182 | |||
183 | return err; | ||
184 | } | ||
185 | |||
186 | static void stop_iso(struct firedtv *fdtv) | ||
187 | { | ||
188 | struct firedtv_receive_context *ctx = fdtv->backend_data; | ||
189 | |||
190 | fw_iso_context_stop(ctx->context); | ||
191 | fw_iso_buffer_destroy(&ctx->buffer, device_of(fdtv)->card); | ||
192 | fw_iso_context_destroy(ctx->context); | ||
193 | kfree(ctx); | ||
194 | } | ||
195 | |||
196 | static const struct firedtv_backend backend = { | ||
197 | .lock = node_lock, | ||
198 | .read = node_read, | ||
199 | .write = node_write, | ||
200 | .start_iso = start_iso, | ||
201 | .stop_iso = stop_iso, | ||
202 | }; | ||
203 | |||
204 | static void handle_fcp(struct fw_card *card, struct fw_request *request, | ||
205 | int tcode, int destination, int source, int generation, | ||
206 | int speed, unsigned long long offset, | ||
207 | void *payload, size_t length, void *callback_data) | ||
208 | { | ||
209 | struct firedtv *f, *fdtv = NULL; | ||
210 | struct fw_device *device; | ||
211 | unsigned long flags; | ||
212 | int su; | ||
213 | |||
214 | if ((tcode != TCODE_WRITE_QUADLET_REQUEST && | ||
215 | tcode != TCODE_WRITE_BLOCK_REQUEST) || | ||
216 | offset != CSR_REGISTER_BASE + CSR_FCP_RESPONSE || | ||
217 | length == 0 || | ||
218 | (((u8 *)payload)[0] & 0xf0) != 0) { | ||
219 | fw_send_response(card, request, RCODE_TYPE_ERROR); | ||
220 | return; | ||
221 | } | ||
222 | |||
223 | su = ((u8 *)payload)[1] & 0x7; | ||
224 | |||
225 | spin_lock_irqsave(&node_list_lock, flags); | ||
226 | list_for_each_entry(f, &node_list, list) { | ||
227 | device = device_of(f); | ||
228 | if (device->generation != generation) | ||
229 | continue; | ||
230 | |||
231 | smp_rmb(); /* node_id vs. generation */ | ||
232 | |||
233 | if (device->card == card && | ||
234 | device->node_id == source && | ||
235 | (f->subunit == su || (f->subunit == 0 && su == 0x7))) { | ||
236 | fdtv = f; | ||
237 | break; | ||
238 | } | ||
239 | } | ||
240 | spin_unlock_irqrestore(&node_list_lock, flags); | ||
241 | |||
242 | if (fdtv) { | ||
243 | avc_recv(fdtv, payload, length); | ||
244 | fw_send_response(card, request, RCODE_COMPLETE); | ||
245 | } | ||
246 | } | ||
247 | |||
248 | static struct fw_address_handler fcp_handler = { | ||
249 | .length = CSR_FCP_END - CSR_FCP_RESPONSE, | ||
250 | .address_callback = handle_fcp, | ||
251 | }; | ||
252 | |||
253 | static const struct fw_address_region fcp_region = { | ||
254 | .start = CSR_REGISTER_BASE + CSR_FCP_RESPONSE, | ||
255 | .end = CSR_REGISTER_BASE + CSR_FCP_END, | ||
256 | }; | ||
257 | |||
258 | /* Adjust the template string if models with longer names appear. */ | ||
259 | #define MAX_MODEL_NAME_LEN ((int)DIV_ROUND_UP(sizeof("FireDTV ????"), 4)) | ||
260 | |||
261 | static size_t model_name(u32 *directory, __be32 *buffer) | ||
262 | { | ||
263 | struct fw_csr_iterator ci; | ||
264 | int i, length, key, value, last_key = 0; | ||
265 | u32 *block = NULL; | ||
266 | |||
267 | fw_csr_iterator_init(&ci, directory); | ||
268 | while (fw_csr_iterator_next(&ci, &key, &value)) { | ||
269 | if (last_key == CSR_MODEL && | ||
270 | key == (CSR_DESCRIPTOR | CSR_LEAF)) | ||
271 | block = ci.p - 1 + value; | ||
272 | last_key = key; | ||
273 | } | ||
274 | |||
275 | if (block == NULL) | ||
276 | return 0; | ||
277 | |||
278 | length = min((int)(block[0] >> 16) - 2, MAX_MODEL_NAME_LEN); | ||
279 | if (length <= 0) | ||
280 | return 0; | ||
281 | |||
282 | /* fast-forward to text string */ | ||
283 | block += 3; | ||
284 | |||
285 | for (i = 0; i < length; i++) | ||
286 | buffer[i] = cpu_to_be32(block[i]); | ||
287 | |||
288 | return length * 4; | ||
289 | } | ||
290 | |||
291 | static int node_probe(struct device *dev) | ||
292 | { | ||
293 | struct firedtv *fdtv; | ||
294 | __be32 name[MAX_MODEL_NAME_LEN]; | ||
295 | int name_len, err; | ||
296 | |||
297 | name_len = model_name(fw_unit(dev)->directory, name); | ||
298 | |||
299 | fdtv = fdtv_alloc(dev, &backend, (char *)name, name_len); | ||
300 | if (!fdtv) | ||
301 | return -ENOMEM; | ||
302 | |||
303 | err = fdtv_register_rc(fdtv, dev); | ||
304 | if (err) | ||
305 | goto fail_free; | ||
306 | |||
307 | spin_lock_irq(&node_list_lock); | ||
308 | list_add_tail(&fdtv->list, &node_list); | ||
309 | spin_unlock_irq(&node_list_lock); | ||
310 | |||
311 | err = avc_identify_subunit(fdtv); | ||
312 | if (err) | ||
313 | goto fail; | ||
314 | |||
315 | err = fdtv_dvb_register(fdtv); | ||
316 | if (err) | ||
317 | goto fail; | ||
318 | |||
319 | avc_register_remote_control(fdtv); | ||
320 | |||
321 | return 0; | ||
322 | fail: | ||
323 | spin_lock_irq(&node_list_lock); | ||
324 | list_del(&fdtv->list); | ||
325 | spin_unlock_irq(&node_list_lock); | ||
326 | fdtv_unregister_rc(fdtv); | ||
327 | fail_free: | ||
328 | kfree(fdtv); | ||
329 | |||
330 | return err; | ||
331 | } | ||
332 | |||
333 | static int node_remove(struct device *dev) | ||
334 | { | ||
335 | struct firedtv *fdtv = dev_get_drvdata(dev); | ||
336 | |||
337 | fdtv_dvb_unregister(fdtv); | ||
338 | |||
339 | spin_lock_irq(&node_list_lock); | ||
340 | list_del(&fdtv->list); | ||
341 | spin_unlock_irq(&node_list_lock); | ||
342 | |||
343 | fdtv_unregister_rc(fdtv); | ||
344 | |||
345 | kfree(fdtv); | ||
346 | return 0; | ||
347 | } | ||
348 | |||
349 | static void node_update(struct fw_unit *unit) | ||
350 | { | ||
351 | struct firedtv *fdtv = dev_get_drvdata(&unit->device); | ||
352 | |||
353 | if (fdtv->isochannel >= 0) | ||
354 | cmp_establish_pp_connection(fdtv, fdtv->subunit, | ||
355 | fdtv->isochannel); | ||
356 | } | ||
357 | |||
358 | static struct fw_driver fdtv_driver = { | ||
359 | .driver = { | ||
360 | .owner = THIS_MODULE, | ||
361 | .name = "firedtv", | ||
362 | .bus = &fw_bus_type, | ||
363 | .probe = node_probe, | ||
364 | .remove = node_remove, | ||
365 | }, | ||
366 | .update = node_update, | ||
367 | .id_table = fdtv_id_table, | ||
368 | }; | ||
369 | |||
370 | int __init fdtv_fw_init(void) | ||
371 | { | ||
372 | int ret; | ||
373 | |||
374 | ret = fw_core_add_address_handler(&fcp_handler, &fcp_region); | ||
375 | if (ret < 0) | ||
376 | return ret; | ||
377 | |||
378 | return driver_register(&fdtv_driver.driver); | ||
379 | } | ||
380 | |||
381 | void fdtv_fw_exit(void) | ||
382 | { | ||
383 | driver_unregister(&fdtv_driver.driver); | ||
384 | fw_core_remove_address_handler(&fcp_handler); | ||
385 | } | ||