18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * driver/media/radio/radio-tea5764.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Driver for TEA5764 radio chip for linux 2.6. 68c2ecf20Sopenharmony_ci * This driver is for TEA5764 chip from NXP, used in EZX phones from Motorola. 78c2ecf20Sopenharmony_ci * The I2C protocol is used for communicate with chip. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Based in radio-tea5761.c Copyright (C) 2005 Nokia Corporation 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * Copyright (c) 2008 Fabio Belavenuto <belavenuto@gmail.com> 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * History: 148c2ecf20Sopenharmony_ci * 2008-12-06 Fabio Belavenuto <belavenuto@gmail.com> 158c2ecf20Sopenharmony_ci * initial code 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * TODO: 188c2ecf20Sopenharmony_ci * add platform_data support for IRQs platform dependencies 198c2ecf20Sopenharmony_ci * add RDS support 208c2ecf20Sopenharmony_ci */ 218c2ecf20Sopenharmony_ci#include <linux/kernel.h> 228c2ecf20Sopenharmony_ci#include <linux/slab.h> 238c2ecf20Sopenharmony_ci#include <linux/module.h> 248c2ecf20Sopenharmony_ci#include <linux/init.h> /* Initdata */ 258c2ecf20Sopenharmony_ci#include <linux/videodev2.h> /* kernel radio structs */ 268c2ecf20Sopenharmony_ci#include <linux/i2c.h> /* I2C */ 278c2ecf20Sopenharmony_ci#include <media/v4l2-common.h> 288c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 298c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 308c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h> 318c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define DRIVER_VERSION "0.0.2" 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define DRIVER_AUTHOR "Fabio Belavenuto <belavenuto@gmail.com>" 368c2ecf20Sopenharmony_ci#define DRIVER_DESC "A driver for the TEA5764 radio chip for EZX Phones." 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define PINFO(format, ...)\ 398c2ecf20Sopenharmony_ci printk(KERN_INFO KBUILD_MODNAME ": "\ 408c2ecf20Sopenharmony_ci DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) 418c2ecf20Sopenharmony_ci#define PWARN(format, ...)\ 428c2ecf20Sopenharmony_ci printk(KERN_WARNING KBUILD_MODNAME ": "\ 438c2ecf20Sopenharmony_ci DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) 448c2ecf20Sopenharmony_ci# define PDEBUG(format, ...)\ 458c2ecf20Sopenharmony_ci printk(KERN_DEBUG KBUILD_MODNAME ": "\ 468c2ecf20Sopenharmony_ci DRIVER_VERSION ": " format "\n", ## __VA_ARGS__) 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci/* Frequency limits in MHz -- these are European values. For Japanese 498c2ecf20Sopenharmony_cidevices, that would be 76000 and 91000. */ 508c2ecf20Sopenharmony_ci#define FREQ_MIN 87500U 518c2ecf20Sopenharmony_ci#define FREQ_MAX 108000U 528c2ecf20Sopenharmony_ci#define FREQ_MUL 16 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* TEA5764 registers */ 558c2ecf20Sopenharmony_ci#define TEA5764_MANID 0x002b 568c2ecf20Sopenharmony_ci#define TEA5764_CHIPID 0x5764 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci#define TEA5764_INTREG_BLMSK 0x0001 598c2ecf20Sopenharmony_ci#define TEA5764_INTREG_FRRMSK 0x0002 608c2ecf20Sopenharmony_ci#define TEA5764_INTREG_LEVMSK 0x0008 618c2ecf20Sopenharmony_ci#define TEA5764_INTREG_IFMSK 0x0010 628c2ecf20Sopenharmony_ci#define TEA5764_INTREG_BLMFLAG 0x0100 638c2ecf20Sopenharmony_ci#define TEA5764_INTREG_FRRFLAG 0x0200 648c2ecf20Sopenharmony_ci#define TEA5764_INTREG_LEVFLAG 0x0800 658c2ecf20Sopenharmony_ci#define TEA5764_INTREG_IFFLAG 0x1000 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#define TEA5764_FRQSET_SUD 0x8000 688c2ecf20Sopenharmony_ci#define TEA5764_FRQSET_SM 0x4000 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_PUPD1 0x8000 718c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_PUPD0 0x4000 728c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_BLIM 0x2000 738c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_SWPM 0x1000 748c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_IFCTC 0x0800 758c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_AFM 0x0400 768c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_SMUTE 0x0200 778c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_SNC 0x0100 788c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_MU 0x0080 798c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_SSL1 0x0040 808c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_SSL0 0x0020 818c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_HLSI 0x0010 828c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_MST 0x0008 838c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_SWP 0x0004 848c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_DTC 0x0002 858c2ecf20Sopenharmony_ci#define TEA5764_TNCTRL_AHLSI 0x0001 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci#define TEA5764_TUNCHK_LEVEL(x) (((x) & 0x00F0) >> 4) 888c2ecf20Sopenharmony_ci#define TEA5764_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9) 898c2ecf20Sopenharmony_ci#define TEA5764_TUNCHK_TUNTO 0x0100 908c2ecf20Sopenharmony_ci#define TEA5764_TUNCHK_LD 0x0008 918c2ecf20Sopenharmony_ci#define TEA5764_TUNCHK_STEREO 0x0004 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci#define TEA5764_TESTREG_TRIGFR 0x0800 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistruct tea5764_regs { 968c2ecf20Sopenharmony_ci u16 intreg; /* INTFLAG & INTMSK */ 978c2ecf20Sopenharmony_ci u16 frqset; /* FRQSETMSB & FRQSETLSB */ 988c2ecf20Sopenharmony_ci u16 tnctrl; /* TNCTRL1 & TNCTRL2 */ 998c2ecf20Sopenharmony_ci u16 frqchk; /* FRQCHKMSB & FRQCHKLSB */ 1008c2ecf20Sopenharmony_ci u16 tunchk; /* IFCHK & LEVCHK */ 1018c2ecf20Sopenharmony_ci u16 testreg; /* TESTBITS & TESTMODE */ 1028c2ecf20Sopenharmony_ci u16 rdsstat; /* RDSSTAT1 & RDSSTAT2 */ 1038c2ecf20Sopenharmony_ci u16 rdslb; /* RDSLBMSB & RDSLBLSB */ 1048c2ecf20Sopenharmony_ci u16 rdspb; /* RDSPBMSB & RDSPBLSB */ 1058c2ecf20Sopenharmony_ci u16 rdsbc; /* RDSBBC & RDSGBC */ 1068c2ecf20Sopenharmony_ci u16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */ 1078c2ecf20Sopenharmony_ci u16 rdsbbl; /* PAUSEDET & RDSBBL */ 1088c2ecf20Sopenharmony_ci u16 manid; /* MANID1 & MANID2 */ 1098c2ecf20Sopenharmony_ci u16 chipid; /* CHIPID1 & CHIPID2 */ 1108c2ecf20Sopenharmony_ci} __attribute__ ((packed)); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistruct tea5764_write_regs { 1138c2ecf20Sopenharmony_ci u8 intreg; /* INTMSK */ 1148c2ecf20Sopenharmony_ci __be16 frqset; /* FRQSETMSB & FRQSETLSB */ 1158c2ecf20Sopenharmony_ci __be16 tnctrl; /* TNCTRL1 & TNCTRL2 */ 1168c2ecf20Sopenharmony_ci __be16 testreg; /* TESTBITS & TESTMODE */ 1178c2ecf20Sopenharmony_ci __be16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */ 1188c2ecf20Sopenharmony_ci __be16 rdsbbl; /* PAUSEDET & RDSBBL */ 1198c2ecf20Sopenharmony_ci} __attribute__ ((packed)); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci#ifdef CONFIG_RADIO_TEA5764_XTAL 1228c2ecf20Sopenharmony_ci#define RADIO_TEA5764_XTAL 1 1238c2ecf20Sopenharmony_ci#else 1248c2ecf20Sopenharmony_ci#define RADIO_TEA5764_XTAL 0 1258c2ecf20Sopenharmony_ci#endif 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic int radio_nr = -1; 1288c2ecf20Sopenharmony_cistatic int use_xtal = RADIO_TEA5764_XTAL; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistruct tea5764_device { 1318c2ecf20Sopenharmony_ci struct v4l2_device v4l2_dev; 1328c2ecf20Sopenharmony_ci struct v4l2_ctrl_handler ctrl_handler; 1338c2ecf20Sopenharmony_ci struct i2c_client *i2c_client; 1348c2ecf20Sopenharmony_ci struct video_device vdev; 1358c2ecf20Sopenharmony_ci struct tea5764_regs regs; 1368c2ecf20Sopenharmony_ci struct mutex mutex; 1378c2ecf20Sopenharmony_ci}; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci/* I2C code related */ 1408c2ecf20Sopenharmony_cistatic int tea5764_i2c_read(struct tea5764_device *radio) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci int i; 1438c2ecf20Sopenharmony_ci u16 *p = (u16 *) &radio->regs; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci struct i2c_msg msgs[1] = { 1468c2ecf20Sopenharmony_ci { .addr = radio->i2c_client->addr, 1478c2ecf20Sopenharmony_ci .flags = I2C_M_RD, 1488c2ecf20Sopenharmony_ci .len = sizeof(radio->regs), 1498c2ecf20Sopenharmony_ci .buf = (void *)&radio->regs 1508c2ecf20Sopenharmony_ci }, 1518c2ecf20Sopenharmony_ci }; 1528c2ecf20Sopenharmony_ci if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1) 1538c2ecf20Sopenharmony_ci return -EIO; 1548c2ecf20Sopenharmony_ci for (i = 0; i < sizeof(struct tea5764_regs) / sizeof(u16); i++) 1558c2ecf20Sopenharmony_ci p[i] = __be16_to_cpu((__force __be16)p[i]); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci return 0; 1588c2ecf20Sopenharmony_ci} 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic int tea5764_i2c_write(struct tea5764_device *radio) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci struct tea5764_write_regs wr; 1638c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 1648c2ecf20Sopenharmony_ci struct i2c_msg msgs[1] = { 1658c2ecf20Sopenharmony_ci { 1668c2ecf20Sopenharmony_ci .addr = radio->i2c_client->addr, 1678c2ecf20Sopenharmony_ci .len = sizeof(wr), 1688c2ecf20Sopenharmony_ci .buf = (void *)&wr 1698c2ecf20Sopenharmony_ci }, 1708c2ecf20Sopenharmony_ci }; 1718c2ecf20Sopenharmony_ci wr.intreg = r->intreg & 0xff; 1728c2ecf20Sopenharmony_ci wr.frqset = __cpu_to_be16(r->frqset); 1738c2ecf20Sopenharmony_ci wr.tnctrl = __cpu_to_be16(r->tnctrl); 1748c2ecf20Sopenharmony_ci wr.testreg = __cpu_to_be16(r->testreg); 1758c2ecf20Sopenharmony_ci wr.rdsctrl = __cpu_to_be16(r->rdsctrl); 1768c2ecf20Sopenharmony_ci wr.rdsbbl = __cpu_to_be16(r->rdsbbl); 1778c2ecf20Sopenharmony_ci if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1) 1788c2ecf20Sopenharmony_ci return -EIO; 1798c2ecf20Sopenharmony_ci return 0; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic void tea5764_power_up(struct tea5764_device *radio) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (!(r->tnctrl & TEA5764_TNCTRL_PUPD0)) { 1878c2ecf20Sopenharmony_ci r->tnctrl &= ~(TEA5764_TNCTRL_AFM | TEA5764_TNCTRL_MU | 1888c2ecf20Sopenharmony_ci TEA5764_TNCTRL_HLSI); 1898c2ecf20Sopenharmony_ci if (!use_xtal) 1908c2ecf20Sopenharmony_ci r->testreg |= TEA5764_TESTREG_TRIGFR; 1918c2ecf20Sopenharmony_ci else 1928c2ecf20Sopenharmony_ci r->testreg &= ~TEA5764_TESTREG_TRIGFR; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci r->tnctrl |= TEA5764_TNCTRL_PUPD0; 1958c2ecf20Sopenharmony_ci tea5764_i2c_write(radio); 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic void tea5764_power_down(struct tea5764_device *radio) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_PUPD0) { 2048c2ecf20Sopenharmony_ci r->tnctrl &= ~TEA5764_TNCTRL_PUPD0; 2058c2ecf20Sopenharmony_ci tea5764_i2c_write(radio); 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cistatic void tea5764_set_freq(struct tea5764_device *radio, int freq) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci /* formula: (freq [+ or -] 225000) / 8192 */ 2148c2ecf20Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_HLSI) 2158c2ecf20Sopenharmony_ci r->frqset = (freq + 225000) / 8192; 2168c2ecf20Sopenharmony_ci else 2178c2ecf20Sopenharmony_ci r->frqset = (freq - 225000) / 8192; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int tea5764_get_freq(struct tea5764_device *radio) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_HLSI) 2258c2ecf20Sopenharmony_ci return (r->frqchk * 8192) - 225000; 2268c2ecf20Sopenharmony_ci else 2278c2ecf20Sopenharmony_ci return (r->frqchk * 8192) + 225000; 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci/* tune an frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ 2318c2ecf20Sopenharmony_cistatic void tea5764_tune(struct tea5764_device *radio, int freq) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci tea5764_set_freq(radio, freq); 2348c2ecf20Sopenharmony_ci if (tea5764_i2c_write(radio)) 2358c2ecf20Sopenharmony_ci PWARN("Could not set frequency!"); 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic void tea5764_set_audout_mode(struct tea5764_device *radio, int audmode) 2398c2ecf20Sopenharmony_ci{ 2408c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 2418c2ecf20Sopenharmony_ci int tnctrl = r->tnctrl; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci if (audmode == V4L2_TUNER_MODE_MONO) 2448c2ecf20Sopenharmony_ci r->tnctrl |= TEA5764_TNCTRL_MST; 2458c2ecf20Sopenharmony_ci else 2468c2ecf20Sopenharmony_ci r->tnctrl &= ~TEA5764_TNCTRL_MST; 2478c2ecf20Sopenharmony_ci if (tnctrl != r->tnctrl) 2488c2ecf20Sopenharmony_ci tea5764_i2c_write(radio); 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistatic int tea5764_get_audout_mode(struct tea5764_device *radio) 2528c2ecf20Sopenharmony_ci{ 2538c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_MST) 2568c2ecf20Sopenharmony_ci return V4L2_TUNER_MODE_MONO; 2578c2ecf20Sopenharmony_ci else 2588c2ecf20Sopenharmony_ci return V4L2_TUNER_MODE_STEREO; 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cistatic void tea5764_mute(struct tea5764_device *radio, int on) 2628c2ecf20Sopenharmony_ci{ 2638c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 2648c2ecf20Sopenharmony_ci int tnctrl = r->tnctrl; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci if (on) 2678c2ecf20Sopenharmony_ci r->tnctrl |= TEA5764_TNCTRL_MU; 2688c2ecf20Sopenharmony_ci else 2698c2ecf20Sopenharmony_ci r->tnctrl &= ~TEA5764_TNCTRL_MU; 2708c2ecf20Sopenharmony_ci if (tnctrl != r->tnctrl) 2718c2ecf20Sopenharmony_ci tea5764_i2c_write(radio); 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci/* V4L2 vidioc */ 2758c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 2768c2ecf20Sopenharmony_ci struct v4l2_capability *v) 2778c2ecf20Sopenharmony_ci{ 2788c2ecf20Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 2798c2ecf20Sopenharmony_ci struct video_device *dev = &radio->vdev; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci strscpy(v->driver, dev->dev.driver->name, sizeof(v->driver)); 2828c2ecf20Sopenharmony_ci strscpy(v->card, dev->name, sizeof(v->card)); 2838c2ecf20Sopenharmony_ci snprintf(v->bus_info, sizeof(v->bus_info), 2848c2ecf20Sopenharmony_ci "I2C:%s", dev_name(&dev->dev)); 2858c2ecf20Sopenharmony_ci return 0; 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 2898c2ecf20Sopenharmony_ci struct v4l2_tuner *v) 2908c2ecf20Sopenharmony_ci{ 2918c2ecf20Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 2928c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci if (v->index > 0) 2958c2ecf20Sopenharmony_ci return -EINVAL; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 2988c2ecf20Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 2998c2ecf20Sopenharmony_ci tea5764_i2c_read(radio); 3008c2ecf20Sopenharmony_ci v->rangelow = FREQ_MIN * FREQ_MUL; 3018c2ecf20Sopenharmony_ci v->rangehigh = FREQ_MAX * FREQ_MUL; 3028c2ecf20Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; 3038c2ecf20Sopenharmony_ci if (r->tunchk & TEA5764_TUNCHK_STEREO) 3048c2ecf20Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_STEREO; 3058c2ecf20Sopenharmony_ci else 3068c2ecf20Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_MONO; 3078c2ecf20Sopenharmony_ci v->audmode = tea5764_get_audout_mode(radio); 3088c2ecf20Sopenharmony_ci v->signal = TEA5764_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf; 3098c2ecf20Sopenharmony_ci v->afc = TEA5764_TUNCHK_IFCNT(r->tunchk); 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci return 0; 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 3158c2ecf20Sopenharmony_ci const struct v4l2_tuner *v) 3168c2ecf20Sopenharmony_ci{ 3178c2ecf20Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci if (v->index > 0) 3208c2ecf20Sopenharmony_ci return -EINVAL; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci tea5764_set_audout_mode(radio, v->audmode); 3238c2ecf20Sopenharmony_ci return 0; 3248c2ecf20Sopenharmony_ci} 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 3278c2ecf20Sopenharmony_ci const struct v4l2_frequency *f) 3288c2ecf20Sopenharmony_ci{ 3298c2ecf20Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 3308c2ecf20Sopenharmony_ci unsigned freq = f->frequency; 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 3338c2ecf20Sopenharmony_ci return -EINVAL; 3348c2ecf20Sopenharmony_ci if (freq == 0) { 3358c2ecf20Sopenharmony_ci /* We special case this as a power down control. */ 3368c2ecf20Sopenharmony_ci tea5764_power_down(radio); 3378c2ecf20Sopenharmony_ci /* Yes, that's what is returned in this case. This 3388c2ecf20Sopenharmony_ci whole special case is non-compliant and should really 3398c2ecf20Sopenharmony_ci be replaced with something better, but changing this 3408c2ecf20Sopenharmony_ci might well break code that depends on this behavior. 3418c2ecf20Sopenharmony_ci So we keep it as-is. */ 3428c2ecf20Sopenharmony_ci return -EINVAL; 3438c2ecf20Sopenharmony_ci } 3448c2ecf20Sopenharmony_ci freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); 3458c2ecf20Sopenharmony_ci tea5764_power_up(radio); 3468c2ecf20Sopenharmony_ci tea5764_tune(radio, (freq * 125) / 2); 3478c2ecf20Sopenharmony_ci return 0; 3488c2ecf20Sopenharmony_ci} 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 3518c2ecf20Sopenharmony_ci struct v4l2_frequency *f) 3528c2ecf20Sopenharmony_ci{ 3538c2ecf20Sopenharmony_ci struct tea5764_device *radio = video_drvdata(file); 3548c2ecf20Sopenharmony_ci struct tea5764_regs *r = &radio->regs; 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci if (f->tuner != 0) 3578c2ecf20Sopenharmony_ci return -EINVAL; 3588c2ecf20Sopenharmony_ci tea5764_i2c_read(radio); 3598c2ecf20Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 3608c2ecf20Sopenharmony_ci if (r->tnctrl & TEA5764_TNCTRL_PUPD0) 3618c2ecf20Sopenharmony_ci f->frequency = (tea5764_get_freq(radio) * 2) / 125; 3628c2ecf20Sopenharmony_ci else 3638c2ecf20Sopenharmony_ci f->frequency = 0; 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci return 0; 3668c2ecf20Sopenharmony_ci} 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_cistatic int tea5764_s_ctrl(struct v4l2_ctrl *ctrl) 3698c2ecf20Sopenharmony_ci{ 3708c2ecf20Sopenharmony_ci struct tea5764_device *radio = 3718c2ecf20Sopenharmony_ci container_of(ctrl->handler, struct tea5764_device, ctrl_handler); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci switch (ctrl->id) { 3748c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 3758c2ecf20Sopenharmony_ci tea5764_mute(radio, ctrl->val); 3768c2ecf20Sopenharmony_ci return 0; 3778c2ecf20Sopenharmony_ci } 3788c2ecf20Sopenharmony_ci return -EINVAL; 3798c2ecf20Sopenharmony_ci} 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops tea5764_ctrl_ops = { 3828c2ecf20Sopenharmony_ci .s_ctrl = tea5764_s_ctrl, 3838c2ecf20Sopenharmony_ci}; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci/* File system interface */ 3868c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations tea5764_fops = { 3878c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 3888c2ecf20Sopenharmony_ci .open = v4l2_fh_open, 3898c2ecf20Sopenharmony_ci .release = v4l2_fh_release, 3908c2ecf20Sopenharmony_ci .poll = v4l2_ctrl_poll, 3918c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 3928c2ecf20Sopenharmony_ci}; 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops tea5764_ioctl_ops = { 3958c2ecf20Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 3968c2ecf20Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 3978c2ecf20Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 3988c2ecf20Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 3998c2ecf20Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 4008c2ecf20Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 4018c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 4028c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 4038c2ecf20Sopenharmony_ci}; 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci/* V4L2 interface */ 4068c2ecf20Sopenharmony_cistatic const struct video_device tea5764_radio_template = { 4078c2ecf20Sopenharmony_ci .name = "TEA5764 FM-Radio", 4088c2ecf20Sopenharmony_ci .fops = &tea5764_fops, 4098c2ecf20Sopenharmony_ci .ioctl_ops = &tea5764_ioctl_ops, 4108c2ecf20Sopenharmony_ci .release = video_device_release_empty, 4118c2ecf20Sopenharmony_ci}; 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci/* I2C probe: check if the device exists and register with v4l if it is */ 4148c2ecf20Sopenharmony_cistatic int tea5764_i2c_probe(struct i2c_client *client, 4158c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 4168c2ecf20Sopenharmony_ci{ 4178c2ecf20Sopenharmony_ci struct tea5764_device *radio; 4188c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev; 4198c2ecf20Sopenharmony_ci struct v4l2_ctrl_handler *hdl; 4208c2ecf20Sopenharmony_ci struct tea5764_regs *r; 4218c2ecf20Sopenharmony_ci int ret; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci PDEBUG("probe"); 4248c2ecf20Sopenharmony_ci radio = kzalloc(sizeof(struct tea5764_device), GFP_KERNEL); 4258c2ecf20Sopenharmony_ci if (!radio) 4268c2ecf20Sopenharmony_ci return -ENOMEM; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci v4l2_dev = &radio->v4l2_dev; 4298c2ecf20Sopenharmony_ci ret = v4l2_device_register(&client->dev, v4l2_dev); 4308c2ecf20Sopenharmony_ci if (ret < 0) { 4318c2ecf20Sopenharmony_ci v4l2_err(v4l2_dev, "could not register v4l2_device\n"); 4328c2ecf20Sopenharmony_ci goto errfr; 4338c2ecf20Sopenharmony_ci } 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci hdl = &radio->ctrl_handler; 4368c2ecf20Sopenharmony_ci v4l2_ctrl_handler_init(hdl, 1); 4378c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(hdl, &tea5764_ctrl_ops, 4388c2ecf20Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 4398c2ecf20Sopenharmony_ci v4l2_dev->ctrl_handler = hdl; 4408c2ecf20Sopenharmony_ci if (hdl->error) { 4418c2ecf20Sopenharmony_ci ret = hdl->error; 4428c2ecf20Sopenharmony_ci v4l2_err(v4l2_dev, "Could not register controls\n"); 4438c2ecf20Sopenharmony_ci goto errunreg; 4448c2ecf20Sopenharmony_ci } 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci mutex_init(&radio->mutex); 4478c2ecf20Sopenharmony_ci radio->i2c_client = client; 4488c2ecf20Sopenharmony_ci ret = tea5764_i2c_read(radio); 4498c2ecf20Sopenharmony_ci if (ret) 4508c2ecf20Sopenharmony_ci goto errunreg; 4518c2ecf20Sopenharmony_ci r = &radio->regs; 4528c2ecf20Sopenharmony_ci PDEBUG("chipid = %04X, manid = %04X", r->chipid, r->manid); 4538c2ecf20Sopenharmony_ci if (r->chipid != TEA5764_CHIPID || 4548c2ecf20Sopenharmony_ci (r->manid & 0x0fff) != TEA5764_MANID) { 4558c2ecf20Sopenharmony_ci PWARN("This chip is not a TEA5764!"); 4568c2ecf20Sopenharmony_ci ret = -EINVAL; 4578c2ecf20Sopenharmony_ci goto errunreg; 4588c2ecf20Sopenharmony_ci } 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_ci radio->vdev = tea5764_radio_template; 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci i2c_set_clientdata(client, radio); 4638c2ecf20Sopenharmony_ci video_set_drvdata(&radio->vdev, radio); 4648c2ecf20Sopenharmony_ci radio->vdev.lock = &radio->mutex; 4658c2ecf20Sopenharmony_ci radio->vdev.v4l2_dev = v4l2_dev; 4668c2ecf20Sopenharmony_ci radio->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci /* initialize and power off the chip */ 4698c2ecf20Sopenharmony_ci tea5764_i2c_read(radio); 4708c2ecf20Sopenharmony_ci tea5764_set_audout_mode(radio, V4L2_TUNER_MODE_STEREO); 4718c2ecf20Sopenharmony_ci tea5764_mute(radio, 1); 4728c2ecf20Sopenharmony_ci tea5764_power_down(radio); 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci ret = video_register_device(&radio->vdev, VFL_TYPE_RADIO, radio_nr); 4758c2ecf20Sopenharmony_ci if (ret < 0) { 4768c2ecf20Sopenharmony_ci PWARN("Could not register video device!"); 4778c2ecf20Sopenharmony_ci goto errunreg; 4788c2ecf20Sopenharmony_ci } 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci PINFO("registered."); 4818c2ecf20Sopenharmony_ci return 0; 4828c2ecf20Sopenharmony_cierrunreg: 4838c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(hdl); 4848c2ecf20Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 4858c2ecf20Sopenharmony_cierrfr: 4868c2ecf20Sopenharmony_ci kfree(radio); 4878c2ecf20Sopenharmony_ci return ret; 4888c2ecf20Sopenharmony_ci} 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_cistatic int tea5764_i2c_remove(struct i2c_client *client) 4918c2ecf20Sopenharmony_ci{ 4928c2ecf20Sopenharmony_ci struct tea5764_device *radio = i2c_get_clientdata(client); 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci PDEBUG("remove"); 4958c2ecf20Sopenharmony_ci if (radio) { 4968c2ecf20Sopenharmony_ci tea5764_power_down(radio); 4978c2ecf20Sopenharmony_ci video_unregister_device(&radio->vdev); 4988c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&radio->ctrl_handler); 4998c2ecf20Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 5008c2ecf20Sopenharmony_ci kfree(radio); 5018c2ecf20Sopenharmony_ci } 5028c2ecf20Sopenharmony_ci return 0; 5038c2ecf20Sopenharmony_ci} 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci/* I2C subsystem interface */ 5068c2ecf20Sopenharmony_cistatic const struct i2c_device_id tea5764_id[] = { 5078c2ecf20Sopenharmony_ci { "radio-tea5764", 0 }, 5088c2ecf20Sopenharmony_ci { } /* Terminating entry */ 5098c2ecf20Sopenharmony_ci}; 5108c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, tea5764_id); 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_cistatic struct i2c_driver tea5764_i2c_driver = { 5138c2ecf20Sopenharmony_ci .driver = { 5148c2ecf20Sopenharmony_ci .name = "radio-tea5764", 5158c2ecf20Sopenharmony_ci }, 5168c2ecf20Sopenharmony_ci .probe = tea5764_i2c_probe, 5178c2ecf20Sopenharmony_ci .remove = tea5764_i2c_remove, 5188c2ecf20Sopenharmony_ci .id_table = tea5764_id, 5198c2ecf20Sopenharmony_ci}; 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_cimodule_i2c_driver(tea5764_i2c_driver); 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 5248c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 5258c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 5268c2ecf20Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION); 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_cimodule_param(use_xtal, int, 0); 5298c2ecf20Sopenharmony_ciMODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board"); 5308c2ecf20Sopenharmony_cimodule_param(radio_nr, int, 0); 5318c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "video4linux device number to use"); 532