msp34xx.c revision 123291
1179407Sobrien/*-
2179407Sobrien * Copyright (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org>
3218822Sdim * All rights reserved.
4179407Sobrien *
5179407Sobrien * Redistribution and use in source and binary forms, with or without
6179407Sobrien * modification, are permitted provided that the following conditions
7179407Sobrien * are met:
8179407Sobrien * 1. Redistributions of source code must retain the above copyright
9179407Sobrien *    notice, this list of conditions and the following disclaimer.
10179407Sobrien * 2. Redistributions in binary form must reproduce the above copyright
11179407Sobrien *    notice, this list of conditions and the following disclaimer in the
12179407Sobrien *    documentation and/or other materials provided with the distribution.
13179407Sobrien *
14179407Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15179407Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16179407Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17179407Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18179407Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19179407Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20179407Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21179407Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22179407Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23179407Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24179407Sobrien * SUCH DAMAGE.
25179407Sobrien *
26218822Sdim * $FreeBSD: head/sys/dev/bktr/msp34xx.c 123291 2003-12-08 07:59:18Z obrien $
27179407Sobrien */
28179407Sobrien
29179407Sobrien/*
30179407Sobrien * programming the msp34* sound processor family
31179407Sobrien *
32218822Sdim * (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org>
33179407Sobrien *
34179407Sobrien * what works and what doesn't:
35179407Sobrien *
36179407Sobrien *  AM-Mono
37179407Sobrien *      Support for Hauppauge cards added (decoding handled by tuner) added by
38179407Sobrien *      Frederic Crozat <fcrozat@mail.dotcom.fr>
39179407Sobrien *
40218822Sdim *  FM-Mono
41179407Sobrien *      should work. The stereo modes are backward compatible to FM-mono,
42179407Sobrien *      therefore FM-Mono should be allways available.
43179407Sobrien *
44179407Sobrien *  FM-Stereo (B/G, used in germany)
45179407Sobrien *      should work, with autodetect
46179407Sobrien *
47179407Sobrien *  FM-Stereo (satellite)
48179407Sobrien *      should work, no autodetect (i.e. default is mono, but you can
49179407Sobrien *      switch to stereo -- untested)
50179407Sobrien *
51179407Sobrien *  NICAM (B/G, L , used in UK, Scandinavia, Spain and France)
52179407Sobrien *      should work, with autodetect. Support for NICAM was added by
53179407Sobrien *      Pekka Pietikainen <pp@netppl.fi>
54179407Sobrien *
55179407Sobrien *
56179407Sobrien * TODO:
57179407Sobrien *   - better SAT support
58179407Sobrien *
59179407Sobrien *
60179407Sobrien * 980623  Thomas Sailer (sailer@ife.ee.ethz.ch)
61179407Sobrien *         using soundcore instead of OSS
62179407Sobrien *
63179407Sobrien *
64179407Sobrien * The FreeBSD modifications by Alexander Langer <alex@FreeBSD.org>
65179407Sobrien * are in the public domain.  Please contact me (Alex) and not Gerd for
66179407Sobrien * any problems you encounter under FreeBSD.
67179407Sobrien *
68179407Sobrien * FreeBSD TODO:
69179407Sobrien * - mutex handling (currently not mp-safe)
70179407Sobrien * - the various options here as loader tunables or compile time or whatever
71179407Sobrien * - how does the new dolby flag work with the current dpl_* stuff?
72179407Sobrien *   Maybe it's just enough to set the dolby flag to 1 and it works.
73179407Sobrien *   As I don't have a dolby card myself, I can't test it, though.
74179407Sobrien */
75179407Sobrien
76179407Sobrien#include "opt_bktr.h"			/* Include any kernel config options */
77179407Sobrien#ifdef BKTR_NEW_MSP34XX_DRIVER		/* file only needed for new driver */
78179407Sobrien
79179407Sobrien#include <sys/param.h>
80179407Sobrien#include <sys/systm.h>
81179407Sobrien#include <sys/kernel.h>
82179407Sobrien#include <sys/vnode.h>
83179407Sobrien
84179407Sobrien#include <sys/unistd.h>
85179407Sobrien#include <sys/kthread.h>
86179407Sobrien#include <sys/malloc.h>
87179407Sobrien
88179407Sobrien#include <machine/bus.h>		/* required by bktr_reg.h */
89179407Sobrien
90179407Sobrien#include <dev/btkr/ioctl_meteor.h>
91179407Sobrien#include <dev/btkr/ioctl_bt848.h>	/* extensions to ioctl_meteor.h */
92179407Sobrien#include <dev/bktr/bktr_reg.h>
93179407Sobrien#include <dev/bktr/bktr_tuner.h>
94179407Sobrien#include <dev/bktr/bktr_audio.h>
95179407Sobrien#include <dev/bktr/bktr_core.h>
96179407Sobrien
97179407Sobrien#define VIDEO_MODE_PAL          0
98179407Sobrien#define VIDEO_MODE_NTSC         1
99179407Sobrien#define VIDEO_MODE_SECAM        2
100179407Sobrien#define VIDEO_MODE_AUTO         3
101179407Sobrien
102179407Sobrien#define VIDEO_SOUND_MONO	1
103179407Sobrien#define VIDEO_SOUND_STEREO	2
104179407Sobrien#define VIDEO_SOUND_LANG1	4
105179407Sobrien#define VIDEO_SOUND_LANG2	8
106179407Sobrien
107179407Sobrien#define DFP_COUNT 0x41
108179407Sobrienstatic const int bl_dfp[] = {
109179407Sobrien	0x00, 0x01, 0x02, 0x03,  0x06, 0x08, 0x09, 0x0a,
110179407Sobrien	0x0b, 0x0d, 0x0e, 0x10
111179407Sobrien};
112179407Sobrien
113179407Sobrienstruct msp3400c {
114179407Sobrien	int simple;
115179407Sobrien	int nicam;
116179407Sobrien	int mode;
117179407Sobrien	int norm;
118179407Sobrien	int stereo;
119179407Sobrien	int nicam_on;
120179407Sobrien	int acb;
121179407Sobrien	int main, second;	/* sound carrier */
122179407Sobrien	int input;
123179407Sobrien
124179407Sobrien	int muted;
125179407Sobrien	int left, right;	/* volume */
126179407Sobrien	int bass, treble;
127179407Sobrien
128179407Sobrien	/* shadow register set */
129179407Sobrien	int dfp_regs[DFP_COUNT];
130179407Sobrien
131179407Sobrien	/* thread */
132179407Sobrien	struct proc	    *kthread;
133179407Sobrien	char                *threaddesc;
134179407Sobrien
135179407Sobrien	int                  active,restart,rmmod;
136179407Sobrien
137179407Sobrien	int                  watch_stereo;
138179407Sobrien	int		     halt_thread;
139179407Sobrien};
140179407Sobrien
141179407Sobrien#define VIDEO_MODE_RADIO 16      /* norm magic for radio mode */
142179407Sobrien
143179407Sobrien/* ---------------------------------------------------------------------- */
144179407Sobrien
145179407Sobrien#define dprintk(...) do {						\
146179407Sobrien	if (bootverbose) {						\
147179407Sobrien		printf("%s: ", bktr_name(client));			\
148179407Sobrien		printf(__VA_ARGS__);					\
149179407Sobrien	}								\
150179407Sobrien} while (0)
151179407Sobrien
152179407Sobrien/* ---------------------------------------------------------------------- */
153179407Sobrien
154179407Sobrien#define I2C_MSP3400C       0x80
155179407Sobrien#define I2C_MSP3400C_DEM   0x10
156179407Sobrien#define I2C_MSP3400C_DFP   0x12
157179407Sobrien
158179407Sobrien/* ----------------------------------------------------------------------- */
159179407Sobrien/* functions for talking to the MSP3400C Sound processor                   */
160179407Sobrien
161179407Sobrienstatic int msp3400c_reset(bktr_ptr_t client)
162179407Sobrien{
163179407Sobrien	/* use our own which handles config(8) options */
164179407Sobrien	msp_dpl_reset(client, client->msp_addr);
165179407Sobrien
166179407Sobrien        return 0;
167179407Sobrien}
168179407Sobrien
169179407Sobrienstatic int
170179407Sobrienmsp3400c_read(bktr_ptr_t client, int dev, int addr)
171179407Sobrien{
172179407Sobrien	/* use our own */
173179407Sobrien	return(msp_dpl_read(client, client->msp_addr, dev, addr));
174179407Sobrien}
175179407Sobrien
176179407Sobrienstatic int
177179407Sobrienmsp3400c_write(bktr_ptr_t client, int dev, int addr, int val)
178179407Sobrien{
179179407Sobrien	/* use our own */
180179407Sobrien	msp_dpl_write(client, client->msp_addr, dev, addr, val);
181179407Sobrien
182179407Sobrien	return(0);
183179407Sobrien}
184179407Sobrien
185179407Sobrien/* ------------------------------------------------------------------------ */
186179407Sobrien
187179407Sobrien/* This macro is allowed for *constants* only, gcc must calculate it
188179407Sobrien   at compile time.  Remember -- no floats in kernel mode */
189179407Sobrien#define MSP_CARRIER(freq) ((int)((float)(freq/18.432)*(1<<24)))
190179407Sobrien
191179407Sobrien#define MSP_MODE_AM_DETECT   0
192179407Sobrien#define MSP_MODE_FM_RADIO    2
193179407Sobrien#define MSP_MODE_FM_TERRA    3
194179407Sobrien#define MSP_MODE_FM_SAT      4
195179407Sobrien#define MSP_MODE_FM_NICAM1   5
196179407Sobrien#define MSP_MODE_FM_NICAM2   6
197179407Sobrien#define MSP_MODE_AM_NICAM    7
198179407Sobrien#define MSP_MODE_BTSC        8
199179407Sobrien#define MSP_MODE_EXTERN      9
200179407Sobrien
201179407Sobrienstatic struct MSP_INIT_DATA_DEM {
202179407Sobrien	int fir1[6];
203179407Sobrien	int fir2[6];
204179407Sobrien	int cdo1;
205179407Sobrien	int cdo2;
206179407Sobrien	int ad_cv;
207179407Sobrien	int mode_reg;
208179407Sobrien	int dfp_src;
209179407Sobrien	int dfp_matrix;
210179407Sobrien} msp_init_data[] = {
211179407Sobrien	/* AM (for carrier detect / msp3400) */
212179407Sobrien	{ { 75, 19, 36, 35, 39, 40 }, { 75, 19, 36, 35, 39, 40 },
213179407Sobrien	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
214179407Sobrien	  0x00d0, 0x0500,   0x0020, 0x3000},
215179407Sobrien
216179407Sobrien	/* AM (for carrier detect / msp3410) */
217179407Sobrien	{ { -1, -1, -8, 2, 59, 126 }, { -1, -1, -8, 2, 59, 126 },
218179407Sobrien	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
219179407Sobrien	  0x00d0, 0x0100,   0x0020, 0x3000},
220179407Sobrien
221179407Sobrien	/* FM Radio */
222179407Sobrien	{ { -8, -8, 4, 6, 78, 107 }, { -8, -8, 4, 6, 78, 107 },
223179407Sobrien	  MSP_CARRIER(10.7), MSP_CARRIER(10.7),
224179407Sobrien	  0x00d0, 0x0480, 0x0020, 0x3000 },
225179407Sobrien
226179407Sobrien	/* Terrestial FM-mono + FM-stereo */
227179407Sobrien	{ {  3, 18, 27, 48, 66, 72 }, {  3, 18, 27, 48, 66, 72 },
228179407Sobrien	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
229179407Sobrien	  0x00d0, 0x0480,   0x0030, 0x3000},
230179407Sobrien
231179407Sobrien	/* Sat FM-mono */
232179407Sobrien	{ {  1,  9, 14, 24, 33, 37 }, {  3, 18, 27, 48, 66, 72 },
233179407Sobrien	  MSP_CARRIER(6.5), MSP_CARRIER(6.5),
234179407Sobrien	  0x00c6, 0x0480,   0x0000, 0x3000},
235179407Sobrien
236179407Sobrien	/* NICAM/FM --  B/G (5.5/5.85), D/K (6.5/5.85) */
237179407Sobrien	{ { -2, -8, -10, 10, 50, 86 }, {  3, 18, 27, 48, 66, 72 },
238179407Sobrien	  MSP_CARRIER(5.5), MSP_CARRIER(5.5),
239179407Sobrien	  0x00d0, 0x0040,   0x0120, 0x3000},
240179407Sobrien
241179407Sobrien	/* NICAM/FM -- I (6.0/6.552) */
242179407Sobrien	{ {  2, 4, -6, -4, 40, 94 }, {  3, 18, 27, 48, 66, 72 },
243179407Sobrien	  MSP_CARRIER(6.0), MSP_CARRIER(6.0),
244179407Sobrien	  0x00d0, 0x0040,   0x0120, 0x3000},
245179407Sobrien
246179407Sobrien	/* NICAM/AM -- L (6.5/5.85) */
247179407Sobrien	{ {  -2, -8, -10, 10, 50, 86 }, {  -4, -12, -9, 23, 79, 126 },
248179407Sobrien	  MSP_CARRIER(6.5), MSP_CARRIER(6.5),
249179407Sobrien	  0x00c6, 0x0140,   0x0120, 0x7c03},
250179407Sobrien};
251179407Sobrien
252179407Sobrienstruct CARRIER_DETECT {
253179407Sobrien	int   cdo;
254179407Sobrien	char *name;
255179407Sobrien};
256179407Sobrien
257179407Sobrienstatic struct CARRIER_DETECT carrier_detect_main[] = {
258179407Sobrien	/* main carrier */
259218822Sdim	{ MSP_CARRIER(4.5),        "4.5   NTSC"                   },
260218822Sdim	{ MSP_CARRIER(5.5),        "5.5   PAL B/G"                },
261218822Sdim	{ MSP_CARRIER(6.0),        "6.0   PAL I"                  },
262179407Sobrien	{ MSP_CARRIER(6.5),        "6.5   PAL D/K + SAT + SECAM"  }
263218822Sdim};
264179407Sobrien
265179407Sobrienstatic struct CARRIER_DETECT carrier_detect_55[] = {
266179407Sobrien	/* PAL B/G */
267179407Sobrien	{ MSP_CARRIER(5.7421875),  "5.742 PAL B/G FM-stereo"     },
268179407Sobrien	{ MSP_CARRIER(5.85),       "5.85  PAL B/G NICAM"         }
269179407Sobrien};
270179407Sobrien
271179407Sobrienstatic struct CARRIER_DETECT carrier_detect_65[] = {
272179407Sobrien	/* PAL SAT / SECAM */
273179407Sobrien	{ MSP_CARRIER(5.85),       "5.85  PAL D/K + SECAM NICAM" },
274179407Sobrien	{ MSP_CARRIER(6.2578125),  "6.25  PAL D/K1 FM-stereo" },
275179407Sobrien	{ MSP_CARRIER(6.7421875),  "6.74  PAL D/K2 FM-stereo" },
276179407Sobrien	{ MSP_CARRIER(7.02),       "7.02  PAL SAT FM-stereo s/b" },
277179407Sobrien	{ MSP_CARRIER(7.20),       "7.20  PAL SAT FM-stereo s"   },
278179407Sobrien	{ MSP_CARRIER(7.38),       "7.38  PAL SAT FM-stereo b"   },
279179407Sobrien};
280179407Sobrien
281179407Sobrien#define CARRIER_COUNT(x) (sizeof(x)/sizeof(struct CARRIER_DETECT))
282179407Sobrien
283179407Sobrien/* ----------------------------------------------------------------------- */
284179407Sobrien
285179407Sobrien#define SCART_MASK    0
286179407Sobrien#define SCART_IN1     1
287179407Sobrien#define SCART_IN2     2
288179407Sobrien#define SCART_IN1_DA  3
289179407Sobrien#define SCART_IN2_DA  4
290179407Sobrien#define SCART_IN3     5
291179407Sobrien#define SCART_IN4     6
292179407Sobrien#define SCART_MONO    7
293179407Sobrien#define SCART_MUTE    8
294179407Sobrien
295179407Sobrienstatic int scarts[3][9] = {
296179407Sobrien  /* MASK    IN1     IN2     IN1_DA  IN2_DA  IN3     IN4     MONO    MUTE   */
297179407Sobrien  {  0x0320, 0x0000, 0x0200, -1,     -1,     0x0300, 0x0020, 0x0100, 0x0320 },
298179407Sobrien  {  0x0c40, 0x0440, 0x0400, 0x0c00, 0x0040, 0x0000, 0x0840, 0x0800, 0x0c40 },
299179407Sobrien  {  0x3080, 0x1000, 0x1080, 0x0000, 0x0080, 0x2080, 0x3080, 0x2000, 0x3000 },
300179407Sobrien};
301179407Sobrien
302179407Sobrienstatic char *scart_names[] = {
303179407Sobrien  "mask", "in1", "in2", "in1 da", "in2 da", "in3", "in4", "mono", "mute"
304179407Sobrien};
305179407Sobrien
306179407Sobrienstatic void
307179407Sobrienmsp3400c_set_scart(bktr_ptr_t client, int in, int out)
308179407Sobrien{
309179407Sobrien	struct msp3400c *msp = client->msp3400c_info;
310179407Sobrien
311179407Sobrien	if (-1 == scarts[out][in])
312179407Sobrien		return;
313179407Sobrien
314179407Sobrien	dprintk("msp34xx: scart switch: %s => %d\n",scart_names[in],out);
315179407Sobrien	msp->acb &= ~scarts[out][SCART_MASK];
316179407Sobrien	msp->acb |=  scarts[out][in];
317179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x0013, msp->acb);
318179407Sobrien}
319179407Sobrien
320179407Sobrien/* ------------------------------------------------------------------------ */
321179407Sobrien
322179407Sobrienstatic void msp3400c_setcarrier(bktr_ptr_t client, int cdo1, int cdo2)
323179407Sobrien{
324179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x0093, cdo1 & 0xfff);
325179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x009b, cdo1 >> 12);
326179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x00a3, cdo2 & 0xfff);
327179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x00ab, cdo2 >> 12);
328179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/
329179407Sobrien}
330179407Sobrien
331179407Sobrienstatic void msp3400c_setvolume(bktr_ptr_t client,
332179407Sobrien			       int muted, int left, int right)
333179407Sobrien{
334179407Sobrien	int vol = 0,val = 0,balance = 0;
335179407Sobrien
336179407Sobrien	if (!muted) {
337179407Sobrien		vol     = (left > right) ? left : right;
338179407Sobrien		val     = (vol * 0x73 / 65535) << 8;
339179407Sobrien	}
340179407Sobrien	if (vol > 0) {
341179407Sobrien		balance = ((right-left) * 127) / vol;
342179407Sobrien	}
343179407Sobrien
344179407Sobrien	dprintk("msp34xx: setvolume: mute=%s %d:%d  v=0x%02x b=0x%02x\n",
345179407Sobrien		muted ? "on" : "off", left, right, val>>8, balance);
346179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x0000, val); /* loudspeaker */
347179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x0006, val); /* headphones  */
348179407Sobrien	/* scart - on/off only */
349179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x0007, val ? 0x4000 : 0);
350179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x0001, balance << 8);
351179407Sobrien}
352179407Sobrien
353179407Sobrienstatic void msp3400c_setbass(bktr_ptr_t client, int bass)
354179407Sobrien{
355179407Sobrien	int val = ((bass-32768) * 0x60 / 65535) << 8;
356179407Sobrien
357179407Sobrien	dprintk("msp34xx: setbass: %d 0x%02x\n",bass, val>>8);
358179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x0002, val); /* loudspeaker */
359179407Sobrien}
360179407Sobrien
361179407Sobrienstatic void msp3400c_settreble(bktr_ptr_t client, int treble)
362179407Sobrien{
363179407Sobrien	int val = ((treble-32768) * 0x60 / 65535) << 8;
364179407Sobrien
365179407Sobrien	dprintk("msp34xx: settreble: %d 0x%02x\n",treble, val>>8);
366179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x0003, val); /* loudspeaker */
367179407Sobrien}
368179407Sobrien
369179407Sobrienstatic void msp3400c_setmode(bktr_ptr_t client, int type)
370179407Sobrien{
371179407Sobrien	struct msp3400c *msp = client->msp3400c_info;
372179407Sobrien	int i;
373179407Sobrien
374179407Sobrien	dprintk("msp3400: setmode: %d\n",type);
375179407Sobrien	msp->mode   = type;
376179407Sobrien	msp->stereo = VIDEO_SOUND_MONO;
377179407Sobrien
378179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x00bb,          /* ad_cv */
379179407Sobrien		       msp_init_data[type].ad_cv);
380179407Sobrien
381179407Sobrien	for (i = 5; i >= 0; i--)                                   /* fir 1 */
382179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DEM, 0x0001,
383179407Sobrien			       msp_init_data[type].fir1[i]);
384179407Sobrien
385179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0004); /* fir 2 */
386179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0040);
387179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005, 0x0000);
388179407Sobrien	for (i = 5; i >= 0; i--)
389179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DEM, 0x0005,
390179407Sobrien			       msp_init_data[type].fir2[i]);
391179407Sobrien
392179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x0083,     /* MODE_REG */
393179407Sobrien		       msp_init_data[type].mode_reg);
394179407Sobrien
395179407Sobrien	msp3400c_setcarrier(client, msp_init_data[type].cdo1,
396179407Sobrien			    msp_init_data[type].cdo2);
397179407Sobrien
398179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DEM, 0x0056, 0); /*LOAD_REG_1/2*/
399179407Sobrien
400179407Sobrien	if (client->dolby) {
401179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,
402179407Sobrien			       0x0520); /* I2S1 */
403179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,
404179407Sobrien			       0x0620); /* I2S2 */
405179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,
406179407Sobrien			       msp_init_data[type].dfp_src);
407179407Sobrien	} else {
408179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,
409179407Sobrien			       msp_init_data[type].dfp_src);
410179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,
411179407Sobrien			       msp_init_data[type].dfp_src);
412179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,
413179407Sobrien			       msp_init_data[type].dfp_src);
414179407Sobrien	}
415179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,
416179407Sobrien		       msp_init_data[type].dfp_src);
417179407Sobrien	msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e,
418179407Sobrien		       msp_init_data[type].dfp_matrix);
419179407Sobrien
420179407Sobrien	if (msp->nicam) {
421179407Sobrien		/* nicam prescale */
422179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0010, 0x5a00); /* was: 0x3000 */
423179407Sobrien	}
424179407Sobrien}
425179407Sobrien
426179407Sobrien/* turn on/off nicam + stereo */
427179407Sobrienstatic void msp3400c_setstereo(bktr_ptr_t client, int mode)
428179407Sobrien{
429179407Sobrien	static char *strmode[] = { "0", "mono", "stereo", "3",
430179407Sobrien				   "lang1", "5", "6", "7", "lang2" };
431179407Sobrien	struct msp3400c *msp = client->msp3400c_info;
432179407Sobrien	int nicam=0; /* channel source: FM/AM or nicam */
433179407Sobrien	int src=0;
434179407Sobrien
435179407Sobrien	/* switch demodulator */
436179407Sobrien	switch (msp->mode) {
437179407Sobrien	case MSP_MODE_FM_TERRA:
438179407Sobrien		dprintk("msp3400: FM setstereo: %s\n",strmode[mode]);
439179407Sobrien		msp3400c_setcarrier(client,msp->second,msp->main);
440179407Sobrien		switch (mode) {
441179407Sobrien		case VIDEO_SOUND_STEREO:
442179407Sobrien			msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3001);
443179407Sobrien			break;
444179407Sobrien		case VIDEO_SOUND_MONO:
445179407Sobrien		case VIDEO_SOUND_LANG1:
446179407Sobrien		case VIDEO_SOUND_LANG2:
447179407Sobrien			msp3400c_write(client,I2C_MSP3400C_DFP, 0x000e, 0x3000);
448179407Sobrien			break;
449179407Sobrien		}
450179407Sobrien		break;
451179407Sobrien	case MSP_MODE_FM_SAT:
452179407Sobrien		dprintk("msp3400: SAT setstereo: %s\n",strmode[mode]);
453179407Sobrien		switch (mode) {
454179407Sobrien		case VIDEO_SOUND_MONO:
455179407Sobrien			msp3400c_setcarrier(client, MSP_CARRIER(6.5), MSP_CARRIER(6.5));
456179407Sobrien			break;
457179407Sobrien		case VIDEO_SOUND_STEREO:
458179407Sobrien			msp3400c_setcarrier(client, MSP_CARRIER(7.2), MSP_CARRIER(7.02));
459179407Sobrien			break;
460179407Sobrien		case VIDEO_SOUND_LANG1:
461179407Sobrien			msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02));
462179407Sobrien			break;
463179407Sobrien		case VIDEO_SOUND_LANG2:
464179407Sobrien			msp3400c_setcarrier(client, MSP_CARRIER(7.38), MSP_CARRIER(7.02));
465179407Sobrien			break;
466179407Sobrien		}
467179407Sobrien		break;
468179407Sobrien	case MSP_MODE_FM_NICAM1:
469179407Sobrien	case MSP_MODE_FM_NICAM2:
470179407Sobrien	case MSP_MODE_AM_NICAM:
471179407Sobrien		dprintk("msp3400: NICAM setstereo: %s\n",strmode[mode]);
472179407Sobrien		msp3400c_setcarrier(client,msp->second,msp->main);
473179407Sobrien		if (msp->nicam_on)
474179407Sobrien			nicam=0x0100;
475179407Sobrien		break;
476179407Sobrien	case MSP_MODE_BTSC:
477179407Sobrien		dprintk("msp3400: BTSC setstereo: %s\n",strmode[mode]);
478179407Sobrien		nicam=0x0300;
479179407Sobrien		break;
480179407Sobrien	case MSP_MODE_EXTERN:
481179407Sobrien		dprintk("msp3400: extern setstereo: %s\n",strmode[mode]);
482179407Sobrien		nicam = 0x0200;
483179407Sobrien		break;
484179407Sobrien	case MSP_MODE_FM_RADIO:
485179407Sobrien		dprintk("msp3400: FM-Radio setstereo: %s\n",strmode[mode]);
486179407Sobrien		break;
487179407Sobrien	default:
488179407Sobrien		dprintk("msp3400: mono setstereo\n");
489179407Sobrien		return;
490179407Sobrien	}
491179407Sobrien
492179407Sobrien	/* switch audio */
493179407Sobrien	switch (mode) {
494179407Sobrien	case VIDEO_SOUND_STEREO:
495179407Sobrien		src = 0x0020 | nicam;
496179407Sobrien#if 0
497179407Sobrien		/* spatial effect */
498179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0005,0x4000);
499179407Sobrien#endif
500179407Sobrien		break;
501179407Sobrien	case VIDEO_SOUND_MONO:
502179407Sobrien		if (msp->mode == MSP_MODE_AM_NICAM) {
503179407Sobrien			dprintk("msp3400: switching to AM mono\n");
504179407Sobrien			/* AM mono decoding is handled by tuner, not MSP chip */
505179407Sobrien			/* SCART switching control register */
506179407Sobrien			msp3400c_set_scart(client,SCART_MONO,0);
507179407Sobrien			src = 0x0200;
508179407Sobrien			break;
509179407Sobrien		}
510179407Sobrien	case VIDEO_SOUND_LANG1:
511179407Sobrien		src = 0x0000 | nicam;
512179407Sobrien		break;
513179407Sobrien	case VIDEO_SOUND_LANG2:
514179407Sobrien		src = 0x0010 | nicam;
515179407Sobrien		break;
516179407Sobrien	}
517179407Sobrien	dprintk("msp3400: setstereo final source/matrix = 0x%x\n", src);
518179407Sobrien
519179407Sobrien	if (client->dolby) {
520179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,0x0520);
521179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,0x0620);
522179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src);
523179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src);
524179407Sobrien	} else {
525179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0008,src);
526179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x0009,src);
527179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x000a,src);
528179407Sobrien		msp3400c_write(client,I2C_MSP3400C_DFP, 0x000b,src);
529179407Sobrien	}
530179407Sobrien}
531179407Sobrien
532179407Sobrienstatic void
533179407Sobrienmsp3400c_print_mode(struct msp3400c *msp)
534179407Sobrien{
535179407Sobrien	if (msp->main == msp->second) {
536179407Sobrien		printf("bktr: msp3400: mono sound carrier: %d.%03d MHz\n",
537179407Sobrien		       msp->main/910000,(msp->main/910)%1000);
538179407Sobrien	} else {
539179407Sobrien		printf("bktr: msp3400: main sound carrier: %d.%03d MHz\n",
540179407Sobrien		       msp->main/910000,(msp->main/910)%1000);
541179407Sobrien	}
542179407Sobrien	if (msp->mode == MSP_MODE_FM_NICAM1 ||
543179407Sobrien	    msp->mode == MSP_MODE_FM_NICAM2)
544179407Sobrien		printf("bktr: msp3400: NICAM/FM carrier   : %d.%03d MHz\n",
545179407Sobrien		       msp->second/910000,(msp->second/910)%1000);
546179407Sobrien	if (msp->mode == MSP_MODE_AM_NICAM)
547179407Sobrien		printf("bktr: msp3400: NICAM/AM carrier   : %d.%03d MHz\n",
548179407Sobrien		       msp->second/910000,(msp->second/910)%1000);
549218822Sdim	if (msp->mode == MSP_MODE_FM_TERRA &&
550218822Sdim	    msp->main != msp->second) {
551218822Sdim		printf("bktr: msp3400: FM-stereo carrier : %d.%03d MHz\n",
552218822Sdim		       msp->second/910000,(msp->second/910)%1000);
553218822Sdim	}
554218822Sdim}
555218822Sdim
556218822Sdimstatic void
557218822Sdimmsp3400c_restore_dfp(bktr_ptr_t client)
558218822Sdim{
559218822Sdim	struct msp3400c *msp = (struct msp3400c*)client->msp3400c_info;
560218822Sdim	int i;
561218822Sdim
562218822Sdim	for (i = 0; i < DFP_COUNT; i++) {
563218822Sdim		if (-1 == msp->dfp_regs[i])
564218822Sdim			continue;
565218822Sdim		msp3400c_write(client,I2C_MSP3400C_DFP, i, msp->dfp_regs[i]);
566218822Sdim	}
567218822Sdim}
568218822Sdim
569218822Sdim/* ----------------------------------------------------------------------- */
570218822Sdim
571218822Sdimstruct REGISTER_DUMP {
572218822Sdim	int   addr;
573218822Sdim	char *name;
574218822Sdim};
575218822Sdim
576218822Sdimstruct REGISTER_DUMP d1[] = {
577218822Sdim	{ 0x007e, "autodetect" },
578218822Sdim	{ 0x0023, "C_AD_BITS " },
579218822Sdim	{ 0x0038, "ADD_BITS  " },
580218822Sdim	{ 0x003e, "CIB_BITS  " },
581218822Sdim	{ 0x0057, "ERROR_RATE" },
582218822Sdim};
583218822Sdim
584218822Sdimstatic int
585218822Sdimautodetect_stereo(bktr_ptr_t client)
586218822Sdim{
587218822Sdim	struct msp3400c *msp = (struct msp3400c*)client->msp3400c_info;
588218822Sdim	int val;
589218822Sdim	int newstereo = msp->stereo;
590218822Sdim	int newnicam  = msp->nicam_on;
591218822Sdim	int update = 0;
592218822Sdim
593218822Sdim	switch (msp->mode) {
594218822Sdim	case MSP_MODE_FM_TERRA:
595218822Sdim		val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x18);
596218822Sdim		if (val > 32767)
597218822Sdim			val -= 65536;
598218822Sdim		dprintk("msp34xx: stereo detect register: %d\n",val);
599218822Sdim
600218822Sdim		if (val > 4096) {
601218822Sdim			newstereo = VIDEO_SOUND_STEREO | VIDEO_SOUND_MONO;
602218822Sdim		} else if (val < -4096) {
603218822Sdim			newstereo = VIDEO_SOUND_LANG1 | VIDEO_SOUND_LANG2;
604218822Sdim		} else {
605218822Sdim			newstereo = VIDEO_SOUND_MONO;
606218822Sdim		}
607218822Sdim		newnicam = 0;
608218822Sdim		break;
609218822Sdim	case MSP_MODE_FM_NICAM1:
610218822Sdim	case MSP_MODE_FM_NICAM2:
611218822Sdim	case MSP_MODE_AM_NICAM:
612218822Sdim		val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x23);
613218822Sdim		dprintk("msp34xx: nicam sync=%d, mode=%d\n",val & 1, (val & 0x1e) >> 1);
614218822Sdim
615218822Sdim		if (val & 1) {
616218822Sdim			/* nicam synced */
617218822Sdim			switch ((val & 0x1e) >> 1)  {
618218822Sdim			case 0:
619218822Sdim			case 8:
620218822Sdim				newstereo = VIDEO_SOUND_STEREO;
621218822Sdim				break;
622218822Sdim			case 1:
623218822Sdim			case 9:
624218822Sdim				newstereo = VIDEO_SOUND_MONO
625218822Sdim					| VIDEO_SOUND_LANG1;
626218822Sdim				break;
627218822Sdim			case 2:
628218822Sdim			case 10:
629218822Sdim				newstereo = VIDEO_SOUND_MONO
630218822Sdim					| VIDEO_SOUND_LANG1
631218822Sdim					| VIDEO_SOUND_LANG2;
632218822Sdim				break;
633218822Sdim			default:
634218822Sdim				newstereo = VIDEO_SOUND_MONO;
635218822Sdim				break;
636218822Sdim			}
637218822Sdim			newnicam=1;
638218822Sdim		} else {
639218822Sdim			newnicam = 0;
640218822Sdim			newstereo = VIDEO_SOUND_MONO;
641218822Sdim		}
642218822Sdim		break;
643218822Sdim	case MSP_MODE_BTSC:
644218822Sdim		val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x200);
645218822Sdim		dprintk("msp3410: status=0x%x (pri=%s, sec=%s, %s%s%s)\n",
646218822Sdim			val,
647218822Sdim			(val & 0x0002) ? "no"     : "yes",
648218822Sdim			(val & 0x0004) ? "no"     : "yes",
649218822Sdim			(val & 0x0040) ? "stereo" : "mono",
650218822Sdim			(val & 0x0080) ? ", nicam 2nd mono" : "",
651218822Sdim			(val & 0x0100) ? ", bilingual/SAP"  : "");
652218822Sdim		newstereo = VIDEO_SOUND_MONO;
653218822Sdim		if (val & 0x0040) newstereo |= VIDEO_SOUND_STEREO;
654218822Sdim		if (val & 0x0100) newstereo |= VIDEO_SOUND_LANG1;
655218822Sdim		break;
656218822Sdim	}
657218822Sdim	if (newstereo != msp->stereo) {
658218822Sdim		update = 1;
659218822Sdim		dprintk("msp34xx: watch: stereo %d => %d\n",
660218822Sdim			msp->stereo,newstereo);
661218822Sdim		msp->stereo   = newstereo;
662218822Sdim	}
663218822Sdim	if (newnicam != msp->nicam_on) {
664218822Sdim		update = 1;
665218822Sdim		dprintk("msp34xx: watch: nicam %d => %d\n",
666218822Sdim			msp->nicam_on,newnicam);
667218822Sdim		msp->nicam_on = newnicam;
668218822Sdim	}
669218822Sdim	return update;
670218822Sdim}
671218822Sdim
672218822Sdim/*
673218822Sdim * A kernel thread for msp3400 control -- we don't want to block the
674218822Sdim * in the ioctl while doing the sound carrier & stereo detect
675218822Sdim */
676218822Sdim
677218822Sdim/* stereo/multilang monitoring */
678218822Sdimstatic void watch_stereo(bktr_ptr_t client)
679218822Sdim{
680218822Sdim	struct msp3400c *msp = (struct msp3400c*)client->msp3400c_info;
681218822Sdim
682218822Sdim	if (autodetect_stereo(client)) {
683218822Sdim		if (msp->stereo & VIDEO_SOUND_STEREO)
684218822Sdim			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
685218822Sdim		else if (msp->stereo & VIDEO_SOUND_LANG1)
686218822Sdim			msp3400c_setstereo(client,VIDEO_SOUND_LANG1);
687218822Sdim		else
688218822Sdim			msp3400c_setstereo(client,VIDEO_SOUND_MONO);
689218822Sdim	}
690218822Sdim	if (client->stereo_once)
691218822Sdim		msp->watch_stereo = 0;
692218822Sdim
693218822Sdim}
694218822Sdim
695218822Sdimstatic void msp3400c_thread(void *data)
696218822Sdim{
697218822Sdim	bktr_ptr_t client = data;
698218822Sdim	struct msp3400c *msp = (struct msp3400c*)client->msp3400c_info;
699218822Sdim
700218822Sdim	struct CARRIER_DETECT *cd;
701218822Sdim	int count, max1,max2,val1,val2, val,this;
702218822Sdim
703218822Sdim	dprintk("msp3400: thread started\n");
704218822Sdim
705218822Sdim	for (;;) {
706218822Sdim		if (msp->rmmod)
707218822Sdim			goto done;
708218822Sdim		tsleep(msp->kthread, PRIBIO, "idle", 0);
709218822Sdim		if (msp->rmmod)
710218822Sdim			goto done;
711218822Sdim		if (msp->halt_thread) {
712218822Sdim			msp->watch_stereo = 0;
713218822Sdim			msp->halt_thread = 0;
714218822Sdim			continue;
715218822Sdim		}
716218822Sdim
717218822Sdim		if (VIDEO_MODE_RADIO == msp->norm ||
718179407Sobrien		    MSP_MODE_EXTERN  == msp->mode)
719179407Sobrien			continue;  /* nothing to do */
720179407Sobrien
721179407Sobrien		msp->active = 1;
722179407Sobrien
723179407Sobrien		if (msp->watch_stereo) {
724179407Sobrien			watch_stereo(client);
725179407Sobrien			msp->active = 0;
726179407Sobrien			continue;
727179407Sobrien		}
728179407Sobrien
729179407Sobrien		/* some time for the tuner to sync */
730179407Sobrien		tsleep(msp->kthread, PRIBIO, "tuner sync", hz/2);
731179407Sobrien
732179407Sobrien	restart:
733179407Sobrien		if (VIDEO_MODE_RADIO == msp->norm ||
734179407Sobrien		    MSP_MODE_EXTERN  == msp->mode)
735179407Sobrien			continue;  /* nothing to do */
736179407Sobrien		msp->restart = 0;
737218822Sdim		msp3400c_setvolume(client, msp->muted, 0, 0);
738218822Sdim		msp3400c_setmode(client, MSP_MODE_AM_DETECT /* +1 */ );
739218822Sdim		val1 = val2 = 0;
740179407Sobrien		max1 = max2 = -1;
741179407Sobrien		msp->watch_stereo = 0;
742179407Sobrien
743179407Sobrien		/* carrier detect pass #1 -- main carrier */
744179407Sobrien		cd = carrier_detect_main; count = CARRIER_COUNT(carrier_detect_main);
745179407Sobrien
746179407Sobrien		if (client->amsound && (msp->norm == VIDEO_MODE_SECAM)) {
747179407Sobrien			/* autodetect doesn't work well with AM ... */
748179407Sobrien			max1 = 3;
749179407Sobrien			count = 0;
750218822Sdim			dprintk("msp3400: AM sound override\n");
751179407Sobrien		}
752179407Sobrien
753179407Sobrien		for (this = 0; this < count; this++) {
754179407Sobrien			msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo);
755218822Sdim
756179407Sobrien			tsleep(msp->kthread, PRIBIO, "carrier detect", hz/100);
757218822Sdim
758179407Sobrien			if (msp->restart)
759179407Sobrien				msp->restart = 0;
760179407Sobrien
761179407Sobrien			val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b);
762179407Sobrien			if (val > 32767)
763179407Sobrien				val -= 65536;
764179407Sobrien			if (val1 < val)
765179407Sobrien				val1 = val, max1 = this;
766179407Sobrien			dprintk("msp3400: carrier1 val: %5d / %s\n", val,cd[this].name);
767179407Sobrien		}
768218822Sdim
769218822Sdim		/* carrier detect pass #2 -- second (stereo) carrier */
770218822Sdim		switch (max1) {
771179407Sobrien		case 1: /* 5.5 */
772218822Sdim			cd = carrier_detect_55; count = CARRIER_COUNT(carrier_detect_55);
773218822Sdim			break;
774218822Sdim		case 3: /* 6.5 */
775218822Sdim			cd = carrier_detect_65; count = CARRIER_COUNT(carrier_detect_65);
776218822Sdim			break;
777218822Sdim		case 0: /* 4.5 */
778218822Sdim		case 2: /* 6.0 */
779218822Sdim		default:
780179407Sobrien			cd = NULL; count = 0;
781179407Sobrien			break;
782179407Sobrien		}
783218822Sdim
784179407Sobrien		if (client->amsound && (msp->norm == VIDEO_MODE_SECAM)) {
785179407Sobrien			/* autodetect doesn't work well with AM ... */
786179407Sobrien			cd = NULL; count = 0; max2 = 0;
787218822Sdim		}
788179407Sobrien		for (this = 0; this < count; this++) {
789218822Sdim			msp3400c_setcarrier(client, cd[this].cdo,cd[this].cdo);
790218822Sdim
791218822Sdim			tsleep(msp->kthread, PRIBIO, "carrier detection", hz/100);
792179407Sobrien			if (msp->restart)
793218822Sdim				goto restart;
794218822Sdim
795179407Sobrien			val = msp3400c_read(client, I2C_MSP3400C_DFP, 0x1b);
796179407Sobrien			if (val > 32767)
797179407Sobrien				val -= 65536;
798218822Sdim			if (val2 < val)
799179407Sobrien				val2 = val, max2 = this;
800179407Sobrien			dprintk("msp3400: carrier2 val: %5d / %s\n", val,cd[this].name);
801179407Sobrien		}
802218822Sdim
803179407Sobrien		/* programm the msp3400 according to the results */
804218822Sdim		msp->main   = carrier_detect_main[max1].cdo;
805218822Sdim		switch (max1) {
806218822Sdim		case 1: /* 5.5 */
807218822Sdim			if (max2 == 0) {
808179407Sobrien				/* B/G FM-stereo */
809179407Sobrien				msp->second = carrier_detect_55[max2].cdo;
810179407Sobrien				msp3400c_setmode(client, MSP_MODE_FM_TERRA);
811179407Sobrien				msp->nicam_on = 0;
812179407Sobrien				/* XXX why mono? this probably can do stereo... - Alex*/
813179407Sobrien				msp3400c_setstereo(client, VIDEO_SOUND_MONO);
814179407Sobrien				msp->watch_stereo = 1;
815179407Sobrien			} else if (max2 == 1 && msp->nicam) {
816179407Sobrien				/* B/G NICAM */
817179407Sobrien				msp->second = carrier_detect_55[max2].cdo;
818179407Sobrien				msp3400c_setmode(client, MSP_MODE_FM_NICAM1);
819179407Sobrien				msp->nicam_on = 1;
820179407Sobrien				msp3400c_setcarrier(client, msp->second, msp->main);
821179407Sobrien				msp->watch_stereo = 1;
822179407Sobrien			} else {
823179407Sobrien				goto no_second;
824179407Sobrien			}
825218822Sdim			break;
826218822Sdim		case 2: /* 6.0 */
827218822Sdim			/* PAL I NICAM */
828218822Sdim			msp->second = MSP_CARRIER(6.552);
829179407Sobrien			msp3400c_setmode(client, MSP_MODE_FM_NICAM2);
830179407Sobrien			msp->nicam_on = 1;
831179407Sobrien			msp3400c_setcarrier(client, msp->second, msp->main);
832179407Sobrien			msp->watch_stereo = 1;
833179407Sobrien			break;
834179407Sobrien		case 3: /* 6.5 */
835179407Sobrien			if (max2 == 1 || max2 == 2) {
836179407Sobrien				/* D/K FM-stereo */
837179407Sobrien				msp->second = carrier_detect_65[max2].cdo;
838179407Sobrien				msp3400c_setmode(client, MSP_MODE_FM_TERRA);
839179407Sobrien				msp->nicam_on = 0;
840179407Sobrien				msp3400c_setstereo(client, VIDEO_SOUND_MONO);
841179407Sobrien				msp->watch_stereo = 1;
842179407Sobrien			} else if (max2 == 0 &&
843179407Sobrien				   msp->norm == VIDEO_MODE_SECAM) {
844179407Sobrien				/* L NICAM or AM-mono */
845179407Sobrien				msp->second = carrier_detect_65[max2].cdo;
846179407Sobrien				msp3400c_setmode(client, MSP_MODE_AM_NICAM);
847179407Sobrien				msp->nicam_on = 0;
848179407Sobrien				msp3400c_setstereo(client, VIDEO_SOUND_MONO);
849179407Sobrien				msp3400c_setcarrier(client, msp->second, msp->main);
850179407Sobrien				/* volume prescale for SCART (AM mono input) */
851179407Sobrien				msp3400c_write(client,I2C_MSP3400C_DFP, 0x000d, 0x1900);
852179407Sobrien				msp->watch_stereo = 1;
853179407Sobrien			} else if (max2 == 0 && msp->nicam) {
854179407Sobrien				/* D/K NICAM */
855179407Sobrien				msp->second = carrier_detect_65[max2].cdo;
856179407Sobrien				msp3400c_setmode(client, MSP_MODE_FM_NICAM1);
857179407Sobrien				msp->nicam_on = 1;
858179407Sobrien				msp3400c_setcarrier(client, msp->second, msp->main);
859179407Sobrien				msp->watch_stereo = 1;
860179407Sobrien			} else {
861179407Sobrien				goto no_second;
862179407Sobrien			}
863179407Sobrien			break;
864179407Sobrien		case 0: /* 4.5 */
865179407Sobrien		default:
866179407Sobrien		no_second:
867179407Sobrien			msp->second = carrier_detect_main[max1].cdo;
868179407Sobrien			msp3400c_setmode(client, MSP_MODE_FM_TERRA);
869179407Sobrien			msp->nicam_on = 0;
870179407Sobrien			msp3400c_setcarrier(client, msp->second, msp->main);
871179407Sobrien			msp->stereo = VIDEO_SOUND_MONO;
872179407Sobrien			msp3400c_setstereo(client, VIDEO_SOUND_MONO);
873179407Sobrien			break;
874179407Sobrien		}
875179407Sobrien
876179407Sobrien		if (msp->watch_stereo)
877179407Sobrien			watch_stereo(client);
878179407Sobrien
879179407Sobrien		/* unmute + restore dfp registers */
880179407Sobrien		msp3400c_setvolume(client, msp->muted, msp->left, msp->right);
881179407Sobrien		msp3400c_restore_dfp(client);
882179407Sobrien
883179407Sobrien		if (bootverbose)
884179407Sobrien			msp3400c_print_mode(msp);
885179407Sobrien
886179407Sobrien		msp->active = 0;
887179407Sobrien	}
888179407Sobrien
889179407Sobriendone:
890179407Sobrien	dprintk("msp3400: thread: exit\n");
891179407Sobrien	msp->active = 0;
892179407Sobrien
893179407Sobrien	msp->kthread = NULL;
894179407Sobrien	wakeup(&msp->kthread);
895179407Sobrien
896179407Sobrien	kthread_exit(0);
897179407Sobrien}
898179407Sobrien
899179407Sobrien/* ----------------------------------------------------------------------- */
900179407Sobrien/* this one uses the automatic sound standard detection of newer           */
901179407Sobrien/* msp34xx chip versions                                                   */
902179407Sobrien
903179407Sobrienstatic struct MODES {
904179407Sobrien	int retval;
905179407Sobrien	int main, second;
906179407Sobrien	char *name;
907179407Sobrien} modelist[] = {
908179407Sobrien	{ 0x0000, 0, 0, "ERROR" },
909179407Sobrien	{ 0x0001, 0, 0, "autodetect start" },
910179407Sobrien	{ 0x0002, MSP_CARRIER(4.5), MSP_CARRIER(4.72), "4.5/4.72  M Dual FM-Stereo" },
911179407Sobrien	{ 0x0003, MSP_CARRIER(5.5), MSP_CARRIER(5.7421875), "5.5/5.74  B/G Dual FM-Stereo" },
912179407Sobrien	{ 0x0004, MSP_CARRIER(6.5), MSP_CARRIER(6.2578125), "6.5/6.25  D/K1 Dual FM-Stereo" },
913179407Sobrien	{ 0x0005, MSP_CARRIER(6.5), MSP_CARRIER(6.7421875), "6.5/6.74  D/K2 Dual FM-Stereo" },
914179407Sobrien	{ 0x0006, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5  D/K FM-Mono (HDEV3)" },
915179407Sobrien	{ 0x0008, MSP_CARRIER(5.5), MSP_CARRIER(5.85), "5.5/5.85  B/G NICAM FM" },
916179407Sobrien	{ 0x0009, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85  L NICAM AM" },
917179407Sobrien	{ 0x000a, MSP_CARRIER(6.0), MSP_CARRIER(6.55), "6.0/6.55  I NICAM FM" },
918179407Sobrien	{ 0x000b, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85  D/K NICAM FM" },
919179407Sobrien	{ 0x000c, MSP_CARRIER(6.5), MSP_CARRIER(5.85), "6.5/5.85  D/K NICAM FM (HDEV2)" },
920179407Sobrien	{ 0x0020, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5  M BTSC-Stereo" },
921179407Sobrien	{ 0x0021, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5  M BTSC-Mono + SAP" },
922179407Sobrien	{ 0x0030, MSP_CARRIER(4.5), MSP_CARRIER(4.5), "4.5  M EIA-J Japan Stereo" },
923179407Sobrien	{ 0x0040, MSP_CARRIER(10.7), MSP_CARRIER(10.7), "10.7  FM-Stereo Radio" },
924179407Sobrien	{ 0x0050, MSP_CARRIER(6.5), MSP_CARRIER(6.5), "6.5  SAT-Mono" },
925179407Sobrien	{ 0x0051, MSP_CARRIER(7.02), MSP_CARRIER(7.20), "7.02/7.20  SAT-Stereo" },
926179407Sobrien	{ 0x0060, MSP_CARRIER(7.2), MSP_CARRIER(7.2), "7.2  SAT ADR" },
927179407Sobrien	{     -1, 0, 0, NULL }, /* EOF */
928179407Sobrien};
929179407Sobrien
930179407Sobrienstatic void msp3410d_thread(void *data)
931179407Sobrien{
932179407Sobrien	bktr_ptr_t client = data;
933179407Sobrien	struct msp3400c *msp = (struct msp3400c*)client->msp3400c_info;
934179407Sobrien	int mode,val,i,std;
935179407Sobrien	int timo = 0;
936179407Sobrien
937179407Sobrien	dprintk("msp3410: thread started\n");
938179407Sobrien
939179407Sobrien	for (;;) {
940179407Sobrien		if (msp->rmmod)
941179407Sobrien			goto done;
942179407Sobrien		if (!msp->watch_stereo)
943179407Sobrien			timo = 0;
944179407Sobrien		else
945179407Sobrien			timo = 10*hz;
946179407Sobrien		tsleep(msp->kthread, PRIBIO, "idle", timo);
947179407Sobrien		if (msp->rmmod)
948179407Sobrien			goto done;
949179407Sobrien		if (msp->halt_thread) {
950179407Sobrien			msp->watch_stereo = 0;
951179407Sobrien			msp->halt_thread = 0;
952179407Sobrien			dprintk("msp3410: thread halted\n");
953179407Sobrien			continue;
954179407Sobrien		}
955179407Sobrien
956179407Sobrien		if (msp->mode == MSP_MODE_EXTERN)
957179407Sobrien			continue;
958179407Sobrien
959179407Sobrien		msp->active = 1;
960179407Sobrien
961179407Sobrien		if (msp->watch_stereo) {
962179407Sobrien			watch_stereo(client);
963179407Sobrien			msp->active = 0;
964179407Sobrien			continue;
965179407Sobrien		}
966179407Sobrien
967179407Sobrien		/* some time for the tuner to sync */
968179407Sobrien		tsleep(msp->kthread, PRIBIO, "tuner sync", hz/2);
969179407Sobrien
970179407Sobrien	restart:
971179407Sobrien		if (msp->mode == MSP_MODE_EXTERN)
972179407Sobrien			continue;
973179407Sobrien		msp->restart = 0;
974179407Sobrien		msp->watch_stereo = 0;
975179407Sobrien
976179407Sobrien		/* put into sane state (and mute) */
977179407Sobrien		msp3400c_reset(client);
978179407Sobrien
979179407Sobrien		/* start autodetect */
980218822Sdim		switch (msp->norm) {
981218822Sdim		case VIDEO_MODE_PAL:
982218822Sdim			mode = 0x1003;
983218822Sdim			std  = 1;
984218822Sdim			break;
985218822Sdim		case VIDEO_MODE_NTSC:  /* BTSC */
986218822Sdim			mode = 0x2003;
987218822Sdim			std  = 0x0020;
988218822Sdim			break;
989218822Sdim		case VIDEO_MODE_SECAM:
990218822Sdim			mode = 0x0003;
991179407Sobrien			std  = 1;
992179407Sobrien			break;
993179407Sobrien		case VIDEO_MODE_RADIO:
994179407Sobrien			mode = 0x0003;
995179407Sobrien			std  = 0x0040;
996179407Sobrien			break;
997179407Sobrien		default:
998179407Sobrien			mode = 0x0003;
999179407Sobrien			std  = 1;
1000179407Sobrien			break;
1001179407Sobrien		}
1002179407Sobrien		msp3400c_write(client, I2C_MSP3400C_DEM, 0x30, mode);
1003179407Sobrien		msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, std);
1004179407Sobrien
1005179407Sobrien		if (bootverbose) {
1006179407Sobrien			int i;
1007179407Sobrien			for (i = 0; modelist[i].name != NULL; i++)
1008179407Sobrien				if (modelist[i].retval == std)
1009179407Sobrien					break;
1010179407Sobrien			dprintk("msp3410: setting mode: %s (0x%04x)\n",
1011179407Sobrien			       modelist[i].name ? modelist[i].name : "unknown",std);
1012179407Sobrien		}
1013179407Sobrien
1014179407Sobrien		if (std != 1) {
1015179407Sobrien			/* programmed some specific mode */
1016179407Sobrien			val = std;
1017179407Sobrien		} else {
1018179407Sobrien			/* triggered autodetect */
1019179407Sobrien			for (;;) {
1020179407Sobrien				tsleep(msp->kthread, PRIBIO, "autodetection", hz/10);
1021218822Sdim				if (msp->restart)
1022218822Sdim					goto restart;
1023218822Sdim
1024218822Sdim				/* check results */
1025218822Sdim				val = msp3400c_read(client, I2C_MSP3400C_DEM, 0x7e);
1026218822Sdim				if (val < 0x07ff)
1027218822Sdim					break;
1028218822Sdim				dprintk("msp3410: detection still in progress\n");
1029218822Sdim			}
1030218822Sdim		}
1031179407Sobrien		for (i = 0; modelist[i].name != NULL; i++)
1032179407Sobrien			if (modelist[i].retval == val)
1033179407Sobrien				break;
1034179407Sobrien		dprintk("msp3410: current mode: %s (0x%04x)\n",
1035179407Sobrien			modelist[i].name ? modelist[i].name : "unknown",
1036179407Sobrien			val);
1037179407Sobrien		msp->main   = modelist[i].main;
1038179407Sobrien		msp->second = modelist[i].second;
1039179407Sobrien
1040179407Sobrien		if (client->amsound && (msp->norm == VIDEO_MODE_SECAM) && (val != 0x0009)) {
1041179407Sobrien			/* autodetection has failed, let backup */
1042179407Sobrien			dprintk("msp3410: autodetection failed, switching to backup mode: %s (0x%04x)\n",
1043179407Sobrien				modelist[8].name ? modelist[8].name : "unknown",val);
1044179407Sobrien			val = 0x0009;
1045179407Sobrien			msp3400c_write(client, I2C_MSP3400C_DEM, 0x20, val);
1046179407Sobrien		}
1047179407Sobrien
1048179407Sobrien		/* set various prescales */
1049179407Sobrien		msp3400c_write(client, I2C_MSP3400C_DFP, 0x0d, 0x1900); /* scart */
1050179407Sobrien		msp3400c_write(client, I2C_MSP3400C_DFP, 0x0e, 0x2403); /* FM */
1051179407Sobrien		msp3400c_write(client, I2C_MSP3400C_DFP, 0x10, 0x5a00); /* nicam */
1052179407Sobrien
1053179407Sobrien		/* set stereo */
1054179407Sobrien		switch (val) {
1055179407Sobrien		case 0x0008: /* B/G NICAM */
1056179407Sobrien		case 0x000a: /* I NICAM */
1057179407Sobrien			if (val == 0x0008)
1058179407Sobrien				msp->mode = MSP_MODE_FM_NICAM1;
1059179407Sobrien			else
1060179407Sobrien				msp->mode = MSP_MODE_FM_NICAM2;
1061179407Sobrien			/* just turn on stereo */
1062179407Sobrien			msp->stereo = VIDEO_SOUND_STEREO;
1063179407Sobrien			msp->nicam_on = 1;
1064218822Sdim			msp->watch_stereo = 1;
1065179407Sobrien			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
1066179407Sobrien			break;
1067179407Sobrien		case 0x0009:
1068179407Sobrien			msp->mode = MSP_MODE_AM_NICAM;
1069179407Sobrien			msp->stereo = VIDEO_SOUND_MONO;
1070179407Sobrien			msp->nicam_on = 1;
1071179407Sobrien			msp3400c_setstereo(client,VIDEO_SOUND_MONO);
1072179407Sobrien			msp->watch_stereo = 1;
1073179407Sobrien			break;
1074179407Sobrien		case 0x0020: /* BTSC */
1075179407Sobrien			/* just turn on stereo */
1076179407Sobrien			msp->mode   = MSP_MODE_BTSC;
1077179407Sobrien			msp->stereo = VIDEO_SOUND_STEREO;
1078179407Sobrien			msp->nicam_on = 0;
1079179407Sobrien			msp->watch_stereo = 1;
1080179407Sobrien			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
1081179407Sobrien			break;
1082179407Sobrien		case 0x0040: /* FM radio */
1083179407Sobrien			msp->mode   = MSP_MODE_FM_RADIO;
1084179407Sobrien			msp->stereo = VIDEO_SOUND_STEREO;
1085179407Sobrien			msp->nicam_on = 0;
1086179407Sobrien			msp->watch_stereo = 0;
1087179407Sobrien			/* scart routing */
1088179407Sobrien			msp3400c_set_scart(client,SCART_IN2,0);
1089179407Sobrien			msp3400c_write(client,I2C_MSP3400C_DFP, 0x08, 0x0220);
1090179407Sobrien			msp3400c_write(client,I2C_MSP3400C_DFP, 0x09, 0x0220);
1091179407Sobrien			msp3400c_write(client,I2C_MSP3400C_DFP, 0x0b, 0x0220);
1092179407Sobrien			break;
1093179407Sobrien		case 0x0003:
1094179407Sobrien			msp->mode   = MSP_MODE_FM_TERRA;
1095179407Sobrien			msp->stereo = VIDEO_SOUND_STEREO;
1096179407Sobrien			msp->nicam_on = 0;
1097179407Sobrien			msp->watch_stereo = 1;
1098179407Sobrien			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
1099179407Sobrien			break;
1100179407Sobrien		}
1101179407Sobrien
1102179407Sobrien		if (msp->watch_stereo)
1103179407Sobrien			watch_stereo(client);
1104179407Sobrien
1105179407Sobrien		/* unmute + restore dfp registers */
1106179407Sobrien		msp3400c_setbass(client, msp->bass);
1107179407Sobrien		msp3400c_settreble(client, msp->treble);
1108179407Sobrien		msp3400c_setvolume(client, msp->muted, msp->left, msp->right);
1109179407Sobrien		msp3400c_restore_dfp(client);
1110179407Sobrien
1111179407Sobrien		msp->active = 0;
1112179407Sobrien	}
1113179407Sobrien
1114179407Sobriendone:
1115179407Sobrien	dprintk("msp3410: thread: exit\n");
1116179407Sobrien	msp->active = 0;
1117179407Sobrien
1118179407Sobrien	msp->kthread = NULL;
1119179407Sobrien	wakeup(&msp->kthread);
1120179407Sobrien
1121179407Sobrien	kthread_exit(0);
1122179407Sobrien}
1123179407Sobrien
1124179407Sobrienint msp_attach(bktr_ptr_t bktr)
1125179407Sobrien{
1126179407Sobrien	struct msp3400c *msp;
1127179407Sobrien	int              rev1,rev2,i;
1128179407Sobrien	int		 err;
1129179407Sobrien	char		 buf[20];
1130179407Sobrien
1131179407Sobrien	msp = (struct msp3400c *) malloc(sizeof(struct msp3400c), M_DEVBUF, M_NOWAIT);
1132179407Sobrien	if (msp == NULL)
1133179407Sobrien                return ENOMEM;
1134179407Sobrien	bktr->msp3400c_info = msp;
1135179407Sobrien
1136179407Sobrien	memset(msp,0,sizeof(struct msp3400c));
1137218822Sdim	msp->left   = 65535;
1138179407Sobrien	msp->right  = 65535;
1139179407Sobrien	msp->bass   = 32768;
1140179407Sobrien	msp->treble = 32768;
1141179407Sobrien	msp->input  = -1;
1142179407Sobrien	msp->threaddesc = malloc(15 * sizeof(char), M_DEVBUF, M_NOWAIT);
1143179407Sobrien	if (msp->threaddesc == NULL) {
1144179407Sobrien		free(msp, M_DEVBUF);
1145179407Sobrien                return ENOMEM;
1146179407Sobrien	}
1147179407Sobrien	snprintf(msp->threaddesc, 14, "%s_msp34xx_thread", bktr->bktr_xname);
1148179407Sobrien
1149179407Sobrien	for (i = 0; i < DFP_COUNT; i++)
1150179407Sobrien		msp->dfp_regs[i] = -1;
1151179407Sobrien
1152179407Sobrien	msp3400c_reset(bktr);
1153179407Sobrien
1154179407Sobrien	rev1 = msp3400c_read(bktr, I2C_MSP3400C_DFP, 0x1e);
1155179407Sobrien	if (-1 != rev1)
1156179407Sobrien		rev2 = msp3400c_read(bktr, I2C_MSP3400C_DFP, 0x1f);
1157179407Sobrien	if ((-1 == rev1) || (0 == rev1 && 0 == rev2)) {
1158179407Sobrien		free(msp->threaddesc, M_DEVBUF);
1159179407Sobrien		free(msp, M_DEVBUF);
1160179407Sobrien		bktr->msp3400c_info = NULL;
1161179407Sobrien		printf("%s: msp3400: error while reading chip version\n", bktr_name(bktr));
1162179407Sobrien		return ENXIO;
1163218822Sdim	}
1164218822Sdim
1165218822Sdim#if 0
1166218822Sdim	/* this will turn on a 1kHz beep - might be useful for debugging... */
1167218822Sdim	msp3400c_write(bktr,I2C_MSP3400C_DFP, 0x0014, 0x1040);
1168218822Sdim#endif
1169218822Sdim
1170218822Sdim	sprintf(buf,"MSP34%02d%c-%c%d",
1171179407Sobrien		(rev2>>8)&0xff, (rev1&0xff)+'@', ((rev1>>8)&0xff)+'@', rev2&0x1f);
1172218822Sdim	msp->nicam = (((rev2>>8)&0xff) != 00) ? 1 : 0;
1173179407Sobrien
1174179407Sobrien	if (bktr->mspsimple == -1) {
1175179407Sobrien		/* default mode */
1176179407Sobrien		/* msp->simple = (((rev2>>8)&0xff) == 0) ? 0 : 1; */
1177179407Sobrien		msp->simple = ((rev1&0xff)+'@' > 'C');
1178179407Sobrien	} else {
1179179407Sobrien		/* use kenv value */
1180179407Sobrien		msp->simple = bktr->mspsimple;
1181179407Sobrien	}
1182179407Sobrien
1183179407Sobrien	/* hello world :-) */
1184179407Sobrien	if (bootverbose) {
1185179407Sobrien		printf("%s: msp34xx: init: chip=%s", bktr_name(bktr), buf);
1186179407Sobrien		if (msp->nicam)
1187179407Sobrien			printf(", has NICAM support");
1188179407Sobrien		printf("\n");
1189179407Sobrien	}
1190179407Sobrien
1191179407Sobrien	/* startup control thread */
1192179407Sobrien	err = kthread_create(msp->simple ? msp3410d_thread : msp3400c_thread,
1193179407Sobrien			     bktr, &msp->kthread, (RFFDG | RFPROC), 0,
1194179407Sobrien			     msp->threaddesc);
1195218822Sdim	if (err) {
1196179407Sobrien		printf("%s: Error returned by kthread_create: %d", bktr_name(bktr), err);
1197179407Sobrien		free(msp->threaddesc, M_DEVBUF);
1198179407Sobrien		free(msp, M_DEVBUF);
1199179407Sobrien		bktr->msp3400c_info = NULL;
1200179407Sobrien		return ENXIO;
1201179407Sobrien	}
1202179407Sobrien	wakeup(msp->kthread);
1203179407Sobrien
1204179407Sobrien	/* done */
1205218822Sdim	return 0;
1206218822Sdim}
1207218822Sdim
1208218822Sdimint msp_detach(bktr_ptr_t client)
1209218822Sdim{
1210218822Sdim	struct msp3400c *msp  = (struct msp3400c*)client->msp3400c_info;
1211218822Sdim
1212218822Sdim	/* shutdown control thread */
1213218822Sdim	if (msp->kthread)
1214218822Sdim	{
1215218822Sdim		/* XXX mutex lock required */
1216218822Sdim		msp->rmmod = 1;
1217218822Sdim		msp->watch_stereo = 0;
1218218822Sdim		wakeup(msp->kthread);
1219179407Sobrien
1220179407Sobrien		while (msp->kthread)
1221218822Sdim			tsleep(&msp->kthread, PRIBIO, "wait for kthread", hz/10);
1222218822Sdim	}
1223218822Sdim
1224218822Sdim	if (client->msp3400c_info != NULL) {
1225218822Sdim		free(client->msp3400c_info, M_DEVBUF);
1226218822Sdim		client->msp3400c_info = NULL;
1227218822Sdim	}
1228218822Sdim
1229179407Sobrien    	msp3400c_reset(client);
1230179407Sobrien
1231179407Sobrien	return 0;
1232179407Sobrien}
1233179407Sobrien
1234179407Sobrienvoid msp_wake_thread(bktr_ptr_t client)
1235179407Sobrien{
1236218822Sdim	struct msp3400c *msp  = (struct msp3400c*)client->msp3400c_info;
1237179407Sobrien
1238179407Sobrien	msp3400c_setvolume(client,msp->muted,0,0);
1239179407Sobrien	msp->watch_stereo=0;
1240179407Sobrien	if (msp->active)
1241179407Sobrien		msp->restart = 1;
1242179407Sobrien	wakeup(msp->kthread);
1243179407Sobrien}
1244179407Sobrien
1245218822Sdimvoid msp_halt_thread(bktr_ptr_t client)
1246218822Sdim{
1247218822Sdim	struct msp3400c *msp  = (struct msp3400c*)client->msp3400c_info;
1248218822Sdim
1249218822Sdim	msp3400c_setvolume(client,msp->muted,0,0);
1250218822Sdim	if (msp->active)
1251218822Sdim		msp->restart = 1;
1252179407Sobrien	msp->halt_thread = 1;
1253179407Sobrien	wakeup(msp->kthread);
1254179407Sobrien}
1255179407Sobrien#endif /* BKTR_NEW_MSP34XX_DRIVER */
1256179407Sobrien