diff options
Diffstat (limited to 'drivers/media/radio')
49 files changed, 9146 insertions, 6284 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index e954781c90b..192f36f2f4a 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -5,18 +5,45 @@ menuconfig RADIO_ADAPTERS bool "Radio Adapters" depends on VIDEO_V4L2 + depends on MEDIA_RADIO_SUPPORT default y ---help--- Say Y here to enable selecting AM/FM radio adapters. if RADIO_ADAPTERS && VIDEO_V4L2 +config RADIO_TEA575X + tristate + config RADIO_SI470X bool "Silicon Labs Si470x FM Radio Receiver support" depends on VIDEO_V4L2 source "drivers/media/radio/si470x/Kconfig" +config RADIO_SI4713 + tristate "Silicon Labs Si4713 FM Radio with RDS Transmitter support" + depends on VIDEO_V4L2 + +source "drivers/media/radio/si4713/Kconfig" + +config RADIO_SI476X + tristate "Silicon Laboratories Si476x I2C FM Radio" + depends on I2C && VIDEO_V4L2 + depends on MFD_SI476X_CORE + depends on SND_SOC + select SND_SOC_SI476X + ---help--- + Choose Y here if you have this FM radio chip. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux 2 API. Information on + this API and pointers to "v4l2" 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-si476x. + config USB_MR800 tristate "AverMedia MR 800 USB FM radio support" depends on USB && VIDEO_V4L2 @@ -44,6 +71,7 @@ config USB_DSBR config RADIO_MAXIRADIO tristate "Guillemot MAXI Radio FM 2000 radio" depends on VIDEO_V4L2 && PCI + select RADIO_TEA575X ---help--- Choose Y here if you have this radio card. This card may also be found as Gemtek PCI FM. @@ -56,29 +84,76 @@ 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 + select RADIO_TEA575X + ---help--- + Choose Y here if you have this radio receiver. -config I2C_SI4713 - tristate "I2C driver for Silicon Labs Si4713 device" - depends on I2C && VIDEO_V4L2 + 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 USB_KEENE + tristate "Keene FM Transmitter USB support" + depends on USB && VIDEO_V4L2 ---help--- - Say Y here if you want support to Si4713 I2C device. - This device driver supports only i2c bus. + Say Y here if you want to connect this type of FM transmitter + to your computer's USB port. To compile this driver as a module, choose M here: the - module will be called si4713. + module will be called radio-keene. -config RADIO_SI4713 - tristate "Silicon Labs Si4713 FM Radio Transmitter support" - depends on I2C && VIDEO_V4L2 - select I2C_SI4713 +config USB_RAREMONO + tristate "Thanko's Raremono AM/FM/SW radio support" + depends on USB && VIDEO_V4L2 ---help--- - Say Y here if you want support to Si4713 FM Radio Transmitter. - This device can transmit audio through FM. It can transmit - RDS and RBDS signals as well. This module is the v4l2 radio - interface for the i2c driver of this device. + The 'Thanko's Raremono' device contains the Si4734 chip from Silicon Labs Inc. + It is one of the very few or perhaps the only consumer USB radio device + to receive the AM/FM/SW bands. + + Say Y here if you want to connect this type of AM/FM/SW receiver + to your computer's USB port. To compile this driver as a module, choose M here: the - module will be called radio-si4713. + module will be called radio-raremono. + +config USB_MA901 + tristate "Masterkit MA901 USB FM radio support" + depends on USB && VIDEO_V4L2 + ---help--- + Say Y here if you want to connect this type of radio to your + computer's USB port. Note that the audio is not digital, and + you must connect the line out connector to a sound card or a + set of speakers or headphones. + + To compile this driver as a module, choose M here: the + module will be called radio-ma901. config RADIO_TEA5764 tristate "TEA5764 I2C FM radio support" @@ -167,6 +242,10 @@ menuconfig V4L_RADIO_ISA_DRIVERS if V4L_RADIO_ISA_DRIVERS +config RADIO_ISA + depends on ISA + tristate + config RADIO_CADET tristate "ADS Cadet AM/FM Tuner" depends on ISA && VIDEO_V4L2 @@ -174,20 +253,13 @@ config RADIO_CADET Choose Y here if you have one of these AM/FM radio cards, and then fill in the port address below. - 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>. - - Further documentation on this driver can be found on the WWW at - <http://linux.blackhawke.net/cadet/>. - To compile this driver as a module, choose M here: the module will be called radio-cadet. config RADIO_RTRACK tristate "AIMSlab RadioTrack (aka RadioReveal) support" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address below. @@ -201,11 +273,7 @@ config RADIO_RTRACK You must also pass the module a suitable io parameter, 0x248 has been reported to be used by these cards. - 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>. More information is - contained in the file + More information is contained in the file <file:Documentation/video4linux/radiotrack.txt>. To compile this driver as a module, choose M here: the @@ -214,7 +282,7 @@ config RADIO_RTRACK config RADIO_RTRACK_PORT hex "RadioTrack i/o port (0x20f or 0x30f)" depends on RADIO_RTRACK=y - default "20f" + default "30f" help Enter either 0x30f or 0x20f here. The card default is 0x30f, if you haven't changed the jumper setting on the card. @@ -222,14 +290,14 @@ config RADIO_RTRACK_PORT config RADIO_RTRACK2 tristate "AIMSlab RadioTrack II support" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have this FM radio card, and then fill in the port address below. - 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>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-rtrack2. @@ -245,15 +313,11 @@ config RADIO_RTRACK2_PORT config RADIO_AZTECH tristate "Aztech/Packard Bell Radio" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address below. - 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-aztech. @@ -269,6 +333,7 @@ config RADIO_AZTECH_PORT config RADIO_GEMTEK tristate "GemTek Radio card (or compatible) support" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have this FM radio card, and then fill in the I/O port address and settings below. The following cards either have @@ -278,23 +343,21 @@ config RADIO_GEMTEK - Typhoon Radio card (some models) - Hama Radio card - 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-gemtek. config RADIO_GEMTEK_PORT - hex "Fixed I/O port (0x20c, 0x30c, 0x24c, 0x34c, 0c24c or 0x28c)" + hex "Fixed I/O port (0x20c, 0x30c, 0x24c, 0x34c, 0x248 or 0x28c)" depends on RADIO_GEMTEK=y default "34c" help - Enter either 0x20c, 0x30c, 0x24c or 0x34c here. The card default is - 0x34c, if you haven't changed the jumper setting on the card. On - Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O + Enter either 0x20c, 0x30c, 0x24c, 0x34c, 0x248 or 0x28c here. The + card default is 0x34c, if you haven't changed the jumper setting + on the card. + + On Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O port is 0x20c, 0x248 or 0x28c. + If automatic I/O port probing is enabled this port will be used only in case of automatic probing failure, ie. as a fallback. @@ -318,78 +381,54 @@ config RADIO_MIROPCM20 sound card driver "Miro miroSOUND PCM1pro/PCM12/PCM20radio" as this is required for the radio-miropcm20. - 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-miropcm20. config RADIO_SF16FMI - tristate "SF16-FMI/SF16-FMP Radio" + tristate "SF16-FMI/SF16-FMP/SF16-FMD Radio" depends on ISA && VIDEO_V4L2 ---help--- Choose Y here if you have one of these FM radio cards. - 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-sf16fmi. config RADIO_SF16FMR2 - tristate "SF16FMR2 Radio" - depends on ISA && VIDEO_V4L2 && SND + tristate "SF16-FMR2/SF16-FMD2 Radio" + depends on ISA && VIDEO_V4L2 + select RADIO_TEA575X ---help--- Choose Y here if you have one of these FM radio cards. - 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 on the WWW at - <http://roadrunner.swansea.uk.linux.org/v4l.shtml>. - To compile this driver as a module, choose M here: the module will be called radio-sf16fmr2. config RADIO_TERRATEC tristate "TerraTec ActiveRadio ISA Standalone" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- - Choose Y here if you have this FM radio card, and then fill in the - port address below. (TODO) + Choose Y here if you have this FM radio card. - Note: This driver is in its early stages. Right now volume and - frequency control and muting works at least for me, but - unfortunately I have not found anybody who wants to use this card - with Linux. So if it is this what YOU are trying to do right now, - PLEASE DROP ME A NOTE!! Rolf Offermanns <rolf@offermanns.de>. - - 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>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-terratec. -config RADIO_TERRATEC_PORT - hex "Terratec i/o port (normally 0x590)" - depends on RADIO_TERRATEC=y - default "590" - help - Fill in the I/O port of your TerraTec FM radio card. If unsure, go - with the default. - config RADIO_TRUST tristate "Trust FM radio card" depends on ISA && VIDEO_V4L2 + select RADIO_ISA help This is a driver for the Trust FM radio cards. Say Y if you have such a card and want to use it under Linux. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. + To compile this driver as a module, choose M here: the module will be called radio-trust. @@ -404,14 +443,14 @@ config RADIO_TRUST_PORT config RADIO_TYPHOON tristate "Typhoon Radio (a.k.a. EcoRadio)" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address and the frequency used for muting below. - 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>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-typhoon. @@ -438,14 +477,14 @@ config RADIO_TYPHOON_MUTEFREQ config RADIO_ZOLTRIX tristate "Zoltrix Radio" depends on ISA && VIDEO_V4L2 + select RADIO_ISA ---help--- Choose Y here if you have one of these FM radio cards, and then fill in the port address below. - 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>. + Note: this driver hasn't been tested since a long time due to lack + of hardware. If you have this hardware, then please contact the + linux-media mailinglist. To compile this driver as a module, choose M here: the module will be called radio-zoltrix. diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 390daf94d84..120e791199b 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -2,6 +2,7 @@ # Makefile for the kernel character device drivers. # +obj-$(CONFIG_RADIO_ISA) += radio-isa.o obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o @@ -10,21 +11,29 @@ 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 obj-$(CONFIG_RADIO_TRUST) += radio-trust.o -obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o -obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o +obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ +obj-$(CONFIG_RADIO_SI4713) += si4713/ obj-$(CONFIG_USB_MR800) += radio-mr800.o +obj-$(CONFIG_USB_KEENE) += radio-keene.o +obj-$(CONFIG_USB_MA901) += radio-ma901.o obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o obj-$(CONFIG_RADIO_TEF6862) += tef6862.o obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o obj-$(CONFIG_RADIO_WL128X) += wl128x/ +obj-$(CONFIG_RADIO_TEA575X) += tea575x.o +obj-$(CONFIG_USB_RAREMONO) += radio-raremono.o + +shark2-objs := radio-shark2.o radio-tea5777.o ccflags-y += -Isound diff --git a/drivers/media/radio/dsbr100.c b/drivers/media/radio/dsbr100.c index f36905b6364..142c2ee64d3 100644 --- a/drivers/media/radio/dsbr100.c +++ b/drivers/media/radio/dsbr100.c @@ -1,92 +1,37 @@ /* A driver for the D-Link DSB-R100 USB radio and Gemtek USB Radio 21. - The device plugs into both the USB and an analog audio input, so this thing - only deals with initialisation and frequency setting, the - audio data has to be handled by a sound driver. - - Major issue: I can't find out where the device reports the signal - strength, and indeed the windows software appearantly just looks - at the stereo indicator as well. So, scanning will only find - stereo stations. Sad, but I can't help it. - - Also, the windows program sends oodles of messages over to the - device, and I couldn't figure out their meaning. My suspicion - is that they don't have any:-) - - You might find some interesting stuff about this module at - http://unimut.fsk.uni-heidelberg.de/unimut/demi/dsbr - - Copyright (c) 2000 Markus Demleitner <msdemlei@cl.uni-heidelberg.de> - - 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 - - History: - - Version 0.46: - Removed usb_dsbr100_open/close calls and radio->users counter. Also, - radio->muted changed to radio->status and suspend/resume calls updated. - - Version 0.45: - Converted to v4l2_device. - - Version 0.44: - Add suspend/resume functions, fix unplug of device, - a lot of cleanups and fixes by Alexey Klimov <klimov.linux@gmail.com> - - Version 0.43: - Oliver Neukum: avoided DMA coherency issue - - Version 0.42: - Converted dsbr100 to use video_ioctl2 - by Douglas Landgraf <dougsland@gmail.com> - - Version 0.41-ac1: - Alan Cox: Some cleanups and fixes - - Version 0.41: - Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> - - Version 0.40: - Markus: Updates for 2.6.x kernels, code layout changes, name sanitizing - - Version 0.30: - Markus: Updates for 2.5.x kernel and more ISO compliant source - - Version 0.25: - PSL and Markus: Cleanup, radio now doesn't stop on device close - - Version 0.24: - Markus: Hope I got these silly VIDEO_TUNER_LOW issues finally - right. Some minor cleanup, improved standalone compilation - - Version 0.23: - Markus: Sign extension bug fixed by declaring transfer_buffer unsigned - - Version 0.22: - Markus: Some (brown bag) cleanup in what VIDIOCSTUNER returns, - thanks to Mike Cox for pointing the problem out. - - Version 0.21: - Markus: Minor cleanup, warnings if something goes wrong, lame attempt - to adhere to Documentation/CodingStyle - - Version 0.2: - Brad Hards <bradh@dynamite.com.au>: Fixes to make it work as non-module - Markus: Copyright clarification - - Version 0.01: Markus: initial release - + * The device plugs into both the USB and an analog audio input, so this thing + * only deals with initialisation and frequency setting, the + * audio data has to be handled by a sound driver. + * + * Major issue: I can't find out where the device reports the signal + * strength, and indeed the windows software appearantly just looks + * at the stereo indicator as well. So, scanning will only find + * stereo stations. Sad, but I can't help it. + * + * Also, the windows program sends oodles of messages over to the + * device, and I couldn't figure out their meaning. My suspicion + * is that they don't have any:-) + * + * You might find some interesting stuff about this module at + * http://unimut.fsk.uni-heidelberg.de/unimut/demi/dsbr + * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. + * + * Copyright (c) 2000 Markus Demleitner <msdemlei@cl.uni-heidelberg.de> + * + * 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/kernel.h> @@ -95,17 +40,19 @@ #include <linux/slab.h> #include <linux/input.h> #include <linux/videodev2.h> +#include <linux/usb.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> -#include <linux/usb.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> /* * Version Information */ -#define DRIVER_VERSION "0.4.7" - -#define DRIVER_AUTHOR "Markus Demleitner <msdemlei@tucana.harvard.edu>" -#define DRIVER_DESC "D-Link DSB-R100 USB FM radio driver" +MODULE_AUTHOR("Markus Demleitner <msdemlei@tucana.harvard.edu>"); +MODULE_DESCRIPTION("D-Link DSB-R100 USB FM radio driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.1.0"); #define DSB100_VENDOR 0x04b4 #define DSB100_PRODUCT 0x1002 @@ -122,19 +69,8 @@ devices, that would be 76 and 91. */ #define FREQ_MAX 108.0 #define FREQ_MUL 16000 -/* defines for radio->status */ -#define STARTED 0 -#define STOPPED 1 - #define v4l2_dev_to_radio(d) container_of(d, struct dsbr100_device, v4l2_dev) -static int usb_dsbr100_probe(struct usb_interface *intf, - const struct usb_device_id *id); -static void usb_dsbr100_disconnect(struct usb_interface *intf); -static int usb_dsbr100_suspend(struct usb_interface *intf, - pm_message_t message); -static int usb_dsbr100_resume(struct usb_interface *intf); - static int radio_nr = -1; module_param(radio_nr, int, 0); @@ -143,179 +79,92 @@ struct dsbr100_device { struct usb_device *usbdev; struct video_device videodev; struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; u8 *transfer_buffer; struct mutex v4l2_lock; int curfreq; - int stereo; - int status; -}; - -static struct usb_device_id usb_dsbr100_device_table [] = { - { USB_DEVICE(DSB100_VENDOR, DSB100_PRODUCT) }, - { } /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE (usb, usb_dsbr100_device_table); - -/* USB subsystem interface */ -static struct usb_driver usb_dsbr100_driver = { - .name = "dsbr100", - .probe = usb_dsbr100_probe, - .disconnect = usb_dsbr100_disconnect, - .id_table = usb_dsbr100_device_table, - .suspend = usb_dsbr100_suspend, - .resume = usb_dsbr100_resume, - .reset_resume = usb_dsbr100_resume, - .supports_autosuspend = 0, + bool stereo; + bool muted; }; /* Low-level device interface begins here */ -/* switch on radio */ -static int dsbr100_start(struct dsbr100_device *radio) +/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ +static int dsbr100_setfreq(struct dsbr100_device *radio, unsigned freq) { - int retval; - int request; - - retval = usb_control_msg(radio->usbdev, - usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00, 0xC7, radio->transfer_buffer, 8, 300); - - if (retval < 0) { - request = USB_REQ_GET_STATUS; - goto usb_control_msg_failed; + unsigned f = (freq / 16 * 80) / 1000 + 856; + int retval = 0; + + if (!radio->muted) { + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_TUNE, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + (f >> 8) & 0x00ff, f & 0xff, + radio->transfer_buffer, 8, 300); + if (retval >= 0) + mdelay(1); } - retval = usb_control_msg(radio->usbdev, - usb_rcvctrlpipe(radio->usbdev, 0), - DSB100_ONOFF, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x01, 0x00, radio->transfer_buffer, 8, 300); - - if (retval < 0) { - request = DSB100_ONOFF; - goto usb_control_msg_failed; + if (retval >= 0) { + radio->curfreq = freq; + return 0; } - - radio->status = STARTED; - return (radio->transfer_buffer)[0]; - -usb_control_msg_failed: dev_err(&radio->usbdev->dev, "%s - usb_control_msg returned %i, request %i\n", - __func__, retval, request); + __func__, retval, DSB100_TUNE); return retval; - } -/* switch off radio */ -static int dsbr100_stop(struct dsbr100_device *radio) +/* switch on radio */ +static int dsbr100_start(struct dsbr100_device *radio) { - int retval; - int request; - - retval = usb_control_msg(radio->usbdev, - usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x16, 0x1C, radio->transfer_buffer, 8, 300); - - if (retval < 0) { - request = USB_REQ_GET_STATUS; - goto usb_control_msg_failed; - } - - retval = usb_control_msg(radio->usbdev, + int retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), DSB100_ONOFF, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00, 0x00, radio->transfer_buffer, 8, 300); - - if (retval < 0) { - request = DSB100_ONOFF; - goto usb_control_msg_failed; - } - - radio->status = STOPPED; - return (radio->transfer_buffer)[0]; + 0x01, 0x00, radio->transfer_buffer, 8, 300); -usb_control_msg_failed: + if (retval >= 0) + return dsbr100_setfreq(radio, radio->curfreq); dev_err(&radio->usbdev->dev, "%s - usb_control_msg returned %i, request %i\n", - __func__, retval, request); + __func__, retval, DSB100_ONOFF); return retval; } -/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ -static int dsbr100_setfreq(struct dsbr100_device *radio) +/* switch off radio */ +static int dsbr100_stop(struct dsbr100_device *radio) { - int retval; - int request; - int freq = (radio->curfreq / 16 * 80) / 1000 + 856; - - retval = usb_control_msg(radio->usbdev, - usb_rcvctrlpipe(radio->usbdev, 0), - DSB100_TUNE, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - (freq >> 8) & 0x00ff, freq & 0xff, - radio->transfer_buffer, 8, 300); - - if (retval < 0) { - request = DSB100_TUNE; - goto usb_control_msg_failed; - } - - retval = usb_control_msg(radio->usbdev, + int retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, + DSB100_ONOFF, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x96, 0xB7, radio->transfer_buffer, 8, 300); - - if (retval < 0) { - request = USB_REQ_GET_STATUS; - goto usb_control_msg_failed; - } - - retval = usb_control_msg(radio->usbdev, - usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00, 0x24, radio->transfer_buffer, 8, 300); - - if (retval < 0) { - request = USB_REQ_GET_STATUS; - goto usb_control_msg_failed; - } - - radio->stereo = !((radio->transfer_buffer)[0] & 0x01); - return (radio->transfer_buffer)[0]; + 0x00, 0x00, radio->transfer_buffer, 8, 300); -usb_control_msg_failed: - radio->stereo = -1; + if (retval >= 0) + return 0; dev_err(&radio->usbdev->dev, "%s - usb_control_msg returned %i, request %i\n", - __func__, retval, request); + __func__, retval, DSB100_ONOFF); return retval; + } /* return the device status. This is, in effect, just whether it sees a stereo signal or not. Pity. */ static void dsbr100_getstat(struct dsbr100_device *radio) { - int retval; - - retval = usb_control_msg(radio->usbdev, + int retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), USB_REQ_GET_STATUS, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00 , 0x24, radio->transfer_buffer, 8, 300); + 0x00, 0x24, radio->transfer_buffer, 8, 300); if (retval < 0) { - radio->stereo = -1; + radio->stereo = false; dev_err(&radio->usbdev->dev, "%s - usb_control_msg returned %i, request %i\n", __func__, retval, USB_REQ_GET_STATUS); @@ -332,7 +181,8 @@ static int vidioc_querycap(struct file *file, void *priv, strlcpy(v->driver, "dsbr100", sizeof(v->driver)); strlcpy(v->card, "D-Link R-100 USB FM Radio", sizeof(v->card)); usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER; + v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -349,34 +199,30 @@ static int vidioc_g_tuner(struct file *file, void *priv, v->type = V4L2_TUNER_RADIO; v->rangelow = FREQ_MIN * FREQ_MUL; v->rangehigh = FREQ_MAX * FREQ_MUL; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if(radio->stereo) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xffff; /* We can't get the signal strength */ + v->rxsubchans = radio->stereo ? V4L2_TUNER_SUB_STEREO : + V4L2_TUNER_SUB_MONO; + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; + v->audmode = V4L2_TUNER_MODE_STEREO; + v->signal = radio->stereo ? 0xffff : 0; /* We can't get the signal strength */ return 0; } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { return v->index ? -EINVAL : 0; } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct dsbr100_device *radio = video_drvdata(file); - int retval; - radio->curfreq = f->frequency; + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; - retval = dsbr100_setfreq(radio); - if (retval < 0) - dev_warn(&radio->usbdev->dev, "Set frequency failed\n"); - return 0; + return dsbr100_setfreq(radio, clamp_t(unsigned, f->frequency, + FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL)); } static int vidioc_g_frequency(struct file *file, void *priv, @@ -384,90 +230,26 @@ static int vidioc_g_frequency(struct file *file, void *priv, { struct dsbr100_device *radio = video_drvdata(file); + if (f->tuner) + return -EINVAL; f->type = V4L2_TUNER_RADIO; f->frequency = radio->curfreq; return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } - - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int usb_dsbr100_s_ctrl(struct v4l2_ctrl *ctrl) { - struct dsbr100_device *radio = video_drvdata(file); + struct dsbr100_device *radio = + container_of(ctrl->handler, struct dsbr100_device, hdl); switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: - ctrl->value = radio->status; - return 0; + radio->muted = ctrl->val; + return radio->muted ? dsbr100_stop(radio) : dsbr100_start(radio); } return -EINVAL; } -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct dsbr100_device *radio = video_drvdata(file); - int retval; - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) { - retval = dsbr100_stop(radio); - if (retval < 0) { - dev_warn(&radio->usbdev->dev, - "Radio did not respond properly\n"); - return -EBUSY; - } - } else { - retval = dsbr100_start(radio); - if (retval < 0) { - dev_warn(&radio->usbdev->dev, - "Radio did not respond properly\n"); - return -EBUSY; - } - } - return 0; - } - return -EINVAL; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - if (a->index > 1) - return -EINVAL; - - strcpy(a->name, "Radio"); - a->capability = V4L2_AUDCAP_STEREO; - 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_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} /* USB subsystem interface begins here */ @@ -481,8 +263,17 @@ static void usb_dsbr100_disconnect(struct usb_interface *intf) { struct dsbr100_device *radio = usb_get_intfdata(intf); - v4l2_device_get(&radio->v4l2_dev); mutex_lock(&radio->v4l2_lock); + /* + * Disconnect is also called on unload, and in that case we need to + * mute the device. This call will silently fail if it is called + * after a physical disconnect. + */ + usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_ONOFF, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00, 0x00, radio->transfer_buffer, 8, 300); usb_set_intfdata(intf, NULL); video_unregister_device(&radio->videodev); v4l2_device_disconnect(&radio->v4l2_dev); @@ -495,25 +286,13 @@ static void usb_dsbr100_disconnect(struct usb_interface *intf) static int usb_dsbr100_suspend(struct usb_interface *intf, pm_message_t message) { struct dsbr100_device *radio = usb_get_intfdata(intf); - int retval; mutex_lock(&radio->v4l2_lock); - if (radio->status == STARTED) { - retval = dsbr100_stop(radio); - if (retval < 0) - dev_warn(&intf->dev, "dsbr100_stop failed\n"); - - /* After dsbr100_stop() status set to STOPPED. - * If we want driver to start radio on resume - * we set status equal to STARTED. - * On resume we will check status and run radio if needed. - */ - radio->status = STARTED; - } + if (!radio->muted && dsbr100_stop(radio) < 0) + dev_warn(&intf->dev, "dsbr100_stop failed\n"); mutex_unlock(&radio->v4l2_lock); dev_info(&intf->dev, "going into suspend..\n"); - return 0; } @@ -521,18 +300,13 @@ static int usb_dsbr100_suspend(struct usb_interface *intf, pm_message_t message) static int usb_dsbr100_resume(struct usb_interface *intf) { struct dsbr100_device *radio = usb_get_intfdata(intf); - int retval; mutex_lock(&radio->v4l2_lock); - if (radio->status == STARTED) { - retval = dsbr100_start(radio); - if (retval < 0) - dev_warn(&intf->dev, "dsbr100_start failed\n"); - } + if (!radio->muted && dsbr100_start(radio) < 0) + dev_warn(&intf->dev, "dsbr100_start failed\n"); mutex_unlock(&radio->v4l2_lock); dev_info(&intf->dev, "coming out of suspend..\n"); - return 0; } @@ -541,15 +315,23 @@ static void usb_dsbr100_release(struct v4l2_device *v4l2_dev) { struct dsbr100_device *radio = v4l2_dev_to_radio(v4l2_dev); + v4l2_ctrl_handler_free(&radio->hdl); v4l2_device_unregister(&radio->v4l2_dev); kfree(radio->transfer_buffer); kfree(radio); } +static const struct v4l2_ctrl_ops usb_dsbr100_ctrl_ops = { + .s_ctrl = usb_dsbr100_s_ctrl, +}; + /* File system interface */ static const struct v4l2_file_operations usb_dsbr100_fops = { .owner = THIS_MODULE, .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, }; static const struct v4l2_ioctl_ops usb_dsbr100_ioctl_ops = { @@ -558,13 +340,9 @@ static const struct v4l2_ioctl_ops usb_dsbr100_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_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* check if the device is present and register with v4l and usb if it is */ @@ -593,11 +371,17 @@ static int usb_dsbr100_probe(struct usb_interface *intf, retval = v4l2_device_register(&intf->dev, v4l2_dev); if (retval < 0) { v4l2_err(v4l2_dev, "couldn't register v4l2_device\n"); - kfree(radio->transfer_buffer); - kfree(radio); - return retval; + goto err_reg_dev; } + v4l2_ctrl_handler_init(&radio->hdl, 1); + v4l2_ctrl_new_std(&radio->hdl, &usb_dsbr100_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + if (radio->hdl.error) { + retval = radio->hdl.error; + v4l2_err(v4l2_dev, "couldn't register control\n"); + goto err_reg_ctrl; + } mutex_init(&radio->v4l2_lock); strlcpy(radio->videodev.name, v4l2_dev->name, sizeof(radio->videodev.name)); radio->videodev.v4l2_dev = v4l2_dev; @@ -605,28 +389,46 @@ static int usb_dsbr100_probe(struct usb_interface *intf, radio->videodev.ioctl_ops = &usb_dsbr100_ioctl_ops; radio->videodev.release = video_device_release_empty; radio->videodev.lock = &radio->v4l2_lock; + radio->videodev.ctrl_handler = &radio->hdl; + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); radio->usbdev = interface_to_usbdev(intf); radio->curfreq = FREQ_MIN * FREQ_MUL; - radio->status = STOPPED; + radio->muted = true; video_set_drvdata(&radio->videodev, radio); + usb_set_intfdata(intf, radio); retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, radio_nr); - if (retval < 0) { - v4l2_err(v4l2_dev, "couldn't register video device\n"); - v4l2_device_unregister(v4l2_dev); - kfree(radio->transfer_buffer); - kfree(radio); - return -EIO; - } - usb_set_intfdata(intf, radio); - return 0; + if (retval == 0) + return 0; + v4l2_err(v4l2_dev, "couldn't register video device\n"); + +err_reg_ctrl: + v4l2_ctrl_handler_free(&radio->hdl); + v4l2_device_unregister(v4l2_dev); +err_reg_dev: + kfree(radio->transfer_buffer); + kfree(radio); + return retval; } -module_usb_driver(usb_dsbr100_driver); +static struct usb_device_id usb_dsbr100_device_table[] = { + { USB_DEVICE(DSB100_VENDOR, DSB100_PRODUCT) }, + { } /* Terminating entry */ +}; -MODULE_AUTHOR( DRIVER_AUTHOR ); -MODULE_DESCRIPTION( DRIVER_DESC ); -MODULE_LICENSE("GPL"); -MODULE_VERSION(DRIVER_VERSION); +MODULE_DEVICE_TABLE(usb, usb_dsbr100_device_table); + +/* USB subsystem interface */ +static struct usb_driver usb_dsbr100_driver = { + .name = "dsbr100", + .probe = usb_dsbr100_probe, + .disconnect = usb_dsbr100_disconnect, + .id_table = usb_dsbr100_device_table, + .suspend = usb_dsbr100_suspend, + .resume = usb_dsbr100_resume, + .reset_resume = usb_dsbr100_resume, +}; + +module_usb_driver(usb_dsbr100_driver); diff --git a/drivers/media/radio/lm7000.h b/drivers/media/radio/lm7000.h new file mode 100644 index 00000000000..139cd6b6882 --- /dev/null +++ b/drivers/media/radio/lm7000.h @@ -0,0 +1,43 @@ +#ifndef __LM7000_H +#define __LM7000_H + +/* Sanyo LM7000 tuner chip control + * + * Copyright 2012 Ondrej Zary <linux@rainbow-software.org> + * based on radio-aimslab.c by M. Kirkwood + * and radio-sf16fmi.c by M. Kirkwood and Petr Vandrovec + */ + +#define LM7000_DATA (1 << 0) +#define LM7000_CLK (1 << 1) +#define LM7000_CE (1 << 2) + +#define LM7000_FM_100 (0 << 20) +#define LM7000_FM_50 (1 << 20) +#define LM7000_FM_25 (2 << 20) +#define LM7000_BIT_FM (1 << 23) + +static inline void lm7000_set_freq(u32 freq, void *handle, + void (*set_pins)(void *handle, u8 pins)) +{ + int i; + u8 data; + u32 val; + + freq += 171200; /* Add 10.7 MHz IF */ + freq /= 400; /* Convert to 25 kHz units */ + val = freq | LM7000_FM_25 | LM7000_BIT_FM; + /* write the 24-bit register, starting with LSB */ + for (i = 0; i < 24; i++) { + data = val & (1 << i) ? LM7000_DATA : 0; + set_pins(handle, data | LM7000_CE); + udelay(2); + set_pins(handle, data | LM7000_CE | LM7000_CLK); + udelay(2); + set_pins(handle, data | LM7000_CE); + udelay(2); + } + set_pins(handle, 0); +} + +#endif /* __LM7000_H */ diff --git a/drivers/media/radio/radio-aimslab.c b/drivers/media/radio/radio-aimslab.c index 1c3f8440a55..a739ad492e7 100644 --- a/drivers/media/radio/radio-aimslab.c +++ b/drivers/media/radio/radio-aimslab.c @@ -1,16 +1,13 @@ -/* radiotrack (radioreveal) driver for Linux radio support - * (c) 1997 M. Kirkwood +/* + * AimsLab RadioTrack (aka RadioVeveal) driver + * + * Copyright 1997 M. Kirkwood + * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> * - * History: - * 1999-02-24 Russell Kroll <rkroll@exploits.org> - * Fine tuning/VIDEO_TUNER_LOW - * Frequency range expanded to start at 87 MHz - * - * TODO: Allow for more than one of these foolish entities :-) - * * Notes on the hardware (reverse engineered from other peoples' * reverse engineering of AIMS' code :-) * @@ -26,6 +23,7 @@ * wait(a_wee_while); * out(port, stop_changing_the_volume); * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -34,401 +32,163 @@ #include <linux/delay.h> /* msleep */ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include "radio-isa.h" +#include "lm7000.h" -MODULE_AUTHOR("M.Kirkwood"); +MODULE_AUTHOR("M. Kirkwood"); MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("1.0.0"); #ifndef CONFIG_RADIO_RTRACK_PORT #define CONFIG_RADIO_RTRACK_PORT -1 #endif -static int io = CONFIG_RADIO_RTRACK_PORT; -static int radio_nr = -1; +#define RTRACK_MAX 2 -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)"); -module_param(radio_nr, int, 0); +static int io[RTRACK_MAX] = { [0] = CONFIG_RADIO_RTRACK_PORT, + [1 ... (RTRACK_MAX - 1)] = -1 }; +static int radio_nr[RTRACK_MAX] = { [0 ... (RTRACK_MAX - 1)] = -1 }; -struct rtrack -{ - struct v4l2_device v4l2_dev; - struct video_device vdev; - int port; +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + +struct rtrack { + struct radio_isa_card isa; int curvol; - unsigned long curfreq; - int muted; - int io; - struct mutex lock; }; -static struct rtrack rtrack_card; - -/* local things */ - -static void rt_decvol(struct rtrack *rt) +static struct radio_isa_card *rtrack_alloc(void) { - outb(0x58, rt->io); /* volume down + sigstr + on */ - msleep(100); - outb(0xd8, rt->io); /* volume steady + sigstr + on */ -} + struct rtrack *rt = kzalloc(sizeof(struct rtrack), GFP_KERNEL); -static void rt_incvol(struct rtrack *rt) -{ - outb(0x98, rt->io); /* volume up + sigstr + on */ - msleep(100); - outb(0xd8, rt->io); /* volume steady + sigstr + on */ + if (rt) + rt->curvol = 0xff; + return rt ? &rt->isa : NULL; } -static void rt_mute(struct rtrack *rt) -{ - rt->muted = 1; - mutex_lock(&rt->lock); - outb(0xd0, rt->io); /* volume steady, off */ - mutex_unlock(&rt->lock); -} +#define AIMS_BIT_TUN_CE (1 << 0) +#define AIMS_BIT_TUN_CLK (1 << 1) +#define AIMS_BIT_TUN_DATA (1 << 2) +#define AIMS_BIT_VOL_CE (1 << 3) +#define AIMS_BIT_TUN_STRQ (1 << 4) +/* bit 5 is not connected */ +#define AIMS_BIT_VOL_UP (1 << 6) /* active low */ +#define AIMS_BIT_VOL_DN (1 << 7) /* active low */ -static int rt_setvol(struct rtrack *rt, int vol) +static void rtrack_set_pins(void *handle, u8 pins) { - int i; - - mutex_lock(&rt->lock); - - if (vol == rt->curvol) { /* requested volume = current */ - if (rt->muted) { /* user is unmuting the card */ - rt->muted = 0; - outb(0xd8, rt->io); /* enable card */ - } - mutex_unlock(&rt->lock); - return 0; - } + struct radio_isa_card *isa = handle; + struct rtrack *rt = container_of(isa, struct rtrack, isa); + u8 bits = AIMS_BIT_VOL_DN | AIMS_BIT_VOL_UP | AIMS_BIT_TUN_STRQ; - if (vol == 0) { /* volume = 0 means mute the card */ - outb(0x48, rt->io); /* volume down but still "on" */ - msleep(2000); /* make sure it's totally down */ - outb(0xd0, rt->io); /* volume steady, off */ - rt->curvol = 0; /* track the volume state! */ - mutex_unlock(&rt->lock); - return 0; - } + if (!v4l2_ctrl_g_ctrl(rt->isa.mute)) + bits |= AIMS_BIT_VOL_CE; - rt->muted = 0; - if (vol > rt->curvol) - for (i = rt->curvol; i < vol; i++) - rt_incvol(rt); - else - for (i = rt->curvol; i > vol; i--) - rt_decvol(rt); + if (pins & LM7000_DATA) + bits |= AIMS_BIT_TUN_DATA; + if (pins & LM7000_CLK) + bits |= AIMS_BIT_TUN_CLK; + if (pins & LM7000_CE) + bits |= AIMS_BIT_TUN_CE; - rt->curvol = vol; - mutex_unlock(&rt->lock); - return 0; + outb_p(bits, rt->isa.io); } -/* the 128+64 on these outb's is to keep the volume stable while tuning - * without them, the volume _will_ creep up with each frequency change - * and bit 4 (+16) is to keep the signal strength meter enabled - */ - -static void send_0_byte(struct rtrack *rt) +static int rtrack_s_frequency(struct radio_isa_card *isa, u32 freq) { - if (rt->curvol == 0 || rt->muted) { - outb_p(128+64+16+ 1, rt->io); /* wr-enable + data low */ - outb_p(128+64+16+2+1, rt->io); /* clock */ - } - else { - outb_p(128+64+16+8+ 1, rt->io); /* on + wr-enable + data low */ - outb_p(128+64+16+8+2+1, rt->io); /* clock */ - } - msleep(1); -} - -static void send_1_byte(struct rtrack *rt) -{ - if (rt->curvol == 0 || rt->muted) { - outb_p(128+64+16+4 +1, rt->io); /* wr-enable+data high */ - outb_p(128+64+16+4+2+1, rt->io); /* clock */ - } - else { - outb_p(128+64+16+8+4 +1, rt->io); /* on+wr-enable+data high */ - outb_p(128+64+16+8+4+2+1, rt->io); /* clock */ - } - - msleep(1); -} - -static int rt_setfreq(struct rtrack *rt, unsigned long freq) -{ - int i; - - mutex_lock(&rt->lock); /* Stop other ops interfering */ - - rt->curfreq = freq; - - /* now uses VIDEO_TUNER_LOW for fine tuning */ - - freq += 171200; /* Add 10.7 MHz IF */ - freq /= 800; /* Convert to 50 kHz units */ - - send_0_byte(rt); /* 0: LSB of frequency */ - - for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ - if (freq & (1 << i)) - send_1_byte(rt); - else - send_0_byte(rt); - - send_0_byte(rt); /* 14: test bit - always 0 */ - send_0_byte(rt); /* 15: test bit - always 0 */ - - send_0_byte(rt); /* 16: band data 0 - always 0 */ - send_0_byte(rt); /* 17: band data 1 - always 0 */ - send_0_byte(rt); /* 18: band data 2 - always 0 */ - send_0_byte(rt); /* 19: time base - always 0 */ - - send_0_byte(rt); /* 20: spacing (0 = 25 kHz) */ - send_1_byte(rt); /* 21: spacing (1 = 25 kHz) */ - send_0_byte(rt); /* 22: spacing (0 = 25 kHz) */ - send_1_byte(rt); /* 23: AM/FM (FM = 1, always) */ - - if (rt->curvol == 0 || rt->muted) - outb(0xd0, rt->io); /* volume steady + sigstr */ - else - outb(0xd8, rt->io); /* volume steady + sigstr + on */ - - mutex_unlock(&rt->lock); + lm7000_set_freq(freq, isa, rtrack_set_pins); return 0; } -static int rt_getsigstr(struct rtrack *rt) +static u32 rtrack_g_signal(struct radio_isa_card *isa) { - int sig = 1; - - mutex_lock(&rt->lock); - if (inb(rt->io) & 2) /* bit set = no signal present */ - sig = 0; - mutex_unlock(&rt->lock); - return sig; + /* bit set = no signal present */ + return 0xffff * !(inb(isa->io) & 2); } -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) +static int rtrack_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - strlcpy(v->driver, "radio-aimslab", sizeof(v->driver)); - strlcpy(v->card, "RadioTrack", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} + struct rtrack *rt = container_of(isa, struct rtrack, isa); + int curvol = rt->curvol; -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct rtrack *rt = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xffff * rt_getsigstr(rt); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct rtrack *rt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - rt_setfreq(rt, f->frequency); - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct rtrack *rt = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = rt->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - 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 rtrack *rt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = rt->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = rt->curvol; + if (mute) { + outb(0xd0, isa->io); /* volume steady + sigstr + off */ return 0; } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct rtrack *rt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - rt_mute(rt); - else - rt_setvol(rt, rt->curvol); - return 0; - case V4L2_CID_AUDIO_VOLUME: - rt_setvol(rt, ctrl->value); - return 0; + if (vol == 0) { /* volume = 0 means mute the card */ + outb(0x48, isa->io); /* volume down but still "on" */ + msleep(curvol * 3); /* make sure it's totally down */ + } else if (curvol < vol) { + outb(0x98, isa->io); /* volume up + sigstr + on */ + for (; curvol < vol; curvol++) + udelay(3000); + } else if (curvol > vol) { + outb(0x58, isa->io); /* volume down + sigstr + on */ + for (; curvol > vol; curvol--) + udelay(3000); } - return -EINVAL; -} - -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; + outb(0xd8, isa->io); /* volume steady + sigstr + on */ + rt->curvol = vol; return 0; } -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) +/* Mute card - prevents noisy bootups */ +static int rtrack_initialize(struct radio_isa_card *isa) { - 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; + /* this ensures that the volume is all the way up */ + outb(0x90, isa->io); /* volume up but still "on" */ + msleep(3000); /* make sure it's totally up */ + outb(0xc0, isa->io); /* steady volume, mute card */ return 0; } -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - -static const struct v4l2_file_operations rtrack_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops rtrack_ops = { + .alloc = rtrack_alloc, + .init = rtrack_initialize, + .s_mute_volume = rtrack_s_mute_volume, + .s_frequency = rtrack_s_frequency, + .g_signal = rtrack_g_signal, }; -static const struct v4l2_ioctl_ops rtrack_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .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_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, +static const int rtrack_ioports[] = { 0x20f, 0x30f }; + +static struct radio_isa_driver rtrack_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-aimslab", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = rtrack_ioports, + .num_of_io_ports = ARRAY_SIZE(rtrack_ioports), + .region_size = 2, + .card = "AIMSlab RadioTrack/RadioReveal", + .ops = &rtrack_ops, + .has_stereo = true, + .max_volume = 0xff, }; static int __init rtrack_init(void) { - struct rtrack *rt = &rtrack_card; - struct v4l2_device *v4l2_dev = &rt->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "rtrack", sizeof(v4l2_dev->name)); - rt->io = io; - - if (rt->io == -1) { - v4l2_err(v4l2_dev, "you must set an I/O address with io=0x20f or 0x30f\n"); - return -EINVAL; - } - - if (!request_region(rt->io, 2, "rtrack")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", rt->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(rt->io, 2); - v4l2_err(v4l2_dev, "could not register v4l2_device\n"); - return res; - } - - strlcpy(rt->vdev.name, v4l2_dev->name, sizeof(rt->vdev.name)); - rt->vdev.v4l2_dev = v4l2_dev; - rt->vdev.fops = &rtrack_fops; - rt->vdev.ioctl_ops = &rtrack_ioctl_ops; - rt->vdev.release = video_device_release_empty; - video_set_drvdata(&rt->vdev, rt); - - /* Set up the I/O locking */ - - mutex_init(&rt->lock); - - /* mute card - prevents noisy bootups */ - - /* this ensures that the volume is all the way down */ - outb(0x48, rt->io); /* volume down but still "on" */ - msleep(2000); /* make sure it's totally down */ - outb(0xc0, rt->io); /* steady volume, mute card */ - - if (video_register_device(&rt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(&rt->v4l2_dev); - release_region(rt->io, 2); - return -EINVAL; - } - v4l2_info(v4l2_dev, "AIMSlab RadioTrack/RadioReveal card driver.\n"); - - return 0; + return isa_register_driver(&rtrack_driver.driver, RTRACK_MAX); } static void __exit rtrack_exit(void) { - struct rtrack *rt = &rtrack_card; - - video_unregister_device(&rt->vdev); - v4l2_device_unregister(&rt->v4l2_dev); - release_region(rt->io, 2); + isa_unregister_driver(&rtrack_driver.driver); } module_init(rtrack_init); module_exit(rtrack_exit); - diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c index eed7b084073..705dd6f9162 100644 --- a/drivers/media/radio/radio-aztech.c +++ b/drivers/media/radio/radio-aztech.c @@ -1,5 +1,7 @@ -/* radio-aztech.c - Aztech radio card driver for Linux 2.2 +/* + * radio-aztech.c - Aztech radio card driver * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@xs4all.nl> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> * Adapted to support the Video for Linux API by * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: @@ -10,19 +12,7 @@ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) * William McGrath (wmcgrath@twilight.vtc.vsc.edu) * - * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/ - * along with more information on the card itself. - * - * History: - * 1999-02-24 Russell Kroll <rkroll@exploits.org> - * Fine tuning/VIDEO_TUNER_LOW - * Range expanded to 87-108 MHz (from 87.9-107.8) - * - * Notable changes from the original source: - * - includes stripped down to the essentials - * - for loops used as delays replaced with udelay() - * - #defines removed, changed to static values - * - tuning structure changed - no more character arrays, other changes + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -31,367 +21,140 @@ #include <linux/delay.h> /* udelay */ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include "radio-isa.h" +#include "lm7000.h" MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); MODULE_DESCRIPTION("A driver for the Aztech radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("1.0.0"); /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ - #ifndef CONFIG_RADIO_AZTECH_PORT #define CONFIG_RADIO_AZTECH_PORT -1 #endif -static int io = CONFIG_RADIO_AZTECH_PORT; -static int radio_nr = -1; -static int radio_wait_time = 1000; +#define AZTECH_MAX 2 -module_param(io, int, 0); -module_param(radio_nr, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)"); +static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT, + [1 ... (AZTECH_MAX - 1)] = -1 }; +static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 }; +static const int radio_wait_time = 1000; -struct aztech -{ - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + +struct aztech { + struct radio_isa_card isa; int curvol; - unsigned long curfreq; - int stereo; - struct mutex lock; }; -static struct aztech aztech_card; - -static int volconvert(int level) -{ - level >>= 14; /* Map 16bits down to 2 bit */ - level &= 3; - - /* convert to card-friendly values */ - switch (level) { - case 0: - return 0; - case 1: - return 1; - case 2: - return 4; - case 3: - return 5; - } - return 0; /* Quieten gcc */ -} - -static void send_0_byte(struct aztech *az) -{ - udelay(radio_wait_time); - outb_p(2 + volconvert(az->curvol), az->io); - outb_p(64 + 2 + volconvert(az->curvol), az->io); -} - -static void send_1_byte(struct aztech *az) -{ - udelay (radio_wait_time); - outb_p(128 + 2 + volconvert(az->curvol), az->io); - outb_p(128 + 64 + 2 + volconvert(az->curvol), az->io); -} - -static int az_setvol(struct aztech *az, int vol) -{ - mutex_lock(&az->lock); - outb(volconvert(vol), az->io); - mutex_unlock(&az->lock); - return 0; -} - -/* thanks to Michael Dwyer for giving me a dose of clues in - * the signal strength department.. - * - * This card has a stereo bit - bit 0 set = mono, not set = stereo - * It also has a "signal" bit - bit 1 set = bad signal, not set = good - * - */ - -static int az_getsigstr(struct aztech *az) -{ - int sig = 1; - - mutex_lock(&az->lock); - if (inb(az->io) & 2) /* bit set = no signal present */ - sig = 0; - mutex_unlock(&az->lock); - return sig; -} - -static int az_getstereo(struct aztech *az) -{ - int stereo = 1; - - mutex_lock(&az->lock); - if (inb(az->io) & 1) /* bit set = mono */ - stereo = 0; - mutex_unlock(&az->lock); - return stereo; -} - -static int az_setfreq(struct aztech *az, unsigned long frequency) -{ - int i; - - mutex_lock(&az->lock); - - az->curfreq = frequency; - frequency += 171200; /* Add 10.7 MHz IF */ - frequency /= 800; /* Convert to 50 kHz units */ - - send_0_byte(az); /* 0: LSB of frequency */ - - for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ - if (frequency & (1 << i)) - send_1_byte(az); - else - send_0_byte(az); - - send_0_byte(az); /* 14: test bit - always 0 */ - send_0_byte(az); /* 15: test bit - always 0 */ - send_0_byte(az); /* 16: band data 0 - always 0 */ - if (az->stereo) /* 17: stereo (1 to enable) */ - send_1_byte(az); - else - send_0_byte(az); - - send_1_byte(az); /* 18: band data 1 - unknown */ - send_0_byte(az); /* 19: time base - always 0 */ - send_0_byte(az); /* 20: spacing (0 = 25 kHz) */ - send_1_byte(az); /* 21: spacing (1 = 25 kHz) */ - send_0_byte(az); /* 22: spacing (0 = 25 kHz) */ - send_1_byte(az); /* 23: AM/FM (FM = 1, always) */ - - /* latch frequency */ +/* bit definitions for register read */ +#define AZTECH_BIT_NOT_TUNED (1 << 0) +#define AZTECH_BIT_MONO (1 << 1) +/* bit definitions for register write */ +#define AZTECH_BIT_TUN_CE (1 << 1) +#define AZTECH_BIT_TUN_CLK (1 << 6) +#define AZTECH_BIT_TUN_DATA (1 << 7) +/* bits 0 and 2 are volume control, bits 3..5 are not connected */ - udelay(radio_wait_time); - outb_p(128 + 64 + volconvert(az->curvol), az->io); - - mutex_unlock(&az->lock); - - return 0; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) +static void aztech_set_pins(void *handle, u8 pins) { - strlcpy(v->driver, "radio-aztech", sizeof(v->driver)); - strlcpy(v->card, "Aztech Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct aztech *az = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; + struct radio_isa_card *isa = handle; + struct aztech *az = container_of(isa, struct aztech, isa); + u8 bits = az->curvol; - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; + if (pins & LM7000_DATA) + bits |= AZTECH_BIT_TUN_DATA; + if (pins & LM7000_CLK) + bits |= AZTECH_BIT_TUN_CLK; + if (pins & LM7000_CE) + bits |= AZTECH_BIT_TUN_CE; - v->rangelow = 87 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (az_getstereo(az)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * az_getsigstr(az); - - return 0; + outb_p(bits, az->isa.io); } -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) +static struct radio_isa_card *aztech_alloc(void) { - return v->index ? -EINVAL : 0; -} + struct aztech *az = kzalloc(sizeof(*az), GFP_KERNEL); -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; + return az ? &az->isa : NULL; } -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) +static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq) { - return i ? -EINVAL : 0; -} + lm7000_set_freq(freq, isa, aztech_set_pins); -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) +static u32 aztech_g_rxsubchans(struct radio_isa_card *isa) { - return a->index ? -EINVAL : 0; + if (inb(isa->io) & AZTECH_BIT_MONO) + return V4L2_TUNER_SUB_MONO; + return V4L2_TUNER_SUB_STEREO; } -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) +static u32 aztech_g_signal(struct radio_isa_card *isa) { - struct aztech *az = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - az_setfreq(az, f->frequency); - return 0; + return (inb(isa->io) & AZTECH_BIT_NOT_TUNED) ? 0 : 0xffff; } -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) +static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - struct aztech *az = video_drvdata(file); + struct aztech *az = container_of(isa, struct aztech, isa); - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = az->curfreq; + if (mute) + vol = 0; + az->curvol = (vol & 1) + ((vol & 2) << 1); + outb(az->curvol, isa->io); return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - 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 aztech *az = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (az->curvol == 0) - ctrl->value = 1; - else - ctrl->value = 0; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = az->curvol * 6554; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct aztech *az = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - az_setvol(az, 0); - else - az_setvol(az, az->curvol); - return 0; - case V4L2_CID_AUDIO_VOLUME: - az_setvol(az, ctrl->value); - return 0; - } - return -EINVAL; -} - -static const struct v4l2_file_operations aztech_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops aztech_ops = { + .alloc = aztech_alloc, + .s_mute_volume = aztech_s_mute_volume, + .s_frequency = aztech_s_frequency, + .g_rxsubchans = aztech_g_rxsubchans, + .g_signal = aztech_g_signal, }; -static const struct v4l2_ioctl_ops aztech_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .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_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, +static const int aztech_ioports[] = { 0x350, 0x358 }; + +static struct radio_isa_driver aztech_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-aztech", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = aztech_ioports, + .num_of_io_ports = ARRAY_SIZE(aztech_ioports), + .region_size = 8, + .card = "Aztech Radio", + .ops = &aztech_ops, + .has_stereo = true, + .max_volume = 3, }; static int __init aztech_init(void) { - struct aztech *az = &aztech_card; - struct v4l2_device *v4l2_dev = &az->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "aztech", sizeof(v4l2_dev->name)); - az->io = io; - - if (az->io == -1) { - v4l2_err(v4l2_dev, "you must set an I/O address with io=0x350 or 0x358\n"); - return -EINVAL; - } - - if (!request_region(az->io, 2, "aztech")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", az->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(az->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - mutex_init(&az->lock); - strlcpy(az->vdev.name, v4l2_dev->name, sizeof(az->vdev.name)); - az->vdev.v4l2_dev = v4l2_dev; - az->vdev.fops = &aztech_fops; - az->vdev.ioctl_ops = &aztech_ioctl_ops; - az->vdev.release = video_device_release_empty; - video_set_drvdata(&az->vdev, az); - /* mute card - prevents noisy bootups */ - outb(0, az->io); - - if (video_register_device(&az->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(az->io, 2); - return -EINVAL; - } - - v4l2_info(v4l2_dev, "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n"); - return 0; + return isa_register_driver(&aztech_driver.driver, AZTECH_MAX); } static void __exit aztech_exit(void) { - struct aztech *az = &aztech_card; - - video_unregister_device(&az->vdev); - v4l2_device_unregister(&az->v4l2_dev); - release_region(az->io, 2); + isa_unregister_driver(&aztech_driver.driver); } module_init(aztech_init); diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c index 16a089fad90..d719e59e217 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,29 @@ 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 }, +}; + +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, + }, }; @@ -91,14 +115,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 +133,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 +154,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 +170,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 +190,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 +208,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 +216,16 @@ static void cadet_setfreq(struct cadet *dev, unsigned freq) int i, j, test; int curvol; + freq = clamp(freq, bands[dev->is_fm_band].rangelow, + bands[dev->is_fm_band].rangehigh); + 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 +235,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 +253,34 @@ 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) +static bool cadet_has_rds_data(struct cadet *dev) { - int ret = 0; + bool result; mutex_lock(&dev->lock); - - outb(7, dev->io); /* Select tuner control */ - if ((inb(dev->io + 1) & 0x20) != 0) - ret = 0xffff; - + result = dev->rdsin != dev->rdsout; mutex_unlock(&dev->lock); - return ret; + return result; } -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; @@ -291,13 +289,12 @@ static void cadet_handler(unsigned long data) if (mutex_trylock(&dev->lock)) { outb(0x3, dev->io); /* Select RDS Decoder Control */ if ((inb(dev->io + 1) & 0x20) != 0) - printk(KERN_CRIT "cadet: RDS fifo overflow\n"); + pr_err("cadet: RDS fifo overflow\n"); 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) - printk(KERN_WARNING "cadet: RDS buffer overflow\n"); - else + if (dev->rdsin + 1 != dev->rdsout) dev->rdsin++; } mutex_unlock(&dev->lock); @@ -306,7 +303,7 @@ static void cadet_handler(unsigned long data) /* * Service pending read */ - if (dev->rdsin != dev->rdsout) + if (cadet_has_rds_data(dev)) wake_up_interruptible(&dev->read_queue); /* @@ -314,11 +311,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,27 +334,22 @@ 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->rdsin == dev->rdsout) { - mutex_unlock(&dev->lock); - if (file->f_flags & O_NONBLOCK) - return -EWOULDBLOCK; - interruptible_sleep_on(&dev->read_queue); - mutex_lock(&dev->lock); - } + if (dev->rdsstat == 0) + cadet_start_rds(dev); + mutex_unlock(&dev->lock); + + if (!cadet_has_rds_data(dev) && (file->f_flags & O_NONBLOCK)) + return -EWOULDBLOCK; + i = wait_event_interruptible(dev->read_queue, cadet_has_rds_data(dev)); + if (i) + return i; + + 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)) + if (i && copy_to_user(data, readbuf, i)) return -EFAULT; return i; } @@ -358,9 +360,10 @@ 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 | + strlcpy(v->bus_info, "ISA:radio-cadet", sizeof(v->bus_info)); + 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; } @@ -369,50 +372,45 @@ static int vidioc_g_tuner(struct file *file, void *priv, { 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; } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const 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,115 +419,57 @@ 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; } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { 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; cadet_setfreq(dev, f->frequency); return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - 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) +static int cadet_s_ctrl(struct v4l2_ctrl *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 +477,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 +489,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->rdsin != dev->rdsout) - return POLLIN | POLLRDNORM; - return 0; + 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 (cadet_has_rds_data(dev)) + res |= POLLIN | POLLRDNORM; + return res; } @@ -572,13 +520,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 +577,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 +597,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 +630,41 @@ 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; - } + res = video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr); + if (res < 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 +672,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-gemtek.c b/drivers/media/radio/radio-gemtek.c index 36ce0611c03..235c0e34982 100644 --- a/drivers/media/radio/radio-gemtek.c +++ b/drivers/media/radio/radio-gemtek.c @@ -1,4 +1,7 @@ -/* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi> +/* + * GemTek radio card driver + * + * Copyright 1998 Jonas Munsin <jmunsin@iki.fi> * * GemTek hasn't released any specs on the card, so the protocol had to * be reverse engineered with dosemu. @@ -11,9 +14,12 @@ * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> * - * TODO: Allow for more than one of these foolish entities :-) - * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> + * + * Note: this card seems to swap the left and right audio channels! + * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -23,8 +29,11 @@ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/mutex.h> #include <linux/io.h> /* outb, outb_p */ +#include <linux/pnp.h> +#include <linux/slab.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> +#include "radio-isa.h" /* * Module info. @@ -33,7 +42,7 @@ MODULE_AUTHOR("Jonas Munsin, Pekka Seppänen <pexu@kapsi.fi>"); MODULE_DESCRIPTION("A driver for the GemTek Radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.4"); +MODULE_VERSION("1.0.0"); /* * Module params. @@ -46,45 +55,29 @@ MODULE_VERSION("0.0.4"); #define CONFIG_RADIO_GEMTEK_PROBE 1 #endif -static int io = CONFIG_RADIO_GEMTEK_PORT; -static bool probe = CONFIG_RADIO_GEMTEK_PROBE; -static bool hardmute; -static bool shutdown = 1; -static bool keepmuted = 1; -static bool initmute = 1; -static int radio_nr = -1; +#define GEMTEK_MAX 4 -module_param(io, int, 0444); -MODULE_PARM_DESC(io, "Force I/O port for the GemTek Radio card if automatic " - "probing is disabled or fails. The most common I/O ports are: 0x20c " - "0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to " - "work for the combined sound/radiocard)."); +static bool probe = CONFIG_RADIO_GEMTEK_PROBE; +static bool hardmute; +static int io[GEMTEK_MAX] = { [0] = CONFIG_RADIO_GEMTEK_PORT, + [1 ... (GEMTEK_MAX - 1)] = -1 }; +static int radio_nr[GEMTEK_MAX] = { [0 ... (GEMTEK_MAX - 1)] = -1 }; module_param(probe, bool, 0444); -MODULE_PARM_DESC(probe, "Enable automatic device probing. Note: only the most " - "common I/O ports used by the card are probed."); +MODULE_PARM_DESC(probe, "Enable automatic device probing."); module_param(hardmute, bool, 0644); -MODULE_PARM_DESC(hardmute, "Enable `hard muting' by shutting down PLL, may " +MODULE_PARM_DESC(hardmute, "Enable 'hard muting' by shutting down PLL, may " "reduce static noise."); -module_param(shutdown, bool, 0644); -MODULE_PARM_DESC(shutdown, "Enable shutting down PLL and muting line when " - "module is unloaded."); - -module_param(keepmuted, bool, 0644); -MODULE_PARM_DESC(keepmuted, "Keep card muted even when frequency is changed."); - -module_param(initmute, bool, 0444); -MODULE_PARM_DESC(initmute, "Mute card when module is loaded."); - -module_param(radio_nr, int, 0444); +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "Force I/O ports for the GemTek Radio card if automatic " + "probing is disabled or fails. The most common I/O ports are: 0x20c " + "0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to " + "work for the combined sound/radiocard)."); -/* - * Functions for controlling the card. - */ -#define GEMTEK_LOWFREQ (87*16000) -#define GEMTEK_HIGHFREQ (108*16000) +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); /* * Frequency calculation constants. Intermediate frequency 10.52 MHz (nominal @@ -108,18 +101,11 @@ module_param(radio_nr, int, 0444); #define LONG_DELAY 75 /* usec */ struct gemtek { - struct v4l2_device v4l2_dev; - struct video_device vdev; - struct mutex lock; - unsigned long lastfreq; - int muted; - int verified; - int io; + struct radio_isa_card isa; + bool muted; u32 bu2614data; }; -static struct gemtek gemtek_card; - #define BU2614_FREQ_BITS 16 /* D0..D15, Frequency data */ #define BU2614_PORT_BITS 3 /* P0..P2, Output port control data */ #define BU2614_VOID_BITS 4 /* unused */ @@ -166,31 +152,24 @@ static struct gemtek gemtek_card; */ static void gemtek_bu2614_transmit(struct gemtek *gt) { + struct radio_isa_card *isa = >->isa; int i, bit, q, mute; - mutex_lock(>->lock); - mute = gt->muted ? GEMTEK_MT : 0x00; - outb_p(mute | GEMTEK_DA | GEMTEK_CK, gt->io); - udelay(SHORT_DELAY); - outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, gt->io); + outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, isa->io); udelay(LONG_DELAY); for (i = 0, q = gt->bu2614data; i < 32; i++, q >>= 1) { bit = (q & 1) ? GEMTEK_DA : 0; - outb_p(mute | GEMTEK_CE | bit, gt->io); + outb_p(mute | GEMTEK_CE | bit, isa->io); udelay(SHORT_DELAY); - outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, gt->io); + outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, isa->io); udelay(SHORT_DELAY); } - outb_p(mute | GEMTEK_DA | GEMTEK_CK, gt->io); + outb_p(mute | GEMTEK_DA | GEMTEK_CK, isa->io); udelay(SHORT_DELAY); - outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, gt->io); - udelay(LONG_DELAY); - - mutex_unlock(>->lock); } /* @@ -198,21 +177,27 @@ static void gemtek_bu2614_transmit(struct gemtek *gt) */ static unsigned long gemtek_convfreq(unsigned long freq) { - return ((freq<<FSCALE) + IF_OFFSET + REF_FREQ/2) / REF_FREQ; + return ((freq << FSCALE) + IF_OFFSET + REF_FREQ / 2) / REF_FREQ; +} + +static struct radio_isa_card *gemtek_alloc(void) +{ + struct gemtek *gt = kzalloc(sizeof(*gt), GFP_KERNEL); + + if (gt) + gt->muted = true; + return gt ? >->isa : NULL; } /* * Set FM-frequency. */ -static void gemtek_setfreq(struct gemtek *gt, unsigned long freq) +static int gemtek_s_frequency(struct radio_isa_card *isa, u32 freq) { - if (keepmuted && hardmute && gt->muted) - return; - - freq = clamp_val(freq, GEMTEK_LOWFREQ, GEMTEK_HIGHFREQ); + struct gemtek *gt = container_of(isa, struct gemtek, isa); - gt->lastfreq = freq; - gt->muted = 0; + if (hardmute && gt->muted) + return 0; gemtek_bu2614_set(gt, BU2614_PORT, 0); gemtek_bu2614_set(gt, BU2614_FMES, 0); @@ -220,23 +205,25 @@ static void gemtek_setfreq(struct gemtek *gt, unsigned long freq) gemtek_bu2614_set(gt, BU2614_SWAL, 0); gemtek_bu2614_set(gt, BU2614_FMUN, 1); /* GT bit set */ gemtek_bu2614_set(gt, BU2614_TEST, 0); - gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_STDF_3_125_KHZ); gemtek_bu2614_set(gt, BU2614_FREQ, gemtek_convfreq(freq)); - gemtek_bu2614_transmit(gt); + return 0; } /* * Set mute flag. */ -static void gemtek_mute(struct gemtek *gt) +static int gemtek_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { + struct gemtek *gt = container_of(isa, struct gemtek, isa); int i; - gt->muted = 1; - + gt->muted = mute; if (hardmute) { + if (!mute) + return gemtek_s_frequency(isa, isa->freq); + /* Turn off PLL, disable data output */ gemtek_bu2614_set(gt, BU2614_PORT, 0); gemtek_bu2614_set(gt, BU2614_FMES, 0); /* CT bit off */ @@ -247,367 +234,109 @@ static void gemtek_mute(struct gemtek *gt) gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_PLL_OFF); gemtek_bu2614_set(gt, BU2614_FREQ, 0); gemtek_bu2614_transmit(gt); - return; + return 0; } - mutex_lock(>->lock); - /* Read bus contents (CE, CK and DA). */ - i = inb_p(gt->io); + i = inb_p(isa->io); /* Write it back with mute flag set. */ - outb_p((i >> 5) | GEMTEK_MT, gt->io); + outb_p((i >> 5) | (mute ? GEMTEK_MT : 0), isa->io); udelay(SHORT_DELAY); - - mutex_unlock(>->lock); -} - -/* - * Unset mute flag. - */ -static void gemtek_unmute(struct gemtek *gt) -{ - int i; - - gt->muted = 0; - if (hardmute) { - /* Turn PLL back on. */ - gemtek_setfreq(gt, gt->lastfreq); - return; - } - mutex_lock(>->lock); - - i = inb_p(gt->io); - outb_p(i >> 5, gt->io); - udelay(SHORT_DELAY); - - mutex_unlock(>->lock); + return 0; } -/* - * Get signal strength (= stereo status). - */ -static inline int gemtek_getsigstr(struct gemtek *gt) +static u32 gemtek_g_rxsubchans(struct radio_isa_card *isa) { - int sig; - - mutex_lock(>->lock); - sig = inb_p(gt->io) & GEMTEK_NS ? 0 : 1; - mutex_unlock(>->lock); - return sig; + if (inb_p(isa->io) & GEMTEK_NS) + return V4L2_TUNER_SUB_MONO; + return V4L2_TUNER_SUB_STEREO; } /* * Check if requested card acts like GemTek Radio card. */ -static int gemtek_verify(struct gemtek *gt, int port) +static bool gemtek_probe(struct radio_isa_card *isa, int io) { int i, q; - if (gt->verified == port) - return 1; - - mutex_lock(>->lock); - - q = inb_p(port); /* Read bus contents before probing. */ + q = inb_p(io); /* Read bus contents before probing. */ /* Try to turn on CE, CK and DA respectively and check if card responds properly. */ for (i = 0; i < 3; ++i) { - outb_p(1 << i, port); + outb_p(1 << i, io); udelay(SHORT_DELAY); - if ((inb_p(port) & (~GEMTEK_NS)) != (0x17 | (1 << (i + 5)))) { - mutex_unlock(>->lock); - return 0; - } + if ((inb_p(io) & ~GEMTEK_NS) != (0x17 | (1 << (i + 5)))) + return false; } - outb_p(q >> 5, port); /* Write bus contents back. */ + outb_p(q >> 5, io); /* Write bus contents back. */ udelay(SHORT_DELAY); - - mutex_unlock(>->lock); - gt->verified = port; - - return 1; -} - -/* - * Automatic probing for card. - */ -static int gemtek_probe(struct gemtek *gt) -{ - struct v4l2_device *v4l2_dev = >->v4l2_dev; - int ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c }; - int i; - - if (!probe) { - v4l2_info(v4l2_dev, "Automatic device probing disabled.\n"); - return -1; - } - - v4l2_info(v4l2_dev, "Automatic device probing enabled.\n"); - - for (i = 0; i < ARRAY_SIZE(ioports); ++i) { - v4l2_info(v4l2_dev, "Trying I/O port 0x%x...\n", ioports[i]); - - if (!request_region(ioports[i], 1, "gemtek-probe")) { - v4l2_warn(v4l2_dev, "I/O port 0x%x busy!\n", - ioports[i]); - continue; - } - - if (gemtek_verify(gt, ioports[i])) { - v4l2_info(v4l2_dev, "Card found from I/O port " - "0x%x!\n", ioports[i]); - - release_region(ioports[i], 1); - gt->io = ioports[i]; - return gt->io; - } - - release_region(ioports[i], 1); - } - - v4l2_err(v4l2_dev, "Automatic probing failed!\n"); - return -1; + return true; } -/* - * Video 4 Linux stuff. - */ - -static const struct v4l2_file_operations gemtek_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops gemtek_ops = { + .alloc = gemtek_alloc, + .probe = gemtek_probe, + .s_mute_volume = gemtek_s_mute_volume, + .s_frequency = gemtek_s_frequency, + .g_rxsubchans = gemtek_g_rxsubchans, }; -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-gemtek", sizeof(v->driver)); - strlcpy(v->card, "GemTek", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) -{ - struct gemtek *gt = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = GEMTEK_LOWFREQ; - v->rangehigh = GEMTEK_HIGHFREQ; - v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; - v->signal = 0xffff * gemtek_getsigstr(gt); - if (v->signal) { - v->audmode = V4L2_TUNER_MODE_STEREO; - v->rxsubchans = V4L2_TUNER_SUB_STEREO; - } else { - v->audmode = V4L2_TUNER_MODE_MONO; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - } - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *v) -{ - return (v->index != 0) ? -EINVAL : 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct gemtek *gt = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = gt->lastfreq; - return 0; -} +static const int gemtek_ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c }; -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct gemtek *gt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - gemtek_setfreq(gt, f->frequency); - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); - default: - return -EINVAL; - } -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct gemtek *gt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = gt->muted; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct gemtek *gt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - gemtek_mute(gt); - else - gemtek_unmute(gt); - return 0; - } - return -EINVAL; -} - -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 != 0) ? -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; -} +#ifdef CONFIG_PNP +static struct pnp_device_id gemtek_pnp_devices[] = { + /* AOpen FX-3D/Pro Radio */ + {.id = "ADS7183", .driver_data = 0}, + {.id = ""} +}; -static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *a) -{ - return (a->index != 0) ? -EINVAL : 0; -} +MODULE_DEVICE_TABLE(pnp, gemtek_pnp_devices); +#endif -static const struct v4l2_ioctl_ops gemtek_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .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_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 +static struct radio_isa_driver gemtek_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-gemtek", + }, + }, +#ifdef CONFIG_PNP + .pnp_driver = { + .name = "radio-gemtek", + .id_table = gemtek_pnp_devices, + .probe = radio_isa_pnp_probe, + .remove = radio_isa_pnp_remove, + }, +#endif + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = gemtek_ioports, + .num_of_io_ports = ARRAY_SIZE(gemtek_ioports), + .region_size = 1, + .card = "GemTek Radio", + .ops = &gemtek_ops, + .has_stereo = true, }; -/* - * Initialization / cleanup related stuff. - */ - static int __init gemtek_init(void) { - struct gemtek *gt = &gemtek_card; - struct v4l2_device *v4l2_dev = >->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "gemtek", sizeof(v4l2_dev->name)); - - v4l2_info(v4l2_dev, "GemTek Radio card driver: v0.0.3\n"); - - mutex_init(>->lock); - - gt->verified = -1; - gt->io = io; - gemtek_probe(gt); - if (gt->io) { - if (!request_region(gt->io, 1, "gemtek")) { - v4l2_err(v4l2_dev, "I/O port 0x%x already in use.\n", gt->io); - return -EBUSY; - } - - if (!gemtek_verify(gt, gt->io)) - v4l2_warn(v4l2_dev, "Card at I/O port 0x%x does not " - "respond properly, check your " - "configuration.\n", gt->io); - else - v4l2_info(v4l2_dev, "Using I/O port 0x%x.\n", gt->io); - } else if (probe) { - v4l2_err(v4l2_dev, "Automatic probing failed and no " - "fixed I/O port defined.\n"); - return -ENODEV; - } else { - v4l2_err(v4l2_dev, "Automatic probing disabled but no fixed " - "I/O port defined."); - return -EINVAL; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - release_region(gt->io, 1); - return res; - } - - strlcpy(gt->vdev.name, v4l2_dev->name, sizeof(gt->vdev.name)); - gt->vdev.v4l2_dev = v4l2_dev; - gt->vdev.fops = &gemtek_fops; - gt->vdev.ioctl_ops = &gemtek_ioctl_ops; - gt->vdev.release = video_device_release_empty; - video_set_drvdata(>->vdev, gt); - - /* Set defaults */ - gt->lastfreq = GEMTEK_LOWFREQ; - gt->bu2614data = 0; - - if (initmute) - gemtek_mute(gt); - - if (video_register_device(>->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(gt->io, 1); - return -EBUSY; - } - - return 0; + gemtek_driver.probe = probe; +#ifdef CONFIG_PNP + pnp_register_driver(&gemtek_driver.pnp_driver); +#endif + return isa_register_driver(&gemtek_driver.driver, GEMTEK_MAX); } -/* - * Module cleanup - */ static void __exit gemtek_exit(void) { - struct gemtek *gt = &gemtek_card; - struct v4l2_device *v4l2_dev = >->v4l2_dev; - - if (shutdown) { - hardmute = 1; /* Turn off PLL */ - gemtek_mute(gt); - } else { - v4l2_info(v4l2_dev, "Module unloaded but card not muted!\n"); - } - - video_unregister_device(>->vdev); - v4l2_device_unregister(>->v4l2_dev); - release_region(gt->io, 1); + hardmute = 1; /* Turn off PLL */ +#ifdef CONFIG_PNP + pnp_unregister_driver(&gemtek_driver.pnp_driver); +#endif + isa_unregister_driver(&gemtek_driver.driver); } module_init(gemtek_init); diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c new file mode 100644 index 00000000000..6ff350831d5 --- /dev/null +++ b/drivers/media/radio/radio-isa.c @@ -0,0 +1,394 @@ +/* + * Framework for ISA radio drivers. + * This takes care of all the V4L2 scaffolding, allowing the ISA drivers + * to concentrate on the actual hardware operation. + * + * Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> + +#include "radio-isa.h" + +MODULE_AUTHOR("Hans Verkuil"); +MODULE_DESCRIPTION("A framework for ISA radio drivers."); +MODULE_LICENSE("GPL"); + +#define FREQ_LOW (87U * 16000U) +#define FREQ_HIGH (108U * 16000U) + +static int radio_isa_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct radio_isa_card *isa = video_drvdata(file); + + strlcpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver)); + strlcpy(v->card, isa->drv->card, sizeof(v->card)); + snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name); + + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int radio_isa_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct radio_isa_card *isa = video_drvdata(file); + const struct radio_isa_ops *ops = isa->drv->ops; + + if (v->index > 0) + return -EINVAL; + + strlcpy(v->name, "FM", sizeof(v->name)); + v->type = V4L2_TUNER_RADIO; + v->rangelow = FREQ_LOW; + v->rangehigh = FREQ_HIGH; + v->capability = V4L2_TUNER_CAP_LOW; + if (isa->drv->has_stereo) + v->capability |= V4L2_TUNER_CAP_STEREO; + + if (ops->g_rxsubchans) + v->rxsubchans = ops->g_rxsubchans(isa); + else + v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO; + if (ops->g_signal) + v->signal = ops->g_signal(isa); + else + v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ? + 0xffff : 0; + return 0; +} + +static int radio_isa_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + struct radio_isa_card *isa = video_drvdata(file); + const struct radio_isa_ops *ops = isa->drv->ops; + + if (v->index) + return -EINVAL; + if (ops->s_stereo) { + isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO); + return ops->s_stereo(isa, isa->stereo); + } + return 0; +} + +static int radio_isa_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct radio_isa_card *isa = video_drvdata(file); + u32 freq = f->frequency; + int res; + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + freq = clamp(freq, FREQ_LOW, FREQ_HIGH); + res = isa->drv->ops->s_frequency(isa, freq); + if (res == 0) + isa->freq = freq; + return res; +} + +static int radio_isa_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct radio_isa_card *isa = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = isa->freq; + return 0; +} + +static int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct radio_isa_card *isa = + container_of(ctrl->handler, struct radio_isa_card, hdl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + return isa->drv->ops->s_mute_volume(isa, ctrl->val, + isa->volume ? isa->volume->val : 0); + } + return -EINVAL; +} + +static int radio_isa_log_status(struct file *file, void *priv) +{ + struct radio_isa_card *isa = video_drvdata(file); + + v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n", isa->io); + v4l2_ctrl_handler_log_status(&isa->hdl, isa->v4l2_dev.name); + return 0; +} + +static const struct v4l2_ctrl_ops radio_isa_ctrl_ops = { + .s_ctrl = radio_isa_s_ctrl, +}; + +static const struct v4l2_file_operations radio_isa_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops radio_isa_ioctl_ops = { + .vidioc_querycap = radio_isa_querycap, + .vidioc_g_tuner = radio_isa_g_tuner, + .vidioc_s_tuner = radio_isa_s_tuner, + .vidioc_g_frequency = radio_isa_g_frequency, + .vidioc_s_frequency = radio_isa_s_frequency, + .vidioc_log_status = radio_isa_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +int radio_isa_match(struct device *pdev, unsigned int dev) +{ + struct radio_isa_driver *drv = pdev->platform_data; + + return drv->probe || drv->io_params[dev] >= 0; +} +EXPORT_SYMBOL_GPL(radio_isa_match); + +static bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io) +{ + int i; + + for (i = 0; i < drv->num_of_io_ports; i++) + if (drv->io_ports[i] == io) + return true; + return false; +} + +static struct radio_isa_card *radio_isa_alloc(struct radio_isa_driver *drv, + struct device *pdev) +{ + struct v4l2_device *v4l2_dev; + struct radio_isa_card *isa = drv->ops->alloc(); + if (!isa) + return NULL; + + dev_set_drvdata(pdev, isa); + isa->drv = drv; + v4l2_dev = &isa->v4l2_dev; + strlcpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name)); + + return isa; +} + +static int radio_isa_common_probe(struct radio_isa_card *isa, + struct device *pdev, + int radio_nr, unsigned region_size) +{ + const struct radio_isa_driver *drv = isa->drv; + const struct radio_isa_ops *ops = drv->ops; + struct v4l2_device *v4l2_dev = &isa->v4l2_dev; + int res; + + if (!request_region(isa->io, region_size, v4l2_dev->name)) { + v4l2_err(v4l2_dev, "port 0x%x already in use\n", isa->io); + kfree(isa); + return -EBUSY; + } + + res = v4l2_device_register(pdev, v4l2_dev); + if (res < 0) { + v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); + goto err_dev_reg; + } + + v4l2_ctrl_handler_init(&isa->hdl, 1); + isa->mute = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + if (drv->max_volume) + isa->volume = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, drv->max_volume, 1, + drv->max_volume); + v4l2_dev->ctrl_handler = &isa->hdl; + if (isa->hdl.error) { + res = isa->hdl.error; + v4l2_err(v4l2_dev, "Could not register controls\n"); + goto err_hdl; + } + if (drv->max_volume) + v4l2_ctrl_cluster(2, &isa->mute); + v4l2_dev->ctrl_handler = &isa->hdl; + + mutex_init(&isa->lock); + isa->vdev.lock = &isa->lock; + strlcpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name)); + isa->vdev.v4l2_dev = v4l2_dev; + isa->vdev.fops = &radio_isa_fops; + isa->vdev.ioctl_ops = &radio_isa_ioctl_ops; + isa->vdev.release = video_device_release_empty; + set_bit(V4L2_FL_USE_FH_PRIO, &isa->vdev.flags); + video_set_drvdata(&isa->vdev, isa); + isa->freq = FREQ_LOW; + isa->stereo = drv->has_stereo; + + if (ops->init) + res = ops->init(isa); + if (!res) + res = v4l2_ctrl_handler_setup(&isa->hdl); + if (!res) + res = ops->s_frequency(isa, isa->freq); + if (!res && ops->s_stereo) + res = ops->s_stereo(isa, isa->stereo); + if (res < 0) { + v4l2_err(v4l2_dev, "Could not setup card\n"); + goto err_hdl; + } + res = video_register_device(&isa->vdev, VFL_TYPE_RADIO, radio_nr); + + if (res < 0) { + v4l2_err(v4l2_dev, "Could not register device node\n"); + goto err_hdl; + } + + v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n", + drv->card, isa->io); + return 0; + +err_hdl: + v4l2_ctrl_handler_free(&isa->hdl); +err_dev_reg: + release_region(isa->io, region_size); + kfree(isa); + return res; +} + +static int radio_isa_common_remove(struct radio_isa_card *isa, + unsigned region_size) +{ + const struct radio_isa_ops *ops = isa->drv->ops; + + ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0); + video_unregister_device(&isa->vdev); + v4l2_ctrl_handler_free(&isa->hdl); + v4l2_device_unregister(&isa->v4l2_dev); + release_region(isa->io, region_size); + v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n", isa->drv->card); + kfree(isa); + return 0; +} + +int radio_isa_probe(struct device *pdev, unsigned int dev) +{ + struct radio_isa_driver *drv = pdev->platform_data; + const struct radio_isa_ops *ops = drv->ops; + struct v4l2_device *v4l2_dev; + struct radio_isa_card *isa; + + isa = radio_isa_alloc(drv, pdev); + if (!isa) + return -ENOMEM; + isa->io = drv->io_params[dev]; + v4l2_dev = &isa->v4l2_dev; + + if (drv->probe && ops->probe) { + int i; + + for (i = 0; i < drv->num_of_io_ports; ++i) { + int io = drv->io_ports[i]; + + if (request_region(io, drv->region_size, v4l2_dev->name)) { + bool found = ops->probe(isa, io); + + release_region(io, drv->region_size); + if (found) { + isa->io = io; + break; + } + } + } + } + + if (!radio_isa_valid_io(drv, isa->io)) { + int i; + + if (isa->io < 0) + return -ENODEV; + v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x", + drv->io_ports[0]); + for (i = 1; i < drv->num_of_io_ports; i++) + printk(KERN_CONT "/0x%03x", drv->io_ports[i]); + printk(KERN_CONT ".\n"); + kfree(isa); + return -EINVAL; + } + + return radio_isa_common_probe(isa, pdev, drv->radio_nr_params[dev], + drv->region_size); +} +EXPORT_SYMBOL_GPL(radio_isa_probe); + +int radio_isa_remove(struct device *pdev, unsigned int dev) +{ + struct radio_isa_card *isa = dev_get_drvdata(pdev); + + return radio_isa_common_remove(isa, isa->drv->region_size); +} +EXPORT_SYMBOL_GPL(radio_isa_remove); + +#ifdef CONFIG_PNP +int radio_isa_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) +{ + struct pnp_driver *pnp_drv = to_pnp_driver(dev->dev.driver); + struct radio_isa_driver *drv = container_of(pnp_drv, + struct radio_isa_driver, pnp_driver); + struct radio_isa_card *isa; + + if (!pnp_port_valid(dev, 0)) + return -ENODEV; + + isa = radio_isa_alloc(drv, &dev->dev); + if (!isa) + return -ENOMEM; + + isa->io = pnp_port_start(dev, 0); + + return radio_isa_common_probe(isa, &dev->dev, drv->radio_nr_params[0], + pnp_port_len(dev, 0)); +} +EXPORT_SYMBOL_GPL(radio_isa_pnp_probe); + +void radio_isa_pnp_remove(struct pnp_dev *dev) +{ + struct radio_isa_card *isa = dev_get_drvdata(&dev->dev); + + radio_isa_common_remove(isa, pnp_port_len(dev, 0)); +} +EXPORT_SYMBOL_GPL(radio_isa_pnp_remove); +#endif diff --git a/drivers/media/radio/radio-isa.h b/drivers/media/radio/radio-isa.h new file mode 100644 index 00000000000..ba4c01f1bd0 --- /dev/null +++ b/drivers/media/radio/radio-isa.h @@ -0,0 +1,114 @@ +/* + * Framework for ISA radio drivers. + * This takes care of all the V4L2 scaffolding, allowing the ISA drivers + * to concentrate on the actual hardware operation. + * + * Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef _RADIO_ISA_H_ +#define _RADIO_ISA_H_ + +#include <linux/isa.h> +#include <linux/pnp.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> + +struct radio_isa_driver; +struct radio_isa_ops; + +/* Core structure for radio ISA cards */ +struct radio_isa_card { + const struct radio_isa_driver *drv; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct video_device vdev; + struct mutex lock; + const struct radio_isa_ops *ops; + struct { /* mute/volume cluster */ + struct v4l2_ctrl *mute; + struct v4l2_ctrl *volume; + }; + /* I/O port */ + int io; + + /* Card is in stereo audio mode */ + bool stereo; + /* Current frequency */ + u32 freq; +}; + +struct radio_isa_ops { + /* Allocate and initialize a radio_isa_card struct */ + struct radio_isa_card *(*alloc)(void); + /* Probe whether a card is present at the given port */ + bool (*probe)(struct radio_isa_card *isa, int io); + /* Special card initialization can be done here, this is called after + * the standard controls are registered, but before they are setup, + * thus allowing drivers to add their own controls here. */ + int (*init)(struct radio_isa_card *isa); + /* Set mute and volume. */ + int (*s_mute_volume)(struct radio_isa_card *isa, bool mute, int volume); + /* Set frequency */ + int (*s_frequency)(struct radio_isa_card *isa, u32 freq); + /* Set stereo/mono audio mode */ + int (*s_stereo)(struct radio_isa_card *isa, bool stereo); + /* Get rxsubchans value for VIDIOC_G_TUNER */ + u32 (*g_rxsubchans)(struct radio_isa_card *isa); + /* Get the signal strength for VIDIOC_G_TUNER */ + u32 (*g_signal)(struct radio_isa_card *isa); +}; + +/* Top level structure needed to instantiate the cards */ +struct radio_isa_driver { + struct isa_driver driver; +#ifdef CONFIG_PNP + struct pnp_driver pnp_driver; +#endif + const struct radio_isa_ops *ops; + /* The module_param_array with the specified I/O ports */ + int *io_params; + /* The module_param_array with the radio_nr values */ + int *radio_nr_params; + /* Whether we should probe for possible cards */ + bool probe; + /* The list of possible I/O ports */ + const int *io_ports; + /* The size of that list */ + int num_of_io_ports; + /* The region size to request */ + unsigned region_size; + /* The name of the card */ + const char *card; + /* Card can capture stereo audio */ + bool has_stereo; + /* The maximum volume for the volume control. If 0, then there + is no volume control possible. */ + int max_volume; +}; + +int radio_isa_match(struct device *pdev, unsigned int dev); +int radio_isa_probe(struct device *pdev, unsigned int dev); +int radio_isa_remove(struct device *pdev, unsigned int dev); +#ifdef CONFIG_PNP +int radio_isa_pnp_probe(struct pnp_dev *dev, + const struct pnp_device_id *dev_id); +void radio_isa_pnp_remove(struct pnp_dev *dev); +#endif + +#endif diff --git a/drivers/media/radio/radio-keene.c b/drivers/media/radio/radio-keene.c new file mode 100644 index 00000000000..3d127825ece --- /dev/null +++ b/drivers/media/radio/radio-keene.c @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2012 Hans Verkuil <hverkuil@xs4all.nl> + * + * 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 + */ + +/* kernel includes */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <linux/usb.h> +#include <linux/mutex.h> + +/* driver and module definitions */ +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); +MODULE_DESCRIPTION("Keene FM Transmitter driver"); +MODULE_LICENSE("GPL"); + +/* Actually, it advertises itself as a Logitech */ +#define USB_KEENE_VENDOR 0x046d +#define USB_KEENE_PRODUCT 0x0a0e + +/* Probably USB_TIMEOUT should be modified in module parameter */ +#define BUFFER_LENGTH 8 +#define USB_TIMEOUT 500 + +/* Frequency limits in MHz */ +#define FREQ_MIN 76U +#define FREQ_MAX 108U +#define FREQ_MUL 16000U + +/* USB Device ID List */ +static struct usb_device_id usb_keene_device_table[] = { + {USB_DEVICE_AND_INTERFACE_INFO(USB_KEENE_VENDOR, USB_KEENE_PRODUCT, + USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_keene_device_table); + +struct keene_device { + struct usb_device *usbdev; + struct usb_interface *intf; + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct mutex lock; + + u8 *buffer; + unsigned curfreq; + u8 tx; + u8 pa; + bool stereo; + bool muted; + bool preemph_75_us; +}; + +static inline struct keene_device *to_keene_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct keene_device, v4l2_dev); +} + +/* Set frequency (if non-0), PA, mute and turn on/off the FM transmitter. */ +static int keene_cmd_main(struct keene_device *radio, unsigned freq, bool play) +{ + unsigned short freq_send = freq ? (freq - 76 * 16000) / 800 : 0; + int ret; + + radio->buffer[0] = 0x00; + radio->buffer[1] = 0x50; + radio->buffer[2] = (freq_send >> 8) & 0xff; + radio->buffer[3] = freq_send & 0xff; + radio->buffer[4] = radio->pa; + /* If bit 4 is set, then tune to the frequency. + If bit 3 is set, then unmute; if bit 2 is set, then mute. + If bit 1 is set, then enter idle mode; if bit 0 is set, + then enter transmit mode. + */ + radio->buffer[5] = (radio->muted ? 4 : 8) | (play ? 1 : 2) | + (freq ? 0x10 : 0); + radio->buffer[6] = 0x00; + radio->buffer[7] = 0x00; + + ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + if (freq) + radio->curfreq = freq; + return 0; +} + +/* Set TX, stereo and preemphasis mode (50 us vs 75 us). */ +static int keene_cmd_set(struct keene_device *radio) +{ + int ret; + + radio->buffer[0] = 0x00; + radio->buffer[1] = 0x51; + radio->buffer[2] = radio->tx; + /* If bit 0 is set, then transmit mono, otherwise stereo. + If bit 2 is set, then enable 75 us preemphasis, otherwise + it is 50 us. */ + radio->buffer[3] = (radio->stereo ? 0 : 1) | (radio->preemph_75_us ? 4 : 0); + radio->buffer[4] = 0x00; + radio->buffer[5] = 0x00; + radio->buffer[6] = 0x00; + radio->buffer[7] = 0x00; + + ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + return 0; +} + +/* Handle unplugging the device. + * We call video_unregister_device in any case. + * The last function called in this procedure is + * usb_keene_device_release. + */ +static void usb_keene_disconnect(struct usb_interface *intf) +{ + struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); + + mutex_lock(&radio->lock); + usb_set_intfdata(intf, NULL); + video_unregister_device(&radio->vdev); + v4l2_device_disconnect(&radio->v4l2_dev); + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); +} + +static int usb_keene_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); + + return keene_cmd_main(radio, 0, false); +} + +static int usb_keene_resume(struct usb_interface *intf) +{ + struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); + + mdelay(50); + keene_cmd_set(radio); + keene_cmd_main(radio, radio->curfreq, true); + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct keene_device *radio = video_drvdata(file); + + strlcpy(v->driver, "radio-keene", sizeof(v->driver)); + strlcpy(v->card, "Keene FM Transmitter", sizeof(v->card)); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_MODULATOR; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int vidioc_g_modulator(struct file *file, void *priv, + struct v4l2_modulator *v) +{ + struct keene_device *radio = video_drvdata(file); + + if (v->index > 0) + return -EINVAL; + + strlcpy(v->name, "FM", sizeof(v->name)); + v->rangelow = FREQ_MIN * FREQ_MUL; + v->rangehigh = FREQ_MAX * FREQ_MUL; + v->txsubchans = radio->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; + return 0; +} + +static int vidioc_s_modulator(struct file *file, void *priv, + const struct v4l2_modulator *v) +{ + struct keene_device *radio = video_drvdata(file); + + if (v->index > 0) + return -EINVAL; + + radio->stereo = (v->txsubchans == V4L2_TUNER_SUB_STEREO); + return keene_cmd_set(radio); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct keene_device *radio = video_drvdata(file); + unsigned freq = f->frequency; + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); + return keene_cmd_main(radio, freq, true); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct keene_device *radio = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = radio->curfreq; + return 0; +} + +static int keene_s_ctrl(struct v4l2_ctrl *ctrl) +{ + static const u8 db2tx[] = { + /* -15, -12, -9, -6, -3, 0 dB */ + 0x03, 0x13, 0x02, 0x12, 0x22, 0x32, + /* 3, 6, 9, 12, 15, 18 dB */ + 0x21, 0x31, 0x20, 0x30, 0x40, 0x50 + }; + struct keene_device *radio = + container_of(ctrl->handler, struct keene_device, hdl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + radio->muted = ctrl->val; + return keene_cmd_main(radio, 0, true); + + case V4L2_CID_TUNE_POWER_LEVEL: + /* To go from dBuV to the register value we apply the + following formula: */ + radio->pa = (ctrl->val - 71) * 100 / 62; + return keene_cmd_main(radio, 0, true); + + case V4L2_CID_TUNE_PREEMPHASIS: + radio->preemph_75_us = ctrl->val == V4L2_PREEMPHASIS_75_uS; + return keene_cmd_set(radio); + + case V4L2_CID_AUDIO_COMPRESSION_GAIN: + radio->tx = db2tx[(ctrl->val - ctrl->minimum) / ctrl->step]; + return keene_cmd_set(radio); + } + return -EINVAL; +} + +/* File system interface */ +static const struct v4l2_file_operations usb_keene_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ctrl_ops keene_ctrl_ops = { + .s_ctrl = keene_s_ctrl, +}; + +static const struct v4l2_ioctl_ops usb_keene_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_modulator = vidioc_g_modulator, + .vidioc_s_modulator = vidioc_s_modulator, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void usb_keene_video_device_release(struct v4l2_device *v4l2_dev) +{ + struct keene_device *radio = to_keene_dev(v4l2_dev); + + /* free rest memory */ + v4l2_ctrl_handler_free(&radio->hdl); + kfree(radio->buffer); + kfree(radio); +} + +/* check if the device is present and register with v4l and usb if it is */ +static int usb_keene_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct keene_device *radio; + struct v4l2_ctrl_handler *hdl; + int retval = 0; + + /* + * The Keene FM transmitter USB device has the same USB ID as + * the Logitech AudioHub Speaker, but it should ignore the hid. + * Check if the name is that of the Keene device. + * If not, then someone connected the AudioHub and we shouldn't + * attempt to handle this driver. + * For reference: the product name of the AudioHub is + * "AudioHub Speaker". + */ + if (dev->product && strcmp(dev->product, "B-LINK USB Audio ")) + return -ENODEV; + + radio = kzalloc(sizeof(struct keene_device), GFP_KERNEL); + if (radio) + radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); + + if (!radio || !radio->buffer) { + dev_err(&intf->dev, "kmalloc for keene_device failed\n"); + kfree(radio); + retval = -ENOMEM; + goto err; + } + + hdl = &radio->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + v4l2_ctrl_new_std_menu(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS, + V4L2_PREEMPHASIS_75_uS, 1, V4L2_PREEMPHASIS_50_uS); + v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_POWER_LEVEL, + 84, 118, 1, 118); + v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_COMPRESSION_GAIN, + -15, 18, 3, 0); + radio->pa = 118; + radio->tx = 0x32; + radio->stereo = true; + if (hdl->error) { + retval = hdl->error; + + v4l2_ctrl_handler_free(hdl); + goto err_v4l2; + } + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); + goto err_v4l2; + } + + mutex_init(&radio->lock); + + radio->v4l2_dev.ctrl_handler = hdl; + radio->v4l2_dev.release = usb_keene_video_device_release; + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_keene_fops; + radio->vdev.ioctl_ops = &usb_keene_ioctl_ops; + radio->vdev.lock = &radio->lock; + radio->vdev.release = video_device_release_empty; + radio->vdev.vfl_dir = VFL_DIR_TX; + + radio->usbdev = interface_to_usbdev(intf); + radio->intf = intf; + usb_set_intfdata(intf, &radio->v4l2_dev); + + video_set_drvdata(&radio->vdev, radio); + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); + + /* at least 11ms is needed in order to settle hardware */ + msleep(20); + keene_cmd_main(radio, 95.16 * FREQ_MUL, false); + + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); + if (retval < 0) { + dev_err(&intf->dev, "could not register video device\n"); + goto err_vdev; + } + v4l2_ctrl_handler_setup(hdl); + dev_info(&intf->dev, "V4L2 device registered as %s\n", + video_device_node_name(&radio->vdev)); + return 0; + +err_vdev: + v4l2_device_unregister(&radio->v4l2_dev); +err_v4l2: + kfree(radio->buffer); + kfree(radio); +err: + return retval; +} + +/* USB subsystem interface */ +static struct usb_driver usb_keene_driver = { + .name = "radio-keene", + .probe = usb_keene_probe, + .disconnect = usb_keene_disconnect, + .id_table = usb_keene_device_table, + .suspend = usb_keene_suspend, + .resume = usb_keene_resume, + .reset_resume = usb_keene_resume, +}; + +module_usb_driver(usb_keene_driver); + diff --git a/drivers/media/radio/radio-ma901.c b/drivers/media/radio/radio-ma901.c new file mode 100644 index 00000000000..a85b064cb7b --- /dev/null +++ b/drivers/media/radio/radio-ma901.c @@ -0,0 +1,471 @@ +/* + * Driver for the MasterKit MA901 USB FM radio. This device plugs + * into the USB port and an analog audio input or headphones, so this thing + * only deals with initialization, frequency setting, volume. + * + * Copyright (c) 2012 Alexey Klimov <klimov.linux@gmail.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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <linux/usb.h> +#include <linux/mutex.h> + +#define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>" +#define DRIVER_DESC "Masterkit MA901 USB FM radio driver" +#define DRIVER_VERSION "0.0.1" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +#define USB_MA901_VENDOR 0x16c0 +#define USB_MA901_PRODUCT 0x05df + +/* dev_warn macro with driver name */ +#define MA901_DRIVER_NAME "radio-ma901" +#define ma901radio_dev_warn(dev, fmt, arg...) \ + dev_warn(dev, MA901_DRIVER_NAME " - " fmt, ##arg) + +#define ma901radio_dev_err(dev, fmt, arg...) \ + dev_err(dev, MA901_DRIVER_NAME " - " fmt, ##arg) + +/* Probably USB_TIMEOUT should be modified in module parameter */ +#define BUFFER_LENGTH 8 +#define USB_TIMEOUT 500 + +#define FREQ_MIN 87.5 +#define FREQ_MAX 108.0 +#define FREQ_MUL 16000 + +#define MA901_VOLUME_MAX 16 +#define MA901_VOLUME_MIN 0 + +/* Commands that device should understand + * List isn't full and will be updated with implementation of new functions + */ +#define MA901_RADIO_SET_FREQ 0x03 +#define MA901_RADIO_SET_VOLUME 0x04 +#define MA901_RADIO_SET_MONO_STEREO 0x05 + +/* Comfortable defines for ma901radio_set_stereo */ +#define MA901_WANT_STEREO 0x50 +#define MA901_WANT_MONO 0xd0 + +/* module parameter */ +static int radio_nr = -1; +module_param(radio_nr, int, 0); +MODULE_PARM_DESC(radio_nr, "Radio file number"); + +/* Data for one (physical) device */ +struct ma901radio_device { + /* reference to USB and video device */ + struct usb_device *usbdev; + struct usb_interface *intf; + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + + u8 *buffer; + struct mutex lock; /* buffer locking */ + int curfreq; + u16 volume; + int stereo; + bool muted; +}; + +static inline struct ma901radio_device *to_ma901radio_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct ma901radio_device, v4l2_dev); +} + +/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ +static int ma901radio_set_freq(struct ma901radio_device *radio, int freq) +{ + unsigned int freq_send = 0x300 + (freq >> 5) / 25; + int retval; + + radio->buffer[0] = 0x0a; + radio->buffer[1] = MA901_RADIO_SET_FREQ; + radio->buffer[2] = ((freq_send >> 8) & 0xff) + 0x80; + radio->buffer[3] = freq_send & 0xff; + radio->buffer[4] = 0x00; + radio->buffer[5] = 0x00; + radio->buffer[6] = 0x00; + radio->buffer[7] = 0x00; + + retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 9, 0x21, 0x0300, 0, + radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + + radio->curfreq = freq; + return 0; +} + +static int ma901radio_set_volume(struct ma901radio_device *radio, u16 vol_to_set) +{ + int retval; + + radio->buffer[0] = 0x0a; + radio->buffer[1] = MA901_RADIO_SET_VOLUME; + radio->buffer[2] = 0xc2; + radio->buffer[3] = vol_to_set + 0x20; + radio->buffer[4] = 0x00; + radio->buffer[5] = 0x00; + radio->buffer[6] = 0x00; + radio->buffer[7] = 0x00; + + retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 9, 0x21, 0x0300, 0, + radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + + radio->volume = vol_to_set; + return retval; +} + +static int ma901_set_stereo(struct ma901radio_device *radio, u8 stereo) +{ + int retval; + + radio->buffer[0] = 0x0a; + radio->buffer[1] = MA901_RADIO_SET_MONO_STEREO; + radio->buffer[2] = stereo; + radio->buffer[3] = 0x00; + radio->buffer[4] = 0x00; + radio->buffer[5] = 0x00; + radio->buffer[6] = 0x00; + radio->buffer[7] = 0x00; + + retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 9, 0x21, 0x0300, 0, + radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + + if (retval < 0) + return retval; + + if (stereo == MA901_WANT_STEREO) + radio->stereo = V4L2_TUNER_MODE_STEREO; + else + radio->stereo = V4L2_TUNER_MODE_MONO; + + return retval; +} + +/* Handle unplugging the device. + * We call video_unregister_device in any case. + * The last function called in this procedure is + * usb_ma901radio_device_release. + */ +static void usb_ma901radio_disconnect(struct usb_interface *intf) +{ + struct ma901radio_device *radio = to_ma901radio_dev(usb_get_intfdata(intf)); + + mutex_lock(&radio->lock); + video_unregister_device(&radio->vdev); + usb_set_intfdata(intf, NULL); + v4l2_device_disconnect(&radio->v4l2_dev); + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); +} + +/* vidioc_querycap - query device capabilities */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct ma901radio_device *radio = video_drvdata(file); + + strlcpy(v->driver, "radio-ma901", sizeof(v->driver)); + strlcpy(v->card, "Masterkit MA901 USB FM Radio", sizeof(v->card)); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +/* vidioc_g_tuner - get tuner attributes */ +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct ma901radio_device *radio = video_drvdata(file); + + if (v->index > 0) + return -EINVAL; + + v->signal = 0; + + /* TODO: the same words like in _probe() goes here. + * When receiving of stats will be implemented then we can call + * ma901radio_get_stat(). + * retval = ma901radio_get_stat(radio, &is_stereo, &v->signal); + */ + + strcpy(v->name, "FM"); + v->type = V4L2_TUNER_RADIO; + v->rangelow = FREQ_MIN * FREQ_MUL; + v->rangehigh = FREQ_MAX * FREQ_MUL; + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; + /* v->rxsubchans = is_stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; */ + v->audmode = radio->stereo ? + V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO; + return 0; +} + +/* vidioc_s_tuner - set tuner attributes */ +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + struct ma901radio_device *radio = video_drvdata(file); + + if (v->index > 0) + return -EINVAL; + + /* mono/stereo selector */ + switch (v->audmode) { + case V4L2_TUNER_MODE_MONO: + return ma901_set_stereo(radio, MA901_WANT_MONO); + default: + return ma901_set_stereo(radio, MA901_WANT_STEREO); + } +} + +/* vidioc_s_frequency - set tuner radio frequency */ +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct ma901radio_device *radio = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + + return ma901radio_set_freq(radio, clamp_t(unsigned, f->frequency, + FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL)); +} + +/* vidioc_g_frequency - get tuner radio frequency */ +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct ma901radio_device *radio = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->frequency = radio->curfreq; + + return 0; +} + +static int usb_ma901radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ma901radio_device *radio = + container_of(ctrl->handler, struct ma901radio_device, hdl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: /* set volume */ + return ma901radio_set_volume(radio, (u16)ctrl->val); + } + + return -EINVAL; +} + +/* TODO: Should we really need to implement suspend and resume functions? + * Radio has it's own memory and will continue playing if power is present + * on usb port and on resume it will start to play again based on freq, volume + * values in device memory. + */ +static int usb_ma901radio_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int usb_ma901radio_resume(struct usb_interface *intf) +{ + return 0; +} + +static const struct v4l2_ctrl_ops usb_ma901radio_ctrl_ops = { + .s_ctrl = usb_ma901radio_s_ctrl, +}; + +/* File system interface */ +static const struct v4l2_file_operations usb_ma901radio_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops usb_ma901radio_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_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void usb_ma901radio_release(struct v4l2_device *v4l2_dev) +{ + struct ma901radio_device *radio = to_ma901radio_dev(v4l2_dev); + + v4l2_ctrl_handler_free(&radio->hdl); + v4l2_device_unregister(&radio->v4l2_dev); + kfree(radio->buffer); + kfree(radio); +} + +/* check if the device is present and register with v4l and usb if it is */ +static int usb_ma901radio_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct ma901radio_device *radio; + int retval = 0; + + /* Masterkit MA901 usb radio has the same USB ID as many others + * Atmel V-USB devices. Let's make additional checks to be sure + * that this is our device. + */ + + if (dev->product && dev->manufacturer && + (strncmp(dev->product, "MA901", 5) != 0 + || strncmp(dev->manufacturer, "www.masterkit.ru", 16) != 0)) + return -ENODEV; + + radio = kzalloc(sizeof(struct ma901radio_device), GFP_KERNEL); + if (!radio) { + dev_err(&intf->dev, "kzalloc for ma901radio_device failed\n"); + retval = -ENOMEM; + goto err; + } + + radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); + if (!radio->buffer) { + dev_err(&intf->dev, "kmalloc for radio->buffer failed\n"); + retval = -ENOMEM; + goto err_nobuf; + } + + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); + goto err_v4l2; + } + + v4l2_ctrl_handler_init(&radio->hdl, 1); + + /* TODO:It looks like this radio doesn't have mute/unmute control + * and windows program just emulate it using volume control. + * Let's plan to do the same in this driver. + * + * v4l2_ctrl_new_std(&radio->hdl, &usb_ma901radio_ctrl_ops, + * V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + */ + + v4l2_ctrl_new_std(&radio->hdl, &usb_ma901radio_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, MA901_VOLUME_MIN, + MA901_VOLUME_MAX, 1, MA901_VOLUME_MAX); + + if (radio->hdl.error) { + retval = radio->hdl.error; + dev_err(&intf->dev, "couldn't register control\n"); + goto err_ctrl; + } + mutex_init(&radio->lock); + + radio->v4l2_dev.ctrl_handler = &radio->hdl; + radio->v4l2_dev.release = usb_ma901radio_release; + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_ma901radio_fops; + radio->vdev.ioctl_ops = &usb_ma901radio_ioctl_ops; + radio->vdev.release = video_device_release_empty; + radio->vdev.lock = &radio->lock; + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); + + radio->usbdev = interface_to_usbdev(intf); + radio->intf = intf; + usb_set_intfdata(intf, &radio->v4l2_dev); + radio->curfreq = 95.21 * FREQ_MUL; + + video_set_drvdata(&radio->vdev, radio); + + /* TODO: we can get some statistics (freq, volume) from device + * but it's not implemented yet. After insertion in usb-port radio + * setups frequency and starts playing without any initialization. + * So we don't call usb_ma901radio_init/get_stat() here. + * retval = usb_ma901radio_init(radio); + */ + + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, + radio_nr); + if (retval < 0) { + dev_err(&intf->dev, "could not register video device\n"); + goto err_vdev; + } + + return 0; + +err_vdev: + v4l2_ctrl_handler_free(&radio->hdl); +err_ctrl: + v4l2_device_unregister(&radio->v4l2_dev); +err_v4l2: + kfree(radio->buffer); +err_nobuf: + kfree(radio); +err: + return retval; +} + +/* USB Device ID List */ +static struct usb_device_id usb_ma901radio_device_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(USB_MA901_VENDOR, USB_MA901_PRODUCT, + USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_ma901radio_device_table); + +/* USB subsystem interface */ +static struct usb_driver usb_ma901radio_driver = { + .name = MA901_DRIVER_NAME, + .probe = usb_ma901radio_probe, + .disconnect = usb_ma901radio_disconnect, + .suspend = usb_ma901radio_suspend, + .resume = usb_ma901radio_resume, + .reset_resume = usb_ma901radio_resume, + .id_table = usb_ma901radio_device_table, +}; + +module_usb_driver(usb_ma901radio_driver); diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c index f872a54cf3d..5236035f0f2 100644 --- a/drivers/media/radio/radio-maxiradio.c +++ b/drivers/media/radio/radio-maxiradio.c @@ -42,67 +42,37 @@ #include <linux/videodev2.h> #include <linux/io.h> #include <linux/slab.h> +#include <media/tea575x.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> - -#define DRIVER_VERSION "0.7.8" - +#include <media/v4l2-fh.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net"); -MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio."); +MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000."); MODULE_LICENSE("GPL"); -MODULE_VERSION(DRIVER_VERSION); +MODULE_VERSION("1.0.0"); static int radio_nr = -1; -module_param(radio_nr, int, 0); - -static int debug; - -module_param(debug, int, 0644); -MODULE_PARM_DESC(debug, "activates debug info"); - -#define dprintk(dev, num, fmt, arg...) \ - v4l2_dbg(num, debug, &dev->v4l2_dev, fmt, ## arg) - -#ifndef PCI_VENDOR_ID_GUILLEMOT -#define PCI_VENDOR_ID_GUILLEMOT 0x5046 -#endif - -#ifndef PCI_DEVICE_ID_GUILLEMOT -#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 -#endif - +module_param(radio_nr, int, 0644); +MODULE_PARM_DESC(radio_nr, "Radio device number"); /* TEA5757 pin mappings */ static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16; -#define FREQ_LO (87 * 16000) -#define FREQ_HI (108 * 16000) - -#define FREQ_IF 171200 /* 10.7*16000 */ -#define FREQ_STEP 200 /* 12.5*16 */ - -/* (x==fmhz*16*1000) -> bits */ -#define FREQ2BITS(x) \ - ((((unsigned int)(x) + FREQ_IF + (FREQ_STEP << 1)) / (FREQ_STEP << 2)) << 2) - -#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF) +static atomic_t maxiradio_instance = ATOMIC_INIT(0); +#define PCI_VENDOR_ID_GUILLEMOT 0x5046 +#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001 struct maxiradio { + struct snd_tea575x tea; struct v4l2_device v4l2_dev; - struct video_device vdev; struct pci_dev *pdev; u16 io; /* base of radio io */ - u16 muted; /* VIDEO_AUDIO_MUTE */ - u16 stereo; /* VIDEO_TUNER_STEREO_ON */ - u16 tuned; /* signal strength (0 or 0xffff) */ - - unsigned long freq; - - struct mutex lock; }; static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev) @@ -110,259 +80,42 @@ static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev) return container_of(v4l2_dev, struct maxiradio, v4l2_dev); } -static void outbit(unsigned long bit, u16 io) +static void maxiradio_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) { - int val = power | wren | (bit ? data : 0); - - outb(val, io); - udelay(4); - outb(val | clk, io); - udelay(4); - outb(val, io); - udelay(4); -} - -static void turn_power(struct maxiradio *dev, int p) -{ - if (p != 0) { - dprintk(dev, 1, "Radio powered on\n"); - outb(power, dev->io); - } else { - dprintk(dev, 1, "Radio powered off\n"); - outb(0, dev->io); - } -} - -static void set_freq(struct maxiradio *dev, u32 freq) -{ - unsigned long int si; - int bl; - int io = dev->io; - int val = FREQ2BITS(freq); - - /* TEA5757 shift register bits (see pdf) */ - - outbit(0, io); /* 24 search */ - outbit(1, io); /* 23 search up/down */ - - outbit(0, io); /* 22 stereo/mono */ + struct maxiradio *dev = tea->private_data; + u8 bits = 0; - outbit(0, io); /* 21 band */ - outbit(0, io); /* 20 band (only 00=FM works I think) */ + bits |= (pins & TEA575X_DATA) ? data : 0; + bits |= (pins & TEA575X_CLK) ? clk : 0; + bits |= (pins & TEA575X_WREN) ? wren : 0; + bits |= power; - outbit(0, io); /* 19 port ? */ - outbit(0, io); /* 18 port ? */ - - outbit(0, io); /* 17 search level */ - outbit(0, io); /* 16 search level */ - - si = 0x8000; - for (bl = 1; bl <= 16; bl++) { - outbit(val & si, io); - si >>= 1; - } - - dprintk(dev, 1, "Radio freq set to %d.%02d MHz\n", - freq / 16000, - freq % 16000 * 100 / 16000); - - turn_power(dev, 1); -} - -static int get_stereo(u16 io) -{ - outb(power,io); - udelay(4); - - return !(inb(io) & mo_st); -} - -static int get_tune(u16 io) -{ - outb(power+clk,io); - udelay(4); - - return !(inb(io) & mo_st); + outb(bits, dev->io); } - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) +/* Note: this card cannot read out the data of the shift registers, + only the mono/stereo pin works. */ +static u8 maxiradio_tea575x_get_pins(struct snd_tea575x *tea) { - struct maxiradio *dev = video_drvdata(file); - - strlcpy(v->driver, "radio-maxiradio", sizeof(v->driver)); - strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof(v->card)); - snprintf(v->bus_info, sizeof(v->bus_info), "PCI:%s", pci_name(dev->pdev)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} + struct maxiradio *dev = tea->private_data; + u8 bits = inb(dev->io); -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct maxiradio *dev = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - mutex_lock(&dev->lock); - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = FREQ_LO; - v->rangehigh = FREQ_HI; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (get_stereo(dev->io)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xffff * get_tune(dev->io); - mutex_unlock(&dev->lock); - - return 0; + return ((bits & data) ? TEA575X_DATA : 0) | + ((bits & mo_st) ? TEA575X_MOST : 0); } -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) +static void maxiradio_tea575x_set_direction(struct snd_tea575x *tea, bool output) { - return v->index ? -EINVAL : 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; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct maxiradio *dev = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) { - dprintk(dev, 1, "radio freq (%d.%02d MHz) out of range (%d-%d)\n", - f->frequency / 16000, - f->frequency % 16000 * 100 / 16000, - FREQ_LO / 16000, FREQ_HI / 16000); - - return -EINVAL; - } - - mutex_lock(&dev->lock); - dev->freq = f->frequency; - set_freq(dev, dev->freq); - msleep(125); - mutex_unlock(&dev->lock); - - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct maxiradio *dev = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = dev->freq; - - dprintk(dev, 4, "radio freq is %d.%02d MHz", - f->frequency / 16000, - f->frequency % 16000 * 100 / 16000); - - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct maxiradio *dev = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = dev->muted; - return 0; - } - - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct maxiradio *dev = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - mutex_lock(&dev->lock); - dev->muted = ctrl->value; - if (dev->muted) - turn_power(dev, 0); - else - set_freq(dev, dev->freq); - mutex_unlock(&dev->lock); - return 0; - } - - return -EINVAL; -} - -static const struct v4l2_file_operations maxiradio_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, -}; - -static const struct v4l2_ioctl_ops maxiradio_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .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_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, +static struct snd_tea575x_ops maxiradio_tea_ops = { + .set_pins = maxiradio_tea575x_set_pins, + .get_pins = maxiradio_tea575x_get_pins, + .set_direction = maxiradio_tea575x_set_direction, }; -static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) +static int maxiradio_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) { struct maxiradio *dev; struct v4l2_device *v4l2_dev; @@ -375,63 +128,60 @@ static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_d } v4l2_dev = &dev->v4l2_dev; - mutex_init(&dev->lock); - dev->pdev = pdev; - dev->muted = 1; - dev->freq = FREQ_LO; - - strlcpy(v4l2_dev->name, "maxiradio", sizeof(v4l2_dev->name)); + v4l2_device_set_name(v4l2_dev, "maxiradio", &maxiradio_instance); retval = v4l2_device_register(&pdev->dev, v4l2_dev); if (retval < 0) { v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); goto errfr; } + dev->tea.private_data = dev; + dev->tea.ops = &maxiradio_tea_ops; + /* The data pin cannot be read. This may be a hardware limitation, or + we just don't know how to read it. */ + dev->tea.cannot_read_data = true; + dev->tea.v4l2_dev = v4l2_dev; + dev->tea.radio_nr = radio_nr; + strlcpy(dev->tea.card, "Maxi Radio FM2000", sizeof(dev->tea.card)); + snprintf(dev->tea.bus_info, sizeof(dev->tea.bus_info), + "PCI:%s", pci_name(pdev)); + + retval = -ENODEV; if (!request_region(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) { - v4l2_err(v4l2_dev, "can't reserve I/O ports\n"); - goto err_out; + pci_resource_len(pdev, 0), v4l2_dev->name)) { + dev_err(&pdev->dev, "can't reserve I/O ports\n"); + goto err_hdl; } if (pci_enable_device(pdev)) goto err_out_free_region; dev->io = pci_resource_start(pdev, 0); - strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); - dev->vdev.v4l2_dev = v4l2_dev; - dev->vdev.fops = &maxiradio_fops; - dev->vdev.ioctl_ops = &maxiradio_ioctl_ops; - dev->vdev.release = video_device_release_empty; - video_set_drvdata(&dev->vdev, dev); - - if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_err(v4l2_dev, "can't register device!"); + if (snd_tea575x_init(&dev->tea, THIS_MODULE)) { + printk(KERN_ERR "radio-maxiradio: Unable to detect TEA575x tuner\n"); goto err_out_free_region; } - - v4l2_info(v4l2_dev, "version " DRIVER_VERSION "\n"); - - v4l2_info(v4l2_dev, "found Guillemot MAXI Radio device (io = 0x%x)\n", - dev->io); return 0; err_out_free_region: release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); -err_out: +err_hdl: v4l2_device_unregister(v4l2_dev); errfr: kfree(dev); - return -ENODEV; + return retval; } -static void __devexit maxiradio_remove_one(struct pci_dev *pdev) +static void maxiradio_remove(struct pci_dev *pdev) { struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev); struct maxiradio *dev = to_maxiradio(v4l2_dev); - video_unregister_device(&dev->vdev); - v4l2_device_unregister(&dev->v4l2_dev); + snd_tea575x_exit(&dev->tea); + /* Turn off power */ + outb(0, dev->io); + v4l2_device_unregister(v4l2_dev); release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); } @@ -446,19 +196,8 @@ MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl); static struct pci_driver maxiradio_driver = { .name = "radio-maxiradio", .id_table = maxiradio_pci_tbl, - .probe = maxiradio_init_one, - .remove = __devexit_p(maxiradio_remove_one), + .probe = maxiradio_probe, + .remove = maxiradio_remove, }; -static int __init maxiradio_radio_init(void) -{ - return pci_register_driver(&maxiradio_driver); -} - -static void __exit maxiradio_radio_exit(void) -{ - pci_unregister_driver(&maxiradio_driver); -} - -module_init(maxiradio_radio_init); -module_exit(maxiradio_radio_exit); +module_pci_driver(maxiradio_driver); diff --git a/drivers/media/radio/radio-miropcm20.c b/drivers/media/radio/radio-miropcm20.c index 87c1ee13b05..a7e93d7477d 100644 --- a/drivers/media/radio/radio-miropcm20.c +++ b/drivers/media/radio/radio-miropcm20.c @@ -17,49 +17,36 @@ #include <linux/videodev2.h> #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> #include <sound/aci.h> static int radio_nr = -1; module_param(radio_nr, int, 0); MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)"); -static bool mono; -module_param(mono, bool, 0); -MODULE_PARM_DESC(mono, "Force tuner into mono mode."); - struct pcm20 { struct v4l2_device v4l2_dev; struct video_device vdev; + struct v4l2_ctrl_handler ctrl_handler; unsigned long freq; - int muted; + u32 audmode; struct snd_miro_aci *aci; struct mutex lock; }; static struct pcm20 pcm20_card = { - .freq = 87*16000, - .muted = 1, + .freq = 87 * 16000, + .audmode = V4L2_TUNER_MODE_STEREO, }; -static int pcm20_mute(struct pcm20 *dev, unsigned char mute) -{ - dev->muted = mute; - return snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, mute, -1); -} - -static int pcm20_stereo(struct pcm20 *dev, unsigned char stereo) -{ - return snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, !stereo, -1); -} - static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq) { unsigned char freql; unsigned char freqh; struct snd_miro_aci *aci = dev->aci; - dev->freq = freq; - freq /= 160; if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0)) freq /= 10; /* I don't know exactly which version @@ -67,46 +54,68 @@ static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq) freql = freq & 0xff; freqh = freq >> 8; - pcm20_stereo(dev, !mono); return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh); } static const struct v4l2_file_operations pcm20_fops = { .owner = THIS_MODULE, + .open = v4l2_fh_open, + .poll = v4l2_ctrl_poll, + .release = v4l2_fh_release, .unlocked_ioctl = video_ioctl2, }; static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *v) { + struct pcm20 *dev = video_drvdata(file); + strlcpy(v->driver, "Miro PCM20", sizeof(v->driver)); strlcpy(v->card, "Miro PCM20", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->version = 0x1; - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", dev->v4l2_dev.name); + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + 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) { - if (v->index) /* Only 1 tuner */ + struct pcm20 *dev = video_drvdata(file); + int res; + + if (v->index) return -EINVAL; strlcpy(v->name, "FM", sizeof(v->name)); v->type = V4L2_TUNER_RADIO; v->rangelow = 87*16000; v->rangehigh = 108*16000; - v->signal = 0xffff; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; + res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTATION, -1, -1); + v->signal = (res & 0x80) ? 0 : 0xffff; + /* Note: stereo detection does not work if the audio is muted, + it will default to mono in that case. */ + res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTEREO, -1, -1); + v->rxsubchans = (res & 0x40) ? V4L2_TUNER_SUB_MONO : + V4L2_TUNER_SUB_STEREO; + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; + v->audmode = dev->audmode; return 0; } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { - return v->index ? -EINVAL : 0; + struct pcm20 *dev = video_drvdata(file); + + if (v->index) + return -EINVAL; + if (v->audmode > V4L2_TUNER_MODE_STEREO) + dev->audmode = V4L2_TUNER_MODE_STEREO; + else + dev->audmode = v->audmode; + snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, + dev->audmode == V4L2_TUNER_MODE_MONO, -1); + return 0; } static int vidioc_g_frequency(struct file *file, void *priv, @@ -124,82 +133,28 @@ static int vidioc_g_frequency(struct file *file, void *priv, static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct pcm20 *dev = video_drvdata(file); if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; - dev->freq = f->frequency; - pcm20_setfreq(dev, f->frequency); - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct pcm20 *dev = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = dev->muted; - break; - default: - return -EINVAL; - } + dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U); + pcm20_setfreq(dev, dev->freq); return 0; } -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int pcm20_s_ctrl(struct v4l2_ctrl *ctrl) { - struct pcm20 *dev = video_drvdata(file); + struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler); switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: - pcm20_mute(dev, ctrl->value); - break; - default: - return -EINVAL; + snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -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 const struct v4l2_ioctl_ops pcm20_ioctl_ops = { @@ -208,19 +163,20 @@ static const struct v4l2_ioctl_ops pcm20_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_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 pcm20_ctrl_ops = { + .s_ctrl = pcm20_s_ctrl, }; static int __init pcm20_init(void) { struct pcm20 *dev = &pcm20_card; struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + struct v4l2_ctrl_handler *hdl; int res; dev->aci = snd_aci_get_aci(); @@ -229,7 +185,7 @@ static int __init pcm20_init(void) "you must load the snd-miro driver first!\n"); return -ENODEV; } - strlcpy(v4l2_dev->name, "miropcm20", sizeof(v4l2_dev->name)); + strlcpy(v4l2_dev->name, "radio-miropcm20", sizeof(v4l2_dev->name)); mutex_init(&dev->lock); res = v4l2_device_register(NULL, v4l2_dev); @@ -238,20 +194,35 @@ static int __init pcm20_init(void) return -EINVAL; } + hdl = &dev->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 1); + v4l2_ctrl_new_std(hdl, &pcm20_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 control\n"); + goto err_hdl; + } strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); dev->vdev.v4l2_dev = v4l2_dev; dev->vdev.fops = &pcm20_fops; dev->vdev.ioctl_ops = &pcm20_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); + snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, + dev->audmode == V4L2_TUNER_MODE_MONO, -1); + pcm20_setfreq(dev, dev->freq); if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) - goto fail; + goto err_hdl; v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n"); return 0; -fail: +err_hdl: + v4l2_ctrl_handler_free(hdl); v4l2_device_unregister(v4l2_dev); return -EINVAL; } @@ -265,6 +236,8 @@ static void __exit pcm20_cleanup(void) struct pcm20 *dev = &pcm20_card; video_unregister_device(&dev->vdev); + snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, 1, -1); + v4l2_ctrl_handler_free(&dev->ctrl_handler); v4l2_device_unregister(&dev->v4l2_dev); } diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c index a860a72a58e..a360227ca3a 100644 --- a/drivers/media/radio/radio-mr800.c +++ b/drivers/media/radio/radio-mr800.c @@ -62,6 +62,8 @@ #include <linux/videodev2.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> #include <linux/usb.h> #include <linux/mutex.h> @@ -101,12 +103,17 @@ devices, that would be 76 and 91. */ * List isn't full and will be updated with implementation of new functions */ #define AMRADIO_SET_FREQ 0xa4 +#define AMRADIO_GET_READY_FLAG 0xa5 +#define AMRADIO_GET_SIGNAL 0xa7 +#define AMRADIO_GET_FREQ 0xa8 +#define AMRADIO_SET_SEARCH_UP 0xa9 +#define AMRADIO_SET_SEARCH_DOWN 0xaa #define AMRADIO_SET_MUTE 0xab +#define AMRADIO_SET_RIGHT_MUTE 0xac +#define AMRADIO_SET_LEFT_MUTE 0xad #define AMRADIO_SET_MONO 0xae - -/* Comfortable defines for amradio_set_mute */ -#define AMRADIO_START 0x00 -#define AMRADIO_STOP 0x01 +#define AMRADIO_SET_SEARCH_LVL 0xb0 +#define AMRADIO_STOP_SEARCH 0xb1 /* Comfortable defines for amradio_set_stereo */ #define WANT_STEREO 0x00 @@ -117,29 +124,20 @@ static int radio_nr = -1; module_param(radio_nr, int, 0); MODULE_PARM_DESC(radio_nr, "Radio Nr"); -static int usb_amradio_probe(struct usb_interface *intf, - const struct usb_device_id *id); -static void usb_amradio_disconnect(struct usb_interface *intf); -static int usb_amradio_open(struct file *file); -static int usb_amradio_close(struct file *file); -static int usb_amradio_suspend(struct usb_interface *intf, - pm_message_t message); -static int usb_amradio_resume(struct usb_interface *intf); - /* Data for one (physical) device */ struct amradio_device { /* reference to USB and video device */ struct usb_device *usbdev; struct usb_interface *intf; - struct video_device videodev; + struct video_device vdev; struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; - unsigned char *buffer; + u8 *buffer; struct mutex lock; /* buffer locking */ int curfreq; int stereo; int muted; - int initialized; }; static inline struct amradio_device *to_amradio_dev(struct v4l2_device *v4l2_dev) @@ -147,29 +145,8 @@ static inline struct amradio_device *to_amradio_dev(struct v4l2_device *v4l2_dev return container_of(v4l2_dev, struct amradio_device, v4l2_dev); } -/* USB Device ID List */ -static struct usb_device_id usb_amradio_device_table[] = { - {USB_DEVICE_AND_INTERFACE_INFO(USB_AMRADIO_VENDOR, USB_AMRADIO_PRODUCT, - USB_CLASS_HID, 0, 0) }, - { } /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE(usb, usb_amradio_device_table); - -/* USB subsystem interface */ -static struct usb_driver usb_amradio_driver = { - .name = MR800_DRIVER_NAME, - .probe = usb_amradio_probe, - .disconnect = usb_amradio_disconnect, - .suspend = usb_amradio_suspend, - .resume = usb_amradio_resume, - .reset_resume = usb_amradio_resume, - .id_table = usb_amradio_device_table, - .supports_autosuspend = 1, -}; - -/* switch on/off the radio. Send 8 bytes to device */ -static int amradio_set_mute(struct amradio_device *radio, char argument) +static int amradio_send_cmd(struct amradio_device *radio, u8 cmd, u8 arg, + u8 *extra, u8 extralen, bool reply) { int retval; int size; @@ -177,99 +154,96 @@ static int amradio_set_mute(struct amradio_device *radio, char argument) radio->buffer[0] = 0x00; radio->buffer[1] = 0x55; radio->buffer[2] = 0xaa; - radio->buffer[3] = 0x00; - radio->buffer[4] = AMRADIO_SET_MUTE; - radio->buffer[5] = argument; + radio->buffer[3] = extralen; + radio->buffer[4] = cmd; + radio->buffer[5] = arg; radio->buffer[6] = 0x00; - radio->buffer[7] = 0x00; + radio->buffer[7] = extra || reply ? 8 : 0; retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), - (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); + radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT); if (retval < 0 || size != BUFFER_LENGTH) { - amradio_dev_warn(&radio->videodev.dev, "set mute failed\n"); - return retval; + if (video_is_registered(&radio->vdev)) + amradio_dev_warn(&radio->vdev.dev, + "cmd %02x failed\n", cmd); + return retval ? retval : -EIO; } + if (!extra && !reply) + return 0; - radio->muted = argument; + if (extra) { + memcpy(radio->buffer, extra, extralen); + memset(radio->buffer + extralen, 0, 8 - extralen); + retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), + radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT); + } else { + memset(radio->buffer, 0, 8); + retval = usb_bulk_msg(radio->usbdev, usb_rcvbulkpipe(radio->usbdev, 0x81), + radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT); + } + if (retval == 0 && size == BUFFER_LENGTH) + return 0; + if (video_is_registered(&radio->vdev) && cmd != AMRADIO_GET_READY_FLAG) + amradio_dev_warn(&radio->vdev.dev, "follow-up to cmd %02x failed\n", cmd); + return retval ? retval : -EIO; +} - return retval; +/* switch on/off the radio. Send 8 bytes to device */ +static int amradio_set_mute(struct amradio_device *radio, bool mute) +{ + int ret = amradio_send_cmd(radio, + AMRADIO_SET_MUTE, mute, NULL, 0, false); + + if (!ret) + radio->muted = mute; + return ret; } /* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ -static int amradio_setfreq(struct amradio_device *radio, int freq) +static int amradio_set_freq(struct amradio_device *radio, int freq) { + unsigned short freq_send; + u8 buf[3]; int retval; - int size; - unsigned short freq_send = 0x10 + (freq >> 3) / 25; - - radio->buffer[0] = 0x00; - radio->buffer[1] = 0x55; - radio->buffer[2] = 0xaa; - radio->buffer[3] = 0x03; - radio->buffer[4] = AMRADIO_SET_FREQ; - radio->buffer[5] = 0x00; - radio->buffer[6] = 0x00; - radio->buffer[7] = 0x08; - retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), - (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); - - if (retval < 0 || size != BUFFER_LENGTH) - goto out_err; + /* we need to be sure that frequency isn't out of range */ + freq = clamp_t(unsigned, freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); + freq_send = 0x10 + (freq >> 3) / 25; /* frequency is calculated from freq_send and placed in first 2 bytes */ - radio->buffer[0] = (freq_send >> 8) & 0xff; - radio->buffer[1] = freq_send & 0xff; - radio->buffer[2] = 0x01; - radio->buffer[3] = 0x00; - radio->buffer[4] = 0x00; - /* 5 and 6 bytes of buffer already = 0x00 */ - radio->buffer[7] = 0x00; - - retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), - (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); - - if (retval < 0 || size != BUFFER_LENGTH) - goto out_err; + buf[0] = (freq_send >> 8) & 0xff; + buf[1] = freq_send & 0xff; + buf[2] = 0x01; + retval = amradio_send_cmd(radio, AMRADIO_SET_FREQ, 0, buf, 3, false); + if (retval) + return retval; radio->curfreq = freq; - goto out; - -out_err: - amradio_dev_warn(&radio->videodev.dev, "set frequency failed\n"); -out: - return retval; + msleep(40); + return 0; } -static int amradio_set_stereo(struct amradio_device *radio, char argument) +static int amradio_set_stereo(struct amradio_device *radio, bool stereo) { - int retval; - int size; + int ret = amradio_send_cmd(radio, + AMRADIO_SET_MONO, !stereo, NULL, 0, false); - radio->buffer[0] = 0x00; - radio->buffer[1] = 0x55; - radio->buffer[2] = 0xaa; - radio->buffer[3] = 0x00; - radio->buffer[4] = AMRADIO_SET_MONO; - radio->buffer[5] = argument; - radio->buffer[6] = 0x00; - radio->buffer[7] = 0x00; - - retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), - (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); - - if (retval < 0 || size != BUFFER_LENGTH) { - amradio_dev_warn(&radio->videodev.dev, "set stereo failed\n"); - return retval; - } + if (!ret) + radio->stereo = stereo; + return ret; +} - if (argument == WANT_STEREO) - radio->stereo = 1; - else - radio->stereo = 0; +static int amradio_get_stat(struct amradio_device *radio, bool *is_stereo, u32 *signal) +{ + int ret = amradio_send_cmd(radio, + AMRADIO_GET_SIGNAL, 0, NULL, 0, true); - return retval; + if (ret) + return ret; + *is_stereo = radio->buffer[2] >> 7; + *signal = (radio->buffer[3] & 0xf0) << 8; + return 0; } /* Handle unplugging the device. @@ -282,25 +256,26 @@ static void usb_amradio_disconnect(struct usb_interface *intf) struct amradio_device *radio = to_amradio_dev(usb_get_intfdata(intf)); mutex_lock(&radio->lock); - /* increase the device node's refcount */ - get_device(&radio->videodev.dev); + video_unregister_device(&radio->vdev); + amradio_set_mute(radio, true); + usb_set_intfdata(intf, NULL); v4l2_device_disconnect(&radio->v4l2_dev); - video_unregister_device(&radio->videodev); mutex_unlock(&radio->lock); - /* decrease the device node's refcount, allowing it to be released */ - put_device(&radio->videodev.dev); + v4l2_device_put(&radio->v4l2_dev); } /* vidioc_querycap - query device capabilities */ static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *v) { - struct amradio_device *radio = file->private_data; + struct amradio_device *radio = video_drvdata(file); strlcpy(v->driver, "radio-mr800", sizeof(v->driver)); strlcpy(v->card, "AverMedia MR 800 USB FM Radio", sizeof(v->card)); usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER; + v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER | + V4L2_CAP_HW_FREQ_SEEK; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -308,44 +283,35 @@ static int vidioc_querycap(struct file *file, void *priv, static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) { - struct amradio_device *radio = file->private_data; + struct amradio_device *radio = video_drvdata(file); + bool is_stereo = false; int retval; if (v->index > 0) return -EINVAL; -/* TODO: Add function which look is signal stereo or not - * amradio_getstat(radio); - */ - -/* we call amradio_set_stereo to set radio->stereo - * Honestly, amradio_getstat should cover this in future and - * amradio_set_stereo shouldn't be here - */ - retval = amradio_set_stereo(radio, WANT_STEREO); + v->signal = 0; + retval = amradio_get_stat(radio, &is_stereo, &v->signal); + if (retval) + return retval; strcpy(v->name, "FM"); v->type = V4L2_TUNER_RADIO; v->rangelow = FREQ_MIN * FREQ_MUL; v->rangehigh = FREQ_MAX * FREQ_MUL; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (radio->stereo) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xffff; /* Can't get the signal strength, sad.. */ - v->afc = 0; /* Don't know what is this */ - - return retval; + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_HWSEEK_WRAP; + v->rxsubchans = is_stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + v->audmode = radio->stereo ? + V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO; + return 0; } /* vidioc_s_tuner - set tuner attributes */ static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { - struct amradio_device *radio = file->private_data; - int retval = -EINVAL; + struct amradio_device *radio = video_drvdata(file); if (v->index > 0) return -EINVAL; @@ -353,34 +319,30 @@ static int vidioc_s_tuner(struct file *file, void *priv, /* mono/stereo selector */ switch (v->audmode) { case V4L2_TUNER_MODE_MONO: - retval = amradio_set_stereo(radio, WANT_MONO); - break; - case V4L2_TUNER_MODE_STEREO: - retval = amradio_set_stereo(radio, WANT_STEREO); - break; + return amradio_set_stereo(radio, WANT_MONO); + default: + return amradio_set_stereo(radio, WANT_STEREO); } - - return retval; } /* vidioc_s_frequency - set tuner radio frequency */ static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { - struct amradio_device *radio = file->private_data; + struct amradio_device *radio = video_drvdata(file); - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + if (f->tuner != 0) return -EINVAL; - return amradio_setfreq(radio, f->frequency); + return amradio_set_freq(radio, f->frequency); } /* vidioc_g_frequency - get tuner radio frequency */ static int vidioc_g_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { - struct amradio_device *radio = file->private_data; + struct amradio_device *radio = video_drvdata(file); - if (f->tuner != 0) + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; f->type = V4L2_TUNER_RADIO; f->frequency = radio->curfreq; @@ -388,148 +350,105 @@ static int vidioc_g_frequency(struct file *file, void *priv, return 0; } -/* vidioc_queryctrl - enumerate control items */ -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) +static int vidioc_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) { - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } + static u8 buf[8] = { + 0x3d, 0x32, 0x0f, 0x08, 0x3d, 0x32, 0x0f, 0x08 + }; + struct amradio_device *radio = video_drvdata(file); + unsigned long timeout; + int retval; - return -EINVAL; -} + if (seek->tuner != 0 || !seek->wrap_around) + return -EINVAL; -/* vidioc_g_ctrl - get the value of a control */ -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct amradio_device *radio = file->private_data; + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = radio->muted; - return 0; + retval = amradio_send_cmd(radio, + AMRADIO_SET_SEARCH_LVL, 0, buf, 8, false); + if (retval) + return retval; + amradio_set_freq(radio, radio->curfreq); + retval = amradio_send_cmd(radio, + seek->seek_upward ? AMRADIO_SET_SEARCH_UP : AMRADIO_SET_SEARCH_DOWN, + 0, NULL, 0, false); + if (retval) + return retval; + timeout = jiffies + msecs_to_jiffies(30000); + for (;;) { + if (time_after(jiffies, timeout)) { + retval = -ENODATA; + break; + } + if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { + retval = -ERESTARTSYS; + break; + } + retval = amradio_send_cmd(radio, AMRADIO_GET_READY_FLAG, + 0, NULL, 0, true); + if (retval) + continue; + amradio_send_cmd(radio, AMRADIO_GET_FREQ, 0, NULL, 0, true); + if (radio->buffer[1] || radio->buffer[2]) { + /* To check: sometimes radio->curfreq is set to out of range value */ + radio->curfreq = (radio->buffer[1] << 8) | radio->buffer[2]; + radio->curfreq = (radio->curfreq - 0x10) * 200; + amradio_send_cmd(radio, AMRADIO_STOP_SEARCH, + 0, NULL, 0, false); + amradio_set_freq(radio, radio->curfreq); + retval = 0; + break; + } } - - return -EINVAL; + amradio_send_cmd(radio, AMRADIO_STOP_SEARCH, 0, NULL, 0, false); + amradio_set_freq(radio, radio->curfreq); + return retval; } -/* vidioc_s_ctrl - set the value of a control */ -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int usb_amradio_s_ctrl(struct v4l2_ctrl *ctrl) { - struct amradio_device *radio = file->private_data; - int retval = -EINVAL; + struct amradio_device *radio = + container_of(ctrl->handler, struct amradio_device, hdl); switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - retval = amradio_set_mute(radio, AMRADIO_STOP); - else - retval = amradio_set_mute(radio, AMRADIO_START); - - break; + return amradio_set_mute(radio, ctrl->val); } - return retval; -} - -/* vidioc_g_audio - get audio attributes */ -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - if (a->index > 1) - return -EINVAL; - - strcpy(a->name, "Radio"); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -/* vidioc_s_audio - set audio attributes */ -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - if (a->index != 0) - return -EINVAL; - return 0; -} - -/* vidioc_g_input - get input */ -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -/* vidioc_s_input - set input */ -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) -{ - if (i != 0) - return -EINVAL; - return 0; + return -EINVAL; } static int usb_amradio_init(struct amradio_device *radio) { int retval; - retval = amradio_set_mute(radio, AMRADIO_STOP); + retval = amradio_set_mute(radio, true); if (retval) goto out_err; - - retval = amradio_set_stereo(radio, WANT_STEREO); + retval = amradio_set_stereo(radio, true); if (retval) goto out_err; - - radio->initialized = 1; - goto out; - -out_err: - amradio_dev_err(&radio->videodev.dev, "initialization failed\n"); -out: - return retval; -} - -/* open device - amradio_start() and amradio_setfreq() */ -static int usb_amradio_open(struct file *file) -{ - struct amradio_device *radio = video_drvdata(file); - int retval; - - file->private_data = radio; - retval = usb_autopm_get_interface(radio->intf); + retval = amradio_set_freq(radio, radio->curfreq); if (retval) - return retval; + goto out_err; + return 0; - if (unlikely(!radio->initialized)) { - retval = usb_amradio_init(radio); - if (retval) - usb_autopm_put_interface(radio->intf); - } +out_err: + amradio_dev_err(&radio->vdev.dev, "initialization failed\n"); return retval; } -/*close device */ -static int usb_amradio_close(struct file *file) -{ - struct amradio_device *radio = file->private_data; - - if (video_is_registered(&radio->videodev)) - usb_autopm_put_interface(radio->intf); - return 0; -} - /* Suspend device - stop device. Need to be checked and fixed */ static int usb_amradio_suspend(struct usb_interface *intf, pm_message_t message) { struct amradio_device *radio = to_amradio_dev(usb_get_intfdata(intf)); mutex_lock(&radio->lock); - if (!radio->muted && radio->initialized) { - amradio_set_mute(radio, AMRADIO_STOP); - radio->muted = 0; + if (!radio->muted) { + amradio_set_mute(radio, true); + radio->muted = false; } mutex_unlock(&radio->lock); @@ -543,31 +462,28 @@ static int usb_amradio_resume(struct usb_interface *intf) struct amradio_device *radio = to_amradio_dev(usb_get_intfdata(intf)); mutex_lock(&radio->lock); - if (unlikely(!radio->initialized)) - goto unlock; - - if (radio->stereo) - amradio_set_stereo(radio, WANT_STEREO); - else - amradio_set_stereo(radio, WANT_MONO); - - amradio_setfreq(radio, radio->curfreq); + amradio_set_stereo(radio, radio->stereo); + amradio_set_freq(radio, radio->curfreq); if (!radio->muted) - amradio_set_mute(radio, AMRADIO_START); + amradio_set_mute(radio, false); -unlock: mutex_unlock(&radio->lock); dev_info(&intf->dev, "coming out of suspend..\n"); return 0; } +static const struct v4l2_ctrl_ops usb_amradio_ctrl_ops = { + .s_ctrl = usb_amradio_s_ctrl, +}; + /* File system interface */ static const struct v4l2_file_operations usb_amradio_fops = { .owner = THIS_MODULE, - .open = usb_amradio_open, - .release = usb_amradio_close, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, .unlocked_ioctl = video_ioctl2, }; @@ -577,20 +493,19 @@ static const struct v4l2_ioctl_ops usb_amradio_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_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 void usb_amradio_video_device_release(struct video_device *videodev) +static void usb_amradio_release(struct v4l2_device *v4l2_dev) { - struct amradio_device *radio = video_get_drvdata(videodev); + struct amradio_device *radio = to_amradio_dev(v4l2_dev); /* free rest memory */ + v4l2_ctrl_handler_free(&radio->hdl); + v4l2_device_unregister(&radio->v4l2_dev); kfree(radio->buffer); kfree(radio); } @@ -624,23 +539,38 @@ static int usb_amradio_probe(struct usb_interface *intf, goto err_v4l2; } + v4l2_ctrl_handler_init(&radio->hdl, 1); + v4l2_ctrl_new_std(&radio->hdl, &usb_amradio_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + if (radio->hdl.error) { + retval = radio->hdl.error; + dev_err(&intf->dev, "couldn't register control\n"); + goto err_ctrl; + } mutex_init(&radio->lock); - strlcpy(radio->videodev.name, radio->v4l2_dev.name, - sizeof(radio->videodev.name)); - radio->videodev.v4l2_dev = &radio->v4l2_dev; - radio->videodev.fops = &usb_amradio_fops; - radio->videodev.ioctl_ops = &usb_amradio_ioctl_ops; - radio->videodev.release = usb_amradio_video_device_release; - radio->videodev.lock = &radio->lock; + radio->v4l2_dev.ctrl_handler = &radio->hdl; + radio->v4l2_dev.release = usb_amradio_release; + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_amradio_fops; + radio->vdev.ioctl_ops = &usb_amradio_ioctl_ops; + radio->vdev.release = video_device_release_empty; + radio->vdev.lock = &radio->lock; + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); radio->usbdev = interface_to_usbdev(intf); radio->intf = intf; + usb_set_intfdata(intf, &radio->v4l2_dev); radio->curfreq = 95.16 * FREQ_MUL; - video_set_drvdata(&radio->videodev, radio); + video_set_drvdata(&radio->vdev, radio); + retval = usb_amradio_init(radio); + if (retval) + goto err_vdev; - retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, radio_nr); if (retval < 0) { dev_err(&intf->dev, "could not register video device\n"); @@ -650,6 +580,8 @@ static int usb_amradio_probe(struct usb_interface *intf, return 0; err_vdev: + v4l2_ctrl_handler_free(&radio->hdl); +err_ctrl: v4l2_device_unregister(&radio->v4l2_dev); err_v4l2: kfree(radio->buffer); @@ -659,4 +591,24 @@ err: return retval; } +/* USB Device ID List */ +static struct usb_device_id usb_amradio_device_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(USB_AMRADIO_VENDOR, USB_AMRADIO_PRODUCT, + USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_amradio_device_table); + +/* USB subsystem interface */ +static struct usb_driver usb_amradio_driver = { + .name = MR800_DRIVER_NAME, + .probe = usb_amradio_probe, + .disconnect = usb_amradio_disconnect, + .suspend = usb_amradio_suspend, + .resume = usb_amradio_resume, + .reset_resume = usb_amradio_resume, + .id_table = usb_amradio_device_table, +}; + module_usb_driver(usb_amradio_driver); diff --git a/drivers/media/radio/radio-raremono.c b/drivers/media/radio/radio-raremono.c new file mode 100644 index 00000000000..7b3bdbb1be7 --- /dev/null +++ b/drivers/media/radio/radio-raremono.c @@ -0,0 +1,387 @@ +/* + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/hid.h> +#include <linux/mutex.h> +#include <linux/videodev2.h> +#include <asm/unaligned.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> + +/* + * 'Thanko's Raremono' is a Japanese si4734-based AM/FM/SW USB receiver: + * + * http://www.raremono.jp/product/484.html/ + * + * The USB protocol has been reversed engineered using wireshark, initially + * by Dinesh Ram <dinesh.ram@cern.ch> and finished by Hans Verkuil + * <hverkuil@xs4all.nl>. + * + * Sadly the firmware used in this product hides lots of goodies since the + * si4734 has more features than are supported by the firmware. Oh well... + */ + +/* driver and module definitions */ +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); +MODULE_DESCRIPTION("Thanko's Raremono AM/FM/SW Receiver USB driver"); +MODULE_LICENSE("GPL v2"); + +/* + * The Device announces itself as Cygnal Integrated Products, Inc. + * + * The vendor and product IDs (and in fact all other lsusb information as + * well) are identical to the si470x Silicon Labs USB FM Radio Reference + * Design board, even though this card has a si4734 device. Clearly the + * designer of this product never bothered to change the USB IDs. + */ + +/* USB Device ID List */ +static struct usb_device_id usb_raremono_device_table[] = { + {USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_raremono_device_table); + +#define BUFFER_LENGTH 64 + +/* Timeout is set to a high value, could probably be reduced. Need more tests */ +#define USB_TIMEOUT 10000 + +/* Frequency limits in KHz */ +#define FM_FREQ_RANGE_LOW 64000 +#define FM_FREQ_RANGE_HIGH 108000 + +#define AM_FREQ_RANGE_LOW 520 +#define AM_FREQ_RANGE_HIGH 1710 + +#define SW_FREQ_RANGE_LOW 2300 +#define SW_FREQ_RANGE_HIGH 26100 + +enum { BAND_FM, BAND_AM, BAND_SW }; + +static const struct v4l2_frequency_band bands[] = { + /* Band FM */ + { + .type = V4L2_TUNER_RADIO, + .index = 0, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = FM_FREQ_RANGE_LOW * 16, + .rangehigh = FM_FREQ_RANGE_HIGH * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + /* Band AM */ + { + .type = V4L2_TUNER_RADIO, + .index = 1, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = AM_FREQ_RANGE_LOW * 16, + .rangehigh = AM_FREQ_RANGE_HIGH * 16, + .modulation = V4L2_BAND_MODULATION_AM, + }, + /* Band SW */ + { + .type = V4L2_TUNER_RADIO, + .index = 2, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = SW_FREQ_RANGE_LOW * 16, + .rangehigh = SW_FREQ_RANGE_HIGH * 16, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +struct raremono_device { + struct usb_device *usbdev; + struct usb_interface *intf; + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct mutex lock; + + u8 *buffer; + u32 band; + unsigned curfreq; +}; + +static inline struct raremono_device *to_raremono_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct raremono_device, v4l2_dev); +} + +/* Set frequency. */ +static int raremono_cmd_main(struct raremono_device *radio, unsigned band, unsigned freq) +{ + unsigned band_offset; + int ret; + + switch (band) { + case BAND_FM: + band_offset = 1; + freq /= 10; + break; + case BAND_AM: + band_offset = 0; + break; + default: + band_offset = 2; + break; + } + radio->buffer[0] = 0x04 + band_offset; + radio->buffer[1] = freq >> 8; + radio->buffer[2] = freq & 0xff; + + ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + HID_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x0300 + radio->buffer[0], 2, + radio->buffer, 3, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + radio->curfreq = (band == BAND_FM) ? freq * 10 : freq; + return 0; +} + +/* Handle unplugging the device. + * We call video_unregister_device in any case. + * The last function called in this procedure is + * usb_raremono_device_release. + */ +static void usb_raremono_disconnect(struct usb_interface *intf) +{ + struct raremono_device *radio = to_raremono_dev(usb_get_intfdata(intf)); + + dev_info(&intf->dev, "Thanko's Raremono disconnected\n"); + + mutex_lock(&radio->lock); + usb_set_intfdata(intf, NULL); + video_unregister_device(&radio->vdev); + v4l2_device_disconnect(&radio->v4l2_dev); + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); +} + +/* + * Linux Video interface + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct raremono_device *radio = video_drvdata(file); + + strlcpy(v->driver, "radio-raremono", sizeof(v->driver)); + strlcpy(v->card, "Thanko's Raremono", sizeof(v->card)); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int 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; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct raremono_device *radio = video_drvdata(file); + int ret; + + if (v->index > 0) + return -EINVAL; + + strlcpy(v->name, "AM/FM/SW", sizeof(v->name)); + v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS; + v->rangelow = AM_FREQ_RANGE_LOW * 16; + v->rangehigh = FM_FREQ_RANGE_HIGH * 16; + v->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + v->audmode = (radio->curfreq < FM_FREQ_RANGE_LOW) ? + V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; + memset(radio->buffer, 1, BUFFER_LENGTH); + ret = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + 1, 0xa1, 0x030d, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); + + if (ret < 0) { + dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); + return ret; + } + v->signal = ((radio->buffer[1] & 0xf) << 8 | radio->buffer[2]) << 4; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + return v->index ? -EINVAL : 0; +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct raremono_device *radio = video_drvdata(file); + u32 freq = f->frequency; + unsigned band; + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (f->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) * 8) + band = BAND_FM; + else if (f->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) * 8) + band = BAND_AM; + else + band = BAND_SW; + + freq = clamp_t(u32, f->frequency, bands[band].rangelow, bands[band].rangehigh); + return raremono_cmd_main(radio, band, freq / 16); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct raremono_device *radio = video_drvdata(file); + + if (f->tuner != 0) + return -EINVAL; + f->type = V4L2_TUNER_RADIO; + f->frequency = radio->curfreq * 16; + return 0; +} + +/* File system interface */ +static const struct v4l2_file_operations usb_raremono_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops usb_raremono_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_enum_freq_bands = vidioc_enum_freq_bands, +}; + +/* check if the device is present and register with v4l and usb if it is */ +static int usb_raremono_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct raremono_device *radio; + int retval = 0; + + radio = devm_kzalloc(&intf->dev, sizeof(struct raremono_device), GFP_KERNEL); + if (radio) + radio->buffer = devm_kmalloc(&intf->dev, BUFFER_LENGTH, GFP_KERNEL); + + if (!radio || !radio->buffer) + return -ENOMEM; + + radio->usbdev = interface_to_usbdev(intf); + radio->intf = intf; + + /* + * This device uses the same USB IDs as the si470x SiLabs reference + * design. So do an additional check: attempt to read the device ID + * from the si470x: the lower 12 bits are 0x0242 for the si470x. The + * Raremono always returns 0x0800 (the meaning of that is unknown, but + * at least it works). + * + * We use this check to determine which device we are dealing with. + */ + msleep(20); + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + HID_REQ_GET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + 1, 2, + radio->buffer, 3, 500); + if (retval != 3 || + (get_unaligned_be16(&radio->buffer[1]) & 0xfff) == 0x0242) { + dev_info(&intf->dev, "this is not Thanko's Raremono.\n"); + return -ENODEV; + } + + dev_info(&intf->dev, "Thanko's Raremono connected: (%04X:%04X)\n", + id->idVendor, id->idProduct); + + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); + return retval; + } + + mutex_init(&radio->lock); + + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_raremono_fops; + radio->vdev.ioctl_ops = &usb_raremono_ioctl_ops; + radio->vdev.lock = &radio->lock; + radio->vdev.release = video_device_release_empty; + + usb_set_intfdata(intf, &radio->v4l2_dev); + + video_set_drvdata(&radio->vdev, radio); + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); + + raremono_cmd_main(radio, BAND_FM, 95160); + + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); + if (retval == 0) { + dev_info(&intf->dev, "V4L2 device registered as %s\n", + video_device_node_name(&radio->vdev)); + return 0; + } + dev_err(&intf->dev, "could not register video device\n"); + v4l2_device_unregister(&radio->v4l2_dev); + return retval; +} + +/* USB subsystem interface */ +static struct usb_driver usb_raremono_driver = { + .name = "radio-raremono", + .probe = usb_raremono_probe, + .disconnect = usb_raremono_disconnect, + .id_table = usb_raremono_device_table, +}; + +module_usb_driver(usb_raremono_driver); diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c index 3628be617ee..09cfbc373c9 100644 --- a/drivers/media/radio/radio-rtrack2.c +++ b/drivers/media/radio/radio-rtrack2.c @@ -1,12 +1,15 @@ -/* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff +/* + * RadioTrack II driver + * Copyright 1998 Ben Pfaff * * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> * - * TODO: Allow for more than one of these foolish entities :-) - * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> + * + * Fully tested with actual hardware and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -16,325 +19,122 @@ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/mutex.h> #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" MODULE_AUTHOR("Ben Pfaff"); MODULE_DESCRIPTION("A driver for the RadioTrack II radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("0.1.99"); #ifndef CONFIG_RADIO_RTRACK2_PORT #define CONFIG_RADIO_RTRACK2_PORT -1 #endif -static int io = CONFIG_RADIO_RTRACK2_PORT; -static int radio_nr = -1; - -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)"); -module_param(radio_nr, int, 0); +#define RTRACK2_MAX 2 -struct rtrack2 -{ - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; - unsigned long curfreq; - int muted; - struct mutex lock; -}; +static int io[RTRACK2_MAX] = { [0] = CONFIG_RADIO_RTRACK2_PORT, + [1 ... (RTRACK2_MAX - 1)] = -1 }; +static int radio_nr[RTRACK2_MAX] = { [0 ... (RTRACK2_MAX - 1)] = -1 }; -static struct rtrack2 rtrack2_card; +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); - -/* local things */ - -static void rt_mute(struct rtrack2 *dev) +static struct radio_isa_card *rtrack2_alloc(void) { - if (dev->muted) - return; - mutex_lock(&dev->lock); - outb(1, dev->io); - mutex_unlock(&dev->lock); - dev->muted = 1; + return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL); } -static void rt_unmute(struct rtrack2 *dev) +static void zero(struct radio_isa_card *isa) { - if(dev->muted == 0) - return; - mutex_lock(&dev->lock); - outb(0, dev->io); - mutex_unlock(&dev->lock); - dev->muted = 0; + outb_p(1, isa->io); + outb_p(3, isa->io); + outb_p(1, isa->io); } -static void zero(struct rtrack2 *dev) +static void one(struct radio_isa_card *isa) { - outb_p(1, dev->io); - outb_p(3, dev->io); - outb_p(1, dev->io); + outb_p(5, isa->io); + outb_p(7, isa->io); + outb_p(5, isa->io); } -static void one(struct rtrack2 *dev) -{ - outb_p(5, dev->io); - outb_p(7, dev->io); - outb_p(5, dev->io); -} - -static int rt_setfreq(struct rtrack2 *dev, unsigned long freq) +static int rtrack2_s_frequency(struct radio_isa_card *isa, u32 freq) { int i; - mutex_lock(&dev->lock); - dev->curfreq = freq; freq = freq / 200 + 856; - outb_p(0xc8, dev->io); - outb_p(0xc9, dev->io); - outb_p(0xc9, dev->io); + outb_p(0xc8, isa->io); + outb_p(0xc9, isa->io); + outb_p(0xc9, isa->io); for (i = 0; i < 10; i++) - zero(dev); + zero(isa); for (i = 14; i >= 0; i--) if (freq & (1 << i)) - one(dev); - else - zero(dev); - - outb_p(0xc8, dev->io); - if (!dev->muted) - outb_p(0, dev->io); - - mutex_unlock(&dev->lock); - return 0; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-rtrack2", sizeof(v->driver)); - strlcpy(v->card, "RadioTrack II", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int rt_getsigstr(struct rtrack2 *dev) -{ - int sig = 1; - - mutex_lock(&dev->lock); - if (inb(dev->io) & 2) /* bit set = no signal present */ - sig = 0; - mutex_unlock(&dev->lock); - return sig; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct rtrack2 *rt = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 88 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * rt_getsigstr(rt); - return 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct rtrack2 *rt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - rt_setfreq(rt, f->frequency); - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct rtrack2 *rt = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = rt->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - 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, 65535, 65535, 65535); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct rtrack2 *rt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = rt->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - if (rt->muted) - ctrl->value = 0; - else - ctrl->value = 65535; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct rtrack2 *rt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - rt_mute(rt); - else - rt_unmute(rt); - return 0; - case V4L2_CID_AUDIO_VOLUME: - if (ctrl->value) - rt_unmute(rt); + one(isa); else - rt_mute(rt); - return 0; - } - return -EINVAL; -} + zero(isa); -static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) -{ - *i = 0; + outb_p(0xc8, isa->io); + outb_p(v4l2_ctrl_g_ctrl(isa->mute), isa->io); return 0; } -static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) +static u32 rtrack2_g_signal(struct radio_isa_card *isa) { - return i ? -EINVAL : 0; + /* bit set = no signal present */ + return (inb(isa->io) & 2) ? 0 : 0xffff; } -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) +static int rtrack2_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - a->index = 0; - strlcpy(a->name, "Radio", sizeof(a->name)); - a->capability = V4L2_AUDCAP_STEREO; + outb(mute, isa->io); return 0; } -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - -static const struct v4l2_file_operations rtrack2_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops rtrack2_ops = { + .alloc = rtrack2_alloc, + .s_mute_volume = rtrack2_s_mute_volume, + .s_frequency = rtrack2_s_frequency, + .g_signal = rtrack2_g_signal, }; -static const struct v4l2_ioctl_ops rtrack2_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_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, +static const int rtrack2_ioports[] = { 0x20f, 0x30f }; + +static struct radio_isa_driver rtrack2_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-rtrack2", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = rtrack2_ioports, + .num_of_io_ports = ARRAY_SIZE(rtrack2_ioports), + .region_size = 4, + .card = "AIMSlab RadioTrack II", + .ops = &rtrack2_ops, + .has_stereo = true, }; static int __init rtrack2_init(void) { - struct rtrack2 *dev = &rtrack2_card; - struct v4l2_device *v4l2_dev = &dev->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "rtrack2", sizeof(v4l2_dev->name)); - dev->io = io; - if (dev->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or io=0x30c\n"); - return -EINVAL; - } - if (!request_region(dev->io, 4, "rtrack2")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", dev->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(dev->io, 4); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); - dev->vdev.v4l2_dev = v4l2_dev; - dev->vdev.fops = &rtrack2_fops; - dev->vdev.ioctl_ops = &rtrack2_ioctl_ops; - dev->vdev.release = video_device_release_empty; - video_set_drvdata(&dev->vdev, dev); - - /* mute card - prevents noisy bootups */ - outb(1, dev->io); - dev->muted = 1; - - mutex_init(&dev->lock); - if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(dev->io, 4); - return -EINVAL; - } - - v4l2_info(v4l2_dev, "AIMSlab Radiotrack II card driver.\n"); - - return 0; + return isa_register_driver(&rtrack2_driver.driver, RTRACK2_MAX); } static void __exit rtrack2_exit(void) { - struct rtrack2 *dev = &rtrack2_card; - - video_unregister_device(&dev->vdev); - v4l2_device_unregister(&dev->v4l2_dev); - release_region(dev->io, 4); + isa_unregister_driver(&rtrack2_driver.driver); } module_init(rtrack2_init); diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c index 22c5743bf9d..6f4318ff0db 100644 --- a/drivers/media/radio/radio-sf16fmi.c +++ b/drivers/media/radio/radio-sf16fmi.c @@ -1,4 +1,4 @@ -/* SF16-FMI and SF16-FMP radio driver for Linux radio support +/* SF16-FMI, SF16-FMP and SF16-FMD radio driver for Linux radio support * heavily based on rtrack driver... * (c) 1997 M. Kirkwood * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz @@ -11,7 +11,7 @@ * * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); * No volume control - only mute/unmute - you have to use line volume - * control on SB-part of SF16-FMI/SF16-FMP + * control on SB-part of SF16-FMI/SF16-FMP/SF16-FMD * * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> */ @@ -27,9 +27,12 @@ #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-event.h> +#include "lm7000.h" MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood"); -MODULE_DESCRIPTION("A driver for the SF16-FMI and SF16-FMP radio."); +MODULE_DESCRIPTION("A driver for the SF16-FMI, SF16-FMP and SF16-FMD radio."); MODULE_LICENSE("GPL"); MODULE_VERSION("0.0.3"); @@ -37,16 +40,17 @@ static int io = -1; static int radio_nr = -1; module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the SF16-FMI or SF16-FMP card (0x284 or 0x384)"); +MODULE_PARM_DESC(io, "I/O address of the SF16-FMI/SF16-FMP/SF16-FMD card (0x284 or 0x384)"); module_param(radio_nr, int, 0); struct fmi { struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; struct video_device vdev; int io; bool mute; - unsigned long curfreq; /* freq in kHz */ + u32 curfreq; /* freq in kHz */ struct mutex lock; }; @@ -54,31 +58,33 @@ static struct fmi fmi_card; static struct pnp_dev *dev; bool pnp_attached; -/* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */ -/* It is only useful to give freq in interval of 800 (=0.05Mhz), - * other bits will be truncated, e.g 92.7400016 -> 92.7, but - * 92.7400017 -> 92.75 - */ -#define RSF16_ENCODE(x) ((x) / 800 + 214) -#define RSF16_MINFREQ (87 * 16000) -#define RSF16_MAXFREQ (108 * 16000) +#define RSF16_MINFREQ (87U * 16000) +#define RSF16_MAXFREQ (108U * 16000) + +#define FMI_BIT_TUN_CE (1 << 0) +#define FMI_BIT_TUN_CLK (1 << 1) +#define FMI_BIT_TUN_DATA (1 << 2) +#define FMI_BIT_VOL_SW (1 << 3) +#define FMI_BIT_TUN_STRQ (1 << 4) -static void outbits(int bits, unsigned int data, int io) +static void fmi_set_pins(void *handle, u8 pins) { - while (bits--) { - if (data & 1) { - outb(5, io); - udelay(6); - outb(7, io); - udelay(6); - } else { - outb(1, io); - udelay(6); - outb(3, io); - udelay(6); - } - data >>= 1; - } + struct fmi *fmi = handle; + u8 bits = FMI_BIT_TUN_STRQ; + + if (!fmi->mute) + bits |= FMI_BIT_VOL_SW; + + if (pins & LM7000_DATA) + bits |= FMI_BIT_TUN_DATA; + if (pins & LM7000_CLK) + bits |= FMI_BIT_TUN_CLK; + if (pins & LM7000_CE) + bits |= FMI_BIT_TUN_CE; + + mutex_lock(&fmi->lock); + outb_p(bits, fmi->io); + mutex_unlock(&fmi->lock); } static inline void fmi_mute(struct fmi *fmi) @@ -95,20 +101,6 @@ static inline void fmi_unmute(struct fmi *fmi) mutex_unlock(&fmi->lock); } -static inline int fmi_setfreq(struct fmi *fmi, unsigned long freq) -{ - mutex_lock(&fmi->lock); - fmi->curfreq = freq; - - outbits(16, RSF16_ENCODE(freq), fmi->io); - outbits(8, 0xC0, fmi->io); - msleep(143); /* was schedule_timeout(HZ/7) */ - mutex_unlock(&fmi->lock); - if (!fmi->mute) - fmi_unmute(fmi); - return 0; -} - static inline int fmi_getsigstr(struct fmi *fmi) { int val; @@ -126,13 +118,22 @@ static inline int fmi_getsigstr(struct fmi *fmi) return (res & 2) ? 0 : 0xFFFF; } +static void fmi_set_freq(struct fmi *fmi) +{ + fmi->curfreq = clamp(fmi->curfreq, RSF16_MINFREQ, RSF16_MAXFREQ); + /* rounding in steps of 800 to match the freq + that will be used */ + lm7000_set_freq((fmi->curfreq / 800) * 800, fmi, fmi_set_pins); +} + static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *v) { strlcpy(v->driver, "radio-sf16fmi", sizeof(v->driver)); - strlcpy(v->card, "SF16-FMx radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + strlcpy(v->card, "SF16-FMI/FMP/FMD radio", sizeof(v->card)); + strlcpy(v->bus_info, "ISA:radio-sf16fmi", sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -156,24 +157,22 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { return v->index ? -EINVAL : 0; } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct fmi *fmi = video_drvdata(file); if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; - if (f->frequency < RSF16_MINFREQ || - f->frequency > RSF16_MAXFREQ) - return -EINVAL; - /* rounding in steps of 800 to match the freq - that will be used */ - fmi_setfreq(fmi, (f->frequency / 800) * 800); + + fmi->curfreq = f->frequency; + fmi_set_freq(fmi); + return 0; } @@ -189,74 +188,31 @@ static int vidioc_g_frequency(struct file *file, void *priv, return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int fmi_s_ctrl(struct v4l2_ctrl *ctrl) { - struct fmi *fmi = video_drvdata(file); + struct fmi *fmi = container_of(ctrl->handler, struct fmi, hdl); switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: - ctrl->value = fmi->mute; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct fmi *fmi = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) + if (ctrl->val) fmi_mute(fmi); else fmi_unmute(fmi); - fmi->mute = ctrl->value; + fmi->mute = ctrl->val; return 0; } return -EINVAL; } -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; -} +static const struct v4l2_ctrl_ops fmi_ctrl_ops = { + .s_ctrl = fmi_s_ctrl, +}; static const struct v4l2_file_operations fmi_fops = { .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, .unlocked_ioctl = video_ioctl2, }; @@ -264,21 +220,21 @@ static const struct v4l2_ioctl_ops fmi_ioctl_ops = { .vidioc_querycap = vidioc_querycap, .vidioc_g_tuner = vidioc_g_tuner, .vidioc_s_tuner = vidioc_s_tuner, - .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_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_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* ladis: this is my card. does any other types exist? */ -static struct isapnp_device_id id_table[] __devinitdata = { +static struct isapnp_device_id id_table[] = { + /* SF16-FMI */ { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0}, + /* SF16-FMD */ + { ISAPNP_ANY_ID, ISAPNP_ANY_ID, + ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad12), 0}, { ISAPNP_CARD_END, }, }; @@ -318,6 +274,7 @@ static int __init fmi_init(void) { struct fmi *fmi = &fmi_card; struct v4l2_device *v4l2_dev = &fmi->v4l2_dev; + struct v4l2_ctrl_handler *hdl = &fmi->hdl; int res, i; int probe_ports[] = { 0, 0x284, 0x384 }; @@ -370,19 +327,35 @@ static int __init fmi_init(void) return res; } + v4l2_ctrl_handler_init(hdl, 1); + v4l2_ctrl_new_std(hdl, &fmi_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"); + v4l2_ctrl_handler_free(hdl); + v4l2_device_unregister(v4l2_dev); + return res; + } + strlcpy(fmi->vdev.name, v4l2_dev->name, sizeof(fmi->vdev.name)); fmi->vdev.v4l2_dev = v4l2_dev; fmi->vdev.fops = &fmi_fops; fmi->vdev.ioctl_ops = &fmi_ioctl_ops; fmi->vdev.release = video_device_release_empty; + set_bit(V4L2_FL_USE_FH_PRIO, &fmi->vdev.flags); video_set_drvdata(&fmi->vdev, fmi); mutex_init(&fmi->lock); - /* mute card - prevents noisy bootups */ - fmi_mute(fmi); + /* mute card and set default frequency */ + fmi->mute = 1; + fmi->curfreq = RSF16_MINFREQ; + fmi_set_freq(fmi); if (video_register_device(&fmi->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { + v4l2_ctrl_handler_free(hdl); v4l2_device_unregister(v4l2_dev); release_region(fmi->io, 2); if (pnp_attached) @@ -398,6 +371,7 @@ static void __exit fmi_exit(void) { struct fmi *fmi = &fmi_card; + v4l2_ctrl_handler_free(&fmi->hdl); video_unregister_device(&fmi->vdev); v4l2_device_unregister(&fmi->v4l2_dev); release_region(fmi->io, 2); diff --git a/drivers/media/radio/radio-sf16fmr2.c b/drivers/media/radio/radio-sf16fmr2.c index 2dd485996ba..93d864eb830 100644 --- a/drivers/media/radio/radio-sf16fmr2.c +++ b/drivers/media/radio/radio-sf16fmr2.c @@ -1,4 +1,4 @@ -/* SF16-FMR2 radio driver for Linux +/* SF16-FMR2 and SF16-FMD2 radio driver for Linux * Copyright (c) 2011 Ondrej Zary * * Original driver was (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com @@ -9,24 +9,40 @@ #include <linux/delay.h> #include <linux/module.h> /* Modules */ #include <linux/init.h> /* Initdata */ +#include <linux/slab.h> #include <linux/ioport.h> /* request_region */ #include <linux/io.h> /* outb, outb_p */ -#include <sound/tea575x-tuner.h> +#include <linux/isa.h> +#include <linux/pnp.h> +#include <media/tea575x.h> MODULE_AUTHOR("Ondrej Zary"); -MODULE_DESCRIPTION("MediaForte SF16-FMR2 FM radio card driver"); +MODULE_DESCRIPTION("MediaForte SF16-FMR2 and SF16-FMD2 FM radio card driver"); MODULE_LICENSE("GPL"); +/* these cards can only use two different ports (0x384 and 0x284) */ +#define FMR2_MAX 2 + +static int radio_nr[FMR2_MAX] = { [0 ... (FMR2_MAX - 1)] = -1 }; +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); + struct fmr2 { int io; + struct v4l2_device v4l2_dev; struct snd_tea575x tea; struct v4l2_ctrl *volume; struct v4l2_ctrl *balance; + bool is_fmd2; }; -/* the port is hardwired so no need to support multiple cards */ +static int num_fmr2_cards; +static struct fmr2 *fmr2_cards[FMR2_MAX]; +static bool isa_registered; +static bool pnp_registered; + +/* the port is hardwired on SF16-FMR2 */ #define FMR2_PORT 0x384 -static struct fmr2 fmr2_card; /* TEA575x tuner pins */ #define STR_DATA (1 << 0) @@ -58,8 +74,8 @@ static u8 fmr2_tea575x_get_pins(struct snd_tea575x *tea) struct fmr2 *fmr2 = tea->private_data; u8 bits = inb(fmr2->io); - return (bits & STR_DATA) ? TEA575X_DATA : 0 | - (bits & STR_MOST) ? TEA575X_MOST : 0; + return ((bits & STR_DATA) ? TEA575X_DATA : 0) | + ((bits & STR_MOST) ? TEA575X_MOST : 0); } static void fmr2_tea575x_set_direction(struct snd_tea575x *tea, bool output) @@ -168,11 +184,12 @@ static int fmr2_tea_ext_init(struct snd_tea575x *tea) { struct fmr2 *fmr2 = tea->private_data; - if (inb(fmr2->io) & FMR2_HASVOL) { + /* FMR2 can have volume control, FMD2 can't (uses SB16 mixer) */ + if (!fmr2->is_fmd2 && inb(fmr2->io) & FMR2_HASVOL) { fmr2->volume = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_VOLUME, 0, 68, 2, 56); fmr2->balance = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_BALANCE, -68, 68, 2, 0); if (tea->ctrl_handler.error) { - printk(KERN_ERR "radio-sf16fmr2: can't initialize contrls\n"); + printk(KERN_ERR "radio-sf16fmr2: can't initialize controls\n"); return tea->ctrl_handler.error; } } @@ -180,39 +197,149 @@ static int fmr2_tea_ext_init(struct snd_tea575x *tea) return 0; } -static int __init fmr2_init(void) +static struct pnp_device_id fmr2_pnp_ids[] = { + { .id = "MFRad13" }, /* tuner subdevice of SF16-FMD2 */ + { .id = "" } +}; +MODULE_DEVICE_TABLE(pnp, fmr2_pnp_ids); + +static int fmr2_probe(struct fmr2 *fmr2, struct device *pdev, int io) { - struct fmr2 *fmr2 = &fmr2_card; + int err, i; + char *card_name = fmr2->is_fmd2 ? "SF16-FMD2" : "SF16-FMR2"; - fmr2->io = FMR2_PORT; + /* avoid errors if a card was already registered at given port */ + for (i = 0; i < num_fmr2_cards; i++) + if (io == fmr2_cards[i]->io) + return -EBUSY; - if (!request_region(fmr2->io, 2, "SF16-FMR2")) { + strlcpy(fmr2->v4l2_dev.name, "radio-sf16fmr2", + sizeof(fmr2->v4l2_dev.name)), + fmr2->io = io; + + if (!request_region(fmr2->io, 2, fmr2->v4l2_dev.name)) { printk(KERN_ERR "radio-sf16fmr2: I/O port 0x%x already in use\n", fmr2->io); return -EBUSY; } + dev_set_drvdata(pdev, fmr2); + err = v4l2_device_register(pdev, &fmr2->v4l2_dev); + if (err < 0) { + v4l2_err(&fmr2->v4l2_dev, "Could not register v4l2_device\n"); + release_region(fmr2->io, 2); + return err; + } + fmr2->tea.v4l2_dev = &fmr2->v4l2_dev; fmr2->tea.private_data = fmr2; + fmr2->tea.radio_nr = radio_nr[num_fmr2_cards]; fmr2->tea.ops = &fmr2_tea_ops; fmr2->tea.ext_init = fmr2_tea_ext_init; - strlcpy(fmr2->tea.card, "SF16-FMR2", sizeof(fmr2->tea.card)); - strcpy(fmr2->tea.bus_info, "ISA"); + strlcpy(fmr2->tea.card, card_name, sizeof(fmr2->tea.card)); + snprintf(fmr2->tea.bus_info, sizeof(fmr2->tea.bus_info), "%s:%s", + fmr2->is_fmd2 ? "PnP" : "ISA", dev_name(pdev)); - if (snd_tea575x_init(&fmr2->tea)) { + if (snd_tea575x_init(&fmr2->tea, THIS_MODULE)) { printk(KERN_ERR "radio-sf16fmr2: Unable to detect TEA575x tuner\n"); release_region(fmr2->io, 2); return -ENODEV; } - printk(KERN_INFO "radio-sf16fmr2: SF16-FMR2 radio card at 0x%x.\n", fmr2->io); + printk(KERN_INFO "radio-sf16fmr2: %s radio card at 0x%x.\n", + card_name, fmr2->io); return 0; } -static void __exit fmr2_exit(void) +static int fmr2_isa_match(struct device *pdev, unsigned int ndev) +{ + struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL); + if (!fmr2) + return 0; + + if (fmr2_probe(fmr2, pdev, FMR2_PORT)) { + kfree(fmr2); + return 0; + } + dev_set_drvdata(pdev, fmr2); + fmr2_cards[num_fmr2_cards++] = fmr2; + + return 1; +} + +static int fmr2_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id) { - struct fmr2 *fmr2 = &fmr2_card; + int ret; + struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL); + if (!fmr2) + return -ENOMEM; + + fmr2->is_fmd2 = true; + ret = fmr2_probe(fmr2, &pdev->dev, pnp_port_start(pdev, 0)); + if (ret) { + kfree(fmr2); + return ret; + } + pnp_set_drvdata(pdev, fmr2); + fmr2_cards[num_fmr2_cards++] = fmr2; + return 0; +} + +static void fmr2_remove(struct fmr2 *fmr2) +{ snd_tea575x_exit(&fmr2->tea); release_region(fmr2->io, 2); + v4l2_device_unregister(&fmr2->v4l2_dev); + kfree(fmr2); +} + +static int fmr2_isa_remove(struct device *pdev, unsigned int ndev) +{ + fmr2_remove(dev_get_drvdata(pdev)); + + return 0; +} + +static void fmr2_pnp_remove(struct pnp_dev *pdev) +{ + fmr2_remove(pnp_get_drvdata(pdev)); + pnp_set_drvdata(pdev, NULL); +} + +struct isa_driver fmr2_isa_driver = { + .match = fmr2_isa_match, + .remove = fmr2_isa_remove, + .driver = { + .name = "radio-sf16fmr2", + }, +}; + +struct pnp_driver fmr2_pnp_driver = { + .name = "radio-sf16fmr2", + .id_table = fmr2_pnp_ids, + .probe = fmr2_pnp_probe, + .remove = fmr2_pnp_remove, +}; + +static int __init fmr2_init(void) +{ + int ret; + + ret = pnp_register_driver(&fmr2_pnp_driver); + if (!ret) + pnp_registered = true; + ret = isa_register_driver(&fmr2_isa_driver, 1); + if (!ret) + isa_registered = true; + + return (pnp_registered || isa_registered) ? 0 : ret; +} + +static void __exit fmr2_exit(void) +{ + if (pnp_registered) + pnp_unregister_driver(&fmr2_pnp_driver); + if (isa_registered) + isa_unregister_driver(&fmr2_isa_driver); } module_init(fmr2_init); diff --git a/drivers/media/radio/radio-shark.c b/drivers/media/radio/radio-shark.c new file mode 100644 index 00000000000..050b3bb96fe --- /dev/null +++ b/drivers/media/radio/radio-shark.c @@ -0,0 +1,423 @@ +/* + * 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 <media/tea575x.h> + +#if defined(CONFIG_LEDS_CLASS) || \ + (defined(CONFIG_LEDS_CLASS_MODULE) && defined(CONFIG_RADIO_SHARK_MODULE)) +#define SHARK_USE_LEDS 1 +#endif + +/* + * 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) + +/* Note BLUE_IS_PULSE comes after NO_LEDS as it is a status bit, not a LED */ +enum { BLUE_LED, BLUE_PULSE_LED, RED_LED, NO_LEDS, BLUE_IS_PULSE }; + +struct shark_device { + struct usb_device *usbdev; + struct v4l2_device v4l2_dev; + struct snd_tea575x tea; + +#ifdef SHARK_USE_LEDS + 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; +#endif + + 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, +}; + +#ifdef SHARK_USE_LEDS +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; + + 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); + } +} + +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); + clear_bit(BLUE_IS_PULSE, &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); + set_bit(BLUE_IS_PULSE, &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 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, + }, +}; + +static int shark_register_leds(struct shark_device *shark, struct device *dev) +{ + int i, retval; + + atomic_set(&shark->brightness[BLUE_LED], 127); + 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]; + retval = led_classdev_register(dev, &shark->leds[i]); + if (retval) { + v4l2_err(&shark->v4l2_dev, + "couldn't register led: %s\n", + shark->led_names[i]); + return retval; + } + } + return 0; +} + +static void shark_unregister_leds(struct shark_device *shark) +{ + int i; + + for (i = 0; i < NO_LEDS; i++) + led_classdev_unregister(&shark->leds[i]); + + cancel_work_sync(&shark->led_work); +} + +static inline void shark_resume_leds(struct shark_device *shark) +{ + if (test_bit(BLUE_IS_PULSE, &shark->brightness_new)) + set_bit(BLUE_PULSE_LED, &shark->brightness_new); + else + set_bit(BLUE_LED, &shark->brightness_new); + set_bit(RED_LED, &shark->brightness_new); + schedule_work(&shark->led_work); +} +#else +static int shark_register_leds(struct shark_device *shark, struct device *dev) +{ + v4l2_warn(&shark->v4l2_dev, + "CONFIG_LEDS_CLASS not enabled, LED support disabled\n"); + return 0; +} +static inline void shark_unregister_leds(struct shark_device *shark) { } +static inline void shark_resume_leds(struct shark_device *shark) { } +#endif + +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); + + mutex_lock(&shark->tea.mutex); + v4l2_device_disconnect(&shark->v4l2_dev); + snd_tea575x_exit(&shark->tea); + mutex_unlock(&shark->tea.mutex); + + shark_unregister_leds(shark); + + 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); + + 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 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; + + v4l2_device_set_name(&shark->v4l2_dev, DRV_NAME, &shark_instance); + + retval = shark_register_leds(shark, &intf->dev); + if (retval) + goto err_reg_leds; + + shark->v4l2_dev.release = usb_shark_release; + 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; + shark->tea.has_am = 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; + } + + return 0; + +err_init_tea: + v4l2_device_unregister(&shark->v4l2_dev); +err_reg_dev: + shark_unregister_leds(shark); +err_reg_leds: + kfree(shark->transfer_buffer); +err_alloc_buffer: + kfree(shark); + + return retval; +} + +#ifdef CONFIG_PM +static int usb_shark_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int usb_shark_resume(struct usb_interface *intf) +{ + struct v4l2_device *v4l2_dev = usb_get_intfdata(intf); + struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev); + + mutex_lock(&shark->tea.mutex); + snd_tea575x_set_freq(&shark->tea); + mutex_unlock(&shark->tea.mutex); + + shark_resume_leds(shark); + + return 0; +} +#endif + +/* 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, +#ifdef CONFIG_PM + .suspend = usb_shark_suspend, + .resume = usb_shark_resume, + .reset_resume = usb_shark_resume, +#endif +}; +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..8654e0dc5c9 --- /dev/null +++ b/drivers/media/radio/radio-shark2.c @@ -0,0 +1,389 @@ +/* + * 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" + +#if defined(CONFIG_LEDS_CLASS) || \ + (defined(CONFIG_LEDS_CLASS_MODULE) && defined(CONFIG_RADIO_SHARK2_MODULE)) +#define SHARK_USE_LEDS 1 +#endif + +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 }; + +struct shark_device { + struct usb_device *usbdev; + struct v4l2_device v4l2_dev; + struct radio_tea5777 tea; + +#ifdef SHARK_USE_LEDS + 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; +#endif + + 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: %*ph\n", + 7, shark->transfer_buffer); + + 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: %*ph\n", + 3, shark->transfer_buffer); + + *reg_ret = reg; + return 0; +} + +static struct radio_tea5777_ops shark_tea_ops = { + .write_reg = shark_write_reg, + .read_reg = shark_read_reg, +}; + +#ifdef SHARK_USE_LEDS +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; + + 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); + } +} + +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 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, + }, +}; + +static int shark_register_leds(struct shark_device *shark, struct device *dev) +{ + int i, retval; + + atomic_set(&shark->brightness[BLUE_LED], 127); + 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]; + retval = led_classdev_register(dev, &shark->leds[i]); + if (retval) { + v4l2_err(&shark->v4l2_dev, + "couldn't register led: %s\n", + shark->led_names[i]); + return retval; + } + } + return 0; +} + +static void shark_unregister_leds(struct shark_device *shark) +{ + int i; + + for (i = 0; i < NO_LEDS; i++) + led_classdev_unregister(&shark->leds[i]); + + cancel_work_sync(&shark->led_work); +} + +static inline void shark_resume_leds(struct shark_device *shark) +{ + int i; + + for (i = 0; i < NO_LEDS; i++) + set_bit(i, &shark->brightness_new); + + schedule_work(&shark->led_work); +} +#else +static int shark_register_leds(struct shark_device *shark, struct device *dev) +{ + v4l2_warn(&shark->v4l2_dev, + "CONFIG_LEDS_CLASS not enabled, LED support disabled\n"); + return 0; +} +static inline void shark_unregister_leds(struct shark_device *shark) { } +static inline void shark_resume_leds(struct shark_device *shark) { } +#endif + +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); + + mutex_lock(&shark->tea.mutex); + v4l2_device_disconnect(&shark->v4l2_dev); + radio_tea5777_exit(&shark->tea); + mutex_unlock(&shark->tea.mutex); + + shark_unregister_leds(shark); + + 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); + + 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 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; + + v4l2_device_set_name(&shark->v4l2_dev, DRV_NAME, &shark_instance); + + retval = shark_register_leds(shark, &intf->dev); + if (retval) + goto err_reg_leds; + + shark->v4l2_dev.release = usb_shark_release; + 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; + } + + return 0; + +err_init_tea: + v4l2_device_unregister(&shark->v4l2_dev); +err_reg_dev: + shark_unregister_leds(shark); +err_reg_leds: + kfree(shark->transfer_buffer); +err_alloc_buffer: + kfree(shark); + + return retval; +} + +#ifdef CONFIG_PM +static int usb_shark_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int usb_shark_resume(struct usb_interface *intf) +{ + struct v4l2_device *v4l2_dev = usb_get_intfdata(intf); + struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev); + int ret; + + mutex_lock(&shark->tea.mutex); + ret = radio_tea5777_set_freq(&shark->tea); + mutex_unlock(&shark->tea.mutex); + + shark_resume_leds(shark); + + return ret; +} +#endif + +/* 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, +#ifdef CONFIG_PM + .suspend = usb_shark_suspend, + .resume = usb_shark_resume, + .reset_resume = usb_shark_resume, +#endif +}; +module_usb_driver(usb_shark_driver); diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c new file mode 100644 index 00000000000..2fd9009f866 --- /dev/null +++ b/drivers/media/radio/radio-si476x.c @@ -0,0 +1,1588 @@ +/* + * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips + * + * Copyright (C) 2012 Innovative Converged Devices(ICD) + * Copyright (C) 2013 Andrey Smirnov + * + * Author: Andrey Smirnov <andrew.smirnov@gmail.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; version 2 of the License. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/atomic.h> +#include <linux/videodev2.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-device.h> + +#include <media/si476x.h> +#include <linux/mfd/si476x-core.h> + +#define FM_FREQ_RANGE_LOW 64000000 +#define FM_FREQ_RANGE_HIGH 108000000 + +#define AM_FREQ_RANGE_LOW 520000 +#define AM_FREQ_RANGE_HIGH 30000000 + +#define PWRLINEFLTR (1 << 8) + +#define FREQ_MUL (10000000 / 625) + +#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0x80 & (status)) + +#define DRIVER_NAME "si476x-radio" +#define DRIVER_CARD "SI476x AM/FM Receiver" + +enum si476x_freq_bands { + SI476X_BAND_FM, + SI476X_BAND_AM, +}; + +static const struct v4l2_frequency_band si476x_bands[] = { + [SI476X_BAND_FM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_FM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 64 * FREQ_MUL, + .rangehigh = 108 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_FM, + }, + [SI476X_BAND_AM] = { + .type = V4L2_TUNER_RADIO, + .index = SI476X_BAND_AM, + .capability = V4L2_TUNER_CAP_LOW + | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 0.52 * FREQ_MUL, + .rangehigh = 30 * FREQ_MUL, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band) +{ + return freq >= si476x_bands[band].rangelow && + freq <= si476x_bands[band].rangehigh; +} + +static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high, + int band) +{ + return low >= si476x_bands[band].rangelow && + high <= si476x_bands[band].rangehigh; +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl); +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl); + +enum phase_diversity_modes_idx { + SI476X_IDX_PHDIV_DISABLED, + SI476X_IDX_PHDIV_PRIMARY_COMBINING, + SI476X_IDX_PHDIV_PRIMARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_ANTENNA, + SI476X_IDX_PHDIV_SECONDARY_COMBINING, +}; + +static const char * const phase_diversity_modes[] = { + [SI476X_IDX_PHDIV_DISABLED] = "Disabled", + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary", + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna", + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary", +}; + +static inline enum phase_diversity_modes_idx +si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode) +{ + switch (mode) { + default: /* FALLTHROUGH */ + case SI476X_PHDIV_DISABLED: + return SI476X_IDX_PHDIV_DISABLED; + case SI476X_PHDIV_PRIMARY_COMBINING: + return SI476X_IDX_PHDIV_PRIMARY_COMBINING; + case SI476X_PHDIV_PRIMARY_ANTENNA: + return SI476X_IDX_PHDIV_PRIMARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_ANTENNA: + return SI476X_IDX_PHDIV_SECONDARY_ANTENNA; + case SI476X_PHDIV_SECONDARY_COMBINING: + return SI476X_IDX_PHDIV_SECONDARY_COMBINING; + } +} + +static inline enum si476x_phase_diversity_mode +si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx) +{ + static const int idx_to_value[] = { + [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED, + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING, + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA, + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING, + }; + + return idx_to_value[idx]; +} + +static const struct v4l2_ctrl_ops si476x_ctrl_ops = { + .g_volatile_ctrl = si476x_radio_g_volatile_ctrl, + .s_ctrl = si476x_radio_s_ctrl, +}; + + +enum si476x_ctrl_idx { + SI476X_IDX_RSSI_THRESHOLD, + SI476X_IDX_SNR_THRESHOLD, + SI476X_IDX_MAX_TUNE_ERROR, + SI476X_IDX_HARMONICS_COUNT, + SI476X_IDX_DIVERSITY_MODE, + SI476X_IDX_INTERCHIP_LINK, +}; +static struct v4l2_ctrl_config si476x_ctrls[] = { + + /** + * SI476X during its station seeking(or tuning) process uses several + * parameters to detrmine if "the station" is valid: + * + * - Signal's SNR(in dBuV) must be lower than + * #V4L2_CID_SI476X_SNR_THRESHOLD + * - Signal's RSSI(in dBuV) must be greater than + * #V4L2_CID_SI476X_RSSI_THRESHOLD + * - Signal's frequency deviation(in units of 2ppm) must not be + * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR + */ + [SI476X_IDX_RSSI_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_RSSI_THRESHOLD, + .name = "Valid RSSI Threshold", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_SNR_THRESHOLD] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_SNR_THRESHOLD, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Valid SNR Threshold", + .min = -128, + .max = 127, + .step = 1, + }, + [SI476X_IDX_MAX_TUNE_ERROR] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_MAX_TUNE_ERROR, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Max Tune Errors", + .min = 0, + .max = 126 * 2, + .step = 2, + }, + + /** + * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics + * built-in power-line noise supression filter is to reject + * during AM-mode operation. + */ + [SI476X_IDX_HARMONICS_COUNT] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_HARMONICS_COUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + + .name = "Count of Harmonics to Reject", + .min = 0, + .max = 20, + .step = 1, + }, + + /** + * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which + * two tuners working in diversity mode are to work in. + * + * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled + * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is + * on, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is + * off, primary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is + * off, secondary tuner's antenna is the main one. + * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is + * on, secondary tuner's antenna is the main one. + */ + [SI476X_IDX_DIVERSITY_MODE] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_DIVERSITY_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Phase Diversity Mode", + .qmenu = phase_diversity_modes, + .min = 0, + .max = ARRAY_SIZE(phase_diversity_modes) - 1, + }, + + /** + * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in + * diversity mode indicator. Allows user to determine if two + * chips working in diversity mode have established a link + * between each other and if the system as a whole uses + * signals from both antennas to receive FM radio. + */ + [SI476X_IDX_INTERCHIP_LINK] = { + .ops = &si476x_ctrl_ops, + .id = V4L2_CID_SI476X_INTERCHIP_LINK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, + .name = "Inter-Chip Link", + .min = 0, + .max = 1, + .step = 1, + }, +}; + +struct si476x_radio; + +/** + * struct si476x_radio_ops - vtable of tuner functions + * + * This table holds pointers to functions implementing particular + * operations depending on the mode in which the tuner chip was + * configured to start in. If the function is not supported + * corresponding element is set to #NULL. + * + * @tune_freq: Tune chip to a specific frequency + * @seek_start: Star station seeking + * @rsq_status: Get Received Signal Quality(RSQ) status + * @rds_blckcnt: Get received RDS blocks count + * @phase_diversity: Change phase diversity mode of the tuner + * @phase_div_status: Get phase diversity mode status + * @acf_status: Get the status of Automatically Controlled + * Features(ACF) + * @agc_status: Get Automatic Gain Control(AGC) status + */ +struct si476x_radio_ops { + int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *); + int (*seek_start)(struct si476x_core *, bool, bool); + int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *, + struct si476x_rsq_status_report *); + int (*rds_blckcnt)(struct si476x_core *, bool, + struct si476x_rds_blockcount_report *); + + int (*phase_diversity)(struct si476x_core *, + enum si476x_phase_diversity_mode); + int (*phase_div_status)(struct si476x_core *); + int (*acf_status)(struct si476x_core *, + struct si476x_acf_status_report *); + int (*agc_status)(struct si476x_core *, + struct si476x_agc_status_report *); +}; + +/** + * struct si476x_radio - radio device + * + * @core: Pointer to underlying core device + * @videodev: Pointer to video device created by V4L2 subsystem + * @ops: Vtable of functions. See struct si476x_radio_ops for details + * @kref: Reference counter + * @core_lock: An r/w semaphore to brebvent the deletion of underlying + * core structure is the radio device is being used + */ +struct si476x_radio { + struct v4l2_device v4l2dev; + struct video_device videodev; + struct v4l2_ctrl_handler ctrl_handler; + + struct si476x_core *core; + /* This field should not be accesses unless core lock is held */ + const struct si476x_radio_ops *ops; + + struct dentry *debugfs; + u32 audmode; +}; + +static inline struct si476x_radio * +v4l2_dev_to_radio(struct v4l2_device *d) +{ + return container_of(d, struct si476x_radio, v4l2dev); +} + +static inline struct si476x_radio * +v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d) +{ + return container_of(d, struct si476x_radio, ctrl_handler); +} + +/* + * si476x_vidioc_querycap - query device capabilities + */ +static int si476x_radio_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct si476x_radio *radio = video_drvdata(file); + + strlcpy(capability->driver, radio->v4l2dev.name, + sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", radio->v4l2dev.name); + + capability->device_caps = V4L2_CAP_TUNER + | V4L2_CAP_RADIO + | V4L2_CAP_HW_FREQ_SEEK; + + si476x_core_lock(radio->core); + if (!si476x_core_is_a_secondary_tuner(radio->core)) + capability->device_caps |= V4L2_CAP_RDS_CAPTURE + | V4L2_CAP_READWRITE; + si476x_core_unlock(radio->core); + + capability->capabilities = capability->device_caps + | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int si476x_radio_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (band->tuner != 0) + return -EINVAL; + + switch (radio->core->chip_id) { + /* AM/FM tuners -- all bands are supported */ + case SI476X_CHIP_SI4761: + case SI476X_CHIP_SI4764: + if (band->index < ARRAY_SIZE(si476x_bands)) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + /* FM companion tuner chips -- only FM bands are + * supported */ + case SI476X_CHIP_SI4768: + if (band->index == SI476X_BAND_FM) { + *band = si476x_bands[band->index]; + err = 0; + } else { + err = -EINVAL; + } + break; + default: + err = -EINVAL; + } + + return err; +} + +static int si476x_radio_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + int err; + struct si476x_rsq_status_report report; + struct si476x_radio *radio = video_drvdata(file); + + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + if (tuner->index != 0) + return -EINVAL; + + tuner->type = V4L2_TUNER_RADIO; + tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies + * in multiples of + * 62.5 Hz */ + | V4L2_TUNER_CAP_STEREO + | V4L2_TUNER_CAP_HWSEEK_BOUNDED + | V4L2_TUNER_CAP_HWSEEK_WRAP + | V4L2_TUNER_CAP_HWSEEK_PROG_LIM; + + si476x_core_lock(radio->core); + + if (si476x_core_is_a_secondary_tuner(radio->core)) { + strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name)); + tuner->rxsubchans = 0; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } else if (si476x_core_has_am(radio->core)) { + if (si476x_core_is_a_primary_tuner(radio->core)) + strlcpy(tuner->name, "AM/FM (primary)", + sizeof(tuner->name)); + else + strlcpy(tuner->name, "AM/FM", sizeof(tuner->name)); + + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO + | V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + + tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow; + } else { + strlcpy(tuner->name, "FM", sizeof(tuner->name)); + tuner->rxsubchans = V4L2_TUNER_SUB_RDS; + tuner->capability |= V4L2_TUNER_CAP_RDS + | V4L2_TUNER_CAP_RDS_BLOCK_IO + | V4L2_TUNER_CAP_FREQ_BANDS; + tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow; + } + + tuner->audmode = radio->audmode; + + tuner->afc = 1; + tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh; + + err = radio->ops->rsq_status(radio->core, + &args, &report); + if (err < 0) { + tuner->signal = 0; + } else { + /* + * tuner->signal value range: 0x0000 .. 0xFFFF, + * report.rssi: -128 .. 127 + */ + tuner->signal = (report.rssi + 128) * 257; + } + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *tuner) +{ + struct si476x_radio *radio = video_drvdata(file); + + if (tuner->index != 0) + return -EINVAL; + + if (tuner->audmode == V4L2_TUNER_MODE_MONO || + tuner->audmode == V4L2_TUNER_MODE_STEREO) + radio->audmode = tuner->audmode; + else + radio->audmode = V4L2_TUNER_MODE_STEREO; + + return 0; +} + +static int si476x_radio_init_vtable(struct si476x_radio *radio, + enum si476x_func func) +{ + static const struct si476x_radio_ops fm_ops = { + .tune_freq = si476x_core_cmd_fm_tune_freq, + .seek_start = si476x_core_cmd_fm_seek_start, + .rsq_status = si476x_core_cmd_fm_rsq_status, + .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount, + .phase_diversity = si476x_core_cmd_fm_phase_diversity, + .phase_div_status = si476x_core_cmd_fm_phase_div_status, + .acf_status = si476x_core_cmd_fm_acf_status, + .agc_status = si476x_core_cmd_agc_status, + }; + + static const struct si476x_radio_ops am_ops = { + .tune_freq = si476x_core_cmd_am_tune_freq, + .seek_start = si476x_core_cmd_am_seek_start, + .rsq_status = si476x_core_cmd_am_rsq_status, + .rds_blckcnt = NULL, + .phase_diversity = NULL, + .phase_div_status = NULL, + .acf_status = si476x_core_cmd_am_acf_status, + .agc_status = NULL, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + radio->ops = &fm_ops; + return 0; + + case SI476X_FUNC_AM_RECEIVER: + radio->ops = &am_ops; + return 0; + default: + WARN(1, "Unexpected tuner function value\n"); + return -EINVAL; + } +} + +static int si476x_radio_pretune(struct si476x_radio *radio, + enum si476x_func func) +{ + int retval; + + struct si476x_tune_freq_args args = { + .zifsr = false, + .hd = false, + .injside = SI476X_INJSIDE_AUTO, + .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE, + .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO, + .antcap = 0, + }; + + switch (func) { + case SI476X_FUNC_FM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 92 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + case SI476X_FUNC_AM_RECEIVER: + args.freq = v4l2_to_si476x(radio->core, + 0.6 * FREQ_MUL); + retval = radio->ops->tune_freq(radio->core, &args); + break; + default: + WARN(1, "Unexpected tuner function value\n"); + retval = -EINVAL; + } + + return retval; +} +static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + + /* regcache_mark_dirty(radio->core->regmap); */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE, + SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + SI476X_PROP_AUDIO_PWR_LINE_FILTER); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_INT_CTL_ENABLE, + SI476X_PROP_INT_CTL_ENABLE); + if (err < 0) + return err; + + /* + * Is there any point in restoring SNR and the like + * when switching between AM/FM? + */ + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + SI476X_PROP_VALID_MAX_TUNE_ERROR); + if (err < 0) + return err; + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + SI476X_PROP_VALID_RSSI_THRESHOLD); + if (err < 0) + return err; + + if (func == SI476X_FUNC_FM_RECEIVER) { + if (si476x_core_has_diversity(radio->core)) { + err = si476x_core_cmd_fm_phase_diversity(radio->core, + radio->core->diversity_mode); + if (err < 0) + return err; + } + + err = regcache_sync_region(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_PROP_FM_RDS_CONFIG); + if (err < 0) + return err; + } + + return si476x_radio_init_vtable(radio, func); + +} + +static int si476x_radio_change_func(struct si476x_radio *radio, + enum si476x_func func) +{ + int err; + bool soft; + /* + * Since power/up down is a very time consuming operation, + * try to avoid doing it if the requested mode matches the one + * the tuner is in + */ + if (func == radio->core->power_up_parameters.func) + return 0; + + soft = true; + err = si476x_core_stop(radio->core, soft); + if (err < 0) { + /* + * OK, if the chip does not want to play nice let's + * try to reset it in more brutal way + */ + soft = false; + err = si476x_core_stop(radio->core, soft); + if (err < 0) + return err; + } + /* + Set the desired radio tuner function + */ + radio->core->power_up_parameters.func = func; + + err = si476x_core_start(radio->core, soft); + if (err < 0) + return err; + + /* + * No need to do the rest of manipulations for the bootlader + * mode + */ + if (func != SI476X_FUNC_FM_RECEIVER && + func != SI476X_FUNC_AM_RECEIVER) + return err; + + return si476x_radio_do_post_powerup_init(radio, func); +} + +static int si476x_radio_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (radio->ops->rsq_status) { + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = true, + .cancel = false, + .stcack = false, + }; + + err = radio->ops->rsq_status(radio->core, &args, &report); + if (!err) + f->frequency = si476x_to_v4l2(radio->core, + report.readfreq); + } else { + err = -EINVAL; + } + + si476x_core_unlock(radio->core); + + return err; +} + +static int si476x_radio_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + int err; + u32 freq = f->frequency; + struct si476x_tune_freq_args args; + struct si476x_radio *radio = video_drvdata(file); + + const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh + + si476x_bands[SI476X_BAND_FM].rangelow) / 2; + const int band = (freq > midrange) ? + SI476X_BAND_FM : SI476X_BAND_AM; + const enum si476x_func func = (band == SI476X_BAND_AM) ? + SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER; + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + freq = clamp(freq, + si476x_bands[band].rangelow, + si476x_bands[band].rangehigh); + + if (si476x_radio_freq_is_inside_of_the_band(freq, + SI476X_BAND_AM) && + (!si476x_core_has_am(radio->core) || + si476x_core_is_a_secondary_tuner(radio->core))) { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + args.zifsr = false; + args.hd = false; + args.injside = SI476X_INJSIDE_AUTO; + args.freq = v4l2_to_si476x(radio->core, freq); + args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE; + args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO; + args.antcap = 0; + + err = radio->ops->tune_freq(radio->core, &args); + +unlock: + si476x_core_unlock(radio->core); + return err; +} + +static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv, + const struct v4l2_hw_freq_seek *seek) +{ + int err; + enum si476x_func func; + u32 rangelow, rangehigh; + struct si476x_radio *radio = video_drvdata(file); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (seek->tuner != 0 || + seek->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + if (!seek->rangelow) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + &rangelow); + if (!err) + rangelow = si476x_to_v4l2(radio->core, rangelow); + else + goto unlock; + } + if (!seek->rangehigh) { + err = regmap_read(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + &rangehigh); + if (!err) + rangehigh = si476x_to_v4l2(radio->core, rangehigh); + else + goto unlock; + } + + if (rangelow > rangehigh) { + err = -EINVAL; + goto unlock; + } + + if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_FM)) { + func = SI476X_FUNC_FM_RECEIVER; + + } else if (si476x_core_has_am(radio->core) && + si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh, + SI476X_BAND_AM)) { + func = SI476X_FUNC_AM_RECEIVER; + } else { + err = -EINVAL; + goto unlock; + } + + err = si476x_radio_change_func(radio, func); + if (err < 0) + goto unlock; + + if (seek->rangehigh) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_TOP, + v4l2_to_si476x(radio->core, + seek->rangehigh)); + if (err) + goto unlock; + } + if (seek->rangelow) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_BAND_BOTTOM, + v4l2_to_si476x(radio->core, + seek->rangelow)); + if (err) + goto unlock; + } + if (seek->spacing) { + err = regmap_write(radio->core->regmap, + SI476X_PROP_SEEK_FREQUENCY_SPACING, + v4l2_to_si476x(radio->core, + seek->spacing)); + if (err) + goto unlock; + } + + err = radio->ops->seek_start(radio->core, + seek->seek_upward, + seek->wrap_around); +unlock: + si476x_core_unlock(radio->core); + + + + return err; +} + +static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_INTERCHIP_LINK: + if (si476x_core_has_diversity(radio->core)) { + if (radio->ops->phase_diversity) { + retval = radio->ops->phase_div_status(radio->core); + if (retval < 0) + break; + + ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval); + retval = 0; + break; + } else { + retval = -ENOTTY; + break; + } + } + retval = -EINVAL; + break; + default: + retval = -EINVAL; + break; + } + si476x_core_unlock(radio->core); + return retval; + +} + +static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl) +{ + int retval; + enum si476x_phase_diversity_mode mode; + struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler); + + si476x_core_lock(radio->core); + + switch (ctrl->id) { + case V4L2_CID_SI476X_HARMONICS_COUNT: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_HARMONICS_MASK, + ctrl->val); + break; + case V4L2_CID_POWER_LINE_FREQUENCY: + switch (ctrl->val) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_ENABLE_MASK, + 0); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_50HZ); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_AUDIO_PWR_LINE_FILTER, + SI476X_PROP_PWR_GRID_MASK, + SI476X_PROP_PWR_GRID_60HZ); + break; + default: + retval = -EINVAL; + break; + } + break; + case V4L2_CID_SI476X_RSSI_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_RSSI_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_SNR_THRESHOLD: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_SNR_THRESHOLD, + ctrl->val); + break; + case V4L2_CID_SI476X_MAX_TUNE_ERROR: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_VALID_MAX_TUNE_ERROR, + ctrl->val); + break; + case V4L2_CID_RDS_RECEPTION: + /* + * It looks like RDS related properties are + * inaccesable when tuner is in AM mode, so cache the + * changes + */ + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, true); + + if (ctrl->val) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT, + radio->core->rds_fifo_depth); + if (retval < 0) + break; + + if (radio->core->client->irq) { + retval = regmap_write(radio->core->regmap, + SI476X_PROP_FM_RDS_INTERRUPT_SOURCE, + SI476X_RDSRECV); + if (retval < 0) + break; + } + + /* Drain RDS FIFO before enabling RDS processing */ + retval = si476x_core_cmd_fm_rds_status(radio->core, + false, + true, + true, + NULL); + if (retval < 0) + break; + + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + SI476X_PROP_RDSEN); + } else { + retval = regmap_update_bits(radio->core->regmap, + SI476X_PROP_FM_RDS_CONFIG, + SI476X_PROP_RDSEN_MASK, + !SI476X_PROP_RDSEN); + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) + regcache_cache_only(radio->core->regmap, false); + break; + case V4L2_CID_TUNE_DEEMPHASIS: + retval = regmap_write(radio->core->regmap, + SI476X_PROP_AUDIO_DEEMPHASIS, + ctrl->val); + break; + + case V4L2_CID_SI476X_DIVERSITY_MODE: + mode = si476x_phase_diversity_idx_to_mode(ctrl->val); + + if (mode == radio->core->diversity_mode) { + retval = 0; + break; + } + + if (si476x_core_is_in_am_receiver_mode(radio->core)) { + /* + * Diversity cannot be configured while tuner + * is in AM mode so save the changes and carry on. + */ + radio->core->diversity_mode = mode; + retval = 0; + } else { + retval = radio->ops->phase_diversity(radio->core, mode); + if (!retval) + radio->core->diversity_mode = mode; + } + break; + + default: + retval = -EINVAL; + break; + } + + si476x_core_unlock(radio->core); + + return retval; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int si476x_radio_g_register(struct file *file, void *fh, + struct v4l2_dbg_register *reg) +{ + int err; + unsigned int value; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + reg->size = 2; + err = regmap_read(radio->core->regmap, + (unsigned int)reg->reg, &value); + reg->val = value; + si476x_core_unlock(radio->core); + + return err; +} +static int si476x_radio_s_register(struct file *file, void *fh, + const struct v4l2_dbg_register *reg) +{ + + int err; + struct si476x_radio *radio = video_drvdata(file); + + si476x_core_lock(radio->core); + err = regmap_write(radio->core->regmap, + (unsigned int)reg->reg, + (unsigned int)reg->val); + si476x_core_unlock(radio->core); + + return err; +} +#endif + +static int si476x_radio_fops_open(struct file *file) +{ + struct si476x_radio *radio = video_drvdata(file); + int err; + + err = v4l2_fh_open(file); + if (err) + return err; + + if (v4l2_fh_is_singular_file(file)) { + si476x_core_lock(radio->core); + err = si476x_core_set_power_state(radio->core, + SI476X_POWER_UP_FULL); + if (err < 0) + goto done; + + err = si476x_radio_do_post_powerup_init(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + err = si476x_radio_pretune(radio, + radio->core->power_up_parameters.func); + if (err < 0) + goto power_down; + + si476x_core_unlock(radio->core); + /*Must be done after si476x_core_unlock to prevent a deadlock*/ + v4l2_ctrl_handler_setup(&radio->ctrl_handler); + } + + return err; + +power_down: + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); +done: + si476x_core_unlock(radio->core); + v4l2_fh_release(file); + + return err; +} + +static int si476x_radio_fops_release(struct file *file) +{ + int err; + struct si476x_radio *radio = video_drvdata(file); + + if (v4l2_fh_is_singular_file(file) && + atomic_read(&radio->core->is_alive)) + si476x_core_set_power_state(radio->core, + SI476X_POWER_DOWN); + + err = v4l2_fh_release(file); + + return err; +} + +static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t rval; + size_t fifo_len; + unsigned int copied; + + struct si476x_radio *radio = video_drvdata(file); + + /* block if no new data available */ + if (kfifo_is_empty(&radio->core->rds_fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + rval = wait_event_interruptible(radio->core->rds_read_queue, + (!kfifo_is_empty(&radio->core->rds_fifo) || + !atomic_read(&radio->core->is_alive))); + if (rval < 0) + return -EINTR; + + if (!atomic_read(&radio->core->is_alive)) + return -ENODEV; + } + + fifo_len = kfifo_len(&radio->core->rds_fifo); + + if (kfifo_to_user(&radio->core->rds_fifo, buf, + min(fifo_len, count), + &copied) != 0) { + dev_warn(&radio->videodev.dev, + "Error during FIFO to userspace copy\n"); + rval = -EIO; + } else { + rval = (ssize_t)copied; + } + + return rval; +} + +static unsigned int si476x_radio_fops_poll(struct file *file, + struct poll_table_struct *pts) +{ + struct si476x_radio *radio = video_drvdata(file); + unsigned long req_events = poll_requested_events(pts); + unsigned int err = v4l2_ctrl_poll(file, pts); + + if (req_events & (POLLIN | POLLRDNORM)) { + if (atomic_read(&radio->core->is_alive)) + poll_wait(file, &radio->core->rds_read_queue, pts); + + if (!atomic_read(&radio->core->is_alive)) + err = POLLHUP; + + if (!kfifo_is_empty(&radio->core->rds_fifo)) + err = POLLIN | POLLRDNORM; + } + + return err; +} + +static const struct v4l2_file_operations si476x_fops = { + .owner = THIS_MODULE, + .read = si476x_radio_fops_read, + .poll = si476x_radio_fops_poll, + .unlocked_ioctl = video_ioctl2, + .open = si476x_radio_fops_open, + .release = si476x_radio_fops_release, +}; + + +static const struct v4l2_ioctl_ops si4761_ioctl_ops = { + .vidioc_querycap = si476x_radio_querycap, + .vidioc_g_tuner = si476x_radio_g_tuner, + .vidioc_s_tuner = si476x_radio_s_tuner, + + .vidioc_g_frequency = si476x_radio_g_frequency, + .vidioc_s_frequency = si476x_radio_s_frequency, + .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek, + .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = si476x_radio_g_register, + .vidioc_s_register = si476x_radio_s_register, +#endif +}; + + +static const struct video_device si476x_viddev_template = { + .fops = &si476x_fops, + .name = DRIVER_NAME, + .release = video_device_release_empty, +}; + + + +static ssize_t si476x_radio_read_acf_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_acf_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->acf_status) + err = radio->ops->acf_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_acf_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_acf_blob, +}; + +static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rds_blockcount_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rds_blckcnt(radio->core, true, + &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rds_blckcnt_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rds_blckcnt_blob, +}; + +static ssize_t si476x_radio_read_agc_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_agc_status_report report; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->agc_status(radio->core, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_agc_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_agc_blob, +}; + +static ssize_t si476x_radio_read_rsq_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = false, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_blob, +}; + +static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + struct si476x_radio *radio = file->private_data; + struct si476x_rsq_status_report report; + struct si476x_rsq_status_args args = { + .primary = true, + .rsqack = false, + .attune = false, + .cancel = false, + .stcack = false, + }; + + si476x_core_lock(radio->core); + if (radio->ops->rds_blckcnt) + err = radio->ops->rsq_status(radio->core, &args, &report); + else + err = -ENOENT; + si476x_core_unlock(radio->core); + + if (err < 0) + return err; + + return simple_read_from_buffer(user_buf, count, ppos, &report, + sizeof(report)); +} + +static const struct file_operations radio_rsq_primary_fops = { + .open = simple_open, + .llseek = default_llseek, + .read = si476x_radio_read_rsq_primary_blob, +}; + + +static int si476x_radio_init_debugfs(struct si476x_radio *radio) +{ + struct dentry *dentry; + int ret; + + dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto exit; + } + radio->debugfs = dentry; + + dentry = debugfs_create_file("acf", S_IRUGO, + radio->debugfs, radio, &radio_acf_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rds_blckcnt", S_IRUGO, + radio->debugfs, radio, + &radio_rds_blckcnt_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("agc", S_IRUGO, + radio->debugfs, radio, &radio_agc_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq", S_IRUGO, + radio->debugfs, radio, &radio_rsq_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + dentry = debugfs_create_file("rsq_primary", S_IRUGO, + radio->debugfs, radio, + &radio_rsq_primary_fops); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto cleanup; + } + + return 0; +cleanup: + debugfs_remove_recursive(radio->debugfs); +exit: + return ret; +} + + +static int si476x_radio_add_new_custom(struct si476x_radio *radio, + enum si476x_ctrl_idx idx) +{ + int rval; + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler, + &si476x_ctrls[idx], + NULL); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) + dev_err(radio->v4l2dev.dev, + "Could not initialize '%s' control %d\n", + si476x_ctrls[idx].name, rval); + + return rval; +} + +static int si476x_radio_probe(struct platform_device *pdev) +{ + int rval; + struct si476x_radio *radio; + struct v4l2_ctrl *ctrl; + + static atomic_t instance = ATOMIC_INIT(0); + + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); + if (!radio) + return -ENOMEM; + + radio->core = i2c_mfd_cell_to_core(&pdev->dev); + + v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance); + + rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev); + if (rval) { + dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); + return rval; + } + + memcpy(&radio->videodev, &si476x_viddev_template, + sizeof(struct video_device)); + + radio->videodev.v4l2_dev = &radio->v4l2dev; + radio->videodev.ioctl_ops = &si4761_ioctl_ops; + + video_set_drvdata(&radio->videodev, radio); + platform_set_drvdata(pdev, radio); + + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); + + radio->v4l2dev.ctrl_handler = &radio->ctrl_handler; + v4l2_ctrl_handler_init(&radio->ctrl_handler, + 1 + ARRAY_SIZE(si476x_ctrls)); + + if (si476x_core_has_am(radio->core)) { + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, + 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n", + rval); + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, + SI476X_IDX_HARMONICS_COUNT); + if (rval < 0) + goto exit; + } + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD); + if (rval < 0) + goto exit; + + rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR); + if (rval < 0) + goto exit; + + ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler, + &si476x_ctrl_ops, + V4L2_CID_TUNE_DEEMPHASIS, + V4L2_DEEMPHASIS_75_uS, 0, 0); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n", + rval); + goto exit; + } + + ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops, + V4L2_CID_RDS_RECEPTION, + 0, 1, 1, 1); + rval = radio->ctrl_handler.error; + if (ctrl == NULL && rval) { + dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n", + rval); + goto exit; + } + + if (si476x_core_has_diversity(radio->core)) { + si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def = + si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode); + si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE); + if (rval < 0) + goto exit; + + si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK); + if (rval < 0) + goto exit; + } + + /* register video device */ + rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1); + if (rval < 0) { + dev_err(&pdev->dev, "Could not register video device\n"); + goto exit; + } + + rval = si476x_radio_init_debugfs(radio); + if (rval < 0) { + dev_err(&pdev->dev, "Could not creat debugfs interface\n"); + goto exit; + } + + return 0; +exit: + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + return rval; +} + +static int si476x_radio_remove(struct platform_device *pdev) +{ + struct si476x_radio *radio = platform_get_drvdata(pdev); + + v4l2_ctrl_handler_free(radio->videodev.ctrl_handler); + video_unregister_device(&radio->videodev); + v4l2_device_unregister(&radio->v4l2dev); + debugfs_remove_recursive(radio->debugfs); + + return 0; +} + +MODULE_ALIAS("platform:si476x-radio"); + +static struct platform_driver si476x_radio_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = si476x_radio_probe, + .remove = si476x_radio_remove, +}; +module_platform_driver(si476x_radio_driver); + +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/radio/radio-tea5764.c b/drivers/media/radio/radio-tea5764.c index db20904d01f..3ed1f5669f7 100644 --- a/drivers/media/radio/radio-tea5764.c +++ b/drivers/media/radio/radio-tea5764.c @@ -39,6 +39,9 @@ #include <linux/i2c.h> /* I2C */ #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> #define DRIVER_VERSION "0.0.2" @@ -57,8 +60,8 @@ /* Frequency limits in MHz -- these are European values. For Japanese devices, that would be 76000 and 91000. */ -#define FREQ_MIN 87500 -#define FREQ_MAX 108000 +#define FREQ_MIN 87500U +#define FREQ_MAX 108000U #define FREQ_MUL 16 /* TEA5764 registers */ @@ -138,21 +141,26 @@ static int radio_nr = -1; static int use_xtal = RADIO_TEA5764_XTAL; struct tea5764_device { + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; struct i2c_client *i2c_client; - struct video_device *videodev; + struct video_device vdev; struct tea5764_regs regs; struct mutex mutex; }; /* I2C code related */ -int tea5764_i2c_read(struct tea5764_device *radio) +static int tea5764_i2c_read(struct tea5764_device *radio) { int i; u16 *p = (u16 *) &radio->regs; struct i2c_msg msgs[1] = { - { radio->i2c_client->addr, I2C_M_RD, sizeof(radio->regs), - (void *)&radio->regs }, + { .addr = radio->i2c_client->addr, + .flags = I2C_M_RD, + .len = sizeof(radio->regs), + .buf = (void *)&radio->regs + }, }; if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1) return -EIO; @@ -162,12 +170,16 @@ int tea5764_i2c_read(struct tea5764_device *radio) return 0; } -int tea5764_i2c_write(struct tea5764_device *radio) +static int tea5764_i2c_write(struct tea5764_device *radio) { struct tea5764_write_regs wr; struct tea5764_regs *r = &radio->regs; struct i2c_msg msgs[1] = { - { radio->i2c_client->addr, 0, sizeof(wr), (void *) &wr }, + { + .addr = radio->i2c_client->addr, + .len = sizeof(wr), + .buf = (void *)&wr + }, }; wr.intreg = r->intreg & 0xff; wr.frqset = __cpu_to_be16(r->frqset); @@ -180,18 +192,6 @@ int tea5764_i2c_write(struct tea5764_device *radio) return 0; } -/* V4L2 code related */ -static struct v4l2_queryctrl radio_qctrl[] = { - { - .id = V4L2_CID_AUDIO_MUTE, - .name = "Mute", - .minimum = 0, - .maximum = 1, - .default_value = 1, - .type = V4L2_CTRL_TYPE_BOOLEAN, - } -}; - static void tea5764_power_up(struct tea5764_device *radio) { struct tea5764_regs *r = &radio->regs; @@ -284,23 +284,19 @@ static void tea5764_mute(struct tea5764_device *radio, int on) tea5764_i2c_write(radio); } -static int tea5764_is_muted(struct tea5764_device *radio) -{ - return radio->regs.tnctrl & TEA5764_TNCTRL_MU; -} - /* V4L2 vidioc */ static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *v) { struct tea5764_device *radio = video_drvdata(file); - struct video_device *dev = radio->videodev; + struct video_device *dev = &radio->vdev; strlcpy(v->driver, dev->dev.driver->name, sizeof(v->driver)); strlcpy(v->card, dev->name, sizeof(v->card)); snprintf(v->bus_info, sizeof(v->bus_info), "I2C:%s", dev_name(&dev->dev)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -313,8 +309,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, if (v->index > 0) return -EINVAL; - memset(v, 0, sizeof(*v)); - strcpy(v->name, "FM"); + strlcpy(v->name, "FM", sizeof(v->name)); v->type = V4L2_TUNER_RADIO; tea5764_i2c_read(radio); v->rangelow = FREQ_MIN * FREQ_MUL; @@ -332,7 +327,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, } static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct tea5764_device *radio = video_drvdata(file); @@ -344,22 +339,26 @@ static int vidioc_s_tuner(struct file *file, void *priv, } static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct tea5764_device *radio = video_drvdata(file); + unsigned freq = f->frequency; if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) return -EINVAL; - if (f->frequency == 0) { + if (freq == 0) { /* We special case this as a power down control. */ tea5764_power_down(radio); - } - if (f->frequency < (FREQ_MIN * FREQ_MUL)) - return -EINVAL; - if (f->frequency > (FREQ_MAX * FREQ_MUL)) + /* Yes, that's what is returned in this case. This + whole special case is non-compliant and should really + be replaced with something better, but changing this + might well break code that depends on this behavior. + So we keep it as-is. */ return -EINVAL; + } + freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); tea5764_power_up(radio); - tea5764_tune(radio, (f->frequency * 125) / 2); + tea5764_tune(radio, (freq * 125) / 2); return 0; } @@ -372,7 +371,6 @@ static int vidioc_g_frequency(struct file *file, void *priv, if (f->tuner != 0) return -EINVAL; tea5764_i2c_read(radio); - memset(f, 0, sizeof(*f)); f->type = V4L2_TUNER_RADIO; if (r->tnctrl & TEA5764_TNCTRL_PUPD0) f->frequency = (tea5764_get_freq(radio) * 2) / 125; @@ -382,83 +380,29 @@ static int vidioc_g_frequency(struct file *file, void *priv, return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { - if (qc->id && qc->id == radio_qctrl[i].id) { - memcpy(qc, &(radio_qctrl[i]), sizeof(*qc)); - return 0; - } - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int tea5764_s_ctrl(struct v4l2_ctrl *ctrl) { - struct tea5764_device *radio = video_drvdata(file); + struct tea5764_device *radio = + container_of(ctrl->handler, struct tea5764_device, ctrl_handler); switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: - tea5764_i2c_read(radio); - ctrl->value = tea5764_is_muted(radio) ? 1 : 0; + tea5764_mute(radio, ctrl->val); return 0; } return -EINVAL; } -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct tea5764_device *radio = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - tea5764_mute(radio, ctrl->value); - return 0; - } - return -EINVAL; -} - -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) -{ - if (i != 0) - return -EINVAL; - return 0; -} - -static int vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - if (a->index > 1) - return -EINVAL; - - strcpy(a->name, "Radio"); - a->capability = V4L2_AUDCAP_STEREO; - return 0; -} - -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - if (a->index != 0) - return -EINVAL; - - return 0; -} +static const struct v4l2_ctrl_ops tea5764_ctrl_ops = { + .s_ctrl = tea5764_s_ctrl, +}; /* File system interface */ static const struct v4l2_file_operations tea5764_fops = { .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, .unlocked_ioctl = video_ioctl2, }; @@ -466,15 +410,11 @@ static const struct v4l2_ioctl_ops tea5764_ioctl_ops = { .vidioc_querycap = vidioc_querycap, .vidioc_g_tuner = vidioc_g_tuner, .vidioc_s_tuner = vidioc_s_tuner, - .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_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_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* V4L2 interface */ @@ -482,14 +422,16 @@ static struct video_device tea5764_radio_template = { .name = "TEA5764 FM-Radio", .fops = &tea5764_fops, .ioctl_ops = &tea5764_ioctl_ops, - .release = video_device_release, + .release = video_device_release_empty, }; /* I2C probe: check if the device exists and register with v4l if it is */ -static int __devinit tea5764_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tea5764_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) { struct tea5764_device *radio; + struct v4l2_device *v4l2_dev; + struct v4l2_ctrl_handler *hdl; struct tea5764_regs *r; int ret; @@ -498,31 +440,45 @@ static int __devinit tea5764_i2c_probe(struct i2c_client *client, if (!radio) return -ENOMEM; + v4l2_dev = &radio->v4l2_dev; + ret = v4l2_device_register(&client->dev, v4l2_dev); + if (ret < 0) { + v4l2_err(v4l2_dev, "could not register v4l2_device\n"); + goto errfr; + } + + hdl = &radio->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 1); + v4l2_ctrl_new_std(hdl, &tea5764_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + v4l2_dev->ctrl_handler = hdl; + if (hdl->error) { + ret = hdl->error; + v4l2_err(v4l2_dev, "Could not register controls\n"); + goto errunreg; + } + mutex_init(&radio->mutex); radio->i2c_client = client; ret = tea5764_i2c_read(radio); if (ret) - goto errfr; + goto errunreg; r = &radio->regs; PDEBUG("chipid = %04X, manid = %04X", r->chipid, r->manid); if (r->chipid != TEA5764_CHIPID || (r->manid & 0x0fff) != TEA5764_MANID) { PWARN("This chip is not a TEA5764!"); ret = -EINVAL; - goto errfr; + goto errunreg; } - radio->videodev = video_device_alloc(); - if (!(radio->videodev)) { - ret = -ENOMEM; - goto errfr; - } - memcpy(radio->videodev, &tea5764_radio_template, - sizeof(tea5764_radio_template)); + radio->vdev = tea5764_radio_template; i2c_set_clientdata(client, radio); - video_set_drvdata(radio->videodev, radio); - radio->videodev->lock = &radio->mutex; + video_set_drvdata(&radio->vdev, radio); + radio->vdev.lock = &radio->mutex; + radio->vdev.v4l2_dev = v4l2_dev; + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); /* initialize and power off the chip */ tea5764_i2c_read(radio); @@ -530,29 +486,32 @@ static int __devinit tea5764_i2c_probe(struct i2c_client *client, tea5764_mute(radio, 1); tea5764_power_down(radio); - ret = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr); + ret = video_register_device(&radio->vdev, VFL_TYPE_RADIO, radio_nr); if (ret < 0) { PWARN("Could not register video device!"); - goto errrel; + goto errunreg; } PINFO("registered."); return 0; -errrel: - video_device_release(radio->videodev); +errunreg: + v4l2_ctrl_handler_free(hdl); + v4l2_device_unregister(v4l2_dev); errfr: kfree(radio); return ret; } -static int __devexit tea5764_i2c_remove(struct i2c_client *client) +static int tea5764_i2c_remove(struct i2c_client *client) { struct tea5764_device *radio = i2c_get_clientdata(client); PDEBUG("remove"); if (radio) { tea5764_power_down(radio); - video_unregister_device(radio->videodev); + video_unregister_device(&radio->vdev); + v4l2_ctrl_handler_free(&radio->ctrl_handler); + v4l2_device_unregister(&radio->v4l2_dev); kfree(radio); } return 0; @@ -571,25 +530,11 @@ static struct i2c_driver tea5764_i2c_driver = { .owner = THIS_MODULE, }, .probe = tea5764_i2c_probe, - .remove = __devexit_p(tea5764_i2c_remove), + .remove = tea5764_i2c_remove, .id_table = tea5764_id, }; -/* init the driver */ -static int __init tea5764_init(void) -{ - int ret = i2c_add_driver(&tea5764_i2c_driver); - - printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ": " - DRIVER_DESC "\n"); - return ret; -} - -/* cleanup the driver */ -static void __exit tea5764_exit(void) -{ - i2c_del_driver(&tea5764_i2c_driver); -} +module_i2c_driver(tea5764_i2c_driver); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); @@ -600,6 +545,3 @@ module_param(use_xtal, int, 0); MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board"); module_param(radio_nr, int, 0); MODULE_PARM_DESC(radio_nr, "video4linux device number to use"); - -module_init(tea5764_init); -module_exit(tea5764_exit); diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c new file mode 100644 index 00000000000..e2455970725 --- /dev/null +++ b/drivers/media/radio/radio-tea5777.c @@ -0,0 +1,603 @@ +/* + * 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 "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"); + +#define TEA5777_FM_IF 150 /* kHz */ +#define TEA5777_FM_FREQ_STEP 50 /* kHz */ + +#define TEA5777_AM_IF 21 /* kHz */ +#define TEA5777_AM_FREQ_STEP 1 /* 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 0LL /* 50k steps, 150k 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_LW 0LL +#define TEA5777_W_AM_MW 1LL +#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 + +enum { BAND_FM, BAND_AM }; + +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_FREQ_BANDS | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_PROG_LIM, + .rangelow = 76000 * 16, + .rangehigh = 108000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 1, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_PROG_LIM, + .rangelow = 530 * 16, + .rangehigh = 1710 * 16, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq) +{ + switch (tea->band) { + case BAND_FM: + return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16; + case BAND_AM: + return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16; + } + return 0; /* Never reached */ +} + +int radio_tea5777_set_freq(struct radio_tea5777 *tea) +{ + u32 freq; + int res; + + freq = clamp(tea->freq, bands[tea->band].rangelow, + bands[tea->band].rangehigh); + freq = (freq + 8) / 16; /* to kHz */ + + switch (tea->band) { + case BAND_FM: + tea->write_reg &= ~TEA5777_W_AM_FM_MASK; + freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP; + tea->write_reg &= ~TEA5777_W_FM_PLL_MASK; + tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT; + tea->write_reg &= ~TEA5777_W_FM_FREF_MASK; + tea->write_reg |= TEA5777_W_FM_FREF_VALUE << + TEA5777_W_FM_FREF_SHIFT; + tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK; + if (tea->audmode == V4L2_TUNER_MODE_MONO) + tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT; + break; + case BAND_AM: + tea->write_reg &= ~TEA5777_W_AM_FM_MASK; + tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT); + freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP; + tea->write_reg &= ~TEA5777_W_AM_PLL_MASK; + tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT; + tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; + tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK; + tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK; + tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT; + tea->write_reg &= ~TEA5777_W_AM_LNA_MASK; + tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT; + tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK; + tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT; + tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK; + break; + } + + 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_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct radio_tea5777 *tea = video_drvdata(file); + + if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) || + (!tea->has_am && band->index == BAND_AM)) + return -EINVAL; + + *band = bands[band->index]; + 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_FREQ_BANDS | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_PROG_LIM; + v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : + bands[BAND_FM].rangelow; + v->rangehigh = bands[BAND_FM].rangehigh; + if (tea->band == BAND_FM && + (tea->read_reg & TEA5777_R_FM_STEREO_MASK)) + v->rxsubchans = V4L2_TUNER_SUB_STEREO; + else + v->rxsubchans = V4L2_TUNER_SUB_MONO; + v->audmode = tea->audmode; + /* 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, + const struct v4l2_tuner *v) +{ + struct radio_tea5777 *tea = video_drvdata(file); + u32 orig_audmode = tea->audmode; + + if (v->index) + return -EINVAL; + + tea->audmode = v->audmode; + if (tea->audmode > V4L2_TUNER_MODE_STEREO) + tea->audmode = V4L2_TUNER_MODE_STEREO; + + if (tea->audmode != orig_audmode && tea->band == BAND_FM) + return radio_tea5777_set_freq(tea); + + return 0; +} + +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, + const struct v4l2_frequency *f) +{ + struct radio_tea5777 *tea = video_drvdata(file); + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (tea->has_am && f->frequency < (20000 * 16)) + tea->band = BAND_AM; + else + tea->band = BAND_FM; + + tea->freq = f->frequency; + return radio_tea5777_set_freq(tea); +} + +static int vidioc_s_hw_freq_seek(struct file *file, void *fh, + const struct v4l2_hw_freq_seek *a) +{ + struct radio_tea5777 *tea = video_drvdata(file); + unsigned long timeout; + u32 rangelow = a->rangelow; + u32 rangehigh = a->rangehigh; + int i, res, spacing; + u32 orig_freq; + + if (a->tuner || a->wrap_around) + return -EINVAL; + + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + if (rangelow || rangehigh) { + for (i = 0; i < ARRAY_SIZE(bands); i++) { + if (i == BAND_AM && !tea->has_am) + continue; + if (bands[i].rangelow >= rangelow && + bands[i].rangehigh <= rangehigh) + break; + } + if (i == ARRAY_SIZE(bands)) + return -EINVAL; /* No matching band found */ + + tea->band = i; + if (tea->freq < rangelow || tea->freq > rangehigh) { + tea->freq = clamp(tea->freq, rangelow, + rangehigh); + res = radio_tea5777_set_freq(tea); + if (res) + return res; + } + } else { + rangelow = bands[tea->band].rangelow; + rangehigh = bands[tea->band].rangehigh; + } + + spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */ + orig_freq = tea->freq; + + tea->write_reg |= TEA5777_W_PROGBLIM_MASK; + if (tea->seek_rangelow != rangelow) { + tea->write_reg &= ~TEA5777_W_UPDWN_MASK; + tea->freq = rangelow; + res = radio_tea5777_set_freq(tea); + if (res) + goto leave; + tea->seek_rangelow = rangelow; + } + if (tea->seek_rangehigh != rangehigh) { + tea->write_reg |= TEA5777_W_UPDWN_MASK; + tea->freq = rangehigh; + res = radio_tea5777_set_freq(tea); + if (res) + goto leave; + tea->seek_rangehigh = rangehigh; + } + 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_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 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) | + (1LL << TEA5777_W_SLEV_SHIFT); + tea->freq = 90500 * 16; /* 90.5Mhz default */ + tea->audmode = V4L2_TUNER_MODE_STEREO; + 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..4ea43a90a15 --- /dev/null +++ b/drivers/media/radio/radio-tea5777.h @@ -0,0 +1,90 @@ +#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 band; /* current band */ + u32 freq; /* current frequency */ + u32 audmode; /* last set audmode */ + 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); +int radio_tea5777_set_freq(struct radio_tea5777 *tea); + +#endif /* __RADIO_TEA5777_H */ diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c index f2ed9cc3cf3..be10a802e3a 100644 --- a/drivers/media/radio/radio-terratec.c +++ b/drivers/media/radio/radio-terratec.c @@ -16,11 +16,7 @@ * Frequency control is done digitally -- ie out(port,encodefreq(95.8)); * Volume Control is done digitally * - * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday - * (as soon i have understand how to get started :) - * If you can help me out with that, please contact me!! - * - * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> */ @@ -30,43 +26,24 @@ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/mutex.h> #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" -MODULE_AUTHOR("R.OFFERMANNS & others"); +MODULE_AUTHOR("R. Offermans & others"); MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); - -#ifndef CONFIG_RADIO_TERRATEC_PORT -#define CONFIG_RADIO_TERRATEC_PORT 0x590 -#endif +MODULE_VERSION("0.1.99"); -static int io = CONFIG_RADIO_TERRATEC_PORT; +/* Note: there seems to be only one possible port (0x590), but without + hardware this is hard to verify. For now, this is the only one we will + support. */ +static int io = 0x590; static int radio_nr = -1; -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)"); -module_param(radio_nr, int, 0); - -static struct v4l2_queryctrl radio_qctrl[] = { - { - .id = V4L2_CID_AUDIO_MUTE, - .name = "Mute", - .minimum = 0, - .maximum = 1, - .default_value = 1, - .type = V4L2_CTRL_TYPE_BOOLEAN, - },{ - .id = V4L2_CID_AUDIO_VOLUME, - .name = "Volume", - .minimum = 0, - .maximum = 0xff, - .step = 1, - .default_value = 0xff, - .type = V4L2_CTRL_TYPE_INTEGER, - } -}; +module_param(radio_nr, int, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device number"); #define WRT_DIS 0x00 #define CLK_OFF 0x00 @@ -76,63 +53,24 @@ static struct v4l2_queryctrl radio_qctrl[] = { #define CLK_ON 0x08 #define WRT_EN 0x10 -struct terratec +static struct radio_isa_card *terratec_alloc(void) { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; - int curvol; - unsigned long curfreq; - int muted; - struct mutex lock; -}; - -static struct terratec terratec_card; - -/* local things */ + return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL); +} -static void tt_write_vol(struct terratec *tt, int volume) +static int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { int i; - volume = volume + (volume * 32); /* change both channels */ - mutex_lock(&tt->lock); + if (mute) + vol = 0; + vol = vol + (vol * 32); /* change both channels */ for (i = 0; i < 8; i++) { - if (volume & (0x80 >> i)) - outb(0x80, tt->io + 1); + if (vol & (0x80 >> i)) + outb(0x80, isa->io + 1); else - outb(0x00, tt->io + 1); - } - mutex_unlock(&tt->lock); -} - - - -static void tt_mute(struct terratec *tt) -{ - tt->muted = 1; - tt_write_vol(tt, 0); -} - -static int tt_setvol(struct terratec *tt, int vol) -{ - if (vol == tt->curvol) { /* requested volume = current */ - if (tt->muted) { /* user is unmuting the card */ - tt->muted = 0; - tt_write_vol(tt, vol); /* enable card */ - } - return 0; - } - - if (vol == 0) { /* volume = 0 means mute the card */ - tt_write_vol(tt, 0); /* "turn off card" by setting vol to 0 */ - tt->curvol = vol; /* track the volume state! */ - return 0; + outb(0x00, isa->io + 1); } - - tt->muted = 0; - tt_write_vol(tt, vol); - tt->curvol = vol; return 0; } @@ -140,20 +78,15 @@ static int tt_setvol(struct terratec *tt, int vol) /* this is the worst part in this driver */ /* many more or less strange things are going on here, but hey, it works :) */ -static int tt_setfreq(struct terratec *tt, unsigned long freq1) +static int terratec_s_frequency(struct radio_isa_card *isa, u32 freq) { - int freq; int i; int p; - int temp; + int temp; long rest; unsigned char buffer[25]; /* we have to bit shift 25 registers */ - mutex_lock(&tt->lock); - - tt->curfreq = freq1; - - freq = freq1 / 160; /* convert the freq. to a nice to handle value */ + freq = freq / 160; /* convert the freq. to a nice to handle value */ memset(buffer, 0, sizeof(buffer)); rest = freq * 10 + 10700; /* I once had understood what is going on here */ @@ -175,239 +108,61 @@ static int tt_setfreq(struct terratec *tt, unsigned long freq1) for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */ if (buffer[i] == 1) { - outb(WRT_EN | DATA, tt->io); - outb(WRT_EN | DATA | CLK_ON, tt->io); - outb(WRT_EN | DATA, tt->io); + outb(WRT_EN | DATA, isa->io); + outb(WRT_EN | DATA | CLK_ON, isa->io); + outb(WRT_EN | DATA, isa->io); } else { - outb(WRT_EN | 0x00, tt->io); - outb(WRT_EN | 0x00 | CLK_ON, tt->io); - } - } - outb(0x00, tt->io); - - mutex_unlock(&tt->lock); - - return 0; -} - -static int tt_getsigstr(struct terratec *tt) -{ - if (inb(tt->io) & 2) /* bit set = no signal present */ - return 0; - return 1; /* signal present */ -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-terratec", sizeof(v->driver)); - strlcpy(v->card, "ActiveRadio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct terratec *tt = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * tt_getsigstr(tt); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct terratec *tt = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - tt_setfreq(tt, f->frequency); - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct terratec *tt = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = tt->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { - if (qc->id && qc->id == radio_qctrl[i].id) { - memcpy(qc, &(radio_qctrl[i]), sizeof(*qc)); - return 0; + outb(WRT_EN | 0x00, isa->io); + outb(WRT_EN | 0x00 | CLK_ON, isa->io); } } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct terratec *tt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (tt->muted) - ctrl->value = 1; - else - ctrl->value = 0; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = tt->curvol * 6554; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct terratec *tt = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - tt_mute(tt); - else - tt_setvol(tt,tt->curvol); - return 0; - case V4L2_CID_AUDIO_VOLUME: - tt_setvol(tt,ctrl->value); - return 0; - } - return -EINVAL; -} - -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; + outb(0x00, isa->io); return 0; } -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) +static u32 terratec_g_signal(struct radio_isa_card *isa) { - return a->index ? -EINVAL : 0; + /* bit set = no signal present */ + return (inb(isa->io) & 2) ? 0 : 0xffff; } -static const struct v4l2_file_operations terratec_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops terratec_ops = { + .alloc = terratec_alloc, + .s_mute_volume = terratec_s_mute_volume, + .s_frequency = terratec_s_frequency, + .g_signal = terratec_g_signal, }; -static const struct v4l2_ioctl_ops terratec_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_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, +static const int terratec_ioports[] = { 0x590 }; + +static struct radio_isa_driver terratec_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-terratec", + }, + }, + .io_params = &io, + .radio_nr_params = &radio_nr, + .io_ports = terratec_ioports, + .num_of_io_ports = ARRAY_SIZE(terratec_ioports), + .region_size = 2, + .card = "TerraTec ActiveRadio", + .ops = &terratec_ops, + .has_stereo = true, + .max_volume = 10, }; static int __init terratec_init(void) { - struct terratec *tt = &terratec_card; - struct v4l2_device *v4l2_dev = &tt->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "terratec", sizeof(v4l2_dev->name)); - tt->io = io; - if (tt->io == -1) { - v4l2_err(v4l2_dev, "you must set an I/O address with io=0x590 or 0x591\n"); - return -EINVAL; - } - if (!request_region(tt->io, 2, "terratec")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(tt->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - strlcpy(tt->vdev.name, v4l2_dev->name, sizeof(tt->vdev.name)); - tt->vdev.v4l2_dev = v4l2_dev; - tt->vdev.fops = &terratec_fops; - tt->vdev.ioctl_ops = &terratec_ioctl_ops; - tt->vdev.release = video_device_release_empty; - video_set_drvdata(&tt->vdev, tt); - - mutex_init(&tt->lock); - - /* mute card - prevents noisy bootups */ - tt_write_vol(tt, 0); - - if (video_register_device(&tt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(&tt->v4l2_dev); - release_region(tt->io, 2); - return -EINVAL; - } - - v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver.\n"); - return 0; + return isa_register_driver(&terratec_driver.driver, 1); } static void __exit terratec_exit(void) { - struct terratec *tt = &terratec_card; - struct v4l2_device *v4l2_dev = &tt->v4l2_dev; - - video_unregister_device(&tt->vdev); - v4l2_device_unregister(&tt->v4l2_dev); - release_region(tt->io, 2); - v4l2_info(v4l2_dev, "TERRATEC ActivRadio Standalone card driver unloaded.\n"); + isa_unregister_driver(&terratec_driver.driver); } module_init(terratec_init); diff --git a/drivers/media/radio/radio-timb.c b/drivers/media/radio/radio-timb.c index 5d9a90ac3a1..0817964d917 100644 --- a/drivers/media/radio/radio-timb.c +++ b/drivers/media/radio/radio-timb.c @@ -19,6 +19,8 @@ #include <linux/io.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/slab.h> @@ -44,7 +46,8 @@ static int timbradio_vidioc_querycap(struct file *file, void *priv, strlcpy(v->driver, DRIVER_NAME, sizeof(v->driver)); strlcpy(v->card, "Timberdale Radio", sizeof(v->card)); snprintf(v->bus_info, sizeof(v->bus_info), "platform:"DRIVER_NAME); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -56,42 +59,14 @@ static int timbradio_vidioc_g_tuner(struct file *file, void *priv, } static int timbradio_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) + const struct v4l2_tuner *v) { struct timbradio *tr = video_drvdata(file); return v4l2_subdev_call(tr->sd_tuner, tuner, s_tuner, v); } -static int timbradio_vidioc_g_input(struct file *filp, void *priv, - unsigned int *i) -{ - *i = 0; - return 0; -} - -static int timbradio_vidioc_s_input(struct file *filp, void *priv, - unsigned int i) -{ - return i ? -EINVAL : 0; -} - -static int timbradio_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 timbradio_vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) -{ - return a->index ? -EINVAL : 0; -} - static int timbradio_vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) + const struct v4l2_frequency *f) { struct timbradio *tr = video_drvdata(file); return v4l2_subdev_call(tr->sd_tuner, tuner, s_frequency, f); @@ -104,48 +79,26 @@ static int timbradio_vidioc_g_frequency(struct file *file, void *priv, return v4l2_subdev_call(tr->sd_tuner, tuner, g_frequency, f); } -static int timbradio_vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - struct timbradio *tr = video_drvdata(file); - return v4l2_subdev_call(tr->sd_dsp, core, queryctrl, qc); -} - -static int timbradio_vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct timbradio *tr = video_drvdata(file); - return v4l2_subdev_call(tr->sd_dsp, core, g_ctrl, ctrl); -} - -static int timbradio_vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct timbradio *tr = video_drvdata(file); - return v4l2_subdev_call(tr->sd_dsp, core, s_ctrl, ctrl); -} - static const struct v4l2_ioctl_ops timbradio_ioctl_ops = { .vidioc_querycap = timbradio_vidioc_querycap, .vidioc_g_tuner = timbradio_vidioc_g_tuner, .vidioc_s_tuner = timbradio_vidioc_s_tuner, .vidioc_g_frequency = timbradio_vidioc_g_frequency, .vidioc_s_frequency = timbradio_vidioc_s_frequency, - .vidioc_g_input = timbradio_vidioc_g_input, - .vidioc_s_input = timbradio_vidioc_s_input, - .vidioc_g_audio = timbradio_vidioc_g_audio, - .vidioc_s_audio = timbradio_vidioc_s_audio, - .vidioc_queryctrl = timbradio_vidioc_queryctrl, - .vidioc_g_ctrl = timbradio_vidioc_g_ctrl, - .vidioc_s_ctrl = timbradio_vidioc_s_ctrl + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; static const struct v4l2_file_operations timbradio_fops = { .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, .unlocked_ioctl = video_ioctl2, }; -static int __devinit timbradio_probe(struct platform_device *pdev) +static int timbradio_probe(struct platform_device *pdev) { struct timb_radio_platform_data *pdata = pdev->dev.platform_data; struct timbradio *tr; @@ -157,7 +110,7 @@ static int __devinit timbradio_probe(struct platform_device *pdev) goto err; } - tr = kzalloc(sizeof(*tr), GFP_KERNEL); + tr = devm_kzalloc(&pdev->dev, sizeof(*tr), GFP_KERNEL); if (!tr) { err = -ENOMEM; goto err; @@ -173,14 +126,24 @@ static int __devinit timbradio_probe(struct platform_device *pdev) tr->video_dev.release = video_device_release_empty; tr->video_dev.minor = -1; tr->video_dev.lock = &tr->lock; + set_bit(V4L2_FL_USE_FH_PRIO, &tr->video_dev.flags); strlcpy(tr->v4l2_dev.name, DRIVER_NAME, sizeof(tr->v4l2_dev.name)); err = v4l2_device_register(NULL, &tr->v4l2_dev); if (err) - goto err_v4l2_dev; + goto err; tr->video_dev.v4l2_dev = &tr->v4l2_dev; + tr->sd_tuner = v4l2_i2c_new_subdev_board(&tr->v4l2_dev, + i2c_get_adapter(pdata->i2c_adapter), pdata->tuner, NULL); + tr->sd_dsp = v4l2_i2c_new_subdev_board(&tr->v4l2_dev, + i2c_get_adapter(pdata->i2c_adapter), pdata->dsp, NULL); + if (tr->sd_tuner == NULL || tr->sd_dsp == NULL) + goto err_video_req; + + tr->v4l2_dev.ctrl_handler = tr->sd_dsp->ctrl_handler; + err = video_register_device(&tr->video_dev, VFL_TYPE_RADIO, -1); if (err) { dev_err(&pdev->dev, "Error reg video\n"); @@ -193,27 +156,19 @@ static int __devinit timbradio_probe(struct platform_device *pdev) return 0; err_video_req: - video_device_release_empty(&tr->video_dev); v4l2_device_unregister(&tr->v4l2_dev); -err_v4l2_dev: - kfree(tr); err: dev_err(&pdev->dev, "Failed to register: %d\n", err); return err; } -static int __devexit timbradio_remove(struct platform_device *pdev) +static int timbradio_remove(struct platform_device *pdev) { struct timbradio *tr = platform_get_drvdata(pdev); video_unregister_device(&tr->video_dev); - video_device_release_empty(&tr->video_dev); - v4l2_device_unregister(&tr->v4l2_dev); - - kfree(tr); - return 0; } diff --git a/drivers/media/radio/radio-trust.c b/drivers/media/radio/radio-trust.c index b3f45a019d8..26a8c600212 100644 --- a/drivers/media/radio/radio-trust.c +++ b/drivers/media/radio/radio-trust.c @@ -21,13 +21,15 @@ #include <linux/ioport.h> #include <linux/videodev2.h> #include <linux/io.h> +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath"); MODULE_DESCRIPTION("A driver for the Trust FM Radio card."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("0.1.99"); /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ @@ -35,39 +37,38 @@ MODULE_VERSION("0.0.3"); #define CONFIG_RADIO_TRUST_PORT -1 #endif -static int io = CONFIG_RADIO_TRUST_PORT; -static int radio_nr = -1; +#define TRUST_MAX 2 -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)"); -module_param(radio_nr, int, 0); +static int io[TRUST_MAX] = { [0] = CONFIG_RADIO_TRUST_PORT, + [1 ... (TRUST_MAX - 1)] = -1 }; +static int radio_nr[TRUST_MAX] = { [0 ... (TRUST_MAX - 1)] = -1 }; + +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Trust FM Radio card (0x350 or 0x358)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); struct trust { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; + struct radio_isa_card isa; int ioval; - __u16 curvol; - __u16 curbass; - __u16 curtreble; - int muted; - unsigned long curfreq; - int curstereo; - int curmute; - struct mutex lock; }; -static struct trust trust_card; +static struct radio_isa_card *trust_alloc(void) +{ + struct trust *tr = kzalloc(sizeof(*tr), GFP_KERNEL); + + return tr ? &tr->isa : NULL; +} /* i2c addresses */ #define TDA7318_ADDR 0x88 #define TSA6060T_ADDR 0xc4 -#define TR_DELAY do { inb(tr->io); inb(tr->io); inb(tr->io); } while (0) -#define TR_SET_SCL outb(tr->ioval |= 2, tr->io) -#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->io) -#define TR_SET_SDA outb(tr->ioval |= 1, tr->io) -#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->io) +#define TR_DELAY do { inb(tr->isa.io); inb(tr->isa.io); inb(tr->isa.io); } while (0) +#define TR_SET_SCL outb(tr->ioval |= 2, tr->isa.io) +#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->isa.io) +#define TR_SET_SDA outb(tr->ioval |= 1, tr->isa.io) +#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->isa.io) static void write_i2c(struct trust *tr, int n, ...) { @@ -84,10 +85,10 @@ static void write_i2c(struct trust *tr, int n, ...) TR_CLR_SCL; TR_DELAY; - for(; n; n--) { + for (; n; n--) { val = va_arg(args, unsigned); - for(mask = 0x80; mask; mask >>= 1) { - if(val & mask) + for (mask = 0x80; mask; mask >>= 1) { + if (val & mask) TR_SET_SDA; else TR_CLR_SDA; @@ -115,317 +116,128 @@ static void write_i2c(struct trust *tr, int n, ...) va_end(args); } -static void tr_setvol(struct trust *tr, __u16 vol) +static int trust_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - mutex_lock(&tr->lock); - tr->curvol = vol / 2048; - write_i2c(tr, 2, TDA7318_ADDR, tr->curvol ^ 0x1f); - mutex_unlock(&tr->lock); -} + struct trust *tr = container_of(isa, struct trust, isa); -static int basstreble2chip[15] = { - 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 -}; - -static void tr_setbass(struct trust *tr, __u16 bass) -{ - mutex_lock(&tr->lock); - tr->curbass = bass / 4370; - write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[tr->curbass]); - mutex_unlock(&tr->lock); -} - -static void tr_settreble(struct trust *tr, __u16 treble) -{ - mutex_lock(&tr->lock); - tr->curtreble = treble / 4370; - write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[tr->curtreble]); - mutex_unlock(&tr->lock); + tr->ioval = (tr->ioval & 0xf7) | (mute << 3); + outb(tr->ioval, isa->io); + write_i2c(tr, 2, TDA7318_ADDR, vol ^ 0x1f); + return 0; } -static void tr_setstereo(struct trust *tr, int stereo) +static int trust_s_stereo(struct radio_isa_card *isa, bool stereo) { - mutex_lock(&tr->lock); - tr->curstereo = !!stereo; - tr->ioval = (tr->ioval & 0xfb) | (!tr->curstereo << 2); - outb(tr->ioval, tr->io); - mutex_unlock(&tr->lock); -} + struct trust *tr = container_of(isa, struct trust, isa); -static void tr_setmute(struct trust *tr, int mute) -{ - mutex_lock(&tr->lock); - tr->curmute = !!mute; - tr->ioval = (tr->ioval & 0xf7) | (tr->curmute << 3); - outb(tr->ioval, tr->io); - mutex_unlock(&tr->lock); + tr->ioval = (tr->ioval & 0xfb) | (!stereo << 2); + outb(tr->ioval, isa->io); + return 0; } -static int tr_getsigstr(struct trust *tr) +static u32 trust_g_signal(struct radio_isa_card *isa) { int i, v; - mutex_lock(&tr->lock); for (i = 0, v = 0; i < 100; i++) - v |= inb(tr->io); - mutex_unlock(&tr->lock); + v |= inb(isa->io); return (v & 1) ? 0 : 0xffff; } -static int tr_getstereo(struct trust *tr) -{ - /* don't know how to determine it, just return the setting */ - return tr->curstereo; -} - -static void tr_setfreq(struct trust *tr, unsigned long f) +static int trust_s_frequency(struct radio_isa_card *isa, u32 freq) { - mutex_lock(&tr->lock); - tr->curfreq = f; - f /= 160; /* Convert to 10 kHz units */ - f += 1070; /* Add 10.7 MHz IF */ - write_i2c(tr, 5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0); - mutex_unlock(&tr->lock); -} + struct trust *tr = container_of(isa, struct trust, isa); -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-trust", sizeof(v->driver)); - strlcpy(v->card, "Trust FM Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + freq /= 160; /* Convert to 10 kHz units */ + freq += 1070; /* Add 10.7 MHz IF */ + write_i2c(tr, 5, TSA6060T_ADDR, (freq << 1) | 1, + freq >> 7, 0x60 | ((freq >> 15) & 1), 0); return 0; } -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct trust *tr = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87.5 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (tr_getstereo(tr)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = tr_getsigstr(tr); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct trust *tr = video_drvdata(file); - - if (v->index) - return -EINVAL; - tr_setstereo(tr, v->audmode == V4L2_TUNER_MODE_STEREO); - return 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct trust *tr = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - tr_setfreq(tr, f->frequency); - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct trust *tr = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = tr->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - 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, 65535, 2048, 65535); - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - return v4l2_ctrl_query_fill(qc, 0, 65535, 4370, 32768); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct trust *tr = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = tr->curmute; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = tr->curvol * 2048; - return 0; - case V4L2_CID_AUDIO_BASS: - ctrl->value = tr->curbass * 4370; - return 0; - case V4L2_CID_AUDIO_TREBLE: - ctrl->value = tr->curtreble * 4370; - return 0; - } - return -EINVAL; -} +static int basstreble2chip[15] = { + 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 +}; -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int trust_s_ctrl(struct v4l2_ctrl *ctrl) { - struct trust *tr = video_drvdata(file); + struct radio_isa_card *isa = + container_of(ctrl->handler, struct radio_isa_card, hdl); + struct trust *tr = container_of(isa, struct trust, isa); switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - tr_setmute(tr, ctrl->value); - return 0; - case V4L2_CID_AUDIO_VOLUME: - tr_setvol(tr, ctrl->value); - return 0; case V4L2_CID_AUDIO_BASS: - tr_setbass(tr, ctrl->value); + write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[ctrl->val]); return 0; case V4L2_CID_AUDIO_TREBLE: - tr_settreble(tr, ctrl->value); + write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[ctrl->val]); return 0; } return -EINVAL; } -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; -} - -static const struct v4l2_file_operations trust_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, -}; - -static const struct v4l2_ioctl_ops trust_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_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, +static const struct v4l2_ctrl_ops trust_ctrl_ops = { + .s_ctrl = trust_s_ctrl, }; -static int __init trust_init(void) +static int trust_initialize(struct radio_isa_card *isa) { - struct trust *tr = &trust_card; - struct v4l2_device *v4l2_dev = &tr->v4l2_dev; - int res; + struct trust *tr = container_of(isa, struct trust, isa); - strlcpy(v4l2_dev->name, "trust", sizeof(v4l2_dev->name)); - tr->io = io; tr->ioval = 0xf; - mutex_init(&tr->lock); - - if (tr->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x0x350 or 0x358\n"); - return -EINVAL; - } - if (!request_region(tr->io, 2, "Trust FM Radio")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", tr->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(tr->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - strlcpy(tr->vdev.name, v4l2_dev->name, sizeof(tr->vdev.name)); - tr->vdev.v4l2_dev = v4l2_dev; - tr->vdev.fops = &trust_fops; - tr->vdev.ioctl_ops = &trust_ioctl_ops; - tr->vdev.release = video_device_release_empty; - video_set_drvdata(&tr->vdev, tr); - write_i2c(tr, 2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */ write_i2c(tr, 2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */ - tr_setvol(tr, 0xffff); - tr_setbass(tr, 0x8000); - tr_settreble(tr, 0x8000); - tr_setstereo(tr, 1); - - /* mute card - prevents noisy bootups */ - tr_setmute(tr, 1); + v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops, + V4L2_CID_AUDIO_BASS, 0, 15, 1, 8); + v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, 0, 15, 1, 8); + return isa->hdl.error; +} - if (video_register_device(&tr->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(tr->io, 2); - return -EINVAL; - } +static const struct radio_isa_ops trust_ops = { + .init = trust_initialize, + .alloc = trust_alloc, + .s_mute_volume = trust_s_mute_volume, + .s_frequency = trust_s_frequency, + .s_stereo = trust_s_stereo, + .g_signal = trust_g_signal, +}; - v4l2_info(v4l2_dev, "Trust FM Radio card driver v1.0.\n"); +static const int trust_ioports[] = { 0x350, 0x358 }; + +static struct radio_isa_driver trust_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-trust", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = trust_ioports, + .num_of_io_ports = ARRAY_SIZE(trust_ioports), + .region_size = 2, + .card = "Trust FM Radio", + .ops = &trust_ops, + .has_stereo = true, + .max_volume = 31, +}; - return 0; +static int __init trust_init(void) +{ + return isa_register_driver(&trust_driver.driver, TRUST_MAX); } -static void __exit cleanup_trust_module(void) +static void __exit trust_exit(void) { - struct trust *tr = &trust_card; - - video_unregister_device(&tr->vdev); - v4l2_device_unregister(&tr->v4l2_dev); - release_region(tr->io, 2); + isa_unregister_driver(&trust_driver.driver); } module_init(trust_init); -module_exit(cleanup_trust_module); +module_exit(trust_exit); diff --git a/drivers/media/radio/radio-typhoon.c b/drivers/media/radio/radio-typhoon.c index 398726abc0c..eb72a4d1375 100644 --- a/drivers/media/radio/radio-typhoon.c +++ b/drivers/media/radio/radio-typhoon.c @@ -33,63 +33,53 @@ #include <linux/ioport.h> /* request_region */ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" #define DRIVER_VERSION "0.1.2" MODULE_AUTHOR("Dr. Henrik Seidel"); MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio)."); MODULE_LICENSE("GPL"); -MODULE_VERSION(DRIVER_VERSION); +MODULE_VERSION("0.1.99"); #ifndef CONFIG_RADIO_TYPHOON_PORT #define CONFIG_RADIO_TYPHOON_PORT -1 #endif #ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ -#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0 +#define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000 #endif -static int io = CONFIG_RADIO_TYPHOON_PORT; -static int radio_nr = -1; - -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)"); - -module_param(radio_nr, int, 0); +#define TYPHOON_MAX 2 +static int io[TYPHOON_MAX] = { [0] = CONFIG_RADIO_TYPHOON_PORT, + [1 ... (TYPHOON_MAX - 1)] = -1 }; +static int radio_nr[TYPHOON_MAX] = { [0 ... (TYPHOON_MAX - 1)] = -1 }; static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ; + +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Typhoon card (0x316 or 0x336)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); module_param(mutefreq, ulong, 0); MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)"); -#define BANNER "Typhoon Radio Card driver v" DRIVER_VERSION "\n" - struct typhoon { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; - int curvol; + struct radio_isa_card isa; int muted; - unsigned long curfreq; - unsigned long mutefreq; - struct mutex lock; }; -static struct typhoon typhoon_card; - -static void typhoon_setvol_generic(struct typhoon *dev, int vol) +static struct radio_isa_card *typhoon_alloc(void) { - mutex_lock(&dev->lock); - vol >>= 14; /* Map 16 bit to 2 bit */ - vol &= 3; - outb_p(vol / 2, dev->io); /* Set the volume, high bit. */ - outb_p(vol % 2, dev->io + 2); /* Set the volume, low bit. */ - mutex_unlock(&dev->lock); + struct typhoon *ty = kzalloc(sizeof(*ty), GFP_KERNEL); + + return ty ? &ty->isa : NULL; } -static int typhoon_setfreq_generic(struct typhoon *dev, - unsigned long frequency) +static int typhoon_s_frequency(struct radio_isa_card *isa, u32 freq) { unsigned long outval; unsigned long x; @@ -105,302 +95,86 @@ static int typhoon_setfreq_generic(struct typhoon *dev, * */ - mutex_lock(&dev->lock); - x = frequency / 160; + x = freq / 160; outval = (x * x + 2500) / 5000; outval = (outval * x + 5000) / 10000; outval -= (10 * x * x + 10433) / 20866; outval += 4 * x - 11505; - outb_p((outval >> 8) & 0x01, dev->io + 4); - outb_p(outval >> 9, dev->io + 6); - outb_p(outval & 0xff, dev->io + 8); - mutex_unlock(&dev->lock); - - return 0; -} - -static int typhoon_setfreq(struct typhoon *dev, unsigned long frequency) -{ - typhoon_setfreq_generic(dev, frequency); - dev->curfreq = frequency; - return 0; -} - -static void typhoon_mute(struct typhoon *dev) -{ - if (dev->muted == 1) - return; - typhoon_setvol_generic(dev, 0); - typhoon_setfreq_generic(dev, dev->mutefreq); - dev->muted = 1; -} - -static void typhoon_unmute(struct typhoon *dev) -{ - if (dev->muted == 0) - return; - typhoon_setfreq_generic(dev, dev->curfreq); - typhoon_setvol_generic(dev, dev->curvol); - dev->muted = 0; -} - -static int typhoon_setvol(struct typhoon *dev, int vol) -{ - if (dev->muted && vol != 0) { /* user is unmuting the card */ - dev->curvol = vol; - typhoon_unmute(dev); - return 0; - } - if (vol == dev->curvol) /* requested volume == current */ - return 0; - - if (vol == 0) { /* volume == 0 means mute the card */ - typhoon_mute(dev); - dev->curvol = vol; - return 0; - } - typhoon_setvol_generic(dev, vol); - dev->curvol = vol; - return 0; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-typhoon", sizeof(v->driver)); - strlcpy(v->card, "Typhoon Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 87.5 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO; - v->capability = V4L2_TUNER_CAP_LOW; - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF; /* We can't get the signal strength */ - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct typhoon *dev = video_drvdata(file); - - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = dev->curfreq; - return 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct typhoon *dev = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - dev->curfreq = f->frequency; - typhoon_setfreq(dev, dev->curfreq); + outb_p((outval >> 8) & 0x01, isa->io + 4); + outb_p(outval >> 9, isa->io + 6); + outb_p(outval & 0xff, isa->io + 8); return 0; } -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) +static int typhoon_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - 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, 65535, 16384, 65535); - } - return -EINVAL; -} + struct typhoon *ty = container_of(isa, struct typhoon, isa); -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct typhoon *dev = video_drvdata(file); + if (mute) + vol = 0; + vol >>= 14; /* Map 16 bit to 2 bit */ + vol &= 3; + outb_p(vol / 2, isa->io); /* Set the volume, high bit. */ + outb_p(vol % 2, isa->io + 2); /* Set the volume, low bit. */ - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = dev->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = dev->curvol; - return 0; + if (vol == 0 && !ty->muted) { + ty->muted = true; + return typhoon_s_frequency(isa, mutefreq << 4); } - return -EINVAL; -} - -static int vidioc_s_ctrl (struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct typhoon *dev = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - typhoon_mute(dev); - else - typhoon_unmute(dev); - return 0; - case V4L2_CID_AUDIO_VOLUME: - typhoon_setvol(dev, ctrl->value); - return 0; + if (vol && ty->muted) { + ty->muted = false; + return typhoon_s_frequency(isa, isa->freq); } - return -EINVAL; -} - -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; -} - -static int vidioc_log_status(struct file *file, void *priv) -{ - struct typhoon *dev = video_drvdata(file); - struct v4l2_device *v4l2_dev = &dev->v4l2_dev; - - v4l2_info(v4l2_dev, BANNER); -#ifdef MODULE - v4l2_info(v4l2_dev, "Load type: Driver loaded as a module\n\n"); -#else - v4l2_info(v4l2_dev, "Load type: Driver compiled into kernel\n\n"); -#endif - v4l2_info(v4l2_dev, "frequency = %lu kHz\n", dev->curfreq >> 4); - v4l2_info(v4l2_dev, "volume = %d\n", dev->curvol); - v4l2_info(v4l2_dev, "mute = %s\n", dev->muted ? "on" : "off"); - v4l2_info(v4l2_dev, "io = 0x%x\n", dev->io); - v4l2_info(v4l2_dev, "mute frequency = %lu kHz\n", dev->mutefreq >> 4); - return 0; -} - -static const struct v4l2_file_operations typhoon_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops typhoon_ops = { + .alloc = typhoon_alloc, + .s_mute_volume = typhoon_s_mute_volume, + .s_frequency = typhoon_s_frequency, }; -static const struct v4l2_ioctl_ops typhoon_ioctl_ops = { - .vidioc_log_status = vidioc_log_status, - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .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_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, +static const int typhoon_ioports[] = { 0x316, 0x336 }; + +static struct radio_isa_driver typhoon_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-typhoon", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = typhoon_ioports, + .num_of_io_ports = ARRAY_SIZE(typhoon_ioports), + .region_size = 8, + .card = "Typhoon Radio", + .ops = &typhoon_ops, + .has_stereo = true, + .max_volume = 3, }; static int __init typhoon_init(void) { - struct typhoon *dev = &typhoon_card; - struct v4l2_device *v4l2_dev = &dev->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "typhoon", sizeof(v4l2_dev->name)); - dev->io = io; - - if (dev->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x316 or io=0x336\n"); - return -EINVAL; - } - - if (mutefreq < 87000 || mutefreq > 108500) { - v4l2_err(v4l2_dev, "You must set a frequency (in kHz) used when muting the card,\n"); - v4l2_err(v4l2_dev, "e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n"); - return -EINVAL; - } - dev->curfreq = dev->mutefreq = mutefreq << 4; - - mutex_init(&dev->lock); - if (!request_region(dev->io, 8, "typhoon")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", - dev->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(dev->io, 8); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; + if (mutefreq < 87000 || mutefreq > 108000) { + printk(KERN_ERR "%s: You must set a frequency (in kHz) used when muting the card,\n", + typhoon_driver.driver.driver.name); + printk(KERN_ERR "%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n", + typhoon_driver.driver.driver.name); + return -ENODEV; } - v4l2_info(v4l2_dev, BANNER); - - strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); - dev->vdev.v4l2_dev = v4l2_dev; - dev->vdev.fops = &typhoon_fops; - dev->vdev.ioctl_ops = &typhoon_ioctl_ops; - dev->vdev.release = video_device_release_empty; - video_set_drvdata(&dev->vdev, dev); - - /* mute card - prevents noisy bootups */ - typhoon_mute(dev); - - if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(&dev->v4l2_dev); - release_region(dev->io, 8); - return -EINVAL; - } - v4l2_info(v4l2_dev, "port 0x%x.\n", dev->io); - v4l2_info(v4l2_dev, "mute frequency is %lu kHz.\n", mutefreq); - - return 0; + return isa_register_driver(&typhoon_driver.driver, TYPHOON_MAX); } static void __exit typhoon_exit(void) { - struct typhoon *dev = &typhoon_card; - - video_unregister_device(&dev->vdev); - v4l2_device_unregister(&dev->v4l2_dev); - release_region(dev->io, 8); + isa_unregister_driver(&typhoon_driver.driver); } + module_init(typhoon_init); module_exit(typhoon_exit); diff --git a/drivers/media/radio/radio-wl1273.c b/drivers/media/radio/radio-wl1273.c index f1b607099b6..9cf6731fb81 100644 --- a/drivers/media/radio/radio-wl1273.c +++ b/drivers/media/radio/radio-wl1273.c @@ -375,7 +375,7 @@ static int wl1273_fm_set_tx_freq(struct wl1273_device *radio, unsigned int freq) if (r) return r; - INIT_COMPLETION(radio->busy); + reinit_completion(&radio->busy); /* wait for the FR IRQ */ r = wait_for_completion_timeout(&radio->busy, msecs_to_jiffies(2000)); @@ -389,7 +389,7 @@ static int wl1273_fm_set_tx_freq(struct wl1273_device *radio, unsigned int freq) if (r) return r; - INIT_COMPLETION(radio->busy); + reinit_completion(&radio->busy); /* wait for the POWER_ENB IRQ */ r = wait_for_completion_timeout(&radio->busy, msecs_to_jiffies(1000)); @@ -444,7 +444,7 @@ static int wl1273_fm_set_rx_freq(struct wl1273_device *radio, unsigned int freq) goto err; } - INIT_COMPLETION(radio->busy); + reinit_completion(&radio->busy); r = wait_for_completion_timeout(&radio->busy, msecs_to_jiffies(2000)); if (!r) { @@ -805,7 +805,7 @@ static int wl1273_fm_set_seek(struct wl1273_device *radio, if (level < SCHAR_MIN || level > SCHAR_MAX) return -EINVAL; - INIT_COMPLETION(radio->busy); + reinit_completion(&radio->busy); dev_dbg(radio->dev, "%s: BUSY\n", __func__); r = core->write(core, WL1273_INT_MASK_SET, radio->irq_flags); @@ -847,7 +847,7 @@ static int wl1273_fm_set_seek(struct wl1273_device *radio, if (r) goto out; - INIT_COMPLETION(radio->busy); + reinit_completion(&radio->busy); dev_dbg(radio->dev, "%s: BUSY\n", __func__); r = core->write(core, WL1273_TUNER_MODE_SET, TUNER_MODE_AUTO_SEEK); @@ -1479,7 +1479,7 @@ static int wl1273_fm_vidioc_g_audio(struct file *file, void *priv, } static int wl1273_fm_vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *audio) + const struct v4l2_audio *audio) { struct wl1273_device *radio = video_get_drvdata(video_devdata(file)); @@ -1514,7 +1514,8 @@ static int wl1273_fm_vidioc_g_tuner(struct file *file, void *priv, tuner->rangehigh = WL1273_FREQ(WL1273_BAND_OTHER_HIGH); tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_RDS | - V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS_BLOCK_IO; + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | V4L2_TUNER_CAP_HWSEEK_WRAP; if (radio->stereo) tuner->audmode = V4L2_TUNER_MODE_STEREO; @@ -1558,7 +1559,7 @@ out: } static int wl1273_fm_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) + const struct v4l2_tuner *tuner) { struct wl1273_device *radio = video_get_drvdata(video_devdata(file)); struct wl1273_core *core = radio->core; @@ -1639,7 +1640,7 @@ static int wl1273_fm_vidioc_g_frequency(struct file *file, void *priv, } static int wl1273_fm_vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *freq) + const struct v4l2_frequency *freq) { struct wl1273_device *radio = video_get_drvdata(video_devdata(file)); struct wl1273_core *core = radio->core; @@ -1681,7 +1682,7 @@ static int wl1273_fm_vidioc_s_frequency(struct file *file, void *priv, #define WL1273_DEFAULT_SEEK_LEVEL 7 static int wl1273_fm_vidioc_s_hw_freq_seek(struct file *file, void *priv, - struct v4l2_hw_freq_seek *seek) + const struct v4l2_hw_freq_seek *seek) { struct wl1273_device *radio = video_get_drvdata(video_devdata(file)); struct wl1273_core *core = radio->core; @@ -1692,6 +1693,9 @@ static int wl1273_fm_vidioc_s_hw_freq_seek(struct file *file, void *priv, if (seek->tuner != 0 || seek->type != V4L2_TUNER_RADIO) return -EINVAL; + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + if (mutex_lock_interruptible(&core->lock)) return -EINTR; @@ -1714,7 +1718,7 @@ out: } static int wl1273_fm_vidioc_s_modulator(struct file *file, void *priv, - struct v4l2_modulator *modulator) + const struct v4l2_modulator *modulator) { struct wl1273_device *radio = video_get_drvdata(video_devdata(file)); struct wl1273_core *core = radio->core; @@ -1967,6 +1971,7 @@ static struct video_device wl1273_viddev_template = { .ioctl_ops = &wl1273_ioctl_ops, .name = WL1273_FM_DRIVER_NAME, .release = wl1273_vdev_release, + .vfl_dir = VFL_DIR_TX, }; static int wl1273_fm_radio_remove(struct platform_device *pdev) @@ -1982,14 +1987,11 @@ static int wl1273_fm_radio_remove(struct platform_device *pdev) v4l2_ctrl_handler_free(&radio->ctrl_handler); video_unregister_device(&radio->videodev); v4l2_device_unregister(&radio->v4l2dev); - kfree(radio->buffer); - kfree(radio->write_buf); - kfree(radio); return 0; } -static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) +static int wl1273_fm_radio_probe(struct platform_device *pdev) { struct wl1273_core **core = pdev->dev.platform_data; struct wl1273_device *radio; @@ -2004,7 +2006,7 @@ static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) goto pdata_err; } - radio = kzalloc(sizeof(*radio), GFP_KERNEL); + radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL); if (!radio) { r = -ENOMEM; goto pdata_err; @@ -2012,11 +2014,11 @@ static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) /* RDS buffer allocation */ radio->buf_size = rds_buf * RDS_BLOCK_SIZE; - radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL); + radio->buffer = devm_kzalloc(&pdev->dev, radio->buf_size, GFP_KERNEL); if (!radio->buffer) { pr_err("Cannot allocate memory for RDS buffer.\n"); r = -ENOMEM; - goto err_kmalloc; + goto pdata_err; } radio->core = *core; @@ -2042,7 +2044,7 @@ static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) if (r) { dev_err(radio->dev, WL1273_FM_DRIVER_NAME ": Cannot get platform data\n"); - goto err_resources; + goto pdata_err; } dev_dbg(radio->dev, "irq: %d\n", radio->core->client->irq); @@ -2060,13 +2062,13 @@ static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) dev_err(radio->dev, WL1273_FM_DRIVER_NAME ": Core WL1273 IRQ" " not configured"); r = -EINVAL; - goto err_resources; + goto pdata_err; } init_completion(&radio->busy); init_waitqueue_head(&radio->read_queue); - radio->write_buf = kmalloc(256, GFP_KERNEL); + radio->write_buf = devm_kzalloc(&pdev->dev, 256, GFP_KERNEL); if (!radio->write_buf) { r = -ENOMEM; goto write_buf_err; @@ -2079,12 +2081,11 @@ static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) r = v4l2_device_register(&pdev->dev, &radio->v4l2dev); if (r) { dev_err(&pdev->dev, "Cannot register v4l2_device.\n"); - goto device_register_err; + goto write_buf_err; } /* V4L2 configuration */ - memcpy(&radio->videodev, &wl1273_viddev_template, - sizeof(wl1273_viddev_template)); + radio->videodev = wl1273_viddev_template; radio->videodev.v4l2_dev = &radio->v4l2dev; @@ -2134,23 +2135,17 @@ static int __devinit wl1273_fm_radio_probe(struct platform_device *pdev) handler_init_err: v4l2_ctrl_handler_free(&radio->ctrl_handler); v4l2_device_unregister(&radio->v4l2dev); -device_register_err: - kfree(radio->write_buf); write_buf_err: free_irq(radio->core->client->irq, radio); err_request_irq: radio->core->pdata->free_resources(); -err_resources: - kfree(radio->buffer); -err_kmalloc: - kfree(radio); pdata_err: return r; } static struct platform_driver wl1273_fm_radio_driver = { .probe = wl1273_fm_radio_probe, - .remove = __devexit_p(wl1273_fm_radio_remove), + .remove = wl1273_fm_radio_remove, .driver = { .name = "wl1273_fm_radio", .owner = THIS_MODULE, diff --git a/drivers/media/radio/radio-zoltrix.c b/drivers/media/radio/radio-zoltrix.c index f5613b94820..026e88eef29 100644 --- a/drivers/media/radio/radio-zoltrix.c +++ b/drivers/media/radio/radio-zoltrix.c @@ -1,5 +1,6 @@ -/* zoltrix radio plus driver for Linux radio support - * (c) 1998 C. van Schaik <carl@leg.uct.ac.za> +/* + * Zoltrix Radio Plus driver + * Copyright 1998 C. van Schaik <carl@leg.uct.ac.za> * * BUGS * Due to the inconsistency in reading from the signal flags @@ -27,6 +28,14 @@ * * 2006-07-24 - Converted to V4L2 API * by Mauro Carvalho Chehab <mchehab@infradead.org> + * + * Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com> + * + * Note that this is the driver for the Zoltrix Radio Plus. + * This driver does not work for the Zoltrix Radio Plus 108 or the + * Zoltrix Radio Plus for Windows. + * + * Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool. */ #include <linux/module.h> /* Modules */ @@ -36,82 +45,70 @@ #include <linux/videodev2.h> /* kernel radio structs */ #include <linux/mutex.h> #include <linux/io.h> /* outb, outb_p */ +#include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include "radio-isa.h" -MODULE_AUTHOR("C.van Schaik"); +MODULE_AUTHOR("C. van Schaik"); MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus."); MODULE_LICENSE("GPL"); -MODULE_VERSION("0.0.3"); +MODULE_VERSION("0.1.99"); #ifndef CONFIG_RADIO_ZOLTRIX_PORT #define CONFIG_RADIO_ZOLTRIX_PORT -1 #endif -static int io = CONFIG_RADIO_ZOLTRIX_PORT; -static int radio_nr = -1; +#define ZOLTRIX_MAX 2 + +static int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT, + [1 ... (ZOLTRIX_MAX - 1)] = -1 }; +static int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 }; -module_param(io, int, 0); -MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)"); -module_param(radio_nr, int, 0); +module_param_array(io, int, NULL, 0444); +MODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)"); +module_param_array(radio_nr, int, NULL, 0444); +MODULE_PARM_DESC(radio_nr, "Radio device numbers"); struct zoltrix { - struct v4l2_device v4l2_dev; - struct video_device vdev; - int io; + struct radio_isa_card isa; int curvol; - unsigned long curfreq; - int muted; - unsigned int stereo; - struct mutex lock; + bool muted; }; -static struct zoltrix zoltrix_card; +static struct radio_isa_card *zoltrix_alloc(void) +{ + struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL); + + return zol ? &zol->isa : NULL; +} -static int zol_setvol(struct zoltrix *zol, int vol) +static int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) { - zol->curvol = vol; - if (zol->muted) - return 0; + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); - mutex_lock(&zol->lock); - if (vol == 0) { - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); /* Zoltrix needs to be read to confirm */ - mutex_unlock(&zol->lock); + zol->curvol = vol; + zol->muted = mute; + if (mute || vol == 0) { + outb(0, isa->io); + outb(0, isa->io); + inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ return 0; } - outb(zol->curvol-1, zol->io); + outb(vol - 1, isa->io); msleep(10); - inb(zol->io + 2); - mutex_unlock(&zol->lock); + inb(isa->io + 2); return 0; } -static void zol_mute(struct zoltrix *zol) -{ - zol->muted = 1; - mutex_lock(&zol->lock); - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); /* Zoltrix needs to be read to confirm */ - mutex_unlock(&zol->lock); -} - -static void zol_unmute(struct zoltrix *zol) -{ - zol->muted = 0; - zol_setvol(zol, zol->curvol); -} - -static int zol_setfreq(struct zoltrix *zol, unsigned long freq) +/* tunes the radio to the desired frequency */ +static int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq) { - /* tunes the radio to the desired frequency */ - struct v4l2_device *v4l2_dev = &zol->v4l2_dev; + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + struct v4l2_device *v4l2_dev = &isa->v4l2_dev; unsigned long long bitmask, f, m; - unsigned int stereo = zol->stereo; + bool stereo = isa->stereo; int i; if (freq == 0) { @@ -125,340 +122,125 @@ static int zol_setfreq(struct zoltrix *zol, unsigned long freq) bitmask = 0xc480402c10080000ull; i = 45; - mutex_lock(&zol->lock); - - zol->curfreq = freq; - - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); /* Zoltrix needs to be read to confirm */ + outb(0, isa->io); + outb(0, isa->io); + inb(isa->io + 3); /* Zoltrix needs to be read to confirm */ - outb(0x40, zol->io); - outb(0xc0, zol->io); + outb(0x40, isa->io); + outb(0xc0, isa->io); bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31)); while (i--) { if ((bitmask & 0x8000000000000000ull) != 0) { - outb(0x80, zol->io); + outb(0x80, isa->io); udelay(50); - outb(0x00, zol->io); + outb(0x00, isa->io); udelay(50); - outb(0x80, zol->io); + outb(0x80, isa->io); udelay(50); } else { - outb(0xc0, zol->io); + outb(0xc0, isa->io); udelay(50); - outb(0x40, zol->io); + outb(0x40, isa->io); udelay(50); - outb(0xc0, zol->io); + outb(0xc0, isa->io); udelay(50); } bitmask *= 2; } /* termination sequence */ - outb(0x80, zol->io); - outb(0xc0, zol->io); - outb(0x40, zol->io); + outb(0x80, isa->io); + outb(0xc0, isa->io); + outb(0x40, isa->io); udelay(1000); - inb(zol->io + 2); - + inb(isa->io + 2); udelay(1000); - if (zol->muted) { - outb(0, zol->io); - outb(0, zol->io); - inb(zol->io + 3); - udelay(1000); - } - - mutex_unlock(&zol->lock); - - if (!zol->muted) - zol_setvol(zol, zol->curvol); - return 0; + return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol); } /* Get signal strength */ -static int zol_getsigstr(struct zoltrix *zol) +static u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa) { + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); int a, b; - mutex_lock(&zol->lock); - outb(0x00, zol->io); /* This stuff I found to do nothing */ - outb(zol->curvol, zol->io); + outb(0x00, isa->io); /* This stuff I found to do nothing */ + outb(zol->curvol, isa->io); msleep(20); - a = inb(zol->io); + a = inb(isa->io); msleep(10); - b = inb(zol->io); + b = inb(isa->io); - mutex_unlock(&zol->lock); - - if (a != b) - return 0; - - /* I found this out by playing with a binary scanner on the card io */ - return a == 0xcf || a == 0xdf || a == 0xef; + return (a == b && a == 0xcf) ? + V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; } -static int zol_is_stereo(struct zoltrix *zol) +static u32 zoltrix_g_signal(struct radio_isa_card *isa) { - int x1, x2; - - mutex_lock(&zol->lock); + struct zoltrix *zol = container_of(isa, struct zoltrix, isa); + int a, b; - outb(0x00, zol->io); - outb(zol->curvol, zol->io); + outb(0x00, isa->io); /* This stuff I found to do nothing */ + outb(zol->curvol, isa->io); msleep(20); - x1 = inb(zol->io); + a = inb(isa->io); msleep(10); - x2 = inb(zol->io); - - mutex_unlock(&zol->lock); - - return x1 == x2 && x1 == 0xcf; -} - -static int vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *v) -{ - strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver)); - strlcpy(v->card, "Zoltrix Radio", sizeof(v->card)); - strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); - v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; - return 0; -} - -static int vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - struct zoltrix *zol = video_drvdata(file); - - if (v->index > 0) - return -EINVAL; - - strlcpy(v->name, "FM", sizeof(v->name)); - v->type = V4L2_TUNER_RADIO; - v->rangelow = 88 * 16000; - v->rangehigh = 108 * 16000; - v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; - v->capability = V4L2_TUNER_CAP_LOW; - if (zol_is_stereo(zol)) - v->audmode = V4L2_TUNER_MODE_STEREO; - else - v->audmode = V4L2_TUNER_MODE_MONO; - v->signal = 0xFFFF * zol_getsigstr(zol); - return 0; -} - -static int vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *v) -{ - return v->index ? -EINVAL : 0; -} - -static int vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct zoltrix *zol = video_drvdata(file); - - if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) - return -EINVAL; - if (zol_setfreq(zol, f->frequency) != 0) - return -EINVAL; - return 0; -} - -static int vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *f) -{ - struct zoltrix *zol = video_drvdata(file); + b = inb(isa->io); - if (f->tuner != 0) - return -EINVAL; - f->type = V4L2_TUNER_RADIO; - f->frequency = zol->curfreq; - return 0; -} - -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - 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, 65535, 4096, 65535); - } - return -EINVAL; -} - -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct zoltrix *zol = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = zol->muted; - return 0; - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = zol->curvol * 4096; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct zoltrix *zol = video_drvdata(file); - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - zol_mute(zol); - else { - zol_unmute(zol); - zol_setvol(zol, zol->curvol); - } - return 0; - case V4L2_CID_AUDIO_VOLUME: - zol_setvol(zol, ctrl->value / 4096); + if (a != b) return 0; - } - zol->stereo = 1; - if (zol_setfreq(zol, zol->curfreq) != 0) - return -EINVAL; -#if 0 -/* FIXME: Implement stereo/mono switch on V4L2 */ - if (v->mode & VIDEO_SOUND_STEREO) { - zol->stereo = 1; - zol_setfreq(zol, zol->curfreq); - } - if (v->mode & VIDEO_SOUND_MONO) { - zol->stereo = 0; - zol_setfreq(zol, zol->curfreq); - } -#endif - return -EINVAL; -} - -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; + /* I found this out by playing with a binary scanner on the card io */ + return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0; } -static int vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *a) +static int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo) { - return a->index ? -EINVAL : 0; + return zoltrix_s_frequency(isa, isa->freq); } -static const struct v4l2_file_operations zoltrix_fops = -{ - .owner = THIS_MODULE, - .unlocked_ioctl = video_ioctl2, +static const struct radio_isa_ops zoltrix_ops = { + .alloc = zoltrix_alloc, + .s_mute_volume = zoltrix_s_mute_volume, + .s_frequency = zoltrix_s_frequency, + .s_stereo = zoltrix_s_stereo, + .g_rxsubchans = zoltrix_g_rxsubchans, + .g_signal = zoltrix_g_signal, }; -static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = { - .vidioc_querycap = vidioc_querycap, - .vidioc_g_tuner = vidioc_g_tuner, - .vidioc_s_tuner = vidioc_s_tuner, - .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_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, +static const int zoltrix_ioports[] = { 0x20c, 0x30c }; + +static struct radio_isa_driver zoltrix_driver = { + .driver = { + .match = radio_isa_match, + .probe = radio_isa_probe, + .remove = radio_isa_remove, + .driver = { + .name = "radio-zoltrix", + }, + }, + .io_params = io, + .radio_nr_params = radio_nr, + .io_ports = zoltrix_ioports, + .num_of_io_ports = ARRAY_SIZE(zoltrix_ioports), + .region_size = 2, + .card = "Zoltrix Radio Plus", + .ops = &zoltrix_ops, + .has_stereo = true, + .max_volume = 15, }; static int __init zoltrix_init(void) { - struct zoltrix *zol = &zoltrix_card; - struct v4l2_device *v4l2_dev = &zol->v4l2_dev; - int res; - - strlcpy(v4l2_dev->name, "zoltrix", sizeof(v4l2_dev->name)); - zol->io = io; - if (zol->io == -1) { - v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or 0x30c\n"); - return -EINVAL; - } - if (zol->io != 0x20c && zol->io != 0x30c) { - v4l2_err(v4l2_dev, "invalid port, try 0x20c or 0x30c\n"); - return -ENXIO; - } - - if (!request_region(zol->io, 2, "zoltrix")) { - v4l2_err(v4l2_dev, "port 0x%x already in use\n", zol->io); - return -EBUSY; - } - - res = v4l2_device_register(NULL, v4l2_dev); - if (res < 0) { - release_region(zol->io, 2); - v4l2_err(v4l2_dev, "Could not register v4l2_device\n"); - return res; - } - - mutex_init(&zol->lock); - - /* mute card - prevents noisy bootups */ - - /* this ensures that the volume is all the way down */ - - outb(0, zol->io); - outb(0, zol->io); - msleep(20); - inb(zol->io + 3); - - zol->curvol = 0; - zol->stereo = 1; - - strlcpy(zol->vdev.name, v4l2_dev->name, sizeof(zol->vdev.name)); - zol->vdev.v4l2_dev = v4l2_dev; - zol->vdev.fops = &zoltrix_fops; - zol->vdev.ioctl_ops = &zoltrix_ioctl_ops; - zol->vdev.release = video_device_release_empty; - video_set_drvdata(&zol->vdev, zol); - - if (video_register_device(&zol->vdev, VFL_TYPE_RADIO, radio_nr) < 0) { - v4l2_device_unregister(v4l2_dev); - release_region(zol->io, 2); - return -EINVAL; - } - v4l2_info(v4l2_dev, "Zoltrix Radio Plus card driver.\n"); - - return 0; + return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX); } static void __exit zoltrix_exit(void) { - struct zoltrix *zol = &zoltrix_card; - - video_unregister_device(&zol->vdev); - v4l2_device_unregister(&zol->v4l2_dev); - release_region(zol->io, 2); + isa_unregister_driver(&zoltrix_driver.driver); } module_init(zoltrix_init); diff --git a/drivers/media/radio/saa7706h.c b/drivers/media/radio/saa7706h.c index b1193dfc508..ec805b09c60 100644 --- a/drivers/media/radio/saa7706h.c +++ b/drivers/media/radio/saa7706h.c @@ -25,7 +25,7 @@ #include <linux/i2c.h> #include <linux/slab.h> #include <media/v4l2-device.h> -#include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> #define DRIVER_NAME "saa7706h" @@ -127,6 +127,7 @@ struct saa7706h_state { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; unsigned muted; }; @@ -199,8 +200,19 @@ static int saa7706h_get_reg16(struct v4l2_subdev *sd, u16 reg) u8 buf[2]; int err; u8 regaddr[] = {reg >> 8, reg}; - struct i2c_msg msg[] = { {client->addr, 0, sizeof(regaddr), regaddr}, - {client->addr, I2C_M_RD, sizeof(buf), buf} }; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .len = sizeof(regaddr), + .buf = regaddr + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(buf), + .buf = buf + } + }; err = saa7706h_i2c_transfer(client, msg, ARRAY_SIZE(msg)); if (err) @@ -306,51 +318,32 @@ static int saa7706h_mute(struct v4l2_subdev *sd) return err; } -static int saa7706h_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) +static int saa7706h_s_ctrl(struct v4l2_ctrl *ctrl) { - switch (qc->id) { - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } - return -EINVAL; -} - -static int saa7706h_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct saa7706h_state *state = to_state(sd); + struct saa7706h_state *state = + container_of(ctrl->handler, struct saa7706h_state, hdl); switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: - ctrl->value = state->muted; - return 0; + if (ctrl->val) + return saa7706h_mute(&state->sd); + return saa7706h_unmute(&state->sd); } return -EINVAL; } -static int saa7706h_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) - return saa7706h_mute(sd); - return saa7706h_unmute(sd); - } - return -EINVAL; -} - -static int saa7706h_g_chip_ident(struct v4l2_subdev *sd, - struct v4l2_dbg_chip_ident *chip) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - - return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_SAA7706H, 0); -} +static const struct v4l2_ctrl_ops saa7706h_ctrl_ops = { + .s_ctrl = saa7706h_s_ctrl, +}; static const struct v4l2_subdev_core_ops saa7706h_core_ops = { - .g_chip_ident = saa7706h_g_chip_ident, - .queryctrl = saa7706h_queryctrl, - .g_ctrl = saa7706h_g_ctrl, - .s_ctrl = saa7706h_s_ctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, }; static const struct v4l2_subdev_ops saa7706h_ops = { @@ -362,8 +355,8 @@ static const struct v4l2_subdev_ops saa7706h_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int __devinit saa7706h_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int saa7706h_probe(struct i2c_client *client, + const struct i2c_device_id *id) { struct saa7706h_state *state; struct v4l2_subdev *sd; @@ -382,13 +375,20 @@ static int __devinit saa7706h_probe(struct i2c_client *client, sd = &state->sd; v4l2_i2c_subdev_init(sd, client, &saa7706h_ops); + v4l2_ctrl_handler_init(&state->hdl, 4); + v4l2_ctrl_new_std(&state->hdl, &saa7706h_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + sd->ctrl_handler = &state->hdl; + err = state->hdl.error; + if (err) + goto err; + /* check the rom versions */ err = saa7706h_get_reg16(sd, SAA7706H_DSP1_ROM_VER); if (err < 0) goto err; if (err != SUPPORTED_DSP1_ROM_VER) v4l2_warn(sd, "Unknown DSP1 ROM code version: 0x%x\n", err); - state->muted = 1; /* startup in a muted state */ @@ -400,6 +400,7 @@ static int __devinit saa7706h_probe(struct i2c_client *client, err: v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); kfree(to_state(sd)); printk(KERN_ERR DRIVER_NAME ": Failed to probe: %d\n", err); @@ -407,12 +408,14 @@ err: return err; } -static int __devexit saa7706h_remove(struct i2c_client *client) +static int saa7706h_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct saa7706h_state *state = to_state(sd); saa7706h_mute(sd); v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&state->hdl); kfree(to_state(sd)); return 0; } @@ -434,18 +437,7 @@ static struct i2c_driver saa7706h_driver = { .id_table = saa7706h_id, }; -static __init int saa7706h_init(void) -{ - return i2c_add_driver(&saa7706h_driver); -} - -static __exit void saa7706h_exit(void) -{ - i2c_del_driver(&saa7706h_driver); -} - -module_init(saa7706h_init); -module_exit(saa7706h_exit); +module_i2c_driver(saa7706h_driver); MODULE_DESCRIPTION("SAA7706H Car Radio DSP driver"); MODULE_AUTHOR("Mocean Laboratories"); diff --git a/drivers/media/radio/si470x/radio-si470x-common.c b/drivers/media/radio/si470x/radio-si470x-common.c index 0e740c98786..0e750aef656 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,69 @@ 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_FREQ_BANDS | + 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_FREQ_BANDS | + 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_FREQ_BANDS | + 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,34 +217,19 @@ 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 */ + reinit_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"); + dev_warn(&radio->videodev.dev, "tune does not complete\n"); if (timed_out) - dev_warn(&radio->videodev->dev, + 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 +238,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,37 +281,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; 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 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; - }; - + 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); } @@ -302,66 +296,70 @@ 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) + const 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; retval = si470x_set_register(radio, POWERCFG); if (retval < 0) - goto done; + 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 */ + reinit_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"); + dev_warn(&radio->videodev.dev, "seek does not complete\n"); if (radio->registers[STATUSRSSI] & STATUSRSSI_SF) - dev_warn(&radio->videodev->dev, + dev_warn(&radio->videodev.dev, "seek failed / band limit reached\n"); - if (timed_out) - dev_warn(&radio->videodev->dev, - "seek timed out after %u ms\n", seek_timeout); -stop: /* stop seeking */ radio->registers[POWERCFG] &= ~POWERCFG_SEEK; retval = si470x_set_register(radio, POWERCFG); -done: /* try again, if timed out */ - if ((retval == 0) && timed_out) - retval = -EAGAIN; - + if (retval == 0 && timed_out) + return -ENODATA; return retval; } @@ -389,8 +387,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); @@ -463,7 +461,6 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf, unsigned int block_count = 0; /* switch on rds reception */ - mutex_lock(&radio->lock); if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) si470x_rds_on(radio); @@ -505,7 +502,6 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf, } done: - mutex_unlock(&radio->lock); return retval; } @@ -517,19 +513,19 @@ static unsigned int si470x_fops_poll(struct file *file, struct poll_table_struct *pts) { struct si470x_device *radio = video_drvdata(file); - int retval = 0; + unsigned long req_events = poll_requested_events(pts); + int retval = v4l2_ctrl_poll(file, pts); - /* switch on rds reception */ + if (req_events & (POLLIN | POLLRDNORM)) { + /* switch on rds reception */ + if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) + si470x_rds_on(radio); - mutex_lock(&radio->lock); - if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) - si470x_rds_on(radio); - mutex_unlock(&radio->lock); - - poll_wait(file, &radio->read_queue, pts); + poll_wait(file, &radio->read_queue, pts); - if (radio->rd_index != radio->wr_index) - retval = POLLIN | POLLRDNORM; + if (radio->rd_index != radio->wr_index) + retval |= POLLIN | POLLRDNORM; + } return retval; } @@ -553,134 +549,26 @@ static const struct v4l2_file_operations si470x_fops = { * Video4Linux Interface **************************************************************************/ -/* - * si470x_vidioc_queryctrl - enumerate control items - */ -static int si470x_vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - struct si470x_device *radio = video_drvdata(file); - int retval = -EINVAL; - - /* abort if qc->id is below V4L2_CID_BASE */ - if (qc->id < V4L2_CID_BASE) - goto done; - - /* search video control */ - switch (qc->id) { - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 15, 1, 15); - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - } - - /* disable unsupported base controls */ - /* to satisfy kradio and such apps */ - if ((retval == -EINVAL) && (qc->id < V4L2_CID_LASTP1)) { - qc->flags = V4L2_CTRL_FLAG_DISABLED; - retval = 0; - } - -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "query controls failed with %d\n", retval); - return retval; -} - - -/* - * si470x_vidioc_g_ctrl - get the value of a control - */ -static int si470x_vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct si470x_device *radio = video_drvdata(file); - int retval = 0; - - mutex_lock(&radio->lock); - /* safety checks */ - retval = si470x_disconnect_check(radio); - if (retval) - goto done; - - switch (ctrl->id) { - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = radio->registers[SYSCONFIG2] & - SYSCONFIG2_VOLUME; - break; - case V4L2_CID_AUDIO_MUTE: - ctrl->value = ((radio->registers[POWERCFG] & - POWERCFG_DMUTE) == 0) ? 1 : 0; - break; - default: - retval = -EINVAL; - } - -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "get control failed with %d\n", retval); - - mutex_unlock(&radio->lock); - return retval; -} - -/* - * si470x_vidioc_s_ctrl - set the value of a control - */ -static int si470x_vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int si470x_s_ctrl(struct v4l2_ctrl *ctrl) { - struct si470x_device *radio = video_drvdata(file); - int retval = 0; - - mutex_lock(&radio->lock); - /* safety checks */ - retval = si470x_disconnect_check(radio); - if (retval) - goto done; + struct si470x_device *radio = + container_of(ctrl->handler, struct si470x_device, hdl); switch (ctrl->id) { case V4L2_CID_AUDIO_VOLUME: radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; - radio->registers[SYSCONFIG2] |= ctrl->value; - retval = si470x_set_register(radio, SYSCONFIG2); - break; + radio->registers[SYSCONFIG2] |= ctrl->val; + return si470x_set_register(radio, SYSCONFIG2); case V4L2_CID_AUDIO_MUTE: - if (ctrl->value == 1) + if (ctrl->val) radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; else radio->registers[POWERCFG] |= POWERCFG_DMUTE; - retval = si470x_set_register(radio, POWERCFG); - break; + return si470x_set_register(radio, POWERCFG); default: - retval = -EINVAL; + return -EINVAL; } - -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "set control failed with %d\n", retval); - mutex_unlock(&radio->lock); - return retval; -} - - -/* - * si470x_vidioc_g_audio - get audio attributes - */ -static int si470x_vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *audio) -{ - /* driver constants */ - audio->index = 0; - strcpy(audio->name, "Radio"); - audio->capability = V4L2_AUDCAP_STEREO; - audio->mode = 0; - - return 0; } @@ -693,51 +581,30 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv, struct si470x_device *radio = video_drvdata(file); int retval = 0; - mutex_lock(&radio->lock); - /* safety checks */ - retval = si470x_disconnect_check(radio); - if (retval) - goto done; + if (tuner->index != 0) + return -EINVAL; - if (tuner->index != 0) { - retval = -EINVAL; - goto done; + if (!radio->status_rssi_auto_update) { + retval = si470x_get_register(radio, STATUSRSSI); + if (retval < 0) + return retval; } - retval = si470x_get_register(radio, STATUSRSSI); - if (retval < 0) - goto done; - /* driver constants */ strcpy(tuner->name, "FM"); tuner->type = V4L2_TUNER_RADIO; tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | - V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO; - - /* 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; - }; + V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP; + tuner->rangelow = 76 * FREQ_MUL; + tuner->rangehigh = 108 * FREQ_MUL; /* stereo indicator == stereo (instead of mono) */ if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0) tuner->rxsubchans = V4L2_TUNER_SUB_MONO; else - tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + tuner->rxsubchans = V4L2_TUNER_SUB_STEREO; /* If there is a reliable method of detecting an RDS channel, then this code should check for that before setting this RDS subchannel. */ @@ -754,16 +621,13 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv, tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI); /* the ideal factor is 0xffff/75 = 873,8 */ tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10); + if (tuner->signal > 0xffff) + tuner->signal = 0xffff; /* automatic frequency control: -1: freq to low, 1 freq to high */ /* AFCRL does only indicate that freq. differs, not if too low/high */ tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0; -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "get tuner failed with %d\n", retval); - mutex_unlock(&radio->lock); return retval; } @@ -772,19 +636,12 @@ done: * si470x_vidioc_s_tuner - set tuner attributes */ static int si470x_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) + const struct v4l2_tuner *tuner) { struct si470x_device *radio = video_drvdata(file); - int retval = 0; - - mutex_lock(&radio->lock); - /* safety checks */ - retval = si470x_disconnect_check(radio); - if (retval) - goto done; if (tuner->index != 0) - goto done; + return -EINVAL; /* mono/stereo selector */ switch (tuner->audmode) { @@ -792,20 +649,12 @@ static int si470x_vidioc_s_tuner(struct file *file, void *priv, radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */ break; case V4L2_TUNER_MODE_STEREO: + default: radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */ break; - default: - goto done; } - retval = si470x_set_register(radio, POWERCFG); - -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "set tuner failed with %d\n", retval); - mutex_unlock(&radio->lock); - return retval; + return si470x_set_register(radio, POWERCFG); } @@ -816,28 +665,12 @@ static int si470x_vidioc_g_frequency(struct file *file, void *priv, struct v4l2_frequency *freq) { struct si470x_device *radio = video_drvdata(file); - int retval = 0; - - /* safety checks */ - mutex_lock(&radio->lock); - retval = si470x_disconnect_check(radio); - if (retval) - goto done; - if (freq->tuner != 0) { - retval = -EINVAL; - goto done; - } + if (freq->tuner != 0) + return -EINVAL; freq->type = V4L2_TUNER_RADIO; - retval = si470x_get_freq(radio, &freq->frequency); - -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "get frequency failed with %d\n", retval); - mutex_unlock(&radio->lock); - return retval; + return si470x_get_freq(radio, &freq->frequency); } @@ -845,30 +678,22 @@ done: * si470x_vidioc_s_frequency - set tuner or modulator radio frequency */ static int si470x_vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *freq) + const struct v4l2_frequency *freq) { struct si470x_device *radio = video_drvdata(file); - int retval = 0; + int retval; - mutex_lock(&radio->lock); - /* safety checks */ - retval = si470x_disconnect_check(radio); - if (retval) - goto done; + if (freq->tuner != 0) + return -EINVAL; - if (freq->tuner != 0) { - retval = -EINVAL; - goto done; + 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; } - - retval = si470x_set_freq(radio, freq->frequency); - -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "set frequency failed with %d\n", retval); - mutex_unlock(&radio->lock); - return retval; + return si470x_set_freq(radio, freq->frequency); } @@ -876,47 +701,50 @@ done: * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek */ static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv, - struct v4l2_hw_freq_seek *seek) + const struct v4l2_hw_freq_seek *seek) { struct si470x_device *radio = video_drvdata(file); - int retval = 0; - mutex_lock(&radio->lock); - /* safety checks */ - retval = si470x_disconnect_check(radio); - if (retval) - goto done; + if (seek->tuner != 0) + return -EINVAL; - if (seek->tuner != 0) { - retval = -EINVAL; - goto done; - } + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; - retval = si470x_set_seek(radio, seek->wrap_around, seek->seek_upward); + return si470x_set_seek(radio, seek); +} -done: - if (retval < 0) - dev_warn(&radio->videodev->dev, - "set hardware frequency seek failed with %d\n", retval); - mutex_unlock(&radio->lock); - return retval; +/* + * 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 = { + .s_ctrl = si470x_s_ctrl, +}; /* * si470x_ioctl_ops - video device ioctl operations */ static const struct v4l2_ioctl_ops si470x_ioctl_ops = { .vidioc_querycap = si470x_vidioc_querycap, - .vidioc_queryctrl = si470x_vidioc_queryctrl, - .vidioc_g_ctrl = si470x_vidioc_g_ctrl, - .vidioc_s_ctrl = si470x_vidioc_s_ctrl, - .vidioc_g_audio = si470x_vidioc_g_audio, .vidioc_g_tuner = si470x_vidioc_g_tuner, .vidioc_s_tuner = si470x_vidioc_s_tuner, .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, }; @@ -926,6 +754,6 @@ static const struct v4l2_ioctl_ops si470x_ioctl_ops = { struct video_device si470x_viddev_template = { .fops = &si470x_fops, .name = DRIVER_NAME, - .release = video_device_release, + .release = video_device_release_empty, .ioctl_ops = &si470x_ioctl_ops, }; diff --git a/drivers/media/radio/si470x/radio-si470x-i2c.c b/drivers/media/radio/si470x/radio-si470x-i2c.c index fd3541b0e91..2a497c80c77 100644 --- a/drivers/media/radio/si470x/radio-si470x-i2c.c +++ b/drivers/media/radio/si470x/radio-si470x-i2c.c @@ -98,8 +98,12 @@ int si470x_get_register(struct si470x_device *radio, int regnr) { u16 buf[READ_REG_NUM]; struct i2c_msg msgs[1] = { - { radio->client->addr, I2C_M_RD, sizeof(u16) * READ_REG_NUM, - (void *)buf }, + { + .addr = radio->client->addr, + .flags = I2C_M_RD, + .len = sizeof(u16) * READ_REG_NUM, + .buf = (void *)buf + }, }; if (i2c_transfer(radio->client->adapter, msgs, 1) != 1) @@ -119,8 +123,11 @@ int si470x_set_register(struct si470x_device *radio, int regnr) int i; u16 buf[WRITE_REG_NUM]; struct i2c_msg msgs[1] = { - { radio->client->addr, 0, sizeof(u16) * WRITE_REG_NUM, - (void *)buf }, + { + .addr = radio->client->addr, + .len = sizeof(u16) * WRITE_REG_NUM, + .buf = (void *)buf + }, }; for (i = 0; i < WRITE_REG_NUM; i++) @@ -146,8 +153,12 @@ static int si470x_get_all_registers(struct si470x_device *radio) int i; u16 buf[READ_REG_NUM]; struct i2c_msg msgs[1] = { - { radio->client->addr, I2C_M_RD, sizeof(u16) * READ_REG_NUM, - (void *)buf }, + { + .addr = radio->client->addr, + .flags = I2C_M_RD, + .len = sizeof(u16) * READ_REG_NUM, + .buf = (void *)buf + }, }; if (i2c_transfer(radio->client->adapter, msgs, 1) != 1) @@ -162,20 +173,6 @@ static int si470x_get_all_registers(struct si470x_device *radio) /************************************************************************** - * General Driver Functions - DISCONNECT_CHECK - **************************************************************************/ - -/* - * si470x_disconnect_check - check whether radio disconnects - */ -int si470x_disconnect_check(struct si470x_device *radio) -{ - return 0; -} - - - -/************************************************************************** * File Operations Interface **************************************************************************/ @@ -185,12 +182,12 @@ int si470x_disconnect_check(struct si470x_device *radio) int si470x_fops_open(struct file *file) { struct si470x_device *radio = video_drvdata(file); - int retval = 0; + int retval = v4l2_fh_open(file); - mutex_lock(&radio->lock); - radio->users++; + if (retval) + return retval; - if (radio->users == 1) { + if (v4l2_fh_is_singular_file(file)) { /* start radio */ retval = si470x_start(radio); if (retval < 0) @@ -205,7 +202,8 @@ int si470x_fops_open(struct file *file) } done: - mutex_unlock(&radio->lock); + if (retval) + v4l2_fh_release(file); return retval; } @@ -216,21 +214,12 @@ done: int si470x_fops_release(struct file *file) { struct si470x_device *radio = video_drvdata(file); - int retval = 0; - - /* safety check */ - if (!radio) - return -ENODEV; - mutex_lock(&radio->lock); - radio->users--; - if (radio->users == 0) + if (v4l2_fh_is_singular_file(file)) /* stop radio */ - retval = si470x_stop(radio); + si470x_stop(radio); - mutex_unlock(&radio->lock); - - return retval; + return v4l2_fh_release(file); } @@ -247,8 +236,9 @@ int si470x_vidioc_querycap(struct file *file, void *priv, { strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); - capability->capabilities = V4L2_CAP_HW_FREQ_SEEK | - V4L2_CAP_TUNER | V4L2_CAP_RADIO; + capability->device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | + V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE; + capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } @@ -318,7 +308,7 @@ static irqreturn_t si470x_i2c_interrupt(int irq, void *dev_id) READCHAN_BLERD) >> 10; rds = radio->registers[RDSD]; break; - }; + } /* Fill the V4L2 RDS buffer */ put_unaligned_le16(rds, &tmpbuf); @@ -357,8 +347,8 @@ end: /* * si470x_i2c_probe - probe for the device */ -static int __devinit si470x_i2c_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int si470x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) { struct si470x_device *radio; int retval = 0; @@ -371,32 +361,27 @@ static int __devinit si470x_i2c_probe(struct i2c_client *client, goto err_initial; } - radio->users = 0; radio->client = client; + radio->band = 1; /* Default to 76 - 108 MHz */ mutex_init(&radio->lock); + init_completion(&radio->completion); - /* video device allocation and initialization */ - radio->videodev = video_device_alloc(); - if (!radio->videodev) { - retval = -ENOMEM; - goto err_radio; - } - memcpy(radio->videodev, &si470x_viddev_template, - sizeof(si470x_viddev_template)); - video_set_drvdata(radio->videodev, radio); + /* video device initialization */ + radio->videodev = si470x_viddev_template; + video_set_drvdata(&radio->videodev, radio); /* power up : need 110ms */ radio->registers[POWERCFG] = POWERCFG_ENABLE; if (si470x_set_register(radio, POWERCFG) < 0) { retval = -EIO; - goto err_video; + goto err_radio; } msleep(110); /* get device and chip versions */ if (si470x_get_all_registers(radio) < 0) { retval = -EIO; - goto err_video; + goto err_radio; } dev_info(&client->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n", radio->registers[DEVICEID], radio->registers[CHIPID]); @@ -427,7 +412,7 @@ static int __devinit si470x_i2c_probe(struct i2c_client *client, radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL); if (!radio->buffer) { retval = -EIO; - goto err_video; + goto err_radio; } /* rds buffer configuration */ @@ -435,10 +420,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) { @@ -447,7 +428,7 @@ static int __devinit si470x_i2c_probe(struct i2c_client *client, } /* register video device */ - retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, + retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, radio_nr); if (retval) { dev_warn(&client->dev, "Could not register video device\n"); @@ -460,8 +441,6 @@ err_all: free_irq(client->irq, radio); err_rds: kfree(radio->buffer); -err_video: - video_device_release(radio->videodev); err_radio: kfree(radio); err_initial: @@ -472,19 +451,19 @@ err_initial: /* * si470x_i2c_remove - remove the device */ -static __devexit int si470x_i2c_remove(struct i2c_client *client) +static int si470x_i2c_remove(struct i2c_client *client) { struct si470x_device *radio = i2c_get_clientdata(client); free_irq(client->irq, radio); - video_unregister_device(radio->videodev); + video_unregister_device(&radio->videodev); kfree(radio); return 0; } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP /* * si470x_i2c_suspend - suspend the device */ @@ -530,42 +509,16 @@ static struct i2c_driver si470x_i2c_driver = { .driver = { .name = "si470x", .owner = THIS_MODULE, -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP .pm = &si470x_i2c_pm, #endif }, .probe = si470x_i2c_probe, - .remove = __devexit_p(si470x_i2c_remove), + .remove = si470x_i2c_remove, .id_table = si470x_i2c_id, }; - - -/************************************************************************** - * Module Interface - **************************************************************************/ - -/* - * si470x_i2c_init - module init - */ -static int __init si470x_i2c_init(void) -{ - printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n"); - return i2c_add_driver(&si470x_i2c_driver); -} - - -/* - * si470x_i2c_exit - module exit - */ -static void __exit si470x_i2c_exit(void) -{ - i2c_del_driver(&si470x_i2c_driver); -} - - -module_init(si470x_i2c_init); -module_exit(si470x_i2c_exit); +module_i2c_driver(si470x_i2c_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c index b7debb67932..07ef40595ef 100644 --- a/drivers/media/radio/si470x/radio-si470x-usb.c +++ b/drivers/media/radio/si470x/radio-si470x-usb.c @@ -51,6 +51,8 @@ static struct usb_device_id si470x_usb_driver_id_table[] = { { USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) }, /* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */ { USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) }, + /* Axentia ALERT FM USB Receiver */ + { USB_DEVICE_AND_INTERFACE_INFO(0x12cf, 0x7111, USB_CLASS_HID, 0, 0) }, /* Terminating entry */ { } }; @@ -135,13 +137,13 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); /* interrupt out endpoint 2 every 1 millisecond */ #define UNUSED_REPORT 23 +#define MAX_REPORT_SIZE 64 + /************************************************************************** * Software/Hardware Versions from Scratch Page **************************************************************************/ -#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6 -#define RADIO_SW_VERSION 7 #define RADIO_HW_VERSION 1 @@ -208,7 +210,7 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); */ static int si470x_get_report(struct si470x_device *radio, void *buf, int size) { - unsigned char *report = (unsigned char *) buf; + unsigned char *report = buf; int retval; retval = usb_control_msg(radio->usbdev, @@ -231,7 +233,7 @@ static int si470x_get_report(struct si470x_device *radio, void *buf, int size) */ static int si470x_set_report(struct si470x_device *radio, void *buf, int size) { - unsigned char *report = (unsigned char *) buf; + unsigned char *report = buf; int retval; retval = usb_control_msg(radio->usbdev, @@ -254,15 +256,14 @@ static int si470x_set_report(struct si470x_device *radio, void *buf, int size) */ int si470x_get_register(struct si470x_device *radio, int regnr) { - unsigned char buf[REGISTER_REPORT_SIZE]; int retval; - buf[0] = REGISTER_REPORT(regnr); + radio->usb_buf[0] = REGISTER_REPORT(regnr); - retval = si470x_get_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_get_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE); if (retval >= 0) - radio->registers[regnr] = get_unaligned_be16(&buf[1]); + radio->registers[regnr] = get_unaligned_be16(&radio->usb_buf[1]); return (retval < 0) ? -EINVAL : 0; } @@ -273,13 +274,12 @@ int si470x_get_register(struct si470x_device *radio, int regnr) */ int si470x_set_register(struct si470x_device *radio, int regnr) { - unsigned char buf[REGISTER_REPORT_SIZE]; int retval; - buf[0] = REGISTER_REPORT(regnr); - put_unaligned_be16(radio->registers[regnr], &buf[1]); + radio->usb_buf[0] = REGISTER_REPORT(regnr); + put_unaligned_be16(radio->registers[regnr], &radio->usb_buf[1]); - retval = si470x_set_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_set_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE); return (retval < 0) ? -EINVAL : 0; } @@ -295,18 +295,17 @@ int si470x_set_register(struct si470x_device *radio, int regnr) */ static int si470x_get_all_registers(struct si470x_device *radio) { - unsigned char buf[ENTIRE_REPORT_SIZE]; int retval; unsigned char regnr; - buf[0] = ENTIRE_REPORT; + radio->usb_buf[0] = ENTIRE_REPORT; - retval = si470x_get_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_get_report(radio, radio->usb_buf, ENTIRE_REPORT_SIZE); if (retval >= 0) for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++) radio->registers[regnr] = get_unaligned_be16( - &buf[regnr * RADIO_REGISTER_SIZE + 1]); + &radio->usb_buf[regnr * RADIO_REGISTER_SIZE + 1]); return (retval < 0) ? -EINVAL : 0; } @@ -323,14 +322,13 @@ static int si470x_get_all_registers(struct si470x_device *radio) static int si470x_set_led_state(struct si470x_device *radio, unsigned char led_state) { - unsigned char buf[LED_REPORT_SIZE]; int retval; - buf[0] = LED_REPORT; - buf[1] = LED_COMMAND; - buf[2] = led_state; + radio->usb_buf[0] = LED_REPORT; + radio->usb_buf[1] = LED_COMMAND; + radio->usb_buf[2] = led_state; - retval = si470x_set_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_set_report(radio, radio->usb_buf, LED_REPORT_SIZE); return (retval < 0) ? -EINVAL : 0; } @@ -346,19 +344,18 @@ static int si470x_set_led_state(struct si470x_device *radio, */ static int si470x_get_scratch_page_versions(struct si470x_device *radio) { - unsigned char buf[SCRATCH_REPORT_SIZE]; int retval; - buf[0] = SCRATCH_REPORT; + radio->usb_buf[0] = SCRATCH_REPORT; - retval = si470x_get_report(radio, (void *) &buf, sizeof(buf)); + retval = si470x_get_report(radio, radio->usb_buf, SCRATCH_REPORT_SIZE); if (retval < 0) dev_warn(&radio->intf->dev, "si470x_get_scratch: " "si470x_get_report returned %d\n", retval); else { - radio->software_version = buf[1]; - radio->hardware_version = buf[2]; + radio->software_version = radio->usb_buf[1]; + radio->hardware_version = radio->usb_buf[2]; } return (retval < 0) ? -EINVAL : 0; @@ -367,23 +364,6 @@ static int si470x_get_scratch_page_versions(struct si470x_device *radio) /************************************************************************** - * General Driver Functions - DISCONNECT_CHECK - **************************************************************************/ - -/* - * si470x_disconnect_check - check whether radio disconnects - */ -int si470x_disconnect_check(struct si470x_device *radio) -{ - if (radio->disconnected) - return -EIO; - else - return 0; -} - - - -/************************************************************************** * RDS Driver Functions **************************************************************************/ @@ -414,15 +394,19 @@ static void si470x_int_in_callback(struct urb *urb) } } - /* safety checks */ - if (radio->disconnected) - return; - 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]); @@ -457,7 +441,7 @@ static void si470x_int_in_callback(struct urb *urb) READCHAN_BLERD) >> 10; rds = radio->registers[RDSD]; break; - }; + } /* Fill the V4L2 RDS buffer */ put_unaligned_le16(rds, &tmpbuf); @@ -498,115 +482,35 @@ resubmit: radio->int_in_running = 0; } } + radio->status_rssi_auto_update = radio->int_in_running; } - -/************************************************************************** - * File Operations Interface - **************************************************************************/ - -/* - * si470x_fops_open - file open - */ int si470x_fops_open(struct file *file) { - struct si470x_device *radio = video_drvdata(file); - int retval; - - mutex_lock(&radio->lock); - radio->users++; - - retval = usb_autopm_get_interface(radio->intf); - if (retval < 0) { - radio->users--; - retval = -EIO; - goto done; - } - - if (radio->users == 1) { - /* start radio */ - retval = si470x_start(radio); - if (retval < 0) { - usb_autopm_put_interface(radio->intf); - goto done; - } - - /* initialize interrupt urb */ - usb_fill_int_urb(radio->int_in_urb, radio->usbdev, - usb_rcvintpipe(radio->usbdev, - radio->int_in_endpoint->bEndpointAddress), - radio->int_in_buffer, - le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize), - si470x_int_in_callback, - radio, - radio->int_in_endpoint->bInterval); - - radio->int_in_running = 1; - mb(); - - retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL); - if (retval) { - dev_info(&radio->intf->dev, - "submitting int urb failed (%d)\n", retval); - radio->int_in_running = 0; - usb_autopm_put_interface(radio->intf); - } - } - -done: - mutex_unlock(&radio->lock); - return retval; + return v4l2_fh_open(file); } - -/* - * si470x_fops_release - file release - */ int si470x_fops_release(struct file *file) { - struct si470x_device *radio = video_drvdata(file); - int retval = 0; - - /* safety check */ - if (!radio) { - retval = -ENODEV; - goto done; - } - - mutex_lock(&radio->lock); - radio->users--; - if (radio->users == 0) { - /* shutdown interrupt handler */ - if (radio->int_in_running) { - radio->int_in_running = 0; - if (radio->int_in_urb) - usb_kill_urb(radio->int_in_urb); - } - - if (radio->disconnected) { - video_unregister_device(radio->videodev); - kfree(radio->int_in_buffer); - kfree(radio->buffer); - mutex_unlock(&radio->lock); - kfree(radio); - goto done; - } + return v4l2_fh_release(file); +} - /* cancel read processes */ - wake_up_interruptible(&radio->read_queue); +static void si470x_usb_release(struct v4l2_device *v4l2_dev) +{ + struct si470x_device *radio = + container_of(v4l2_dev, struct si470x_device, v4l2_dev); - /* stop radio */ - retval = si470x_stop(radio); - usb_autopm_put_interface(radio->intf); - } - mutex_unlock(&radio->lock); -done: - return retval; + usb_free_urb(radio->int_in_urb); + v4l2_ctrl_handler_free(&radio->hdl); + v4l2_device_unregister(&radio->v4l2_dev); + kfree(radio->int_in_buffer); + kfree(radio->buffer); + kfree(radio->usb_buf); + kfree(radio); } - /************************************************************************** * Video4Linux Interface **************************************************************************/ @@ -623,13 +527,47 @@ int si470x_vidioc_querycap(struct file *file, void *priv, strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); usb_make_path(radio->usbdev, capability->bus_info, sizeof(capability->bus_info)); - capability->capabilities = V4L2_CAP_HW_FREQ_SEEK | + capability->device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE; - + capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } +static int si470x_start_usb(struct si470x_device *radio) +{ + int retval; + + /* initialize interrupt urb */ + usb_fill_int_urb(radio->int_in_urb, radio->usbdev, + usb_rcvintpipe(radio->usbdev, + radio->int_in_endpoint->bEndpointAddress), + radio->int_in_buffer, + le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize), + si470x_int_in_callback, + radio, + radio->int_in_endpoint->bInterval); + + radio->int_in_running = 1; + mb(); + + retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL); + if (retval) { + dev_info(&radio->intf->dev, + "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; +} /************************************************************************** * USB Interface @@ -653,11 +591,16 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, retval = -ENOMEM; goto err_initial; } - radio->users = 0; - radio->disconnected = 0; + radio->usb_buf = kmalloc(MAX_REPORT_SIZE, GFP_KERNEL); + if (radio->usb_buf == NULL) { + retval = -ENOMEM; + goto err_radio; + } 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; @@ -672,7 +615,7 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, if (!radio->int_in_endpoint) { dev_info(&intf->dev, "could not find interrupt in endpoint\n"); retval = -EIO; - goto err_radio; + goto err_usbbuf; } int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize); @@ -681,7 +624,7 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, if (!radio->int_in_buffer) { dev_info(&intf->dev, "could not allocate int_in_buffer"); retval = -ENOMEM; - goto err_radio; + goto err_usbbuf; } radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); @@ -691,20 +634,59 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, goto err_intbuffer; } - /* video device allocation and initialization */ - radio->videodev = video_device_alloc(); - if (!radio->videodev) { - retval = -ENOMEM; + radio->v4l2_dev.release = si470x_usb_release; + + /* + * The si470x SiLabs reference design uses the same USB IDs as + * 'Thanko's Raremono' si4734 based receiver. So check here which we + * have: attempt to read the device ID from the si470x: the lower 12 + * bits should be 0x0242 for the si470x. + * + * We use this check to determine which device we are dealing with. + */ + if (id->idVendor == 0x10c4 && id->idProduct == 0x818a) { + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + HID_REQ_GET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + 1, 2, + radio->usb_buf, 3, 500); + if (retval != 3 || + (get_unaligned_be16(&radio->usb_buf[1]) & 0xfff) != 0x0242) { + dev_info(&intf->dev, "this is not a si470x device.\n"); + retval = -ENODEV; + goto err_urb; + } + } + + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); goto err_urb; } - memcpy(radio->videodev, &si470x_viddev_template, - sizeof(si470x_viddev_template)); - video_set_drvdata(radio->videodev, radio); + + v4l2_ctrl_handler_init(&radio->hdl, 2); + v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); + v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15); + if (radio->hdl.error) { + retval = radio->hdl.error; + dev_err(&intf->dev, "couldn't register control\n"); + goto err_dev; + } + radio->videodev = si470x_viddev_template; + radio->videodev.ctrl_handler = &radio->hdl; + radio->videodev.lock = &radio->lock; + radio->videodev.v4l2_dev = &radio->v4l2_dev; + radio->videodev.release = video_device_release_empty; + set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags); + video_set_drvdata(&radio->videodev, radio); /* get device and chip versions */ if (si470x_get_all_registers(radio) < 0) { retval = -EIO; - goto err_video; + goto err_ctrl; } dev_info(&intf->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n", radio->registers[DEVICEID], radio->registers[CHIPID]); @@ -721,19 +703,10 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, /* get software and hardware versions */ if (si470x_get_scratch_page_versions(radio) < 0) { retval = -EIO; - goto err_video; + goto err_ctrl; } dev_info(&intf->dev, "software version %d, hardware version %d\n", radio->software_version, radio->hardware_version); - if (radio->software_version < RADIO_SW_VERSION) { - dev_warn(&intf->dev, - "This driver is known to work with " - "software version %hu,\n", RADIO_SW_VERSION); - dev_warn(&intf->dev, - "but the device has software version %hu.\n", - radio->software_version); - version_warning = 1; - } if (radio->hardware_version < RADIO_HW_VERSION) { dev_warn(&intf->dev, "This driver is known to work with " @@ -753,9 +726,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); @@ -764,32 +734,44 @@ static int si470x_usb_driver_probe(struct usb_interface *intf, radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL); if (!radio->buffer) { retval = -EIO; - goto err_video; + goto err_ctrl; } /* rds buffer configuration */ radio->wr_index = 0; radio->rd_index = 0; init_waitqueue_head(&radio->read_queue); + usb_set_intfdata(intf, radio); + + /* start radio */ + retval = si470x_start_usb(radio); + 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, + retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, radio_nr); if (retval) { - dev_warn(&intf->dev, "Could not register video device\n"); + dev_err(&intf->dev, "Could not register video device\n"); goto err_all; } - usb_set_intfdata(intf, radio); return 0; err_all: kfree(radio->buffer); -err_video: - video_device_release(radio->videodev); +err_ctrl: + v4l2_ctrl_handler_free(&radio->hdl); +err_dev: + v4l2_device_unregister(&radio->v4l2_dev); err_urb: usb_free_urb(radio->int_in_urb); err_intbuffer: kfree(radio->int_in_buffer); +err_usbbuf: + kfree(radio->usb_buf); err_radio: kfree(radio); err_initial: @@ -803,8 +785,22 @@ err_initial: static int si470x_usb_driver_suspend(struct usb_interface *intf, pm_message_t message) { + struct si470x_device *radio = usb_get_intfdata(intf); + dev_info(&intf->dev, "suspending now...\n"); + /* shutdown interrupt handler */ + if (radio->int_in_running) { + radio->int_in_running = 0; + if (radio->int_in_urb) + usb_kill_urb(radio->int_in_urb); + } + + /* cancel read processes */ + wake_up_interruptible(&radio->read_queue); + + /* stop radio */ + si470x_stop(radio); return 0; } @@ -814,9 +810,17 @@ 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"); - return 0; + /* start radio */ + ret = si470x_start_usb(radio); + if (ret == 0) + v4l2_ctrl_handler_setup(&radio->hdl); + + return ret; } @@ -828,28 +832,22 @@ static void si470x_usb_driver_disconnect(struct usb_interface *intf) struct si470x_device *radio = usb_get_intfdata(intf); mutex_lock(&radio->lock); - radio->disconnected = 1; + v4l2_device_disconnect(&radio->v4l2_dev); + video_unregister_device(&radio->videodev); usb_set_intfdata(intf, NULL); - if (radio->users == 0) { - /* set led to disconnect state */ - si470x_set_led_state(radio, BLINK_ORANGE_LED); - - /* Free data structures. */ - usb_free_urb(radio->int_in_urb); - - kfree(radio->int_in_buffer); - video_unregister_device(radio->videodev); - kfree(radio->buffer); - mutex_unlock(&radio->lock); - kfree(radio); - } else { - mutex_unlock(&radio->lock); - } + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); } /* * si470x_usb_driver - usb driver interface + * + * A note on suspend/resume: this driver had only empty suspend/resume + * functions, and when I tried to test suspend/resume it always disconnected + * instead of resuming (using my ADS InstantFM stick). So I've decided to + * remove these callbacks until someone else with better hardware can + * implement and test this. */ static struct usb_driver si470x_usb_driver = { .name = DRIVER_NAME, @@ -857,8 +855,8 @@ static struct usb_driver si470x_usb_driver = { .disconnect = si470x_usb_driver_disconnect, .suspend = si470x_usb_driver_suspend, .resume = si470x_usb_driver_resume, + .reset_resume = si470x_usb_driver_resume, .id_table = si470x_usb_driver_id_table, - .supports_autosuspend = 1, }; module_usb_driver(si470x_usb_driver); diff --git a/drivers/media/radio/si470x/radio-si470x.h b/drivers/media/radio/si470x/radio-si470x.h index f300a55ed85..4b7660470e2 100644 --- a/drivers/media/radio/si470x/radio-si470x.h +++ b/drivers/media/radio/si470x/radio-si470x.h @@ -36,6 +36,9 @@ #include <linux/mutex.h> #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/v4l2-device.h> #include <asm/unaligned.h> @@ -84,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 */ @@ -141,10 +144,10 @@ * si470x_device - private data */ struct si470x_device { - struct video_device *videodev; - - /* driver management */ - unsigned int users; + 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]; @@ -158,12 +161,13 @@ 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) +#if IS_ENABLED(CONFIG_USB_SI470X) /* reference to USB and video device */ struct usb_device *usbdev; struct usb_interface *intf; + char *usb_buf; /* Interrupt endpoint handling */ char *int_in_buffer; @@ -174,12 +178,9 @@ struct si470x_device { /* scratch page */ unsigned char software_version; unsigned char hardware_version; - - /* driver management */ - unsigned char disconnected; #endif -#if defined(CONFIG_I2C_SI470X) || defined(CONFIG_I2C_SI470X_MODULE) +#if IS_ENABLED(CONFIG_I2C_SI470X) struct i2c_client *client; #endif }; @@ -190,7 +191,7 @@ struct si470x_device { * Firmware Versions **************************************************************************/ -#define RADIO_FW_VERSION 15 +#define RADIO_FW_VERSION 12 @@ -213,6 +214,7 @@ struct si470x_device { * Common Functions **************************************************************************/ extern struct video_device si470x_viddev_template; +extern const struct v4l2_ctrl_ops si470x_ctrl_ops; int si470x_get_register(struct si470x_device *radio, int regnr); int si470x_set_register(struct si470x_device *radio, int regnr); int si470x_disconnect_check(struct si470x_device *radio); diff --git a/drivers/media/radio/si4713/Kconfig b/drivers/media/radio/si4713/Kconfig new file mode 100644 index 00000000000..9c8b887cff7 --- /dev/null +++ b/drivers/media/radio/si4713/Kconfig @@ -0,0 +1,40 @@ +config USB_SI4713 + tristate "Silicon Labs Si4713 FM Radio Transmitter support with USB" + depends on USB && I2C && RADIO_SI4713 + select I2C_SI4713 + ---help--- + This is a driver for USB devices with the Silicon Labs SI4713 + chip. Currently these devices are known to work. + - 10c4:8244: Silicon Labs FM Transmitter USB device. + + Say Y here if you want to connect this type of radio to your + computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called radio-usb-si4713. + +config PLATFORM_SI4713 + tristate "Silicon Labs Si4713 FM Radio Transmitter support with I2C" + depends on I2C && RADIO_SI4713 + select I2C_SI4713 + ---help--- + This is a driver for I2C devices with the Silicon Labs SI4713 + chip. + + Say Y here if you want to connect this type of radio to your + computer's I2C port. + + To compile this driver as a module, choose M here: the + module will be called radio-platform-si4713. + +config I2C_SI4713 + tristate "Silicon Labs Si4713 FM Radio Transmitter support" + depends on I2C && RADIO_SI4713 + ---help--- + Say Y here if you want support to Si4713 FM Radio Transmitter. + This device can transmit audio through FM. It can transmit + RDS and RBDS signals as well. This module is the v4l2 radio + interface for the i2c driver of this device. + + To compile this driver as a module, choose M here: the + module will be called si4713. diff --git a/drivers/media/radio/si4713/Makefile b/drivers/media/radio/si4713/Makefile new file mode 100644 index 00000000000..ddaaf925e88 --- /dev/null +++ b/drivers/media/radio/si4713/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for radios with Silicon Labs Si4713 FM Radio Transmitters +# + +obj-$(CONFIG_I2C_SI4713) += si4713.o +obj-$(CONFIG_USB_SI4713) += radio-usb-si4713.o +obj-$(CONFIG_PLATFORM_SI4713) += radio-platform-si4713.o diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/si4713/radio-platform-si4713.c index c54210c7fef..ba4cfc94686 100644 --- a/drivers/media/radio/radio-si4713.c +++ b/drivers/media/radio/si4713/radio-platform-si4713.c @@ -31,6 +31,9 @@ #include <media/v4l2-device.h> #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> #include <media/radio-si4713.h> /* module parameters */ @@ -39,54 +42,30 @@ module_param(radio_nr, int, 0); MODULE_PARM_DESC(radio_nr, "Minor number for radio device (-1 ==> auto assign)"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>"); MODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter"); MODULE_VERSION("0.0.1"); +MODULE_ALIAS("platform:radio-si4713"); /* Driver state struct */ struct radio_si4713_device { struct v4l2_device v4l2_dev; - struct video_device *radio_dev; + struct video_device radio_dev; + struct mutex lock; }; /* radio_si4713_fops - file operations interface */ static const struct v4l2_file_operations radio_si4713_fops = { .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, /* Note: locking is done at the subdev level in the i2c driver. */ .unlocked_ioctl = video_ioctl2, }; /* Video4Linux Interface */ -static int radio_si4713_fill_audout(struct v4l2_audioout *vao) -{ - /* TODO: check presence of audio output */ - strlcpy(vao->name, "FM Modulator Audio Out", 32); - - return 0; -} - -static int radio_si4713_enumaudout(struct file *file, void *priv, - struct v4l2_audioout *vao) -{ - return radio_si4713_fill_audout(vao); -} - -static int radio_si4713_g_audout(struct file *file, void *priv, - struct v4l2_audioout *vao) -{ - int rval = radio_si4713_fill_audout(vao); - - vao->index = 0; - - return rval; -} - -static int radio_si4713_s_audout(struct file *file, void *priv, - struct v4l2_audioout *vao) -{ - return vao->index ? -EINVAL : 0; -} /* radio_si4713_querycap - query device capabilities */ static int radio_si4713_querycap(struct file *file, void *priv, @@ -94,67 +73,15 @@ static int radio_si4713_querycap(struct file *file, void *priv, { strlcpy(capability->driver, "radio-si4713", sizeof(capability->driver)); strlcpy(capability->card, "Silicon Labs Si4713 Modulator", - sizeof(capability->card)); - capability->capabilities = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; + sizeof(capability->card)); + strlcpy(capability->bus_info, "platform:radio-si4713", + sizeof(capability->bus_info)); + capability->device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; + capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS; return 0; } -/* radio_si4713_queryctrl - enumerate control items */ -static int radio_si4713_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - /* Must be sorted from low to high control ID! */ - static const u32 user_ctrls[] = { - V4L2_CID_USER_CLASS, - V4L2_CID_AUDIO_MUTE, - 0 - }; - - /* Must be sorted from low to high control ID! */ - static const u32 fmtx_ctrls[] = { - V4L2_CID_FM_TX_CLASS, - V4L2_CID_RDS_TX_DEVIATION, - V4L2_CID_RDS_TX_PI, - V4L2_CID_RDS_TX_PTY, - V4L2_CID_RDS_TX_PS_NAME, - V4L2_CID_RDS_TX_RADIO_TEXT, - V4L2_CID_AUDIO_LIMITER_ENABLED, - V4L2_CID_AUDIO_LIMITER_RELEASE_TIME, - V4L2_CID_AUDIO_LIMITER_DEVIATION, - V4L2_CID_AUDIO_COMPRESSION_ENABLED, - V4L2_CID_AUDIO_COMPRESSION_GAIN, - V4L2_CID_AUDIO_COMPRESSION_THRESHOLD, - V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME, - V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME, - V4L2_CID_PILOT_TONE_ENABLED, - V4L2_CID_PILOT_TONE_DEVIATION, - V4L2_CID_PILOT_TONE_FREQUENCY, - V4L2_CID_TUNE_PREEMPHASIS, - V4L2_CID_TUNE_POWER_LEVEL, - V4L2_CID_TUNE_ANTENNA_CAPACITOR, - 0 - }; - static const u32 *ctrl_classes[] = { - user_ctrls, - fmtx_ctrls, - NULL - }; - struct radio_si4713_device *rsdev; - - rsdev = video_get_drvdata(video_devdata(file)); - - qc->id = v4l2_ctrl_next(ctrl_classes, qc->id); - if (qc->id == 0) - return -EINVAL; - - if (qc->id == V4L2_CID_USER_CLASS || qc->id == V4L2_CID_FM_TX_CLASS) - return v4l2_ctrl_query_fill(qc, 0, 0, 0, 0); - - return v4l2_device_call_until_err(&rsdev->v4l2_dev, 0, core, - queryctrl, qc); -} - /* * v4l2 ioctl call backs. * we are just a wrapper for v4l2_sub_devs. @@ -164,83 +91,50 @@ static inline struct v4l2_device *get_v4l2_dev(struct file *file) return &((struct radio_si4713_device *)video_drvdata(file))->v4l2_dev; } -static int radio_si4713_g_ext_ctrls(struct file *file, void *p, - struct v4l2_ext_controls *vecs) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - g_ext_ctrls, vecs); -} - -static int radio_si4713_s_ext_ctrls(struct file *file, void *p, - struct v4l2_ext_controls *vecs) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - s_ext_ctrls, vecs); -} - -static int radio_si4713_g_ctrl(struct file *file, void *p, - struct v4l2_control *vc) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - g_ctrl, vc); -} - -static int radio_si4713_s_ctrl(struct file *file, void *p, - struct v4l2_control *vc) -{ - return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - s_ctrl, vc); -} - static int radio_si4713_g_modulator(struct file *file, void *p, - struct v4l2_modulator *vm) + struct v4l2_modulator *vm) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - g_modulator, vm); + g_modulator, vm); } static int radio_si4713_s_modulator(struct file *file, void *p, - struct v4l2_modulator *vm) + const struct v4l2_modulator *vm) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - s_modulator, vm); + s_modulator, vm); } static int radio_si4713_g_frequency(struct file *file, void *p, - struct v4l2_frequency *vf) + struct v4l2_frequency *vf) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - g_frequency, vf); + g_frequency, vf); } static int radio_si4713_s_frequency(struct file *file, void *p, - struct v4l2_frequency *vf) + const struct v4l2_frequency *vf) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner, - s_frequency, vf); + s_frequency, vf); } static long radio_si4713_default(struct file *file, void *p, - bool valid_prio, int cmd, void *arg) + bool valid_prio, unsigned int cmd, void *arg) { return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core, - ioctl, cmd, arg); + ioctl, cmd, arg); } static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = { - .vidioc_enumaudout = radio_si4713_enumaudout, - .vidioc_g_audout = radio_si4713_g_audout, - .vidioc_s_audout = radio_si4713_s_audout, .vidioc_querycap = radio_si4713_querycap, - .vidioc_queryctrl = radio_si4713_queryctrl, - .vidioc_g_ext_ctrls = radio_si4713_g_ext_ctrls, - .vidioc_s_ext_ctrls = radio_si4713_s_ext_ctrls, - .vidioc_g_ctrl = radio_si4713_g_ctrl, - .vidioc_s_ctrl = radio_si4713_s_ctrl, .vidioc_g_modulator = radio_si4713_g_modulator, .vidioc_s_modulator = radio_si4713_s_modulator, .vidioc_g_frequency = radio_si4713_g_frequency, .vidioc_s_frequency = radio_si4713_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_default = radio_si4713_default, }; @@ -248,8 +142,9 @@ static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = { static struct video_device radio_si4713_vdev_template = { .fops = &radio_si4713_fops, .name = "radio-si4713", - .release = video_device_release, + .release = video_device_release_empty, .ioctl_ops = &radio_si4713_ioctl_ops, + .vfl_dir = VFL_DIR_TX, }; /* Platform driver interface */ @@ -268,81 +163,73 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev) goto exit; } - rsdev = kzalloc(sizeof *rsdev, GFP_KERNEL); + rsdev = devm_kzalloc(&pdev->dev, sizeof(*rsdev), GFP_KERNEL); if (!rsdev) { dev_err(&pdev->dev, "Failed to alloc video device.\n"); rval = -ENOMEM; goto exit; } + mutex_init(&rsdev->lock); rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev); if (rval) { dev_err(&pdev->dev, "Failed to register v4l2 device.\n"); - goto free_rsdev; + goto exit; } adapter = i2c_get_adapter(pdata->i2c_bus); if (!adapter) { dev_err(&pdev->dev, "Cannot get i2c adapter %d\n", - pdata->i2c_bus); + pdata->i2c_bus); rval = -ENODEV; goto unregister_v4l2_dev; } sd = v4l2_i2c_new_subdev_board(&rsdev->v4l2_dev, adapter, - pdata->subdev_board_info, NULL); + pdata->subdev_board_info, NULL); if (!sd) { dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n"); rval = -ENODEV; goto put_adapter; } - rsdev->radio_dev = video_device_alloc(); - if (!rsdev->radio_dev) { - dev_err(&pdev->dev, "Failed to alloc video device.\n"); - rval = -ENOMEM; - goto put_adapter; - } - - memcpy(rsdev->radio_dev, &radio_si4713_vdev_template, - sizeof(radio_si4713_vdev_template)); - video_set_drvdata(rsdev->radio_dev, rsdev); - if (video_register_device(rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) { + rsdev->radio_dev = radio_si4713_vdev_template; + rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev; + rsdev->radio_dev.ctrl_handler = sd->ctrl_handler; + set_bit(V4L2_FL_USE_FH_PRIO, &rsdev->radio_dev.flags); + /* Serialize all access to the si4713 */ + rsdev->radio_dev.lock = &rsdev->lock; + video_set_drvdata(&rsdev->radio_dev, rsdev); + if (video_register_device(&rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) { dev_err(&pdev->dev, "Could not register video device.\n"); rval = -EIO; - goto free_vdev; + goto put_adapter; } dev_info(&pdev->dev, "New device successfully probed\n"); goto exit; -free_vdev: - video_device_release(rsdev->radio_dev); put_adapter: i2c_put_adapter(adapter); unregister_v4l2_dev: v4l2_device_unregister(&rsdev->v4l2_dev); -free_rsdev: - kfree(rsdev); exit: return rval; } /* radio_si4713_pdriver_remove - remove the device */ -static int __exit radio_si4713_pdriver_remove(struct platform_device *pdev) +static int radio_si4713_pdriver_remove(struct platform_device *pdev) { struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); - struct radio_si4713_device *rsdev = container_of(v4l2_dev, - struct radio_si4713_device, - v4l2_dev); struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next, struct v4l2_subdev, list); struct i2c_client *client = v4l2_get_subdevdata(sd); + struct radio_si4713_device *rsdev; - video_unregister_device(rsdev->radio_dev); + rsdev = container_of(v4l2_dev, struct radio_si4713_device, v4l2_dev); + video_unregister_device(&rsdev->radio_dev); i2c_put_adapter(client->adapter); v4l2_device_unregister(&rsdev->v4l2_dev); - kfree(rsdev); return 0; } @@ -350,9 +237,10 @@ static int __exit radio_si4713_pdriver_remove(struct platform_device *pdev) static struct platform_driver radio_si4713_pdriver = { .driver = { .name = "radio-si4713", + .owner = THIS_MODULE, }, .probe = radio_si4713_pdriver_probe, - .remove = __exit_p(radio_si4713_pdriver_remove), + .remove = radio_si4713_pdriver_remove, }; module_platform_driver(radio_si4713_pdriver); diff --git a/drivers/media/radio/si4713/radio-usb-si4713.c b/drivers/media/radio/si4713/radio-usb-si4713.c new file mode 100644 index 00000000000..86502b2786d --- /dev/null +++ b/drivers/media/radio/si4713/radio-usb-si4713.c @@ -0,0 +1,540 @@ +/* + * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. + * All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* kernel includes */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +/* V4l includes */ +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/si4713.h> + +#include "si4713.h" + +/* driver and module definitions */ +MODULE_AUTHOR("Dinesh Ram <dinesh.ram@cern.ch>"); +MODULE_DESCRIPTION("Si4713 FM Transmitter USB driver"); +MODULE_LICENSE("GPL v2"); + +/* The Device announces itself as Cygnal Integrated Products, Inc. */ +#define USB_SI4713_VENDOR 0x10c4 +#define USB_SI4713_PRODUCT 0x8244 + +#define BUFFER_LENGTH 64 +#define USB_TIMEOUT 1000 +#define USB_RESP_TIMEOUT 50000 + +/* USB Device ID List */ +static struct usb_device_id usb_si4713_usb_device_table[] = { + {USB_DEVICE_AND_INTERFACE_INFO(USB_SI4713_VENDOR, USB_SI4713_PRODUCT, + USB_CLASS_HID, 0, 0) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_si4713_usb_device_table); + +struct si4713_usb_device { + struct usb_device *usbdev; + struct usb_interface *intf; + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_subdev *v4l2_subdev; + struct mutex lock; + struct i2c_adapter i2c_adapter; + + u8 *buffer; +}; + +static inline struct si4713_usb_device *to_si4713_dev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct si4713_usb_device, v4l2_dev); +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + strlcpy(v->driver, "radio-usb-si4713", sizeof(v->driver)); + strlcpy(v->card, "Si4713 FM Transmitter", sizeof(v->card)); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int vidioc_g_modulator(struct file *file, void *priv, + struct v4l2_modulator *vm) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_modulator, vm); +} + +static int vidioc_s_modulator(struct file *file, void *priv, + const struct v4l2_modulator *vm) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_modulator, vm); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *vf) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_frequency, vf); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *vf) +{ + struct si4713_usb_device *radio = video_drvdata(file); + + return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_frequency, vf); +} + +static const struct v4l2_ioctl_ops usb_si4713_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_modulator = vidioc_g_modulator, + .vidioc_s_modulator = vidioc_s_modulator, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* File system interface */ +static const struct v4l2_file_operations usb_si4713_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .poll = v4l2_ctrl_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static void usb_si4713_video_device_release(struct v4l2_device *v4l2_dev) +{ + struct si4713_usb_device *radio = to_si4713_dev(v4l2_dev); + struct i2c_adapter *adapter = &radio->i2c_adapter; + + i2c_del_adapter(adapter); + v4l2_device_unregister(&radio->v4l2_dev); + kfree(radio->buffer); + kfree(radio); +} + +/* + * This command sequence emulates the behaviour of the Windows driver. + * The structure of these commands was determined by sniffing the + * usb traffic of the device during startup. + * Most likely, these commands make some queries to the device. + * Commands are sent to enquire parameters like the bus mode, + * component revision, boot mode, the device serial number etc. + * + * These commands are necessary to be sent in this order during startup. + * The device fails to powerup if these commands are not sent. + * + * The complete list of startup commands is given in the start_seq table below. + */ +static int si4713_send_startup_command(struct si4713_usb_device *radio) +{ + unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; + u8 *buffer = radio->buffer; + int retval; + + /* send the command */ + retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 0x09, 0x21, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + + for (;;) { + /* receive the response */ + retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + 0x01, 0xa1, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + if (!radio->buffer[1]) { + /* USB traffic sniffing showed that some commands require + * additional checks. */ + switch (buffer[1]) { + case 0x32: + if (radio->buffer[2] == 0) + return 0; + break; + case 0x14: + case 0x12: + if (radio->buffer[2] & SI4713_CTS) + return 0; + break; + case 0x06: + if ((radio->buffer[2] & SI4713_CTS) && radio->buffer[9] == 0x08) + return 0; + break; + default: + return 0; + } + } + if (time_is_before_jiffies(until_jiffies)) + return -EIO; + msleep(3); + } + + return retval; +} + +struct si4713_start_seq_table { + int len; + u8 payload[8]; +}; + +/* + * Some of the startup commands that could be recognized are : + * (0x03): Get serial number of the board (Response : CB000-00-00) + * (0x06, 0x03, 0x03, 0x08, 0x01, 0x0f) : Get Component revision + */ +static const struct si4713_start_seq_table start_seq[] = { + + { 1, { 0x03 } }, + { 2, { 0x32, 0x7f } }, + { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, + { 2, { 0x14, 0x02 } }, + { 2, { 0x09, 0x90 } }, + { 3, { 0x08, 0x90, 0xfa } }, + { 2, { 0x36, 0x01 } }, + { 2, { 0x05, 0x03 } }, + { 7, { 0x06, 0x00, 0x06, 0x0e, 0x01, 0x0f, 0x05 } }, + { 1, { 0x12 } }, + /* Commands that are sent after pressing the 'Initialize' + button in the windows application */ + { 1, { 0x03 } }, + { 1, { 0x01 } }, + { 2, { 0x09, 0x90 } }, + { 3, { 0x08, 0x90, 0xfa } }, + { 1, { 0x34 } }, + { 2, { 0x35, 0x01 } }, + { 2, { 0x36, 0x01 } }, + { 2, { 0x30, 0x09 } }, + { 4, { 0x30, 0x06, 0x00, 0xe2 } }, + { 3, { 0x31, 0x01, 0x30 } }, + { 3, { 0x31, 0x04, 0x09 } }, + { 2, { 0x05, 0x02 } }, + { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } }, +}; + +static int si4713_start_seq(struct si4713_usb_device *radio) +{ + int retval = 0; + int i; + + radio->buffer[0] = 0x3f; + + for (i = 0; i < ARRAY_SIZE(start_seq); i++) { + int len = start_seq[i].len; + const u8 *payload = start_seq[i].payload; + + memcpy(radio->buffer + 1, payload, len); + memset(radio->buffer + len + 1, 0, BUFFER_LENGTH - 1 - len); + retval = si4713_send_startup_command(radio); + } + + return retval; +} + +static struct i2c_board_info si4713_board_info = { + I2C_BOARD_INFO("si4713", SI4713_I2C_ADDR_BUSEN_HIGH), +}; + +struct si4713_command_table { + int command_id; + u8 payload[8]; +}; + +/* + * Structure of a command : + * Byte 1 : 0x3f (always) + * Byte 2 : 0x06 (send a command) + * Byte 3 : Unknown + * Byte 4 : Number of arguments + 1 (for the command byte) + * Byte 5 : Number of response bytes + */ +static struct si4713_command_table command_table[] = { + + { SI4713_CMD_POWER_UP, { 0x00, SI4713_PWUP_NARGS + 1, SI4713_PWUP_NRESP} }, + { SI4713_CMD_GET_REV, { 0x03, 0x01, SI4713_GETREV_NRESP } }, + { SI4713_CMD_POWER_DOWN, { 0x00, 0x01, SI4713_PWDN_NRESP} }, + { SI4713_CMD_SET_PROPERTY, { 0x00, SI4713_SET_PROP_NARGS + 1, SI4713_SET_PROP_NRESP } }, + { SI4713_CMD_GET_PROPERTY, { 0x00, SI4713_GET_PROP_NARGS + 1, SI4713_GET_PROP_NRESP } }, + { SI4713_CMD_TX_TUNE_FREQ, { 0x03, SI4713_TXFREQ_NARGS + 1, SI4713_TXFREQ_NRESP } }, + { SI4713_CMD_TX_TUNE_POWER, { 0x03, SI4713_TXPWR_NARGS + 1, SI4713_TXPWR_NRESP } }, + { SI4713_CMD_TX_TUNE_MEASURE, { 0x03, SI4713_TXMEA_NARGS + 1, SI4713_TXMEA_NRESP } }, + { SI4713_CMD_TX_TUNE_STATUS, { 0x00, SI4713_TXSTATUS_NARGS + 1, SI4713_TXSTATUS_NRESP } }, + { SI4713_CMD_TX_ASQ_STATUS, { 0x03, SI4713_ASQSTATUS_NARGS + 1, SI4713_ASQSTATUS_NRESP } }, + { SI4713_CMD_GET_INT_STATUS, { 0x03, 0x01, SI4713_GET_STATUS_NRESP } }, + { SI4713_CMD_TX_RDS_BUFF, { 0x03, SI4713_RDSBUFF_NARGS + 1, SI4713_RDSBUFF_NRESP } }, + { SI4713_CMD_TX_RDS_PS, { 0x00, SI4713_RDSPS_NARGS + 1, SI4713_RDSPS_NRESP } }, +}; + +static int send_command(struct si4713_usb_device *radio, u8 *payload, char *data, int len) +{ + int retval; + + radio->buffer[0] = 0x3f; + radio->buffer[1] = 0x06; + + memcpy(radio->buffer + 2, payload, 3); + memcpy(radio->buffer + 5, data, len); + memset(radio->buffer + 5 + len, 0, BUFFER_LENGTH - 5 - len); + + /* send the command */ + retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), + 0x09, 0x21, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + + return retval < 0 ? retval : 0; +} + +static int si4713_i2c_read(struct si4713_usb_device *radio, char *data, int len) +{ + unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1; + int retval; + + /* receive the response */ + for (;;) { + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + 0x01, 0xa1, 0x033f, 0, radio->buffer, + BUFFER_LENGTH, USB_TIMEOUT); + if (retval < 0) + return retval; + + /* + * Check that we get a valid reply back (buffer[1] == 0) and + * that CTS is set before returning, otherwise we wait and try + * again. The i2c driver also does the CTS check, but the timeouts + * used there are much too small for this USB driver, so we wait + * for it here. + */ + if (radio->buffer[1] == 0 && (radio->buffer[2] & SI4713_CTS)) { + memcpy(data, radio->buffer + 2, len); + return 0; + } + if (time_is_before_jiffies(until_jiffies)) { + /* Zero the status value, ensuring CTS isn't set */ + data[0] = 0; + return 0; + } + msleep(3); + } +} + +static int si4713_i2c_write(struct si4713_usb_device *radio, char *data, int len) +{ + int retval = -EINVAL; + int i; + + if (len > BUFFER_LENGTH - 5) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(command_table); i++) { + if (data[0] == command_table[i].command_id) + retval = send_command(radio, command_table[i].payload, + data, len); + } + + return retval < 0 ? retval : 0; +} + +static int si4713_transfer(struct i2c_adapter *i2c_adapter, + struct i2c_msg *msgs, int num) +{ + struct si4713_usb_device *radio = i2c_get_adapdata(i2c_adapter); + int retval = -EINVAL; + int i; + + if (num <= 0) + return 0; + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + retval = si4713_i2c_read(radio, msgs[i].buf, msgs[i].len); + else + retval = si4713_i2c_write(radio, msgs[i].buf, msgs[i].len); + if (retval) + break; + } + + return retval ? retval : num; +} + +static u32 si4713_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm si4713_algo = { + .master_xfer = si4713_transfer, + .functionality = si4713_functionality, +}; + +/* This name value shows up in the sysfs filename associated + with this I2C adapter */ +static struct i2c_adapter si4713_i2c_adapter_template = { + .name = "si4713-i2c", + .owner = THIS_MODULE, + .algo = &si4713_algo, +}; + +static int si4713_register_i2c_adapter(struct si4713_usb_device *radio) +{ + radio->i2c_adapter = si4713_i2c_adapter_template; + /* set up sysfs linkage to our parent device */ + radio->i2c_adapter.dev.parent = &radio->usbdev->dev; + i2c_set_adapdata(&radio->i2c_adapter, radio); + + return i2c_add_adapter(&radio->i2c_adapter); +} + +/* check if the device is present and register with v4l and usb if it is */ +static int usb_si4713_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct si4713_usb_device *radio; + struct i2c_adapter *adapter; + struct v4l2_subdev *sd; + int retval = -ENOMEM; + + dev_info(&intf->dev, "Si4713 development board discovered: (%04X:%04X)\n", + id->idVendor, id->idProduct); + + /* Initialize local device structure */ + radio = kzalloc(sizeof(struct si4713_usb_device), GFP_KERNEL); + if (radio) + radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); + + if (!radio || !radio->buffer) { + dev_err(&intf->dev, "kmalloc for si4713_usb_device failed\n"); + kfree(radio); + return -ENOMEM; + } + + mutex_init(&radio->lock); + + radio->usbdev = interface_to_usbdev(intf); + radio->intf = intf; + usb_set_intfdata(intf, &radio->v4l2_dev); + + retval = si4713_start_seq(radio); + if (retval < 0) + goto err_v4l2; + + retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register v4l2_device\n"); + goto err_v4l2; + } + + retval = si4713_register_i2c_adapter(radio); + if (retval < 0) { + dev_err(&intf->dev, "could not register i2c device\n"); + goto err_i2cdev; + } + + adapter = &radio->i2c_adapter; + sd = v4l2_i2c_new_subdev_board(&radio->v4l2_dev, adapter, + &si4713_board_info, NULL); + radio->v4l2_subdev = sd; + if (!sd) { + dev_err(&intf->dev, "cannot get v4l2 subdevice\n"); + retval = -ENODEV; + goto del_adapter; + } + + radio->vdev.ctrl_handler = sd->ctrl_handler; + radio->v4l2_dev.release = usb_si4713_video_device_release; + strlcpy(radio->vdev.name, radio->v4l2_dev.name, + sizeof(radio->vdev.name)); + radio->vdev.v4l2_dev = &radio->v4l2_dev; + radio->vdev.fops = &usb_si4713_fops; + radio->vdev.ioctl_ops = &usb_si4713_ioctl_ops; + radio->vdev.lock = &radio->lock; + radio->vdev.release = video_device_release_empty; + radio->vdev.vfl_dir = VFL_DIR_TX; + + video_set_drvdata(&radio->vdev, radio); + set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags); + + retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); + if (retval < 0) { + dev_err(&intf->dev, "could not register video device\n"); + goto del_adapter; + } + + dev_info(&intf->dev, "V4L2 device registered as %s\n", + video_device_node_name(&radio->vdev)); + + return 0; + +del_adapter: + i2c_del_adapter(adapter); +err_i2cdev: + v4l2_device_unregister(&radio->v4l2_dev); +err_v4l2: + kfree(radio->buffer); + kfree(radio); + return retval; +} + +static void usb_si4713_disconnect(struct usb_interface *intf) +{ + struct si4713_usb_device *radio = to_si4713_dev(usb_get_intfdata(intf)); + + dev_info(&intf->dev, "Si4713 development board now disconnected\n"); + + mutex_lock(&radio->lock); + usb_set_intfdata(intf, NULL); + video_unregister_device(&radio->vdev); + v4l2_device_disconnect(&radio->v4l2_dev); + mutex_unlock(&radio->lock); + v4l2_device_put(&radio->v4l2_dev); +} + +/* USB subsystem interface */ +static struct usb_driver usb_si4713_driver = { + .name = "radio-usb-si4713", + .probe = usb_si4713_probe, + .disconnect = usb_si4713_disconnect, + .id_table = usb_si4713_usb_device_table, +}; + +module_usb_driver(usb_si4713_driver); diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713/si4713.c index 27aba936fb2..07d5153811e 100644 --- a/drivers/media/radio/si4713-i2c.c +++ b/drivers/media/radio/si4713/si4713.c @@ -21,20 +21,18 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <linux/mutex.h> #include <linux/completion.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/slab.h> #include <linux/gpio.h> -#include <linux/regulator/consumer.h> #include <linux/module.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-common.h> -#include "si4713-i2c.h" +#include "si4713.h" /* module parameters */ static int debug; @@ -46,25 +44,18 @@ MODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>"); MODULE_DESCRIPTION("I2C driver for Si4713 FM Radio Transmitter"); MODULE_VERSION("0.0.1"); -static const char *si4713_supply_names[SI4713_NUM_SUPPLIES] = { - "vio", - "vdd", -}; - #define DEFAULT_RDS_PI 0x00 #define DEFAULT_RDS_PTY 0x00 -#define DEFAULT_RDS_PS_NAME "" -#define DEFAULT_RDS_RADIO_TEXT DEFAULT_RDS_PS_NAME #define DEFAULT_RDS_DEVIATION 0x00C8 #define DEFAULT_RDS_PS_REPEAT_COUNT 0x0003 #define DEFAULT_LIMITER_RTIME 0x1392 #define DEFAULT_LIMITER_DEV 0x102CA -#define DEFAULT_PILOT_FREQUENCY 0x4A38 +#define DEFAULT_PILOT_FREQUENCY 0x4A38 #define DEFAULT_PILOT_DEVIATION 0x1A5E #define DEFAULT_ACOMP_ATIME 0x0000 #define DEFAULT_ACOMP_RTIME 0xF4240L #define DEFAULT_ACOMP_GAIN 0x0F -#define DEFAULT_ACOMP_THRESHOLD (-0x28) +#define DEFAULT_ACOMP_THRESHOLD (-0x28) #define DEFAULT_MUTE 0x01 #define DEFAULT_POWER_LEVEL 88 #define DEFAULT_FREQUENCY 8800 @@ -108,7 +99,6 @@ static const char *si4713_supply_names[SI4713_NUM_SUPPLIES] = { (status & SI4713_ERR)) /* mute definition */ #define set_mute(p) ((p & 1) | ((p & 1) << 1)); -#define get_mute(p) (p & 0x01) #ifdef DEBUG #define DBG_BUFFER(device, message, buffer, size) \ @@ -190,21 +180,6 @@ static int usecs_to_dev(unsigned long usecs, unsigned long const array[], return rval; } -static unsigned long dev_to_usecs(int value, unsigned long const array[], - int size) -{ - int i; - int rval = -EINVAL; - - for (i = 0; i < size / 2; i++) - if (array[i * 2] == value) { - rval = array[(i * 2) + 1]; - break; - } - - return rval; -} - /* si4713_handler: IRQ handler, just complete work */ static irqreturn_t si4713_handler(int irq, void *dev) { @@ -232,6 +207,7 @@ static int si4713_send_command(struct si4713_device *sdev, const u8 command, u8 response[], const int respn, const int usecs) { struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd); + unsigned long until_jiffies; u8 data1[MAX_ARGS + 1]; int err; @@ -247,30 +223,42 @@ static int si4713_send_command(struct si4713_device *sdev, const u8 command, if (err != argn + 1) { v4l2_err(&sdev->sd, "Error while sending command 0x%02x\n", command); - return (err > 0) ? -EIO : err; + return err < 0 ? err : -EIO; } + until_jiffies = jiffies + usecs_to_jiffies(usecs) + 1; + /* Wait response from interrupt */ - if (!wait_for_completion_timeout(&sdev->work, + if (client->irq) { + if (!wait_for_completion_timeout(&sdev->work, usecs_to_jiffies(usecs) + 1)) - v4l2_warn(&sdev->sd, + v4l2_warn(&sdev->sd, "(%s) Device took too much time to answer.\n", __func__); - - /* Then get the response */ - err = i2c_master_recv(client, response, respn); - if (err != respn) { - v4l2_err(&sdev->sd, - "Error while reading response for command 0x%02x\n", - command); - return (err > 0) ? -EIO : err; } - DBG_BUFFER(&sdev->sd, "Response", response, respn); - if (check_command_failed(response[0])) - return -EBUSY; + do { + err = i2c_master_recv(client, response, respn); + if (err != respn) { + v4l2_err(&sdev->sd, + "Error %d while reading response for command 0x%02x\n", + err, command); + return err < 0 ? err : -EIO; + } + + DBG_BUFFER(&sdev->sd, "Response", response, respn); + if (!check_command_failed(response[0])) + return 0; - return 0; + if (client->irq) + return -EBUSY; + if (usecs <= 1000) + usleep_range(usecs, 1000); + else + usleep_range(1000, 2000); + } while (time_is_after_jiffies(until_jiffies)); + + return -EBUSY; } /* @@ -284,9 +272,9 @@ static int si4713_read_property(struct si4713_device *sdev, u16 prop, u32 *pv) int err; u8 val[SI4713_GET_PROP_NRESP]; /* - * .First byte = 0 - * .Second byte = property's MSB - * .Third byte = property's LSB + * .First byte = 0 + * .Second byte = property's MSB + * .Third byte = property's LSB */ const u8 args[SI4713_GET_PROP_NARGS] = { 0x00, @@ -321,11 +309,11 @@ static int si4713_write_property(struct si4713_device *sdev, u16 prop, u16 val) int rval; u8 resp[SI4713_SET_PROP_NRESP]; /* - * .First byte = 0 - * .Second byte = property's MSB - * .Third byte = property's LSB - * .Fourth byte = value's MSB - * .Fifth byte = value's LSB + * .First byte = 0 + * .Second byte = property's MSB + * .Third byte = property's LSB + * .Fourth byte = value's MSB + * .Fifth byte = value's LSB */ const u8 args[SI4713_SET_PROP_NARGS] = { 0x00, @@ -363,31 +351,36 @@ static int si4713_write_property(struct si4713_device *sdev, u16 prop, u16 val) */ static int si4713_powerup(struct si4713_device *sdev) { + struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd); int err; u8 resp[SI4713_PWUP_NRESP]; /* - * .First byte = Enabled interrupts and boot function - * .Second byte = Input operation mode + * .First byte = Enabled interrupts and boot function + * .Second byte = Input operation mode */ - const u8 args[SI4713_PWUP_NARGS] = { - SI4713_PWUP_CTSIEN | SI4713_PWUP_GPO2OEN | SI4713_PWUP_FUNC_TX, + u8 args[SI4713_PWUP_NARGS] = { + SI4713_PWUP_GPO2OEN | SI4713_PWUP_FUNC_TX, SI4713_PWUP_OPMOD_ANALOG, }; if (sdev->power_state) return 0; - err = regulator_bulk_enable(ARRAY_SIZE(sdev->supplies), - sdev->supplies); - if (err) { - v4l2_err(&sdev->sd, "Failed to enable supplies: %d\n", err); - return err; + if (sdev->supplies) { + err = regulator_bulk_enable(sdev->supplies, sdev->supply_data); + if (err) { + v4l2_err(&sdev->sd, "Failed to enable supplies: %d\n", err); + return err; + } } if (gpio_is_valid(sdev->gpio_reset)) { udelay(50); gpio_set_value(sdev->gpio_reset, 1); } + if (client->irq) + args[0] |= SI4713_PWUP_CTSIEN; + err = si4713_send_command(sdev, SI4713_CMD_POWER_UP, args, ARRAY_SIZE(args), resp, ARRAY_SIZE(resp), @@ -399,13 +392,15 @@ static int si4713_powerup(struct si4713_device *sdev) v4l2_dbg(1, debug, &sdev->sd, "Device in power up mode\n"); sdev->power_state = POWER_ON; - err = si4713_write_property(sdev, SI4713_GPO_IEN, + if (client->irq) + err = si4713_write_property(sdev, SI4713_GPO_IEN, SI4713_STC_INT | SI4713_CTS); - } else { - if (gpio_is_valid(sdev->gpio_reset)) - gpio_set_value(sdev->gpio_reset, 0); - err = regulator_bulk_disable(ARRAY_SIZE(sdev->supplies), - sdev->supplies); + return err; + } + if (gpio_is_valid(sdev->gpio_reset)) + gpio_set_value(sdev->gpio_reset, 0); + if (sdev->supplies) { + err = regulator_bulk_disable(sdev->supplies, sdev->supply_data); if (err) v4l2_err(&sdev->sd, "Failed to disable supplies: %d\n", err); @@ -437,11 +432,13 @@ static int si4713_powerdown(struct si4713_device *sdev) v4l2_dbg(1, debug, &sdev->sd, "Device in reset mode\n"); if (gpio_is_valid(sdev->gpio_reset)) gpio_set_value(sdev->gpio_reset, 0); - err = regulator_bulk_disable(ARRAY_SIZE(sdev->supplies), - sdev->supplies); - if (err) - v4l2_err(&sdev->sd, - "Failed to disable supplies: %d\n", err); + if (sdev->supplies) { + err = regulator_bulk_disable(sdev->supplies, + sdev->supply_data); + if (err) + v4l2_err(&sdev->sd, + "Failed to disable supplies: %d\n", err); + } sdev->power_state = POWER_OFF; } @@ -458,26 +455,21 @@ static int si4713_checkrev(struct si4713_device *sdev) int rval; u8 resp[SI4713_GETREV_NRESP]; - mutex_lock(&sdev->mutex); - rval = si4713_send_command(sdev, SI4713_CMD_GET_REV, NULL, 0, resp, ARRAY_SIZE(resp), DEFAULT_TIMEOUT); if (rval < 0) - goto unlock; + return rval; if (resp[1] == SI4713_PRODUCT_NUMBER) { v4l2_info(&sdev->sd, "chip found @ 0x%02x (%s)\n", client->addr << 1, client->adapter->name); } else { - v4l2_err(&sdev->sd, "Invalid product number\n"); + v4l2_err(&sdev->sd, "Invalid product number 0x%X\n", resp[1]); rval = -EINVAL; } - -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -489,39 +481,45 @@ unlock: */ static int si4713_wait_stc(struct si4713_device *sdev, const int usecs) { - int err; + struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd); u8 resp[SI4713_GET_STATUS_NRESP]; + unsigned long start_jiffies = jiffies; + int err; - /* Wait response from STC interrupt */ - if (!wait_for_completion_timeout(&sdev->work, - usecs_to_jiffies(usecs) + 1)) + if (client->irq && + !wait_for_completion_timeout(&sdev->work, usecs_to_jiffies(usecs) + 1)) v4l2_warn(&sdev->sd, - "%s: device took too much time to answer (%d usec).\n", - __func__, usecs); - - /* Clear status bits */ - err = si4713_send_command(sdev, SI4713_CMD_GET_INT_STATUS, - NULL, 0, - resp, ARRAY_SIZE(resp), - DEFAULT_TIMEOUT); - - if (err < 0) - goto exit; - - v4l2_dbg(1, debug, &sdev->sd, - "%s: status bits: 0x%02x\n", __func__, resp[0]); - - if (!(resp[0] & SI4713_STC_INT)) - err = -EIO; - -exit: - return err; + "(%s) Device took too much time to answer.\n", __func__); + + for (;;) { + /* Clear status bits */ + err = si4713_send_command(sdev, SI4713_CMD_GET_INT_STATUS, + NULL, 0, + resp, ARRAY_SIZE(resp), + DEFAULT_TIMEOUT); + /* The USB device returns errors when it waits for the + * STC bit to be set. Hence polling */ + if (err >= 0) { + v4l2_dbg(1, debug, &sdev->sd, + "%s: status bits: 0x%02x\n", __func__, resp[0]); + + if (resp[0] & SI4713_STC_INT) + return 0; + } + if (jiffies_to_usecs(jiffies - start_jiffies) > usecs) + return err < 0 ? err : -EIO; + /* We sleep here for 3-4 ms in order to avoid flooding the device + * with USB requests. The si4713 USB driver was developed + * by reverse engineering the Windows USB driver. The windows + * driver also has a ~2.5 ms delay between responses. */ + usleep_range(3000, 4000); + } } /* * si4713_tx_tune_freq - Sets the state of the RF carrier and sets the tuning - * frequency between 76 and 108 MHz in 10 kHz units and - * steps of 50 kHz. + * frequency between 76 and 108 MHz in 10 kHz units and + * steps of 50 kHz. * @sdev: si4713_device structure for the device we are communicating * @frequency: desired frequency (76 - 108 MHz, unit 10 KHz, step 50 kHz) */ @@ -530,9 +528,9 @@ static int si4713_tx_tune_freq(struct si4713_device *sdev, u16 frequency) int err; u8 val[SI4713_TXFREQ_NRESP]; /* - * .First byte = 0 - * .Second byte = frequency's MSB - * .Third byte = frequency's LSB + * .First byte = 0 + * .Second byte = frequency's MSB + * .Third byte = frequency's LSB */ const u8 args[SI4713_TXFREQ_NARGS] = { 0x00, @@ -559,14 +557,14 @@ static int si4713_tx_tune_freq(struct si4713_device *sdev, u16 frequency) } /* - * si4713_tx_tune_power - Sets the RF voltage level between 88 and 115 dBuV in - * 1 dB units. A value of 0x00 indicates off. The command - * also sets the antenna tuning capacitance. A value of 0 - * indicates autotuning, and a value of 1 - 191 indicates - * a manual override, which results in a tuning - * capacitance of 0.25 pF x @antcap. + * si4713_tx_tune_power - Sets the RF voltage level between 88 and 120 dBuV in + * 1 dB units. A value of 0x00 indicates off. The command + * also sets the antenna tuning capacitance. A value of 0 + * indicates autotuning, and a value of 1 - 191 indicates + * a manual override, which results in a tuning + * capacitance of 0.25 pF x @antcap. * @sdev: si4713_device structure for the device we are communicating - * @power: tuning power (88 - 115 dBuV, unit/step 1 dB) + * @power: tuning power (88 - 120 dBuV, unit/step 1 dB) * @antcap: value of antenna tuning capacitor (0 - 191) */ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power, @@ -575,21 +573,21 @@ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power, int err; u8 val[SI4713_TXPWR_NRESP]; /* - * .First byte = 0 - * .Second byte = 0 - * .Third byte = power - * .Fourth byte = antcap + * .First byte = 0 + * .Second byte = 0 + * .Third byte = power + * .Fourth byte = antcap */ - const u8 args[SI4713_TXPWR_NARGS] = { + u8 args[SI4713_TXPWR_NARGS] = { 0x00, 0x00, power, antcap, }; - if (((power > 0) && (power < SI4713_MIN_POWER)) || - power > SI4713_MAX_POWER || antcap > SI4713_MAX_ANTCAP) - return -EDOM; + /* Map power values 1-87 to MIN_POWER (88) */ + if (power > 0 && power < SI4713_MIN_POWER) + args[2] = power = SI4713_MIN_POWER; err = si4713_send_command(sdev, SI4713_CMD_TX_TUNE_POWER, args, ARRAY_SIZE(args), val, @@ -607,12 +605,12 @@ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power, /* * si4713_tx_tune_measure - Enters receive mode and measures the received noise - * level in units of dBuV on the selected frequency. - * The Frequency must be between 76 and 108 MHz in 10 kHz - * units and steps of 50 kHz. The command also sets the - * antenna tuning capacitance. A value of 0 means - * autotuning, and a value of 1 to 191 indicates manual - * override. + * level in units of dBuV on the selected frequency. + * The Frequency must be between 76 and 108 MHz in 10 kHz + * units and steps of 50 kHz. The command also sets the + * antenna tuning capacitance. A value of 0 means + * autotuning, and a value of 1 to 191 indicates manual + * override. * @sdev: si4713_device structure for the device we are communicating * @frequency: desired frequency (76 - 108 MHz, unit 10 KHz, step 50 kHz) * @antcap: value of antenna tuning capacitor (0 - 191) @@ -623,10 +621,10 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency, int err; u8 val[SI4713_TXMEA_NRESP]; /* - * .First byte = 0 - * .Second byte = frequency's MSB - * .Third byte = frequency's LSB - * .Fourth byte = antcap + * .First byte = 0 + * .Second byte = frequency's MSB + * .Third byte = frequency's LSB + * .Fourth byte = antcap */ const u8 args[SI4713_TXMEA_NARGS] = { 0x00, @@ -656,11 +654,11 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency, /* * si4713_tx_tune_status- Returns the status of the tx_tune_freq, tx_tune_mea or - * tx_tune_power commands. This command return the current - * frequency, output voltage in dBuV, the antenna tunning - * capacitance value and the received noise level. The - * command also clears the stcint interrupt bit when the - * first bit of its arguments is high. + * tx_tune_power commands. This command return the current + * frequency, output voltage in dBuV, the antenna tunning + * capacitance value and the received noise level. The + * command also clears the stcint interrupt bit when the + * first bit of its arguments is high. * @sdev: si4713_device structure for the device we are communicating * @intack: 0x01 to clear the seek/tune complete interrupt status indicator. * @frequency: returned frequency @@ -675,7 +673,7 @@ static int si4713_tx_tune_status(struct si4713_device *sdev, u8 intack, int err; u8 val[SI4713_TXSTATUS_NRESP]; /* - * .First byte = intack bit + * .First byte = intack bit */ const u8 args[SI4713_TXSTATUS_NARGS] = { intack & SI4713_INTACK_MASK, @@ -778,17 +776,9 @@ static int si4713_tx_rds_ps(struct si4713_device *sdev, u8 psid, static int si4713_set_power_state(struct si4713_device *sdev, u8 value) { - int rval; - - mutex_lock(&sdev->mutex); - if (value) - rval = si4713_powerup(sdev); - else - rval = si4713_powerdown(sdev); - - mutex_unlock(&sdev->mutex); - return rval; + return si4713_powerup(sdev); + return si4713_powerdown(sdev); } static int si4713_set_mute(struct si4713_device *sdev, u16 mute) @@ -797,17 +787,10 @@ static int si4713_set_mute(struct si4713_device *sdev, u16 mute) mute = set_mute(mute); - mutex_lock(&sdev->mutex); - if (sdev->power_state) rval = si4713_write_property(sdev, SI4713_TX_LINE_INPUT_MUTE, mute); - if (rval >= 0) - sdev->mute = get_mute(mute); - - mutex_unlock(&sdev->mutex); - return rval; } @@ -820,15 +803,13 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name) if (!strlen(ps_name)) memset(ps_name, 0, MAX_RDS_PS_NAME + 1); - mutex_lock(&sdev->mutex); - if (sdev->power_state) { /* Write the new ps name and clear the padding */ for (i = 0; i < MAX_RDS_PS_NAME; i += (RDS_BLOCK / 2)) { rval = si4713_tx_rds_ps(sdev, (i / (RDS_BLOCK / 2)), ps_name + i); if (rval < 0) - goto unlock; + return rval; } /* Setup the size to be sent */ @@ -841,51 +822,45 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name) SI4713_TX_RDS_PS_MESSAGE_COUNT, rds_ps_nblocks(len)); if (rval < 0) - goto unlock; + return rval; rval = si4713_write_property(sdev, SI4713_TX_RDS_PS_REPEAT_COUNT, DEFAULT_RDS_PS_REPEAT_COUNT * 2); if (rval < 0) - goto unlock; + return rval; } - strncpy(sdev->rds_info.ps_name, ps_name, MAX_RDS_PS_NAME); - -unlock: - mutex_unlock(&sdev->mutex); return rval; } -static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) +static int si4713_set_rds_radio_text(struct si4713_device *sdev, const char *rt) { + static const char cr[RDS_RADIOTEXT_BLK_SIZE] = { RDS_CARRIAGE_RETURN, 0 }; int rval = 0, i; u16 t_index = 0; u8 b_index = 0, cr_inserted = 0; s8 left; - mutex_lock(&sdev->mutex); - if (!sdev->power_state) - goto copy; + return rval; rval = si4713_tx_rds_buff(sdev, RDS_BLOCK_CLEAR, 0, 0, 0, &left); if (rval < 0) - goto unlock; + return rval; if (!strlen(rt)) - goto copy; + return rval; do { /* RDS spec says that if the last block isn't used, * then apply a carriage return */ - if (t_index < (RDS_RADIOTEXT_INDEX_MAX * - RDS_RADIOTEXT_BLK_SIZE)) { + if (t_index < (RDS_RADIOTEXT_INDEX_MAX * RDS_RADIOTEXT_BLK_SIZE)) { for (i = 0; i < RDS_RADIOTEXT_BLK_SIZE; i++) { - if (!rt[t_index + i] || rt[t_index + i] == - RDS_CARRIAGE_RETURN) { - rt[t_index + i] = RDS_CARRIAGE_RETURN; + if (!rt[t_index + i] || + rt[t_index + i] == RDS_CARRIAGE_RETURN) { + rt = cr; cr_inserted = 1; break; } @@ -898,7 +873,7 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) compose_u16(rt[t_index + 2], rt[t_index + 3]), &left); if (rval < 0) - goto unlock; + return rval; t_index += RDS_RADIOTEXT_BLK_SIZE; @@ -906,16 +881,38 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt) break; } while (left > 0); -copy: - strncpy(sdev->rds_info.radio_text, rt, MAX_RDS_RADIO_TEXT); + return rval; +} -unlock: - mutex_unlock(&sdev->mutex); +/* + * si4713_update_tune_status - update properties from tx_tune_status + * command. Must be called with sdev->mutex held. + * @sdev: si4713_device structure for the device we are communicating + */ +static int si4713_update_tune_status(struct si4713_device *sdev) +{ + int rval; + u16 f = 0; + u8 p = 0, a = 0, n = 0; + + rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n); + + if (rval < 0) + goto exit; + +/* TODO: check that power_level and antenna_capacitor really are not + changed by the hardware. If they are, then these controls should become + volatiles. + sdev->power_level = p; + sdev->antenna_capacitor = a;*/ + sdev->tune_rnl = n; + +exit: return rval; } static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id, - u32 **shadow, s32 *bit, s32 *mask, u16 *property, int *mul, + s32 *bit, s32 *mask, u16 *property, int *mul, unsigned long **table, int *size) { s32 rval = 0; @@ -925,406 +922,108 @@ static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id, case V4L2_CID_RDS_TX_PI: *property = SI4713_TX_RDS_PI; *mul = 1; - *shadow = &sdev->rds_info.pi; break; case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: *property = SI4713_TX_ACOMP_THRESHOLD; *mul = 1; - *shadow = &sdev->acomp_info.threshold; break; case V4L2_CID_AUDIO_COMPRESSION_GAIN: *property = SI4713_TX_ACOMP_GAIN; *mul = 1; - *shadow = &sdev->acomp_info.gain; break; case V4L2_CID_PILOT_TONE_FREQUENCY: *property = SI4713_TX_PILOT_FREQUENCY; *mul = 1; - *shadow = &sdev->pilot_info.frequency; break; case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: *property = SI4713_TX_ACOMP_ATTACK_TIME; *mul = ATTACK_TIME_UNIT; - *shadow = &sdev->acomp_info.attack_time; break; case V4L2_CID_PILOT_TONE_DEVIATION: *property = SI4713_TX_PILOT_DEVIATION; *mul = 10; - *shadow = &sdev->pilot_info.deviation; break; case V4L2_CID_AUDIO_LIMITER_DEVIATION: *property = SI4713_TX_AUDIO_DEVIATION; *mul = 10; - *shadow = &sdev->limiter_info.deviation; break; case V4L2_CID_RDS_TX_DEVIATION: *property = SI4713_TX_RDS_DEVIATION; *mul = 1; - *shadow = &sdev->rds_info.deviation; break; case V4L2_CID_RDS_TX_PTY: *property = SI4713_TX_RDS_PS_MISC; *bit = 5; *mask = 0x1F << 5; - *shadow = &sdev->rds_info.pty; break; case V4L2_CID_AUDIO_LIMITER_ENABLED: *property = SI4713_TX_ACOMP_ENABLE; *bit = 1; *mask = 1 << 1; - *shadow = &sdev->limiter_info.enabled; break; case V4L2_CID_AUDIO_COMPRESSION_ENABLED: *property = SI4713_TX_ACOMP_ENABLE; *bit = 0; *mask = 1 << 0; - *shadow = &sdev->acomp_info.enabled; break; case V4L2_CID_PILOT_TONE_ENABLED: *property = SI4713_TX_COMPONENT_ENABLE; *bit = 0; *mask = 1 << 0; - *shadow = &sdev->pilot_info.enabled; break; case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: *property = SI4713_TX_LIMITER_RELEASE_TIME; *table = limiter_times; *size = ARRAY_SIZE(limiter_times); - *shadow = &sdev->limiter_info.release_time; break; case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: *property = SI4713_TX_ACOMP_RELEASE_TIME; *table = acomp_rtimes; *size = ARRAY_SIZE(acomp_rtimes); - *shadow = &sdev->acomp_info.release_time; break; case V4L2_CID_TUNE_PREEMPHASIS: *property = SI4713_TX_PREEMPHASIS; *table = preemphasis_values; *size = ARRAY_SIZE(preemphasis_values); - *shadow = &sdev->preemphasis; - break; - - default: - rval = -EINVAL; - }; - - return rval; -} - -static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc); - -/* write string property */ -static int si4713_write_econtrol_string(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - struct v4l2_queryctrl vqc; - int len; - s32 rval = 0; - - vqc.id = control->id; - rval = si4713_queryctrl(&sdev->sd, &vqc); - if (rval < 0) - goto exit; - - switch (control->id) { - case V4L2_CID_RDS_TX_PS_NAME: { - char ps_name[MAX_RDS_PS_NAME + 1]; - - len = control->size - 1; - if (len < 0 || len > MAX_RDS_PS_NAME) { - rval = -ERANGE; - goto exit; - } - rval = copy_from_user(ps_name, control->string, len); - if (rval) { - rval = -EFAULT; - goto exit; - } - ps_name[len] = '\0'; - - if (strlen(ps_name) % vqc.step) { - rval = -ERANGE; - goto exit; - } - - rval = si4713_set_rds_ps_name(sdev, ps_name); - } - break; - - case V4L2_CID_RDS_TX_RADIO_TEXT: { - char radio_text[MAX_RDS_RADIO_TEXT + 1]; - - len = control->size - 1; - if (len < 0 || len > MAX_RDS_RADIO_TEXT) { - rval = -ERANGE; - goto exit; - } - rval = copy_from_user(radio_text, control->string, len); - if (rval) { - rval = -EFAULT; - goto exit; - } - radio_text[len] = '\0'; - - if (strlen(radio_text) % vqc.step) { - rval = -ERANGE; - goto exit; - } - - rval = si4713_set_rds_radio_text(sdev, radio_text); - } break; default: rval = -EINVAL; break; - }; - -exit: - return rval; -} - -static int validate_range(struct v4l2_subdev *sd, - struct v4l2_ext_control *control) -{ - struct v4l2_queryctrl vqc; - int rval; - - vqc.id = control->id; - rval = si4713_queryctrl(sd, &vqc); - if (rval < 0) - goto exit; - - if (control->value < vqc.minimum || control->value > vqc.maximum) - rval = -ERANGE; - -exit: - return rval; -} - -/* properties which use tx_tune_power*/ -static int si4713_write_econtrol_tune(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval = 0; - u8 power, antcap; - - rval = validate_range(&sdev->sd, control); - if (rval < 0) - goto exit; - - mutex_lock(&sdev->mutex); - - switch (control->id) { - case V4L2_CID_TUNE_POWER_LEVEL: - power = control->value; - antcap = sdev->antenna_capacitor; - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - power = sdev->power_level; - antcap = control->value; - break; - default: - rval = -EINVAL; - goto unlock; - }; - - if (sdev->power_state) - rval = si4713_tx_tune_power(sdev, power, antcap); - - if (rval == 0) { - sdev->power_level = power; - sdev->antenna_capacitor = antcap; } -unlock: - mutex_unlock(&sdev->mutex); -exit: return rval; } -static int si4713_write_econtrol_integers(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval; - u32 *shadow = NULL, val = 0; - s32 bit = 0, mask = 0; - u16 property = 0; - int mul = 0; - unsigned long *table = NULL; - int size = 0; - - rval = validate_range(&sdev->sd, control); - if (rval < 0) - goto exit; - - rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit, - &mask, &property, &mul, &table, &size); - if (rval < 0) - goto exit; - - val = control->value; - if (mul) { - val = control->value / mul; - } else if (table) { - rval = usecs_to_dev(control->value, table, size); - if (rval < 0) - goto exit; - val = rval; - rval = 0; - } - - mutex_lock(&sdev->mutex); - - if (sdev->power_state) { - if (mask) { - rval = si4713_read_property(sdev, property, &val); - if (rval < 0) - goto unlock; - val = set_bits(val, control->value, bit, mask); - } - - rval = si4713_write_property(sdev, property, val); - if (rval < 0) - goto unlock; - if (mask) - val = control->value; - } - - if (mul) { - *shadow = val * mul; - } else if (table) { - rval = dev_to_usecs(val, table, size); - if (rval < 0) - goto unlock; - *shadow = rval; - rval = 0; - } else { - *shadow = val; - } - -unlock: - mutex_unlock(&sdev->mutex); -exit: - return rval; -} - -static int si4713_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f); -static int si4713_s_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *); +static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f); +static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulator *); /* * si4713_setup - Sets the device up with current configuration. * @sdev: si4713_device structure for the device we are communicating */ static int si4713_setup(struct si4713_device *sdev) { - struct v4l2_ext_control ctrl; struct v4l2_frequency f; struct v4l2_modulator vm; - struct si4713_device *tmp; - int rval = 0; - - tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); - if (!tmp) - return -ENOMEM; - - /* Get a local copy to avoid race */ - mutex_lock(&sdev->mutex); - memcpy(tmp, sdev, sizeof(*sdev)); - mutex_unlock(&sdev->mutex); - - ctrl.id = V4L2_CID_RDS_TX_PI; - ctrl.value = tmp->rds_info.pi; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_THRESHOLD; - ctrl.value = tmp->acomp_info.threshold; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_GAIN; - ctrl.value = tmp->acomp_info.gain; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_PILOT_TONE_FREQUENCY; - ctrl.value = tmp->pilot_info.frequency; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME; - ctrl.value = tmp->acomp_info.attack_time; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_PILOT_TONE_DEVIATION; - ctrl.value = tmp->pilot_info.deviation; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_LIMITER_DEVIATION; - ctrl.value = tmp->limiter_info.deviation; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_RDS_TX_DEVIATION; - ctrl.value = tmp->rds_info.deviation; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_RDS_TX_PTY; - ctrl.value = tmp->rds_info.pty; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_LIMITER_ENABLED; - ctrl.value = tmp->limiter_info.enabled; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ENABLED; - ctrl.value = tmp->acomp_info.enabled; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_PILOT_TONE_ENABLED; - ctrl.value = tmp->pilot_info.enabled; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_LIMITER_RELEASE_TIME; - ctrl.value = tmp->limiter_info.release_time; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME; - ctrl.value = tmp->acomp_info.release_time; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_TUNE_PREEMPHASIS; - ctrl.value = tmp->preemphasis; - rval |= si4713_write_econtrol_integers(sdev, &ctrl); - - ctrl.id = V4L2_CID_RDS_TX_PS_NAME; - rval |= si4713_set_rds_ps_name(sdev, tmp->rds_info.ps_name); - - ctrl.id = V4L2_CID_RDS_TX_RADIO_TEXT; - rval |= si4713_set_rds_radio_text(sdev, tmp->rds_info.radio_text); + int rval; /* Device procedure needs to set frequency first */ - f.frequency = tmp->frequency ? tmp->frequency : DEFAULT_FREQUENCY; + f.tuner = 0; + f.frequency = sdev->frequency ? sdev->frequency : DEFAULT_FREQUENCY; f.frequency = si4713_to_v4l2(f.frequency); - rval |= si4713_s_frequency(&sdev->sd, &f); - - ctrl.id = V4L2_CID_TUNE_POWER_LEVEL; - ctrl.value = tmp->power_level; - rval |= si4713_write_econtrol_tune(sdev, &ctrl); - - ctrl.id = V4L2_CID_TUNE_ANTENNA_CAPACITOR; - ctrl.value = tmp->antenna_capacitor; - rval |= si4713_write_econtrol_tune(sdev, &ctrl); + rval = si4713_s_frequency(&sdev->sd, &f); vm.index = 0; - if (tmp->stereo) + if (sdev->stereo) vm.txsubchans = V4L2_TUNER_SUB_STEREO; else vm.txsubchans = V4L2_TUNER_SUB_MONO; - if (tmp->rds_info.enabled) + if (sdev->rds_enabled) vm.txsubchans |= V4L2_TUNER_SUB_RDS; si4713_s_modulator(&sdev->sd, &vm); - kfree(tmp); - return rval; } @@ -1338,438 +1037,129 @@ static int si4713_initialize(struct si4713_device *sdev) rval = si4713_set_power_state(sdev, POWER_ON); if (rval < 0) - goto exit; + return rval; rval = si4713_checkrev(sdev); if (rval < 0) - goto exit; + return rval; rval = si4713_set_power_state(sdev, POWER_OFF); if (rval < 0) - goto exit; - - mutex_lock(&sdev->mutex); - - sdev->rds_info.pi = DEFAULT_RDS_PI; - sdev->rds_info.pty = DEFAULT_RDS_PTY; - sdev->rds_info.deviation = DEFAULT_RDS_DEVIATION; - strlcpy(sdev->rds_info.ps_name, DEFAULT_RDS_PS_NAME, MAX_RDS_PS_NAME); - strlcpy(sdev->rds_info.radio_text, DEFAULT_RDS_RADIO_TEXT, - MAX_RDS_RADIO_TEXT); - sdev->rds_info.enabled = 1; - - sdev->limiter_info.release_time = DEFAULT_LIMITER_RTIME; - sdev->limiter_info.deviation = DEFAULT_LIMITER_DEV; - sdev->limiter_info.enabled = 1; - - sdev->pilot_info.deviation = DEFAULT_PILOT_DEVIATION; - sdev->pilot_info.frequency = DEFAULT_PILOT_FREQUENCY; - sdev->pilot_info.enabled = 1; - - sdev->acomp_info.release_time = DEFAULT_ACOMP_RTIME; - sdev->acomp_info.attack_time = DEFAULT_ACOMP_ATIME; - sdev->acomp_info.threshold = DEFAULT_ACOMP_THRESHOLD; - sdev->acomp_info.gain = DEFAULT_ACOMP_GAIN; - sdev->acomp_info.enabled = 1; + return rval; sdev->frequency = DEFAULT_FREQUENCY; - sdev->preemphasis = DEFAULT_PREEMPHASIS; - sdev->mute = DEFAULT_MUTE; - sdev->power_level = DEFAULT_POWER_LEVEL; - sdev->antenna_capacitor = 0; sdev->stereo = 1; sdev->tune_rnl = DEFAULT_TUNE_RNL; - - mutex_unlock(&sdev->mutex); - -exit: - return rval; -} - -/* read string property */ -static int si4713_read_econtrol_string(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval = 0; - - switch (control->id) { - case V4L2_CID_RDS_TX_PS_NAME: - if (strlen(sdev->rds_info.ps_name) + 1 > control->size) { - control->size = MAX_RDS_PS_NAME + 1; - rval = -ENOSPC; - goto exit; - } - rval = copy_to_user(control->string, sdev->rds_info.ps_name, - strlen(sdev->rds_info.ps_name) + 1); - if (rval) - rval = -EFAULT; - break; - - case V4L2_CID_RDS_TX_RADIO_TEXT: - if (strlen(sdev->rds_info.radio_text) + 1 > control->size) { - control->size = MAX_RDS_RADIO_TEXT + 1; - rval = -ENOSPC; - goto exit; - } - rval = copy_to_user(control->string, sdev->rds_info.radio_text, - strlen(sdev->rds_info.radio_text) + 1); - if (rval) - rval = -EFAULT; - break; - - default: - rval = -EINVAL; - break; - }; - -exit: - return rval; -} - -/* - * si4713_update_tune_status - update properties from tx_tune_status - * command. Must be called with sdev->mutex held. - * @sdev: si4713_device structure for the device we are communicating - */ -static int si4713_update_tune_status(struct si4713_device *sdev) -{ - int rval; - u16 f = 0; - u8 p = 0, a = 0, n = 0; - - rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n); - - if (rval < 0) - goto exit; - - sdev->power_level = p; - sdev->antenna_capacitor = a; - sdev->tune_rnl = n; - -exit: - return rval; -} - -/* properties which use tx_tune_status */ -static int si4713_read_econtrol_tune(struct si4713_device *sdev, - struct v4l2_ext_control *control) -{ - s32 rval = 0; - - mutex_lock(&sdev->mutex); - - if (sdev->power_state) { - rval = si4713_update_tune_status(sdev); - if (rval < 0) - goto unlock; - } - - switch (control->id) { - case V4L2_CID_TUNE_POWER_LEVEL: - control->value = sdev->power_level; - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - control->value = sdev->antenna_capacitor; - break; - default: - rval = -EINVAL; - }; - -unlock: - mutex_unlock(&sdev->mutex); - return rval; + return 0; } -static int si4713_read_econtrol_integers(struct si4713_device *sdev, - struct v4l2_ext_control *control) +/* si4713_s_ctrl - set the value of a control */ +static int si4713_s_ctrl(struct v4l2_ctrl *ctrl) { - s32 rval; - u32 *shadow = NULL, val = 0; + struct si4713_device *sdev = + container_of(ctrl->handler, struct si4713_device, ctrl_handler); + u32 val = 0; s32 bit = 0, mask = 0; u16 property = 0; int mul = 0; unsigned long *table = NULL; int size = 0; + bool force = false; + int c; + int ret = 0; - rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit, - &mask, &property, &mul, &table, &size); - if (rval < 0) - goto exit; - - mutex_lock(&sdev->mutex); - - if (sdev->power_state) { - rval = si4713_read_property(sdev, property, &val); - if (rval < 0) - goto unlock; - - /* Keep negative values for threshold */ - if (control->id == V4L2_CID_AUDIO_COMPRESSION_THRESHOLD) - *shadow = (s16)val; - else if (mask) - *shadow = get_status_bit(val, bit, mask); - else if (mul) - *shadow = val * mul; - else - *shadow = dev_to_usecs(val, table, size); - } - - control->value = *shadow; - -unlock: - mutex_unlock(&sdev->mutex); -exit: - return rval; -} - -/* - * Video4Linux Subdev Interface - */ -/* si4713_s_ext_ctrls - set extended controls value */ -static int si4713_s_ext_ctrls(struct v4l2_subdev *sd, - struct v4l2_ext_controls *ctrls) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int i; - - if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) + if (ctrl->id != V4L2_CID_AUDIO_MUTE) return -EINVAL; - - for (i = 0; i < ctrls->count; i++) { - int err; - - switch ((ctrls->controls + i)->id) { - case V4L2_CID_RDS_TX_PS_NAME: - case V4L2_CID_RDS_TX_RADIO_TEXT: - err = si4713_write_econtrol_string(sdev, - ctrls->controls + i); - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - case V4L2_CID_TUNE_POWER_LEVEL: - err = si4713_write_econtrol_tune(sdev, - ctrls->controls + i); - break; - default: - err = si4713_write_econtrol_integers(sdev, - ctrls->controls + i); - } - - if (err < 0) { - ctrls->error_idx = i; - return err; + if (ctrl->is_new) { + if (ctrl->val) { + ret = si4713_set_mute(sdev, ctrl->val); + if (!ret) + ret = si4713_set_power_state(sdev, POWER_DOWN); + return ret; } + ret = si4713_set_power_state(sdev, POWER_UP); + if (!ret) + ret = si4713_set_mute(sdev, ctrl->val); + if (!ret) + ret = si4713_setup(sdev); + if (ret) + return ret; + force = true; } - return 0; -} - -/* si4713_g_ext_ctrls - get extended controls value */ -static int si4713_g_ext_ctrls(struct v4l2_subdev *sd, - struct v4l2_ext_controls *ctrls) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int i; + if (!sdev->power_state) + return 0; - if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) - return -EINVAL; + for (c = 1; !ret && c < ctrl->ncontrols; c++) { + ctrl = ctrl->cluster[c]; - for (i = 0; i < ctrls->count; i++) { - int err; + if (!force && !ctrl->is_new) + continue; - switch ((ctrls->controls + i)->id) { + switch (ctrl->id) { case V4L2_CID_RDS_TX_PS_NAME: + ret = si4713_set_rds_ps_name(sdev, ctrl->string); + break; + case V4L2_CID_RDS_TX_RADIO_TEXT: - err = si4713_read_econtrol_string(sdev, - ctrls->controls + i); + ret = si4713_set_rds_radio_text(sdev, ctrl->string); break; + case V4L2_CID_TUNE_ANTENNA_CAPACITOR: + /* don't handle this control if we force setting all + * controls since in that case it will be handled by + * V4L2_CID_TUNE_POWER_LEVEL. */ + if (force) + break; + /* fall through */ case V4L2_CID_TUNE_POWER_LEVEL: - err = si4713_read_econtrol_tune(sdev, - ctrls->controls + i); + ret = si4713_tx_tune_power(sdev, + sdev->tune_pwr_level->val, sdev->tune_ant_cap->val); + if (!ret) { + /* Make sure we don't set this twice */ + sdev->tune_ant_cap->is_new = false; + sdev->tune_pwr_level->is_new = false; + } break; - default: - err = si4713_read_econtrol_integers(sdev, - ctrls->controls + i); - } - - if (err < 0) { - ctrls->error_idx = i; - return err; - } - } - - return 0; -} - -/* si4713_queryctrl - enumerate control items */ -static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - int rval = 0; - switch (qc->id) { - /* User class controls */ - case V4L2_CID_AUDIO_MUTE: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, DEFAULT_MUTE); - break; - /* FM_TX class controls */ - case V4L2_CID_RDS_TX_PI: - rval = v4l2_ctrl_query_fill(qc, 0, 0xFFFF, 1, DEFAULT_RDS_PI); - break; - case V4L2_CID_RDS_TX_PTY: - rval = v4l2_ctrl_query_fill(qc, 0, 31, 1, DEFAULT_RDS_PTY); - break; - case V4L2_CID_RDS_TX_DEVIATION: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_DEVIATION, - 10, DEFAULT_RDS_DEVIATION); - break; - case V4L2_CID_RDS_TX_PS_NAME: - /* - * Report step as 8. From RDS spec, psname - * should be 8. But there are receivers which scroll strings - * sized as 8xN. - */ - rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_PS_NAME, 8, 0); - break; - case V4L2_CID_RDS_TX_RADIO_TEXT: - /* - * Report step as 32 (2A block). From RDS spec, - * radio text should be 32 for 2A block. But there are receivers - * which scroll strings sized as 32xN. Setting default to 32. - */ - rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_RADIO_TEXT, 32, 0); - break; - - case V4L2_CID_AUDIO_LIMITER_ENABLED: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - break; - case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME: - rval = v4l2_ctrl_query_fill(qc, 250, MAX_LIMITER_RELEASE_TIME, - 50, DEFAULT_LIMITER_RTIME); - break; - case V4L2_CID_AUDIO_LIMITER_DEVIATION: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_LIMITER_DEVIATION, - 10, DEFAULT_LIMITER_DEV); - break; - - case V4L2_CID_AUDIO_COMPRESSION_ENABLED: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - break; - case V4L2_CID_AUDIO_COMPRESSION_GAIN: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_GAIN, 1, - DEFAULT_ACOMP_GAIN); - break; - case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD: - rval = v4l2_ctrl_query_fill(qc, MIN_ACOMP_THRESHOLD, - MAX_ACOMP_THRESHOLD, 1, - DEFAULT_ACOMP_THRESHOLD); - break; - case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_ATTACK_TIME, - 500, DEFAULT_ACOMP_ATIME); - break; - case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME: - rval = v4l2_ctrl_query_fill(qc, 100000, MAX_ACOMP_RELEASE_TIME, - 100000, DEFAULT_ACOMP_RTIME); - break; - - case V4L2_CID_PILOT_TONE_ENABLED: - rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); - break; - case V4L2_CID_PILOT_TONE_DEVIATION: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_DEVIATION, - 10, DEFAULT_PILOT_DEVIATION); - break; - case V4L2_CID_PILOT_TONE_FREQUENCY: - rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_FREQUENCY, - 1, DEFAULT_PILOT_FREQUENCY); - break; - - case V4L2_CID_TUNE_PREEMPHASIS: - rval = v4l2_ctrl_query_fill(qc, V4L2_PREEMPHASIS_DISABLED, - V4L2_PREEMPHASIS_75_uS, 1, - V4L2_PREEMPHASIS_50_uS); - break; - case V4L2_CID_TUNE_POWER_LEVEL: - rval = v4l2_ctrl_query_fill(qc, 0, 120, 1, DEFAULT_POWER_LEVEL); - break; - case V4L2_CID_TUNE_ANTENNA_CAPACITOR: - rval = v4l2_ctrl_query_fill(qc, 0, 191, 1, 0); - break; - default: - rval = -EINVAL; - break; - }; - - return rval; -} - -/* si4713_g_ctrl - get the value of a control */ -static int si4713_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int rval = 0; - - if (!sdev) - return -ENODEV; - - mutex_lock(&sdev->mutex); - - if (sdev->power_state) { - rval = si4713_read_property(sdev, SI4713_TX_LINE_INPUT_MUTE, - &sdev->mute); - - if (rval < 0) - goto unlock; - } - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - ctrl->value = get_mute(sdev->mute); - break; - } - -unlock: - mutex_unlock(&sdev->mutex); - return rval; -} - -/* si4713_s_ctrl - set the value of a control */ -static int si4713_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct si4713_device *sdev = to_si4713_device(sd); - int rval = 0; - - if (!sdev) - return -ENODEV; - - switch (ctrl->id) { - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value) { - rval = si4713_set_mute(sdev, ctrl->value); - if (rval < 0) - goto exit; - - rval = si4713_set_power_state(sdev, POWER_DOWN); - } else { - rval = si4713_set_power_state(sdev, POWER_UP); - if (rval < 0) - goto exit; + default: + ret = si4713_choose_econtrol_action(sdev, ctrl->id, &bit, + &mask, &property, &mul, &table, &size); + if (ret < 0) + break; + + val = ctrl->val; + if (mul) { + val = val / mul; + } else if (table) { + ret = usecs_to_dev(val, table, size); + if (ret < 0) + break; + val = ret; + ret = 0; + } - rval = si4713_setup(sdev); - if (rval < 0) - goto exit; + if (mask) { + ret = si4713_read_property(sdev, property, &val); + if (ret < 0) + break; + val = set_bits(val, ctrl->val, bit, mask); + } - rval = si4713_set_mute(sdev, ctrl->value); + ret = si4713_write_property(sdev, property, val); + if (ret < 0) + break; + if (mask) + val = ctrl->val; + break; } - break; } -exit: - return rval; + return ret; } /* si4713_ioctl - deal with private ioctls (only rnl for now) */ -long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { struct si4713_device *sdev = to_si4713_device(sd); struct si4713_rnl *rnl = arg; @@ -1779,7 +1169,6 @@ long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) if (!arg) return -EINVAL; - mutex_lock(&sdev->mutex); switch (cmd) { case SI4713_IOC_MEASURE_RNL: frequency = v4l2_to_si4713(rnl->frequency); @@ -1788,11 +1177,11 @@ long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) /* Set desired measurement frequency */ rval = si4713_tx_tune_measure(sdev, frequency, 0); if (rval < 0) - goto unlock; + return rval; /* get results from tune status */ rval = si4713_update_tune_status(sdev); if (rval < 0) - goto unlock; + return rval; } rnl->rnl = sdev->tune_rnl; break; @@ -1802,35 +1191,20 @@ long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) rval = -ENOIOCTLCMD; } -unlock: - mutex_unlock(&sdev->mutex); return rval; } -static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = { - .queryctrl = si4713_queryctrl, - .g_ext_ctrls = si4713_g_ext_ctrls, - .s_ext_ctrls = si4713_s_ext_ctrls, - .g_ctrl = si4713_g_ctrl, - .s_ctrl = si4713_s_ctrl, - .ioctl = si4713_ioctl, -}; - /* si4713_g_modulator - get modulator attributes */ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) { struct si4713_device *sdev = to_si4713_device(sd); int rval = 0; - if (!sdev) { - rval = -ENODEV; - goto exit; - } + if (!sdev) + return -ENODEV; - if (vm->index > 0) { - rval = -EINVAL; - goto exit; - } + if (vm->index > 0) + return -EINVAL; strncpy(vm->name, "FM Modulator", 32); vm->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW | @@ -1840,18 +1214,15 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) vm->rangelow = si4713_to_v4l2(FREQ_RANGE_LOW); vm->rangehigh = si4713_to_v4l2(FREQ_RANGE_HIGH); - mutex_lock(&sdev->mutex); - if (sdev->power_state) { u32 comp_en = 0; rval = si4713_read_property(sdev, SI4713_TX_COMPONENT_ENABLE, &comp_en); if (rval < 0) - goto unlock; + return rval; sdev->stereo = get_status_bit(comp_en, 1, 1 << 1); - sdev->rds_info.enabled = get_status_bit(comp_en, 2, 1 << 2); } /* Report current audio mode: mono or stereo */ @@ -1861,19 +1232,16 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) vm->txsubchans = V4L2_TUNER_SUB_MONO; /* Report rds feature status */ - if (sdev->rds_info.enabled) + if (sdev->rds_enabled) vm->txsubchans |= V4L2_TUNER_SUB_RDS; else vm->txsubchans &= ~V4L2_TUNER_SUB_RDS; -unlock: - mutex_unlock(&sdev->mutex); -exit: return rval; } /* si4713_s_modulator - set modulator attributes */ -static int si4713_s_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) +static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulator *vm) { struct si4713_device *sdev = to_si4713_device(sd); int rval = 0; @@ -1896,13 +1264,11 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) rds = !!(vm->txsubchans & V4L2_TUNER_SUB_RDS); - mutex_lock(&sdev->mutex); - if (sdev->power_state) { rval = si4713_read_property(sdev, SI4713_TX_COMPONENT_ENABLE, &p); if (rval < 0) - goto unlock; + return rval; p = set_bits(p, stereo, 1, 1 << 1); p = set_bits(p, rds, 2, 1 << 2); @@ -1910,14 +1276,12 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm) rval = si4713_write_property(sdev, SI4713_TX_COMPONENT_ENABLE, p); if (rval < 0) - goto unlock; + return rval; } sdev->stereo = stereo; - sdev->rds_info.enabled = rds; + sdev->rds_enabled = rds; -unlock: - mutex_unlock(&sdev->mutex); return rval; } @@ -1927,9 +1291,8 @@ static int si4713_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) struct si4713_device *sdev = to_si4713_device(sd); int rval = 0; - f->type = V4L2_TUNER_RADIO; - - mutex_lock(&sdev->mutex); + if (f->tuner) + return -EINVAL; if (sdev->power_state) { u16 freq; @@ -1937,46 +1300,49 @@ static int si4713_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) rval = si4713_tx_tune_status(sdev, 0x00, &freq, &p, &a, &n); if (rval < 0) - goto unlock; + return rval; sdev->frequency = freq; } f->frequency = si4713_to_v4l2(sdev->frequency); -unlock: - mutex_unlock(&sdev->mutex); return rval; } /* si4713_s_frequency - set tuner or modulator radio frequency */ -static int si4713_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) +static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f) { struct si4713_device *sdev = to_si4713_device(sd); int rval = 0; u16 frequency = v4l2_to_si4713(f->frequency); - /* Check frequency range */ - if (frequency < FREQ_RANGE_LOW || frequency > FREQ_RANGE_HIGH) - return -EDOM; + if (f->tuner) + return -EINVAL; - mutex_lock(&sdev->mutex); + /* Check frequency range */ + frequency = clamp_t(u16, frequency, FREQ_RANGE_LOW, FREQ_RANGE_HIGH); if (sdev->power_state) { rval = si4713_tx_tune_freq(sdev, frequency); if (rval < 0) - goto unlock; + return rval; frequency = rval; rval = 0; } sdev->frequency = frequency; - f->frequency = si4713_to_v4l2(frequency); -unlock: - mutex_unlock(&sdev->mutex); return rval; } +static const struct v4l2_ctrl_ops si4713_ctrl_ops = { + .s_ctrl = si4713_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = { + .ioctl = si4713_ioctl, +}; + static const struct v4l2_subdev_tuner_ops si4713_subdev_tuner_ops = { .g_frequency = si4713_g_frequency, .s_frequency = si4713_s_frequency, @@ -1998,9 +1364,10 @@ static int si4713_probe(struct i2c_client *client, { struct si4713_device *sdev; struct si4713_platform_data *pdata = client->dev.platform_data; + struct v4l2_ctrl_handler *hdl; int rval, i; - sdev = kzalloc(sizeof *sdev, GFP_KERNEL); + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); if (!sdev) { dev_err(&client->dev, "Failed to alloc video device.\n"); rval = -ENOMEM; @@ -2017,13 +1384,14 @@ static int si4713_probe(struct i2c_client *client, } sdev->gpio_reset = pdata->gpio_reset; gpio_direction_output(sdev->gpio_reset, 0); + sdev->supplies = pdata->supplies; } - for (i = 0; i < ARRAY_SIZE(sdev->supplies); i++) - sdev->supplies[i].supply = si4713_supply_names[i]; + for (i = 0; i < sdev->supplies; i++) + sdev->supply_data[i].supply = pdata->supply_names[i]; - rval = regulator_bulk_get(&client->dev, ARRAY_SIZE(sdev->supplies), - sdev->supplies); + rval = regulator_bulk_get(&client->dev, sdev->supplies, + sdev->supply_data); if (rval) { dev_err(&client->dev, "Cannot get regulators: %d\n", rval); goto free_gpio; @@ -2031,12 +1399,89 @@ static int si4713_probe(struct i2c_client *client, v4l2_i2c_subdev_init(&sdev->sd, client, &si4713_subdev_ops); - mutex_init(&sdev->mutex); init_completion(&sdev->work); + hdl = &sdev->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 20); + sdev->mute = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, DEFAULT_MUTE); + + sdev->rds_pi = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_PI, 0, 0xffff, 1, DEFAULT_RDS_PI); + sdev->rds_pty = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_PTY, 0, 31, 1, DEFAULT_RDS_PTY); + sdev->rds_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_DEVIATION, 0, MAX_RDS_DEVIATION, + 10, DEFAULT_RDS_DEVIATION); + /* + * Report step as 8. From RDS spec, psname + * should be 8. But there are receivers which scroll strings + * sized as 8xN. + */ + sdev->rds_ps_name = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_PS_NAME, 0, MAX_RDS_PS_NAME, 8, 0); + /* + * Report step as 32 (2A block). From RDS spec, + * radio text should be 32 for 2A block. But there are receivers + * which scroll strings sized as 32xN. Setting default to 32. + */ + sdev->rds_radio_text = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_RDS_TX_RADIO_TEXT, 0, MAX_RDS_RADIO_TEXT, 32, 0); + + sdev->limiter_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_LIMITER_ENABLED, 0, 1, 1, 1); + sdev->limiter_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_LIMITER_RELEASE_TIME, 250, + MAX_LIMITER_RELEASE_TIME, 10, DEFAULT_LIMITER_RTIME); + sdev->limiter_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_LIMITER_DEVIATION, 0, + MAX_LIMITER_DEVIATION, 10, DEFAULT_LIMITER_DEV); + + sdev->compression_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_ENABLED, 0, 1, 1, 1); + sdev->compression_gain = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_GAIN, 0, MAX_ACOMP_GAIN, 1, + DEFAULT_ACOMP_GAIN); + sdev->compression_threshold = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_THRESHOLD, + MIN_ACOMP_THRESHOLD, MAX_ACOMP_THRESHOLD, 1, + DEFAULT_ACOMP_THRESHOLD); + sdev->compression_attack_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME, 0, + MAX_ACOMP_ATTACK_TIME, 500, DEFAULT_ACOMP_ATIME); + sdev->compression_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME, 100000, + MAX_ACOMP_RELEASE_TIME, 100000, DEFAULT_ACOMP_RTIME); + + sdev->pilot_tone_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_PILOT_TONE_ENABLED, 0, 1, 1, 1); + sdev->pilot_tone_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_PILOT_TONE_DEVIATION, 0, MAX_PILOT_DEVIATION, + 10, DEFAULT_PILOT_DEVIATION); + sdev->pilot_tone_freq = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_PILOT_TONE_FREQUENCY, 0, MAX_PILOT_FREQUENCY, + 1, DEFAULT_PILOT_FREQUENCY); + + sdev->tune_preemphasis = v4l2_ctrl_new_std_menu(hdl, &si4713_ctrl_ops, + V4L2_CID_TUNE_PREEMPHASIS, + V4L2_PREEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_50_uS); + sdev->tune_pwr_level = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_TUNE_POWER_LEVEL, 0, SI4713_MAX_POWER, + 1, DEFAULT_POWER_LEVEL); + sdev->tune_ant_cap = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops, + V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, SI4713_MAX_ANTCAP, + 1, 0); + + if (hdl->error) { + rval = hdl->error; + goto free_ctrls; + } + v4l2_ctrl_cluster(20, &sdev->mute); + sdev->sd.ctrl_handler = hdl; + if (client->irq) { rval = request_irq(client->irq, - si4713_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, + si4713_handler, IRQF_TRIGGER_FALLING, client->name, sdev); if (rval < 0) { v4l2_err(&sdev->sd, "Could not request IRQ\n"); @@ -2058,8 +1503,10 @@ static int si4713_probe(struct i2c_client *client, free_irq: if (client->irq) free_irq(client->irq, sdev); +free_ctrls: + v4l2_ctrl_handler_free(hdl); put_reg: - regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies); + regulator_bulk_free(sdev->supplies, sdev->supply_data); free_gpio: if (gpio_is_valid(sdev->gpio_reset)) gpio_free(sdev->gpio_reset); @@ -2082,7 +1529,8 @@ static int si4713_remove(struct i2c_client *client) free_irq(client->irq, sdev); v4l2_device_unregister_subdev(sd); - regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies); + v4l2_ctrl_handler_free(sd->ctrl_handler); + regulator_bulk_free(sdev->supplies, sdev->supply_data); if (gpio_is_valid(sdev->gpio_reset)) gpio_free(sdev->gpio_reset); kfree(sdev); @@ -2106,17 +1554,4 @@ static struct i2c_driver si4713_i2c_driver = { .id_table = si4713_id, }; -/* Module Interface */ -static int __init si4713_module_init(void) -{ - return i2c_add_driver(&si4713_i2c_driver); -} - -static void __exit si4713_module_exit(void) -{ - i2c_del_driver(&si4713_i2c_driver); -} - -module_init(si4713_module_init); -module_exit(si4713_module_exit); - +module_i2c_driver(si4713_i2c_driver); diff --git a/drivers/media/radio/si4713-i2c.h b/drivers/media/radio/si4713/si4713.h index c6dfa7fb101..4837cf6e0e1 100644 --- a/drivers/media/radio/si4713-i2c.h +++ b/drivers/media/radio/si4713/si4713.h @@ -15,7 +15,9 @@ #ifndef SI4713_I2C_H #define SI4713_I2C_H +#include <linux/regulator/consumer.h> #include <media/v4l2-subdev.h> +#include <media/v4l2-ctrls.h> #include <media/si4713.h> #define SI4713_PRODUCT_NUMBER 0x0D @@ -160,56 +162,33 @@ #define POWER_UP 0x01 #define POWER_DOWN 0x00 -struct rds_info { - u32 pi; #define MAX_RDS_PTY 31 - u32 pty; #define MAX_RDS_DEVIATION 90000 - u32 deviation; + /* * PSNAME is known to be defined as 8 character sized (RDS Spec). * However, there is receivers which scroll PSNAME 8xN sized. */ #define MAX_RDS_PS_NAME 96 - u8 ps_name[MAX_RDS_PS_NAME + 1]; + /* * MAX_RDS_RADIO_TEXT is known to be defined as 32 (2A group) or 64 (2B group) * character sized (RDS Spec). * However, there is receivers which scroll them as well. */ #define MAX_RDS_RADIO_TEXT 384 - u8 radio_text[MAX_RDS_RADIO_TEXT + 1]; - u32 enabled; -}; -struct limiter_info { #define MAX_LIMITER_RELEASE_TIME 102390 - u32 release_time; #define MAX_LIMITER_DEVIATION 90000 - u32 deviation; - u32 enabled; -}; -struct pilot_info { #define MAX_PILOT_DEVIATION 90000 - u32 deviation; #define MAX_PILOT_FREQUENCY 19000 - u32 frequency; - u32 enabled; -}; -struct acomp_info { #define MAX_ACOMP_RELEASE_TIME 1000000 - u32 release_time; #define MAX_ACOMP_ATTACK_TIME 5000 - u32 attack_time; #define MAX_ACOMP_THRESHOLD 0 #define MIN_ACOMP_THRESHOLD (-40) - s32 threshold; #define MAX_ACOMP_GAIN 20 - u32 gain; - u32 enabled; -}; #define SI4713_NUM_SUPPLIES 2 @@ -219,21 +198,42 @@ struct acomp_info { struct si4713_device { /* v4l2_subdev and i2c reference (v4l2_subdev priv data) */ struct v4l2_subdev sd; + struct v4l2_ctrl_handler ctrl_handler; /* private data structures */ - struct mutex mutex; + struct { /* si4713 control cluster */ + /* This is one big cluster since the mute control + * powers off the device and after unmuting again all + * controls need to be set at once. The only way of doing + * that is by making it one big cluster. */ + struct v4l2_ctrl *mute; + struct v4l2_ctrl *rds_ps_name; + struct v4l2_ctrl *rds_radio_text; + struct v4l2_ctrl *rds_pi; + struct v4l2_ctrl *rds_deviation; + struct v4l2_ctrl *rds_pty; + struct v4l2_ctrl *compression_enabled; + struct v4l2_ctrl *compression_threshold; + struct v4l2_ctrl *compression_gain; + struct v4l2_ctrl *compression_attack_time; + struct v4l2_ctrl *compression_release_time; + struct v4l2_ctrl *pilot_tone_enabled; + struct v4l2_ctrl *pilot_tone_freq; + struct v4l2_ctrl *pilot_tone_deviation; + struct v4l2_ctrl *limiter_enabled; + struct v4l2_ctrl *limiter_deviation; + struct v4l2_ctrl *limiter_release_time; + struct v4l2_ctrl *tune_preemphasis; + struct v4l2_ctrl *tune_pwr_level; + struct v4l2_ctrl *tune_ant_cap; + }; struct completion work; - struct rds_info rds_info; - struct limiter_info limiter_info; - struct pilot_info pilot_info; - struct acomp_info acomp_info; - struct regulator_bulk_data supplies[SI4713_NUM_SUPPLIES]; + unsigned supplies; + struct regulator_bulk_data supply_data[SI4713_NUM_SUPPLIES]; int gpio_reset; + u32 power_state; + u32 rds_enabled; u32 frequency; u32 preemphasis; - u32 mute; - u32 power_level; - u32 power_state; - u32 antenna_capacitor; u32 stereo; u32 tune_rnl; }; diff --git a/drivers/media/radio/tea575x.c b/drivers/media/radio/tea575x.c new file mode 100644 index 00000000000..7c14060a40b --- /dev/null +++ b/drivers/media/radio/tea575x.c @@ -0,0 +1,584 @@ +/* + * 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/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <asm/io.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 <media/tea575x.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips"); +MODULE_LICENSE("GPL"); + +/* + * definitions + */ + +#define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */ +#define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */ +#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 TEA575X_BIT_BAND_MW (1<<20) +#define TEA575X_BIT_BAND_LW (2<<20) +#define TEA575X_BIT_BAND_SW (3<<20) +#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */ +#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */ +#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */ +#define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */ +#define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */ +#define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */ +#define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */ +#define TEA575X_BIT_DUMMY (1<<15) /* buffer */ +#define TEA575X_BIT_FREQ_MASK 0x7fff + +enum { BAND_FM, BAND_FM_JAPAN, BAND_AM }; + +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_FREQ_BANDS, + .rangelow = 87500 * 16, + .rangehigh = 108000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 0, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 76000 * 16, + .rangehigh = 91000 * 16, + .modulation = V4L2_BAND_MODULATION_FM, + }, + { + .type = V4L2_TUNER_RADIO, + .index = 1, + .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, + .rangelow = 530 * 16, + .rangehigh = 1710 * 16, + .modulation = V4L2_BAND_MODULATION_AM, + }, +}; + +/* + * lowlevel part + */ + +static void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val) +{ + u16 l; + u8 data; + + if (tea->ops->write_val) + return tea->ops->write_val(tea, val); + + tea->ops->set_direction(tea, 1); + udelay(16); + + for (l = 25; l > 0; l--) { + data = (val >> 24) & TEA575X_DATA; + val <<= 1; /* shift data */ + tea->ops->set_pins(tea, data | TEA575X_WREN); + udelay(2); + tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK); + udelay(2); + tea->ops->set_pins(tea, data | TEA575X_WREN); + udelay(2); + } + + if (!tea->mute) + tea->ops->set_pins(tea, 0); +} + +static u32 snd_tea575x_read(struct snd_tea575x *tea) +{ + u16 l, rdata; + u32 data = 0; + + if (tea->ops->read_val) + return tea->ops->read_val(tea); + + tea->ops->set_direction(tea, 0); + tea->ops->set_pins(tea, 0); + udelay(16); + + for (l = 24; l--;) { + tea->ops->set_pins(tea, TEA575X_CLK); + udelay(2); + if (!l) + tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1; + tea->ops->set_pins(tea, 0); + udelay(2); + data <<= 1; /* shift data */ + rdata = tea->ops->get_pins(tea); + if (!l) + tea->stereo = (rdata & TEA575X_MOST) ? 0 : 1; + if (rdata & TEA575X_DATA) + data++; + udelay(2); + } + + if (tea->mute) + tea->ops->set_pins(tea, TEA575X_WREN); + + return data; +} + +static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val) +{ + u32 freq = val & TEA575X_BIT_FREQ_MASK; + + if (freq == 0) + return freq; + + switch (tea->band) { + case BAND_FM: + /* freq *= 12.5 */ + freq *= 125; + freq /= 10; + /* crystal fixup */ + freq -= TEA575X_FMIF; + break; + case BAND_FM_JAPAN: + /* freq *= 12.5 */ + freq *= 125; + freq /= 10; + /* crystal fixup */ + freq += TEA575X_FMIF; + break; + case BAND_AM: + /* crystal fixup */ + freq -= TEA575X_AMIF; + break; + } + + return clamp(freq * 16, bands[tea->band].rangelow, + bands[tea->band].rangehigh); /* from kHz */ +} + +static u32 snd_tea575x_get_freq(struct snd_tea575x *tea) +{ + return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea)); +} + +void snd_tea575x_set_freq(struct snd_tea575x *tea) +{ + u32 freq = tea->freq / 16; /* to kHz */ + u32 band = 0; + + switch (tea->band) { + case BAND_FM: + band = TEA575X_BIT_BAND_FM; + /* crystal fixup */ + freq += TEA575X_FMIF; + /* freq /= 12.5 */ + freq *= 10; + freq /= 125; + break; + case BAND_FM_JAPAN: + band = TEA575X_BIT_BAND_FM; + /* crystal fixup */ + freq -= TEA575X_FMIF; + /* freq /= 12.5 */ + freq *= 10; + freq /= 125; + break; + case BAND_AM: + band = TEA575X_BIT_BAND_MW; + /* crystal fixup */ + freq += TEA575X_AMIF; + break; + } + + tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK); + tea->val |= band; + tea->val |= freq & TEA575X_BIT_FREQ_MASK; + snd_tea575x_write(tea, tea->val); + tea->freq = snd_tea575x_val_to_freq(tea, tea->val); +} + +/* + * Linux Video interface + */ + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct snd_tea575x *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, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card)); + strlcpy(v->bus_info, tea->bus_info, sizeof(v->bus_info)); + v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + if (!tea->cannot_read_data) + v->device_caps |= V4L2_CAP_HW_FREQ_SEEK; + v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static int vidioc_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct snd_tea575x *tea = video_drvdata(file); + int index; + + if (band->tuner != 0) + return -EINVAL; + + switch (band->index) { + case 0: + if (tea->tea5759) + index = BAND_FM_JAPAN; + else + index = BAND_FM; + break; + case 1: + if (tea->has_am) { + index = BAND_AM; + break; + } + /* Fall through */ + default: + return -EINVAL; + } + + *band = bands[index]; + if (!tea->cannot_read_data) + band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED; + + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + struct snd_tea575x *tea = video_drvdata(file); + struct v4l2_frequency_band band_fm = { 0, }; + + if (v->index > 0) + return -EINVAL; + + snd_tea575x_read(tea); + vidioc_enum_freq_bands(file, priv, &band_fm); + + memset(v, 0, sizeof(*v)); + strlcpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name)); + v->type = V4L2_TUNER_RADIO; + v->capability = band_fm.capability; + v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow; + v->rangehigh = band_fm.rangehigh; + v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; + v->audmode = (tea->val & TEA575X_BIT_MONO) ? + V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; + v->signal = tea->tuned ? 0xffff : 0; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *v) +{ + struct snd_tea575x *tea = video_drvdata(file); + u32 orig_val = tea->val; + + if (v->index) + return -EINVAL; + tea->val &= ~TEA575X_BIT_MONO; + if (v->audmode == V4L2_TUNER_MODE_MONO) + tea->val |= TEA575X_BIT_MONO; + /* Only apply changes if currently tuning FM */ + if (tea->band != BAND_AM && tea->val != orig_val) + snd_tea575x_set_freq(tea); + + return 0; +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct snd_tea575x *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, + const struct v4l2_frequency *f) +{ + struct snd_tea575x *tea = video_drvdata(file); + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + if (tea->has_am && f->frequency < (20000 * 16)) + tea->band = BAND_AM; + else if (tea->tea5759) + tea->band = BAND_FM_JAPAN; + else + tea->band = BAND_FM; + + tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow, + bands[tea->band].rangehigh); + snd_tea575x_set_freq(tea); + return 0; +} + +static int vidioc_s_hw_freq_seek(struct file *file, void *fh, + const struct v4l2_hw_freq_seek *a) +{ + struct snd_tea575x *tea = video_drvdata(file); + unsigned long timeout; + int i, spacing; + + if (tea->cannot_read_data) + return -ENOTTY; + if (a->tuner || a->wrap_around) + return -EINVAL; + + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + + if (a->rangelow || a->rangehigh) { + for (i = 0; i < ARRAY_SIZE(bands); i++) { + if ((i == BAND_FM && tea->tea5759) || + (i == BAND_FM_JAPAN && !tea->tea5759) || + (i == BAND_AM && !tea->has_am)) + continue; + if (bands[i].rangelow == a->rangelow && + bands[i].rangehigh == a->rangehigh) + break; + } + if (i == ARRAY_SIZE(bands)) + return -EINVAL; /* No matching band found */ + if (i != tea->band) { + tea->band = i; + tea->freq = clamp(tea->freq, bands[i].rangelow, + bands[i].rangehigh); + snd_tea575x_set_freq(tea); + } + } + + spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */ + + /* clear the frequency, HW will fill it in */ + tea->val &= ~TEA575X_BIT_FREQ_MASK; + tea->val |= TEA575X_BIT_SEARCH; + if (a->seek_upward) + tea->val |= TEA575X_BIT_UPDOWN; + else + tea->val &= ~TEA575X_BIT_UPDOWN; + snd_tea575x_write(tea, tea->val); + timeout = jiffies + msecs_to_jiffies(10000); + for (;;) { + if (time_after(jiffies, timeout)) + break; + if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { + /* some signal arrived, stop search */ + tea->val &= ~TEA575X_BIT_SEARCH; + snd_tea575x_set_freq(tea); + return -ERESTARTSYS; + } + if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) { + u32 freq; + + /* Found a frequency, wait until it can be read */ + for (i = 0; i < 100; i++) { + msleep(10); + freq = snd_tea575x_get_freq(tea); + if (freq) /* available */ + break; + } + if (freq == 0) /* shouldn't happen */ + break; + /* + * if we moved by less than the spacing, or in the + * wrong direction, continue seeking + */ + if (abs(tea->freq - freq) < 16 * spacing || + (a->seek_upward && freq < tea->freq) || + (!a->seek_upward && freq > tea->freq)) { + snd_tea575x_write(tea, tea->val); + continue; + } + tea->freq = freq; + tea->val &= ~TEA575X_BIT_SEARCH; + return 0; + } + } + tea->val &= ~TEA575X_BIT_SEARCH; + snd_tea575x_set_freq(tea); + return -ENODATA; +} + +static int tea575x_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + tea->mute = ctrl->val; + snd_tea575x_set_freq(tea); + return 0; + } + + 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_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 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 snd_tea575x_hw_init(struct snd_tea575x *tea) +{ + tea->mute = true; + + /* Not all devices can or know how to read the data back. + Such devices can set cannot_read_data to true. */ + if (!tea->cannot_read_data) { + snd_tea575x_write(tea, 0x55AA); + if (snd_tea575x_read(tea) != 0x55AA) + return -ENODEV; + } + + tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28; + tea->freq = 90500 * 16; /* 90.5Mhz default */ + snd_tea575x_set_freq(tea); + + return 0; +} +EXPORT_SYMBOL(snd_tea575x_hw_init); + +int snd_tea575x_init(struct snd_tea575x *tea, struct module *owner) +{ + int retval = snd_tea575x_hw_init(tea); + + if (retval) + return retval; + + 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); + /* disable hw_freq_seek if we can't use it */ + if (tea->cannot_read_data) + v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK); + + if (!tea->cannot_mute) { + 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); + retval = tea->ctrl_handler.error; + if (retval) { + v4l2_err(tea->v4l2_dev, "can't initialize controls\n"); + v4l2_ctrl_handler_free(&tea->ctrl_handler); + return retval; + } + + if (tea->ext_init) { + retval = tea->ext_init(tea); + if (retval) { + v4l2_ctrl_handler_free(&tea->ctrl_handler); + return retval; + } + } + + v4l2_ctrl_handler_setup(&tea->ctrl_handler); + } + + retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr); + if (retval) { + v4l2_err(tea->v4l2_dev, "can't register video device!\n"); + v4l2_ctrl_handler_free(tea->vd.ctrl_handler); + return retval; + } + + return 0; +} + +void snd_tea575x_exit(struct snd_tea575x *tea) +{ + video_unregister_device(&tea->vd); + v4l2_ctrl_handler_free(tea->vd.ctrl_handler); +} + +static int __init alsa_tea575x_module_init(void) +{ + return 0; +} + +static void __exit alsa_tea575x_module_exit(void) +{ +} + +module_init(alsa_tea575x_module_init) +module_exit(alsa_tea575x_module_exit) + +EXPORT_SYMBOL(snd_tea575x_init); +EXPORT_SYMBOL(snd_tea575x_exit); +EXPORT_SYMBOL(snd_tea575x_set_freq); diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c index 3408685b690..a9319a24c7e 100644 --- a/drivers/media/radio/tef6862.c +++ b/drivers/media/radio/tef6862.c @@ -25,14 +25,13 @@ #include <linux/slab.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-device.h> -#include <media/v4l2-chip-ident.h> #define DRIVER_NAME "tef6862" #define FREQ_MUL 16000 -#define TEF6862_LO_FREQ (875 * FREQ_MUL / 10) -#define TEF6862_HI_FREQ (108 * FREQ_MUL) +#define TEF6862_LO_FREQ (875U * FREQ_MUL / 10) +#define TEF6862_HI_FREQ (108U * FREQ_MUL) /* Write mode sub addresses */ #define WM_SUB_BANDWIDTH 0x0 @@ -49,15 +48,15 @@ #define WM_SUB_TEST 0xF /* Different modes of the MSA register */ -#define MODE_BUFFER 0x0 -#define MODE_PRESET 0x1 -#define MODE_SEARCH 0x2 -#define MODE_AF_UPDATE 0x3 -#define MODE_JUMP 0x4 -#define MODE_CHECK 0x5 -#define MODE_LOAD 0x6 -#define MODE_END 0x7 -#define MODE_SHIFT 5 +#define MSA_MODE_BUFFER 0x0 +#define MSA_MODE_PRESET 0x1 +#define MSA_MODE_SEARCH 0x2 +#define MSA_MODE_AF_UPDATE 0x3 +#define MSA_MODE_JUMP 0x4 +#define MSA_MODE_CHECK 0x5 +#define MSA_MODE_LOAD 0x6 +#define MSA_MODE_END 0x7 +#define MSA_MODE_SHIFT 5 struct tef6862_state { struct v4l2_subdev sd; @@ -96,15 +95,16 @@ static int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) return 0; } -static int tef6862_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) +static int tef6862_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v) { return v->index ? -EINVAL : 0; } -static int tef6862_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) +static int tef6862_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f) { struct tef6862_state *state = to_state(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); + unsigned freq = f->frequency; u16 pll; u8 i2cmsg[3]; int err; @@ -112,8 +112,9 @@ static int tef6862_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) if (f->tuner != 0) return -EINVAL; - pll = 1964 + ((f->frequency - TEF6862_LO_FREQ) * 20) / FREQ_MUL; - i2cmsg[0] = (MODE_PRESET << MODE_SHIFT) | WM_SUB_PLLM; + freq = clamp(freq, TEF6862_LO_FREQ, TEF6862_HI_FREQ); + pll = 1964 + ((freq - TEF6862_LO_FREQ) * 20) / FREQ_MUL; + i2cmsg[0] = (MSA_MODE_PRESET << MSA_MODE_SHIFT) | WM_SUB_PLLM; i2cmsg[1] = (pll >> 8) & 0xff; i2cmsg[2] = pll & 0xff; @@ -121,7 +122,7 @@ static int tef6862_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) if (err != sizeof(i2cmsg)) return err < 0 ? err : -EIO; - state->freq = f->frequency; + state->freq = freq; return 0; } @@ -136,14 +137,6 @@ static int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) return 0; } -static int tef6862_g_chip_ident(struct v4l2_subdev *sd, - struct v4l2_dbg_chip_ident *chip) -{ - struct i2c_client *client = v4l2_get_subdevdata(sd); - - return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_TEF6862, 0); -} - static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = { .g_tuner = tef6862_g_tuner, .s_tuner = tef6862_s_tuner, @@ -151,12 +144,7 @@ static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = { .g_frequency = tef6862_g_frequency, }; -static const struct v4l2_subdev_core_ops tef6862_core_ops = { - .g_chip_ident = tef6862_g_chip_ident, -}; - static const struct v4l2_subdev_ops tef6862_ops = { - .core = &tef6862_core_ops, .tuner = &tef6862_tuner_ops, }; @@ -165,8 +153,8 @@ static const struct v4l2_subdev_ops tef6862_ops = { * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ -static int __devinit tef6862_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tef6862_probe(struct i2c_client *client, + const struct i2c_device_id *id) { struct tef6862_state *state; struct v4l2_subdev *sd; @@ -189,7 +177,7 @@ static int __devinit tef6862_probe(struct i2c_client *client, return 0; } -static int __devexit tef6862_remove(struct i2c_client *client) +static int tef6862_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); @@ -215,20 +203,8 @@ static struct i2c_driver tef6862_driver = { .id_table = tef6862_id, }; -static __init int tef6862_init(void) -{ - return i2c_add_driver(&tef6862_driver); -} - -static __exit void tef6862_exit(void) -{ - i2c_del_driver(&tef6862_driver); -} - -module_init(tef6862_init); -module_exit(tef6862_exit); +module_i2c_driver(tef6862_driver); MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner"); MODULE_AUTHOR("Mocean Laboratories"); MODULE_LICENSE("GPL v2"); - diff --git a/drivers/media/radio/wl128x/Kconfig b/drivers/media/radio/wl128x/Kconfig index 86b28579f0c..f359be7e9dd 100644 --- a/drivers/media/radio/wl128x/Kconfig +++ b/drivers/media/radio/wl128x/Kconfig @@ -4,8 +4,8 @@ menu "Texas Instruments WL128x FM driver (ST based)" config RADIO_WL128X tristate "Texas Instruments WL128x FM Radio" - depends on VIDEO_V4L2 && RFKILL - select TI_ST if NET && GPIOLIB + depends on VIDEO_V4L2 && RFKILL && GPIOLIB && TTY + select TI_ST if NET help Choose Y here if you have this FM radio chip. diff --git a/drivers/media/radio/wl128x/fmdrv.h b/drivers/media/radio/wl128x/fmdrv.h index d84ad9dad32..a587c9bac93 100644 --- a/drivers/media/radio/wl128x/fmdrv.h +++ b/drivers/media/radio/wl128x/fmdrv.h @@ -30,6 +30,7 @@ #include <linux/timer.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-common.h> +#include <media/v4l2-device.h> #include <media/v4l2-ctrls.h> #define FM_DRV_VERSION "0.1.1" @@ -60,7 +61,7 @@ #define fmdbg(format, ...) \ printk(KERN_DEBUG "fmdrv: " format, ## __VA_ARGS__) #else /* DEBUG */ -#define fmdbg(format, ...) +#define fmdbg(format, ...) do {} while(0) #endif enum { FM_MODE_OFF, @@ -202,6 +203,7 @@ struct fmtx_data { /* FM driver operation structure */ struct fmdev { struct video_device *radio_dev; /* V4L2 video device pointer */ + struct v4l2_device v4l2_dev; /* V4L2 top level struct */ struct snd_card *card; /* Card which holds FM mixer controls */ u16 asci_id; spinlock_t rds_buff_lock; /* To protect access to RDS buffer */ diff --git a/drivers/media/radio/wl128x/fmdrv_common.c b/drivers/media/radio/wl128x/fmdrv_common.c index bf867a6b5ea..4b2e9e8298e 100644 --- a/drivers/media/radio/wl128x/fmdrv_common.c +++ b/drivers/media/radio/wl128x/fmdrv_common.c @@ -175,7 +175,7 @@ static int_handler_prototype int_handler_table[] = { fm_irq_handle_intmsk_cmd_resp }; -long (*g_st_write) (struct sk_buff *skb); +static long (*g_st_write) (struct sk_buff *skb); static struct completion wait_for_fmdrv_reg_comp; static inline void fm_irq_call(struct fmdev *fmdev) @@ -715,7 +715,7 @@ static void fm_irq_handle_rdsdata_getcmd_resp(struct fmdev *fmdev) struct fm_rdsdata_format rds_fmt; struct fm_rds *rds = &fmdev->rx.rds; unsigned long group_idx, flags; - u8 *rds_data, meta_data, tmpbuf[3]; + u8 *rds_data, meta_data, tmpbuf[FM_RDS_BLK_SIZE]; u8 type, blk_idx; u16 cur_picode; u32 rds_len; @@ -742,7 +742,7 @@ static void fm_irq_handle_rdsdata_getcmd_resp(struct fmdev *fmdev) if ((meta_data & FM_RDS_STATUS_ERR_MASK) != 0) break; - if (blk_idx < FM_RDS_BLK_IDX_A || blk_idx > FM_RDS_BLK_IDX_D) { + if (blk_idx > FM_RDS_BLK_IDX_D) { fmdbg("Block sequence mismatch\n"); rds->last_blk_idx = -1; break; @@ -1073,6 +1073,7 @@ int fmc_transfer_rds_from_internal_buff(struct fmdev *fmdev, struct file *file, u8 __user *buf, size_t count) { u32 block_count; + u8 tmpbuf[FM_RDS_BLK_SIZE]; unsigned long flags; int ret; @@ -1087,29 +1088,32 @@ int fmc_transfer_rds_from_internal_buff(struct fmdev *fmdev, struct file *file, } /* Calculate block count from byte count */ - count /= 3; + count /= FM_RDS_BLK_SIZE; block_count = 0; ret = 0; - spin_lock_irqsave(&fmdev->rds_buff_lock, flags); - while (block_count < count) { - if (fmdev->rx.rds.wr_idx == fmdev->rx.rds.rd_idx) - break; + spin_lock_irqsave(&fmdev->rds_buff_lock, flags); - if (copy_to_user(buf, &fmdev->rx.rds.buff[fmdev->rx.rds.rd_idx], - FM_RDS_BLK_SIZE)) + if (fmdev->rx.rds.wr_idx == fmdev->rx.rds.rd_idx) { + spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags); break; - + } + memcpy(tmpbuf, &fmdev->rx.rds.buff[fmdev->rx.rds.rd_idx], + FM_RDS_BLK_SIZE); fmdev->rx.rds.rd_idx += FM_RDS_BLK_SIZE; if (fmdev->rx.rds.rd_idx >= fmdev->rx.rds.buf_size) fmdev->rx.rds.rd_idx = 0; + spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags); + + if (copy_to_user(buf, tmpbuf, FM_RDS_BLK_SIZE)) + break; + block_count++; buf += FM_RDS_BLK_SIZE; ret += FM_RDS_BLK_SIZE; } - spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags); return ret; } @@ -1563,8 +1567,7 @@ int fmc_prepare(struct fmdev *fmdev) fmdev->irq_info.mask = FM_MAL_EVENT; /* Region info */ - memcpy(&fmdev->rx.region, ®ion_configs[default_radio_region], - sizeof(struct region_info)); + fmdev->rx.region = region_configs[default_radio_region]; fmdev->rx.mute_mode = FM_MUTE_OFF; fmdev->rx.rf_depend_mute = FM_RX_RF_DEPENDENT_MUTE_OFF; diff --git a/drivers/media/radio/wl128x/fmdrv_rx.c b/drivers/media/radio/wl128x/fmdrv_rx.c index 43fb72291be..ebf09a3927d 100644 --- a/drivers/media/radio/wl128x/fmdrv_rx.c +++ b/drivers/media/radio/wl128x/fmdrv_rx.c @@ -251,7 +251,7 @@ again: if (!timeleft) { fmerr("Timeout(%d sec),didn't get tune ended int\n", jiffies_to_msecs(FM_DRV_RX_SEEK_TIMEOUT) / 1000); - return -ETIMEDOUT; + return -ENODATA; } int_reason = fmdev->irq_info.flag & (FM_TUNE_COMPLETE | FM_BAND_LIMIT); @@ -305,7 +305,7 @@ int fm_rx_set_volume(struct fmdev *fmdev, u16 vol_to_set) if (fmdev->curr_fmmode != FM_MODE_RX) return -EPERM; - if (vol_to_set < FM_RX_VOLUME_MIN || vol_to_set > FM_RX_VOLUME_MAX) { + if (vol_to_set > FM_RX_VOLUME_MAX) { fmerr("Volume is not within(%d-%d) range\n", FM_RX_VOLUME_MIN, FM_RX_VOLUME_MAX); return -EINVAL; diff --git a/drivers/media/radio/wl128x/fmdrv_v4l2.c b/drivers/media/radio/wl128x/fmdrv_v4l2.c index 077d369a017..b55012c1184 100644 --- a/drivers/media/radio/wl128x/fmdrv_v4l2.c +++ b/drivers/media/radio/wl128x/fmdrv_v4l2.c @@ -56,23 +56,29 @@ static ssize_t fm_v4l2_fops_read(struct file *file, char __user * buf, return -EIO; } - /* Turn on RDS mode , if it is disabled */ + if (mutex_lock_interruptible(&fmdev->mutex)) + return -ERESTARTSYS; + + /* Turn on RDS mode if it is disabled */ ret = fm_rx_get_rds_mode(fmdev, &rds_mode); if (ret < 0) { fmerr("Unable to read current rds mode\n"); - return ret; + goto read_unlock; } if (rds_mode == FM_RDS_DISABLE) { ret = fmc_set_rds_mode(fmdev, FM_RDS_ENABLE); if (ret < 0) { fmerr("Failed to enable rds mode\n"); - return ret; + goto read_unlock; } } /* Copy RDS data from internal buffer to user buffer */ - return fmc_transfer_rds_from_internal_buff(fmdev, file, buf, count); + ret = fmc_transfer_rds_from_internal_buff(fmdev, file, buf, count); +read_unlock: + mutex_unlock(&fmdev->mutex); + return ret; } /* Write TX RDS data */ @@ -91,8 +97,11 @@ static ssize_t fm_v4l2_fops_write(struct file *file, const char __user * buf, return -EFAULT; fmdev = video_drvdata(file); + if (mutex_lock_interruptible(&fmdev->mutex)) + return -ERESTARTSYS; fm_tx_set_radio_text(fmdev, rds.text, rds.text_type); fm_tx_set_af(fmdev, rds.af_freq); + mutex_unlock(&fmdev->mutex); return sizeof(rds); } @@ -103,7 +112,9 @@ static u32 fm_v4l2_fops_poll(struct file *file, struct poll_table_struct *pts) struct fmdev *fmdev; fmdev = video_drvdata(file); + mutex_lock(&fmdev->mutex); ret = fmc_is_rds_data_available(fmdev, file, pts); + mutex_unlock(&fmdev->mutex); if (ret < 0) return POLLIN | POLLRDNORM; @@ -127,10 +138,12 @@ static int fm_v4l2_fops_open(struct file *file) fmdev = video_drvdata(file); + if (mutex_lock_interruptible(&fmdev->mutex)) + return -ERESTARTSYS; ret = fmc_prepare(fmdev); if (ret < 0) { fmerr("Unable to prepare FM CORE\n"); - return ret; + goto open_unlock; } fmdbg("Load FM RX firmware..\n"); @@ -138,10 +151,12 @@ static int fm_v4l2_fops_open(struct file *file) ret = fmc_set_mode(fmdev, FM_MODE_RX); if (ret < 0) { fmerr("Unable to load FM RX firmware\n"); - return ret; + goto open_unlock; } radio_disconnected = 1; +open_unlock: + mutex_unlock(&fmdev->mutex); return ret; } @@ -156,19 +171,22 @@ static int fm_v4l2_fops_release(struct file *file) return 0; } + mutex_lock(&fmdev->mutex); ret = fmc_set_mode(fmdev, FM_MODE_OFF); if (ret < 0) { fmerr("Unable to turn off the chip\n"); - return ret; + goto release_unlock; } ret = fmc_release(fmdev); if (ret < 0) { fmerr("FM CORE release failed\n"); - return ret; + goto release_unlock; } radio_disconnected = 0; +release_unlock: + mutex_unlock(&fmdev->mutex); return ret; } @@ -240,7 +258,7 @@ static int fm_v4l2_vidioc_g_audio(struct file *file, void *priv, } static int fm_v4l2_vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *audio) + const struct v4l2_audio *audio) { if (audio->index != 0) return -EINVAL; @@ -285,7 +303,9 @@ static int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv, tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO | ((fmdev->rx.rds.flag == FM_RDS_ENABLE) ? V4L2_TUNER_SUB_RDS : 0); tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS | - V4L2_TUNER_CAP_LOW; + V4L2_TUNER_CAP_LOW | + V4L2_TUNER_CAP_HWSEEK_BOUNDED | + V4L2_TUNER_CAP_HWSEEK_WRAP; tuner->audmode = (stereo_mono_mode ? V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO); @@ -311,7 +331,7 @@ static int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv, * Should we set other tuner attributes, too? */ static int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner) + const struct v4l2_tuner *tuner) { struct fmdev *fmdev = video_drvdata(file); u16 aud_mode; @@ -368,7 +388,7 @@ static int fm_v4l2_vidioc_g_freq(struct file *file, void *priv, /* Set tuner or modulator radio frequency */ static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv, - struct v4l2_frequency *freq) + const struct v4l2_frequency *freq) { struct fmdev *fmdev = video_drvdata(file); @@ -376,18 +396,19 @@ static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv, * As V4L2_TUNER_CAP_LOW is set 1 user sends the frequency * in units of 62.5 Hz. */ - freq->frequency = (u32)(freq->frequency / 16); - - return fmc_set_freq(fmdev, freq->frequency); + return fmc_set_freq(fmdev, freq->frequency / 16); } /* Set hardware frequency seek. If current mode is NOT RX, set it RX. */ static int fm_v4l2_vidioc_s_hw_freq_seek(struct file *file, void *priv, - struct v4l2_hw_freq_seek *seek) + const struct v4l2_hw_freq_seek *seek) { struct fmdev *fmdev = video_drvdata(file); int ret; + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + if (fmdev->curr_fmmode != FM_MODE_RX) { ret = fmc_set_mode(fmdev, FM_MODE_RX); if (ret != 0) { @@ -428,7 +449,7 @@ static int fm_v4l2_vidioc_g_modulator(struct file *file, void *priv, /* Set modulator attributes. If mode is not TX, set to TX. */ static int fm_v4l2_vidioc_s_modulator(struct file *file, void *priv, - struct v4l2_modulator *mod) + const struct v4l2_modulator *mod) { struct fmdev *fmdev = video_drvdata(file); u8 rds_mode; @@ -495,6 +516,16 @@ static struct video_device fm_viddev_template = { .ioctl_ops = &fm_drv_ioctl_ops, .name = FM_DRV_NAME, .release = video_device_release, + /* + * To ensure both the tuner and modulator ioctls are accessible we + * set the vfl_dir to M2M to indicate this. + * + * It is not really a mem2mem device of course, but it can both receive + * and transmit using the same radio device. It's the only radio driver + * that does this and it should really be split in two radio devices, + * but that would affect applications using this driver. + */ + .vfl_dir = VFL_DIR_M2M, }; int fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr) @@ -502,6 +533,11 @@ int fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr) struct v4l2_ctrl *ctrl; int ret; + strlcpy(fmdev->v4l2_dev.name, FM_DRV_NAME, sizeof(fmdev->v4l2_dev.name)); + ret = v4l2_device_register(NULL, &fmdev->v4l2_dev); + if (ret < 0) + return ret; + /* Init mutex for core locking */ mutex_init(&fmdev->mutex); @@ -518,6 +554,7 @@ int fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr) video_set_drvdata(gradio_dev, fmdev); gradio_dev->lock = &fmdev->mutex; + gradio_dev->v4l2_dev = &fmdev->v4l2_dev; /* Register with V4L2 subsystem as RADIO device */ if (video_register_device(gradio_dev, VFL_TYPE_RADIO, radio_nr)) { @@ -580,5 +617,7 @@ void *fm_v4l2_deinit_video_device(void) /* Unregister RADIO device from V4L2 subsystem */ video_unregister_device(gradio_dev); + v4l2_device_unregister(&fmdev->v4l2_dev); + return fmdev; } |
