summaryrefslogtreecommitdiff
path: root/v4l_experimental/pvrusb2/pvrusb2-eeprom.c
blob: c7a8ec53b924799a8691e0a2c902a832910719d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/*
 *
 *  $Id: pvrusb2-eeprom.c,v 1.2 2005/11/27 23:01:16 mcisely Exp $
 *
 *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
 *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "pvrusb2-eeprom.h"
#include "pvrusb2-hdw-internal.h"
#include "pvrusb2-debug.h"
#include "compat.h"

#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__)

/*

  isely@pobox.com 16-Oct-2005 - There are two method by which we can
  theoretically retrieve information from the device's eeprom:

  Method #1: We expect tveeprom to attach to our I2C adapter as a
      client, in which case we send it a command to tell us what it
      knows about the device.  This is the "indirect" method.

  Method #2: We retrieve the eeprom contents ourselves and call into
      tveeprom_hauppauge_analog() to parse the data and tell us what
      it knows about the device.  This is the "direct" method.

  Unfortunately it isn't perfectly clear which method is the best.
  They each have pros and cons:

  #1 is simpler & more portable and has an API which is more stable.

  #1 doesn't provide as much information as #2 does.  For example, we
     can't retrieve the device's serial number with method #1.

  #1 requires that tveeprom.ko autonomously detect the eeprom chip on
     its own; we can't help it out here.  Worse still, it seems that
     the eeprom in some PVR USB2 devices (like mine) can't be detected
     correctly (I don't see an ack on a zero length write which is
     what the I2C core attempts).

  #2 uses an unstable API.  Current the ivtv implementation of #2 uses
     a completely different tveeprom struct than the v4l
     implementation of #2.  This causes a usability nightmare.

  Since I can't decide, both methods are implemented below.  Method #2
  (direct) is the default choice, but if you want to try method #1,
  then define PVR2_EEPROM_INDIRECT and cross your fingers...

  If you use method #1, please be aware that you won't have a serial
  number for the device and thus the sysfs interface may be a little
  different.  In addition, if tveeprom.ko fails to detect the eeprom
  you may have to force it using standard i2c module options (try
  force=-1,80).  FINALLY (and this may foreclose this option for you
  completely), the PVR USB2 eeprom seems to have valid data only in
  the upper 128 bytes - the lower 128 bytes causes tveeprom.ko to
  abort.  In method #2 we only read the upper 128 bytes...

 */


#ifdef PVR2_EEPROM_INDIRECT
/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
/* BEGIN INDIRECT METHOD */

#include "pvrusb2-i2c.h"

/* Use I2C client interface to retrieve usable information from within
 * tveeprom.  This tends to be more stable than directly calling. */

int pvr2_eeprom_analyze(struct pvr2_hdw *hdw)
{
	u32 props[5];
	int stat;

	stat = pvr2_i2c_tveeprom_cmd(hdw,0,props);
	if (stat < 0) {
		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
			   "Failed to retrieve eeprom data stat=%d",stat);
		return stat;
	}

	trace_eeprom("eeprom client query results:");
	trace_eeprom("tuner_type=%d",props[0]);
	trace_eeprom("tuner_formats=0x%x",props[1]);
	trace_eeprom("model=%d",props[2]);
	trace_eeprom("revision=%d",props[3]);
	trace_eeprom("has_radio=%d",props[4]);

	hdw->tuner_type = props[0];
	hdw->video_standards = props[1];

	return 0;
}

/* END INDIRECT METHOD */
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
#endif /* PVR2_EEPROM_INDIRECT */

#ifndef PVR2_EEPROM_INDIRECT

/* Stuff common to direct approach of operation tveeprom */

/*

   Read and analyze data in the eeprom.  Use tveeprom to figure out
   the packet structure, since this is another Hauppauge device and
   internally it has a family resemblence to ivtv-type devices

*/

#define PVR_EEPROM_I2C_ADDR    0x50

#include <media/tveeprom.h>

/* We seem to only be interested in the back half of the EEPROM */
#define EEPROM_SIZE 128
#define EEPROM_OFFS 128

/* Grab EEPROM contents, needed for direct method. */
static u8 *pvr2_eeprom_fetch(struct pvr2_hdw *hdw)
{
	struct i2c_msg msg[2];
	u8 *eeprom;
	u8 offs;
	int ret;
	unsigned pcnt,tcnt;
	eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL);
	if (!eeprom) {
		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
			   "Failed to allocate memory"
			   " required to read eeprom");
		return 0;
	}

	msg[0].addr = PVR_EEPROM_I2C_ADDR;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &offs;
	msg[1].addr = PVR_EEPROM_I2C_ADDR;
	msg[1].flags = I2C_M_RD;

	/* We have to do the actual eeprom data fetch ourselves, because
	   (1) we're only fetching part of the eeprom, and (2) if we were
	   getting the whole thing our I2C driver can't grab it in one
	   pass - which is what tveeprom is otherwise going to attempt */
	memset(eeprom,0,EEPROM_SIZE);
	for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) {
		pcnt = 16;
		if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt;
		offs = tcnt + EEPROM_OFFS;
		msg[1].len = pcnt;
		msg[1].buf = eeprom+tcnt;
		if ((ret = i2c_transfer(
			     &hdw->i2c_adap,
			     msg,sizeof(msg)/sizeof(msg[0]))) != 2) {
			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
				   "eeprom fetch set offs err=%d",ret);
			kfree(eeprom);
			return 0;
		}
	}
	return eeprom;
}


