162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#include <linux/module.h>
362306a36Sopenharmony_ci#include <linux/kernel.h>
462306a36Sopenharmony_ci#include <linux/i2c.h>
562306a36Sopenharmony_ci#include <linux/types.h>
662306a36Sopenharmony_ci#include <linux/init.h>
762306a36Sopenharmony_ci#include <linux/errno.h>
862306a36Sopenharmony_ci#include <linux/delay.h>
962306a36Sopenharmony_ci#include <linux/videodev2.h>
1062306a36Sopenharmony_ci#include <media/v4l2-common.h>
1162306a36Sopenharmony_ci#include <media/tuner.h>
1262306a36Sopenharmony_ci#include "tuner-i2c.h"
1362306a36Sopenharmony_ci#include "tda9887.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/* Chips:
1762306a36Sopenharmony_ci   TDA9885 (PAL, NTSC)
1862306a36Sopenharmony_ci   TDA9886 (PAL, SECAM, NTSC)
1962306a36Sopenharmony_ci   TDA9887 (PAL, SECAM, NTSC, FM Radio)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci   Used as part of several tuners
2262306a36Sopenharmony_ci*/
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic int debug;
2562306a36Sopenharmony_cimodule_param(debug, int, 0644);
2662306a36Sopenharmony_ciMODULE_PARM_DESC(debug, "enable verbose debug messages");
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic DEFINE_MUTEX(tda9887_list_mutex);
2962306a36Sopenharmony_cistatic LIST_HEAD(hybrid_tuner_instance_list);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct tda9887_priv {
3262306a36Sopenharmony_ci	struct tuner_i2c_props i2c_props;
3362306a36Sopenharmony_ci	struct list_head hybrid_tuner_instance_list;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	unsigned char	   data[4];
3662306a36Sopenharmony_ci	unsigned int       config;
3762306a36Sopenharmony_ci	unsigned int       mode;
3862306a36Sopenharmony_ci	unsigned int       audmode;
3962306a36Sopenharmony_ci	v4l2_std_id        std;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	bool               standby;
4262306a36Sopenharmony_ci};
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/* ---------------------------------------------------------------------- */
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci#define UNSET       (-1U)
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistruct tvnorm {
4962306a36Sopenharmony_ci	v4l2_std_id       std;
5062306a36Sopenharmony_ci	char              *name;
5162306a36Sopenharmony_ci	unsigned char     b;
5262306a36Sopenharmony_ci	unsigned char     c;
5362306a36Sopenharmony_ci	unsigned char     e;
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/* ---------------------------------------------------------------------- */
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci//
5962306a36Sopenharmony_ci// TDA defines
6062306a36Sopenharmony_ci//
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci//// first reg (b)
6362306a36Sopenharmony_ci#define cVideoTrapBypassOFF     0x00    // bit b0
6462306a36Sopenharmony_ci#define cVideoTrapBypassON      0x01    // bit b0
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci#define cAutoMuteFmInactive     0x00    // bit b1
6762306a36Sopenharmony_ci#define cAutoMuteFmActive       0x02    // bit b1
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci#define cIntercarrier           0x00    // bit b2
7062306a36Sopenharmony_ci#define cQSS                    0x04    // bit b2
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci#define cPositiveAmTV           0x00    // bit b3:4
7362306a36Sopenharmony_ci#define cFmRadio                0x08    // bit b3:4
7462306a36Sopenharmony_ci#define cNegativeFmTV           0x10    // bit b3:4
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci#define cForcedMuteAudioON      0x20    // bit b5
7862306a36Sopenharmony_ci#define cForcedMuteAudioOFF     0x00    // bit b5
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci#define cOutputPort1Active      0x00    // bit b6
8162306a36Sopenharmony_ci#define cOutputPort1Inactive    0x40    // bit b6
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci#define cOutputPort2Active      0x00    // bit b7
8462306a36Sopenharmony_ci#define cOutputPort2Inactive    0x80    // bit b7
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci//// second reg (c)
8862306a36Sopenharmony_ci#define cDeemphasisOFF          0x00    // bit c5
8962306a36Sopenharmony_ci#define cDeemphasisON           0x20    // bit c5
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci#define cDeemphasis75           0x00    // bit c6
9262306a36Sopenharmony_ci#define cDeemphasis50           0x40    // bit c6
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci#define cAudioGain0             0x00    // bit c7
9562306a36Sopenharmony_ci#define cAudioGain6             0x80    // bit c7
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci#define cTopMask                0x1f    // bit c0:4
9862306a36Sopenharmony_ci#define cTopDefault		0x10	// bit c0:4
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci//// third reg (e)
10162306a36Sopenharmony_ci#define cAudioIF_4_5             0x00    // bit e0:1
10262306a36Sopenharmony_ci#define cAudioIF_5_5             0x01    // bit e0:1
10362306a36Sopenharmony_ci#define cAudioIF_6_0             0x02    // bit e0:1
10462306a36Sopenharmony_ci#define cAudioIF_6_5             0x03    // bit e0:1
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci#define cVideoIFMask		0x1c	// bit e2:4
10862306a36Sopenharmony_ci/* Video IF selection in TV Mode (bit B3=0) */
10962306a36Sopenharmony_ci#define cVideoIF_58_75           0x00    // bit e2:4
11062306a36Sopenharmony_ci#define cVideoIF_45_75           0x04    // bit e2:4
11162306a36Sopenharmony_ci#define cVideoIF_38_90           0x08    // bit e2:4
11262306a36Sopenharmony_ci#define cVideoIF_38_00           0x0C    // bit e2:4
11362306a36Sopenharmony_ci#define cVideoIF_33_90           0x10    // bit e2:4
11462306a36Sopenharmony_ci#define cVideoIF_33_40           0x14    // bit e2:4
11562306a36Sopenharmony_ci#define cRadioIF_45_75           0x18    // bit e2:4
11662306a36Sopenharmony_ci#define cRadioIF_38_90           0x1C    // bit e2:4
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci/* IF1 selection in Radio Mode (bit B3=1) */
11962306a36Sopenharmony_ci#define cRadioIF_33_30		0x00	// bit e2,4 (also 0x10,0x14)
12062306a36Sopenharmony_ci#define cRadioIF_41_30		0x04	// bit e2,4
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci/* Output of AFC pin in radio mode when bit E7=1 */
12362306a36Sopenharmony_ci#define cRadioAGC_SIF		0x00	// bit e3
12462306a36Sopenharmony_ci#define cRadioAGC_FM		0x08	// bit e3
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci#define cTunerGainNormal         0x00    // bit e5
12762306a36Sopenharmony_ci#define cTunerGainLow            0x20    // bit e5
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci#define cGating_18               0x00    // bit e6
13062306a36Sopenharmony_ci#define cGating_36               0x40    // bit e6
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci#define cAgcOutON                0x80    // bit e7
13362306a36Sopenharmony_ci#define cAgcOutOFF               0x00    // bit e7
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci/* ---------------------------------------------------------------------- */
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic struct tvnorm tvnorms[] = {
13862306a36Sopenharmony_ci	{
13962306a36Sopenharmony_ci		.std   = V4L2_STD_PAL_BG | V4L2_STD_PAL_H | V4L2_STD_PAL_N,
14062306a36Sopenharmony_ci		.name  = "PAL-BGHN",
14162306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
14262306a36Sopenharmony_ci			   cQSS           ),
14362306a36Sopenharmony_ci		.c     = ( cDeemphasisON  |
14462306a36Sopenharmony_ci			   cDeemphasis50  |
14562306a36Sopenharmony_ci			   cTopDefault),
14662306a36Sopenharmony_ci		.e     = ( cGating_36     |
14762306a36Sopenharmony_ci			   cAudioIF_5_5   |
14862306a36Sopenharmony_ci			   cVideoIF_38_90 ),
14962306a36Sopenharmony_ci	},{
15062306a36Sopenharmony_ci		.std   = V4L2_STD_PAL_I,
15162306a36Sopenharmony_ci		.name  = "PAL-I",
15262306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
15362306a36Sopenharmony_ci			   cQSS           ),
15462306a36Sopenharmony_ci		.c     = ( cDeemphasisON  |
15562306a36Sopenharmony_ci			   cDeemphasis50  |
15662306a36Sopenharmony_ci			   cTopDefault),
15762306a36Sopenharmony_ci		.e     = ( cGating_36     |
15862306a36Sopenharmony_ci			   cAudioIF_6_0   |
15962306a36Sopenharmony_ci			   cVideoIF_38_90 ),
16062306a36Sopenharmony_ci	},{
16162306a36Sopenharmony_ci		.std   = V4L2_STD_PAL_DK,
16262306a36Sopenharmony_ci		.name  = "PAL-DK",
16362306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
16462306a36Sopenharmony_ci			   cQSS           ),
16562306a36Sopenharmony_ci		.c     = ( cDeemphasisON  |
16662306a36Sopenharmony_ci			   cDeemphasis50  |
16762306a36Sopenharmony_ci			   cTopDefault),
16862306a36Sopenharmony_ci		.e     = ( cGating_36     |
16962306a36Sopenharmony_ci			   cAudioIF_6_5   |
17062306a36Sopenharmony_ci			   cVideoIF_38_90 ),
17162306a36Sopenharmony_ci	},{
17262306a36Sopenharmony_ci		.std   = V4L2_STD_PAL_M | V4L2_STD_PAL_Nc,
17362306a36Sopenharmony_ci		.name  = "PAL-M/Nc",
17462306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
17562306a36Sopenharmony_ci			   cQSS           ),
17662306a36Sopenharmony_ci		.c     = ( cDeemphasisON  |
17762306a36Sopenharmony_ci			   cDeemphasis75  |
17862306a36Sopenharmony_ci			   cTopDefault),
17962306a36Sopenharmony_ci		.e     = ( cGating_36     |
18062306a36Sopenharmony_ci			   cAudioIF_4_5   |
18162306a36Sopenharmony_ci			   cVideoIF_45_75 ),
18262306a36Sopenharmony_ci	},{
18362306a36Sopenharmony_ci		.std   = V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H,
18462306a36Sopenharmony_ci		.name  = "SECAM-BGH",
18562306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
18662306a36Sopenharmony_ci			   cQSS           ),
18762306a36Sopenharmony_ci		.c     = ( cTopDefault),
18862306a36Sopenharmony_ci		.e     = ( cAudioIF_5_5   |
18962306a36Sopenharmony_ci			   cVideoIF_38_90 ),
19062306a36Sopenharmony_ci	},{
19162306a36Sopenharmony_ci		.std   = V4L2_STD_SECAM_L,
19262306a36Sopenharmony_ci		.name  = "SECAM-L",
19362306a36Sopenharmony_ci		.b     = ( cPositiveAmTV  |
19462306a36Sopenharmony_ci			   cQSS           ),
19562306a36Sopenharmony_ci		.c     = ( cTopDefault),
19662306a36Sopenharmony_ci		.e     = ( cGating_36	  |
19762306a36Sopenharmony_ci			   cAudioIF_6_5   |
19862306a36Sopenharmony_ci			   cVideoIF_38_90 ),
19962306a36Sopenharmony_ci	},{
20062306a36Sopenharmony_ci		.std   = V4L2_STD_SECAM_LC,
20162306a36Sopenharmony_ci		.name  = "SECAM-L'",
20262306a36Sopenharmony_ci		.b     = ( cOutputPort2Inactive |
20362306a36Sopenharmony_ci			   cPositiveAmTV  |
20462306a36Sopenharmony_ci			   cQSS           ),
20562306a36Sopenharmony_ci		.c     = ( cTopDefault),
20662306a36Sopenharmony_ci		.e     = ( cGating_36	  |
20762306a36Sopenharmony_ci			   cAudioIF_6_5   |
20862306a36Sopenharmony_ci			   cVideoIF_33_90 ),
20962306a36Sopenharmony_ci	},{
21062306a36Sopenharmony_ci		.std   = V4L2_STD_SECAM_DK,
21162306a36Sopenharmony_ci		.name  = "SECAM-DK",
21262306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
21362306a36Sopenharmony_ci			   cQSS           ),
21462306a36Sopenharmony_ci		.c     = ( cDeemphasisON  |
21562306a36Sopenharmony_ci			   cDeemphasis50  |
21662306a36Sopenharmony_ci			   cTopDefault),
21762306a36Sopenharmony_ci		.e     = ( cGating_36     |
21862306a36Sopenharmony_ci			   cAudioIF_6_5   |
21962306a36Sopenharmony_ci			   cVideoIF_38_90 ),
22062306a36Sopenharmony_ci	},{
22162306a36Sopenharmony_ci		.std   = V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_KR,
22262306a36Sopenharmony_ci		.name  = "NTSC-M",
22362306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
22462306a36Sopenharmony_ci			   cQSS           ),
22562306a36Sopenharmony_ci		.c     = ( cDeemphasisON  |
22662306a36Sopenharmony_ci			   cDeemphasis75  |
22762306a36Sopenharmony_ci			   cTopDefault),
22862306a36Sopenharmony_ci		.e     = ( cGating_36     |
22962306a36Sopenharmony_ci			   cAudioIF_4_5   |
23062306a36Sopenharmony_ci			   cVideoIF_45_75 ),
23162306a36Sopenharmony_ci	},{
23262306a36Sopenharmony_ci		.std   = V4L2_STD_NTSC_M_JP,
23362306a36Sopenharmony_ci		.name  = "NTSC-M-JP",
23462306a36Sopenharmony_ci		.b     = ( cNegativeFmTV  |
23562306a36Sopenharmony_ci			   cQSS           ),
23662306a36Sopenharmony_ci		.c     = ( cDeemphasisON  |
23762306a36Sopenharmony_ci			   cDeemphasis50  |
23862306a36Sopenharmony_ci			   cTopDefault),
23962306a36Sopenharmony_ci		.e     = ( cGating_36     |
24062306a36Sopenharmony_ci			   cAudioIF_4_5   |
24162306a36Sopenharmony_ci			   cVideoIF_58_75 ),
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci};
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic struct tvnorm radio_stereo = {
24662306a36Sopenharmony_ci	.name = "Radio Stereo",
24762306a36Sopenharmony_ci	.b    = ( cFmRadio       |
24862306a36Sopenharmony_ci		  cQSS           ),
24962306a36Sopenharmony_ci	.c    = ( cDeemphasisOFF |
25062306a36Sopenharmony_ci		  cAudioGain6    |
25162306a36Sopenharmony_ci		  cTopDefault),
25262306a36Sopenharmony_ci	.e    = ( cTunerGainLow  |
25362306a36Sopenharmony_ci		  cAudioIF_5_5   |
25462306a36Sopenharmony_ci		  cRadioIF_38_90 ),
25562306a36Sopenharmony_ci};
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic struct tvnorm radio_mono = {
25862306a36Sopenharmony_ci	.name = "Radio Mono",
25962306a36Sopenharmony_ci	.b    = ( cFmRadio       |
26062306a36Sopenharmony_ci		  cQSS           ),
26162306a36Sopenharmony_ci	.c    = ( cDeemphasisON  |
26262306a36Sopenharmony_ci		  cDeemphasis75  |
26362306a36Sopenharmony_ci		  cTopDefault),
26462306a36Sopenharmony_ci	.e    = ( cTunerGainLow  |
26562306a36Sopenharmony_ci		  cAudioIF_5_5   |
26662306a36Sopenharmony_ci		  cRadioIF_38_90 ),
26762306a36Sopenharmony_ci};
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci/* ---------------------------------------------------------------------- */
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cistatic void dump_read_message(struct dvb_frontend *fe, unsigned char *buf)
27262306a36Sopenharmony_ci{
27362306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	static char *afc[16] = {
27662306a36Sopenharmony_ci		"- 12.5 kHz",
27762306a36Sopenharmony_ci		"- 37.5 kHz",
27862306a36Sopenharmony_ci		"- 62.5 kHz",
27962306a36Sopenharmony_ci		"- 87.5 kHz",
28062306a36Sopenharmony_ci		"-112.5 kHz",
28162306a36Sopenharmony_ci		"-137.5 kHz",
28262306a36Sopenharmony_ci		"-162.5 kHz",
28362306a36Sopenharmony_ci		"-187.5 kHz [min]",
28462306a36Sopenharmony_ci		"+187.5 kHz [max]",
28562306a36Sopenharmony_ci		"+162.5 kHz",
28662306a36Sopenharmony_ci		"+137.5 kHz",
28762306a36Sopenharmony_ci		"+112.5 kHz",
28862306a36Sopenharmony_ci		"+ 87.5 kHz",
28962306a36Sopenharmony_ci		"+ 62.5 kHz",
29062306a36Sopenharmony_ci		"+ 37.5 kHz",
29162306a36Sopenharmony_ci		"+ 12.5 kHz",
29262306a36Sopenharmony_ci	};
29362306a36Sopenharmony_ci	tuner_info("read: 0x%2x\n", buf[0]);
29462306a36Sopenharmony_ci	tuner_info("  after power on : %s\n", (buf[0] & 0x01) ? "yes" : "no");
29562306a36Sopenharmony_ci	tuner_info("  afc            : %s\n", afc[(buf[0] >> 1) & 0x0f]);
29662306a36Sopenharmony_ci	tuner_info("  fmif level     : %s\n", (buf[0] & 0x20) ? "high" : "low");
29762306a36Sopenharmony_ci	tuner_info("  afc window     : %s\n", (buf[0] & 0x40) ? "in" : "out");
29862306a36Sopenharmony_ci	tuner_info("  vfi level      : %s\n", (buf[0] & 0x80) ? "high" : "low");
29962306a36Sopenharmony_ci}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistatic void dump_write_message(struct dvb_frontend *fe, unsigned char *buf)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	static char *sound[4] = {
30662306a36Sopenharmony_ci		"AM/TV",
30762306a36Sopenharmony_ci		"FM/radio",
30862306a36Sopenharmony_ci		"FM/TV",
30962306a36Sopenharmony_ci		"FM/radio"
31062306a36Sopenharmony_ci	};
31162306a36Sopenharmony_ci	static char *adjust[32] = {
31262306a36Sopenharmony_ci		"-16", "-15", "-14", "-13", "-12", "-11", "-10", "-9",
31362306a36Sopenharmony_ci		"-8",  "-7",  "-6",  "-5",  "-4",  "-3",  "-2",  "-1",
31462306a36Sopenharmony_ci		"0",   "+1",  "+2",  "+3",  "+4",  "+5",  "+6",  "+7",
31562306a36Sopenharmony_ci		"+8",  "+9",  "+10", "+11", "+12", "+13", "+14", "+15"
31662306a36Sopenharmony_ci	};
31762306a36Sopenharmony_ci	static char *deemph[4] = {
31862306a36Sopenharmony_ci		"no", "no", "75", "50"
31962306a36Sopenharmony_ci	};
32062306a36Sopenharmony_ci	static char *carrier[4] = {
32162306a36Sopenharmony_ci		"4.5 MHz",
32262306a36Sopenharmony_ci		"5.5 MHz",
32362306a36Sopenharmony_ci		"6.0 MHz",
32462306a36Sopenharmony_ci		"6.5 MHz / AM"
32562306a36Sopenharmony_ci	};
32662306a36Sopenharmony_ci	static char *vif[8] = {
32762306a36Sopenharmony_ci		"58.75 MHz",
32862306a36Sopenharmony_ci		"45.75 MHz",
32962306a36Sopenharmony_ci		"38.9 MHz",
33062306a36Sopenharmony_ci		"38.0 MHz",
33162306a36Sopenharmony_ci		"33.9 MHz",
33262306a36Sopenharmony_ci		"33.4 MHz",
33362306a36Sopenharmony_ci		"45.75 MHz + pin13",
33462306a36Sopenharmony_ci		"38.9 MHz + pin13",
33562306a36Sopenharmony_ci	};
33662306a36Sopenharmony_ci	static char *rif[4] = {
33762306a36Sopenharmony_ci		"44 MHz",
33862306a36Sopenharmony_ci		"52 MHz",
33962306a36Sopenharmony_ci		"52 MHz",
34062306a36Sopenharmony_ci		"44 MHz",
34162306a36Sopenharmony_ci	};
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	tuner_info("write: byte B 0x%02x\n", buf[1]);
34462306a36Sopenharmony_ci	tuner_info("  B0   video mode      : %s\n",
34562306a36Sopenharmony_ci		   (buf[1] & 0x01) ? "video trap" : "sound trap");
34662306a36Sopenharmony_ci	tuner_info("  B1   auto mute fm    : %s\n",
34762306a36Sopenharmony_ci		   (buf[1] & 0x02) ? "yes" : "no");
34862306a36Sopenharmony_ci	tuner_info("  B2   carrier mode    : %s\n",
34962306a36Sopenharmony_ci		   (buf[1] & 0x04) ? "QSS" : "Intercarrier");
35062306a36Sopenharmony_ci	tuner_info("  B3-4 tv sound/radio  : %s\n",
35162306a36Sopenharmony_ci		   sound[(buf[1] & 0x18) >> 3]);
35262306a36Sopenharmony_ci	tuner_info("  B5   force mute audio: %s\n",
35362306a36Sopenharmony_ci		   (buf[1] & 0x20) ? "yes" : "no");
35462306a36Sopenharmony_ci	tuner_info("  B6   output port 1   : %s\n",
35562306a36Sopenharmony_ci		   (buf[1] & 0x40) ? "high (inactive)" : "low (active)");
35662306a36Sopenharmony_ci	tuner_info("  B7   output port 2   : %s\n",
35762306a36Sopenharmony_ci		   (buf[1] & 0x80) ? "high (inactive)" : "low (active)");
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	tuner_info("write: byte C 0x%02x\n", buf[2]);
36062306a36Sopenharmony_ci	tuner_info("  C0-4 top adjustment  : %s dB\n",
36162306a36Sopenharmony_ci		   adjust[buf[2] & 0x1f]);
36262306a36Sopenharmony_ci	tuner_info("  C5-6 de-emphasis     : %s\n",
36362306a36Sopenharmony_ci		   deemph[(buf[2] & 0x60) >> 5]);
36462306a36Sopenharmony_ci	tuner_info("  C7   audio gain      : %s\n",
36562306a36Sopenharmony_ci		   (buf[2] & 0x80) ? "-6" : "0");
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	tuner_info("write: byte E 0x%02x\n", buf[3]);
36862306a36Sopenharmony_ci	tuner_info("  E0-1 sound carrier   : %s\n",
36962306a36Sopenharmony_ci		   carrier[(buf[3] & 0x03)]);
37062306a36Sopenharmony_ci	tuner_info("  E6   l pll gating   : %s\n",
37162306a36Sopenharmony_ci		   (buf[3] & 0x40) ? "36" : "13");
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	if (buf[1] & 0x08) {
37462306a36Sopenharmony_ci		/* radio */
37562306a36Sopenharmony_ci		tuner_info("  E2-4 video if        : %s\n",
37662306a36Sopenharmony_ci			   rif[(buf[3] & 0x0c) >> 2]);
37762306a36Sopenharmony_ci		tuner_info("  E7   vif agc output  : %s\n",
37862306a36Sopenharmony_ci			   (buf[3] & 0x80)
37962306a36Sopenharmony_ci			   ? ((buf[3] & 0x10) ? "fm-agc radio" :
38062306a36Sopenharmony_ci						"sif-agc radio")
38162306a36Sopenharmony_ci			   : "fm radio carrier afc");
38262306a36Sopenharmony_ci	} else {
38362306a36Sopenharmony_ci		/* video */
38462306a36Sopenharmony_ci		tuner_info("  E2-4 video if        : %s\n",
38562306a36Sopenharmony_ci			   vif[(buf[3] & 0x1c) >> 2]);
38662306a36Sopenharmony_ci		tuner_info("  E5   tuner gain      : %s\n",
38762306a36Sopenharmony_ci			   (buf[3] & 0x80)
38862306a36Sopenharmony_ci			   ? ((buf[3] & 0x20) ? "external" : "normal")
38962306a36Sopenharmony_ci			   : ((buf[3] & 0x20) ? "minimum"  : "normal"));
39062306a36Sopenharmony_ci		tuner_info("  E7   vif agc output  : %s\n",
39162306a36Sopenharmony_ci			   (buf[3] & 0x80) ? ((buf[3] & 0x20)
39262306a36Sopenharmony_ci				? "pin3 port, pin22 vif agc out"
39362306a36Sopenharmony_ci				: "pin22 port, pin3 vif acg ext in")
39462306a36Sopenharmony_ci				: "pin3+pin22 port");
39562306a36Sopenharmony_ci	}
39662306a36Sopenharmony_ci	tuner_info("--\n");
39762306a36Sopenharmony_ci}
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci/* ---------------------------------------------------------------------- */
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic int tda9887_set_tvnorm(struct dvb_frontend *fe)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
40462306a36Sopenharmony_ci	struct tvnorm *norm = NULL;
40562306a36Sopenharmony_ci	char *buf = priv->data;
40662306a36Sopenharmony_ci	int i;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	if (priv->mode == V4L2_TUNER_RADIO) {
40962306a36Sopenharmony_ci		if (priv->audmode == V4L2_TUNER_MODE_MONO)
41062306a36Sopenharmony_ci			norm = &radio_mono;
41162306a36Sopenharmony_ci		else
41262306a36Sopenharmony_ci			norm = &radio_stereo;
41362306a36Sopenharmony_ci	} else {
41462306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(tvnorms); i++) {
41562306a36Sopenharmony_ci			if (tvnorms[i].std & priv->std) {
41662306a36Sopenharmony_ci				norm = tvnorms+i;
41762306a36Sopenharmony_ci				break;
41862306a36Sopenharmony_ci			}
41962306a36Sopenharmony_ci		}
42062306a36Sopenharmony_ci	}
42162306a36Sopenharmony_ci	if (NULL == norm) {
42262306a36Sopenharmony_ci		tuner_dbg("Unsupported tvnorm entry - audio muted\n");
42362306a36Sopenharmony_ci		return -1;
42462306a36Sopenharmony_ci	}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	tuner_dbg("configure for: %s\n", norm->name);
42762306a36Sopenharmony_ci	buf[1] = norm->b;
42862306a36Sopenharmony_ci	buf[2] = norm->c;
42962306a36Sopenharmony_ci	buf[3] = norm->e;
43062306a36Sopenharmony_ci	return 0;
43162306a36Sopenharmony_ci}
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_cistatic unsigned int port1  = UNSET;
43462306a36Sopenharmony_cistatic unsigned int port2  = UNSET;
43562306a36Sopenharmony_cistatic unsigned int qss    = UNSET;
43662306a36Sopenharmony_cistatic unsigned int adjust = UNSET;
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_cimodule_param(port1, int, 0644);
43962306a36Sopenharmony_cimodule_param(port2, int, 0644);
44062306a36Sopenharmony_cimodule_param(qss, int, 0644);
44162306a36Sopenharmony_cimodule_param(adjust, int, 0644);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_cistatic int tda9887_set_insmod(struct dvb_frontend *fe)
44462306a36Sopenharmony_ci{
44562306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
44662306a36Sopenharmony_ci	char *buf = priv->data;
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	if (UNSET != port1) {
44962306a36Sopenharmony_ci		if (port1)
45062306a36Sopenharmony_ci			buf[1] |= cOutputPort1Inactive;
45162306a36Sopenharmony_ci		else
45262306a36Sopenharmony_ci			buf[1] &= ~cOutputPort1Inactive;
45362306a36Sopenharmony_ci	}
45462306a36Sopenharmony_ci	if (UNSET != port2) {
45562306a36Sopenharmony_ci		if (port2)
45662306a36Sopenharmony_ci			buf[1] |= cOutputPort2Inactive;
45762306a36Sopenharmony_ci		else
45862306a36Sopenharmony_ci			buf[1] &= ~cOutputPort2Inactive;
45962306a36Sopenharmony_ci	}
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	if (UNSET != qss) {
46262306a36Sopenharmony_ci		if (qss)
46362306a36Sopenharmony_ci			buf[1] |= cQSS;
46462306a36Sopenharmony_ci		else
46562306a36Sopenharmony_ci			buf[1] &= ~cQSS;
46662306a36Sopenharmony_ci	}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	if (adjust < 0x20) {
46962306a36Sopenharmony_ci		buf[2] &= ~cTopMask;
47062306a36Sopenharmony_ci		buf[2] |= adjust;
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci	return 0;
47362306a36Sopenharmony_ci}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_cistatic int tda9887_do_config(struct dvb_frontend *fe)
47662306a36Sopenharmony_ci{
47762306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
47862306a36Sopenharmony_ci	char *buf = priv->data;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	if (priv->config & TDA9887_PORT1_ACTIVE)
48162306a36Sopenharmony_ci		buf[1] &= ~cOutputPort1Inactive;
48262306a36Sopenharmony_ci	if (priv->config & TDA9887_PORT1_INACTIVE)
48362306a36Sopenharmony_ci		buf[1] |= cOutputPort1Inactive;
48462306a36Sopenharmony_ci	if (priv->config & TDA9887_PORT2_ACTIVE)
48562306a36Sopenharmony_ci		buf[1] &= ~cOutputPort2Inactive;
48662306a36Sopenharmony_ci	if (priv->config & TDA9887_PORT2_INACTIVE)
48762306a36Sopenharmony_ci		buf[1] |= cOutputPort2Inactive;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	if (priv->config & TDA9887_QSS)
49062306a36Sopenharmony_ci		buf[1] |= cQSS;
49162306a36Sopenharmony_ci	if (priv->config & TDA9887_INTERCARRIER)
49262306a36Sopenharmony_ci		buf[1] &= ~cQSS;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	if (priv->config & TDA9887_AUTOMUTE)
49562306a36Sopenharmony_ci		buf[1] |= cAutoMuteFmActive;
49662306a36Sopenharmony_ci	if (priv->config & TDA9887_DEEMPHASIS_MASK) {
49762306a36Sopenharmony_ci		buf[2] &= ~0x60;
49862306a36Sopenharmony_ci		switch (priv->config & TDA9887_DEEMPHASIS_MASK) {
49962306a36Sopenharmony_ci		case TDA9887_DEEMPHASIS_NONE:
50062306a36Sopenharmony_ci			buf[2] |= cDeemphasisOFF;
50162306a36Sopenharmony_ci			break;
50262306a36Sopenharmony_ci		case TDA9887_DEEMPHASIS_50:
50362306a36Sopenharmony_ci			buf[2] |= cDeemphasisON | cDeemphasis50;
50462306a36Sopenharmony_ci			break;
50562306a36Sopenharmony_ci		case TDA9887_DEEMPHASIS_75:
50662306a36Sopenharmony_ci			buf[2] |= cDeemphasisON | cDeemphasis75;
50762306a36Sopenharmony_ci			break;
50862306a36Sopenharmony_ci		}
50962306a36Sopenharmony_ci	}
51062306a36Sopenharmony_ci	if (priv->config & TDA9887_TOP_SET) {
51162306a36Sopenharmony_ci		buf[2] &= ~cTopMask;
51262306a36Sopenharmony_ci		buf[2] |= (priv->config >> 8) & cTopMask;
51362306a36Sopenharmony_ci	}
51462306a36Sopenharmony_ci	if ((priv->config & TDA9887_INTERCARRIER_NTSC) &&
51562306a36Sopenharmony_ci	    (priv->std & V4L2_STD_NTSC))
51662306a36Sopenharmony_ci		buf[1] &= ~cQSS;
51762306a36Sopenharmony_ci	if (priv->config & TDA9887_GATING_18)
51862306a36Sopenharmony_ci		buf[3] &= ~cGating_36;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	if (priv->mode == V4L2_TUNER_RADIO) {
52162306a36Sopenharmony_ci		if (priv->config & TDA9887_RIF_41_3) {
52262306a36Sopenharmony_ci			buf[3] &= ~cVideoIFMask;
52362306a36Sopenharmony_ci			buf[3] |= cRadioIF_41_30;
52462306a36Sopenharmony_ci		}
52562306a36Sopenharmony_ci		if (priv->config & TDA9887_GAIN_NORMAL)
52662306a36Sopenharmony_ci			buf[3] &= ~cTunerGainLow;
52762306a36Sopenharmony_ci	}
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	return 0;
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci/* ---------------------------------------------------------------------- */
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_cistatic int tda9887_status(struct dvb_frontend *fe)
53562306a36Sopenharmony_ci{
53662306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
53762306a36Sopenharmony_ci	unsigned char buf[1];
53862306a36Sopenharmony_ci	int rc;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	rc = tuner_i2c_xfer_recv(&priv->i2c_props, buf, 1);
54162306a36Sopenharmony_ci	if (rc != 1)
54262306a36Sopenharmony_ci		tuner_info("i2c i/o error: rc == %d (should be 1)\n", rc);
54362306a36Sopenharmony_ci	dump_read_message(fe, buf);
54462306a36Sopenharmony_ci	return 0;
54562306a36Sopenharmony_ci}
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_cistatic void tda9887_configure(struct dvb_frontend *fe)
54862306a36Sopenharmony_ci{
54962306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
55062306a36Sopenharmony_ci	int rc;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	memset(priv->data,0,sizeof(priv->data));
55362306a36Sopenharmony_ci	tda9887_set_tvnorm(fe);
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	/* A note on the port settings:
55662306a36Sopenharmony_ci	   These settings tend to depend on the specifics of the board.
55762306a36Sopenharmony_ci	   By default they are set to inactive (bit value 1) by this driver,
55862306a36Sopenharmony_ci	   overwriting any changes made by the tvnorm. This means that it
55962306a36Sopenharmony_ci	   is the responsibility of the module using the tda9887 to set
56062306a36Sopenharmony_ci	   these values in case of changes in the tvnorm.
56162306a36Sopenharmony_ci	   In many cases port 2 should be made active (0) when selecting
56262306a36Sopenharmony_ci	   SECAM-L, and port 2 should remain inactive (1) for SECAM-L'.
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	   For the other standards the tda9887 application note says that
56562306a36Sopenharmony_ci	   the ports should be set to active (0), but, again, that may
56662306a36Sopenharmony_ci	   differ depending on the precise hardware configuration.
56762306a36Sopenharmony_ci	 */
56862306a36Sopenharmony_ci	priv->data[1] |= cOutputPort1Inactive;
56962306a36Sopenharmony_ci	priv->data[1] |= cOutputPort2Inactive;
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	tda9887_do_config(fe);
57262306a36Sopenharmony_ci	tda9887_set_insmod(fe);
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	if (priv->standby)
57562306a36Sopenharmony_ci		priv->data[1] |= cForcedMuteAudioON;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	tuner_dbg("writing: b=0x%02x c=0x%02x e=0x%02x\n",
57862306a36Sopenharmony_ci		  priv->data[1], priv->data[2], priv->data[3]);
57962306a36Sopenharmony_ci	if (debug > 1)
58062306a36Sopenharmony_ci		dump_write_message(fe, priv->data);
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	if (4 != (rc = tuner_i2c_xfer_send(&priv->i2c_props,priv->data,4)))
58362306a36Sopenharmony_ci		tuner_info("i2c i/o error: rc == %d (should be 4)\n", rc);
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	if (debug > 2) {
58662306a36Sopenharmony_ci		msleep_interruptible(1000);
58762306a36Sopenharmony_ci		tda9887_status(fe);
58862306a36Sopenharmony_ci	}
58962306a36Sopenharmony_ci}
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci/* ---------------------------------------------------------------------- */
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_cistatic void tda9887_tuner_status(struct dvb_frontend *fe)
59462306a36Sopenharmony_ci{
59562306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
59662306a36Sopenharmony_ci	tuner_info("Data bytes: b=0x%02x c=0x%02x e=0x%02x\n",
59762306a36Sopenharmony_ci		   priv->data[1], priv->data[2], priv->data[3]);
59862306a36Sopenharmony_ci}
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_cistatic int tda9887_get_afc(struct dvb_frontend *fe, s32 *afc)
60162306a36Sopenharmony_ci{
60262306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
60362306a36Sopenharmony_ci	static const int AFC_BITS_2_kHz[] = {
60462306a36Sopenharmony_ci		-12500,  -37500,  -62500,  -97500,
60562306a36Sopenharmony_ci		-112500, -137500, -162500, -187500,
60662306a36Sopenharmony_ci		187500,  162500,  137500,  112500,
60762306a36Sopenharmony_ci		97500 ,  62500,   37500 ,  12500
60862306a36Sopenharmony_ci	};
60962306a36Sopenharmony_ci	__u8 reg = 0;
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	if (priv->mode != V4L2_TUNER_RADIO)
61262306a36Sopenharmony_ci		return 0;
61362306a36Sopenharmony_ci	if (1 == tuner_i2c_xfer_recv(&priv->i2c_props, &reg, 1))
61462306a36Sopenharmony_ci		*afc = AFC_BITS_2_kHz[(reg >> 1) & 0x0f];
61562306a36Sopenharmony_ci	return 0;
61662306a36Sopenharmony_ci}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_cistatic void tda9887_standby(struct dvb_frontend *fe)
61962306a36Sopenharmony_ci{
62062306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	priv->standby = true;
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	tda9887_configure(fe);
62562306a36Sopenharmony_ci}
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_cistatic void tda9887_set_params(struct dvb_frontend *fe,
62862306a36Sopenharmony_ci			       struct analog_parameters *params)
62962306a36Sopenharmony_ci{
63062306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	priv->standby = false;
63362306a36Sopenharmony_ci	priv->mode    = params->mode;
63462306a36Sopenharmony_ci	priv->audmode = params->audmode;
63562306a36Sopenharmony_ci	priv->std     = params->std;
63662306a36Sopenharmony_ci	tda9887_configure(fe);
63762306a36Sopenharmony_ci}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_cistatic int tda9887_set_config(struct dvb_frontend *fe, void *priv_cfg)
64062306a36Sopenharmony_ci{
64162306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	priv->config = *(unsigned int *)priv_cfg;
64462306a36Sopenharmony_ci	tda9887_configure(fe);
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	return 0;
64762306a36Sopenharmony_ci}
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_cistatic void tda9887_release(struct dvb_frontend *fe)
65062306a36Sopenharmony_ci{
65162306a36Sopenharmony_ci	struct tda9887_priv *priv = fe->analog_demod_priv;
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	mutex_lock(&tda9887_list_mutex);
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	if (priv)
65662306a36Sopenharmony_ci		hybrid_tuner_release_state(priv);
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	mutex_unlock(&tda9887_list_mutex);
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	fe->analog_demod_priv = NULL;
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_cistatic const struct analog_demod_ops tda9887_ops = {
66462306a36Sopenharmony_ci	.info		= {
66562306a36Sopenharmony_ci		.name	= "tda9887",
66662306a36Sopenharmony_ci	},
66762306a36Sopenharmony_ci	.set_params     = tda9887_set_params,
66862306a36Sopenharmony_ci	.standby        = tda9887_standby,
66962306a36Sopenharmony_ci	.tuner_status   = tda9887_tuner_status,
67062306a36Sopenharmony_ci	.get_afc        = tda9887_get_afc,
67162306a36Sopenharmony_ci	.release        = tda9887_release,
67262306a36Sopenharmony_ci	.set_config     = tda9887_set_config,
67362306a36Sopenharmony_ci};
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_cistruct dvb_frontend *tda9887_attach(struct dvb_frontend *fe,
67662306a36Sopenharmony_ci				    struct i2c_adapter *i2c_adap,
67762306a36Sopenharmony_ci				    u8 i2c_addr)
67862306a36Sopenharmony_ci{
67962306a36Sopenharmony_ci	struct tda9887_priv *priv = NULL;
68062306a36Sopenharmony_ci	int instance;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	mutex_lock(&tda9887_list_mutex);
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	instance = hybrid_tuner_request_state(struct tda9887_priv, priv,
68562306a36Sopenharmony_ci					      hybrid_tuner_instance_list,
68662306a36Sopenharmony_ci					      i2c_adap, i2c_addr, "tda9887");
68762306a36Sopenharmony_ci	switch (instance) {
68862306a36Sopenharmony_ci	case 0:
68962306a36Sopenharmony_ci		mutex_unlock(&tda9887_list_mutex);
69062306a36Sopenharmony_ci		return NULL;
69162306a36Sopenharmony_ci	case 1:
69262306a36Sopenharmony_ci		fe->analog_demod_priv = priv;
69362306a36Sopenharmony_ci		priv->standby = true;
69462306a36Sopenharmony_ci		tuner_info("tda988[5/6/7] found\n");
69562306a36Sopenharmony_ci		break;
69662306a36Sopenharmony_ci	default:
69762306a36Sopenharmony_ci		fe->analog_demod_priv = priv;
69862306a36Sopenharmony_ci		break;
69962306a36Sopenharmony_ci	}
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci	mutex_unlock(&tda9887_list_mutex);
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci	memcpy(&fe->ops.analog_ops, &tda9887_ops,
70462306a36Sopenharmony_ci	       sizeof(struct analog_demod_ops));
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	return fe;
70762306a36Sopenharmony_ci}
70862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(tda9887_attach);
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
711