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 */
tea5764_i2c_read(struct tea5764_device * radio)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 
tea5764_i2c_write(struct tea5764_device * radio)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 
tea5764_power_up(struct tea5764_device * radio)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 
tea5764_power_down(struct tea5764_device * radio)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 
tea5764_set_freq(struct tea5764_device * radio,int freq)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 
tea5764_get_freq(struct tea5764_device * radio)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 */
tea5764_tune(struct tea5764_device * radio,int freq)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 
tea5764_set_audout_mode(struct tea5764_device * radio,int audmode)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 
tea5764_get_audout_mode(struct tea5764_device * radio)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 
tea5764_mute(struct tea5764_device * radio,int on)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 */
vidioc_querycap(struct file * file,void * priv,struct v4l2_capability * v)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));
28546a60cfeSFabio Belavenuto 	return 0;
28646a60cfeSFabio Belavenuto }
28746a60cfeSFabio Belavenuto 
vidioc_g_tuner(struct file * file,void * priv,struct v4l2_tuner * v)28846a60cfeSFabio Belavenuto static int vidioc_g_tuner(struct file *file, void *priv,
28946a60cfeSFabio Belavenuto 				struct v4l2_tuner *v)
29046a60cfeSFabio Belavenuto {
29146a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
29246a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
29346a60cfeSFabio Belavenuto 
29446a60cfeSFabio Belavenuto 	if (v->index > 0)
29546a60cfeSFabio Belavenuto 		return -EINVAL;
29646a60cfeSFabio Belavenuto 
297c0decac1SMauro Carvalho Chehab 	strscpy(v->name, "FM", sizeof(v->name));
29846a60cfeSFabio Belavenuto 	v->type = V4L2_TUNER_RADIO;
29946a60cfeSFabio Belavenuto 	tea5764_i2c_read(radio);
30046a60cfeSFabio Belavenuto 	v->rangelow   = FREQ_MIN * FREQ_MUL;
30146a60cfeSFabio Belavenuto 	v->rangehigh  = FREQ_MAX * FREQ_MUL;
30246a60cfeSFabio Belavenuto 	v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
30346a60cfeSFabio Belavenuto 	if (r->tunchk & TEA5764_TUNCHK_STEREO)
30446a60cfeSFabio Belavenuto 		v->rxsubchans = V4L2_TUNER_SUB_STEREO;
3055543e2b4SHans Verkuil 	else
3065543e2b4SHans Verkuil 		v->rxsubchans = V4L2_TUNER_SUB_MONO;
30746a60cfeSFabio Belavenuto 	v->audmode = tea5764_get_audout_mode(radio);
30846a60cfeSFabio Belavenuto 	v->signal = TEA5764_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf;
30946a60cfeSFabio Belavenuto 	v->afc = TEA5764_TUNCHK_IFCNT(r->tunchk);
31046a60cfeSFabio Belavenuto 
31146a60cfeSFabio Belavenuto 	return 0;
31246a60cfeSFabio Belavenuto }
31346a60cfeSFabio Belavenuto 
vidioc_s_tuner(struct file * file,void * priv,const struct v4l2_tuner * v)31446a60cfeSFabio Belavenuto static int vidioc_s_tuner(struct file *file, void *priv,
3152f73c7c5SHans Verkuil 				const struct v4l2_tuner *v)
31646a60cfeSFabio Belavenuto {
31746a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
31846a60cfeSFabio Belavenuto 
31946a60cfeSFabio Belavenuto 	if (v->index > 0)
32046a60cfeSFabio Belavenuto 		return -EINVAL;
32146a60cfeSFabio Belavenuto 
32246a60cfeSFabio Belavenuto 	tea5764_set_audout_mode(radio, v->audmode);
32346a60cfeSFabio Belavenuto 	return 0;
32446a60cfeSFabio Belavenuto }
32546a60cfeSFabio Belavenuto 
vidioc_s_frequency(struct file * file,void * priv,const struct v4l2_frequency * f)32646a60cfeSFabio Belavenuto static int vidioc_s_frequency(struct file *file, void *priv,
327b530a447SHans Verkuil 				const struct v4l2_frequency *f)
32846a60cfeSFabio Belavenuto {
32946a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
330b7300892SHans Verkuil 	unsigned freq = f->frequency;
33146a60cfeSFabio Belavenuto 
332a3a9e287SHans Verkuil 	if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
33346a60cfeSFabio Belavenuto 		return -EINVAL;
334b7300892SHans Verkuil 	if (freq == 0) {
33546a60cfeSFabio Belavenuto 		/* We special case this as a power down control. */
33646a60cfeSFabio Belavenuto 		tea5764_power_down(radio);
337b7300892SHans Verkuil 		/* Yes, that's what is returned in this case. This
338b7300892SHans Verkuil 		   whole special case is non-compliant and should really
339b7300892SHans Verkuil 		   be replaced with something better, but changing this
340b7300892SHans Verkuil 		   might well break code that depends on this behavior.
341b7300892SHans Verkuil 		   So we keep it as-is. */
342b7300892SHans Verkuil 		return -EINVAL;
34346a60cfeSFabio Belavenuto 	}
3449ba6a91fSHans Verkuil 	freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL);
34546a60cfeSFabio Belavenuto 	tea5764_power_up(radio);
346b7300892SHans Verkuil 	tea5764_tune(radio, (freq * 125) / 2);
34746a60cfeSFabio Belavenuto 	return 0;
34846a60cfeSFabio Belavenuto }
34946a60cfeSFabio Belavenuto 
vidioc_g_frequency(struct file * file,void * priv,struct v4l2_frequency * f)35046a60cfeSFabio Belavenuto static int vidioc_g_frequency(struct file *file, void *priv,
35146a60cfeSFabio Belavenuto 				struct v4l2_frequency *f)
35246a60cfeSFabio Belavenuto {
35346a60cfeSFabio Belavenuto 	struct tea5764_device *radio = video_drvdata(file);
35446a60cfeSFabio Belavenuto 	struct tea5764_regs *r = &radio->regs;
35546a60cfeSFabio Belavenuto 
356a3a9e287SHans Verkuil 	if (f->tuner != 0)
357a3a9e287SHans Verkuil 		return -EINVAL;
35846a60cfeSFabio Belavenuto 	tea5764_i2c_read(radio);
35946a60cfeSFabio Belavenuto 	f->type = V4L2_TUNER_RADIO;
36046a60cfeSFabio Belavenuto 	if (r->tnctrl & TEA5764_TNCTRL_PUPD0)
36146a60cfeSFabio Belavenuto 		f->frequency = (tea5764_get_freq(radio) * 2) / 125;
36246a60cfeSFabio Belavenuto 	else
36346a60cfeSFabio Belavenuto 		f->frequency = 0;
36446a60cfeSFabio Belavenuto 
36546a60cfeSFabio Belavenuto 	return 0;
36646a60cfeSFabio Belavenuto }
36746a60cfeSFabio Belavenuto 
tea5764_s_ctrl(struct v4l2_ctrl * ctrl)36816b37178SHans Verkuil static int tea5764_s_ctrl(struct v4l2_ctrl *ctrl)
36946a60cfeSFabio Belavenuto {
37016b37178SHans Verkuil 	struct tea5764_device *radio =
37116b37178SHans Verkuil 		container_of(ctrl->handler, struct tea5764_device, ctrl_handler);
37246a60cfeSFabio Belavenuto 
37346a60cfeSFabio Belavenuto 	switch (ctrl->id) {
37446a60cfeSFabio Belavenuto 	case V4L2_CID_AUDIO_MUTE:
37516b37178SHans Verkuil 		tea5764_mute(radio, ctrl->val);
37646a60cfeSFabio Belavenuto 		return 0;
37746a60cfeSFabio Belavenuto 	}
37846a60cfeSFabio Belavenuto 	return -EINVAL;
37946a60cfeSFabio Belavenuto }
38046a60cfeSFabio Belavenuto 
38116b37178SHans Verkuil static const struct v4l2_ctrl_ops tea5764_ctrl_ops = {
38216b37178SHans Verkuil 	.s_ctrl = tea5764_s_ctrl,
38316b37178SHans Verkuil };
38416b37178SHans Verkuil 
38546a60cfeSFabio Belavenuto /* File system interface */
38646a60cfeSFabio Belavenuto static const struct v4l2_file_operations tea5764_fops = {
38746a60cfeSFabio Belavenuto 	.owner		= THIS_MODULE,
388090fdf6aSHans Verkuil 	.open		= v4l2_fh_open,
389090fdf6aSHans Verkuil 	.release	= v4l2_fh_release,
390090fdf6aSHans Verkuil 	.poll		= v4l2_ctrl_poll,
391ee71e423SHans Verkuil 	.unlocked_ioctl	= video_ioctl2,
39246a60cfeSFabio Belavenuto };
39346a60cfeSFabio Belavenuto 
39446a60cfeSFabio Belavenuto static const struct v4l2_ioctl_ops tea5764_ioctl_ops = {
39546a60cfeSFabio Belavenuto 	.vidioc_querycap    = vidioc_querycap,
39646a60cfeSFabio Belavenuto 	.vidioc_g_tuner     = vidioc_g_tuner,
39746a60cfeSFabio Belavenuto 	.vidioc_s_tuner     = vidioc_s_tuner,
39846a60cfeSFabio Belavenuto 	.vidioc_g_frequency = vidioc_g_frequency,
39946a60cfeSFabio Belavenuto 	.vidioc_s_frequency = vidioc_s_frequency,
400090fdf6aSHans Verkuil 	.vidioc_log_status  = v4l2_ctrl_log_status,
401090fdf6aSHans Verkuil 	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
402090fdf6aSHans Verkuil 	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
40346a60cfeSFabio Belavenuto };
40446a60cfeSFabio Belavenuto 
40546a60cfeSFabio Belavenuto /* V4L2 interface */
40696cc6956SBhumika Goyal static const struct video_device tea5764_radio_template = {
40746a60cfeSFabio Belavenuto 	.name		= "TEA5764 FM-Radio",
40846a60cfeSFabio Belavenuto 	.fops           = &tea5764_fops,
40946a60cfeSFabio Belavenuto 	.ioctl_ops	= &tea5764_ioctl_ops,
410cf9033f9SHans Verkuil 	.release	= video_device_release_empty,
41146a60cfeSFabio Belavenuto };
41246a60cfeSFabio Belavenuto 
41346a60cfeSFabio Belavenuto /* I2C probe: check if the device exists and register with v4l if it is */
tea5764_i2c_probe(struct i2c_client * client)414df3d5d5dSUwe Kleine-König static int tea5764_i2c_probe(struct i2c_client *client)
41546a60cfeSFabio Belavenuto {
41646a60cfeSFabio Belavenuto 	struct tea5764_device *radio;
417099f88eeSHans Verkuil 	struct v4l2_device *v4l2_dev;
41816b37178SHans Verkuil 	struct v4l2_ctrl_handler *hdl;
41946a60cfeSFabio Belavenuto 	struct tea5764_regs *r;
42046a60cfeSFabio Belavenuto 	int ret;
42146a60cfeSFabio Belavenuto 
42246a60cfeSFabio Belavenuto 	PDEBUG("probe");
423ee71e423SHans Verkuil 	radio = kzalloc(sizeof(struct tea5764_device), GFP_KERNEL);
42446a60cfeSFabio Belavenuto 	if (!radio)
42546a60cfeSFabio Belavenuto 		return -ENOMEM;
42646a60cfeSFabio Belavenuto 
427099f88eeSHans Verkuil 	v4l2_dev = &radio->v4l2_dev;
428099f88eeSHans Verkuil 	ret = v4l2_device_register(&client->dev, v4l2_dev);
429099f88eeSHans Verkuil 	if (ret < 0) {
430099f88eeSHans Verkuil 		v4l2_err(v4l2_dev, "could not register v4l2_device\n");
431099f88eeSHans Verkuil 		goto errfr;
432099f88eeSHans Verkuil 	}
43316b37178SHans Verkuil 
43416b37178SHans Verkuil 	hdl = &radio->ctrl_handler;
43516b37178SHans Verkuil 	v4l2_ctrl_handler_init(hdl, 1);
43616b37178SHans Verkuil 	v4l2_ctrl_new_std(hdl, &tea5764_ctrl_ops,
43716b37178SHans Verkuil 			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
43816b37178SHans Verkuil 	v4l2_dev->ctrl_handler = hdl;
43916b37178SHans Verkuil 	if (hdl->error) {
44016b37178SHans Verkuil 		ret = hdl->error;
44116b37178SHans Verkuil 		v4l2_err(v4l2_dev, "Could not register controls\n");
44216b37178SHans Verkuil 		goto errunreg;
44316b37178SHans Verkuil 	}
44416b37178SHans Verkuil 
44546a60cfeSFabio Belavenuto 	mutex_init(&radio->mutex);
44646a60cfeSFabio Belavenuto 	radio->i2c_client = client;
44746a60cfeSFabio Belavenuto 	ret = tea5764_i2c_read(radio);
44846a60cfeSFabio Belavenuto 	if (ret)
449099f88eeSHans Verkuil 		goto errunreg;
45046a60cfeSFabio Belavenuto 	r = &radio->regs;
45146a60cfeSFabio Belavenuto 	PDEBUG("chipid = %04X, manid = %04X", r->chipid, r->manid);
45246a60cfeSFabio Belavenuto 	if (r->chipid != TEA5764_CHIPID ||
45346a60cfeSFabio Belavenuto 		(r->manid & 0x0fff) != TEA5764_MANID) {
45446a60cfeSFabio Belavenuto 		PWARN("This chip is not a TEA5764!");
45546a60cfeSFabio Belavenuto 		ret = -EINVAL;
456099f88eeSHans Verkuil 		goto errunreg;
45746a60cfeSFabio Belavenuto 	}
45846a60cfeSFabio Belavenuto 
459cf9033f9SHans Verkuil 	radio->vdev = tea5764_radio_template;
46046a60cfeSFabio Belavenuto 
46146a60cfeSFabio Belavenuto 	i2c_set_clientdata(client, radio);
462cf9033f9SHans Verkuil 	video_set_drvdata(&radio->vdev, radio);
463cf9033f9SHans Verkuil 	radio->vdev.lock = &radio->mutex;
464cf9033f9SHans Verkuil 	radio->vdev.v4l2_dev = v4l2_dev;
465e83ce300SHans Verkuil 	radio->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
46646a60cfeSFabio Belavenuto 
46746a60cfeSFabio Belavenuto 	/* initialize and power off the chip */
46846a60cfeSFabio Belavenuto 	tea5764_i2c_read(radio);
46946a60cfeSFabio Belavenuto 	tea5764_set_audout_mode(radio, V4L2_TUNER_MODE_STEREO);
47046a60cfeSFabio Belavenuto 	tea5764_mute(radio, 1);
47146a60cfeSFabio Belavenuto 	tea5764_power_down(radio);
47246a60cfeSFabio Belavenuto 
473cf9033f9SHans Verkuil 	ret = video_register_device(&radio->vdev, VFL_TYPE_RADIO, radio_nr);
474ee71e423SHans Verkuil 	if (ret < 0) {
475ee71e423SHans Verkuil 		PWARN("Could not register video device!");
476cf9033f9SHans Verkuil 		goto errunreg;
477ee71e423SHans Verkuil 	}
478ee71e423SHans Verkuil 
47946a60cfeSFabio Belavenuto 	PINFO("registered.");
48046a60cfeSFabio Belavenuto 	return 0;
481099f88eeSHans Verkuil errunreg:
48216b37178SHans Verkuil 	v4l2_ctrl_handler_free(hdl);
483099f88eeSHans Verkuil 	v4l2_device_unregister(v4l2_dev);
48446a60cfeSFabio Belavenuto errfr:
48546a60cfeSFabio Belavenuto 	kfree(radio);
48646a60cfeSFabio Belavenuto 	return ret;
48746a60cfeSFabio Belavenuto }
48846a60cfeSFabio Belavenuto 
tea5764_i2c_remove(struct i2c_client * client)489ed5c2f5fSUwe Kleine-König static void tea5764_i2c_remove(struct i2c_client *client)
49046a60cfeSFabio Belavenuto {
49146a60cfeSFabio Belavenuto 	struct tea5764_device *radio = i2c_get_clientdata(client);
49246a60cfeSFabio Belavenuto 
49346a60cfeSFabio Belavenuto 	PDEBUG("remove");
49446a60cfeSFabio Belavenuto 	if (radio) {
49546a60cfeSFabio Belavenuto 		tea5764_power_down(radio);
496cf9033f9SHans Verkuil 		video_unregister_device(&radio->vdev);
49716b37178SHans Verkuil 		v4l2_ctrl_handler_free(&radio->ctrl_handler);
498099f88eeSHans Verkuil 		v4l2_device_unregister(&radio->v4l2_dev);
49946a60cfeSFabio Belavenuto 		kfree(radio);
50046a60cfeSFabio Belavenuto 	}
50146a60cfeSFabio Belavenuto }
50246a60cfeSFabio Belavenuto 
50346a60cfeSFabio Belavenuto /* I2C subsystem interface */
50446a60cfeSFabio Belavenuto static const struct i2c_device_id tea5764_id[] = {
50546a60cfeSFabio Belavenuto 	{ "radio-tea5764", 0 },
50646a60cfeSFabio Belavenuto 	{ }					/* Terminating entry */
50746a60cfeSFabio Belavenuto };
50846a60cfeSFabio Belavenuto MODULE_DEVICE_TABLE(i2c, tea5764_id);
50946a60cfeSFabio Belavenuto 
51046a60cfeSFabio Belavenuto static struct i2c_driver tea5764_i2c_driver = {
51146a60cfeSFabio Belavenuto 	.driver = {
51246a60cfeSFabio Belavenuto 		.name = "radio-tea5764",
51346a60cfeSFabio Belavenuto 	},
514*aaeb31c0SUwe Kleine-König 	.probe = tea5764_i2c_probe,
5154c62e976SGreg Kroah-Hartman 	.remove = tea5764_i2c_remove,
51646a60cfeSFabio Belavenuto 	.id_table = tea5764_id,
51746a60cfeSFabio Belavenuto };
51846a60cfeSFabio Belavenuto 
519c6e8d86fSAxel Lin module_i2c_driver(tea5764_i2c_driver);
52046a60cfeSFabio Belavenuto 
52146a60cfeSFabio Belavenuto MODULE_AUTHOR(DRIVER_AUTHOR);
52246a60cfeSFabio Belavenuto MODULE_DESCRIPTION(DRIVER_DESC);
52346a60cfeSFabio Belavenuto MODULE_LICENSE("GPL");
52429834c1aSMauro Carvalho Chehab MODULE_VERSION(DRIVER_VERSION);
52546a60cfeSFabio Belavenuto 
526731884baSJean Delvare module_param(use_xtal, int, 0);
52746a60cfeSFabio Belavenuto MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board");
52846a60cfeSFabio Belavenuto module_param(radio_nr, int, 0);
52946a60cfeSFabio Belavenuto MODULE_PARM_DESC(radio_nr, "video4linux device number to use");
530