1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
246a60cfeSFabio Belavenuto /*
346a60cfeSFabio Belavenuto  * driver/media/radio/radio-tea5764.c
446a60cfeSFabio Belavenuto  *
546a60cfeSFabio Belavenuto  * Driver for TEA5764 radio chip for linux 2.6.
646a60cfeSFabio Belavenuto  * This driver is for TEA5764 chip from NXP, used in EZX phones from Motorola.
746a60cfeSFabio Belavenuto  * The I2C protocol is used for communicate with chip.
846a60cfeSFabio Belavenuto  *
946a60cfeSFabio Belavenuto  * Based in radio-tea5761.c Copyright (C) 2005 Nokia Corporation
1046a60cfeSFabio Belavenuto  *
1146a60cfeSFabio Belavenuto  *  Copyright (c) 2008 Fabio Belavenuto <belavenuto@gmail.com>
1246a60cfeSFabio Belavenuto  *
1346a60cfeSFabio Belavenuto  * History:
1446a60cfeSFabio Belavenuto  * 2008-12-06   Fabio Belavenuto <belavenuto@gmail.com>
1546a60cfeSFabio Belavenuto  *              initial code
1646a60cfeSFabio Belavenuto  *
1746a60cfeSFabio Belavenuto  * TODO:
1846a60cfeSFabio Belavenuto  *  add platform_data support for IRQs platform dependencies
1946a60cfeSFabio Belavenuto  *  add RDS support
2046a60cfeSFabio Belavenuto  */
2146a60cfeSFabio Belavenuto #include <linux/kernel.h>
225a0e3ad6STejun Heo #include <linux/slab.h>
2346a60cfeSFabio Belavenuto #include <linux/module.h>
2446a60cfeSFabio Belavenuto #include <linux/init.h>			/* Initdata			*/
2546a60cfeSFabio Belavenuto #include <linux/videodev2.h>		/* kernel radio structs		*/
2646a60cfeSFabio Belavenuto #include <linux/i2c.h>			/* I2C				*/
2746a60cfeSFabio Belavenuto #include <media/v4l2-common.h>
2846a60cfeSFabio Belavenuto #include <media/v4l2-ioctl.h>
29099f88eeSHans Verkuil #include <media/v4l2-device.h>
3016b37178SHans Verkuil #include <media/v4l2-ctrls.h>
31090fdf6aSHans Verkuil #include <media/v4l2-event.h>
3246a60cfeSFabio Belavenuto 
3329834c1aSMauro Carvalho Chehab #define DRIVER_VERSION	"0.0.2"
3446a60cfeSFabio Belavenuto 
3546a60cfeSFabio Belavenuto #define DRIVER_AUTHOR	"Fabio Belavenuto <belavenuto@gmail.com>"
3646a60cfeSFabio Belavenuto #define DRIVER_DESC	"A driver for the TEA5764 radio chip for EZX Phones."
3746a60cfeSFabio Belavenuto 
3846a60cfeSFabio Belavenuto #define PINFO(format, ...)\
3946a60cfeSFabio Belavenuto 	printk(KERN_INFO KBUILD_MODNAME ": "\
4046a60cfeSFabio Belavenuto 		DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)
4146a60cfeSFabio Belavenuto #define PWARN(format, ...)\
4246a60cfeSFabio Belavenuto 	printk(KERN_WARNING KBUILD_MODNAME ": "\
4346a60cfeSFabio Belavenuto 		DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)
4446a60cfeSFabio Belavenuto # define PDEBUG(format, ...)\
4546a60cfeSFabio Belavenuto 	printk(KERN_DEBUG KBUILD_MODNAME ": "\
4646a60cfeSFabio Belavenuto 		DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)
4746a60cfeSFabio Belavenuto 
4846a60cfeSFabio Belavenuto /* Frequency limits in MHz -- these are European values.  For Japanese
4946a60cfeSFabio Belavenuto devices, that would be 76000 and 91000.  */
50b7300892SHans Verkuil #define FREQ_MIN  87500U
51b7300892SHans Verkuil #define FREQ_MAX 108000U
5246a60cfeSFabio Belavenuto #define FREQ_MUL 16
5346a60cfeSFabio Belavenuto 
5446a60cfeSFabio Belavenuto /* TEA5764 registers */
5546a60cfeSFabio Belavenuto #define TEA5764_MANID		0x002b
5646a60cfeSFabio Belavenuto #define TEA5764_CHIPID		0x5764
5746a60cfeSFabio Belavenuto 
5846a60cfeSFabio Belavenuto #define TEA5764_INTREG_BLMSK	0x0001
5946a60cfeSFabio Belavenuto #define TEA5764_INTREG_FRRMSK	0x0002
6046a60cfeSFabio Belavenuto #define TEA5764_INTREG_LEVMSK	0x0008
6146a60cfeSFabio Belavenuto #define TEA5764_INTREG_IFMSK	0x0010
6246a60cfeSFabio Belavenuto #define TEA5764_INTREG_BLMFLAG	0x0100
6346a60cfeSFabio Belavenuto #define TEA5764_INTREG_FRRFLAG	0x0200
6446a60cfeSFabio Belavenuto #define TEA5764_INTREG_LEVFLAG	0x0800
6546a60cfeSFabio Belavenuto #define TEA5764_INTREG_IFFLAG	0x1000
6646a60cfeSFabio Belavenuto 
6746a60cfeSFabio Belavenuto #define TEA5764_FRQSET_SUD	0x8000
6846a60cfeSFabio Belavenuto #define TEA5764_FRQSET_SM	0x4000
6946a60cfeSFabio Belavenuto 
7046a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_PUPD1	0x8000
7146a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_PUPD0	0x4000
7246a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_BLIM	0x2000
7346a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_SWPM	0x1000
7446a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_IFCTC	0x0800
7546a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_AFM	0x0400
7646a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_SMUTE	0x0200
7746a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_SNC	0x0100
7846a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_MU	0x0080
7946a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_SSL1	0x0040
8046a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_SSL0	0x0020
8146a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_HLSI	0x0010
8246a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_MST	0x0008
8346a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_SWP	0x0004
8446a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_DTC	0x0002
8546a60cfeSFabio Belavenuto #define TEA5764_TNCTRL_AHLSI	0x0001
8646a60cfeSFabio Belavenuto 
8746a60cfeSFabio Belavenuto #define TEA5764_TUNCHK_LEVEL(x)	(((x) & 0x00F0) >> 4)
8846a60cfeSFabio Belavenuto #define TEA5764_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9)
8946a60cfeSFabio Belavenuto #define TEA5764_TUNCHK_TUNTO	0x0100
9046a60cfeSFabio Belavenuto #define TEA5764_TUNCHK_LD	0x0008
9146a60cfeSFabio Belavenuto #define TEA5764_TUNCHK_STEREO	0x0004
9246a60cfeSFabio Belavenuto 
9346a60cfeSFabio Belavenuto #define TEA5764_TESTREG_TRIGFR	0x0800
9446a60cfeSFabio Belavenuto 
9546a60cfeSFabio Belavenuto struct tea5764_regs {
9646a60cfeSFabio Belavenuto 	u16 intreg;				/* INTFLAG & INTMSK */
9746a60cfeSFabio Belavenuto 	u16 frqset;				/* FRQSETMSB & FRQSETLSB */
9846a60cfeSFabio Belavenuto 	u16 tnctrl;				/* TNCTRL1 & TNCTRL2 */
9946a60cfeSFabio Belavenuto 	u16 frqchk;				/* FRQCHKMSB & FRQCHKLSB */
10046a60cfeSFabio Belavenuto 	u16 tunchk;				/* IFCHK & LEVCHK */
10146a60cfeSFabio Belavenuto 	u16 testreg;				/* TESTBITS & TESTMODE */
10246a60cfeSFabio Belavenuto 	u16 rdsstat;				/* RDSSTAT1 & RDSSTAT2 */
10346a60cfeSFabio Belavenuto 	u16 rdslb;				/* RDSLBMSB & RDSLBLSB */
10446a60cfeSFabio Belavenuto 	u16 rdspb;				/* RDSPBMSB & RDSPBLSB */
10546a60cfeSFabio Belavenuto 	u16 rdsbc;				/* RDSBBC & RDSGBC */
10646a60cfeSFabio Belavenuto 	u16 rdsctrl;				/* RDSCTRL1 & RDSCTRL2 */
10746a60cfeSFabio Belavenuto 	u16 rdsbbl;				/* PAUSEDET & RDSBBL */
10846a60cfeSFabio Belavenuto 	u16 manid;				/* MANID1 & MANID2 */
10946a60cfeSFabio Belavenuto 	u16 chipid;				/* CHIPID1 & CHIPID2 */
11046a60cfeSFabio Belavenuto } __attribute__ ((packed));
11146a60cfeSFabio Belavenuto 
11246a60cfeSFabio Belavenuto struct tea5764_write_regs {
11346a60cfeSFabio Belavenuto 	u8 intreg;				/* INTMSK */
1147754622bSHans Verkuil 	__be16 frqset;				/* FRQSETMSB & FRQSETLSB */
1157754622bSHans Verkuil 	__be16 tnctrl;				/* TNCTRL1 & TNCTRL2 */
1167754622bSHans Verkuil 	__be16 testreg;				/* TESTBITS & TESTMODE */
1177754622bSHans Verkuil 	__be16 rdsctrl;				/* RDSCTRL1 & RDSCTRL2 */
1187754622bSHans Verkuil 	__be16 rdsbbl;				/* PAUSEDET & RDSBBL */
11946a60cfeSFabio Belavenuto } __attribute__ ((packed));
12046a60cfeSFabio Belavenuto 
1218c4343e5SPaul Bolle #ifdef CONFIG_RADIO_TEA5764_XTAL
12246a60cfeSFabio Belavenuto #define RADIO_TEA5764_XTAL 1
1238c4343e5SPaul Bolle #else
1248c4343e5SPaul Bolle #define RADIO_TEA5764_XTAL 0
12546a60cfeSFabio Belavenuto #endif
12646a60cfeSFabio Belavenuto 
12746a60cfeSFabio Belavenuto static int radio_nr = -1;
12846a60cfeSFabio Belavenuto static int use_xtal = RADIO_TEA5764_XTAL;
12946a60cfeSFabio Belavenuto 
13046a60cfeSFabio Belavenuto struct tea5764_device {
131099f88eeSHans Verkuil 	struct v4l2_device		v4l2_dev;
13216b37178SHans Verkuil 	struct v4l2_ctrl_handler	ctrl_handler;
13346a60cfeSFabio Belavenuto 	struct i2c_client		*i2c_client;
134cf9033f9SHans Verkuil 	struct video_device		vdev;
13546a60cfeSFabio Belavenuto 	struct tea5764_regs		regs;
13646a60cfeSFabio Belavenuto 	struct mutex			mutex;
13746a60cfeSFabio Belavenuto };
13846a60cfeSFabio Belavenuto 
13946a60cfeSFabio Belavenuto /* I2C code related */
1400dec8688SMauro Carvalho Chehab static int tea5764_i2c_read(struct tea5764_device *radio)
14146a60cfeSFabio Belavenuto {
14246a60cfeSFabio Belavenuto 	int i;
14346a60cfeSFabio Belavenuto 	u16 *p = (u16 *) &radio->regs;
14446a60cfeSFabio Belavenuto 
14546a60cfeSFabio Belavenuto 	struct i2c_msg msgs[1] = {
14666ba9590SShubhrajyoti D 		{	.addr = radio->i2c_client->addr,
14766ba9590SShubhrajyoti D 			.flags = I2C_M_RD,
14866ba9590SShubhrajyoti D 			.len = sizeof(radio->regs),
14966ba9590SShubhrajyoti D 			.buf = (void *)&radio->regs
15066ba9590SShubhrajyoti D 		},
15146a60cfeSFabio Belavenuto 	};
15246a60cfeSFabio Belavenuto 	if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1)
15346a60cfeSFabio Belavenuto 		return -EIO;
15446a60cfeSFabio Belavenuto 	for (i = 0; i < sizeof(struct tea5764_regs) / sizeof(u16); i++)
1557754622bSHans Verkuil 		p[i] = __be16_to_cpu((__force __be16)p[i]);
15646a60cfeSFabio Belavenuto 
15746a60cfeSFabio Belavenuto 	return 0;
15846a60cfeSFabio Belavenuto }
15946a60cfeSFabio Belavenuto 
1600dec8688SMauro Carvalho Chehab static int tea5764_i2c_write(struct tea5764_device *radio)
16146a60cfeSFabio Belavenuto {
16246a60cfeSFabio Belavenuto 	struct tea5764_write_regs wr;
16346a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
16446a60cfeSFabio Belavenuto 	struct i2c_msg msgs[1] = {
16566ba9590SShubhrajyoti D 		{
16666ba9590SShubhrajyoti D 			.addr = radio->i2c_client->addr,
16766ba9590SShubhrajyoti D 			.len = sizeof(wr),
16866ba9590SShubhrajyoti D 			.buf = (void *)&wr
16966ba9590SShubhrajyoti D 		},
17046a60cfeSFabio Belavenuto 	};
17146a60cfeSFabio Belavenuto 	wr.intreg  = r->intreg & 0xff;
17246a60cfeSFabio Belavenuto 	wr.frqset  = __cpu_to_be16(r->frqset);
17346a60cfeSFabio Belavenuto 	wr.tnctrl  = __cpu_to_be16(r->tnctrl);
17446a60cfeSFabio Belavenuto 	wr.testreg = __cpu_to_be16(r->testreg);
17546a60cfeSFabio Belavenuto 	wr.rdsctrl = __cpu_to_be16(r->rdsctrl);
17646a60cfeSFabio Belavenuto 	wr.rdsbbl  = __cpu_to_be16(r->rdsbbl);
17746a60cfeSFabio Belavenuto 	if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1)
17846a60cfeSFabio Belavenuto 		return -EIO;
17946a60cfeSFabio Belavenuto 	return 0;
18046a60cfeSFabio Belavenuto }
18146a60cfeSFabio Belavenuto 
18246a60cfeSFabio Belavenuto static void tea5764_power_up(struct tea5764_device *radio)
18346a60cfeSFabio Belavenuto {
18446a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
18546a60cfeSFabio Belavenuto 
18646a60cfeSFabio Belavenuto 	if (!(r->tnctrl & TEA5764_TNCTRL_PUPD0)) {
18746a60cfeSFabio Belavenuto 		r->tnctrl &= ~(TEA5764_TNCTRL_AFM | TEA5764_TNCTRL_MU |
18846a60cfeSFabio Belavenuto 			       TEA5764_TNCTRL_HLSI);
18946a60cfeSFabio Belavenuto 		if (!use_xtal)
19046a60cfeSFabio Belavenuto 			r->testreg |= TEA5764_TESTREG_TRIGFR;
19146a60cfeSFabio Belavenuto 		else
19246a60cfeSFabio Belavenuto 			r->testreg &= ~TEA5764_TESTREG_TRIGFR;
19346a60cfeSFabio Belavenuto 
19446a60cfeSFabio Belavenuto 		r->tnctrl |= TEA5764_TNCTRL_PUPD0;
19546a60cfeSFabio Belavenuto 		tea5764_i2c_write(radio);
19646a60cfeSFabio Belavenuto 	}
19746a60cfeSFabio Belavenuto }
19846a60cfeSFabio Belavenuto 
19946a60cfeSFabio Belavenuto static void tea5764_power_down(struct tea5764_device *radio)
20046a60cfeSFabio Belavenuto {
20146a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
20246a60cfeSFabio Belavenuto 
20346a60cfeSFabio Belavenuto 	if (r->tnctrl & TEA5764_TNCTRL_PUPD0) {
20446a60cfeSFabio Belavenuto 		r->tnctrl &= ~TEA5764_TNCTRL_PUPD0;
20546a60cfeSFabio Belavenuto 		tea5764_i2c_write(radio);
20646a60cfeSFabio Belavenuto 	}
20746a60cfeSFabio Belavenuto }
20846a60cfeSFabio Belavenuto 
20946a60cfeSFabio Belavenuto static void tea5764_set_freq(struct tea5764_device *radio, int freq)
21046a60cfeSFabio Belavenuto {
21146a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
21246a60cfeSFabio Belavenuto 
21346a60cfeSFabio Belavenuto 	/* formula: (freq [+ or -] 225000) / 8192 */
21446a60cfeSFabio Belavenuto 	if (r->tnctrl & TEA5764_TNCTRL_HLSI)
21546a60cfeSFabio Belavenuto 		r->frqset = (freq + 225000) / 8192;
21646a60cfeSFabio Belavenuto 	else
21746a60cfeSFabio Belavenuto 		r->frqset = (freq - 225000) / 8192;
21846a60cfeSFabio Belavenuto }
21946a60cfeSFabio Belavenuto 
22046a60cfeSFabio Belavenuto static int tea5764_get_freq(struct tea5764_device *radio)
22146a60cfeSFabio Belavenuto {
22246a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
22346a60cfeSFabio Belavenuto 
22446a60cfeSFabio Belavenuto 	if (r->tnctrl & TEA5764_TNCTRL_HLSI)
22546a60cfeSFabio Belavenuto 		return (r->frqchk * 8192) - 225000;
22646a60cfeSFabio Belavenuto 	else
22746a60cfeSFabio Belavenuto 		return (r->frqchk * 8192) + 225000;
22846a60cfeSFabio Belavenuto }
22946a60cfeSFabio Belavenuto 
23046a60cfeSFabio Belavenuto /* tune an frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
23146a60cfeSFabio Belavenuto static void tea5764_tune(struct tea5764_device *radio, int freq)
23246a60cfeSFabio Belavenuto {
23346a60cfeSFabio Belavenuto 	tea5764_set_freq(radio, freq);
23446a60cfeSFabio Belavenuto 	if (tea5764_i2c_write(radio))
23546a60cfeSFabio Belavenuto 		PWARN("Could not set frequency!");
23646a60cfeSFabio Belavenuto }
23746a60cfeSFabio Belavenuto 
23846a60cfeSFabio Belavenuto static void tea5764_set_audout_mode(struct tea5764_device *radio, int audmode)
23946a60cfeSFabio Belavenuto {
24046a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
24146a60cfeSFabio Belavenuto 	int tnctrl = r->tnctrl;
24246a60cfeSFabio Belavenuto 
24346a60cfeSFabio Belavenuto 	if (audmode == V4L2_TUNER_MODE_MONO)
24446a60cfeSFabio Belavenuto 		r->tnctrl |= TEA5764_TNCTRL_MST;
24546a60cfeSFabio Belavenuto 	else
24646a60cfeSFabio Belavenuto 		r->tnctrl &= ~TEA5764_TNCTRL_MST;
24746a60cfeSFabio Belavenuto 	if (tnctrl != r->tnctrl)
24846a60cfeSFabio Belavenuto 		tea5764_i2c_write(radio);
24946a60cfeSFabio Belavenuto }
25046a60cfeSFabio Belavenuto 
25146a60cfeSFabio Belavenuto static int tea5764_get_audout_mode(struct tea5764_device *radio)
25246a60cfeSFabio Belavenuto {
25346a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
25446a60cfeSFabio Belavenuto 
25546a60cfeSFabio Belavenuto 	if (r->tnctrl & TEA5764_TNCTRL_MST)
25646a60cfeSFabio Belavenuto 		return V4L2_TUNER_MODE_MONO;
25746a60cfeSFabio Belavenuto 	else
25846a60cfeSFabio Belavenuto 		return V4L2_TUNER_MODE_STEREO;
25946a60cfeSFabio Belavenuto }
26046a60cfeSFabio Belavenuto 
26146a60cfeSFabio Belavenuto static void tea5764_mute(struct tea5764_device *radio, int on)
26246a60cfeSFabio Belavenuto {
26346a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
26446a60cfeSFabio Belavenuto 	int tnctrl = r->tnctrl;
26546a60cfeSFabio Belavenuto 
26646a60cfeSFabio Belavenuto 	if (on)
26746a60cfeSFabio Belavenuto 		r->tnctrl |= TEA5764_TNCTRL_MU;
26846a60cfeSFabio Belavenuto 	else
26946a60cfeSFabio Belavenuto 		r->tnctrl &= ~TEA5764_TNCTRL_MU;
27046a60cfeSFabio Belavenuto 	if (tnctrl != r->tnctrl)
27146a60cfeSFabio Belavenuto 		tea5764_i2c_write(radio);
27246a60cfeSFabio Belavenuto }
27346a60cfeSFabio Belavenuto 
27446a60cfeSFabio Belavenuto /* V4L2 vidioc */
27546a60cfeSFabio Belavenuto static int vidioc_querycap(struct file *file, void  *priv,
27646a60cfeSFabio Belavenuto 					struct v4l2_capability *v)
27746a60cfeSFabio Belavenuto {
27846a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
279cf9033f9SHans Verkuil 	struct video_device *dev = &radio->vdev;
28046a60cfeSFabio Belavenuto 
281c0decac1SMauro Carvalho Chehab 	strscpy(v->driver, dev->dev.driver->name, sizeof(v->driver));
282c0decac1SMauro Carvalho Chehab 	strscpy(v->card, dev->name, sizeof(v->card));
283c3ef01ceSKay Sievers 	snprintf(v->bus_info, sizeof(v->bus_info),
284c3ef01ceSKay Sievers 		 "I2C:%s", dev_name(&dev->dev));
285cc9231f8SHans Verkuil 	v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
286cc9231f8SHans Verkuil 	v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
28746a60cfeSFabio Belavenuto 	return 0;
28846a60cfeSFabio Belavenuto }
28946a60cfeSFabio Belavenuto 
29046a60cfeSFabio Belavenuto static int vidioc_g_tuner(struct file *file, void *priv,
29146a60cfeSFabio Belavenuto 				struct v4l2_tuner *v)
29246a60cfeSFabio Belavenuto {
29346a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
29446a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
29546a60cfeSFabio Belavenuto 
29646a60cfeSFabio Belavenuto 	if (v->index > 0)
29746a60cfeSFabio Belavenuto 		return -EINVAL;
29846a60cfeSFabio Belavenuto 
299c0decac1SMauro Carvalho Chehab 	strscpy(v->name, "FM", sizeof(v->name));
30046a60cfeSFabio Belavenuto 	v->type = V4L2_TUNER_RADIO;
30146a60cfeSFabio Belavenuto 	tea5764_i2c_read(radio);
30246a60cfeSFabio Belavenuto 	v->rangelow   = FREQ_MIN * FREQ_MUL;
30346a60cfeSFabio Belavenuto 	v->rangehigh  = FREQ_MAX * FREQ_MUL;
30446a60cfeSFabio Belavenuto 	v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
30546a60cfeSFabio Belavenuto 	if (r->tunchk & TEA5764_TUNCHK_STEREO)
30646a60cfeSFabio Belavenuto 		v->rxsubchans = V4L2_TUNER_SUB_STEREO;
3075543e2b4SHans Verkuil 	else
3085543e2b4SHans Verkuil 		v->rxsubchans = V4L2_TUNER_SUB_MONO;
30946a60cfeSFabio Belavenuto 	v->audmode = tea5764_get_audout_mode(radio);
31046a60cfeSFabio Belavenuto 	v->signal = TEA5764_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf;
31146a60cfeSFabio Belavenuto 	v->afc = TEA5764_TUNCHK_IFCNT(r->tunchk);
31246a60cfeSFabio Belavenuto 
31346a60cfeSFabio Belavenuto 	return 0;
31446a60cfeSFabio Belavenuto }
31546a60cfeSFabio Belavenuto 
31646a60cfeSFabio Belavenuto static int vidioc_s_tuner(struct file *file, void *priv,
3172f73c7c5SHans Verkuil 				const struct v4l2_tuner *v)
31846a60cfeSFabio Belavenuto {
31946a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
32046a60cfeSFabio Belavenuto 
32146a60cfeSFabio Belavenuto 	if (v->index > 0)
32246a60cfeSFabio Belavenuto 		return -EINVAL;
32346a60cfeSFabio Belavenuto 
32446a60cfeSFabio Belavenuto 	tea5764_set_audout_mode(radio, v->audmode);
32546a60cfeSFabio Belavenuto 	return 0;
32646a60cfeSFabio Belavenuto }
32746a60cfeSFabio Belavenuto 
32846a60cfeSFabio Belavenuto static int vidioc_s_frequency(struct file *file, void *priv,
329b530a447SHans Verkuil 				const struct v4l2_frequency *f)
33046a60cfeSFabio Belavenuto {
33146a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
332b7300892SHans Verkuil 	unsigned freq = f->frequency;
33346a60cfeSFabio Belavenuto 
334a3a9e287SHans Verkuil 	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
33546a60cfeSFabio Belavenuto 		return -EINVAL;
336b7300892SHans Verkuil 	if (freq == 0) {
33746a60cfeSFabio Belavenuto 		/* We special case this as a power down control. */
33846a60cfeSFabio Belavenuto 		tea5764_power_down(radio);
339b7300892SHans Verkuil 		/* Yes, that's what is returned in this case. This
340b7300892SHans Verkuil 		   whole special case is non-compliant and should really
341b7300892SHans Verkuil 		   be replaced with something better, but changing this
342b7300892SHans Verkuil 		   might well break code that depends on this behavior.
343b7300892SHans Verkuil 		   So we keep it as-is. */
344b7300892SHans Verkuil 		return -EINVAL;
34546a60cfeSFabio Belavenuto 	}
3469ba6a91fSHans Verkuil 	freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL);
34746a60cfeSFabio Belavenuto 	tea5764_power_up(radio);
348b7300892SHans Verkuil 	tea5764_tune(radio, (freq * 125) / 2);
34946a60cfeSFabio Belavenuto 	return 0;
35046a60cfeSFabio Belavenuto }
35146a60cfeSFabio Belavenuto 
35246a60cfeSFabio Belavenuto static int vidioc_g_frequency(struct file *file, void *priv,
35346a60cfeSFabio Belavenuto 				struct v4l2_frequency *f)
35446a60cfeSFabio Belavenuto {
35546a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
35646a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
35746a60cfeSFabio Belavenuto 
358a3a9e287SHans Verkuil 	if (f->tuner != 0)
359a3a9e287SHans Verkuil 		return -EINVAL;
36046a60cfeSFabio Belavenuto 	tea5764_i2c_read(radio);
36146a60cfeSFabio Belavenuto 	f->type = V4L2_TUNER_RADIO;
36246a60cfeSFabio Belavenuto 	if (r->tnctrl & TEA5764_TNCTRL_PUPD0)
36346a60cfeSFabio Belavenuto 		f->frequency = (tea5764_get_freq(radio) * 2) / 125;
36446a60cfeSFabio Belavenuto 	else
36546a60cfeSFabio Belavenuto 		f->frequency = 0;
36646a60cfeSFabio Belavenuto 
36746a60cfeSFabio Belavenuto 	return 0;
36846a60cfeSFabio Belavenuto }
36946a60cfeSFabio Belavenuto 
37016b37178SHans Verkuil static int tea5764_s_ctrl(struct v4l2_ctrl *ctrl)
37146a60cfeSFabio Belavenuto {
37216b37178SHans Verkuil 	struct tea5764_device *radio =
37316b37178SHans Verkuil 		container_of(ctrl->handler, struct tea5764_device, ctrl_handler);
37446a60cfeSFabio Belavenuto 
37546a60cfeSFabio Belavenuto 	switch (ctrl->id) {
37646a60cfeSFabio Belavenuto 	case V4L2_CID_AUDIO_MUTE:
37716b37178SHans Verkuil 		tea5764_mute(radio, ctrl->val);
37846a60cfeSFabio Belavenuto 		return 0;
37946a60cfeSFabio Belavenuto 	}
38046a60cfeSFabio Belavenuto 	return -EINVAL;
38146a60cfeSFabio Belavenuto }
38246a60cfeSFabio Belavenuto 
38316b37178SHans Verkuil static const struct v4l2_ctrl_ops tea5764_ctrl_ops = {
38416b37178SHans Verkuil 	.s_ctrl = tea5764_s_ctrl,
38516b37178SHans Verkuil };
38616b37178SHans Verkuil 
38746a60cfeSFabio Belavenuto /* File system interface */
38846a60cfeSFabio Belavenuto static const struct v4l2_file_operations tea5764_fops = {
38946a60cfeSFabio Belavenuto 	.owner		= THIS_MODULE,
390090fdf6aSHans Verkuil 	.open		= v4l2_fh_open,
391090fdf6aSHans Verkuil 	.release	= v4l2_fh_release,
392090fdf6aSHans Verkuil 	.poll		= v4l2_ctrl_poll,
393ee71e423SHans Verkuil 	.unlocked_ioctl	= video_ioctl2,
39446a60cfeSFabio Belavenuto };
39546a60cfeSFabio Belavenuto 
39646a60cfeSFabio Belavenuto static const struct v4l2_ioctl_ops tea5764_ioctl_ops = {
39746a60cfeSFabio Belavenuto 	.vidioc_querycap    = vidioc_querycap,
39846a60cfeSFabio Belavenuto 	.vidioc_g_tuner     = vidioc_g_tuner,
39946a60cfeSFabio Belavenuto 	.vidioc_s_tuner     = vidioc_s_tuner,
40046a60cfeSFabio Belavenuto 	.vidioc_g_frequency = vidioc_g_frequency,
40146a60cfeSFabio Belavenuto 	.vidioc_s_frequency = vidioc_s_frequency,
402090fdf6aSHans Verkuil 	.vidioc_log_status  = v4l2_ctrl_log_status,
403090fdf6aSHans Verkuil 	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
404090fdf6aSHans Verkuil 	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
40546a60cfeSFabio Belavenuto };
40646a60cfeSFabio Belavenuto 
40746a60cfeSFabio Belavenuto /* V4L2 interface */
40896cc6956SBhumika Goyal static const struct video_device tea5764_radio_template = {
40946a60cfeSFabio Belavenuto 	.name		= "TEA5764 FM-Radio",
41046a60cfeSFabio Belavenuto 	.fops           = &tea5764_fops,
41146a60cfeSFabio Belavenuto 	.ioctl_ops	= &tea5764_ioctl_ops,
412cf9033f9SHans Verkuil 	.release	= video_device_release_empty,
41346a60cfeSFabio Belavenuto };
41446a60cfeSFabio Belavenuto 
41546a60cfeSFabio Belavenuto /* I2C probe: check if the device exists and register with v4l if it is */
4164c62e976SGreg Kroah-Hartman static int tea5764_i2c_probe(struct i2c_client *client,
41746a60cfeSFabio Belavenuto 			     const struct i2c_device_id *id)
41846a60cfeSFabio Belavenuto {
41946a60cfeSFabio Belavenuto 	struct tea5764_device *radio;
420099f88eeSHans Verkuil 	struct v4l2_device *v4l2_dev;
42116b37178SHans Verkuil 	struct v4l2_ctrl_handler *hdl;
42246a60cfeSFabio Belavenuto 	struct tea5764_regs *r;
42346a60cfeSFabio Belavenuto 	int ret;
42446a60cfeSFabio Belavenuto 
42546a60cfeSFabio Belavenuto 	PDEBUG("probe");
426ee71e423SHans Verkuil 	radio = kzalloc(sizeof(struct tea5764_device), GFP_KERNEL);
42746a60cfeSFabio Belavenuto 	if (!radio)
42846a60cfeSFabio Belavenuto 		return -ENOMEM;
42946a60cfeSFabio Belavenuto 
430099f88eeSHans Verkuil 	v4l2_dev = &radio->v4l2_dev;
431099f88eeSHans Verkuil 	ret = v4l2_device_register(&client->dev, v4l2_dev);
432099f88eeSHans Verkuil 	if (ret < 0) {
433099f88eeSHans Verkuil 		v4l2_err(v4l2_dev, "could not register v4l2_device\n");
434099f88eeSHans Verkuil 		goto errfr;
435099f88eeSHans Verkuil 	}
43616b37178SHans Verkuil 
43716b37178SHans Verkuil 	hdl = &radio->ctrl_handler;
43816b37178SHans Verkuil 	v4l2_ctrl_handler_init(hdl, 1);
43916b37178SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tea5764_ctrl_ops,
44016b37178SHans Verkuil 			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
44116b37178SHans Verkuil 	v4l2_dev->ctrl_handler = hdl;
44216b37178SHans Verkuil 	if (hdl->error) {
44316b37178SHans Verkuil 		ret = hdl->error;
44416b37178SHans Verkuil 		v4l2_err(v4l2_dev, "Could not register controls\n");
44516b37178SHans Verkuil 		goto errunreg;
44616b37178SHans Verkuil 	}
44716b37178SHans Verkuil 
44846a60cfeSFabio Belavenuto 	mutex_init(&radio->mutex);
44946a60cfeSFabio Belavenuto 	radio->i2c_client = client;
45046a60cfeSFabio Belavenuto 	ret = tea5764_i2c_read(radio);
45146a60cfeSFabio Belavenuto 	if (ret)
452099f88eeSHans Verkuil 		goto errunreg;
45346a60cfeSFabio Belavenuto 	r = &radio->regs;
45446a60cfeSFabio Belavenuto 	PDEBUG("chipid = %04X, manid = %04X", r->chipid, r->manid);
45546a60cfeSFabio Belavenuto 	if (r->chipid != TEA5764_CHIPID ||
45646a60cfeSFabio Belavenuto 		(r->manid & 0x0fff) != TEA5764_MANID) {
45746a60cfeSFabio Belavenuto 		PWARN("This chip is not a TEA5764!");
45846a60cfeSFabio Belavenuto 		ret = -EINVAL;
459099f88eeSHans Verkuil 		goto errunreg;
46046a60cfeSFabio Belavenuto 	}
46146a60cfeSFabio Belavenuto 
462cf9033f9SHans Verkuil 	radio->vdev = tea5764_radio_template;
46346a60cfeSFabio Belavenuto 
46446a60cfeSFabio Belavenuto 	i2c_set_clientdata(client, radio);
465cf9033f9SHans Verkuil 	video_set_drvdata(&radio->vdev, radio);
466cf9033f9SHans Verkuil 	radio->vdev.lock = &radio->mutex;
467cf9033f9SHans Verkuil 	radio->vdev.v4l2_dev = v4l2_dev;
46846a60cfeSFabio Belavenuto 
46946a60cfeSFabio Belavenuto 	/* initialize and power off the chip */
47046a60cfeSFabio Belavenuto 	tea5764_i2c_read(radio);
47146a60cfeSFabio Belavenuto 	tea5764_set_audout_mode(radio, V4L2_TUNER_MODE_STEREO);
47246a60cfeSFabio Belavenuto 	tea5764_mute(radio, 1);
47346a60cfeSFabio Belavenuto 	tea5764_power_down(radio);
47446a60cfeSFabio Belavenuto 
475cf9033f9SHans Verkuil 	ret = video_register_device(&radio->vdev, VFL_TYPE_RADIO, radio_nr);
476ee71e423SHans Verkuil 	if (ret < 0) {
477ee71e423SHans Verkuil 		PWARN("Could not register video device!");
478cf9033f9SHans Verkuil 		goto errunreg;
479ee71e423SHans Verkuil 	}
480ee71e423SHans Verkuil 
48146a60cfeSFabio Belavenuto 	PINFO("registered.");
48246a60cfeSFabio Belavenuto 	return 0;
483099f88eeSHans Verkuil errunreg:
48416b37178SHans Verkuil 	v4l2_ctrl_handler_free(hdl);
485099f88eeSHans Verkuil 	v4l2_device_unregister(v4l2_dev);
48646a60cfeSFabio Belavenuto errfr:
48746a60cfeSFabio Belavenuto 	kfree(radio);
48846a60cfeSFabio Belavenuto 	return ret;
48946a60cfeSFabio Belavenuto }
49046a60cfeSFabio Belavenuto 
4914c62e976SGreg Kroah-Hartman static int tea5764_i2c_remove(struct i2c_client *client)
49246a60cfeSFabio Belavenuto {
49346a60cfeSFabio Belavenuto 	struct tea5764_device *radio = i2c_get_clientdata(client);
49446a60cfeSFabio Belavenuto 
49546a60cfeSFabio Belavenuto 	PDEBUG("remove");
49646a60cfeSFabio Belavenuto 	if (radio) {
49746a60cfeSFabio Belavenuto 		tea5764_power_down(radio);
498cf9033f9SHans Verkuil 		video_unregister_device(&radio->vdev);
49916b37178SHans Verkuil 		v4l2_ctrl_handler_free(&radio->ctrl_handler);
500099f88eeSHans Verkuil 		v4l2_device_unregister(&radio->v4l2_dev);
50146a60cfeSFabio Belavenuto 		kfree(radio);
50246a60cfeSFabio Belavenuto 	}
50346a60cfeSFabio Belavenuto 	return 0;
50446a60cfeSFabio Belavenuto }
50546a60cfeSFabio Belavenuto 
50646a60cfeSFabio Belavenuto /* I2C subsystem interface */
50746a60cfeSFabio Belavenuto static const struct i2c_device_id tea5764_id[] = {
50846a60cfeSFabio Belavenuto 	{ "radio-tea5764", 0 },
50946a60cfeSFabio Belavenuto 	{ }					/* Terminating entry */
51046a60cfeSFabio Belavenuto };
51146a60cfeSFabio Belavenuto MODULE_DEVICE_TABLE(i2c, tea5764_id);
51246a60cfeSFabio Belavenuto 
51346a60cfeSFabio Belavenuto static struct i2c_driver tea5764_i2c_driver = {
51446a60cfeSFabio Belavenuto 	.driver = {
51546a60cfeSFabio Belavenuto 		.name = "radio-tea5764",
51646a60cfeSFabio Belavenuto 	},
51746a60cfeSFabio Belavenuto 	.probe = tea5764_i2c_probe,
5184c62e976SGreg Kroah-Hartman 	.remove = tea5764_i2c_remove,
51946a60cfeSFabio Belavenuto 	.id_table = tea5764_id,
52046a60cfeSFabio Belavenuto };
52146a60cfeSFabio Belavenuto 
522c6e8d86fSAxel Lin module_i2c_driver(tea5764_i2c_driver);
52346a60cfeSFabio Belavenuto 
52446a60cfeSFabio Belavenuto MODULE_AUTHOR(DRIVER_AUTHOR);
52546a60cfeSFabio Belavenuto MODULE_DESCRIPTION(DRIVER_DESC);
52646a60cfeSFabio Belavenuto MODULE_LICENSE("GPL");
52729834c1aSMauro Carvalho Chehab MODULE_VERSION(DRIVER_VERSION);
52846a60cfeSFabio Belavenuto 
529731884baSJean Delvare module_param(use_xtal, int, 0);
53046a60cfeSFabio Belavenuto MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board");
53146a60cfeSFabio Belavenuto module_param(radio_nr, int, 0);
53246a60cfeSFabio Belavenuto MODULE_PARM_DESC(radio_nr, "video4linux device number to use");
533