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