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