1338c658aSOndrej Zary /* 2338c658aSOndrej Zary * ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips 3338c658aSOndrej Zary * 4338c658aSOndrej Zary * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> 5338c658aSOndrej Zary * 6338c658aSOndrej Zary * 7338c658aSOndrej Zary * This program is free software; you can redistribute it and/or modify 8338c658aSOndrej Zary * it under the terms of the GNU General Public License as published by 9338c658aSOndrej Zary * the Free Software Foundation; either version 2 of the License, or 10338c658aSOndrej Zary * (at your option) any later version. 11338c658aSOndrej Zary * 12338c658aSOndrej Zary * This program is distributed in the hope that it will be useful, 13338c658aSOndrej Zary * but WITHOUT ANY WARRANTY; without even the implied warranty of 14338c658aSOndrej Zary * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15338c658aSOndrej Zary * GNU General Public License for more details. 16338c658aSOndrej Zary * 17338c658aSOndrej Zary */ 18338c658aSOndrej Zary 19338c658aSOndrej Zary #include <linux/delay.h> 20338c658aSOndrej Zary #include <linux/module.h> 21338c658aSOndrej Zary #include <linux/init.h> 22338c658aSOndrej Zary #include <linux/slab.h> 23338c658aSOndrej Zary #include <linux/sched.h> 24eab924d0SMauro Carvalho Chehab #include <asm/io.h> 25338c658aSOndrej Zary #include <media/v4l2-device.h> 26338c658aSOndrej Zary #include <media/v4l2-dev.h> 27338c658aSOndrej Zary #include <media/v4l2-fh.h> 28338c658aSOndrej Zary #include <media/v4l2-ioctl.h> 29338c658aSOndrej Zary #include <media/v4l2-event.h> 30d647f0b7SMauro Carvalho Chehab #include <media/drv-intf/tea575x.h> 31338c658aSOndrej Zary 32338c658aSOndrej Zary MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); 33338c658aSOndrej Zary MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips"); 34338c658aSOndrej Zary MODULE_LICENSE("GPL"); 35338c658aSOndrej Zary 36338c658aSOndrej Zary /* 37338c658aSOndrej Zary * definitions 38338c658aSOndrej Zary */ 39338c658aSOndrej Zary 40338c658aSOndrej Zary #define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */ 41338c658aSOndrej Zary #define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */ 42338c658aSOndrej Zary #define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */ 43338c658aSOndrej Zary #define TEA575X_BIT_BAND_MASK (3<<20) 44338c658aSOndrej Zary #define TEA575X_BIT_BAND_FM (0<<20) 45338c658aSOndrej Zary #define TEA575X_BIT_BAND_MW (1<<20) 46338c658aSOndrej Zary #define TEA575X_BIT_BAND_LW (2<<20) 47338c658aSOndrej Zary #define TEA575X_BIT_BAND_SW (3<<20) 48338c658aSOndrej Zary #define TEA575X_BIT_PORT_0 (1<<19) /* user bit */ 49338c658aSOndrej Zary #define TEA575X_BIT_PORT_1 (1<<18) /* user bit */ 50338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */ 51338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */ 52338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */ 53338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */ 54338c658aSOndrej Zary #define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */ 55338c658aSOndrej Zary #define TEA575X_BIT_DUMMY (1<<15) /* buffer */ 56338c658aSOndrej Zary #define TEA575X_BIT_FREQ_MASK 0x7fff 57338c658aSOndrej Zary 58338c658aSOndrej Zary enum { BAND_FM, BAND_FM_JAPAN, BAND_AM }; 59338c658aSOndrej Zary 60338c658aSOndrej Zary static const struct v4l2_frequency_band bands[] = { 61338c658aSOndrej Zary { 62338c658aSOndrej Zary .type = V4L2_TUNER_RADIO, 63338c658aSOndrej Zary .index = 0, 64338c658aSOndrej Zary .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 65338c658aSOndrej Zary V4L2_TUNER_CAP_FREQ_BANDS, 66338c658aSOndrej Zary .rangelow = 87500 * 16, 67338c658aSOndrej Zary .rangehigh = 108000 * 16, 68338c658aSOndrej Zary .modulation = V4L2_BAND_MODULATION_FM, 69338c658aSOndrej Zary }, 70338c658aSOndrej Zary { 71338c658aSOndrej Zary .type = V4L2_TUNER_RADIO, 72338c658aSOndrej Zary .index = 0, 73338c658aSOndrej Zary .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 74338c658aSOndrej Zary V4L2_TUNER_CAP_FREQ_BANDS, 75338c658aSOndrej Zary .rangelow = 76000 * 16, 76338c658aSOndrej Zary .rangehigh = 91000 * 16, 77338c658aSOndrej Zary .modulation = V4L2_BAND_MODULATION_FM, 78338c658aSOndrej Zary }, 79338c658aSOndrej Zary { 80338c658aSOndrej Zary .type = V4L2_TUNER_RADIO, 81338c658aSOndrej Zary .index = 1, 82338c658aSOndrej Zary .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, 83338c658aSOndrej Zary .rangelow = 530 * 16, 84338c658aSOndrej Zary .rangehigh = 1710 * 16, 85338c658aSOndrej Zary .modulation = V4L2_BAND_MODULATION_AM, 86338c658aSOndrej Zary }, 87338c658aSOndrej Zary }; 88338c658aSOndrej Zary 89338c658aSOndrej Zary /* 90338c658aSOndrej Zary * lowlevel part 91338c658aSOndrej Zary */ 92338c658aSOndrej Zary 93338c658aSOndrej Zary static void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val) 94338c658aSOndrej Zary { 95338c658aSOndrej Zary u16 l; 96338c658aSOndrej Zary u8 data; 97338c658aSOndrej Zary 98338c658aSOndrej Zary if (tea->ops->write_val) 99338c658aSOndrej Zary return tea->ops->write_val(tea, val); 100338c658aSOndrej Zary 101338c658aSOndrej Zary tea->ops->set_direction(tea, 1); 102338c658aSOndrej Zary udelay(16); 103338c658aSOndrej Zary 104338c658aSOndrej Zary for (l = 25; l > 0; l--) { 105338c658aSOndrej Zary data = (val >> 24) & TEA575X_DATA; 106338c658aSOndrej Zary val <<= 1; /* shift data */ 107338c658aSOndrej Zary tea->ops->set_pins(tea, data | TEA575X_WREN); 108338c658aSOndrej Zary udelay(2); 109338c658aSOndrej Zary tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK); 110338c658aSOndrej Zary udelay(2); 111338c658aSOndrej Zary tea->ops->set_pins(tea, data | TEA575X_WREN); 112338c658aSOndrej Zary udelay(2); 113338c658aSOndrej Zary } 114338c658aSOndrej Zary 115338c658aSOndrej Zary if (!tea->mute) 116338c658aSOndrej Zary tea->ops->set_pins(tea, 0); 117338c658aSOndrej Zary } 118338c658aSOndrej Zary 119338c658aSOndrej Zary static u32 snd_tea575x_read(struct snd_tea575x *tea) 120338c658aSOndrej Zary { 121338c658aSOndrej Zary u16 l, rdata; 122338c658aSOndrej Zary u32 data = 0; 123338c658aSOndrej Zary 124338c658aSOndrej Zary if (tea->ops->read_val) 125338c658aSOndrej Zary return tea->ops->read_val(tea); 126338c658aSOndrej Zary 127338c658aSOndrej Zary tea->ops->set_direction(tea, 0); 128338c658aSOndrej Zary tea->ops->set_pins(tea, 0); 129338c658aSOndrej Zary udelay(16); 130338c658aSOndrej Zary 131338c658aSOndrej Zary for (l = 24; l--;) { 132338c658aSOndrej Zary tea->ops->set_pins(tea, TEA575X_CLK); 133338c658aSOndrej Zary udelay(2); 134338c658aSOndrej Zary if (!l) 135338c658aSOndrej Zary tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1; 136338c658aSOndrej Zary tea->ops->set_pins(tea, 0); 137338c658aSOndrej Zary udelay(2); 138338c658aSOndrej Zary data <<= 1; /* shift data */ 139338c658aSOndrej Zary rdata = tea->ops->get_pins(tea); 140338c658aSOndrej Zary if (!l) 141338c658aSOndrej Zary tea->stereo = (rdata & TEA575X_MOST) ? 0 : 1; 142338c658aSOndrej Zary if (rdata & TEA575X_DATA) 143338c658aSOndrej Zary data++; 144338c658aSOndrej Zary udelay(2); 145338c658aSOndrej Zary } 146338c658aSOndrej Zary 147338c658aSOndrej Zary if (tea->mute) 148338c658aSOndrej Zary tea->ops->set_pins(tea, TEA575X_WREN); 149338c658aSOndrej Zary 150338c658aSOndrej Zary return data; 151338c658aSOndrej Zary } 152338c658aSOndrej Zary 153338c658aSOndrej Zary static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val) 154338c658aSOndrej Zary { 155338c658aSOndrej Zary u32 freq = val & TEA575X_BIT_FREQ_MASK; 156338c658aSOndrej Zary 157338c658aSOndrej Zary if (freq == 0) 158338c658aSOndrej Zary return freq; 159338c658aSOndrej Zary 160338c658aSOndrej Zary switch (tea->band) { 161338c658aSOndrej Zary case BAND_FM: 162338c658aSOndrej Zary /* freq *= 12.5 */ 163338c658aSOndrej Zary freq *= 125; 164338c658aSOndrej Zary freq /= 10; 165338c658aSOndrej Zary /* crystal fixup */ 166338c658aSOndrej Zary freq -= TEA575X_FMIF; 167338c658aSOndrej Zary break; 168338c658aSOndrej Zary case BAND_FM_JAPAN: 169338c658aSOndrej Zary /* freq *= 12.5 */ 170338c658aSOndrej Zary freq *= 125; 171338c658aSOndrej Zary freq /= 10; 172338c658aSOndrej Zary /* crystal fixup */ 173338c658aSOndrej Zary freq += TEA575X_FMIF; 174338c658aSOndrej Zary break; 175338c658aSOndrej Zary case BAND_AM: 176338c658aSOndrej Zary /* crystal fixup */ 177338c658aSOndrej Zary freq -= TEA575X_AMIF; 178338c658aSOndrej Zary break; 179338c658aSOndrej Zary } 180338c658aSOndrej Zary 181338c658aSOndrej Zary return clamp(freq * 16, bands[tea->band].rangelow, 182338c658aSOndrej Zary bands[tea->band].rangehigh); /* from kHz */ 183338c658aSOndrej Zary } 184338c658aSOndrej Zary 185338c658aSOndrej Zary static u32 snd_tea575x_get_freq(struct snd_tea575x *tea) 186338c658aSOndrej Zary { 187338c658aSOndrej Zary return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea)); 188338c658aSOndrej Zary } 189338c658aSOndrej Zary 190338c658aSOndrej Zary void snd_tea575x_set_freq(struct snd_tea575x *tea) 191338c658aSOndrej Zary { 192338c658aSOndrej Zary u32 freq = tea->freq / 16; /* to kHz */ 193338c658aSOndrej Zary u32 band = 0; 194338c658aSOndrej Zary 195338c658aSOndrej Zary switch (tea->band) { 196338c658aSOndrej Zary case BAND_FM: 197338c658aSOndrej Zary band = TEA575X_BIT_BAND_FM; 198338c658aSOndrej Zary /* crystal fixup */ 199338c658aSOndrej Zary freq += TEA575X_FMIF; 200338c658aSOndrej Zary /* freq /= 12.5 */ 201338c658aSOndrej Zary freq *= 10; 202338c658aSOndrej Zary freq /= 125; 203338c658aSOndrej Zary break; 204338c658aSOndrej Zary case BAND_FM_JAPAN: 205338c658aSOndrej Zary band = TEA575X_BIT_BAND_FM; 206338c658aSOndrej Zary /* crystal fixup */ 207338c658aSOndrej Zary freq -= TEA575X_FMIF; 208338c658aSOndrej Zary /* freq /= 12.5 */ 209338c658aSOndrej Zary freq *= 10; 210338c658aSOndrej Zary freq /= 125; 211338c658aSOndrej Zary break; 212338c658aSOndrej Zary case BAND_AM: 213338c658aSOndrej Zary band = TEA575X_BIT_BAND_MW; 214338c658aSOndrej Zary /* crystal fixup */ 215338c658aSOndrej Zary freq += TEA575X_AMIF; 216338c658aSOndrej Zary break; 217338c658aSOndrej Zary } 218338c658aSOndrej Zary 219338c658aSOndrej Zary tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK); 220338c658aSOndrej Zary tea->val |= band; 221338c658aSOndrej Zary tea->val |= freq & TEA575X_BIT_FREQ_MASK; 222338c658aSOndrej Zary snd_tea575x_write(tea, tea->val); 223338c658aSOndrej Zary tea->freq = snd_tea575x_val_to_freq(tea, tea->val); 224338c658aSOndrej Zary } 2250e2a706bSAndy Shevchenko EXPORT_SYMBOL(snd_tea575x_set_freq); 226338c658aSOndrej Zary 227338c658aSOndrej Zary /* 228338c658aSOndrej Zary * Linux Video interface 229338c658aSOndrej Zary */ 230338c658aSOndrej Zary 231338c658aSOndrej Zary static int vidioc_querycap(struct file *file, void *priv, 232338c658aSOndrej Zary struct v4l2_capability *v) 233338c658aSOndrej Zary { 234338c658aSOndrej Zary struct snd_tea575x *tea = video_drvdata(file); 235338c658aSOndrej Zary 236c0decac1SMauro Carvalho Chehab strscpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); 237c0decac1SMauro Carvalho Chehab strscpy(v->card, tea->card, sizeof(v->card)); 238338c658aSOndrej Zary strlcat(v->card, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card)); 239c0decac1SMauro Carvalho Chehab strscpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); 240338c658aSOndrej Zary return 0; 241338c658aSOndrej Zary } 242338c658aSOndrej Zary 2436994ca3dSOndrej Zary int snd_tea575x_enum_freq_bands(struct snd_tea575x *tea, 244338c658aSOndrej Zary struct v4l2_frequency_band *band) 245338c658aSOndrej Zary { 246338c658aSOndrej Zary int index; 247338c658aSOndrej Zary 248338c658aSOndrej Zary if (band->tuner != 0) 249338c658aSOndrej Zary return -EINVAL; 250338c658aSOndrej Zary 251338c658aSOndrej Zary switch (band->index) { 252338c658aSOndrej Zary case 0: 253338c658aSOndrej Zary if (tea->tea5759) 254338c658aSOndrej Zary index = BAND_FM_JAPAN; 255338c658aSOndrej Zary else 256338c658aSOndrej Zary index = BAND_FM; 257338c658aSOndrej Zary break; 258338c658aSOndrej Zary case 1: 259338c658aSOndrej Zary if (tea->has_am) { 260338c658aSOndrej Zary index = BAND_AM; 261338c658aSOndrej Zary break; 262338c658aSOndrej Zary } 263338c658aSOndrej Zary /* Fall through */ 264338c658aSOndrej Zary default: 265338c658aSOndrej Zary return -EINVAL; 266338c658aSOndrej Zary } 267338c658aSOndrej Zary 268338c658aSOndrej Zary *band = bands[index]; 269338c658aSOndrej Zary if (!tea->cannot_read_data) 270338c658aSOndrej Zary band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED; 271338c658aSOndrej Zary 272338c658aSOndrej Zary return 0; 273338c658aSOndrej Zary } 2746994ca3dSOndrej Zary EXPORT_SYMBOL(snd_tea575x_enum_freq_bands); 275338c658aSOndrej Zary 2766994ca3dSOndrej Zary static int vidioc_enum_freq_bands(struct file *file, void *priv, 2776994ca3dSOndrej Zary struct v4l2_frequency_band *band) 278338c658aSOndrej Zary { 279338c658aSOndrej Zary struct snd_tea575x *tea = video_drvdata(file); 2806994ca3dSOndrej Zary 2816994ca3dSOndrej Zary return snd_tea575x_enum_freq_bands(tea, band); 2826994ca3dSOndrej Zary } 2836994ca3dSOndrej Zary 2846994ca3dSOndrej Zary int snd_tea575x_g_tuner(struct snd_tea575x *tea, struct v4l2_tuner *v) 2856994ca3dSOndrej Zary { 286338c658aSOndrej Zary struct v4l2_frequency_band band_fm = { 0, }; 287338c658aSOndrej Zary 288338c658aSOndrej Zary if (v->index > 0) 289338c658aSOndrej Zary return -EINVAL; 290338c658aSOndrej Zary 291338c658aSOndrej Zary snd_tea575x_read(tea); 2926994ca3dSOndrej Zary snd_tea575x_enum_freq_bands(tea, &band_fm); 293338c658aSOndrej Zary 294338c658aSOndrej Zary memset(v, 0, sizeof(*v)); 295c0decac1SMauro Carvalho Chehab strscpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name)); 296338c658aSOndrej Zary v->type = V4L2_TUNER_RADIO; 297338c658aSOndrej Zary v->capability = band_fm.capability; 298338c658aSOndrej Zary v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow; 299338c658aSOndrej Zary v->rangehigh = band_fm.rangehigh; 300338c658aSOndrej Zary v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 301338c658aSOndrej Zary v->audmode = (tea->val & TEA575X_BIT_MONO) ? 302338c658aSOndrej Zary V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; 303338c658aSOndrej Zary v->signal = tea->tuned ? 0xffff : 0; 304338c658aSOndrej Zary return 0; 305338c658aSOndrej Zary } 3066994ca3dSOndrej Zary EXPORT_SYMBOL(snd_tea575x_g_tuner); 3076994ca3dSOndrej Zary 3086994ca3dSOndrej Zary static int vidioc_g_tuner(struct file *file, void *priv, 3096994ca3dSOndrej Zary struct v4l2_tuner *v) 3106994ca3dSOndrej Zary { 3116994ca3dSOndrej Zary struct snd_tea575x *tea = video_drvdata(file); 3126994ca3dSOndrej Zary 3136994ca3dSOndrej Zary return snd_tea575x_g_tuner(tea, v); 3146994ca3dSOndrej Zary } 315338c658aSOndrej Zary 316338c658aSOndrej Zary static int vidioc_s_tuner(struct file *file, void *priv, 317338c658aSOndrej Zary const struct v4l2_tuner *v) 318338c658aSOndrej Zary { 319338c658aSOndrej Zary struct snd_tea575x *tea = video_drvdata(file); 320338c658aSOndrej Zary u32 orig_val = tea->val; 321338c658aSOndrej Zary 322338c658aSOndrej Zary if (v->index) 323338c658aSOndrej Zary return -EINVAL; 324338c658aSOndrej Zary tea->val &= ~TEA575X_BIT_MONO; 325338c658aSOndrej Zary if (v->audmode == V4L2_TUNER_MODE_MONO) 326338c658aSOndrej Zary tea->val |= TEA575X_BIT_MONO; 327338c658aSOndrej Zary /* Only apply changes if currently tuning FM */ 328338c658aSOndrej Zary if (tea->band != BAND_AM && tea->val != orig_val) 329338c658aSOndrej Zary snd_tea575x_set_freq(tea); 330338c658aSOndrej Zary 331338c658aSOndrej Zary return 0; 332338c658aSOndrej Zary } 333338c658aSOndrej Zary 334338c658aSOndrej Zary static int vidioc_g_frequency(struct file *file, void *priv, 335338c658aSOndrej Zary struct v4l2_frequency *f) 336338c658aSOndrej Zary { 337338c658aSOndrej Zary struct snd_tea575x *tea = video_drvdata(file); 338338c658aSOndrej Zary 339338c658aSOndrej Zary if (f->tuner != 0) 340338c658aSOndrej Zary return -EINVAL; 341338c658aSOndrej Zary f->type = V4L2_TUNER_RADIO; 342338c658aSOndrej Zary f->frequency = tea->freq; 343338c658aSOndrej Zary return 0; 344338c658aSOndrej Zary } 345338c658aSOndrej Zary 346338c658aSOndrej Zary static int vidioc_s_frequency(struct file *file, void *priv, 347338c658aSOndrej Zary const struct v4l2_frequency *f) 348338c658aSOndrej Zary { 349338c658aSOndrej Zary struct snd_tea575x *tea = video_drvdata(file); 350338c658aSOndrej Zary 351338c658aSOndrej Zary if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 352338c658aSOndrej Zary return -EINVAL; 353338c658aSOndrej Zary 354338c658aSOndrej Zary if (tea->has_am && f->frequency < (20000 * 16)) 355338c658aSOndrej Zary tea->band = BAND_AM; 356338c658aSOndrej Zary else if (tea->tea5759) 357338c658aSOndrej Zary tea->band = BAND_FM_JAPAN; 358338c658aSOndrej Zary else 359338c658aSOndrej Zary tea->band = BAND_FM; 360338c658aSOndrej Zary 361338c658aSOndrej Zary tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow, 362338c658aSOndrej Zary bands[tea->band].rangehigh); 363338c658aSOndrej Zary snd_tea575x_set_freq(tea); 364338c658aSOndrej Zary return 0; 365338c658aSOndrej Zary } 366338c658aSOndrej Zary 3676994ca3dSOndrej Zary int snd_tea575x_s_hw_freq_seek(struct file *file, struct snd_tea575x *tea, 368338c658aSOndrej Zary const struct v4l2_hw_freq_seek *a) 369338c658aSOndrej Zary { 370338c658aSOndrej Zary unsigned long timeout; 371338c658aSOndrej Zary int i, spacing; 372338c658aSOndrej Zary 373338c658aSOndrej Zary if (tea->cannot_read_data) 374338c658aSOndrej Zary return -ENOTTY; 375338c658aSOndrej Zary if (a->tuner || a->wrap_around) 376338c658aSOndrej Zary return -EINVAL; 377338c658aSOndrej Zary 378338c658aSOndrej Zary if (file->f_flags & O_NONBLOCK) 379338c658aSOndrej Zary return -EWOULDBLOCK; 380338c658aSOndrej Zary 381338c658aSOndrej Zary if (a->rangelow || a->rangehigh) { 382338c658aSOndrej Zary for (i = 0; i < ARRAY_SIZE(bands); i++) { 383338c658aSOndrej Zary if ((i == BAND_FM && tea->tea5759) || 384338c658aSOndrej Zary (i == BAND_FM_JAPAN && !tea->tea5759) || 385338c658aSOndrej Zary (i == BAND_AM && !tea->has_am)) 386338c658aSOndrej Zary continue; 387338c658aSOndrej Zary if (bands[i].rangelow == a->rangelow && 388338c658aSOndrej Zary bands[i].rangehigh == a->rangehigh) 389338c658aSOndrej Zary break; 390338c658aSOndrej Zary } 391338c658aSOndrej Zary if (i == ARRAY_SIZE(bands)) 392338c658aSOndrej Zary return -EINVAL; /* No matching band found */ 393338c658aSOndrej Zary if (i != tea->band) { 394338c658aSOndrej Zary tea->band = i; 395338c658aSOndrej Zary tea->freq = clamp(tea->freq, bands[i].rangelow, 396338c658aSOndrej Zary bands[i].rangehigh); 397338c658aSOndrej Zary snd_tea575x_set_freq(tea); 398338c658aSOndrej Zary } 399338c658aSOndrej Zary } 400338c658aSOndrej Zary 401338c658aSOndrej Zary spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */ 402338c658aSOndrej Zary 403338c658aSOndrej Zary /* clear the frequency, HW will fill it in */ 404338c658aSOndrej Zary tea->val &= ~TEA575X_BIT_FREQ_MASK; 405338c658aSOndrej Zary tea->val |= TEA575X_BIT_SEARCH; 406338c658aSOndrej Zary if (a->seek_upward) 407338c658aSOndrej Zary tea->val |= TEA575X_BIT_UPDOWN; 408338c658aSOndrej Zary else 409338c658aSOndrej Zary tea->val &= ~TEA575X_BIT_UPDOWN; 410338c658aSOndrej Zary snd_tea575x_write(tea, tea->val); 411338c658aSOndrej Zary timeout = jiffies + msecs_to_jiffies(10000); 412338c658aSOndrej Zary for (;;) { 413338c658aSOndrej Zary if (time_after(jiffies, timeout)) 414338c658aSOndrej Zary break; 415338c658aSOndrej Zary if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { 416338c658aSOndrej Zary /* some signal arrived, stop search */ 417338c658aSOndrej Zary tea->val &= ~TEA575X_BIT_SEARCH; 418338c658aSOndrej Zary snd_tea575x_set_freq(tea); 419338c658aSOndrej Zary return -ERESTARTSYS; 420338c658aSOndrej Zary } 421338c658aSOndrej Zary if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) { 422338c658aSOndrej Zary u32 freq; 423338c658aSOndrej Zary 424338c658aSOndrej Zary /* Found a frequency, wait until it can be read */ 425338c658aSOndrej Zary for (i = 0; i < 100; i++) { 426338c658aSOndrej Zary msleep(10); 427338c658aSOndrej Zary freq = snd_tea575x_get_freq(tea); 428338c658aSOndrej Zary if (freq) /* available */ 429338c658aSOndrej Zary break; 430338c658aSOndrej Zary } 431338c658aSOndrej Zary if (freq == 0) /* shouldn't happen */ 432338c658aSOndrej Zary break; 433338c658aSOndrej Zary /* 434338c658aSOndrej Zary * if we moved by less than the spacing, or in the 435338c658aSOndrej Zary * wrong direction, continue seeking 436338c658aSOndrej Zary */ 437338c658aSOndrej Zary if (abs(tea->freq - freq) < 16 * spacing || 438338c658aSOndrej Zary (a->seek_upward && freq < tea->freq) || 439338c658aSOndrej Zary (!a->seek_upward && freq > tea->freq)) { 440338c658aSOndrej Zary snd_tea575x_write(tea, tea->val); 441338c658aSOndrej Zary continue; 442338c658aSOndrej Zary } 443338c658aSOndrej Zary tea->freq = freq; 444338c658aSOndrej Zary tea->val &= ~TEA575X_BIT_SEARCH; 445338c658aSOndrej Zary return 0; 446338c658aSOndrej Zary } 447338c658aSOndrej Zary } 448338c658aSOndrej Zary tea->val &= ~TEA575X_BIT_SEARCH; 449338c658aSOndrej Zary snd_tea575x_set_freq(tea); 450338c658aSOndrej Zary return -ENODATA; 451338c658aSOndrej Zary } 4526994ca3dSOndrej Zary EXPORT_SYMBOL(snd_tea575x_s_hw_freq_seek); 4536994ca3dSOndrej Zary 4546994ca3dSOndrej Zary static int vidioc_s_hw_freq_seek(struct file *file, void *fh, 4556994ca3dSOndrej Zary const struct v4l2_hw_freq_seek *a) 4566994ca3dSOndrej Zary { 4576994ca3dSOndrej Zary struct snd_tea575x *tea = video_drvdata(file); 4586994ca3dSOndrej Zary 4596994ca3dSOndrej Zary return snd_tea575x_s_hw_freq_seek(file, tea, a); 4606994ca3dSOndrej Zary } 461338c658aSOndrej Zary 462338c658aSOndrej Zary static int tea575x_s_ctrl(struct v4l2_ctrl *ctrl) 463338c658aSOndrej Zary { 464338c658aSOndrej Zary struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler); 465338c658aSOndrej Zary 466338c658aSOndrej Zary switch (ctrl->id) { 467338c658aSOndrej Zary case V4L2_CID_AUDIO_MUTE: 468338c658aSOndrej Zary tea->mute = ctrl->val; 469338c658aSOndrej Zary snd_tea575x_set_freq(tea); 470338c658aSOndrej Zary return 0; 471338c658aSOndrej Zary } 472338c658aSOndrej Zary 473338c658aSOndrej Zary return -EINVAL; 474338c658aSOndrej Zary } 475338c658aSOndrej Zary 476338c658aSOndrej Zary static const struct v4l2_file_operations tea575x_fops = { 477338c658aSOndrej Zary .unlocked_ioctl = video_ioctl2, 478338c658aSOndrej Zary .open = v4l2_fh_open, 479338c658aSOndrej Zary .release = v4l2_fh_release, 480338c658aSOndrej Zary .poll = v4l2_ctrl_poll, 481338c658aSOndrej Zary }; 482338c658aSOndrej Zary 483338c658aSOndrej Zary static const struct v4l2_ioctl_ops tea575x_ioctl_ops = { 484338c658aSOndrej Zary .vidioc_querycap = vidioc_querycap, 485338c658aSOndrej Zary .vidioc_g_tuner = vidioc_g_tuner, 486338c658aSOndrej Zary .vidioc_s_tuner = vidioc_s_tuner, 487338c658aSOndrej Zary .vidioc_g_frequency = vidioc_g_frequency, 488338c658aSOndrej Zary .vidioc_s_frequency = vidioc_s_frequency, 489338c658aSOndrej Zary .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 490338c658aSOndrej Zary .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 491338c658aSOndrej Zary .vidioc_log_status = v4l2_ctrl_log_status, 492338c658aSOndrej Zary .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 493338c658aSOndrej Zary .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 494338c658aSOndrej Zary }; 495338c658aSOndrej Zary 496338c658aSOndrej Zary static const struct video_device tea575x_radio = { 497338c658aSOndrej Zary .ioctl_ops = &tea575x_ioctl_ops, 498338c658aSOndrej Zary .release = video_device_release_empty, 499338c658aSOndrej Zary }; 500338c658aSOndrej Zary 501338c658aSOndrej Zary static const struct v4l2_ctrl_ops tea575x_ctrl_ops = { 502338c658aSOndrej Zary .s_ctrl = tea575x_s_ctrl, 503338c658aSOndrej Zary }; 504338c658aSOndrej Zary 505338c658aSOndrej Zary 506338c658aSOndrej Zary int snd_tea575x_hw_init(struct snd_tea575x *tea) 507338c658aSOndrej Zary { 508338c658aSOndrej Zary tea->mute = true; 509338c658aSOndrej Zary 510338c658aSOndrej Zary /* Not all devices can or know how to read the data back. 511338c658aSOndrej Zary Such devices can set cannot_read_data to true. */ 512338c658aSOndrej Zary if (!tea->cannot_read_data) { 513338c658aSOndrej Zary snd_tea575x_write(tea, 0x55AA); 514338c658aSOndrej Zary if (snd_tea575x_read(tea) != 0x55AA) 515338c658aSOndrej Zary return -ENODEV; 516338c658aSOndrej Zary } 517338c658aSOndrej Zary 518338c658aSOndrej Zary tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28; 519338c658aSOndrej Zary tea->freq = 90500 * 16; /* 90.5Mhz default */ 520338c658aSOndrej Zary snd_tea575x_set_freq(tea); 521338c658aSOndrej Zary 522338c658aSOndrej Zary return 0; 523338c658aSOndrej Zary } 524338c658aSOndrej Zary EXPORT_SYMBOL(snd_tea575x_hw_init); 525338c658aSOndrej Zary 526338c658aSOndrej Zary int snd_tea575x_init(struct snd_tea575x *tea, struct module *owner) 527338c658aSOndrej Zary { 528338c658aSOndrej Zary int retval = snd_tea575x_hw_init(tea); 529338c658aSOndrej Zary 530338c658aSOndrej Zary if (retval) 531338c658aSOndrej Zary return retval; 532338c658aSOndrej Zary 533338c658aSOndrej Zary tea->vd = tea575x_radio; 534338c658aSOndrej Zary video_set_drvdata(&tea->vd, tea); 535338c658aSOndrej Zary mutex_init(&tea->mutex); 536c0decac1SMauro Carvalho Chehab strscpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); 537338c658aSOndrej Zary tea->vd.lock = &tea->mutex; 538338c658aSOndrej Zary tea->vd.v4l2_dev = tea->v4l2_dev; 539*e83ce300SHans Verkuil tea->vd.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 540*e83ce300SHans Verkuil if (!tea->cannot_read_data) 541*e83ce300SHans Verkuil tea->vd.device_caps |= V4L2_CAP_HW_FREQ_SEEK; 542338c658aSOndrej Zary tea->fops = tea575x_fops; 543338c658aSOndrej Zary tea->fops.owner = owner; 544338c658aSOndrej Zary tea->vd.fops = &tea->fops; 545338c658aSOndrej Zary /* disable hw_freq_seek if we can't use it */ 546338c658aSOndrej Zary if (tea->cannot_read_data) 547338c658aSOndrej Zary v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK); 548338c658aSOndrej Zary 549338c658aSOndrej Zary if (!tea->cannot_mute) { 550338c658aSOndrej Zary tea->vd.ctrl_handler = &tea->ctrl_handler; 551338c658aSOndrej Zary v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); 552338c658aSOndrej Zary v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, 553338c658aSOndrej Zary V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 554338c658aSOndrej Zary retval = tea->ctrl_handler.error; 555338c658aSOndrej Zary if (retval) { 556338c658aSOndrej Zary v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); 557338c658aSOndrej Zary v4l2_ctrl_handler_free(&tea->ctrl_handler); 558338c658aSOndrej Zary return retval; 559338c658aSOndrej Zary } 560338c658aSOndrej Zary 561338c658aSOndrej Zary if (tea->ext_init) { 562338c658aSOndrej Zary retval = tea->ext_init(tea); 563338c658aSOndrej Zary if (retval) { 564338c658aSOndrej Zary v4l2_ctrl_handler_free(&tea->ctrl_handler); 565338c658aSOndrej Zary return retval; 566338c658aSOndrej Zary } 567338c658aSOndrej Zary } 568338c658aSOndrej Zary 569338c658aSOndrej Zary v4l2_ctrl_handler_setup(&tea->ctrl_handler); 570338c658aSOndrej Zary } 571338c658aSOndrej Zary 572338c658aSOndrej Zary retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr); 573338c658aSOndrej Zary if (retval) { 574338c658aSOndrej Zary v4l2_err(tea->v4l2_dev, "can't register video device!\n"); 575338c658aSOndrej Zary v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 576338c658aSOndrej Zary return retval; 577338c658aSOndrej Zary } 578338c658aSOndrej Zary 579338c658aSOndrej Zary return 0; 580338c658aSOndrej Zary } 5810e2a706bSAndy Shevchenko EXPORT_SYMBOL(snd_tea575x_init); 582338c658aSOndrej Zary 583338c658aSOndrej Zary void snd_tea575x_exit(struct snd_tea575x *tea) 584338c658aSOndrej Zary { 585338c658aSOndrej Zary video_unregister_device(&tea->vd); 586338c658aSOndrej Zary v4l2_ctrl_handler_free(tea->vd.ctrl_handler); 587338c658aSOndrej Zary } 588338c658aSOndrej Zary EXPORT_SYMBOL(snd_tea575x_exit); 589