162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/delay.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/slab.h> 1262306a36Sopenharmony_ci#include <linux/sched.h> 1362306a36Sopenharmony_ci#include <asm/io.h> 1462306a36Sopenharmony_ci#include <media/v4l2-device.h> 1562306a36Sopenharmony_ci#include <media/v4l2-dev.h> 1662306a36Sopenharmony_ci#include <media/v4l2-fh.h> 1762306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 1862306a36Sopenharmony_ci#include <media/v4l2-event.h> 1962306a36Sopenharmony_ci#include <media/drv-intf/tea575x.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); 2262306a36Sopenharmony_ciMODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips"); 2362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * definitions 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */ 3062306a36Sopenharmony_ci#define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */ 3162306a36Sopenharmony_ci#define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */ 3262306a36Sopenharmony_ci#define TEA575X_BIT_BAND_MASK (3<<20) 3362306a36Sopenharmony_ci#define TEA575X_BIT_BAND_FM (0<<20) 3462306a36Sopenharmony_ci#define TEA575X_BIT_BAND_MW (1<<20) 3562306a36Sopenharmony_ci#define TEA575X_BIT_BAND_LW (2<<20) 3662306a36Sopenharmony_ci#define TEA575X_BIT_BAND_SW (3<<20) 3762306a36Sopenharmony_ci#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */ 3862306a36Sopenharmony_ci#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */ 3962306a36Sopenharmony_ci#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */ 4062306a36Sopenharmony_ci#define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */ 4162306a36Sopenharmony_ci#define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */ 4262306a36Sopenharmony_ci#define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */ 4362306a36Sopenharmony_ci#define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */ 4462306a36Sopenharmony_ci#define TEA575X_BIT_DUMMY (1<<15) /* buffer */ 4562306a36Sopenharmony_ci#define TEA575X_BIT_FREQ_MASK 0x7fff 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cienum { BAND_FM, BAND_FM_JAPAN, BAND_AM }; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = { 5062306a36Sopenharmony_ci { 5162306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 5262306a36Sopenharmony_ci .index = 0, 5362306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 5462306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS, 5562306a36Sopenharmony_ci .rangelow = 87500 * 16, 5662306a36Sopenharmony_ci .rangehigh = 108000 * 16, 5762306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 5862306a36Sopenharmony_ci }, 5962306a36Sopenharmony_ci { 6062306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 6162306a36Sopenharmony_ci .index = 0, 6262306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 6362306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS, 6462306a36Sopenharmony_ci .rangelow = 76000 * 16, 6562306a36Sopenharmony_ci .rangehigh = 91000 * 16, 6662306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 6762306a36Sopenharmony_ci }, 6862306a36Sopenharmony_ci { 6962306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 7062306a36Sopenharmony_ci .index = 1, 7162306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, 7262306a36Sopenharmony_ci .rangelow = 530 * 16, 7362306a36Sopenharmony_ci .rangehigh = 1710 * 16, 7462306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 7562306a36Sopenharmony_ci }, 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* 7962306a36Sopenharmony_ci * lowlevel part 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci u16 l; 8562306a36Sopenharmony_ci u8 data; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (tea->ops->write_val) 8862306a36Sopenharmony_ci return tea->ops->write_val(tea, val); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci tea->ops->set_direction(tea, 1); 9162306a36Sopenharmony_ci udelay(16); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci for (l = 25; l > 0; l--) { 9462306a36Sopenharmony_ci data = (val >> 24) & TEA575X_DATA; 9562306a36Sopenharmony_ci val <<= 1; /* shift data */ 9662306a36Sopenharmony_ci tea->ops->set_pins(tea, data | TEA575X_WREN); 9762306a36Sopenharmony_ci udelay(2); 9862306a36Sopenharmony_ci tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK); 9962306a36Sopenharmony_ci udelay(2); 10062306a36Sopenharmony_ci tea->ops->set_pins(tea, data | TEA575X_WREN); 10162306a36Sopenharmony_ci udelay(2); 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (!tea->mute) 10562306a36Sopenharmony_ci tea->ops->set_pins(tea, 0); 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic u32 snd_tea575x_read(struct snd_tea575x *tea) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci u16 l, rdata; 11162306a36Sopenharmony_ci u32 data = 0; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (tea->ops->read_val) 11462306a36Sopenharmony_ci return tea->ops->read_val(tea); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci tea->ops->set_direction(tea, 0); 11762306a36Sopenharmony_ci tea->ops->set_pins(tea, 0); 11862306a36Sopenharmony_ci udelay(16); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci for (l = 24; l--;) { 12162306a36Sopenharmony_ci tea->ops->set_pins(tea, TEA575X_CLK); 12262306a36Sopenharmony_ci udelay(2); 12362306a36Sopenharmony_ci if (!l) 12462306a36Sopenharmony_ci tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1; 12562306a36Sopenharmony_ci tea->ops->set_pins(tea, 0); 12662306a36Sopenharmony_ci udelay(2); 12762306a36Sopenharmony_ci data <<= 1; /* shift data */ 12862306a36Sopenharmony_ci rdata = tea->ops->get_pins(tea); 12962306a36Sopenharmony_ci if (!l) 13062306a36Sopenharmony_ci tea->stereo = (rdata & TEA575X_MOST) ? 0 : 1; 13162306a36Sopenharmony_ci if (rdata & TEA575X_DATA) 13262306a36Sopenharmony_ci data++; 13362306a36Sopenharmony_ci udelay(2); 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (tea->mute) 13762306a36Sopenharmony_ci tea->ops->set_pins(tea, TEA575X_WREN); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return data; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci u32 freq = val & TEA575X_BIT_FREQ_MASK; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci if (freq == 0) 14762306a36Sopenharmony_ci return freq; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci switch (tea->band) { 15062306a36Sopenharmony_ci case BAND_FM: 15162306a36Sopenharmony_ci /* freq *= 12.5 */ 15262306a36Sopenharmony_ci freq *= 125; 15362306a36Sopenharmony_ci freq /= 10; 15462306a36Sopenharmony_ci /* crystal fixup */ 15562306a36Sopenharmony_ci freq -= TEA575X_FMIF; 15662306a36Sopenharmony_ci break; 15762306a36Sopenharmony_ci case BAND_FM_JAPAN: 15862306a36Sopenharmony_ci /* freq *= 12.5 */ 15962306a36Sopenharmony_ci freq *= 125; 16062306a36Sopenharmony_ci freq /= 10; 16162306a36Sopenharmony_ci /* crystal fixup */ 16262306a36Sopenharmony_ci freq += TEA575X_FMIF; 16362306a36Sopenharmony_ci break; 16462306a36Sopenharmony_ci case BAND_AM: 16562306a36Sopenharmony_ci /* crystal fixup */ 16662306a36Sopenharmony_ci freq -= TEA575X_AMIF; 16762306a36Sopenharmony_ci break; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci return clamp(freq * 16, bands[tea->band].rangelow, 17162306a36Sopenharmony_ci bands[tea->band].rangehigh); /* from kHz */ 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic u32 snd_tea575x_get_freq(struct snd_tea575x *tea) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea)); 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_civoid snd_tea575x_set_freq(struct snd_tea575x *tea) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci u32 freq = tea->freq / 16; /* to kHz */ 18262306a36Sopenharmony_ci u32 band = 0; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci switch (tea->band) { 18562306a36Sopenharmony_ci case BAND_FM: 18662306a36Sopenharmony_ci band = TEA575X_BIT_BAND_FM; 18762306a36Sopenharmony_ci /* crystal fixup */ 18862306a36Sopenharmony_ci freq += TEA575X_FMIF; 18962306a36Sopenharmony_ci /* freq /= 12.5 */ 19062306a36Sopenharmony_ci freq *= 10; 19162306a36Sopenharmony_ci freq /= 125; 19262306a36Sopenharmony_ci break; 19362306a36Sopenharmony_ci case BAND_FM_JAPAN: 19462306a36Sopenharmony_ci band = TEA575X_BIT_BAND_FM; 19562306a36Sopenharmony_ci /* crystal fixup */ 19662306a36Sopenharmony_ci freq -= TEA575X_FMIF; 19762306a36Sopenharmony_ci /* freq /= 12.5 */ 19862306a36Sopenharmony_ci freq *= 10; 19962306a36Sopenharmony_ci freq /= 125; 20062306a36Sopenharmony_ci break; 20162306a36Sopenharmony_ci case BAND_AM: 20262306a36Sopenharmony_ci band = TEA575X_BIT_BAND_MW; 20362306a36Sopenharmony_ci /* crystal fixup */ 20462306a36Sopenharmony_ci freq += TEA575X_AMIF; 20562306a36Sopenharmony_ci break; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK); 20962306a36Sopenharmony_ci tea->val |= band; 21062306a36Sopenharmony_ci tea->val |= freq & TEA575X_BIT_FREQ_MASK; 21162306a36Sopenharmony_ci snd_tea575x_write(tea, tea->val); 21262306a36Sopenharmony_ci tea->freq = snd_tea575x_val_to_freq(tea, tea->val); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_set_freq); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci/* 21762306a36Sopenharmony_ci * Linux Video interface 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 22162306a36Sopenharmony_ci struct v4l2_capability *v) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); 22662306a36Sopenharmony_ci strscpy(v->card, tea->card, sizeof(v->card)); 22762306a36Sopenharmony_ci strlcat(v->card, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card)); 22862306a36Sopenharmony_ci strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ciint snd_tea575x_enum_freq_bands(struct snd_tea575x *tea, 23362306a36Sopenharmony_ci struct v4l2_frequency_band *band) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci int index; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (band->tuner != 0) 23862306a36Sopenharmony_ci return -EINVAL; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci switch (band->index) { 24162306a36Sopenharmony_ci case 0: 24262306a36Sopenharmony_ci if (tea->tea5759) 24362306a36Sopenharmony_ci index = BAND_FM_JAPAN; 24462306a36Sopenharmony_ci else 24562306a36Sopenharmony_ci index = BAND_FM; 24662306a36Sopenharmony_ci break; 24762306a36Sopenharmony_ci case 1: 24862306a36Sopenharmony_ci if (tea->has_am) { 24962306a36Sopenharmony_ci index = BAND_AM; 25062306a36Sopenharmony_ci break; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci fallthrough; 25362306a36Sopenharmony_ci default: 25462306a36Sopenharmony_ci return -EINVAL; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci *band = bands[index]; 25862306a36Sopenharmony_ci if (!tea->cannot_read_data) 25962306a36Sopenharmony_ci band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci return 0; 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_enum_freq_bands); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic int vidioc_enum_freq_bands(struct file *file, void *priv, 26662306a36Sopenharmony_ci struct v4l2_frequency_band *band) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci return snd_tea575x_enum_freq_bands(tea, band); 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ciint snd_tea575x_g_tuner(struct snd_tea575x *tea, struct v4l2_tuner *v) 27462306a36Sopenharmony_ci{ 27562306a36Sopenharmony_ci struct v4l2_frequency_band band_fm = { 0, }; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (v->index > 0) 27862306a36Sopenharmony_ci return -EINVAL; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci snd_tea575x_read(tea); 28162306a36Sopenharmony_ci snd_tea575x_enum_freq_bands(tea, &band_fm); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci memset(v, 0, sizeof(*v)); 28462306a36Sopenharmony_ci strscpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name)); 28562306a36Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 28662306a36Sopenharmony_ci v->capability = band_fm.capability; 28762306a36Sopenharmony_ci v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow; 28862306a36Sopenharmony_ci v->rangehigh = band_fm.rangehigh; 28962306a36Sopenharmony_ci v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 29062306a36Sopenharmony_ci v->audmode = (tea->val & TEA575X_BIT_MONO) ? 29162306a36Sopenharmony_ci V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; 29262306a36Sopenharmony_ci v->signal = tea->tuned ? 0xffff : 0; 29362306a36Sopenharmony_ci return 0; 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_g_tuner); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 29862306a36Sopenharmony_ci struct v4l2_tuner *v) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci return snd_tea575x_g_tuner(tea, v); 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 30662306a36Sopenharmony_ci const struct v4l2_tuner *v) 30762306a36Sopenharmony_ci{ 30862306a36Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 30962306a36Sopenharmony_ci u32 orig_val = tea->val; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci if (v->index) 31262306a36Sopenharmony_ci return -EINVAL; 31362306a36Sopenharmony_ci tea->val &= ~TEA575X_BIT_MONO; 31462306a36Sopenharmony_ci if (v->audmode == V4L2_TUNER_MODE_MONO) 31562306a36Sopenharmony_ci tea->val |= TEA575X_BIT_MONO; 31662306a36Sopenharmony_ci /* Only apply changes if currently tuning FM */ 31762306a36Sopenharmony_ci if (tea->band != BAND_AM && tea->val != orig_val) 31862306a36Sopenharmony_ci snd_tea575x_set_freq(tea); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci return 0; 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 32462306a36Sopenharmony_ci struct v4l2_frequency *f) 32562306a36Sopenharmony_ci{ 32662306a36Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci if (f->tuner != 0) 32962306a36Sopenharmony_ci return -EINVAL; 33062306a36Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 33162306a36Sopenharmony_ci f->frequency = tea->freq; 33262306a36Sopenharmony_ci return 0; 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 33662306a36Sopenharmony_ci const struct v4l2_frequency *f) 33762306a36Sopenharmony_ci{ 33862306a36Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 34162306a36Sopenharmony_ci return -EINVAL; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (tea->has_am && f->frequency < (20000 * 16)) 34462306a36Sopenharmony_ci tea->band = BAND_AM; 34562306a36Sopenharmony_ci else if (tea->tea5759) 34662306a36Sopenharmony_ci tea->band = BAND_FM_JAPAN; 34762306a36Sopenharmony_ci else 34862306a36Sopenharmony_ci tea->band = BAND_FM; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow, 35162306a36Sopenharmony_ci bands[tea->band].rangehigh); 35262306a36Sopenharmony_ci snd_tea575x_set_freq(tea); 35362306a36Sopenharmony_ci return 0; 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ciint snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea, 35762306a36Sopenharmony_ci const struct v4l2_hw_freq_seek *a) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci unsigned long timeout; 36062306a36Sopenharmony_ci int i, spacing; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci if (tea->cannot_read_data) 36362306a36Sopenharmony_ci return -ENOTTY; 36462306a36Sopenharmony_ci if (a->tuner || a->wrap_around) 36562306a36Sopenharmony_ci return -EINVAL; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 36862306a36Sopenharmony_ci return -EWOULDBLOCK; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci if (a->rangelow || a->rangehigh) { 37162306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(bands); i++) { 37262306a36Sopenharmony_ci if ((i == BAND_FM && tea->tea5759) || 37362306a36Sopenharmony_ci (i == BAND_FM_JAPAN && !tea->tea5759) || 37462306a36Sopenharmony_ci (i == BAND_AM && !tea->has_am)) 37562306a36Sopenharmony_ci continue; 37662306a36Sopenharmony_ci if (bands[i].rangelow == a->rangelow && 37762306a36Sopenharmony_ci bands[i].rangehigh == a->rangehigh) 37862306a36Sopenharmony_ci break; 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci if (i == ARRAY_SIZE(bands)) 38162306a36Sopenharmony_ci return -EINVAL; /* No matching band found */ 38262306a36Sopenharmony_ci if (i != tea->band) { 38362306a36Sopenharmony_ci tea->band = i; 38462306a36Sopenharmony_ci tea->freq = clamp(tea->freq, bands[i].rangelow, 38562306a36Sopenharmony_ci bands[i].rangehigh); 38662306a36Sopenharmony_ci snd_tea575x_set_freq(tea); 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */ 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* clear the frequency, HW will fill it in */ 39362306a36Sopenharmony_ci tea->val &= ~TEA575X_BIT_FREQ_MASK; 39462306a36Sopenharmony_ci tea->val |= TEA575X_BIT_SEARCH; 39562306a36Sopenharmony_ci if (a->seek_upward) 39662306a36Sopenharmony_ci tea->val |= TEA575X_BIT_UPDOWN; 39762306a36Sopenharmony_ci else 39862306a36Sopenharmony_ci tea->val &= ~TEA575X_BIT_UPDOWN; 39962306a36Sopenharmony_ci snd_tea575x_write(tea, tea->val); 40062306a36Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(10000); 40162306a36Sopenharmony_ci for (;;) { 40262306a36Sopenharmony_ci if (time_after(jiffies, timeout)) 40362306a36Sopenharmony_ci break; 40462306a36Sopenharmony_ci if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { 40562306a36Sopenharmony_ci /* some signal arrived, stop search */ 40662306a36Sopenharmony_ci tea->val &= ~TEA575X_BIT_SEARCH; 40762306a36Sopenharmony_ci snd_tea575x_set_freq(tea); 40862306a36Sopenharmony_ci return -ERESTARTSYS; 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) { 41162306a36Sopenharmony_ci u32 freq; 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci /* Found a frequency, wait until it can be read */ 41462306a36Sopenharmony_ci for (i = 0; i < 100; i++) { 41562306a36Sopenharmony_ci msleep(10); 41662306a36Sopenharmony_ci freq = snd_tea575x_get_freq(tea); 41762306a36Sopenharmony_ci if (freq) /* available */ 41862306a36Sopenharmony_ci break; 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci if (freq == 0) /* shouldn't happen */ 42162306a36Sopenharmony_ci break; 42262306a36Sopenharmony_ci /* 42362306a36Sopenharmony_ci * if we moved by less than the spacing, or in the 42462306a36Sopenharmony_ci * wrong direction, continue seeking 42562306a36Sopenharmony_ci */ 42662306a36Sopenharmony_ci if (abs(tea->freq - freq) < 16 * spacing || 42762306a36Sopenharmony_ci (a->seek_upward && freq < tea->freq) || 42862306a36Sopenharmony_ci (!a->seek_upward && freq > tea->freq)) { 42962306a36Sopenharmony_ci snd_tea575x_write(tea, tea->val); 43062306a36Sopenharmony_ci continue; 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci tea->freq = freq; 43362306a36Sopenharmony_ci tea->val &= ~TEA575X_BIT_SEARCH; 43462306a36Sopenharmony_ci return 0; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci tea->val &= ~TEA575X_BIT_SEARCH; 43862306a36Sopenharmony_ci snd_tea575x_set_freq(tea); 43962306a36Sopenharmony_ci return -ENODATA; 44062306a36Sopenharmony_ci} 44162306a36Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_s_hw_freq_seek); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_cistatic int vidioc_s_hw_freq_seek(struct file *file, void *fh, 44462306a36Sopenharmony_ci const struct v4l2_hw_freq_seek *a) 44562306a36Sopenharmony_ci{ 44662306a36Sopenharmony_ci struct snd_tea575x *tea = video_drvdata(file); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci return snd_tea575x_s_hw_freq_seek(file, tea, a); 44962306a36Sopenharmony_ci} 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_cistatic int tea575x_s_ctrl(struct v4l2_ctrl *ctrl) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci switch (ctrl->id) { 45662306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 45762306a36Sopenharmony_ci tea->mute = ctrl->val; 45862306a36Sopenharmony_ci snd_tea575x_set_freq(tea); 45962306a36Sopenharmony_ci return 0; 46062306a36Sopenharmony_ci } 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci return -EINVAL; 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_cistatic const struct v4l2_file_operations tea575x_fops = { 46662306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 46762306a36Sopenharmony_ci .open = v4l2_fh_open, 46862306a36Sopenharmony_ci .release = v4l2_fh_release, 46962306a36Sopenharmony_ci .poll = v4l2_ctrl_poll, 47062306a36Sopenharmony_ci}; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops tea575x_ioctl_ops = { 47362306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 47462306a36Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 47562306a36Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 47662306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 47762306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 47862306a36Sopenharmony_ci .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 47962306a36Sopenharmony_ci .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 48062306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 48162306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 48262306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 48362306a36Sopenharmony_ci}; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic const struct video_device tea575x_radio = { 48662306a36Sopenharmony_ci .ioctl_ops = &tea575x_ioctl_ops, 48762306a36Sopenharmony_ci .release = video_device_release_empty, 48862306a36Sopenharmony_ci}; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops tea575x_ctrl_ops = { 49162306a36Sopenharmony_ci .s_ctrl = tea575x_s_ctrl, 49262306a36Sopenharmony_ci}; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ciint snd_tea575x_hw_init(struct snd_tea575x *tea) 49662306a36Sopenharmony_ci{ 49762306a36Sopenharmony_ci tea->mute = true; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci /* Not all devices can or know how to read the data back. 50062306a36Sopenharmony_ci Such devices can set cannot_read_data to true. */ 50162306a36Sopenharmony_ci if (!tea->cannot_read_data) { 50262306a36Sopenharmony_ci snd_tea575x_write(tea, 0x55AA); 50362306a36Sopenharmony_ci if (snd_tea575x_read(tea) != 0x55AA) 50462306a36Sopenharmony_ci return -ENODEV; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28; 50862306a36Sopenharmony_ci tea->freq = 90500 * 16; /* 90.5Mhz default */ 50962306a36Sopenharmony_ci snd_tea575x_set_freq(tea); 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci return 0; 51262306a36Sopenharmony_ci} 51362306a36Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_hw_init); 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ciint snd_tea575x_init(struct snd_tea575x *tea, struct module *owner) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci int retval = snd_tea575x_hw_init(tea); 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci if (retval) 52062306a36Sopenharmony_ci return retval; 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci tea->vd = tea575x_radio; 52362306a36Sopenharmony_ci video_set_drvdata(&tea->vd, tea); 52462306a36Sopenharmony_ci mutex_init(&tea->mutex); 52562306a36Sopenharmony_ci strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); 52662306a36Sopenharmony_ci tea->vd.lock = &tea->mutex; 52762306a36Sopenharmony_ci tea->vd.v4l2_dev = tea->v4l2_dev; 52862306a36Sopenharmony_ci tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 52962306a36Sopenharmony_ci if (!tea->cannot_read_data) 53062306a36Sopenharmony_ci tea->vd.device_caps |= V4L2_CAP_HW_FREQ_SEEK; 53162306a36Sopenharmony_ci tea->fops = tea575x_fops; 53262306a36Sopenharmony_ci tea->fops.owner = owner; 53362306a36Sopenharmony_ci tea->vd.fops = &tea->fops; 53462306a36Sopenharmony_ci /* disable hw_freq_seek if we can't use it */ 53562306a36Sopenharmony_ci if (tea->cannot_read_data) 53662306a36Sopenharmony_ci v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK); 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci if (!tea->cannot_mute) { 53962306a36Sopenharmony_ci tea->vd.ctrl_handler = &tea->ctrl_handler; 54062306a36Sopenharmony_ci v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); 54162306a36Sopenharmony_ci v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, 54262306a36Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 54362306a36Sopenharmony_ci retval = tea->ctrl_handler.error; 54462306a36Sopenharmony_ci if (retval) { 54562306a36Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); 54662306a36Sopenharmony_ci v4l2_ctrl_handler_free(&tea->ctrl_handler); 54762306a36Sopenharmony_ci return retval; 54862306a36Sopenharmony_ci } 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci if (tea->ext_init) { 55162306a36Sopenharmony_ci retval = tea->ext_init(tea); 55262306a36Sopenharmony_ci if (retval) { 55362306a36Sopenharmony_ci v4l2_ctrl_handler_free(&tea->ctrl_handler); 55462306a36Sopenharmony_ci return retval; 55562306a36Sopenharmony_ci } 55662306a36Sopenharmony_ci } 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci v4l2_ctrl_handler_setup(&tea->ctrl_handler); 55962306a36Sopenharmony_ci } 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr); 56262306a36Sopenharmony_ci if (retval) { 56362306a36Sopenharmony_ci v4l2_err(tea->v4l2_dev, "can't register video device!\n"); 56462306a36Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 56562306a36Sopenharmony_ci return retval; 56662306a36Sopenharmony_ci } 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci return 0; 56962306a36Sopenharmony_ci} 57062306a36Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_init); 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_civoid snd_tea575x_exit(struct snd_tea575x *tea) 57362306a36Sopenharmony_ci{ 57462306a36Sopenharmony_ci video_unregister_device(&tea->vd); 57562306a36Sopenharmony_ci v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 57662306a36Sopenharmony_ci} 57762306a36Sopenharmony_ciEXPORT_SYMBOL(snd_tea575x_exit); 578