18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/delay.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/sched.h> 138c2ecf20Sopenharmony_ci#include <asm/io.h> 148c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 158c2ecf20Sopenharmony_ci#include <media/v4l2-dev.h> 168c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h> 178c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 188c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 198c2ecf20Sopenharmony_ci#include <media/drv-intf/tea575x.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); 228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips"); 238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* 268c2ecf20Sopenharmony_ci * definitions 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */ 308c2ecf20Sopenharmony_ci#define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */ 318c2ecf20Sopenharmony_ci#define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */ 328c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_MASK (3<<20) 338c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_FM (0<<20) 348c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_MW (1<<20) 358c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_LW (2<<20) 368c2ecf20Sopenharmony_ci#define TEA575X_BIT_BAND_SW (3<<20) 378c2ecf20Sopenharmony_ci#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */ 388c2ecf20Sopenharmony_ci#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */ 398c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */ 408c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */ 418c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */ 428c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */ 438c2ecf20Sopenharmony_ci#define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */ 448c2ecf20Sopenharmony_ci#define TEA575X_BIT_DUMMY (1<<15) /* buffer */ 458c2ecf20Sopenharmony_ci#define TEA575X_BIT_FREQ_MASK 0x7fff 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cienum { BAND_FM, BAND_FM_JAPAN, BAND_AM }; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = { 508c2ecf20Sopenharmony_ci { 518c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 528c2ecf20Sopenharmony_ci .index = 0, 538c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 548c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS, 558c2ecf20Sopenharmony_ci .rangelow = 87500 * 16, 568c2ecf20Sopenharmony_ci .rangehigh = 108000 * 16, 578c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 588c2ecf20Sopenharmony_ci }, 598c2ecf20Sopenharmony_ci { 608c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 618c2ecf20Sopenharmony_ci .index = 0, 628c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 638c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS, 648c2ecf20Sopenharmony_ci .rangelow = 76000 * 16, 658c2ecf20Sopenharmony_ci .rangehigh = 91000 * 16, 668c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 678c2ecf20Sopenharmony_ci }, 688c2ecf20Sopenharmony_ci { 698c2ecf20Sopenharmony_ci .type = V4L2_TUNER_RADIO, 708c2ecf20Sopenharmony_ci .index = 1, 718c2ecf20Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, 728c2ecf20Sopenharmony_ci .rangelow = 530 * 16, 738c2ecf20Sopenharmony_ci .rangehigh = 1710 * 16, 748c2ecf20Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 758c2ecf20Sopenharmony_ci }, 768c2ecf20Sopenharmony_ci}; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci/* 798c2ecf20Sopenharmony_ci * lowlevel part 808c2ecf20Sopenharmony_ci */ 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci u16 l; 858c2ecf20Sopenharmony_ci u8 data; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (tea->ops->write_val) 888c2ecf20Sopenharmony_ci return tea->ops->write_val(tea, val); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci tea->ops->set_direction(tea, 1); 918c2ecf20Sopenharmony_ci udelay(16); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci for (l = 25; l > 0; l--) { 948c2ecf20Sopenharmony_ci data = (val >> 24) & TEA575X_DATA; 958c2ecf20Sopenharmony_ci val <<= 1; /* shift data */ 968c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, data | TEA575X_WREN); 978c2ecf20Sopenharmony_ci udelay(2); 988c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK); 998c2ecf20Sopenharmony_ci udelay(2); 1008c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, data | TEA575X_WREN); 1018c2ecf20Sopenharmony_ci udelay(2); 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (!tea->mute) 1058c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, 0); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic u32 snd_tea575x_read(struct snd_tea575x *tea) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci u16 l, rdata; 1118c2ecf20Sopenharmony_ci u32 data = 0; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci if (tea->ops->read_val) 1148c2ecf20Sopenharmony_ci return tea->ops->read_val(tea); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci tea->ops->set_direction(tea, 0); 1178c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, 0); 1188c2ecf20Sopenharmony_ci udelay(16); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci for (l = 24; l--;) { 1218c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, TEA575X_CLK); 1228c2ecf20Sopenharmony_ci udelay(2); 1238c2ecf20Sopenharmony_ci if (!l) 1248c2ecf20Sopenharmony_ci tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1; 1258c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, 0); 1268c2ecf20Sopenharmony_ci udelay(2); 1278c2ecf20Sopenharmony_ci data <<= 1; /* shift data */ 1288c2ecf20Sopenharmony_ci rdata = tea->ops->get_pins(tea); 1298c2ecf20Sopenharmony_ci if (!l) 1308c2ecf20Sopenharmony_ci tea->stereo = (rdata & TEA575X_MOST) ? 0 : 1; 1318c2ecf20Sopenharmony_ci if (rdata & TEA575X_DATA) 1328c2ecf20Sopenharmony_ci data++; 1338c2ecf20Sopenharmony_ci udelay(2); 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (tea->mute) 1378c2ecf20Sopenharmony_ci tea->ops->set_pins(tea, TEA575X_WREN); 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci return data; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci u32 freq = val & TEA575X_BIT_FREQ_MASK; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (freq == 0) 1478c2ecf20Sopenharmony_ci return freq; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci switch (tea->band) { 1508c2ecf20Sopenharmony_ci case BAND_FM: 1518c2ecf20Sopenharmony_ci /* freq *= 12.5 */ 1528c2ecf20Sopenharmony_ci freq *= 125; 1538c2ecf20Sopenharmony_ci freq /= 10; 1548c2ecf20Sopenharmony_ci /* crystal fixup */ 1558c2ecf20Sopenharmony_ci freq -= TEA575X_FMIF; 1568c2ecf20Sopenharmony_ci break; 1578c2ecf20Sopenharmony_ci case BAND_FM_JAPAN: 1588c2ecf20Sopenharmony_ci /* freq *= 12.5 */ 1598c2ecf20Sopenharmony_ci freq *= 125; 1608c2ecf20Sopenharmony_ci freq /= 10; 1618c2ecf20Sopenharmony_ci /* crystal fixup */ 1628c2ecf20Sopenharmony_ci freq += TEA575X_FMIF; 1638c2ecf20Sopenharmony_ci break; 1648c2ecf20Sopenharmony_ci case BAND_AM: 1658c2ecf20Sopenharmony_ci /* crystal fixup */ 1668c2ecf20Sopenharmony_ci freq -= TEA575X_AMIF; 1678c2ecf20Sopenharmony_ci break; 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci return clamp(freq * 16, bands[tea->band].rangelow, 1718c2ecf20Sopenharmony_ci bands[tea->band].rangehigh); /* from kHz */ 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic u32 snd_tea575x_get_freq(struct snd_tea575x *tea) 1758c2ecf20Sopenharmony_ci{ 1768c2ecf20Sopenharmony_ci return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea)); 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_civoid snd_tea575x_set_freq(struct snd_tea575x *tea) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci u32 freq = tea->freq / 16; /* to kHz */ 1828c2ecf20Sopenharmony_ci u32 band = 0; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci switch (tea->band) { 1858c2ecf20Sopenharmony_ci case BAND_FM: 1868c2ecf20Sopenharmony_ci band = TEA575X_BIT_BAND_FM; 1878c2ecf20Sopenharmony_ci /* crystal fixup */ 1888c2ecf20Sopenharmony_ci freq += TEA575X_FMIF; 1898c2ecf20Sopenharmony_ci /* freq /= 12.5 */ 1908c2ecf20Sopenharmony_ci freq *= 10; 1918c2ecf20Sopenharmony_ci freq /= 125; 1928c2ecf20Sopenharmony_ci break; 1938c2ecf20Sopenharmony_ci case BAND_FM_JAPAN: 1948c2ecf20Sopenharmony_ci band = TEA575X_BIT_BAND_FM; 1958c2ecf20Sopenharmony_ci /* crystal fixup */ 1968c2ecf20Sopenharmony_ci freq -= TEA575X_FMIF; 1978c2ecf20Sopenharmony_ci /* freq /= 12.5 */ 1988c2ecf20Sopenharmony_ci freq *= 10; 1998c2ecf20Sopenharmony_ci freq /= 125; 2008c2ecf20Sopenharmony_ci break; 2018c2ecf20Sopenharmony_ci case BAND_AM: 2028c2ecf20Sopenharmony_ci band = TEA575X_BIT_BAND_MW; 2038c2ecf20Sopenharmony_ci /* crystal fixup */ 2048c2ecf20Sopenharmony_ci freq += TEA575X_AMIF; 2058c2ecf20Sopenharmony_ci break; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK); 2098c2ecf20Sopenharmony_ci tea->val |= band; 2108c2ecf20Sopenharmony_ci tea->val |= freq & TEA575X_BIT_FREQ_MASK; 2118c2ecf20Sopenharmony_ci snd_tea575x_write(tea, tea->val); 2128c2ecf20Sopenharmony_ci tea->freq = snd_tea575x_val_to_freq(tea, tea->val); 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_set_freq); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci/* 2178c2ecf20Sopenharmony_ci * Linux Video interface 2188c2ecf20Sopenharmony_ci */ 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 2218c2ecf20Sopenharmony_ci struct v4l2_capability *v) 2228c2ecf20Sopenharmony_ci{ 2238c2ecf20Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); 2268c2ecf20Sopenharmony_ci strscpy(v->card, tea->card, sizeof(v->card)); 2278c2ecf20Sopenharmony_ci strlcat(v->card, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card)); 2288c2ecf20Sopenharmony_ci strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); 2298c2ecf20Sopenharmony_ci return 0; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ciint snd_tea575x_enum_freq_bands(struct snd_tea575x *tea, 2338c2ecf20Sopenharmony_ci struct v4l2_frequency_band *band) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci int index; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci if (band->tuner != 0) 2388c2ecf20Sopenharmony_ci return -EINVAL; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci switch (band->index) { 2418c2ecf20Sopenharmony_ci case 0: 2428c2ecf20Sopenharmony_ci if (tea->tea5759) 2438c2ecf20Sopenharmony_ci index = BAND_FM_JAPAN; 2448c2ecf20Sopenharmony_ci else 2458c2ecf20Sopenharmony_ci index = BAND_FM; 2468c2ecf20Sopenharmony_ci break; 2478c2ecf20Sopenharmony_ci case 1: 2488c2ecf20Sopenharmony_ci if (tea->has_am) { 2498c2ecf20Sopenharmony_ci index = BAND_AM; 2508c2ecf20Sopenharmony_ci break; 2518c2ecf20Sopenharmony_ci } 2528c2ecf20Sopenharmony_ci fallthrough; 2538c2ecf20Sopenharmony_ci default: 2548c2ecf20Sopenharmony_ci return -EINVAL; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci *band = bands[index]; 2588c2ecf20Sopenharmony_ci if (!tea->cannot_read_data) 2598c2ecf20Sopenharmony_ci band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci return 0; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_enum_freq_bands); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic int vidioc_enum_freq_bands(struct file *file, void *priv, 2668c2ecf20Sopenharmony_ci struct v4l2_frequency_band *band) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci return snd_tea575x_enum_freq_bands(tea, band); 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ciint snd_tea575x_g_tuner(struct snd_tea575x *tea, struct v4l2_tuner *v) 2748c2ecf20Sopenharmony_ci{ 2758c2ecf20Sopenharmony_ci struct v4l2_frequency_band band_fm = { 0, }; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci if (v->index > 0) 2788c2ecf20Sopenharmony_ci return -EINVAL; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci snd_tea575x_read(tea); 2818c2ecf20Sopenharmony_ci snd_tea575x_enum_freq_bands(tea, &band_fm); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci memset(v, 0, sizeof(*v)); 2848c2ecf20Sopenharmony_ci strscpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name)); 2858c2ecf20Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 2868c2ecf20Sopenharmony_ci v->capability = band_fm.capability; 2878c2ecf20Sopenharmony_ci v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow; 2888c2ecf20Sopenharmony_ci v->rangehigh = band_fm.rangehigh; 2898c2ecf20Sopenharmony_ci v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 2908c2ecf20Sopenharmony_ci v->audmode = (tea->val & TEA575X_BIT_MONO) ? 2918c2ecf20Sopenharmony_ci V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; 2928c2ecf20Sopenharmony_ci v->signal = tea->tuned ? 0xffff : 0; 2938c2ecf20Sopenharmony_ci return 0; 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_g_tuner); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 2988c2ecf20Sopenharmony_ci struct v4l2_tuner *v) 2998c2ecf20Sopenharmony_ci{ 3008c2ecf20Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci return snd_tea575x_g_tuner(tea, v); 3038c2ecf20Sopenharmony_ci} 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 3068c2ecf20Sopenharmony_ci const struct v4l2_tuner *v) 3078c2ecf20Sopenharmony_ci{ 3088c2ecf20Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 3098c2ecf20Sopenharmony_ci u32 orig_val = tea->val; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci if (v->index) 3128c2ecf20Sopenharmony_ci return -EINVAL; 3138c2ecf20Sopenharmony_ci tea->val &= ~TEA575X_BIT_MONO; 3148c2ecf20Sopenharmony_ci if (v->audmode == V4L2_TUNER_MODE_MONO) 3158c2ecf20Sopenharmony_ci tea->val |= TEA575X_BIT_MONO; 3168c2ecf20Sopenharmony_ci /* Only apply changes if currently tuning FM */ 3178c2ecf20Sopenharmony_ci if (tea->band != BAND_AM && tea->val != orig_val) 3188c2ecf20Sopenharmony_ci snd_tea575x_set_freq(tea); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci return 0; 3218c2ecf20Sopenharmony_ci} 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 3248c2ecf20Sopenharmony_ci struct v4l2_frequency *f) 3258c2ecf20Sopenharmony_ci{ 3268c2ecf20Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci if (f->tuner != 0) 3298c2ecf20Sopenharmony_ci return -EINVAL; 3308c2ecf20Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 3318c2ecf20Sopenharmony_ci f->frequency = tea->freq; 3328c2ecf20Sopenharmony_ci return 0; 3338c2ecf20Sopenharmony_ci} 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 3368c2ecf20Sopenharmony_ci const struct v4l2_frequency *f) 3378c2ecf20Sopenharmony_ci{ 3388c2ecf20Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 3418c2ecf20Sopenharmony_ci return -EINVAL; 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci if (tea->has_am && f->frequency < (20000 * 16)) 3448c2ecf20Sopenharmony_ci tea->band = BAND_AM; 3458c2ecf20Sopenharmony_ci else if (tea->tea5759) 3468c2ecf20Sopenharmony_ci tea->band = BAND_FM_JAPAN; 3478c2ecf20Sopenharmony_ci else 3488c2ecf20Sopenharmony_ci tea->band = BAND_FM; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow, 3518c2ecf20Sopenharmony_ci bands[tea->band].rangehigh); 3528c2ecf20Sopenharmony_ci snd_tea575x_set_freq(tea); 3538c2ecf20Sopenharmony_ci return 0; 3548c2ecf20Sopenharmony_ci} 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ciint snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea, 3578c2ecf20Sopenharmony_ci const struct v4l2_hw_freq_seek *a) 3588c2ecf20Sopenharmony_ci{ 3598c2ecf20Sopenharmony_ci unsigned long timeout; 3608c2ecf20Sopenharmony_ci int i, spacing; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci if (tea->cannot_read_data) 3638c2ecf20Sopenharmony_ci return -ENOTTY; 3648c2ecf20Sopenharmony_ci if (a->tuner || a->wrap_around) 3658c2ecf20Sopenharmony_ci return -EINVAL; 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 3688c2ecf20Sopenharmony_ci return -EWOULDBLOCK; 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci if (a->rangelow || a->rangehigh) { 3718c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(bands); i++) { 3728c2ecf20Sopenharmony_ci if ((i == BAND_FM && tea->tea5759) || 3738c2ecf20Sopenharmony_ci (i == BAND_FM_JAPAN && !tea->tea5759) || 3748c2ecf20Sopenharmony_ci (i == BAND_AM && !tea->has_am)) 3758c2ecf20Sopenharmony_ci continue; 3768c2ecf20Sopenharmony_ci if (bands[i].rangelow == a->rangelow && 3778c2ecf20Sopenharmony_ci bands[i].rangehigh == a->rangehigh) 3788c2ecf20Sopenharmony_ci break; 3798c2ecf20Sopenharmony_ci } 3808c2ecf20Sopenharmony_ci if (i == ARRAY_SIZE(bands)) 3818c2ecf20Sopenharmony_ci return -EINVAL; /* No matching band found */ 3828c2ecf20Sopenharmony_ci if (i != tea->band) { 3838c2ecf20Sopenharmony_ci tea->band = i; 3848c2ecf20Sopenharmony_ci tea->freq = clamp(tea->freq, bands[i].rangelow, 3858c2ecf20Sopenharmony_ci bands[i].rangehigh); 3868c2ecf20Sopenharmony_ci snd_tea575x_set_freq(tea); 3878c2ecf20Sopenharmony_ci } 3888c2ecf20Sopenharmony_ci } 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */ 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci /* clear the frequency, HW will fill it in */ 3938c2ecf20Sopenharmony_ci tea->val &= ~TEA575X_BIT_FREQ_MASK; 3948c2ecf20Sopenharmony_ci tea->val |= TEA575X_BIT_SEARCH; 3958c2ecf20Sopenharmony_ci if (a->seek_upward) 3968c2ecf20Sopenharmony_ci tea->val |= TEA575X_BIT_UPDOWN; 3978c2ecf20Sopenharmony_ci else 3988c2ecf20Sopenharmony_ci tea->val &= ~TEA575X_BIT_UPDOWN; 3998c2ecf20Sopenharmony_ci snd_tea575x_write(tea, tea->val); 4008c2ecf20Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(10000); 4018c2ecf20Sopenharmony_ci for (;;) { 4028c2ecf20Sopenharmony_ci if (time_after(jiffies, timeout)) 4038c2ecf20Sopenharmony_ci break; 4048c2ecf20Sopenharmony_ci if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { 4058c2ecf20Sopenharmony_ci /* some signal arrived, stop search */ 4068c2ecf20Sopenharmony_ci tea->val &= ~TEA575X_BIT_SEARCH; 4078c2ecf20Sopenharmony_ci snd_tea575x_set_freq(tea); 4088c2ecf20Sopenharmony_ci return -ERESTARTSYS; 4098c2ecf20Sopenharmony_ci } 4108c2ecf20Sopenharmony_ci if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) { 4118c2ecf20Sopenharmony_ci u32 freq; 4128c2ecf20Sopenharmony_ci 4138c2ecf20Sopenharmony_ci /* Found a frequency, wait until it can be read */ 4148c2ecf20Sopenharmony_ci for (i = 0; i < 100; i++) { 4158c2ecf20Sopenharmony_ci msleep(10); 4168c2ecf20Sopenharmony_ci freq = snd_tea575x_get_freq(tea); 4178c2ecf20Sopenharmony_ci if (freq) /* available */ 4188c2ecf20Sopenharmony_ci break; 4198c2ecf20Sopenharmony_ci } 4208c2ecf20Sopenharmony_ci if (freq == 0) /* shouldn't happen */ 4218c2ecf20Sopenharmony_ci break; 4228c2ecf20Sopenharmony_ci /* 4238c2ecf20Sopenharmony_ci * if we moved by less than the spacing, or in the 4248c2ecf20Sopenharmony_ci * wrong direction, continue seeking 4258c2ecf20Sopenharmony_ci */ 4268c2ecf20Sopenharmony_ci if (abs(tea->freq - freq) < 16 * spacing || 4278c2ecf20Sopenharmony_ci (a->seek_upward && freq < tea->freq) || 4288c2ecf20Sopenharmony_ci (!a->seek_upward && freq > tea->freq)) { 4298c2ecf20Sopenharmony_ci snd_tea575x_write(tea, tea->val); 4308c2ecf20Sopenharmony_ci continue; 4318c2ecf20Sopenharmony_ci } 4328c2ecf20Sopenharmony_ci tea->freq = freq; 4338c2ecf20Sopenharmony_ci tea->val &= ~TEA575X_BIT_SEARCH; 4348c2ecf20Sopenharmony_ci return 0; 4358c2ecf20Sopenharmony_ci } 4368c2ecf20Sopenharmony_ci } 4378c2ecf20Sopenharmony_ci tea->val &= ~TEA575X_BIT_SEARCH; 4388c2ecf20Sopenharmony_ci snd_tea575x_set_freq(tea); 4398c2ecf20Sopenharmony_ci return -ENODATA; 4408c2ecf20Sopenharmony_ci} 4418c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_s_hw_freq_seek); 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_cistatic int vidioc_s_hw_freq_seek(struct file *file, void *fh, 4448c2ecf20Sopenharmony_ci const struct v4l2_hw_freq_seek *a) 4458c2ecf20Sopenharmony_ci{ 4468c2ecf20Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci return snd_tea575x_s_hw_freq_seek(file, tea, a); 4498c2ecf20Sopenharmony_ci} 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_cistatic int tea575x_s_ctrl(struct v4l2_ctrl *ctrl) 4528c2ecf20Sopenharmony_ci{ 4538c2ecf20Sopenharmony_ci struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler); 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci switch (ctrl->id) { 4568c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 4578c2ecf20Sopenharmony_ci tea->mute = ctrl->val; 4588c2ecf20Sopenharmony_ci snd_tea575x_set_freq(tea); 4598c2ecf20Sopenharmony_ci return 0; 4608c2ecf20Sopenharmony_ci } 4618c2ecf20Sopenharmony_ci 4628c2ecf20Sopenharmony_ci return -EINVAL; 4638c2ecf20Sopenharmony_ci} 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations tea575x_fops = { 4668c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 4678c2ecf20Sopenharmony_ci .open = v4l2_fh_open, 4688c2ecf20Sopenharmony_ci .release = v4l2_fh_release, 4698c2ecf20Sopenharmony_ci .poll = v4l2_ctrl_poll, 4708c2ecf20Sopenharmony_ci}; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops tea575x_ioctl_ops = { 4738c2ecf20Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 4748c2ecf20Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 4758c2ecf20Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 4768c2ecf20Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 4778c2ecf20Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 4788c2ecf20Sopenharmony_ci .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 4798c2ecf20Sopenharmony_ci .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 4808c2ecf20Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 4818c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 4828c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 4838c2ecf20Sopenharmony_ci}; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_cistatic const struct video_device tea575x_radio = { 4868c2ecf20Sopenharmony_ci .ioctl_ops = &tea575x_ioctl_ops, 4878c2ecf20Sopenharmony_ci .release = video_device_release_empty, 4888c2ecf20Sopenharmony_ci}; 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops tea575x_ctrl_ops = { 4918c2ecf20Sopenharmony_ci .s_ctrl = tea575x_s_ctrl, 4928c2ecf20Sopenharmony_ci}; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ciint snd_tea575x_hw_init(struct snd_tea575x *tea) 4968c2ecf20Sopenharmony_ci{ 4978c2ecf20Sopenharmony_ci tea->mute = true; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci /* Not all devices can or know how to read the data back. 5008c2ecf20Sopenharmony_ci Such devices can set cannot_read_data to true. */ 5018c2ecf20Sopenharmony_ci if (!tea->cannot_read_data) { 5028c2ecf20Sopenharmony_ci snd_tea575x_write(tea, 0x55AA); 5038c2ecf20Sopenharmony_ci if (snd_tea575x_read(tea) != 0x55AA) 5048c2ecf20Sopenharmony_ci return -ENODEV; 5058c2ecf20Sopenharmony_ci } 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28; 5088c2ecf20Sopenharmony_ci tea->freq = 90500 * 16; /* 90.5Mhz default */ 5098c2ecf20Sopenharmony_ci snd_tea575x_set_freq(tea); 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci return 0; 5128c2ecf20Sopenharmony_ci} 5138c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_hw_init); 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ciint snd_tea575x_init(struct snd_tea575x *tea, struct module *owner) 5168c2ecf20Sopenharmony_ci{ 5178c2ecf20Sopenharmony_ci int retval = snd_tea575x_hw_init(tea); 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci if (retval) 5208c2ecf20Sopenharmony_ci return retval; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci tea->vd = tea575x_radio; 5238c2ecf20Sopenharmony_ci video_set_drvdata(&tea->vd, tea); 5248c2ecf20Sopenharmony_ci mutex_init(&tea->mutex); 5258c2ecf20Sopenharmony_ci strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); 5268c2ecf20Sopenharmony_ci tea->vd.lock = &tea->mutex; 5278c2ecf20Sopenharmony_ci tea->vd.v4l2_dev = tea->v4l2_dev; 5288c2ecf20Sopenharmony_ci tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 5298c2ecf20Sopenharmony_ci if (!tea->cannot_read_data) 5308c2ecf20Sopenharmony_ci tea->vd.device_caps |= V4L2_CAP_HW_FREQ_SEEK; 5318c2ecf20Sopenharmony_ci tea->fops = tea575x_fops; 5328c2ecf20Sopenharmony_ci tea->fops.owner = owner; 5338c2ecf20Sopenharmony_ci tea->vd.fops = &tea->fops; 5348c2ecf20Sopenharmony_ci /* disable hw_freq_seek if we can't use it */ 5358c2ecf20Sopenharmony_ci if (tea->cannot_read_data) 5368c2ecf20Sopenharmony_ci v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK); 5378c2ecf20Sopenharmony_ci 5388c2ecf20Sopenharmony_ci if (!tea->cannot_mute) { 5398c2ecf20Sopenharmony_ci tea->vd.ctrl_handler = &tea->ctrl_handler; 5408c2ecf20Sopenharmony_ci v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); 5418c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, 5428c2ecf20Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 5438c2ecf20Sopenharmony_ci retval = tea->ctrl_handler.error; 5448c2ecf20Sopenharmony_ci if (retval) { 5458c2ecf20Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); 5468c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&tea->ctrl_handler); 5478c2ecf20Sopenharmony_ci return retval; 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci if (tea->ext_init) { 5518c2ecf20Sopenharmony_ci retval = tea->ext_init(tea); 5528c2ecf20Sopenharmony_ci if (retval) { 5538c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&tea->ctrl_handler); 5548c2ecf20Sopenharmony_ci return retval; 5558c2ecf20Sopenharmony_ci } 5568c2ecf20Sopenharmony_ci } 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci v4l2_ctrl_handler_setup(&tea->ctrl_handler); 5598c2ecf20Sopenharmony_ci } 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr); 5628c2ecf20Sopenharmony_ci if (retval) { 5638c2ecf20Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't register video device!\n"); 5648c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 5658c2ecf20Sopenharmony_ci return retval; 5668c2ecf20Sopenharmony_ci } 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci return 0; 5698c2ecf20Sopenharmony_ci} 5708c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_init); 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_civoid snd_tea575x_exit(struct snd_tea575x *tea) 5738c2ecf20Sopenharmony_ci{ 5748c2ecf20Sopenharmony_ci video_unregister_device(&tea->vd); 5758c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 5768c2ecf20Sopenharmony_ci} 5778c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_exit); 578