aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/video/pvrusb2/pvrusb2-encoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/video/pvrusb2/pvrusb2-encoder.c')
-rw-r--r--drivers/media/video/pvrusb2/pvrusb2-encoder.c418
1 files changed, 418 insertions, 0 deletions
diff --git a/drivers/media/video/pvrusb2/pvrusb2-encoder.c b/drivers/media/video/pvrusb2/pvrusb2-encoder.c
new file mode 100644
index 000000000000..2cc31695b435
--- /dev/null
+++ b/drivers/media/video/pvrusb2/pvrusb2-encoder.c
@@ -0,0 +1,418 @@
1/*
2 *
3 * $Id$
4 *
5 * Copyright (C) 2005 Mike Isely <isely@pobox.com>
6 * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22
23#include <linux/device.h> // for linux/firmware.h
24#include <linux/firmware.h>
25#include "pvrusb2-util.h"
26#include "pvrusb2-encoder.h"
27#include "pvrusb2-hdw-internal.h"
28#include "pvrusb2-debug.h"
29
30
31
32/* Firmware mailbox flags - definitions found from ivtv */
33#define IVTV_MBOX_FIRMWARE_DONE 0x00000004
34#define IVTV_MBOX_DRIVER_DONE 0x00000002
35#define IVTV_MBOX_DRIVER_BUSY 0x00000001
36
37
38static int pvr2_encoder_write_words(struct pvr2_hdw *hdw,
39 const u32 *data, unsigned int dlen)
40{
41 unsigned int idx;
42 int ret;
43 unsigned int offs = 0;
44 unsigned int chunkCnt;
45
46 /*
47
48 Format: First byte must be 0x01. Remaining 32 bit words are
49 spread out into chunks of 7 bytes each, little-endian ordered,
50 offset at zero within each 2 blank bytes following and a
51 single byte that is 0x44 plus the offset of the word. Repeat
52 request for additional words, with offset adjusted
53 accordingly.
54
55 */
56 while (dlen) {
57 chunkCnt = 8;
58 if (chunkCnt > dlen) chunkCnt = dlen;
59 memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer));
60 hdw->cmd_buffer[0] = 0x01;
61 for (idx = 0; idx < chunkCnt; idx++) {
62 hdw->cmd_buffer[1+(idx*7)+6] = 0x44 + idx + offs;
63 PVR2_DECOMPOSE_LE(hdw->cmd_buffer, 1+(idx*7),
64 data[idx]);
65 }
66 ret = pvr2_send_request(hdw,
67 hdw->cmd_buffer,1+(chunkCnt*7),
68 0,0);
69 if (ret) return ret;
70 data += chunkCnt;
71 dlen -= chunkCnt;
72 offs += chunkCnt;
73 }
74
75 return 0;
76}
77
78
79static int pvr2_encoder_read_words(struct pvr2_hdw *hdw,int statusFl,
80 u32 *data, unsigned int dlen)
81{
82 unsigned int idx;
83 int ret;
84 unsigned int offs = 0;
85 unsigned int chunkCnt;
86
87 /*
88
89 Format: First byte must be 0x02 (status check) or 0x28 (read
90 back block of 32 bit words). Next 6 bytes must be zero,
91 followed by a single byte of 0x44+offset for portion to be
92 read. Returned data is packed set of 32 bits words that were
93 read.
94
95 */
96
97 while (dlen) {
98 chunkCnt = 16;
99 if (chunkCnt > dlen) chunkCnt = dlen;
100 memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer));
101 hdw->cmd_buffer[0] = statusFl ? 0x02 : 0x28;
102 hdw->cmd_buffer[7] = 0x44 + offs;
103 ret = pvr2_send_request(hdw,
104 hdw->cmd_buffer,8,
105 hdw->cmd_buffer,chunkCnt * 4);
106 if (ret) return ret;
107
108 for (idx = 0; idx < chunkCnt; idx++) {
109 data[idx] = PVR2_COMPOSE_LE(hdw->cmd_buffer,idx*4);
110 }
111 data += chunkCnt;
112 dlen -= chunkCnt;
113 offs += chunkCnt;
114 }
115
116 return 0;
117}
118
119
120/* This prototype is set up to be compatible with the
121 cx2341x_mbox_func prototype in cx2341x.h, which should be in
122 kernels 2.6.18 or later. We do this so that we can enable
123 cx2341x.ko to write to our encoder (by handing it a pointer to this
124 function). For earlier kernels this doesn't really matter. */
125static int pvr2_encoder_cmd(void *ctxt,
126 int cmd,
127 int arg_cnt_send,
128 int arg_cnt_recv,
129 u32 *argp)
130{
131 unsigned int poll_count;
132 int ret = 0;
133 unsigned int idx;
134 /* These sizes look to be limited by the FX2 firmware implementation */
135 u32 wrData[16];
136 u32 rdData[16];
137 struct pvr2_hdw *hdw = (struct pvr2_hdw *)ctxt;
138
139
140 /*
141
142 The encoder seems to speak entirely using blocks 32 bit words.
143 In ivtv driver terms, this is a mailbox which we populate with
144 data and watch what the hardware does with it. The first word
145 is a set of flags used to control the transaction, the second
146 word is the command to execute, the third byte is zero (ivtv
147 driver suggests that this is some kind of return value), and
148 the fourth byte is a specified timeout (windows driver always
149 uses 0x00060000 except for one case when it is zero). All
150 successive words are the argument words for the command.
151
152 First, write out the entire set of words, with the first word
153 being zero.
154
155 Next, write out just the first word again, but set it to
156 IVTV_MBOX_DRIVER_DONE | IVTV_DRIVER_BUSY this time (which
157 probably means "go").
158
159 Next, read back 16 words as status. Check the first word,
160 which should have IVTV_MBOX_FIRMWARE_DONE set. If however
161 that bit is not set, then the command isn't done so repeat the
162 read.
163
164 Next, read back 32 words and compare with the original
165 arugments. Hopefully they will match.
166
167 Finally, write out just the first word again, but set it to
168 0x0 this time (which probably means "idle").
169
170 */
171
172 if (arg_cnt_send > (sizeof(wrData)/sizeof(wrData[0]))-4) {
173 pvr2_trace(
174 PVR2_TRACE_ERROR_LEGS,
175 "Failed to write cx23416 command"
176 " - too many input arguments"
177 " (was given %u limit %u)",
178 arg_cnt_send,
179 (unsigned int)(sizeof(wrData)/sizeof(wrData[0])) - 4);
180 return -EINVAL;
181 }
182
183 if (arg_cnt_recv > (sizeof(rdData)/sizeof(rdData[0]))-4) {
184 pvr2_trace(
185 PVR2_TRACE_ERROR_LEGS,
186 "Failed to write cx23416 command"
187 " - too many return arguments"
188 " (was given %u limit %u)",
189 arg_cnt_recv,
190 (unsigned int)(sizeof(rdData)/sizeof(rdData[0])) - 4);
191 return -EINVAL;
192 }
193
194
195 LOCK_TAKE(hdw->ctl_lock); do {
196
197 wrData[0] = 0;
198 wrData[1] = cmd;
199 wrData[2] = 0;
200 wrData[3] = 0x00060000;
201 for (idx = 0; idx < arg_cnt_send; idx++) {
202 wrData[idx+4] = argp[idx];
203 }
204 for (; idx < (sizeof(wrData)/sizeof(wrData[0]))-4; idx++) {
205 wrData[idx+4] = 0;
206 }
207
208 ret = pvr2_encoder_write_words(hdw,wrData,idx);
209 if (ret) break;
210 wrData[0] = IVTV_MBOX_DRIVER_DONE|IVTV_MBOX_DRIVER_BUSY;
211 ret = pvr2_encoder_write_words(hdw,wrData,1);
212 if (ret) break;
213 poll_count = 0;
214 while (1) {
215 if (poll_count < 10000000) poll_count++;
216 ret = pvr2_encoder_read_words(hdw,!0,rdData,1);
217 if (ret) break;
218 if (rdData[0] & IVTV_MBOX_FIRMWARE_DONE) {
219 break;
220 }
221 if (poll_count == 100) {
222 pvr2_trace(
223 PVR2_TRACE_ERROR_LEGS,
224 "***WARNING*** device's encoder"
225 " appears to be stuck"
226 " (status=0%08x)",rdData[0]);
227 pvr2_trace(
228 PVR2_TRACE_ERROR_LEGS,
229 "Encoder command: 0x%02x",cmd);
230 for (idx = 4; idx < arg_cnt_send; idx++) {
231 pvr2_trace(
232 PVR2_TRACE_ERROR_LEGS,
233 "Encoder arg%d: 0x%08x",
234 idx-3,wrData[idx]);
235 }
236 pvr2_trace(
237 PVR2_TRACE_ERROR_LEGS,
238 "Giving up waiting."
239 " It is likely that"
240 " this is a bad idea...");
241 ret = -EBUSY;
242 break;
243 }
244 }
245 if (ret) break;
246 wrData[0] = 0x7;
247 ret = pvr2_encoder_read_words(
248 hdw,0,rdData,
249 sizeof(rdData)/sizeof(rdData[0]));
250 if (ret) break;
251 for (idx = 0; idx < arg_cnt_recv; idx++) {
252 argp[idx] = rdData[idx+4];
253 }
254
255 wrData[0] = 0x0;
256 ret = pvr2_encoder_write_words(hdw,wrData,1);
257 if (ret) break;
258
259 } while(0); LOCK_GIVE(hdw->ctl_lock);
260
261 return ret;
262}
263
264
265static int pvr2_encoder_vcmd(struct pvr2_hdw *hdw, int cmd,
266 int args, ...)
267{
268 va_list vl;
269 unsigned int idx;
270 u32 data[12];
271
272 if (args > sizeof(data)/sizeof(data[0])) {
273 pvr2_trace(
274 PVR2_TRACE_ERROR_LEGS,
275 "Failed to write cx23416 command"
276 " - too many arguments"
277 " (was given %u limit %u)",
278 args,(unsigned int)(sizeof(data)/sizeof(data[0])));
279 return -EINVAL;
280 }
281
282 va_start(vl, args);
283 for (idx = 0; idx < args; idx++) {
284 data[idx] = va_arg(vl, u32);
285 }
286 va_end(vl);
287
288 return pvr2_encoder_cmd(hdw,cmd,args,0,data);
289}
290
291int pvr2_encoder_configure(struct pvr2_hdw *hdw)
292{
293 int ret;
294 pvr2_trace(PVR2_TRACE_ENCODER,"pvr2_encoder_configure"
295 " (cx2341x module)");
296 hdw->enc_ctl_state.port = CX2341X_PORT_STREAMING;
297 hdw->enc_ctl_state.width = hdw->res_hor_val;
298 hdw->enc_ctl_state.height = hdw->res_ver_val;
299 hdw->enc_ctl_state.is_50hz = ((hdw->std_mask_cur &
300 (V4L2_STD_NTSC|V4L2_STD_PAL_M)) ?
301 0 : 1);
302
303 ret = 0;
304
305 if (!ret) ret = pvr2_encoder_vcmd(
306 hdw,CX2341X_ENC_SET_NUM_VSYNC_LINES, 2,
307 0xf0, 0xf0);
308
309 /* setup firmware to notify us about some events (don't know why...) */
310 if (!ret) ret = pvr2_encoder_vcmd(
311 hdw,CX2341X_ENC_SET_EVENT_NOTIFICATION, 4,
312 0, 0, 0x10000000, 0xffffffff);
313
314 if (!ret) ret = pvr2_encoder_vcmd(
315 hdw,CX2341X_ENC_SET_VBI_LINE, 5,
316 0xffffffff,0,0,0,0);
317
318 if (ret) {
319 pvr2_trace(PVR2_TRACE_ERROR_LEGS,
320 "Failed to configure cx32416");
321 return ret;
322 }
323
324 ret = cx2341x_update(hdw,pvr2_encoder_cmd,
325 (hdw->enc_cur_valid ? &hdw->enc_cur_state : 0),
326 &hdw->enc_ctl_state);
327 if (ret) {
328 pvr2_trace(PVR2_TRACE_ERROR_LEGS,
329 "Error from cx2341x module code=%d",ret);
330 return ret;
331 }
332
333 ret = 0;
334
335 if (!ret) ret = pvr2_encoder_vcmd(
336 hdw, CX2341X_ENC_INITIALIZE_INPUT, 0);
337
338 if (ret) {
339 pvr2_trace(PVR2_TRACE_ERROR_LEGS,
340 "Failed to initialize cx32416 video input");
341 return ret;
342 }
343
344 hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_CFG);
345 memcpy(&hdw->enc_cur_state,&hdw->enc_ctl_state,
346 sizeof(struct cx2341x_mpeg_params));
347 hdw->enc_cur_valid = !0;
348 return 0;
349}
350
351
352int pvr2_encoder_start(struct pvr2_hdw *hdw)
353{
354 int status;
355
356 /* unmask some interrupts */
357 pvr2_write_register(hdw, 0x0048, 0xbfffffff);
358
359 /* change some GPIO data */
360 pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000481);
361 pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000);
362
363 if (hdw->config == pvr2_config_vbi) {
364 status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
365 0x01,0x14);
366 } else if (hdw->config == pvr2_config_mpeg) {
367 status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
368 0,0x13);
369 } else {
370 status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
371 0,0x13);
372 }
373 if (!status) {
374 hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_RUN);
375 }
376 return status;
377}
378
379int pvr2_encoder_stop(struct pvr2_hdw *hdw)
380{
381 int status;
382
383 /* mask all interrupts */
384 pvr2_write_register(hdw, 0x0048, 0xffffffff);
385
386 if (hdw->config == pvr2_config_vbi) {
387 status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
388 0x01,0x01,0x14);
389 } else if (hdw->config == pvr2_config_mpeg) {
390 status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
391 0x01,0,0x13);
392 } else {
393 status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
394 0x01,0,0x13);
395 }
396
397 /* change some GPIO data */
398 /* Note: Bit d7 of dir appears to control the LED. So we shut it
399 off here. */
400 pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000401);
401 pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000);
402
403 if (!status) {
404 hdw->subsys_enabled_mask &= ~(1<<PVR2_SUBSYS_B_ENC_RUN);
405 }
406 return status;
407}
408
409
410/*
411 Stuff for Emacs to see, in order to encourage consistent editing style:
412 *** Local Variables: ***
413 *** mode: c ***
414 *** fill-column: 70 ***
415 *** tab-width: 8 ***
416 *** c-basic-offset: 8 ***
417 *** End: ***
418 */