diff options
Diffstat (limited to 'drivers/media/radio')
-rw-r--r-- | drivers/media/radio/Kconfig | 33 | ||||
-rw-r--r-- | drivers/media/radio/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/radio/radio-cadet.c | 388 | ||||
-rw-r--r-- | drivers/media/radio/radio-shark.c | 376 | ||||
-rw-r--r-- | drivers/media/radio/radio-shark2.c | 348 | ||||
-rw-r--r-- | drivers/media/radio/radio-tea5777.c | 491 | ||||
-rw-r--r-- | drivers/media/radio/radio-tea5777.h | 87 | ||||
-rw-r--r-- | drivers/media/radio/si470x/radio-si470x-common.c | 283 | ||||
-rw-r--r-- | drivers/media/radio/si470x/radio-si470x-i2c.c | 6 | ||||
-rw-r--r-- | drivers/media/radio/si470x/radio-si470x-usb.c | 47 | ||||
-rw-r--r-- | drivers/media/radio/si470x/radio-si470x.h | 7 |
11 files changed, 1694 insertions, 376 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 24ce5a47f95..8090b87b306 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -57,6 +57,39 @@ config RADIO_MAXIRADIO To compile this driver as a module, choose M here: the module will be called radio-maxiradio. +config RADIO_SHARK + tristate "Griffin radioSHARK USB radio receiver" + depends on USB && SND + ---help--- + Choose Y here if you have this radio receiver. + + There are 2 versions of this device, this driver is for version 1, + which is white. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-shark. + +config RADIO_SHARK2 + tristate "Griffin radioSHARK2 USB radio receiver" + depends on USB + ---help--- + Choose Y here if you have this radio receiver. + + There are 2 versions of this device, this driver is for version 2, + which is black. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-shark2. config I2C_SI4713 tristate "I2C driver for Silicon Labs Si4713 device" diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index ca8c7d134b9..c03ce4fe74e 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_RADIO_CADET) += radio-cadet.o obj-$(CONFIG_RADIO_TYPHOON) += radio-typhoon.o obj-$(CONFIG_RADIO_TERRATEC) += radio-terratec.o obj-$(CONFIG_RADIO_MAXIRADIO) += radio-maxiradio.o +obj-$(CONFIG_RADIO_SHARK) += radio-shark.o +obj-$(CONFIG_RADIO_SHARK2) += shark2.o obj-$(CONFIG_RADIO_RTRACK) += radio-aimslab.o obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o @@ -29,4 +31,6 @@ obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o obj-$(CONFIG_RADIO_WL128X) += wl128x/ +shark2-objs := radio-shark2.o radio-tea5777.o + ccflags-y += -Isound diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c index 16a089fad90..697a421c994 100644 --- a/drivers/media/radio/radio-cadet.c +++ b/drivers/media/radio/radio-cadet.c @@ -41,6 +41,9 @@ #include <linux/io.h> /* outb, outb_p */ #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card."); @@ -61,14 +64,15 @@ module_param(radio_nr, int, 0); struct cadet { struct v4l2_device v4l2_dev; struct video_device vdev; + struct v4l2_ctrl_handler ctrl_handler; int io; - int users; - int curtuner; + bool is_fm_band; + u32 curfreq; int tunestat; int sigstrength; wait_queue_head_t read_queue; struct timer_list readtimer; - __u8 rdsin, rdsout, rdsstat; + u8 rdsin, rdsout, rdsstat; unsigned char rdsbuf[RDS_BUFFER]; struct mutex lock; int reading; @@ -81,9 +85,9 @@ static struct cadet cadet_card; * The V4L API spec does not define any particular unit for the signal * strength value. These values are in microvolts of RF at the tuner's input. */ -static __u16 sigtable[2][4] = { - { 5, 10, 30, 150 }, - { 28, 40, 63, 1000 } +static u16 sigtable[2][4] = { + { 1835, 2621, 4128, 65535 }, + { 2185, 4369, 13107, 65535 }, }; @@ -91,14 +95,12 @@ static int cadet_getstereo(struct cadet *dev) { int ret = V4L2_TUNER_SUB_MONO; - if (dev->curtuner != 0) /* Only FM has stereo capability! */ + if (!dev->is_fm_band) /* Only FM has stereo capability! */ return V4L2_TUNER_SUB_MONO; - mutex_lock(&dev->lock); outb(7, dev->io); /* Select tuner control */ if ((inb(dev->io + 1) & 0x40) == 0) ret = V4L2_TUNER_SUB_STEREO; - mutex_unlock(&dev->lock); return ret; } @@ -111,8 +113,6 @@ static unsigned cadet_gettune(struct cadet *dev) * Prepare for read */ - mutex_lock(&dev->lock); - outb(7, dev->io); /* Select tuner control */ curvol = inb(dev->io + 1); /* Save current volume/mute setting */ outb(0x00, dev->io + 1); /* Ensure WRITE-ENABLE is LOW */ @@ -134,8 +134,6 @@ static unsigned cadet_gettune(struct cadet *dev) * Restore volume/mute setting */ outb(curvol, dev->io + 1); - mutex_unlock(&dev->lock); - return fifo; } @@ -152,20 +150,18 @@ static unsigned cadet_getfreq(struct cadet *dev) /* * Convert to actual frequency */ - if (dev->curtuner == 0) { /* FM */ - test = 12500; - for (i = 0; i < 14; i++) { - if ((fifo & 0x01) != 0) - freq += test; - test = test << 1; - fifo = fifo >> 1; - } - freq -= 10700000; /* IF frequency is 10.7 MHz */ - freq = (freq * 16) / 1000000; /* Make it 1/16 MHz */ + if (!dev->is_fm_band) /* AM */ + return ((fifo & 0x7fff) - 450) * 16; + + test = 12500; + for (i = 0; i < 14; i++) { + if ((fifo & 0x01) != 0) + freq += test; + test = test << 1; + fifo = fifo >> 1; } - if (dev->curtuner == 1) /* AM */ - freq = ((fifo & 0x7fff) - 2010) * 16; - + freq -= 10700000; /* IF frequency is 10.7 MHz */ + freq = (freq * 16) / 1000; /* Make it 1/16 kHz */ return freq; } @@ -174,8 +170,6 @@ static void cadet_settune(struct cadet *dev, unsigned fifo) int i; unsigned test; - mutex_lock(&dev->lock); - outb(7, dev->io); /* Select tuner control */ /* * Write the shift register @@ -194,7 +188,6 @@ static void cadet_settune(struct cadet *dev, unsigned fifo) test = 0x1c | ((fifo >> 23) & 0x02); outb(test, dev->io + 1); } - mutex_unlock(&dev->lock); } static void cadet_setfreq(struct cadet *dev, unsigned freq) @@ -203,13 +196,14 @@ static void cadet_setfreq(struct cadet *dev, unsigned freq) int i, j, test; int curvol; + dev->curfreq = freq; /* * Formulate a fifo command */ fifo = 0; - if (dev->curtuner == 0) { /* FM */ + if (dev->is_fm_band) { /* FM */ test = 102400; - freq = (freq * 1000) / 16; /* Make it kHz */ + freq = freq / 16; /* Make it kHz */ freq += 10700; /* IF is 10700 kHz */ for (i = 0; i < 14; i++) { fifo = fifo << 1; @@ -219,20 +213,17 @@ static void cadet_setfreq(struct cadet *dev, unsigned freq) } test = test >> 1; } - } - if (dev->curtuner == 1) { /* AM */ - fifo = (freq / 16) + 2010; /* Make it kHz */ - fifo |= 0x100000; /* Select AM Band */ + } else { /* AM */ + fifo = (freq / 16) + 450; /* Make it kHz */ + fifo |= 0x100000; /* Select AM Band */ } /* * Save current volume/mute setting */ - mutex_lock(&dev->lock); outb(7, dev->io); /* Select tuner control */ curvol = inb(dev->io + 1); - mutex_unlock(&dev->lock); /* * Tune the card @@ -240,49 +231,24 @@ static void cadet_setfreq(struct cadet *dev, unsigned freq) for (j = 3; j > -1; j--) { cadet_settune(dev, fifo | (j << 16)); - mutex_lock(&dev->lock); outb(7, dev->io); /* Select tuner control */ outb(curvol, dev->io + 1); - mutex_unlock(&dev->lock); msleep(100); cadet_gettune(dev); if ((dev->tunestat & 0x40) == 0) { /* Tuned */ - dev->sigstrength = sigtable[dev->curtuner][j]; - return; + dev->sigstrength = sigtable[dev->is_fm_band][j]; + goto reset_rds; } } dev->sigstrength = 0; +reset_rds: + outb(3, dev->io); + outb(inb(dev->io + 1) & 0x7f, dev->io + 1); } -static int cadet_getvol(struct cadet *dev) -{ - int ret = 0; - - mutex_lock(&dev->lock); - - outb(7, dev->io); /* Select tuner control */ - if ((inb(dev->io + 1) & 0x20) != 0) - ret = 0xffff; - - mutex_unlock(&dev->lock); - return ret; -} - - -static void cadet_setvol(struct cadet *dev, int vol) -{ - mutex_lock(&dev->lock); - outb(7, dev->io); /* Select tuner control */ - if (vol > 0) - outb(0x20, dev->io + 1); - else - outb(0x00, dev->io + 1); - mutex_unlock(&dev->lock); -} - static void cadet_handler(unsigned long data) { struct cadet *dev = (void *)data; @@ -295,7 +261,7 @@ static void cadet_handler(unsigned long data) outb(0x80, dev->io); /* Select RDS fifo */ while ((inb(dev->io) & 0x80) != 0) { dev->rdsbuf[dev->rdsin] = inb(dev->io + 1); - if (dev->rdsin == dev->rdsout) + if (dev->rdsin + 1 == dev->rdsout) printk(KERN_WARNING "cadet: RDS buffer overflow\n"); else dev->rdsin++; @@ -314,11 +280,21 @@ static void cadet_handler(unsigned long data) */ init_timer(&dev->readtimer); dev->readtimer.function = cadet_handler; - dev->readtimer.data = (unsigned long)0; + dev->readtimer.data = data; dev->readtimer.expires = jiffies + msecs_to_jiffies(50); add_timer(&dev->readtimer); } +static void cadet_start_rds(struct cadet *dev) +{ + dev->rdsstat = 1; + outb(0x80, dev->io); /* Select RDS fifo */ + init_timer(&dev->readtimer); + dev->readtimer.function = cadet_handler; + dev->readtimer.data = (unsigned long)dev; + dev->readtimer.expires = jiffies + msecs_to_jiffies(50); + add_timer(&dev->readtimer); +} static ssize_t cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { @@ -327,28 +303,24 @@ static ssize_t cadet_read(struct file *file, char __user *data, size_t count, lo int i = 0; mutex_lock(&dev->lock); - if (dev->rdsstat == 0) { - dev->rdsstat = 1; - outb(0x80, dev->io); /* Select RDS fifo */ - init_timer(&dev->readtimer); - dev->readtimer.function = cadet_handler; - dev->readtimer.data = (unsigned long)dev; - dev->readtimer.expires = jiffies + msecs_to_jiffies(50); - add_timer(&dev->readtimer); - } + if (dev->rdsstat == 0) + cadet_start_rds(dev); if (dev->rdsin == dev->rdsout) { + if (file->f_flags & O_NONBLOCK) { + i = -EWOULDBLOCK; + goto unlock; + } mutex_unlock(&dev->lock); - if (file->f_flags & O_NONBLOCK) - return -EWOULDBLOCK; interruptible_sleep_on(&dev->read_queue); mutex_lock(&dev->lock); } while (i < count && dev->rdsin != dev->rdsout) readbuf[i++] = dev->rdsbuf[dev->rdsout++]; - mutex_unlock(&dev->lock); - if (copy_to_user(data, readbuf, i)) - return -EFAULT; + if (i && copy_to_user(data, readbuf, i)) + i = -EFAULT; +unlock: + mutex_unlock(&dev->lock); return i; } @@ -359,48 +331,58 @@ static int vidioc_querycap(struct file *file, void *priv, strlcpy(v->driver, "ADS Cadet", sizeof(v->driver)); strlcpy(v->card, "ADS Cadet", sizeof(v->card)); strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO | + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } +static const struct v4l2_frequency_band bands[] = { + { + .index = 0, + .type = V4L2_TUNER_RADIO, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 8320, /* 520 kHz */ + .rangehigh = 26400, /* 1650 kHz */ + .modulation = V4L2_BAND_MODULATION_AM, + }, { + .index = 1, + .type = V4L2_TUNER_RADIO, + .capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | + V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW | + V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 1400000, /* 87.5 MHz */ + .rangehigh = 1728000, /* 108.0 MHz */ + .modulation = V4L2_BAND_MODULATION_FM, + }, +}; + static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) { struct cadet *dev = video_drvdata(file); + if (v->index) + return -EINVAL; v->type = V4L2_TUNER_RADIO; - switch (v->index) { - case 0: - strlcpy(v->name, "FM", sizeof(v->name)); - v->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | - V4L2_TUNER_CAP_RDS_BLOCK_IO; - v->rangelow = 1400; /* 87.5 MHz */ - v->rangehigh = 1728; /* 108.0 MHz */ + strlcpy(v->name, "Radio", sizeof(v->name)); + v->capability = bands[0].capability | bands[1].capability; + v->rangelow = bands[0].rangelow; /* 520 kHz (start of AM band) */ + v->rangehigh = bands[1].rangehigh; /* 108.0 MHz (end of FM band) */ + if (dev->is_fm_band) { v->rxsubchans = cadet_getstereo(dev); - switch (v->rxsubchans) { - case V4L2_TUNER_SUB_MONO: - v->audmode = V4L2_TUNER_MODE_MONO; - break; - case V4L2_TUNER_SUB_STEREO: - v->audmode = V4L2_TUNER_MODE_STEREO; - break; - default: - break; - } - v->rxsubchans |= V4L2_TUNER_SUB_RDS; - break; - case 1: - strlcpy(v->name, "AM", sizeof(v->name)); - v->capability = V4L2_TUNER_CAP_LOW; + outb(3, dev->io); + outb(inb(dev->io + 1) & 0x7f, dev->io + 1); + mdelay(100); + outb(3, dev->io); + if (inb(dev->io + 1) & 0x80) + v->rxsubchans |= V4L2_TUNER_SUB_RDS; + } else { v->rangelow = 8320; /* 520 kHz */ v->rangehigh = 26400; /* 1650 kHz */ v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->audmode = V4L2_TUNER_MODE_MONO; - break; - default: - return -EINVAL; } + v->audmode = V4L2_TUNER_MODE_STEREO; v->signal = dev->sigstrength; /* We might need to modify scaling of this */ return 0; } @@ -408,11 +390,17 @@ static int vidioc_g_tuner(struct file *file, void *priv, static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *v) { - struct cadet *dev = video_drvdata(file); + return v->index ? -EINVAL : 0; +} - if (v->index != 0 && v->index != 1) +static int vidioc_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + if (band->tuner) + return -EINVAL; + if (band->index >= ARRAY_SIZE(bands)) return -EINVAL; - dev->curtuner = v->index; + *band = bands[band->index]; return 0; } @@ -421,9 +409,10 @@ static int vidioc_g_frequency(struct file *file, void *priv, { struct cadet *dev = video_drvdata(file); - f->tuner = dev->curtuner; + if (f->tuner) + return -EINVAL; f->type = V4L2_TUNER_RADIO; - f->frequency = cadet_getfreq(dev); + f->frequency = dev->curfreq; return 0; } @@ -433,103 +422,46 @@ static int vidioc_s_frequency(struct file *file, void *priv, { struct cadet *dev = video_drvdata(file); - if (f->type != V4L2_TUNER_RADIO) - return -EINVAL; - if (dev->curtuner == 0 && (f->frequency < 1400 || f->frequency > 1728)) - return -EINVAL; - if (dev->curtuner == 1 && (f->frequency < 8320 || f->frequency > 26400)) + if (f->tuner) return -EINVAL; + dev->is_fm_band = + f->frequency >= (bands[0].rangehigh + bands[1].rangelow) / 2; + clamp(f->frequency, bands[dev->is_fm_band].rangelow, + bands[dev->is_fm_band].rangehigh); cadet_setfreq(dev, f->frequency); return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) +static int cadet_s_ctrl(struct v4l2_ctrl *ctrl) { - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct cadet *dev = video_drvdata(file); + struct cadet *dev = container_of(ctrl->handler, struct cadet, ctrl_handler); switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */ - ctrl->value = (cadet_getvol(dev) == 0); - break; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = cadet_getvol(dev); - break; - default: - return -EINVAL; - } - return 0; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct cadet *dev = video_drvdata(file); - - switch (ctrl->id){ - case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */ - if (ctrl->value) - cadet_setvol(dev, 0); + case V4L2_CID_AUDIO_MUTE: + outb(7, dev->io); /* Select tuner control */ + if (ctrl->val) + outb(0x00, dev->io + 1); else - cadet_setvol(dev, 0xffff); - break; - case V4L2_CID_AUDIO_VOLUME: - cadet_setvol(dev, ctrl->value); - break; - default: - return -EINVAL; + outb(0x20, dev->io + 1); + return 0; } - return 0; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; + return -EINVAL; } static int cadet_open(struct file *file) { struct cadet *dev = video_drvdata(file); + int err; mutex_lock(&dev->lock); - dev->users++; - if (1 == dev->users) + err = v4l2_fh_open(file); + if (err) + goto fail; + if (v4l2_fh_is_singular_file(file)) init_waitqueue_head(&dev->read_queue); +fail: mutex_unlock(&dev->lock); - return 0; + return err; } static int cadet_release(struct file *file) @@ -537,11 +469,11 @@ static int cadet_release(struct file *file) struct cadet *dev = video_drvdata(file); mutex_lock(&dev->lock); - dev->users--; - if (0 == dev->users) { + if (v4l2_fh_is_singular_file(file) && dev->rdsstat) { del_timer_sync(&dev->readtimer); dev->rdsstat = 0; } + v4l2_fh_release(file); mutex_unlock(&dev->lock); return 0; } @@ -549,11 +481,19 @@ static int cadet_release(struct file *file) static unsigned int cadet_poll(struct file *file, struct poll_table_struct *wait) { struct cadet *dev = video_drvdata(file); + unsigned long req_events = poll_requested_events(wait); + unsigned int res = v4l2_ctrl_poll(file, wait); poll_wait(file, &dev->read_queue, wait); + if (dev->rdsstat == 0 && (req_events & (POLLIN | POLLRDNORM))) { + mutex_lock(&dev->lock); + if (dev->rdsstat == 0) + cadet_start_rds(dev); + mutex_unlock(&dev->lock); + } if (dev->rdsin != dev->rdsout) - return POLLIN | POLLRDNORM; - return 0; + res |= POLLIN | POLLRDNORM; + return res; } @@ -572,13 +512,14 @@ static const struct v4l2_ioctl_ops cadet_ioctl_ops = { .vidioc_s_tuner = vidioc_s_tuner, .vidioc_g_frequency = vidioc_g_frequency, .vidioc_s_frequency = vidioc_s_frequency, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, - .vidioc_g_audio = vidioc_g_audio, - .vidioc_s_audio = vidioc_s_audio, - .vidioc_g_input = vidioc_g_input, - .vidioc_s_input = vidioc_s_input, + .vidioc_enum_freq_bands = vidioc_enum_freq_bands, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_ctrl_ops cadet_ctrl_ops = { + .s_ctrl = cadet_s_ctrl, }; #ifdef CONFIG_PNP @@ -628,8 +569,8 @@ static void cadet_probe(struct cadet *dev) for (i = 0; i < 8; i++) { dev->io = iovals[i]; if (request_region(dev->io, 2, "cadet-probe")) { - cadet_setfreq(dev, 1410); - if (cadet_getfreq(dev) == 1410) { + cadet_setfreq(dev, bands[1].rangelow); + if (cadet_getfreq(dev) == bands[1].rangelow) { release_region(dev->io, 2); return; } @@ -648,7 +589,8 @@ static int __init cadet_init(void) { struct cadet *dev = &cadet_card; struct v4l2_device *v4l2_dev = &dev->v4l2_dev; - int res; + struct v4l2_ctrl_handler *hdl; + int res = -ENODEV; strlcpy(v4l2_dev->name, "cadet", sizeof(v4l2_dev->name)); mutex_init(&dev->lock); @@ -680,23 +622,40 @@ static int __init cadet_init(void) goto fail; } + hdl = &dev->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 2); + v4l2_ctrl_new_std(hdl, &cadet_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + v4l2_dev->ctrl_handler = hdl; + if (hdl->error) { + res = hdl->error; + v4l2_err(v4l2_dev, "Could not register controls\n"); + goto err_hdl; + } + + dev->is_fm_band = true; + dev->curfreq = bands[dev->is_fm_band].rangelow; + cadet_setfreq(dev, dev->curfreq); strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); dev->vdev.v4l2_dev = v4l2_dev; dev->vdev.fops = &cadet_fops; dev->vdev.ioctl_ops = &cadet_ioctl_ops; dev->vdev.release = video_device_release_empty; + dev->vdev.lock = &dev->lock; + set_bit(V4L2_FL_USE_FH_PRIO, &dev->vdev.flags); video_set_drvdata(&dev->vdev, dev); - if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(dev->io, 2); - goto fail; - } + if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) + goto err_hdl; v4l2_info(v4l2_dev, "ADS Cadet Radio Card at 0x%x\n", dev->io); return 0; +err_hdl: + v4l2_ctrl_handler_free(hdl); + v4l2_device_unregister(v4l2_dev); + release_region(dev->io, 2); fail: pnp_unregister_driver(&cadet_pnp_driver); - return -ENODEV; + return res; } static void __exit cadet_exit(void) @@ -704,7 +663,10 @@ static void __exit cadet_exit(void) struct cadet *dev = &cadet_card; video_unregister_device(&dev->vdev); + v4l2_ctrl_handler_free(&dev->ctrl_handler); v4l2_device_unregister(&dev->v4l2_dev); + outb(7, dev->io); /* Mute */ + outb(0x00, dev->io + 1); release_region(dev->io, 2); pnp_unregister_driver(&cadet_pnp_driver); } diff --git a/drivers/media/radio/radio-shark.c b/drivers/media/radio/radio-shark.c new file mode 100644 index 00000000000..d0b6bb50763 --- /dev/null +++ b/drivers/media/radio/radio-shark.c @@ -0,0 +1,376 @@ +/* + * Linux V4L2 radio driver for the Griffin radioSHARK USB radio receiver + * + * Note the radioSHARK offers the audio through a regular USB audio device, + * this driver only handles the tuning. + * + * The info necessary to drive the shark was taken from the small userspace + * shark.c program by Michael Rolig, which he kindly placed in the Public + * Domain. + * + * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <media/v4l2-device.h> +#include <sound/tea575x-tuner.h> + +/* + * Version Information + */ +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_DESCRIPTION("Griffin radioSHARK, USB radio receiver driver"); +MODULE_LICENSE("GPL"); + +#define SHARK_IN_EP 0x83 +#define SHARK_OUT_EP 0x05 + +#define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */ +#define TEA575X_BIT_BAND_MASK (3<<20) +#define TEA575X_BIT_BAND_FM (0<<20) + +#define TB_LEN 6 +#define DRV_NAME "radioshark" + +#define v4l2_dev_to_shark(d) container_of(d, struct shark_device, v4l2_dev) + +enum { BLUE_LED, BLUE_PULSE_LED, RED_LED, NO_LEDS }; + +static void shark_led_set_blue(struct led_classdev *led_cdev, + enum led_brightness value); +static void shark_led_set_blue_pulse(struct led_classdev *led_cdev, + enum led_brightness value); +static void shark_led_set_red(struct led_classdev *led_cdev, + enum led_brightness value); + +static const struct led_classdev shark_led_templates[NO_LEDS] = { + [BLUE_LED] = { + .name = "%s:blue:", + .brightness = LED_OFF, + .max_brightness = 127, + .brightness_set = shark_led_set_blue, + }, + [BLUE_PULSE_LED] = { + .name = "%s:blue-pulse:", + .brightness = LED_OFF, + .max_brightness = 255, + .brightness_set = shark_led_set_blue_pulse, + }, + [RED_LED] = { + .name = "%s:red:", + .brightness = LED_OFF, + .max_brightness = 1, + .brightness_set = shark_led_set_red, + }, +}; + +struct shark_device { + struct usb_device *usbdev; + struct v4l2_device v4l2_dev; + struct snd_tea575x tea; + + struct work_struct led_work; + struct led_classdev leds[NO_LEDS]; + char led_names[NO_LEDS][32]; + atomic_t brightness[NO_LEDS]; + unsigned long brightness_new; + + u8 *transfer_buffer; + u32 last_val; +}; + +static atomic_t shark_instance = ATOMIC_INIT(0); + +static void shark_write_val(struct snd_tea575x *tea, u32 val) +{ + struct shark_device *shark = tea->private_data; + int i, res, actual_len; + + /* Avoid unnecessary (slow) USB transfers */ + if (shark->last_val == val) + return; + + memset(shark->transfer_buffer, 0, TB_LEN); + shark->transfer_buffer[0] = 0xc0; /* Write shift register command */ + for (i = 0; i < 4; i++) + shark->transfer_buffer[i] |= (val >> (24 - i * 8)) & 0xff; + + res = usb_interrupt_msg(shark->usbdev, + usb_sndintpipe(shark->usbdev, SHARK_OUT_EP), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res >= 0) + shark->last_val = val; + else + v4l2_err(&shark->v4l2_dev, "set-freq error: %d\n", res); +} + +static u32 shark_read_val(struct snd_tea575x *tea) +{ + struct shark_device *shark = tea->private_data; + int i, res, actual_len; + u32 val = 0; + + memset(shark->transfer_buffer, 0, TB_LEN); + shark->transfer_buffer[0] = 0x80; + res = usb_interrupt_msg(shark->usbdev, + usb_sndintpipe(shark->usbdev, SHARK_OUT_EP), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res < 0) { + v4l2_err(&shark->v4l2_dev, "request-status error: %d\n", res); + return shark->last_val; + } + + res = usb_interrupt_msg(shark->usbdev, + usb_rcvintpipe(shark->usbdev, SHARK_IN_EP), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res < 0) { + v4l2_err(&shark->v4l2_dev, "get-status error: %d\n", res); + return shark->last_val; + } + + for (i = 0; i < 4; i++) + val |= shark->transfer_buffer[i] << (24 - i * 8); + + shark->last_val = val; + + /* + * The shark does not allow actually reading the stereo / mono pin :( + * So assume that when we're tuned to an FM station and mono has not + * been requested, that we're receiving stereo. + */ + if (((val & TEA575X_BIT_BAND_MASK) == TEA575X_BIT_BAND_FM) && + !(val & TEA575X_BIT_MONO)) + shark->tea.stereo = true; + else + shark->tea.stereo = false; + + return val; +} + +static struct snd_tea575x_ops shark_tea_ops = { + .write_val = shark_write_val, + .read_val = shark_read_val, +}; + +static void shark_led_work(struct work_struct *work) +{ + struct shark_device *shark = + container_of(work, struct shark_device, led_work); + int i, res, brightness, actual_len; + + /* + * We use the v4l2_dev lock and registered bit to ensure the device + * does not get unplugged and unreffed while we're running. + */ + mutex_lock(&shark->tea.mutex); + if (!video_is_registered(&shark->tea.vd)) + goto leave; + + for (i = 0; i < 3; i++) { + if (!test_and_clear_bit(i, &shark->brightness_new)) + continue; + + brightness = atomic_read(&shark->brightness[i]); + memset(shark->transfer_buffer, 0, TB_LEN); + if (i != RED_LED) { + shark->transfer_buffer[0] = 0xA0 + i; + shark->transfer_buffer[1] = brightness; + } else + shark->transfer_buffer[0] = brightness ? 0xA9 : 0xA8; + res = usb_interrupt_msg(shark->usbdev, + usb_sndintpipe(shark->usbdev, 0x05), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res < 0) + v4l2_err(&shark->v4l2_dev, "set LED %s error: %d\n", + shark->led_names[i], res); + } +leave: + mutex_unlock(&shark->tea.mutex); +} + +static void shark_led_set_blue(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct shark_device *shark = + container_of(led_cdev, struct shark_device, leds[BLUE_LED]); + + atomic_set(&shark->brightness[BLUE_LED], value); + set_bit(BLUE_LED, &shark->brightness_new); + schedule_work(&shark->led_work); +} + +static void shark_led_set_blue_pulse(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct shark_device *shark = container_of(led_cdev, + struct shark_device, leds[BLUE_PULSE_LED]); + + atomic_set(&shark->brightness[BLUE_PULSE_LED], 256 - value); + set_bit(BLUE_PULSE_LED, &shark->brightness_new); + schedule_work(&shark->led_work); +} + +static void shark_led_set_red(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct shark_device *shark = + container_of(led_cdev, struct shark_device, leds[RED_LED]); + + atomic_set(&shark->brightness[RED_LED], value); + set_bit(RED_LED, &shark->brightness_new); + schedule_work(&shark->led_work); +} + +static void usb_shark_disconnect(struct usb_interface *intf) +{ + struct v4l2_device *v4l2_dev = usb_get_intfdata(intf); + struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev); + int i; + + mutex_lock(&shark->tea.mutex); + v4l2_device_disconnect(&shark->v4l2_dev); + snd_tea575x_exit(&shark->tea); + mutex_unlock(&shark->tea.mutex); + + for (i = 0; i < NO_LEDS; i++) + led_classdev_unregister(&shark->leds[i]); + + v4l2_device_put(&shark->v4l2_dev); +} + +static void usb_shark_release(struct v4l2_device *v4l2_dev) +{ + struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev); + + cancel_work_sync(&shark->led_work); + v4l2_device_unregister(&shark->v4l2_dev); + kfree(shark->transfer_buffer); + kfree(shark); +} + +static int usb_shark_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct shark_device *shark; + int i, retval = -ENOMEM; + + shark = kzalloc(sizeof(struct shark_device), GFP_KERNEL); + if (!shark) + return retval; + + shark->transfer_buffer = kmalloc(TB_LEN, GFP_KERNEL); + if (!shark->transfer_buffer) + goto err_alloc_buffer; + + /* + * Work around a bug in usbhid/hid-core.c, where it leaves a dangling + * pointer in intfdata causing v4l2-device.c to not set it. Which + * results in usb_shark_disconnect() referencing the dangling pointer + * + * REMOVE (as soon as the above bug is fixed, patch submitted) + */ + usb_set_intfdata(intf, NULL); + + shark->v4l2_dev.release = usb_shark_release; + v4l2_device_set_name(&shark->v4l2_dev, DRV_NAME, &shark_instance); + retval = v4l2_device_register(&intf->dev, &shark->v4l2_dev); + if (retval) { + v4l2_err(&shark->v4l2_dev, "couldn't register v4l2_device\n"); + goto err_reg_dev; + } + + shark->usbdev = interface_to_usbdev(intf); + shark->tea.v4l2_dev = &shark->v4l2_dev; + shark->tea.private_data = shark; + shark->tea.radio_nr = -1; + shark->tea.ops = &shark_tea_ops; + shark->tea.cannot_mute = true; + strlcpy(shark->tea.card, "Griffin radioSHARK", + sizeof(shark->tea.card)); + usb_make_path(shark->usbdev, shark->tea.bus_info, + sizeof(shark->tea.bus_info)); + + retval = snd_tea575x_init(&shark->tea, THIS_MODULE); + if (retval) { + v4l2_err(&shark->v4l2_dev, "couldn't init tea5757\n"); + goto err_init_tea; + } + + INIT_WORK(&shark->led_work, shark_led_work); + for (i = 0; i < NO_LEDS; i++) { + shark->leds[i] = shark_led_templates[i]; + snprintf(shark->led_names[i], sizeof(shark->led_names[0]), + shark->leds[i].name, shark->v4l2_dev.name); + shark->leds[i].name = shark->led_names[i]; + /* + * We don't fail the probe if we fail to register the leds, + * because once we've called snd_tea575x_init, the /dev/radio0 + * node may be opened from userspace holding a reference to us! + * + * Note we cannot register the leds first instead as + * shark_led_work depends on the v4l2 mutex and registered bit. + */ + retval = led_classdev_register(&intf->dev, &shark->leds[i]); + if (retval) + v4l2_err(&shark->v4l2_dev, + "couldn't register led: %s\n", + shark->led_names[i]); + } + + return 0; + +err_init_tea: + v4l2_device_unregister(&shark->v4l2_dev); +err_reg_dev: + kfree(shark->transfer_buffer); +err_alloc_buffer: + kfree(shark); + + return retval; +} + +/* Specify the bcdDevice value, as the radioSHARK and radioSHARK2 share ids */ +static struct usb_device_id usb_shark_device_table[] = { + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_CLASS, + .idVendor = 0x077d, + .idProduct = 0x627a, + .bcdDevice_lo = 0x0001, + .bcdDevice_hi = 0x0001, + .bInterfaceClass = 3, + }, + { } +}; +MODULE_DEVICE_TABLE(usb, usb_shark_device_table); + +static struct usb_driver usb_shark_driver = { + .name = DRV_NAME, + .probe = usb_shark_probe, + .disconnect = usb_shark_disconnect, + .id_table = usb_shark_device_table, +}; +module_usb_driver(usb_shark_driver); diff --git a/drivers/media/radio/radio-shark2.c b/drivers/media/radio/radio-shark2.c new file mode 100644 index 00000000000..b9575de3e7e --- /dev/null +++ b/drivers/media/radio/radio-shark2.c @@ -0,0 +1,348 @@ +/* + * Linux V4L2 radio driver for the Griffin radioSHARK2 USB radio receiver + * + * Note the radioSHARK2 offers the audio through a regular USB audio device, + * this driver only handles the tuning. + * + * The info necessary to drive the shark2 was taken from the small userspace + * shark2.c program by Hisaaki Shibata, which he kindly placed in the Public + * Domain. + * + * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <media/v4l2-device.h> +#include "radio-tea5777.h" + +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_DESCRIPTION("Griffin radioSHARK2, USB radio receiver driver"); +MODULE_LICENSE("GPL"); + +static int debug; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + + +#define SHARK_IN_EP 0x83 +#define SHARK_OUT_EP 0x05 + +#define TB_LEN 7 +#define DRV_NAME "radioshark2" + +#define v4l2_dev_to_shark(d) container_of(d, struct shark_device, v4l2_dev) + +enum { BLUE_LED, RED_LED, NO_LEDS }; + +static void shark_led_set_blue(struct led_classdev *led_cdev, + enum led_brightness value); +static void shark_led_set_red(struct led_classdev *led_cdev, + enum led_brightness value); + +static const struct led_classdev shark_led_templates[NO_LEDS] = { + [BLUE_LED] = { + .name = "%s:blue:", + .brightness = LED_OFF, + .max_brightness = 127, + .brightness_set = shark_led_set_blue, + }, + [RED_LED] = { + .name = "%s:red:", + .brightness = LED_OFF, + .max_brightness = 1, + .brightness_set = shark_led_set_red, + }, +}; + +struct shark_device { + struct usb_device *usbdev; + struct v4l2_device v4l2_dev; + struct radio_tea5777 tea; + + struct work_struct led_work; + struct led_classdev leds[NO_LEDS]; + char led_names[NO_LEDS][32]; + atomic_t brightness[NO_LEDS]; + unsigned long brightness_new; + + u8 *transfer_buffer; +}; + +static atomic_t shark_instance = ATOMIC_INIT(0); + +static int shark_write_reg(struct radio_tea5777 *tea, u64 reg) +{ + struct shark_device *shark = tea->private_data; + int i, res, actual_len; + + memset(shark->transfer_buffer, 0, TB_LEN); + shark->transfer_buffer[0] = 0x81; /* Write register command */ + for (i = 0; i < 6; i++) + shark->transfer_buffer[i + 1] = (reg >> (40 - i * 8)) & 0xff; + + v4l2_dbg(1, debug, tea->v4l2_dev, + "shark2-write: %02x %02x %02x %02x %02x %02x %02x\n", + shark->transfer_buffer[0], shark->transfer_buffer[1], + shark->transfer_buffer[2], shark->transfer_buffer[3], + shark->transfer_buffer[4], shark->transfer_buffer[5], + shark->transfer_buffer[6]); + + res = usb_interrupt_msg(shark->usbdev, + usb_sndintpipe(shark->usbdev, SHARK_OUT_EP), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res < 0) { + v4l2_err(tea->v4l2_dev, "write error: %d\n", res); + return res; + } + + return 0; +} + +static int shark_read_reg(struct radio_tea5777 *tea, u32 *reg_ret) +{ + struct shark_device *shark = tea->private_data; + int i, res, actual_len; + u32 reg = 0; + + memset(shark->transfer_buffer, 0, TB_LEN); + shark->transfer_buffer[0] = 0x82; + res = usb_interrupt_msg(shark->usbdev, + usb_sndintpipe(shark->usbdev, SHARK_OUT_EP), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res < 0) { + v4l2_err(tea->v4l2_dev, "request-read error: %d\n", res); + return res; + } + + res = usb_interrupt_msg(shark->usbdev, + usb_rcvintpipe(shark->usbdev, SHARK_IN_EP), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res < 0) { + v4l2_err(tea->v4l2_dev, "read error: %d\n", res); + return res; + } + + for (i = 0; i < 3; i++) + reg |= shark->transfer_buffer[i] << (16 - i * 8); + + v4l2_dbg(1, debug, tea->v4l2_dev, "shark2-read: %02x %02x %02x\n", + shark->transfer_buffer[0], shark->transfer_buffer[1], + shark->transfer_buffer[2]); + + *reg_ret = reg; + return 0; +} + +static struct radio_tea5777_ops shark_tea_ops = { + .write_reg = shark_write_reg, + .read_reg = shark_read_reg, +}; + +static void shark_led_work(struct work_struct *work) +{ + struct shark_device *shark = + container_of(work, struct shark_device, led_work); + int i, res, brightness, actual_len; + /* + * We use the v4l2_dev lock and registered bit to ensure the device + * does not get unplugged and unreffed while we're running. + */ + mutex_lock(&shark->tea.mutex); + if (!video_is_registered(&shark->tea.vd)) + goto leave; + + for (i = 0; i < 2; i++) { + if (!test_and_clear_bit(i, &shark->brightness_new)) + continue; + + brightness = atomic_read(&shark->brightness[i]); + memset(shark->transfer_buffer, 0, TB_LEN); + shark->transfer_buffer[0] = 0x83 + i; + shark->transfer_buffer[1] = brightness; + res = usb_interrupt_msg(shark->usbdev, + usb_sndintpipe(shark->usbdev, + SHARK_OUT_EP), + shark->transfer_buffer, TB_LEN, + &actual_len, 1000); + if (res < 0) + v4l2_err(&shark->v4l2_dev, "set LED %s error: %d\n", + shark->led_names[i], res); + } +leave: + mutex_unlock(&shark->tea.mutex); +} + +static void shark_led_set_blue(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct shark_device *shark = + container_of(led_cdev, struct shark_device, leds[BLUE_LED]); + + atomic_set(&shark->brightness[BLUE_LED], value); + set_bit(BLUE_LED, &shark->brightness_new); + schedule_work(&shark->led_work); +} + +static void shark_led_set_red(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct shark_device *shark = + container_of(led_cdev, struct shark_device, leds[RED_LED]); + + atomic_set(&shark->brightness[RED_LED], value); + set_bit(RED_LED, &shark->brightness_new); + schedule_work(&shark->led_work); +} + +static void usb_shark_disconnect(struct usb_interface *intf) +{ + struct v4l2_device *v4l2_dev = usb_get_intfdata(intf); + struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev); + int i; + + mutex_lock(&shark->tea.mutex); + v4l2_device_disconnect(&shark->v4l2_dev); + radio_tea5777_exit(&shark->tea); + mutex_unlock(&shark->tea.mutex); + + for (i = 0; i < NO_LEDS; i++) + led_classdev_unregister(&shark->leds[i]); + + v4l2_device_put(&shark->v4l2_dev); +} + +static void usb_shark_release(struct v4l2_device *v4l2_dev) +{ + struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev); + + cancel_work_sync(&shark->led_work); + v4l2_device_unregister(&shark->v4l2_dev); + kfree(shark->transfer_buffer); + kfree(shark); +} + +static int usb_shark_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct shark_device *shark; + int i, retval = -ENOMEM; + + shark = kzalloc(sizeof(struct shark_device), GFP_KERNEL); + if (!shark) + return retval; + + shark->transfer_buffer = kmalloc(TB_LEN, GFP_KERNEL); + if (!shark->transfer_buffer) + goto err_alloc_buffer; + + /* + * Work around a bug in usbhid/hid-core.c, where it leaves a dangling + * pointer in intfdata causing v4l2-device.c to not set it. Which + * results in usb_shark_disconnect() referencing the dangling pointer + * + * REMOVE (as soon as the above bug is fixed, patch submitted) + */ + usb_set_intfdata(intf, NULL); + + shark->v4l2_dev.release = usb_shark_release; + v4l2_device_set_name(&shark->v4l2_dev, DRV_NAME, &shark_instance); + retval = v4l2_device_register(&intf->dev, &shark->v4l2_dev); + if (retval) { + v4l2_err(&shark->v4l2_dev, "couldn't register v4l2_device\n"); + goto err_reg_dev; + } + + shark->usbdev = interface_to_usbdev(intf); + shark->tea.v4l2_dev = &shark->v4l2_dev; + shark->tea.private_data = shark; + shark->tea.ops = &shark_tea_ops; + shark->tea.has_am = true; + shark->tea.write_before_read = true; + strlcpy(shark->tea.card, "Griffin radioSHARK2", + sizeof(shark->tea.card)); + usb_make_path(shark->usbdev, shark->tea.bus_info, + sizeof(shark->tea.bus_info)); + + retval = radio_tea5777_init(&shark->tea, THIS_MODULE); + if (retval) { + v4l2_err(&shark->v4l2_dev, "couldn't init tea5777\n"); + goto err_init_tea; + } + + INIT_WORK(&shark->led_work, shark_led_work); + for (i = 0; i < NO_LEDS; i++) { + shark->leds[i] = shark_led_templates[i]; + snprintf(shark->led_names[i], sizeof(shark->led_names[0]), + shark->leds[i].name, shark->v4l2_dev.name); + shark->leds[i].name = shark->led_names[i]; + /* + * We don't fail the probe if we fail to register the leds, + * because once we've called radio_tea5777_init, the /dev/radio0 + * node may be opened from userspace holding a reference to us! + * + * Note we cannot register the leds first instead as + * shark_led_work depends on the v4l2 mutex and registered bit. + */ + retval = led_classdev_register(&intf->dev, &shark->leds[i]); + if (retval) + v4l2_err(&shark->v4l2_dev, + "couldn't register led: %s\n", + shark->led_names[i]); + } + + return 0; + +err_init_tea: + v4l2_device_unregister(&shark->v4l2_dev); +err_reg_dev: + kfree(shark->transfer_buffer); +err_alloc_buffer: + kfree(shark); + + return retval; +} + +/* Specify the bcdDevice value, as the radioSHARK and radioSHARK2 share ids */ +static struct usb_device_id usb_shark_device_table[] = { + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_CLASS, + .idVendor = 0x077d, + .idProduct = 0x627a, + .bcdDevice_lo = 0x0010, + .bcdDevice_hi = 0x0010, + .bInterfaceClass = 3, + }, + { } +}; +MODULE_DEVICE_TABLE(usb, usb_shark_device_table); + +static struct usb_driver usb_shark_driver = { + .name = DRV_NAME, + .probe = usb_shark_probe, + .disconnect = usb_shark_disconnect, + .id_table = usb_shark_device_table, +}; +module_usb_driver(usb_shark_driver); diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c new file mode 100644 index 00000000000..5bc9fa62720 --- /dev/null +++ b/drivers/media/radio/radio-tea5777.c @@ -0,0 +1,491 @@ +/* + * v4l2 driver for TEA5777 Philips AM/FM radio tuner chips + * + * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> + * + * Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips: + * + * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <asm/div64.h> +#include "radio-tea5777.h" + +MODULE_AUTHOR("Hans de Goede <perex@perex.cz>"); +MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips"); +MODULE_LICENSE("GPL"); + +/* Fixed FM only band for now, will implement multi-band support when the + VIDIOC_ENUM_FREQ_BANDS API is upstream */ +#define TEA5777_FM_RANGELOW (76000 * 16) +#define TEA5777_FM_RANGEHIGH (108000 * 16) + +#define TEA5777_FM_IF 150 /* kHz */ +#define TEA5777_FM_FREQ_STEP 50 /* kHz */ + +/* Write reg, common bits */ +#define TEA5777_W_MUTE_MASK (1LL << 47) +#define TEA5777_W_MUTE_SHIFT 47 +#define TEA5777_W_AM_FM_MASK (1LL << 46) +#define TEA5777_W_AM_FM_SHIFT 46 +#define TEA5777_W_STB_MASK (1LL << 45) +#define TEA5777_W_STB_SHIFT 45 + +#define TEA5777_W_IFCE_MASK (1LL << 29) +#define TEA5777_W_IFCE_SHIFT 29 +#define TEA5777_W_IFW_MASK (1LL << 28) +#define TEA5777_W_IFW_SHIFT 28 +#define TEA5777_W_HILO_MASK (1LL << 27) +#define TEA5777_W_HILO_SHIFT 27 +#define TEA5777_W_DBUS_MASK (1LL << 26) +#define TEA5777_W_DBUS_SHIFT 26 + +#define TEA5777_W_INTEXT_MASK (1LL << 24) +#define TEA5777_W_INTEXT_SHIFT 24 +#define TEA5777_W_P1_MASK (1LL << 23) +#define TEA5777_W_P1_SHIFT 23 +#define TEA5777_W_P0_MASK (1LL << 22) +#define TEA5777_W_P0_SHIFT 22 +#define TEA5777_W_PEN1_MASK (1LL << 21) +#define TEA5777_W_PEN1_SHIFT 21 +#define TEA5777_W_PEN0_MASK (1LL << 20) +#define TEA5777_W_PEN0_SHIFT 20 + +#define TEA5777_W_CHP0_MASK (1LL << 18) +#define TEA5777_W_CHP0_SHIFT 18 +#define TEA5777_W_DEEM_MASK (1LL << 17) +#define TEA5777_W_DEEM_SHIFT 17 + +#define TEA5777_W_SEARCH_MASK (1LL << 7) +#define TEA5777_W_SEARCH_SHIFT 7 +#define TEA5777_W_PROGBLIM_MASK (1LL << 6) +#define TEA5777_W_PROGBLIM_SHIFT 6 +#define TEA5777_W_UPDWN_MASK (1LL << 5) +#define TEA5777_W_UPDWN_SHIFT 5 +#define TEA5777_W_SLEV_MASK (3LL << 3) +#define TEA5777_W_SLEV_SHIFT 3 + +/* Write reg, FM specific bits */ +#define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32) +#define TEA5777_W_FM_PLL_SHIFT 32 +#define TEA5777_W_FM_FREF_MASK (0x03LL << 30) +#define TEA5777_W_FM_FREF_SHIFT 30 +#define TEA5777_W_FM_FREF_VALUE 0 /* 50 kHz tune steps, 150 kHz IF */ + +#define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15) +#define TEA5777_W_FM_FORCEMONO_SHIFT 15 +#define TEA5777_W_FM_SDSOFF_MASK (1LL << 14) +#define TEA5777_W_FM_SDSOFF_SHIFT 14 +#define TEA5777_W_FM_DOFF_MASK (1LL << 13) +#define TEA5777_W_FM_DOFF_SHIFT 13 + +#define TEA5777_W_FM_STEP_MASK (3LL << 1) +#define TEA5777_W_FM_STEP_SHIFT 1 + +/* Write reg, AM specific bits */ +#define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34) +#define TEA5777_W_AM_PLL_SHIFT 34 +#define TEA5777_W_AM_AGCRF_MASK (1LL << 33) +#define TEA5777_W_AM_AGCRF_SHIFT 33 +#define TEA5777_W_AM_AGCIF_MASK (1LL << 32) +#define TEA5777_W_AM_AGCIF_SHIFT 32 +#define TEA5777_W_AM_MWLW_MASK (1LL << 31) +#define TEA5777_W_AM_MWLW_SHIFT 31 +#define TEA5777_W_AM_LNA_MASK (1LL << 30) +#define TEA5777_W_AM_LNA_SHIFT 30 + +#define TEA5777_W_AM_PEAK_MASK (1LL << 25) +#define TEA5777_W_AM_PEAK_SHIFT 25 + +#define TEA5777_W_AM_RFB_MASK (1LL << 16) +#define TEA5777_W_AM_RFB_SHIFT 16 +#define TEA5777_W_AM_CALLIGN_MASK (1LL << 15) +#define TEA5777_W_AM_CALLIGN_SHIFT 15 +#define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8) +#define TEA5777_W_AM_CBANK_SHIFT 8 + +#define TEA5777_W_AM_DELAY_MASK (1LL << 2) +#define TEA5777_W_AM_DELAY_SHIFT 2 +#define TEA5777_W_AM_STEP_MASK (1LL << 1) +#define TEA5777_W_AM_STEP_SHIFT 1 + +/* Read reg, common bits */ +#define TEA5777_R_LEVEL_MASK (0x0f << 17) +#define TEA5777_R_LEVEL_SHIFT 17 +#define TEA5777_R_SFOUND_MASK (0x01 << 16) +#define TEA5777_R_SFOUND_SHIFT 16 +#define TEA5777_R_BLIM_MASK (0x01 << 15) +#define TEA5777_R_BLIM_SHIFT 15 + +/* Read reg, FM specific bits */ +#define TEA5777_R_FM_STEREO_MASK (0x01 << 21) +#define TEA5777_R_FM_STEREO_SHIFT 21 +#define TEA5777_R_FM_PLL_MASK 0x1fff +#define TEA5777_R_FM_PLL_SHIFT 0 + +static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq) +{ + return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16; +} + +static int radio_tea5777_set_freq(struct radio_tea5777 *tea) +{ + u64 freq; + int res; + + freq = clamp_t(u32, tea->freq, + TEA5777_FM_RANGELOW, TEA5777_FM_RANGEHIGH) + 8; + do_div(freq, 16); /* to kHz */ + + freq -= TEA5777_FM_IF; + do_div(freq, TEA5777_FM_FREQ_STEP); + + tea->write_reg &= ~(TEA5777_W_FM_PLL_MASK | TEA5777_W_FM_FREF_MASK); + tea->write_reg |= freq << TEA5777_W_FM_PLL_SHIFT; + tea->write_reg |= TEA5777_W_FM_FREF_VALUE << TEA5777_W_FM_FREF_SHIFT; + + res = tea->ops->write_reg(tea, tea->write_reg); + if (res) + return res; + + tea->needs_write = false; + tea->read_reg = -1; + tea->freq = tea5777_freq_to_v4l2_freq(tea, freq); + + return 0; +} + +static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait) +{ + int res; + + if (tea->read_reg != -1) + return 0; + + if (tea->write_before_read && tea->needs_write) { + res = radio_tea5777_set_freq(tea); + if (res) + return res; + } + + if (wait) { + if (schedule_timeout_interruptible(msecs_to_jiffies(wait))) + return -ERESTARTSYS; + } + + res = tea->ops->read_reg(tea, &tea->read_reg); + if (res) + return res; + + tea->needs_write = true; + return 0; +} + +/* + * Linux Video interface + */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct radio_tea5777 *tea = video_drvdata(file); + + strlcpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver)); + strlcpy(v->card, tea->card, sizeof(v->card)); + strlcat(v->card, " TEA5777", sizeof(v->card)); + strlcpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->device_caps |= V4L2_CAP_HW_FREQ_SEEK; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct radio_tea5777 *tea = video_drvdata(file); + int res; + + if (v->index > 0) + return -EINVAL; + + res = radio_tea5777_update_read_reg(tea, 0); + if (res) + return res; + + memset(v, 0, sizeof(*v)); + if (tea->has_am) + strlcpy(v->name, "AM/FM", sizeof(v->name)); + else + strlcpy(v->name, "FM", sizeof(v->name)); + v->type = V4L2_TUNER_RADIO; + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_HWSEEK_BOUNDED; + v->rangelow = TEA5777_FM_RANGELOW; + v->rangehigh = TEA5777_FM_RANGEHIGH; + v->rxsubchans = (tea->read_reg & TEA5777_R_FM_STEREO_MASK) ? + V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + v->audmode = (tea->write_reg & TEA5777_W_FM_FORCEMONO_MASK) ? + V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; + /* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */ + v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >> + (TEA5777_R_LEVEL_SHIFT - 12); + + /* Invalidate read_reg, so that next call we return up2date signal */ + tea->read_reg = -1; + + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct radio_tea5777 *tea = video_drvdata(file); + + if (v->index) + return -EINVAL; + + if (v->audmode == V4L2_TUNER_MODE_MONO) + tea->write_reg |= TEA5777_W_FM_FORCEMONO_MASK; + else + tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK; + + return radio_tea5777_set_freq(tea); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct radio_tea5777 *tea = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = tea->freq; + return 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct radio_tea5777 *tea = video_drvdata(file); + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + tea->freq = f->frequency; + return radio_tea5777_set_freq(tea); +} + +static int vidioc_s_hw_freq_seek(struct file *file, void *fh, + struct v4l2_hw_freq_seek *a) +{ + struct radio_tea5777 *tea = video_drvdata(file); + u32 orig_freq = tea->freq; + unsigned long timeout; + int res, spacing = 200 * 16; /* 200 kHz */ + /* These are fixed *for now* */ + const u32 seek_rangelow = TEA5777_FM_RANGELOW; + const u32 seek_rangehigh = TEA5777_FM_RANGEHIGH; + + if (a->tuner || a->wrap_around) + return -EINVAL; + + tea->write_reg |= TEA5777_W_PROGBLIM_MASK; + if (seek_rangelow != tea->seek_rangelow) { + tea->write_reg &= ~TEA5777_W_UPDWN_MASK; + tea->freq = seek_rangelow; + res = radio_tea5777_set_freq(tea); + if (res) + goto leave; + tea->seek_rangelow = tea->freq; + } + if (seek_rangehigh != tea->seek_rangehigh) { + tea->write_reg |= TEA5777_W_UPDWN_MASK; + tea->freq = seek_rangehigh; + res = radio_tea5777_set_freq(tea); + if (res) + goto leave; + tea->seek_rangehigh = tea->freq; + } + tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; + + tea->write_reg |= TEA5777_W_SEARCH_MASK; + if (a->seek_upward) { + tea->write_reg |= TEA5777_W_UPDWN_MASK; + tea->freq = orig_freq + spacing; + } else { + tea->write_reg &= ~TEA5777_W_UPDWN_MASK; + tea->freq = orig_freq - spacing; + } + res = radio_tea5777_set_freq(tea); + if (res) + goto leave; + + timeout = jiffies + msecs_to_jiffies(5000); + for (;;) { + if (time_after(jiffies, timeout)) { + res = -ENODATA; + break; + } + + res = radio_tea5777_update_read_reg(tea, 100); + if (res) + break; + + /* + * Note we use tea->freq to track how far we've searched sofar + * this is necessary to ensure we continue seeking at the right + * point, in the write_before_read case. + */ + tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK); + tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq); + + if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) { + tea->write_reg &= ~TEA5777_W_SEARCH_MASK; + return 0; + } + + if (tea->read_reg & TEA5777_R_BLIM_MASK) { + res = -ENODATA; + break; + } + + /* Force read_reg update */ + tea->read_reg = -1; + } +leave: + tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK; + tea->write_reg &= ~TEA5777_W_SEARCH_MASK; + tea->freq = orig_freq; + radio_tea5777_set_freq(tea); + return res; +} + +static int tea575x_s_ctrl(struct v4l2_ctrl *c) +{ + struct radio_tea5777 *tea = + container_of(c->handler, struct radio_tea5777, ctrl_handler); + + switch (c->id) { + case V4L2_CID_AUDIO_MUTE: + if (c->val) + tea->write_reg |= TEA5777_W_MUTE_MASK; + else + tea->write_reg &= ~TEA5777_W_MUTE_MASK; + + return radio_tea5777_set_freq(tea); + } + + return -EINVAL; +} + +static const struct v4l2_file_operations tea575x_fops = { + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, +}; + +static const struct v4l2_ioctl_ops tea575x_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device tea575x_radio = { + .ioctl_ops = &tea575x_ioctl_ops, + .release = video_device_release_empty, +}; + +static const struct v4l2_ctrl_ops tea575x_ctrl_ops = { + .s_ctrl = tea575x_s_ctrl, +}; + +int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner) +{ + int res; + + tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) | + (1LL << TEA5777_W_IFW_SHIFT) | + (1LL << TEA5777_W_INTEXT_SHIFT) | + (1LL << TEA5777_W_CHP0_SHIFT) | + (2LL << TEA5777_W_SLEV_SHIFT); + tea->freq = 90500 * 16; /* 90.5Mhz default */ + res = radio_tea5777_set_freq(tea); + if (res) { + v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res); + return res; + } + + tea->vd = tea575x_radio; + video_set_drvdata(&tea->vd, tea); + mutex_init(&tea->mutex); + strlcpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name)); + tea->vd.lock = &tea->mutex; + tea->vd.v4l2_dev = tea->v4l2_dev; + tea->fops = tea575x_fops; + tea->fops.owner = owner; + tea->vd.fops = &tea->fops; + set_bit(V4L2_FL_USE_FH_PRIO, &tea->vd.flags); + + tea->vd.ctrl_handler = &tea->ctrl_handler; + v4l2_ctrl_handler_init(&tea->ctrl_handler, 1); + v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + res = tea->ctrl_handler.error; + if (res) { + v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); + v4l2_ctrl_handler_free(&tea->ctrl_handler); + return res; + } + v4l2_ctrl_handler_setup(&tea->ctrl_handler); + + res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1); + if (res) { + v4l2_err(tea->v4l2_dev, "can't register video device!\n"); + v4l2_ctrl_handler_free(tea->vd.ctrl_handler); + return res; + } + + return 0; +} +EXPORT_SYMBOL_GPL(radio_tea5777_init); + +void radio_tea5777_exit(struct radio_tea5777 *tea) +{ + video_unregister_device(&tea->vd); + v4l2_ctrl_handler_free(tea->vd.ctrl_handler); +} +EXPORT_SYMBOL_GPL(radio_tea5777_exit); diff --git a/drivers/media/radio/radio-tea5777.h b/drivers/media/radio/radio-tea5777.h new file mode 100644 index 00000000000..55cbd78df5e --- /dev/null +++ b/drivers/media/radio/radio-tea5777.h @@ -0,0 +1,87 @@ +#ifndef __RADIO_TEA5777_H +#define __RADIO_TEA5777_H + +/* + * v4l2 driver for TEA5777 Philips AM/FM radio tuner chips + * + * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> + * + * Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips: + * + * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> + +#define TEA575X_FMIF 10700 +#define TEA575X_AMIF 450 + +struct radio_tea5777; + +struct radio_tea5777_ops { + /* + * Write the 6 bytes large write register of the tea5777 + * + * val represents the 6 write registers, with byte 1 from the + * datasheet being the most significant byte (so byte 5 of the u64), + * and byte 6 from the datasheet being the least significant byte. + * + * returns 0 on success. + */ + int (*write_reg)(struct radio_tea5777 *tea, u64 val); + /* + * Read the 3 bytes large read register of the tea5777 + * + * The read value gets returned in val, akin to write_reg, byte 1 from + * the datasheet is stored as the most significant byte (so byte 2 of + * the u32), and byte 3 from the datasheet gets stored as the least + * significant byte (iow byte 0 of the u32). + * + * returns 0 on success. + */ + int (*read_reg)(struct radio_tea5777 *tea, u32 *val); +}; + +struct radio_tea5777 { + struct v4l2_device *v4l2_dev; + struct v4l2_file_operations fops; + struct video_device vd; /* video device */ + bool has_am; /* Device can tune to AM freqs */ + bool write_before_read; /* must write before read quirk */ + bool needs_write; /* for write before read quirk */ + u32 freq; /* current frequency */ + u32 seek_rangelow; /* current hwseek limits */ + u32 seek_rangehigh; + u32 read_reg; + u64 write_reg; + struct mutex mutex; + struct radio_tea5777_ops *ops; + void *private_data; + u8 card[32]; + u8 bus_info[32]; + struct v4l2_ctrl_handler ctrl_handler; +}; + +int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner); +void radio_tea5777_exit(struct radio_tea5777 *tea); + +#endif /* __RADIO_TEA5777_H */ diff --git a/drivers/media/radio/si470x/radio-si470x-common.c b/drivers/media/radio/si470x/radio-si470x-common.c index d485b79222f..9e38132afec 100644 --- a/drivers/media/radio/si470x/radio-si470x-common.c +++ b/drivers/media/radio/si470x/radio-si470x-common.c @@ -4,6 +4,7 @@ * Driver for radios with Silicon Labs Si470x FM Radio Receivers * * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net> + * Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -127,14 +128,6 @@ static unsigned short space = 2; module_param(space, ushort, 0444); MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*"); -/* Bottom of Band (MHz) */ -/* 0: 87.5 - 108 MHz (USA, Europe)*/ -/* 1: 76 - 108 MHz (Japan wide band) */ -/* 2: 76 - 90 MHz (Japan) */ -static unsigned short band = 1; -module_param(band, ushort, 0444); -MODULE_PARM_DESC(band, "Band: 0=87.5..108MHz *1=76..108MHz* 2=76..90MHz"); - /* De-emphasis */ /* 0: 75 us (USA) */ /* 1: 50 us (Europe, Australia, Japan) */ @@ -152,19 +145,66 @@ static unsigned int seek_timeout = 5000; module_param(seek_timeout, uint, 0644); MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*"); - +static const struct v4l2_frequency_band bands[] = { + { + .type = V4L2_TUNER_RADIO, + .index = 0, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP, + .rangelow = 87500 * 16, + .rangehigh = 108000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 1, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP, + .rangelow = 76000 * 16, + .rangehigh = 108000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 2, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP, + .rangelow = 76000 * 16, + .rangehigh = 90000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, +}; /************************************************************************** * Generic Functions **************************************************************************/ /* + * si470x_set_band - set the band + */ +static int si470x_set_band(struct si470x_device *radio, int band) +{ + if (radio->band == band) + return 0; + + radio->band = band; + radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND; + radio->registers[SYSCONFIG2] |= radio->band << 6; + return si470x_set_register(radio, SYSCONFIG2); +} + +/* * si470x_set_chan - set the channel */ static int si470x_set_chan(struct si470x_device *radio, unsigned short chan) { int retval; - unsigned long timeout; bool timed_out = 0; /* start tuning */ @@ -174,26 +214,12 @@ static int si470x_set_chan(struct si470x_device *radio, unsigned short chan) if (retval < 0) goto done; - /* currently I2C driver only uses interrupt way to tune */ - if (radio->stci_enabled) { - INIT_COMPLETION(radio->completion); - - /* wait till tune operation has completed */ - retval = wait_for_completion_timeout(&radio->completion, - msecs_to_jiffies(tune_timeout)); - if (!retval) - timed_out = true; - } else { - /* wait till tune operation has completed */ - timeout = jiffies + msecs_to_jiffies(tune_timeout); - do { - retval = si470x_get_register(radio, STATUSRSSI); - if (retval < 0) - goto stop; - timed_out = time_after(jiffies, timeout); - } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) - && (!timed_out)); - } + /* wait till tune operation has completed */ + INIT_COMPLETION(radio->completion); + retval = wait_for_completion_timeout(&radio->completion, + msecs_to_jiffies(tune_timeout)); + if (!retval) + timed_out = true; if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) dev_warn(&radio->videodev.dev, "tune does not complete\n"); @@ -201,7 +227,6 @@ static int si470x_set_chan(struct si470x_device *radio, unsigned short chan) dev_warn(&radio->videodev.dev, "tune timed out after %u ms\n", tune_timeout); -stop: /* stop tuning */ radio->registers[CHANNEL] &= ~CHANNEL_TUNE; retval = si470x_set_register(radio, CHANNEL); @@ -210,48 +235,39 @@ done: return retval; } - /* - * si470x_get_freq - get the frequency + * si470x_get_step - get channel spacing */ -static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq) +static unsigned int si470x_get_step(struct si470x_device *radio) { - unsigned int spacing, band_bottom; - unsigned short chan; - int retval; - /* Spacing (kHz) */ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) { /* 0: 200 kHz (USA, Australia) */ case 0: - spacing = 0.200 * FREQ_MUL; break; + return 200 * 16; /* 1: 100 kHz (Europe, Japan) */ case 1: - spacing = 0.100 * FREQ_MUL; break; + return 100 * 16; /* 2: 50 kHz */ default: - spacing = 0.050 * FREQ_MUL; break; + return 50 * 16; }; +} - /* Bottom of Band (MHz) */ - switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) { - /* 0: 87.5 - 108 MHz (USA, Europe) */ - case 0: - band_bottom = 87.5 * FREQ_MUL; break; - /* 1: 76 - 108 MHz (Japan wide band) */ - default: - band_bottom = 76 * FREQ_MUL; break; - /* 2: 76 - 90 MHz (Japan) */ - case 2: - band_bottom = 76 * FREQ_MUL; break; - }; + +/* + * si470x_get_freq - get the frequency + */ +static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq) +{ + int chan, retval; /* read channel */ retval = si470x_get_register(radio, READCHAN); chan = radio->registers[READCHAN] & READCHAN_READCHAN; /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */ - *freq = chan * spacing + band_bottom; + *freq = chan * si470x_get_step(radio) + bands[radio->band].rangelow; return retval; } @@ -262,44 +278,12 @@ static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq) */ int si470x_set_freq(struct si470x_device *radio, unsigned int freq) { - unsigned int spacing, band_bottom, band_top; unsigned short chan; - /* Spacing (kHz) */ - switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) { - /* 0: 200 kHz (USA, Australia) */ - case 0: - spacing = 0.200 * FREQ_MUL; break; - /* 1: 100 kHz (Europe, Japan) */ - case 1: - spacing = 0.100 * FREQ_MUL; break; - /* 2: 50 kHz */ - default: - spacing = 0.050 * FREQ_MUL; break; - }; - - /* Bottom/Top of Band (MHz) */ - switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) { - /* 0: 87.5 - 108 MHz (USA, Europe) */ - case 0: - band_bottom = 87.5 * FREQ_MUL; - band_top = 108 * FREQ_MUL; - break; - /* 1: 76 - 108 MHz (Japan wide band) */ - default: - band_bottom = 76 * FREQ_MUL; - band_top = 108 * FREQ_MUL; - break; - /* 2: 76 - 90 MHz (Japan) */ - case 2: - band_bottom = 76 * FREQ_MUL; - band_top = 90 * FREQ_MUL; - break; - }; - - freq = clamp(freq, band_bottom, band_top); + freq = clamp(freq, bands[radio->band].rangelow, + bands[radio->band].rangehigh); /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */ - chan = (freq - band_bottom) / spacing; + chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio); return si470x_set_chan(radio, chan); } @@ -309,19 +293,43 @@ int si470x_set_freq(struct si470x_device *radio, unsigned int freq) * si470x_set_seek - set seek */ static int si470x_set_seek(struct si470x_device *radio, - unsigned int wrap_around, unsigned int seek_upward) + struct v4l2_hw_freq_seek *seek) { - int retval = 0; - unsigned long timeout; + int band, retval; + unsigned int freq; bool timed_out = 0; + /* set band */ + if (seek->rangelow || seek->rangehigh) { + for (band = 0; band < ARRAY_SIZE(bands); band++) { + if (bands[band].rangelow == seek->rangelow && + bands[band].rangehigh == seek->rangehigh) + break; + } + if (band == ARRAY_SIZE(bands)) + return -EINVAL; /* No matching band found */ + } else + band = 1; /* If nothing is specified seek 76 - 108 Mhz */ + + if (radio->band != band) { + retval = si470x_get_freq(radio, &freq); + if (retval) + return retval; + retval = si470x_set_band(radio, band); + if (retval) + return retval; + retval = si470x_set_freq(radio, freq); + if (retval) + return retval; + } + /* start seeking */ radio->registers[POWERCFG] |= POWERCFG_SEEK; - if (wrap_around == 1) + if (seek->wrap_around) radio->registers[POWERCFG] &= ~POWERCFG_SKMODE; else radio->registers[POWERCFG] |= POWERCFG_SKMODE; - if (seek_upward == 1) + if (seek->seek_upward) radio->registers[POWERCFG] |= POWERCFG_SEEKUP; else radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP; @@ -329,26 +337,12 @@ static int si470x_set_seek(struct si470x_device *radio, if (retval < 0) return retval; - /* currently I2C driver only uses interrupt way to seek */ - if (radio->stci_enabled) { - INIT_COMPLETION(radio->completion); - - /* wait till seek operation has completed */ - retval = wait_for_completion_timeout(&radio->completion, - msecs_to_jiffies(seek_timeout)); - if (!retval) - timed_out = true; - } else { - /* wait till seek operation has completed */ - timeout = jiffies + msecs_to_jiffies(seek_timeout); - do { - retval = si470x_get_register(radio, STATUSRSSI); - if (retval < 0) - goto stop; - timed_out = time_after(jiffies, timeout); - } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) - && (!timed_out)); - } + /* wait till tune operation has completed */ + INIT_COMPLETION(radio->completion); + retval = wait_for_completion_timeout(&radio->completion, + msecs_to_jiffies(seek_timeout)); + if (!retval) + timed_out = true; if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) dev_warn(&radio->videodev.dev, "seek does not complete\n"); @@ -356,7 +350,6 @@ static int si470x_set_seek(struct si470x_device *radio, dev_warn(&radio->videodev.dev, "seek failed / band limit reached\n"); -stop: /* stop seeking */ radio->registers[POWERCFG] &= ~POWERCFG_SEEK; retval = si470x_set_register(radio, POWERCFG); @@ -391,8 +384,8 @@ int si470x_start(struct si470x_device *radio) /* sysconfig 2 */ radio->registers[SYSCONFIG2] = - (0x3f << 8) | /* SEEKTH */ - ((band << 6) & SYSCONFIG2_BAND) | /* BAND */ + (0x1f << 8) | /* SEEKTH */ + ((radio->band << 6) & SYSCONFIG2_BAND) |/* BAND */ ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */ 15; /* VOLUME (max) */ retval = si470x_set_register(radio, SYSCONFIG2); @@ -583,14 +576,16 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *tuner) { struct si470x_device *radio = video_drvdata(file); - int retval; + int retval = 0; if (tuner->index != 0) return -EINVAL; - retval = si470x_get_register(radio, STATUSRSSI); - if (retval < 0) - return retval; + if (!radio->status_rssi_auto_update) { + retval = si470x_get_register(radio, STATUSRSSI); + if (retval < 0) + return retval; + } /* driver constants */ strcpy(tuner->name, "FM"); @@ -599,25 +594,8 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv, V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_HWSEEK_BOUNDED | V4L2_TUNER_CAP_HWSEEK_WRAP; - - /* range limits */ - switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) { - /* 0: 87.5 - 108 MHz (USA, Europe, default) */ - default: - tuner->rangelow = 87.5 * FREQ_MUL; - tuner->rangehigh = 108 * FREQ_MUL; - break; - /* 1: 76 - 108 MHz (Japan wide band) */ - case 1: - tuner->rangelow = 76 * FREQ_MUL; - tuner->rangehigh = 108 * FREQ_MUL; - break; - /* 2: 76 - 90 MHz (Japan) */ - case 2: - tuner->rangelow = 76 * FREQ_MUL; - tuner->rangehigh = 90 * FREQ_MUL; - break; - }; + tuner->rangelow = 76 * FREQ_MUL; + tuner->rangehigh = 108 * FREQ_MUL; /* stereo indicator == stereo (instead of mono) */ if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0) @@ -700,10 +678,18 @@ static int si470x_vidioc_s_frequency(struct file *file, void *priv, struct v4l2_frequency *freq) { struct si470x_device *radio = video_drvdata(file); + int retval; if (freq->tuner != 0) return -EINVAL; + if (freq->frequency < bands[radio->band].rangelow || + freq->frequency > bands[radio->band].rangehigh) { + /* Switch to band 1 which covers everything we support */ + retval = si470x_set_band(radio, 1); + if (retval) + return retval; + } return si470x_set_freq(radio, freq->frequency); } @@ -719,7 +705,21 @@ static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv, if (seek->tuner != 0) return -EINVAL; - return si470x_set_seek(radio, seek->wrap_around, seek->seek_upward); + return si470x_set_seek(radio, seek); +} + +/* + * si470x_vidioc_enum_freq_bands - enumerate supported bands + */ +static int si470x_vidioc_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + if (band->tuner != 0) + return -EINVAL; + if (band->index >= ARRAY_SIZE(bands)) + return -EINVAL; + *band = bands[band->index]; + return 0; } const struct v4l2_ctrl_ops si470x_ctrl_ops = { @@ -736,6 +736,7 @@ static const struct v4l2_ioctl_ops si470x_ioctl_ops = { .vidioc_g_frequency = si470x_vidioc_g_frequency, .vidioc_s_frequency = si470x_vidioc_s_frequency, .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek, + .vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; diff --git a/drivers/media/radio/si470x/radio-si470x-i2c.c b/drivers/media/radio/si470x/radio-si470x-i2c.c index a80044c5874..643a6ff7c5d 100644 --- a/drivers/media/radio/si470x/radio-si470x-i2c.c +++ b/drivers/media/radio/si470x/radio-si470x-i2c.c @@ -350,7 +350,9 @@ static int __devinit si470x_i2c_probe(struct i2c_client *client, } radio->client = client; + radio->band = 1; /* Default to 76 - 108 MHz */ mutex_init(&radio->lock); + init_completion(&radio->completion); /* video device initialization */ radio->videodev = si470x_viddev_template; @@ -406,10 +408,6 @@ static int __devinit si470x_i2c_probe(struct i2c_client *client, radio->rd_index = 0; init_waitqueue_head(&radio->read_queue); - /* mark Seek/Tune Complete Interrupt enabled */ - radio->stci_enabled = true; - init_completion(&radio->completion); - retval = request_threaded_irq(client->irq, NULL, si470x_i2c_interrupt, IRQF_TRIGGER_FALLING, DRIVER_NAME, radio); if (retval) { diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c index f412f7ab270..146be4263ea 100644 --- a/drivers/media/radio/si470x/radio-si470x-usb.c +++ b/drivers/media/radio/si470x/radio-si470x-usb.c @@ -143,7 +143,7 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); * Software/Hardware Versions from Scratch Page **************************************************************************/ #define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6 -#define RADIO_SW_VERSION 7 +#define RADIO_SW_VERSION 1 #define RADIO_HW_VERSION 1 @@ -399,12 +399,19 @@ static void si470x_int_in_callback(struct urb *urb) } } - if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) + /* Sometimes the device returns len 0 packets */ + if (urb->actual_length != RDS_REPORT_SIZE) goto resubmit; - if (urb->actual_length > 0) { + radio->registers[STATUSRSSI] = + get_unaligned_be16(&radio->int_in_buffer[1]); + + if (radio->registers[STATUSRSSI] & STATUSRSSI_STC) + complete(&radio->completion); + + if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)) { /* Update RDS registers with URB data */ - for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++) + for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) radio->registers[STATUSRSSI + regnr] = get_unaligned_be16(&radio->int_in_buffer[ regnr * RADIO_REGISTER_SIZE + 1]); @@ -480,6 +487,7 @@ resubmit: radio->int_in_running = 0; } } + radio->status_rssi_auto_update = radio->int_in_running; } @@ -534,13 +542,6 @@ static int si470x_start_usb(struct si470x_device *radio) { int retval; - /* start radio */ - retval = si470x_start(radio); - if (retval < 0) - return retval; - - v4l2_ctrl_handler_setup(&radio->hdl); - /* initialize interrupt urb */ usb_fill_int_urb(radio->int_in_urb, radio->usbdev, usb_rcvintpipe(radio->usbdev, @@ -560,6 +561,15 @@ static int si470x_start_usb(struct si470x_device *radio) "submitting int urb failed (%d)\n", retval); radio->int_in_running = 0; } + radio->status_rssi_auto_update = radio->int_in_running; + + /* start radio */ + retval = si470x_start(radio); + if (retval < 0) + return retval; + + v4l2_ctrl_handler_setup(&radio->hdl); + return retval; } @@ -587,7 +597,9 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, } radio->usbdev = interface_to_usbdev(intf); radio->intf = intf; + radio->band = 1; /* Default to 76 - 108 MHz */ mutex_init(&radio->lock); + init_completion(&radio->completion); iface_desc = intf->cur_altsetting; @@ -698,9 +710,6 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, "linux-media@vger.kernel.org\n"); } - /* set initial frequency */ - si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */ - /* set led to connect state */ si470x_set_led_state(radio, BLINK_GREEN_LED); @@ -723,6 +732,9 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, if (retval < 0) goto err_all; + /* set initial frequency */ + si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */ + /* register video device */ retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, radio_nr); @@ -781,11 +793,16 @@ static int si470x_usb_driver_suspend(struct usb_interface *intf, static int si470x_usb_driver_resume(struct usb_interface *intf) { struct si470x_device *radio = usb_get_intfdata(intf); + int ret; dev_info(&intf->dev, "resuming now...\n"); /* start radio */ - return si470x_start_usb(radio); + ret = si470x_start_usb(radio); + if (ret == 0) + v4l2_ctrl_handler_setup(&radio->hdl); + + return ret; } diff --git a/drivers/media/radio/si470x/radio-si470x.h b/drivers/media/radio/si470x/radio-si470x.h index 4921cab8e0f..2f089b4252d 100644 --- a/drivers/media/radio/si470x/radio-si470x.h +++ b/drivers/media/radio/si470x/radio-si470x.h @@ -87,7 +87,7 @@ #define SYSCONFIG2 5 /* System Configuration 2 */ #define SYSCONFIG2_SEEKTH 0xff00 /* bits 15..08: RSSI Seek Threshold */ -#define SYSCONFIG2_BAND 0x0080 /* bits 07..06: Band Select */ +#define SYSCONFIG2_BAND 0x00c0 /* bits 07..06: Band Select */ #define SYSCONFIG2_SPACE 0x0030 /* bits 05..04: Channel Spacing */ #define SYSCONFIG2_VOLUME 0x000f /* bits 03..00: Volume */ @@ -147,6 +147,7 @@ struct si470x_device { struct v4l2_device v4l2_dev; struct video_device videodev; struct v4l2_ctrl_handler hdl; + int band; /* Silabs internal registers (0..15) */ unsigned short registers[RADIO_REGISTER_NUM]; @@ -160,7 +161,7 @@ struct si470x_device { unsigned int wr_index; struct completion completion; - bool stci_enabled; /* Seek/Tune Complete Interrupt */ + bool status_rssi_auto_update; /* Does RSSI get updated automatic? */ #if defined(CONFIG_USB_SI470X) || defined(CONFIG_USB_SI470X_MODULE) /* reference to USB and video device */ @@ -189,7 +190,7 @@ struct si470x_device { * Firmware Versions **************************************************************************/ -#define RADIO_FW_VERSION 15 +#define RADIO_FW_VERSION 12 |