#ifndef PVR2_EEPROM_IVTV
/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
/* BEGIN DIRECT METHOD, V4L ONLY */


/* Directly call eeprom analysis function within tveeprom.  This
   version directly assumes it is talking to the V4L version of
   tveeprom.ko and does not attempt anything ugly to maintain
   backwards compatibility. */

int pvr2_eeprom_analyze(struct pvr2_hdw *hdw)
{
	u8 *eeprom;
	struct tveeprom tvdata;

	memset(&tvdata,0,sizeof(tvdata));

	eeprom = pvr2_eeprom_fetch(hdw);
	if (!eeprom) return -EINVAL;

	{
		struct i2c_client fake_client;
		/* Newer version expects a useless client interface */
		fake_client.addr = PVR_EEPROM_I2C_ADDR;
		fake_client.adapter = &hdw->i2c_adap;
		tveeprom_hauppauge_analog(&fake_client,&tvdata,eeprom);
	}

	trace_eeprom("eeprom assumed v4l tveeprom module");
	trace_eeprom("eeprom direct call results:");
	trace_eeprom("has_radio=%d",tvdata.has_radio);
	trace_eeprom("tuner_type=%d",tvdata.tuner_type);
	trace_eeprom("tuner_formats=0x%x",tvdata.tuner_formats);
	trace_eeprom("audio_processor=%d",tvdata.audio_processor);
	trace_eeprom("model=%d",tvdata.model);
	trace_eeprom("revision=%d",tvdata.revision);
	trace_eeprom("serial_number=%d",tvdata.serial_number);
	trace_eeprom("rev_str=%s",tvdata.rev_str);
	hdw->tuner_type = tvdata.tuner_type;
	hdw->serial_number = tvdata.serial_number;
	hdw->video_standards = tvdata.tuner_formats;

	kfree(eeprom);

	return 0;
}



/* END DIRECT METHOD, V4L ONLY */
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
#endif /* PVR2_EEPROM_IVTV */




#ifdef PVR2_EEPROM_IVTV
/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
/* BEGIN DIRECT METHOD, V4L OR IVTV */

/* Directly call eeprom analysis function within tveeprom.  This has
   portability issues because the internal API has been changing.  We
   have to do something positively gross here.  The two variants of
   tveeprom that we deal with (ivtv and v4l) use completely different
   definitions for the tveeprom structure.  To accomodate this, we'll
   allocate enough storage for the larger of the two, initialize it to
   bad but predictable data, and then call the analysis function.
   Upon return, we'll check how much data was changed and use that as
   a hint to determine exactly which tveeprom structure had been
   used.  Did I say this was ugly?  It's disgusting. */


/* This has to be an EXACT(!!) match with the tveeprom structure
   defined in our local copy of tveeprom.c. */
struct tveeprom_ivtv {
	u32 has_radio;

	u32 tuner_type;
	u32 tuner_formats;

	u32 digitizer;
	u32 digitizer_formats;

	u32 audio_processor;
	u32 decoder_processor;
	/* a_p_fmts? */

	u32 model;
	u32 revision;
	u32 serial_number;
	char rev_str[5];
};


