162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci// For Philips TEA5761 FM Chip
362306a36Sopenharmony_ci// I2C address is always 0x20 (0x10 at 7-bit mode).
462306a36Sopenharmony_ci//
562306a36Sopenharmony_ci// Copyright (c) 2005-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/i2c.h>
862306a36Sopenharmony_ci#include <linux/slab.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/videodev2.h>
1162306a36Sopenharmony_ci#include <media/tuner.h>
1262306a36Sopenharmony_ci#include "tuner-i2c.h"
1362306a36Sopenharmony_ci#include "tea5761.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic int debug;
1662306a36Sopenharmony_cimodule_param(debug, int, 0644);
1762306a36Sopenharmony_ciMODULE_PARM_DESC(debug, "enable verbose debug messages");
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct tea5761_priv {
2062306a36Sopenharmony_ci	struct tuner_i2c_props i2c_props;
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	u32 frequency;
2362306a36Sopenharmony_ci	bool standby;
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/*****************************************************************************/
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci/***************************
2962306a36Sopenharmony_ci * TEA5761HN I2C registers *
3062306a36Sopenharmony_ci ***************************/
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* INTREG - Read: bytes 0 and 1 / Write: byte 0 */
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	/* first byte for reading */
3562306a36Sopenharmony_ci#define TEA5761_INTREG_IFFLAG		0x10
3662306a36Sopenharmony_ci#define TEA5761_INTREG_LEVFLAG		0x8
3762306a36Sopenharmony_ci#define TEA5761_INTREG_FRRFLAG		0x2
3862306a36Sopenharmony_ci#define TEA5761_INTREG_BLFLAG		0x1
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	/* second byte for reading / byte for writing */
4162306a36Sopenharmony_ci#define TEA5761_INTREG_IFMSK		0x10
4262306a36Sopenharmony_ci#define TEA5761_INTREG_LEVMSK		0x8
4362306a36Sopenharmony_ci#define TEA5761_INTREG_FRMSK		0x2
4462306a36Sopenharmony_ci#define TEA5761_INTREG_BLMSK		0x1
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci/* FRQSET - Read: bytes 2 and 3 / Write: byte 1 and 2 */
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	/* First byte */
4962306a36Sopenharmony_ci#define TEA5761_FRQSET_SEARCH_UP 0x80		/* 1=Station search from botton to up */
5062306a36Sopenharmony_ci#define TEA5761_FRQSET_SEARCH_MODE 0x40		/* 1=Search mode */
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	/* Bits 0-5 for divider MSB */
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	/* Second byte */
5562306a36Sopenharmony_ci	/* Bits 0-7 for divider LSB */
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/* TNCTRL - Read: bytes 4 and 5 / Write: Bytes 3 and 4 */
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	/* first byte */
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define TEA5761_TNCTRL_PUPD_0	0x40	/* Power UP/Power Down MSB */
6262306a36Sopenharmony_ci#define TEA5761_TNCTRL_BLIM	0X20	/* 1= Japan Frequencies, 0= European frequencies */
6362306a36Sopenharmony_ci#define TEA5761_TNCTRL_SWPM	0x10	/* 1= software port is FRRFLAG */
6462306a36Sopenharmony_ci#define TEA5761_TNCTRL_IFCTC	0x08	/* 1= IF count time 15.02 ms, 0= IF count time 2.02 ms */
6562306a36Sopenharmony_ci#define TEA5761_TNCTRL_AFM	0x04
6662306a36Sopenharmony_ci#define TEA5761_TNCTRL_SMUTE	0x02	/* 1= Soft mute */
6762306a36Sopenharmony_ci#define TEA5761_TNCTRL_SNC	0x01
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/* second byte */
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci#define TEA5761_TNCTRL_MU	0x80	/* 1=Hard mute */
7262306a36Sopenharmony_ci#define TEA5761_TNCTRL_SSL_1	0x40
7362306a36Sopenharmony_ci#define TEA5761_TNCTRL_SSL_0	0x20
7462306a36Sopenharmony_ci#define TEA5761_TNCTRL_HLSI	0x10
7562306a36Sopenharmony_ci#define TEA5761_TNCTRL_MST	0x08	/* 1 = mono */
7662306a36Sopenharmony_ci#define TEA5761_TNCTRL_SWP	0x04
7762306a36Sopenharmony_ci#define TEA5761_TNCTRL_DTC	0x02	/* 1 = deemphasis 50 us, 0 = deemphasis 75 us */
7862306a36Sopenharmony_ci#define TEA5761_TNCTRL_AHLSI	0x01
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci/* FRQCHECK - Read: bytes 6 and 7  */
8162306a36Sopenharmony_ci	/* First byte */
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/* Bits 0-5 for divider MSB */
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	/* Second byte */
8662306a36Sopenharmony_ci	/* Bits 0-7 for divider LSB */
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/* TUNCHECK - Read: bytes 8 and 9  */
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	/* First byte */
9162306a36Sopenharmony_ci#define TEA5761_TUNCHECK_IF_MASK	0x7e	/* IF count */
9262306a36Sopenharmony_ci#define TEA5761_TUNCHECK_TUNTO		0x01
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	/* Second byte */
9562306a36Sopenharmony_ci#define TEA5761_TUNCHECK_LEV_MASK	0xf0	/* Level Count */
9662306a36Sopenharmony_ci#define TEA5761_TUNCHECK_LD		0x08
9762306a36Sopenharmony_ci#define TEA5761_TUNCHECK_STEREO		0x04
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci/* TESTREG - Read: bytes 10 and 11 / Write: bytes 5 and 6 */
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* All zero = no test mode */
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci/* MANID - Read: bytes 12 and 13 */
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	/* First byte - should be 0x10 */
10662306a36Sopenharmony_ci#define TEA5767_MANID_VERSION_MASK	0xf0	/* Version = 1 */
10762306a36Sopenharmony_ci#define TEA5767_MANID_ID_MSB_MASK	0x0f	/* Manufacurer ID - should be 0 */
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/* Second byte - Should be 0x2b */
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci#define TEA5767_MANID_ID_LSB_MASK	0xfe	/* Manufacturer ID - should be 0x15 */
11262306a36Sopenharmony_ci#define TEA5767_MANID_IDAV		0x01	/* 1 = Chip has ID, 0 = Chip has no ID */
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci/* Chip ID - Read: bytes 14 and 15 */
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* First byte - should be 0x57 */
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/* Second byte - should be 0x61 */
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/*****************************************************************************/
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci#define FREQ_OFFSET 0 /* for TEA5767, it is 700 to give the right freq */
12362306a36Sopenharmony_cistatic void tea5761_status_dump(unsigned char *buffer)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	unsigned int div, frq;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	div = ((buffer[2] & 0x3f) << 8) | buffer[3];
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	frq = 1000 * (div * 32768 / 1000 + FREQ_OFFSET + 225) / 4;	/* Freq in KHz */
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	printk(KERN_INFO "tea5761: Frequency %d.%03d KHz (divider = 0x%04x)\n",
13262306a36Sopenharmony_ci	       frq / 1000, frq % 1000, div);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci/* Freq should be specifyed at 62.5 Hz */
13662306a36Sopenharmony_cistatic int __set_radio_freq(struct dvb_frontend *fe,
13762306a36Sopenharmony_ci			    unsigned int freq,
13862306a36Sopenharmony_ci			    bool mono)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct tea5761_priv *priv = fe->tuner_priv;
14162306a36Sopenharmony_ci	unsigned int frq = freq;
14262306a36Sopenharmony_ci	unsigned char buffer[7] = {0, 0, 0, 0, 0, 0, 0 };
14362306a36Sopenharmony_ci	unsigned div;
14462306a36Sopenharmony_ci	int rc;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	tuner_dbg("radio freq counter %d\n", frq);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	if (priv->standby) {
14962306a36Sopenharmony_ci		tuner_dbg("TEA5761 set to standby mode\n");
15062306a36Sopenharmony_ci		buffer[5] |= TEA5761_TNCTRL_MU;
15162306a36Sopenharmony_ci	} else {
15262306a36Sopenharmony_ci		buffer[4] |= TEA5761_TNCTRL_PUPD_0;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	if (mono) {
15762306a36Sopenharmony_ci		tuner_dbg("TEA5761 set to mono\n");
15862306a36Sopenharmony_ci		buffer[5] |= TEA5761_TNCTRL_MST;
15962306a36Sopenharmony_ci	} else {
16062306a36Sopenharmony_ci		tuner_dbg("TEA5761 set to stereo\n");
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	div = (1000 * (frq * 4 / 16 + 700 + 225) ) >> 15;
16462306a36Sopenharmony_ci	buffer[1] = (div >> 8) & 0x3f;
16562306a36Sopenharmony_ci	buffer[2] = div & 0xff;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (debug)
16862306a36Sopenharmony_ci		tea5761_status_dump(buffer);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (7 != (rc = tuner_i2c_xfer_send(&priv->i2c_props, buffer, 7)))
17162306a36Sopenharmony_ci		tuner_warn("i2c i/o error: rc == %d (should be 5)\n", rc);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	priv->frequency = frq * 125 / 2;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	return 0;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic int set_radio_freq(struct dvb_frontend *fe,
17962306a36Sopenharmony_ci			  struct analog_parameters *params)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	struct tea5761_priv *priv = fe->analog_demod_priv;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	priv->standby = false;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	return __set_radio_freq(fe, params->frequency,
18662306a36Sopenharmony_ci				params->audmode == V4L2_TUNER_MODE_MONO);
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic int set_radio_sleep(struct dvb_frontend *fe)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct tea5761_priv *priv = fe->analog_demod_priv;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	priv->standby = true;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return __set_radio_freq(fe, priv->frequency, false);
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic int tea5761_read_status(struct dvb_frontend *fe, char *buffer)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	struct tea5761_priv *priv = fe->tuner_priv;
20162306a36Sopenharmony_ci	int rc;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	memset(buffer, 0, 16);
20462306a36Sopenharmony_ci	if (16 != (rc = tuner_i2c_xfer_recv(&priv->i2c_props, buffer, 16))) {
20562306a36Sopenharmony_ci		tuner_warn("i2c i/o error: rc == %d (should be 16)\n", rc);
20662306a36Sopenharmony_ci		return -EREMOTEIO;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return 0;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic inline int tea5761_signal(struct dvb_frontend *fe, const char *buffer)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	struct tea5761_priv *priv = fe->tuner_priv;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	int signal = ((buffer[9] & TEA5761_TUNCHECK_LEV_MASK) << (13 - 4));
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	tuner_dbg("Signal strength: %d\n", signal);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return signal;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic inline int tea5761_stereo(struct dvb_frontend *fe, const char *buffer)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct tea5761_priv *priv = fe->tuner_priv;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	int stereo = buffer[9] & TEA5761_TUNCHECK_STEREO;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	tuner_dbg("Radio ST GET = %02x\n", stereo);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	return (stereo ? V4L2_TUNER_SUB_STEREO : 0);
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic int tea5761_get_status(struct dvb_frontend *fe, u32 *status)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	unsigned char buffer[16];
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	*status = 0;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	if (0 == tea5761_read_status(fe, buffer)) {
24162306a36Sopenharmony_ci		if (tea5761_signal(fe, buffer))
24262306a36Sopenharmony_ci			*status = TUNER_STATUS_LOCKED;
24362306a36Sopenharmony_ci		if (tea5761_stereo(fe, buffer))
24462306a36Sopenharmony_ci			*status |= TUNER_STATUS_STEREO;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return 0;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic int tea5761_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	unsigned char buffer[16];
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	*strength = 0;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	if (0 == tea5761_read_status(fe, buffer))
25762306a36Sopenharmony_ci		*strength = tea5761_signal(fe, buffer);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	return 0;
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ciint tea5761_autodetection(struct i2c_adapter* i2c_adap, u8 i2c_addr)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	unsigned char buffer[16];
26562306a36Sopenharmony_ci	int rc;
26662306a36Sopenharmony_ci	struct tuner_i2c_props i2c = { .adap = i2c_adap, .addr = i2c_addr };
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (16 != (rc = tuner_i2c_xfer_recv(&i2c, buffer, 16))) {
26962306a36Sopenharmony_ci		printk(KERN_WARNING "it is not a TEA5761. Received %i chars.\n", rc);
27062306a36Sopenharmony_ci		return -EINVAL;
27162306a36Sopenharmony_ci	}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	if ((buffer[13] != 0x2b) || (buffer[14] != 0x57) || (buffer[15] != 0x061)) {
27462306a36Sopenharmony_ci		printk(KERN_WARNING "Manufacturer ID= 0x%02x, Chip ID = %02x%02x. It is not a TEA5761\n",
27562306a36Sopenharmony_ci				    buffer[13], buffer[14], buffer[15]);
27662306a36Sopenharmony_ci		return -EINVAL;
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci	printk(KERN_WARNING "tea5761: TEA%02x%02x detected. Manufacturer ID= 0x%02x\n",
27962306a36Sopenharmony_ci			    buffer[14], buffer[15], buffer[13]);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	return 0;
28262306a36Sopenharmony_ci}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cistatic void tea5761_release(struct dvb_frontend *fe)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	kfree(fe->tuner_priv);
28762306a36Sopenharmony_ci	fe->tuner_priv = NULL;
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_cistatic int tea5761_get_frequency(struct dvb_frontend *fe, u32 *frequency)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	struct tea5761_priv *priv = fe->tuner_priv;
29362306a36Sopenharmony_ci	*frequency = priv->frequency;
29462306a36Sopenharmony_ci	return 0;
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_cistatic const struct dvb_tuner_ops tea5761_tuner_ops = {
29862306a36Sopenharmony_ci	.info = {
29962306a36Sopenharmony_ci		.name           = "tea5761", // Philips TEA5761HN FM Radio
30062306a36Sopenharmony_ci	},
30162306a36Sopenharmony_ci	.set_analog_params = set_radio_freq,
30262306a36Sopenharmony_ci	.sleep		   = set_radio_sleep,
30362306a36Sopenharmony_ci	.release           = tea5761_release,
30462306a36Sopenharmony_ci	.get_frequency     = tea5761_get_frequency,
30562306a36Sopenharmony_ci	.get_status        = tea5761_get_status,
30662306a36Sopenharmony_ci	.get_rf_strength   = tea5761_get_rf_strength,
30762306a36Sopenharmony_ci};
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cistruct dvb_frontend *tea5761_attach(struct dvb_frontend *fe,
31062306a36Sopenharmony_ci				    struct i2c_adapter* i2c_adap,
31162306a36Sopenharmony_ci				    u8 i2c_addr)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	struct tea5761_priv *priv = NULL;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	if (tea5761_autodetection(i2c_adap, i2c_addr) != 0)
31662306a36Sopenharmony_ci		return NULL;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	priv = kzalloc(sizeof(struct tea5761_priv), GFP_KERNEL);
31962306a36Sopenharmony_ci	if (priv == NULL)
32062306a36Sopenharmony_ci		return NULL;
32162306a36Sopenharmony_ci	fe->tuner_priv = priv;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	priv->i2c_props.addr = i2c_addr;
32462306a36Sopenharmony_ci	priv->i2c_props.adap = i2c_adap;
32562306a36Sopenharmony_ci	priv->i2c_props.name = "tea5761";
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	memcpy(&fe->ops.tuner_ops, &tea5761_tuner_ops,
32862306a36Sopenharmony_ci	       sizeof(struct dvb_tuner_ops));
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	tuner_info("type set to %s\n", "Philips TEA5761HN FM Radio");
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	return fe;
33362306a36Sopenharmony_ci}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(tea5761_attach);
33762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(tea5761_autodetection);
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ciMODULE_DESCRIPTION("Philips TEA5761 FM tuner driver");
34062306a36Sopenharmony_ciMODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@kernel.org>");
34162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
342