162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * driver/media/radio/radio-tea5764.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Driver for TEA5764 radio chip for linux 2.6. 662306a36Sopenharmony_ci * This driver is for TEA5764 chip from NXP, used in EZX phones from Motorola. 762306a36Sopenharmony_ci * The I2C protocol is used for communicate with chip. 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Based in radio-tea5761.c Copyright (C) 2005 Nokia Corporation 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Copyright (c) 2008 Fabio Belavenuto <belavenuto@gmail.com> 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * History: 1462306a36Sopenharmony_ci * 2008-12-06 Fabio Belavenuto <belavenuto@gmail.com> 1562306a36Sopenharmony_ci * initial code 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * TODO: 1862306a36Sopenharmony_ci * add platform_data support for IRQs platform dependencies 1962306a36Sopenharmony_ci * add RDS support 2062306a36Sopenharmony_ci */ 2162306a36Sopenharmony_ci#include <linux/kernel.h> 2262306a36Sopenharmony_ci#include <linux/slab.h> 2362306a36Sopenharmony_ci#include <linux/module.h> 2462306a36Sopenharmony_ci#include <linux/init.h> /* Initdata */ 2562306a36Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 2662306a36Sopenharmony_ci#include <linux/i2c.h> /* I2C */ 2762306a36Sopenharmony_ci#include <media/v4l2-common.h> 2862306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 2962306a36Sopenharmony_ci#include <media/v4l2-device.h> 3062306a36Sopenharmony_ci#include <media/v4l2-ctrls.h> 3162306a36Sopenharmony_ci#include <media/v4l2-event.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define DRIVER_VERSION "0.0.2" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define DRIVER_AUTHOR "Fabio Belavenuto <belavenuto@gmail.com>" 3662306a36Sopenharmony_ci#define DRIVER_DESC "A driver for the TEA5764 radio chip for EZX Phones." 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define PINFO(format, ...)\ 3962306a36Sopenharmony_ci printk(KERN_INFO KBUILD_MODNAME ": "\ 4062306a36Sopenharmony_ci DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) 4162306a36Sopenharmony_ci#define PWARN(format, ...)\ 4262306a36Sopenharmony_ci printk(KERN_WARNING KBUILD_MODNAME ": "\ 4362306a36Sopenharmony_ci DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) 4462306a36Sopenharmony_ci# define PDEBUG(format, ...)\ 4562306a36Sopenharmony_ci printk(KERN_DEBUG KBUILD_MODNAME ": "\ 4662306a36Sopenharmony_ci DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* Frequency limits in MHz -- these are European values. For Japanese 4962306a36Sopenharmony_cidevices, that would be 76000 and 91000. */ 5062306a36Sopenharmony_ci#define FREQ_MIN 87500U 5162306a36Sopenharmony_ci#define FREQ_MAX 108000U 5262306a36Sopenharmony_ci#define FREQ_MUL 16 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/* TEA5764 registers */ 5562306a36Sopenharmony_ci#define TEA5764_MANID 0x002b 5662306a36Sopenharmony_ci#define TEA5764_CHIPID 0x5764 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci#define TEA5764_INTREG_BLMSK 0x0001 5962306a36Sopenharmony_ci#define TEA5764_INTREG_FRRMSK 0x0002 6062306a36Sopenharmony_ci#define TEA5764_INTREG_LEVMSK 0x0008 6162306a36Sopenharmony_ci#define TEA5764_INTREG_IFMSK 0x0010 6262306a36Sopenharmony_ci#define TEA5764_INTREG_BLMFLAG 0x0100 6362306a36Sopenharmony_ci#define TEA5764_INTREG_FRRFLAG 0x0200 6462306a36Sopenharmony_ci#define TEA5764_INTREG_LEVFLAG 0x0800 6562306a36Sopenharmony_ci#define TEA5764_INTREG_IFFLAG 0x1000 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define TEA5764_FRQSET_SUD 0x8000 6862306a36Sopenharmony_ci#define TEA5764_FRQSET_SM 0x4000 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci#define TEA5764_TNCTRL_PUPD1 0x8000 7162306a36Sopenharmony_ci#define TEA5764_TNCTRL_PUPD0 0x4000 7262306a36Sopenharmony_ci#define TEA5764_TNCTRL_BLIM 0x2000 7362306a36Sopenharmony_ci#define TEA5764_TNCTRL_SWPM 0x1000 7462306a36Sopenharmony_ci#define TEA5764_TNCTRL_IFCTC 0x0800 7562306a36Sopenharmony_ci#define TEA5764_TNCTRL_AFM 0x0400 7662306a36Sopenharmony_ci#define TEA5764_TNCTRL_SMUTE 0x0200 7762306a36Sopenharmony_ci#define TEA5764_TNCTRL_SNC 0x0100 7862306a36Sopenharmony_ci#define TEA5764_TNCTRL_MU 0x0080 7962306a36Sopenharmony_ci#define TEA5764_TNCTRL_SSL1 0x0040 8062306a36Sopenharmony_ci#define TEA5764_TNCTRL_SSL0 0x0020 8162306a36Sopenharmony_ci#define TEA5764_TNCTRL_HLSI 0x0010 8262306a36Sopenharmony_ci#define TEA5764_TNCTRL_MST 0x0008 8362306a36Sopenharmony_ci#define TEA5764_TNCTRL_SWP 0x0004 8462306a36Sopenharmony_ci#define TEA5764_TNCTRL_DTC 0x0002 8562306a36Sopenharmony_ci#define TEA5764_TNCTRL_AHLSI 0x0001 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci#define TEA5764_TUNCHK_LEVEL(x) (((x) & 0x00F0) >> 4) 8862306a36Sopenharmony_ci#define TEA5764_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9) 8962306a36Sopenharmony_ci#define TEA5764_TUNCHK_TUNTO 0x0100 9062306a36Sopenharmony_ci#define TEA5764_TUNCHK_LD 0x0008 9162306a36Sopenharmony_ci#define TEA5764_TUNCHK_STEREO 0x0004 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci#define TEA5764_TESTREG_TRIGFR 0x0800 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistruct tea5764_regs { 9662306a36Sopenharmony_ci u16 intreg; /* INTFLAG & INTMSK */ 9762306a36Sopenharmony_ci u16 frqset; /* FRQSETMSB & FRQSETLSB */ 9862306a36Sopenharmony_ci u16 tnctrl; /* TNCTRL1 & TNCTRL2 */ 9962306a36Sopenharmony_ci u16 frqchk; /* FRQCHKMSB & FRQCHKLSB */ 10062306a36Sopenharmony_ci u16 tunchk; /* IFCHK & LEVCHK */ 10162306a36Sopenharmony_ci u16 testreg; /* TESTBITS & TESTMODE */ 10262306a36Sopenharmony_ci u16 rdsstat; /* RDSSTAT1 & RDSSTAT2 */ 10362306a36Sopenharmony_ci u16 rdslb; /* RDSLBMSB & RDSLBLSB */ 10462306a36Sopenharmony_ci u16 rdspb; /* RDSPBMSB & RDSPBLSB */ 10562306a36Sopenharmony_ci u16 rdsbc; /* RDSBBC & RDSGBC */ 10662306a36Sopenharmony_ci u16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */ 10762306a36Sopenharmony_ci u16 rdsbbl; /* PAUSEDET & RDSBBL */ 10862306a36Sopenharmony_ci u16 manid; /* MANID1 & MANID2 */ 10962306a36Sopenharmony_ci u16 chipid; /* CHIPID1 & CHIPID2 */ 11062306a36Sopenharmony_ci} __attribute__ ((packed)); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistruct tea5764_write_regs { 11362306a36Sopenharmony_ci u8 intreg; /* INTMSK */ 11462306a36Sopenharmony_ci __be16 frqset; /* FRQSETMSB & FRQSETLSB */ 11562306a36Sopenharmony_ci __be16 tnctrl; /* TNCTRL1 & TNCTRL2 */ 11662306a36Sopenharmony_ci __be16 testreg; /* TESTBITS & TESTMODE */ 11762306a36Sopenharmony_ci __be16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */ 11862306a36Sopenharmony_ci __be16 rdsbbl; /* PAUSEDET & RDSBBL */ 11962306a36Sopenharmony_ci} __attribute__ ((packed)); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci#ifdef CONFIG_RADIO_TEA5764_XTAL 12262306a36Sopenharmony_ci#define RADIO_TEA5764_XTAL 1 12362306a36Sopenharmony_ci#else 12462306a36Sopenharmony_ci#define RADIO_TEA5764_XTAL 0 12562306a36Sopenharmony_ci#endif 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic int radio_nr = -1; 12862306a36Sopenharmony_cistatic int use_xtal = RADIO_TEA5764_XTAL; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistruct tea5764_device { 13162306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 13262306a36Sopenharmony_ci struct v4l2_ctrl_handler ctrl_handler; 13362306a36Sopenharmony_ci struct i2c_client *i2c_client; 13462306a36Sopenharmony_ci struct video_device vdev; 13562306a36Sopenharmony_ci struct tea5764_regs regs; 13662306a36Sopenharmony_ci struct mutex mutex; 13762306a36Sopenharmony_ci}; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci/* I2C code related */ 14062306a36Sopenharmony_cistatic int tea5764_i2c_read(struct tea5764_device *radio) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci int i; 14362306a36Sopenharmony_ci u16 *p = (u16 *) &radio->regs; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci struct i2c_msg msgs[1] = { 14662306a36Sopenharmony_ci { .addr = radio->i2c_client->addr, 14762306a36Sopenharmony_ci .flags = I2C_M_RD, 14862306a36Sopenharmony_ci .len = sizeof(radio->regs), 14962306a36Sopenharmony_ci .buf = (void *)&radio->regs 15062306a36Sopenharmony_ci }, 15162306a36Sopenharmony_ci }; 15262306a36Sopenharmony_ci if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1) 15362306a36Sopenharmony_ci return -EIO; 15462306a36Sopenharmony_ci for (i = 0; i < sizeof(struct tea5764_regs) / sizeof(u16); i++) 15562306a36Sopenharmony_ci p[i] = __be16_to_cpu((__force __be16)p[i]); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return 0; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic int tea5764_i2c_write(struct tea5764_device *radio) 16162306a36Sopenharmony_ci{ 16262306a36Sopenharmony_ci struct tea5764_write_regs wr; 16362306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 16462306a36Sopenharmony_ci struct i2c_msg msgs[1] = { 16562306a36Sopenharmony_ci { 16662306a36Sopenharmony_ci .addr = radio->i2c_client->addr, 16762306a36Sopenharmony_ci .len = sizeof(wr), 16862306a36Sopenharmony_ci .buf = (void *)&wr 16962306a36Sopenharmony_ci }, 17062306a36Sopenharmony_ci }; 17162306a36Sopenharmony_ci wr.intreg = r->intreg & 0xff; 17262306a36Sopenharmony_ci wr.frqset = __cpu_to_be16(r->frqset); 17362306a36Sopenharmony_ci wr.tnctrl = __cpu_to_be16(r->tnctrl); 17462306a36Sopenharmony_ci wr.testreg = __cpu_to_be16(r->testreg); 17562306a36Sopenharmony_ci wr.rdsctrl = __cpu_to_be16(r->rdsctrl); 17662306a36Sopenharmony_ci wr.rdsbbl = __cpu_to_be16(r->rdsbbl); 17762306a36Sopenharmony_ci if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1) 17862306a36Sopenharmony_ci return -EIO; 17962306a36Sopenharmony_ci return 0; 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic void tea5764_power_up(struct tea5764_device *radio) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if (!(r->tnctrl & TEA5764_TNCTRL_PUPD0)) { 18762306a36Sopenharmony_ci r->tnctrl &= ~(TEA5764_TNCTRL_AFM | TEA5764_TNCTRL_MU | 18862306a36Sopenharmony_ci TEA5764_TNCTRL_HLSI); 18962306a36Sopenharmony_ci if (!use_xtal) 19062306a36Sopenharmony_ci r->testreg |= TEA5764_TESTREG_TRIGFR; 19162306a36Sopenharmony_ci else 19262306a36Sopenharmony_ci r->testreg &= ~TEA5764_TESTREG_TRIGFR; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci r->tnctrl |= TEA5764_TNCTRL_PUPD0; 19562306a36Sopenharmony_ci tea5764_i2c_write(radio); 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic void tea5764_power_down(struct tea5764_device *radio) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_PUPD0) { 20462306a36Sopenharmony_ci r->tnctrl &= ~TEA5764_TNCTRL_PUPD0; 20562306a36Sopenharmony_ci tea5764_i2c_write(radio); 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic void tea5764_set_freq(struct tea5764_device *radio, int freq) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* formula: (freq [+ or -] 225000) / 8192 */ 21462306a36Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_HLSI) 21562306a36Sopenharmony_ci r->frqset = (freq + 225000) / 8192; 21662306a36Sopenharmony_ci else 21762306a36Sopenharmony_ci r->frqset = (freq - 225000) / 8192; 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic int tea5764_get_freq(struct tea5764_device *radio) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_HLSI) 22562306a36Sopenharmony_ci return (r->frqchk * 8192) - 225000; 22662306a36Sopenharmony_ci else 22762306a36Sopenharmony_ci return (r->frqchk * 8192) + 225000; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci/* tune an frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ 23162306a36Sopenharmony_cistatic void tea5764_tune(struct tea5764_device *radio, int freq) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci tea5764_set_freq(radio, freq); 23462306a36Sopenharmony_ci if (tea5764_i2c_write(radio)) 23562306a36Sopenharmony_ci PWARN("Could not set frequency!"); 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic void tea5764_set_audout_mode(struct tea5764_device *radio, int audmode) 23962306a36Sopenharmony_ci{ 24062306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 24162306a36Sopenharmony_ci int tnctrl = r->tnctrl; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (audmode == V4L2_TUNER_MODE_MONO) 24462306a36Sopenharmony_ci r->tnctrl |= TEA5764_TNCTRL_MST; 24562306a36Sopenharmony_ci else 24662306a36Sopenharmony_ci r->tnctrl &= ~TEA5764_TNCTRL_MST; 24762306a36Sopenharmony_ci if (tnctrl != r->tnctrl) 24862306a36Sopenharmony_ci tea5764_i2c_write(radio); 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_cistatic int tea5764_get_audout_mode(struct tea5764_device *radio) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_MST) 25662306a36Sopenharmony_ci return V4L2_TUNER_MODE_MONO; 25762306a36Sopenharmony_ci else 25862306a36Sopenharmony_ci return V4L2_TUNER_MODE_STEREO; 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cistatic void tea5764_mute(struct tea5764_device *radio, int on) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 26462306a36Sopenharmony_ci int tnctrl = r->tnctrl; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (on) 26762306a36Sopenharmony_ci r->tnctrl |= TEA5764_TNCTRL_MU; 26862306a36Sopenharmony_ci else 26962306a36Sopenharmony_ci r->tnctrl &= ~TEA5764_TNCTRL_MU; 27062306a36Sopenharmony_ci if (tnctrl != r->tnctrl) 27162306a36Sopenharmony_ci tea5764_i2c_write(radio); 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci/* V4L2 vidioc */ 27562306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 27662306a36Sopenharmony_ci struct v4l2_capability *v) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 27962306a36Sopenharmony_ci struct video_device *dev = &radio->vdev; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci strscpy(v->driver, dev->dev.driver->name, sizeof(v->driver)); 28262306a36Sopenharmony_ci strscpy(v->card, dev->name, sizeof(v->card)); 28362306a36Sopenharmony_ci snprintf(v->bus_info, sizeof(v->bus_info), 28462306a36Sopenharmony_ci "I2C:%s", dev_name(&dev->dev)); 28562306a36Sopenharmony_ci return 0; 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 28962306a36Sopenharmony_ci struct v4l2_tuner *v) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 29262306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci if (v->index > 0) 29562306a36Sopenharmony_ci return -EINVAL; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 29862306a36Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 29962306a36Sopenharmony_ci tea5764_i2c_read(radio); 30062306a36Sopenharmony_ci v->rangelow = FREQ_MIN * FREQ_MUL; 30162306a36Sopenharmony_ci v->rangehigh = FREQ_MAX * FREQ_MUL; 30262306a36Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; 30362306a36Sopenharmony_ci if (r->tunchk & TEA5764_TUNCHK_STEREO) 30462306a36Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_STEREO; 30562306a36Sopenharmony_ci else 30662306a36Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_MONO; 30762306a36Sopenharmony_ci v->audmode = tea5764_get_audout_mode(radio); 30862306a36Sopenharmony_ci v->signal = TEA5764_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf; 30962306a36Sopenharmony_ci v->afc = TEA5764_TUNCHK_IFCNT(r->tunchk); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 31562306a36Sopenharmony_ci const struct v4l2_tuner *v) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci if (v->index > 0) 32062306a36Sopenharmony_ci return -EINVAL; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci tea5764_set_audout_mode(radio, v->audmode); 32362306a36Sopenharmony_ci return 0; 32462306a36Sopenharmony_ci} 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 32762306a36Sopenharmony_ci const struct v4l2_frequency *f) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 33062306a36Sopenharmony_ci unsigned freq = f->frequency; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 33362306a36Sopenharmony_ci return -EINVAL; 33462306a36Sopenharmony_ci if (freq == 0) { 33562306a36Sopenharmony_ci /* We special case this as a power down control. */ 33662306a36Sopenharmony_ci tea5764_power_down(radio); 33762306a36Sopenharmony_ci /* Yes, that's what is returned in this case. This 33862306a36Sopenharmony_ci whole special case is non-compliant and should really 33962306a36Sopenharmony_ci be replaced with something better, but changing this 34062306a36Sopenharmony_ci might well break code that depends on this behavior. 34162306a36Sopenharmony_ci So we keep it as-is. */ 34262306a36Sopenharmony_ci return -EINVAL; 34362306a36Sopenharmony_ci } 34462306a36Sopenharmony_ci freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); 34562306a36Sopenharmony_ci tea5764_power_up(radio); 34662306a36Sopenharmony_ci tea5764_tune(radio, (freq * 125) / 2); 34762306a36Sopenharmony_ci return 0; 34862306a36Sopenharmony_ci} 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 35162306a36Sopenharmony_ci struct v4l2_frequency *f) 35262306a36Sopenharmony_ci{ 35362306a36Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 35462306a36Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci if (f->tuner != 0) 35762306a36Sopenharmony_ci return -EINVAL; 35862306a36Sopenharmony_ci tea5764_i2c_read(radio); 35962306a36Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 36062306a36Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_PUPD0) 36162306a36Sopenharmony_ci f->frequency = (tea5764_get_freq(radio) * 2) / 125; 36262306a36Sopenharmony_ci else 36362306a36Sopenharmony_ci f->frequency = 0; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci return 0; 36662306a36Sopenharmony_ci} 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_cistatic int tea5764_s_ctrl(struct v4l2_ctrl *ctrl) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci struct tea5764_device *radio = 37162306a36Sopenharmony_ci container_of(ctrl->handler, struct tea5764_device, ctrl_handler); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci switch (ctrl->id) { 37462306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 37562306a36Sopenharmony_ci tea5764_mute(radio, ctrl->val); 37662306a36Sopenharmony_ci return 0; 37762306a36Sopenharmony_ci } 37862306a36Sopenharmony_ci return -EINVAL; 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops tea5764_ctrl_ops = { 38262306a36Sopenharmony_ci .s_ctrl = tea5764_s_ctrl, 38362306a36Sopenharmony_ci}; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci/* File system interface */ 38662306a36Sopenharmony_cistatic const struct v4l2_file_operations tea5764_fops = { 38762306a36Sopenharmony_ci .owner = THIS_MODULE, 38862306a36Sopenharmony_ci .open = v4l2_fh_open, 38962306a36Sopenharmony_ci .release = v4l2_fh_release, 39062306a36Sopenharmony_ci .poll = v4l2_ctrl_poll, 39162306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 39262306a36Sopenharmony_ci}; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops tea5764_ioctl_ops = { 39562306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 39662306a36Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 39762306a36Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 39862306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 39962306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 40062306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 40162306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 40262306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 40362306a36Sopenharmony_ci}; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci/* V4L2 interface */ 40662306a36Sopenharmony_cistatic const struct video_device tea5764_radio_template = { 40762306a36Sopenharmony_ci .name = "TEA5764 FM-Radio", 40862306a36Sopenharmony_ci .fops = &tea5764_fops, 40962306a36Sopenharmony_ci .ioctl_ops = &tea5764_ioctl_ops, 41062306a36Sopenharmony_ci .release = video_device_release_empty, 41162306a36Sopenharmony_ci}; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci/* I2C probe: check if the device exists and register with v4l if it is */ 41462306a36Sopenharmony_cistatic int tea5764_i2c_probe(struct i2c_client *client) 41562306a36Sopenharmony_ci{ 41662306a36Sopenharmony_ci struct tea5764_device *radio; 41762306a36Sopenharmony_ci struct v4l2_device *v4l2_dev; 41862306a36Sopenharmony_ci struct v4l2_ctrl_handler *hdl; 41962306a36Sopenharmony_ci struct tea5764_regs *r; 42062306a36Sopenharmony_ci int ret; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci PDEBUG("probe"); 42362306a36Sopenharmony_ci radio = kzalloc(sizeof(struct tea5764_device), GFP_KERNEL); 42462306a36Sopenharmony_ci if (!radio) 42562306a36Sopenharmony_ci return -ENOMEM; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci v4l2_dev = &radio->v4l2_dev; 42862306a36Sopenharmony_ci ret = v4l2_device_register(&client->dev, v4l2_dev); 42962306a36Sopenharmony_ci if (ret < 0) { 43062306a36Sopenharmony_ci v4l2_err(v4l2_dev, "could not register v4l2_device\n"); 43162306a36Sopenharmony_ci goto errfr; 43262306a36Sopenharmony_ci } 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci hdl = &radio->ctrl_handler; 43562306a36Sopenharmony_ci v4l2_ctrl_handler_init(hdl, 1); 43662306a36Sopenharmony_ci v4l2_ctrl_new_std(hdl, &tea5764_ctrl_ops, 43762306a36Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 43862306a36Sopenharmony_ci v4l2_dev->ctrl_handler = hdl; 43962306a36Sopenharmony_ci if (hdl->error) { 44062306a36Sopenharmony_ci ret = hdl->error; 44162306a36Sopenharmony_ci v4l2_err(v4l2_dev, "Could not register controls\n"); 44262306a36Sopenharmony_ci goto errunreg; 44362306a36Sopenharmony_ci } 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci mutex_init(&radio->mutex); 44662306a36Sopenharmony_ci radio->i2c_client = client; 44762306a36Sopenharmony_ci ret = tea5764_i2c_read(radio); 44862306a36Sopenharmony_ci if (ret) 44962306a36Sopenharmony_ci goto errunreg; 45062306a36Sopenharmony_ci r = &radio->regs; 45162306a36Sopenharmony_ci PDEBUG("chipid = %04X, manid = %04X", r->chipid, r->manid); 45262306a36Sopenharmony_ci if (r->chipid != TEA5764_CHIPID || 45362306a36Sopenharmony_ci (r->manid & 0x0fff) != TEA5764_MANID) { 45462306a36Sopenharmony_ci PWARN("This chip is not a TEA5764!"); 45562306a36Sopenharmony_ci ret = -EINVAL; 45662306a36Sopenharmony_ci goto errunreg; 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci radio->vdev = tea5764_radio_template; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci i2c_set_clientdata(client, radio); 46262306a36Sopenharmony_ci video_set_drvdata(&radio->vdev, radio); 46362306a36Sopenharmony_ci radio->vdev.lock = &radio->mutex; 46462306a36Sopenharmony_ci radio->vdev.v4l2_dev = v4l2_dev; 46562306a36Sopenharmony_ci radio->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci /* initialize and power off the chip */ 46862306a36Sopenharmony_ci tea5764_i2c_read(radio); 46962306a36Sopenharmony_ci tea5764_set_audout_mode(radio, V4L2_TUNER_MODE_STEREO); 47062306a36Sopenharmony_ci tea5764_mute(radio, 1); 47162306a36Sopenharmony_ci tea5764_power_down(radio); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci ret = video_register_device(&radio->vdev, VFL_TYPE_RADIO, radio_nr); 47462306a36Sopenharmony_ci if (ret < 0) { 47562306a36Sopenharmony_ci PWARN("Could not register video device!"); 47662306a36Sopenharmony_ci goto errunreg; 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci PINFO("registered."); 48062306a36Sopenharmony_ci return 0; 48162306a36Sopenharmony_cierrunreg: 48262306a36Sopenharmony_ci v4l2_ctrl_handler_free(hdl); 48362306a36Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 48462306a36Sopenharmony_cierrfr: 48562306a36Sopenharmony_ci kfree(radio); 48662306a36Sopenharmony_ci return ret; 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_cistatic void tea5764_i2c_remove(struct i2c_client *client) 49062306a36Sopenharmony_ci{ 49162306a36Sopenharmony_ci struct tea5764_device *radio = i2c_get_clientdata(client); 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci PDEBUG("remove"); 49462306a36Sopenharmony_ci if (radio) { 49562306a36Sopenharmony_ci tea5764_power_down(radio); 49662306a36Sopenharmony_ci video_unregister_device(&radio->vdev); 49762306a36Sopenharmony_ci v4l2_ctrl_handler_free(&radio->ctrl_handler); 49862306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 49962306a36Sopenharmony_ci kfree(radio); 50062306a36Sopenharmony_ci } 50162306a36Sopenharmony_ci} 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci/* I2C subsystem interface */ 50462306a36Sopenharmony_cistatic const struct i2c_device_id tea5764_id[] = { 50562306a36Sopenharmony_ci { "radio-tea5764", 0 }, 50662306a36Sopenharmony_ci { } /* Terminating entry */ 50762306a36Sopenharmony_ci}; 50862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, tea5764_id); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_cistatic struct i2c_driver tea5764_i2c_driver = { 51162306a36Sopenharmony_ci .driver = { 51262306a36Sopenharmony_ci .name = "radio-tea5764", 51362306a36Sopenharmony_ci }, 51462306a36Sopenharmony_ci .probe = tea5764_i2c_probe, 51562306a36Sopenharmony_ci .remove = tea5764_i2c_remove, 51662306a36Sopenharmony_ci .id_table = tea5764_id, 51762306a36Sopenharmony_ci}; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_cimodule_i2c_driver(tea5764_i2c_driver); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 52262306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 52362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 52462306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION); 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_cimodule_param(use_xtal, int, 0); 52762306a36Sopenharmony_ciMODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board"); 52862306a36Sopenharmony_cimodule_param(radio_nr, int, 0); 52962306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "video4linux device number to use"); 530