int pvr2_eeprom_analyze(struct pvr2_hdw *hdw)
{
	u8 *eeprom;
	unsigned tcnt;
	int tp;
	union {
		struct tveeprom v4l;
		struct tveeprom_ivtv ivtv;
	} tvdata;

	memset(&tvdata,0x93,sizeof(tvdata));

	eeprom = pvr2_eeprom_fetch(hdw);
	if (!eeprom) return -EINVAL;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
	{
		struct i2c_client fake_client;
		/* Newer version expects a useless client interface */
		fake_client.addr = PVR_EEPROM_I2C_ADDR;
		fake_client.adapter = &hdw->i2c_adap;
		tveeprom_hauppauge_analog(&fake_client,
					  (struct tveeprom *)&tvdata,eeprom);
	}
#else
	tveeprom_hauppauge_analog((struct tveeprom *)&tvdata,eeprom);
#endif

	/* Now figure out which structure was used */
	for (tcnt = 0; tcnt < sizeof(tvdata); tcnt++) {
		if (((unsigned char *)(&tvdata))[sizeof(tvdata) - (tcnt+1)] !=
		    0x93) {
			break;
		}
	}
	tcnt = sizeof(tvdata) - tcnt;

	if (sizeof(tvdata.ivtv) < sizeof(tvdata.v4l)) {
		tp = 0;
		if (tcnt > sizeof(tvdata.ivtv)) tp = 1;
	} else {
		tp = 1;
		if (tcnt > sizeof(tvdata.v4l)) tp = 0;
	}

	if (tp) {
		/* v4l */
		trace_eeprom("eeprom detected v4l tveeprom module");
		trace_eeprom("eeprom direct call results:");
		trace_eeprom("has_radio=%d",tvdata.v4l.has_radio);
		trace_eeprom("tuner_type=%d",tvdata.v4l.tuner_type);
		trace_eeprom("tuner_formats=0x%x",tvdata.v4l.tuner_formats);
		trace_eeprom("audio_processor=%d",tvdata.v4l.audio_processor);
		trace_eeprom("model=%d",tvdata.v4l.model);
		trace_eeprom("revision=%d",tvdata.v4l.revision);
		trace_eeprom("serial_number=%d",tvdata.v4l.serial_number);
		trace_eeprom("rev_str=%s",tvdata.v4l.rev_str);
		hdw->tuner_type = tvdata.v4l.tuner_type;
		hdw->serial_number = tvdata.v4l.serial_number;
		hdw->video_standards = tvdata.v4l.tuner_formats;
	} else {
		/* ivtv */
		trace_eeprom("eeprom detected ivtv tveeprom module");
		trace_eeprom("eeprom direct call results:");
		trace_eeprom("has_radio=%d",tvdata.ivtv.has_radio);
		trace_eeprom("tuner_type=%d",tvdata.ivtv.tuner_type);
		trace_eeprom("tuner_formats=0x%x",tvdata.ivtv.tuner_formats);
		trace_eeprom("audio_processor=%d",tvdata.ivtv.audio_processor);
		trace_eeprom("model=%d",tvdata.ivtv.model);
		trace_eeprom("revision=%d",tvdata.ivtv.revision);
		trace_eeprom("serial_number=%d",tvdata.ivtv.serial_number);
		trace_eeprom("rev_str=%s",tvdata.ivtv.rev_str);
		hdw->tuner_type = tvdata.ivtv.tuner_type;
		hdw->serial_number = tvdata.ivtv.serial_number;
		hdw->video_standards = tvdata.ivtv.tuner_formats;
	}

	kfree(eeprom);

	return 0;
}

/* END DIRECT METHOD, V4L OR IVTV */
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
#endif /* PVR2_EEPROM_V4L */

#endif /* PVR2_EEPROM_INDIRECT */


static v4l2_std_id std_choices[] = {
	[PVR2_CVAL_VIDEOSTANDARD_NTSC_M]  = V4L2_STD_NTSC,
	[PVR2_CVAL_VIDEOSTANDARD_PAL_BG]  = V4L2_STD_PAL_BG,
	[PVR2_CVAL_VIDEOSTANDARD_PAL_I]   = V4L2_STD_PAL_I,
	[PVR2_CVAL_VIDEOSTANDARD_PAL_DK]  = V4L2_STD_PAL_DK,
	[PVR2_CVAL_VIDEOSTANDARD_SECAM_L] = V4L2_STD_SECAM_L,
};

void pvr2_eeprom_set_default_standard(struct pvr2_hdw *hdw)
{
	int vstd_value = 0;
	int vstd_found = 0;
	unsigned int idx;
	v4l2_std_id vs = (v4l2_std_id)hdw->video_standards;

	for (idx = 0; idx < sizeof(std_choices)/sizeof(std_choices[0]);
	     idx++) {
		if (!(vs & std_choices[idx])) continue;
		trace_eeprom("Detected video standard %s (from eeprom)",
			     pvr2_hdw_get_ctl_value_name(
				     hdw,PVR2_CID_VIDEOSTANDARD,idx));
		if (vstd_found) continue;
		vstd_value = idx;
		vstd_found = !0;
	}

	if (!vstd_found) {
		trace_eeprom("eeprom unable to recognize"
			     " a known video standard");
		return;
	}

	trace_eeprom("Setting initial video standard to %s"
		     " (detected from eeprom)",
		     pvr2_hdw_get_ctl_value_name(hdw,
						 PVR2_CID_VIDEOSTANDARD,
						 vstd_value));
	pvr2_hdw_set_ctl_value_internal(hdw,PVR2_CID_VIDEOSTANDARD,vstd_value);
}


/*
  Stuff for Emacs to see, in order to encourage consistent editing style:
  *** Local Variables: ***
  *** mode: c ***
  *** fill-column: 70 ***
  *** tab-width: 8 ***
  *** c-basic-offset: 8 ***
  *** End: ***
  */