162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * A driver for the AverMedia MR 800 USB FM radio. This device plugs 462306a36Sopenharmony_ci * into both the USB and an analog audio input, so this thing 562306a36Sopenharmony_ci * only deals with initialization and frequency setting, the 662306a36Sopenharmony_ci * audio data has to be handled by a sound driver. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright (c) 2008 Alexey Klimov <klimov.linux@gmail.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/* 1262306a36Sopenharmony_ci * Big thanks to authors and contributors of dsbr100.c and radio-si470x.c 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * When work was looked pretty good, i discover this: 1562306a36Sopenharmony_ci * http://av-usbradio.sourceforge.net/index.php 1662306a36Sopenharmony_ci * http://sourceforge.net/projects/av-usbradio/ 1762306a36Sopenharmony_ci * Latest release of theirs project was in 2005. 1862306a36Sopenharmony_ci * Probably, this driver could be improved through using their 1962306a36Sopenharmony_ci * achievements (specifications given). 2062306a36Sopenharmony_ci * Also, Faidon Liambotis <paravoid@debian.org> wrote nice driver for this radio 2162306a36Sopenharmony_ci * in 2007. He allowed to use his driver to improve current mr800 radio driver. 2262306a36Sopenharmony_ci * http://www.spinics.net/lists/linux-usb-devel/msg10109.html 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * Version 0.01: First working version. 2562306a36Sopenharmony_ci * It's required to blacklist AverMedia USB Radio 2662306a36Sopenharmony_ci * in usbhid/hid-quirks.c 2762306a36Sopenharmony_ci * Version 0.10: A lot of cleanups and fixes: unpluging the device, 2862306a36Sopenharmony_ci * few mutex locks were added, codinstyle issues, etc. 2962306a36Sopenharmony_ci * Added stereo support. Thanks to 3062306a36Sopenharmony_ci * Douglas Schilling Landgraf <dougsland@gmail.com> and 3162306a36Sopenharmony_ci * David Ellingsworth <david@identd.dyndns.org> 3262306a36Sopenharmony_ci * for discussion, help and support. 3362306a36Sopenharmony_ci * Version 0.11: Converted to v4l2_device. 3462306a36Sopenharmony_ci * 3562306a36Sopenharmony_ci * Many things to do: 3662306a36Sopenharmony_ci * - Correct power management of device (suspend & resume) 3762306a36Sopenharmony_ci * - Add code for scanning and smooth tuning 3862306a36Sopenharmony_ci * - Add code for sensitivity value 3962306a36Sopenharmony_ci * - Correct mistakes 4062306a36Sopenharmony_ci * - In Japan another FREQ_MIN and FREQ_MAX 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/* kernel includes */ 4462306a36Sopenharmony_ci#include <linux/kernel.h> 4562306a36Sopenharmony_ci#include <linux/module.h> 4662306a36Sopenharmony_ci#include <linux/init.h> 4762306a36Sopenharmony_ci#include <linux/slab.h> 4862306a36Sopenharmony_ci#include <linux/input.h> 4962306a36Sopenharmony_ci#include <linux/videodev2.h> 5062306a36Sopenharmony_ci#include <media/v4l2-device.h> 5162306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 5262306a36Sopenharmony_ci#include <media/v4l2-ctrls.h> 5362306a36Sopenharmony_ci#include <media/v4l2-event.h> 5462306a36Sopenharmony_ci#include <linux/usb.h> 5562306a36Sopenharmony_ci#include <linux/mutex.h> 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/* driver and module definitions */ 5862306a36Sopenharmony_ci#define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>" 5962306a36Sopenharmony_ci#define DRIVER_DESC "AverMedia MR 800 USB FM radio driver" 6062306a36Sopenharmony_ci#define DRIVER_VERSION "0.1.2" 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 6362306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 6462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 6562306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci#define USB_AMRADIO_VENDOR 0x07ca 6862306a36Sopenharmony_ci#define USB_AMRADIO_PRODUCT 0xb800 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci/* dev_warn macro with driver name */ 7162306a36Sopenharmony_ci#define MR800_DRIVER_NAME "radio-mr800" 7262306a36Sopenharmony_ci#define amradio_dev_warn(dev, fmt, arg...) \ 7362306a36Sopenharmony_ci dev_warn(dev, MR800_DRIVER_NAME " - " fmt, ##arg) 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci#define amradio_dev_err(dev, fmt, arg...) \ 7662306a36Sopenharmony_ci dev_err(dev, MR800_DRIVER_NAME " - " fmt, ##arg) 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* Probably USB_TIMEOUT should be modified in module parameter */ 7962306a36Sopenharmony_ci#define BUFFER_LENGTH 8 8062306a36Sopenharmony_ci#define USB_TIMEOUT 500 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci/* Frequency limits in MHz -- these are European values. For Japanese 8362306a36Sopenharmony_cidevices, that would be 76 and 91. */ 8462306a36Sopenharmony_ci#define FREQ_MIN 87.5 8562306a36Sopenharmony_ci#define FREQ_MAX 108.0 8662306a36Sopenharmony_ci#define FREQ_MUL 16000 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* 8962306a36Sopenharmony_ci * Commands that device should understand 9062306a36Sopenharmony_ci * List isn't full and will be updated with implementation of new functions 9162306a36Sopenharmony_ci */ 9262306a36Sopenharmony_ci#define AMRADIO_SET_FREQ 0xa4 9362306a36Sopenharmony_ci#define AMRADIO_GET_READY_FLAG 0xa5 9462306a36Sopenharmony_ci#define AMRADIO_GET_SIGNAL 0xa7 9562306a36Sopenharmony_ci#define AMRADIO_GET_FREQ 0xa8 9662306a36Sopenharmony_ci#define AMRADIO_SET_SEARCH_UP 0xa9 9762306a36Sopenharmony_ci#define AMRADIO_SET_SEARCH_DOWN 0xaa 9862306a36Sopenharmony_ci#define AMRADIO_SET_MUTE 0xab 9962306a36Sopenharmony_ci#define AMRADIO_SET_RIGHT_MUTE 0xac 10062306a36Sopenharmony_ci#define AMRADIO_SET_LEFT_MUTE 0xad 10162306a36Sopenharmony_ci#define AMRADIO_SET_MONO 0xae 10262306a36Sopenharmony_ci#define AMRADIO_SET_SEARCH_LVL 0xb0 10362306a36Sopenharmony_ci#define AMRADIO_STOP_SEARCH 0xb1 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci/* Comfortable defines for amradio_set_stereo */ 10662306a36Sopenharmony_ci#define WANT_STEREO 0x00 10762306a36Sopenharmony_ci#define WANT_MONO 0x01 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci/* module parameter */ 11062306a36Sopenharmony_cistatic int radio_nr = -1; 11162306a36Sopenharmony_cimodule_param(radio_nr, int, 0); 11262306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio Nr"); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci/* Data for one (physical) device */ 11562306a36Sopenharmony_cistruct amradio_device { 11662306a36Sopenharmony_ci /* reference to USB and video device */ 11762306a36Sopenharmony_ci struct usb_device *usbdev; 11862306a36Sopenharmony_ci struct usb_interface *intf; 11962306a36Sopenharmony_ci struct video_device vdev; 12062306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 12162306a36Sopenharmony_ci struct v4l2_ctrl_handler hdl; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci u8 *buffer; 12462306a36Sopenharmony_ci struct mutex lock; /* buffer locking */ 12562306a36Sopenharmony_ci int curfreq; 12662306a36Sopenharmony_ci int stereo; 12762306a36Sopenharmony_ci int muted; 12862306a36Sopenharmony_ci}; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic inline struct amradio_device *to_amradio_dev(struct v4l2_device *v4l2_dev) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci return container_of(v4l2_dev, struct amradio_device, v4l2_dev); 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic int amradio_send_cmd(struct amradio_device *radio, u8 cmd, u8 arg, 13662306a36Sopenharmony_ci u8 *extra, u8 extralen, bool reply) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci int retval; 13962306a36Sopenharmony_ci int size; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci radio->buffer[0] = 0x00; 14262306a36Sopenharmony_ci radio->buffer[1] = 0x55; 14362306a36Sopenharmony_ci radio->buffer[2] = 0xaa; 14462306a36Sopenharmony_ci radio->buffer[3] = extralen; 14562306a36Sopenharmony_ci radio->buffer[4] = cmd; 14662306a36Sopenharmony_ci radio->buffer[5] = arg; 14762306a36Sopenharmony_ci radio->buffer[6] = 0x00; 14862306a36Sopenharmony_ci radio->buffer[7] = extra || reply ? 8 : 0; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), 15162306a36Sopenharmony_ci radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (retval < 0 || size != BUFFER_LENGTH) { 15462306a36Sopenharmony_ci if (video_is_registered(&radio->vdev)) 15562306a36Sopenharmony_ci amradio_dev_warn(&radio->vdev.dev, 15662306a36Sopenharmony_ci "cmd %02x failed\n", cmd); 15762306a36Sopenharmony_ci return retval ? retval : -EIO; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci if (!extra && !reply) 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if (extra) { 16362306a36Sopenharmony_ci memcpy(radio->buffer, extra, extralen); 16462306a36Sopenharmony_ci memset(radio->buffer + extralen, 0, 8 - extralen); 16562306a36Sopenharmony_ci retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), 16662306a36Sopenharmony_ci radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT); 16762306a36Sopenharmony_ci } else { 16862306a36Sopenharmony_ci memset(radio->buffer, 0, 8); 16962306a36Sopenharmony_ci retval = usb_bulk_msg(radio->usbdev, usb_rcvbulkpipe(radio->usbdev, 0x81), 17062306a36Sopenharmony_ci radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT); 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci if (retval == 0 && size == BUFFER_LENGTH) 17362306a36Sopenharmony_ci return 0; 17462306a36Sopenharmony_ci if (video_is_registered(&radio->vdev) && cmd != AMRADIO_GET_READY_FLAG) 17562306a36Sopenharmony_ci amradio_dev_warn(&radio->vdev.dev, "follow-up to cmd %02x failed\n", cmd); 17662306a36Sopenharmony_ci return retval ? retval : -EIO; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci/* switch on/off the radio. Send 8 bytes to device */ 18062306a36Sopenharmony_cistatic int amradio_set_mute(struct amradio_device *radio, bool mute) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci int ret = amradio_send_cmd(radio, 18362306a36Sopenharmony_ci AMRADIO_SET_MUTE, mute, NULL, 0, false); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci if (!ret) 18662306a36Sopenharmony_ci radio->muted = mute; 18762306a36Sopenharmony_ci return ret; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ 19162306a36Sopenharmony_cistatic int amradio_set_freq(struct amradio_device *radio, int freq) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci unsigned short freq_send; 19462306a36Sopenharmony_ci u8 buf[3]; 19562306a36Sopenharmony_ci int retval; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci /* we need to be sure that frequency isn't out of range */ 19862306a36Sopenharmony_ci freq = clamp_t(unsigned, freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); 19962306a36Sopenharmony_ci freq_send = 0x10 + (freq >> 3) / 25; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* frequency is calculated from freq_send and placed in first 2 bytes */ 20262306a36Sopenharmony_ci buf[0] = (freq_send >> 8) & 0xff; 20362306a36Sopenharmony_ci buf[1] = freq_send & 0xff; 20462306a36Sopenharmony_ci buf[2] = 0x01; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci retval = amradio_send_cmd(radio, AMRADIO_SET_FREQ, 0, buf, 3, false); 20762306a36Sopenharmony_ci if (retval) 20862306a36Sopenharmony_ci return retval; 20962306a36Sopenharmony_ci radio->curfreq = freq; 21062306a36Sopenharmony_ci msleep(40); 21162306a36Sopenharmony_ci return 0; 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic int amradio_set_stereo(struct amradio_device *radio, bool stereo) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci int ret = amradio_send_cmd(radio, 21762306a36Sopenharmony_ci AMRADIO_SET_MONO, !stereo, NULL, 0, false); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci if (!ret) 22062306a36Sopenharmony_ci radio->stereo = stereo; 22162306a36Sopenharmony_ci return ret; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic int amradio_get_stat(struct amradio_device *radio, bool *is_stereo, u32 *signal) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci int ret = amradio_send_cmd(radio, 22762306a36Sopenharmony_ci AMRADIO_GET_SIGNAL, 0, NULL, 0, true); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (ret) 23062306a36Sopenharmony_ci return ret; 23162306a36Sopenharmony_ci *is_stereo = radio->buffer[2] >> 7; 23262306a36Sopenharmony_ci *signal = (radio->buffer[3] & 0xf0) << 8; 23362306a36Sopenharmony_ci return 0; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci/* Handle unplugging the device. 23762306a36Sopenharmony_ci * We call video_unregister_device in any case. 23862306a36Sopenharmony_ci * The last function called in this procedure is 23962306a36Sopenharmony_ci * usb_amradio_device_release. 24062306a36Sopenharmony_ci */ 24162306a36Sopenharmony_cistatic void usb_amradio_disconnect(struct usb_interface *intf) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci struct amradio_device *radio = to_amradio_dev(usb_get_intfdata(intf)); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci mutex_lock(&radio->lock); 24662306a36Sopenharmony_ci video_unregister_device(&radio->vdev); 24762306a36Sopenharmony_ci amradio_set_mute(radio, true); 24862306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 24962306a36Sopenharmony_ci v4l2_device_disconnect(&radio->v4l2_dev); 25062306a36Sopenharmony_ci mutex_unlock(&radio->lock); 25162306a36Sopenharmony_ci v4l2_device_put(&radio->v4l2_dev); 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/* vidioc_querycap - query device capabilities */ 25562306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 25662306a36Sopenharmony_ci struct v4l2_capability *v) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci struct amradio_device *radio = video_drvdata(file); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci strscpy(v->driver, "radio-mr800", sizeof(v->driver)); 26162306a36Sopenharmony_ci strscpy(v->card, "AverMedia MR 800 USB FM Radio", sizeof(v->card)); 26262306a36Sopenharmony_ci usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci/* vidioc_g_tuner - get tuner attributes */ 26762306a36Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 26862306a36Sopenharmony_ci struct v4l2_tuner *v) 26962306a36Sopenharmony_ci{ 27062306a36Sopenharmony_ci struct amradio_device *radio = video_drvdata(file); 27162306a36Sopenharmony_ci bool is_stereo = false; 27262306a36Sopenharmony_ci int retval; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (v->index > 0) 27562306a36Sopenharmony_ci return -EINVAL; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci v->signal = 0; 27862306a36Sopenharmony_ci retval = amradio_get_stat(radio, &is_stereo, &v->signal); 27962306a36Sopenharmony_ci if (retval) 28062306a36Sopenharmony_ci return retval; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 28362306a36Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 28462306a36Sopenharmony_ci v->rangelow = FREQ_MIN * FREQ_MUL; 28562306a36Sopenharmony_ci v->rangehigh = FREQ_MAX * FREQ_MUL; 28662306a36Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 28762306a36Sopenharmony_ci V4L2_TUNER_CAP_HWSEEK_WRAP; 28862306a36Sopenharmony_ci v->rxsubchans = is_stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 28962306a36Sopenharmony_ci v->audmode = radio->stereo ? 29062306a36Sopenharmony_ci V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO; 29162306a36Sopenharmony_ci return 0; 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci/* vidioc_s_tuner - set tuner attributes */ 29562306a36Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 29662306a36Sopenharmony_ci const struct v4l2_tuner *v) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci struct amradio_device *radio = video_drvdata(file); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci if (v->index > 0) 30162306a36Sopenharmony_ci return -EINVAL; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci /* mono/stereo selector */ 30462306a36Sopenharmony_ci switch (v->audmode) { 30562306a36Sopenharmony_ci case V4L2_TUNER_MODE_MONO: 30662306a36Sopenharmony_ci return amradio_set_stereo(radio, WANT_MONO); 30762306a36Sopenharmony_ci default: 30862306a36Sopenharmony_ci return amradio_set_stereo(radio, WANT_STEREO); 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci} 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci/* vidioc_s_frequency - set tuner radio frequency */ 31362306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 31462306a36Sopenharmony_ci const struct v4l2_frequency *f) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci struct amradio_device *radio = video_drvdata(file); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci if (f->tuner != 0) 31962306a36Sopenharmony_ci return -EINVAL; 32062306a36Sopenharmony_ci return amradio_set_freq(radio, f->frequency); 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci/* vidioc_g_frequency - get tuner radio frequency */ 32462306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 32562306a36Sopenharmony_ci struct v4l2_frequency *f) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci struct amradio_device *radio = video_drvdata(file); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 33062306a36Sopenharmony_ci return -EINVAL; 33162306a36Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 33262306a36Sopenharmony_ci f->frequency = radio->curfreq; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci return 0; 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_cistatic int vidioc_s_hw_freq_seek(struct file *file, void *priv, 33862306a36Sopenharmony_ci const struct v4l2_hw_freq_seek *seek) 33962306a36Sopenharmony_ci{ 34062306a36Sopenharmony_ci static u8 buf[8] = { 34162306a36Sopenharmony_ci 0x3d, 0x32, 0x0f, 0x08, 0x3d, 0x32, 0x0f, 0x08 34262306a36Sopenharmony_ci }; 34362306a36Sopenharmony_ci struct amradio_device *radio = video_drvdata(file); 34462306a36Sopenharmony_ci unsigned long timeout; 34562306a36Sopenharmony_ci int retval; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci if (seek->tuner != 0 || !seek->wrap_around) 34862306a36Sopenharmony_ci return -EINVAL; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 35162306a36Sopenharmony_ci return -EWOULDBLOCK; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci retval = amradio_send_cmd(radio, 35462306a36Sopenharmony_ci AMRADIO_SET_SEARCH_LVL, 0, buf, 8, false); 35562306a36Sopenharmony_ci if (retval) 35662306a36Sopenharmony_ci return retval; 35762306a36Sopenharmony_ci amradio_set_freq(radio, radio->curfreq); 35862306a36Sopenharmony_ci retval = amradio_send_cmd(radio, 35962306a36Sopenharmony_ci seek->seek_upward ? AMRADIO_SET_SEARCH_UP : AMRADIO_SET_SEARCH_DOWN, 36062306a36Sopenharmony_ci 0, NULL, 0, false); 36162306a36Sopenharmony_ci if (retval) 36262306a36Sopenharmony_ci return retval; 36362306a36Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(30000); 36462306a36Sopenharmony_ci for (;;) { 36562306a36Sopenharmony_ci if (time_after(jiffies, timeout)) { 36662306a36Sopenharmony_ci retval = -ENODATA; 36762306a36Sopenharmony_ci break; 36862306a36Sopenharmony_ci } 36962306a36Sopenharmony_ci if (schedule_timeout_interruptible(msecs_to_jiffies(10))) { 37062306a36Sopenharmony_ci retval = -ERESTARTSYS; 37162306a36Sopenharmony_ci break; 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci retval = amradio_send_cmd(radio, AMRADIO_GET_READY_FLAG, 37462306a36Sopenharmony_ci 0, NULL, 0, true); 37562306a36Sopenharmony_ci if (retval) 37662306a36Sopenharmony_ci continue; 37762306a36Sopenharmony_ci amradio_send_cmd(radio, AMRADIO_GET_FREQ, 0, NULL, 0, true); 37862306a36Sopenharmony_ci if (radio->buffer[1] || radio->buffer[2]) { 37962306a36Sopenharmony_ci /* To check: sometimes radio->curfreq is set to out of range value */ 38062306a36Sopenharmony_ci radio->curfreq = (radio->buffer[1] << 8) | radio->buffer[2]; 38162306a36Sopenharmony_ci radio->curfreq = (radio->curfreq - 0x10) * 200; 38262306a36Sopenharmony_ci amradio_send_cmd(radio, AMRADIO_STOP_SEARCH, 38362306a36Sopenharmony_ci 0, NULL, 0, false); 38462306a36Sopenharmony_ci amradio_set_freq(radio, radio->curfreq); 38562306a36Sopenharmony_ci retval = 0; 38662306a36Sopenharmony_ci break; 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci amradio_send_cmd(radio, AMRADIO_STOP_SEARCH, 0, NULL, 0, false); 39062306a36Sopenharmony_ci amradio_set_freq(radio, radio->curfreq); 39162306a36Sopenharmony_ci return retval; 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_cistatic int usb_amradio_s_ctrl(struct v4l2_ctrl *ctrl) 39562306a36Sopenharmony_ci{ 39662306a36Sopenharmony_ci struct amradio_device *radio = 39762306a36Sopenharmony_ci container_of(ctrl->handler, struct amradio_device, hdl); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci switch (ctrl->id) { 40062306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 40162306a36Sopenharmony_ci return amradio_set_mute(radio, ctrl->val); 40262306a36Sopenharmony_ci } 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci return -EINVAL; 40562306a36Sopenharmony_ci} 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_cistatic int usb_amradio_init(struct amradio_device *radio) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci int retval; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci retval = amradio_set_mute(radio, true); 41262306a36Sopenharmony_ci if (retval) 41362306a36Sopenharmony_ci goto out_err; 41462306a36Sopenharmony_ci retval = amradio_set_stereo(radio, true); 41562306a36Sopenharmony_ci if (retval) 41662306a36Sopenharmony_ci goto out_err; 41762306a36Sopenharmony_ci retval = amradio_set_freq(radio, radio->curfreq); 41862306a36Sopenharmony_ci if (retval) 41962306a36Sopenharmony_ci goto out_err; 42062306a36Sopenharmony_ci return 0; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ciout_err: 42362306a36Sopenharmony_ci amradio_dev_err(&radio->vdev.dev, "initialization failed\n"); 42462306a36Sopenharmony_ci return retval; 42562306a36Sopenharmony_ci} 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci/* Suspend device - stop device. Need to be checked and fixed */ 42862306a36Sopenharmony_cistatic int usb_amradio_suspend(struct usb_interface *intf, pm_message_t message) 42962306a36Sopenharmony_ci{ 43062306a36Sopenharmony_ci struct amradio_device *radio = to_amradio_dev(usb_get_intfdata(intf)); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci mutex_lock(&radio->lock); 43362306a36Sopenharmony_ci if (!radio->muted) { 43462306a36Sopenharmony_ci amradio_set_mute(radio, true); 43562306a36Sopenharmony_ci radio->muted = false; 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci mutex_unlock(&radio->lock); 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci dev_info(&intf->dev, "going into suspend..\n"); 44062306a36Sopenharmony_ci return 0; 44162306a36Sopenharmony_ci} 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci/* Resume device - start device. Need to be checked and fixed */ 44462306a36Sopenharmony_cistatic int usb_amradio_resume(struct usb_interface *intf) 44562306a36Sopenharmony_ci{ 44662306a36Sopenharmony_ci struct amradio_device *radio = to_amradio_dev(usb_get_intfdata(intf)); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci mutex_lock(&radio->lock); 44962306a36Sopenharmony_ci amradio_set_stereo(radio, radio->stereo); 45062306a36Sopenharmony_ci amradio_set_freq(radio, radio->curfreq); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci if (!radio->muted) 45362306a36Sopenharmony_ci amradio_set_mute(radio, false); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci mutex_unlock(&radio->lock); 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci dev_info(&intf->dev, "coming out of suspend..\n"); 45862306a36Sopenharmony_ci return 0; 45962306a36Sopenharmony_ci} 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops usb_amradio_ctrl_ops = { 46262306a36Sopenharmony_ci .s_ctrl = usb_amradio_s_ctrl, 46362306a36Sopenharmony_ci}; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci/* File system interface */ 46662306a36Sopenharmony_cistatic const struct v4l2_file_operations usb_amradio_fops = { 46762306a36Sopenharmony_ci .owner = THIS_MODULE, 46862306a36Sopenharmony_ci .open = v4l2_fh_open, 46962306a36Sopenharmony_ci .release = v4l2_fh_release, 47062306a36Sopenharmony_ci .poll = v4l2_ctrl_poll, 47162306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 47262306a36Sopenharmony_ci}; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops usb_amradio_ioctl_ops = { 47562306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 47662306a36Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 47762306a36Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 47862306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 47962306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 48062306a36Sopenharmony_ci .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, 48162306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 48262306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 48362306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 48462306a36Sopenharmony_ci}; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_cistatic void usb_amradio_release(struct v4l2_device *v4l2_dev) 48762306a36Sopenharmony_ci{ 48862306a36Sopenharmony_ci struct amradio_device *radio = to_amradio_dev(v4l2_dev); 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci /* free rest memory */ 49162306a36Sopenharmony_ci v4l2_ctrl_handler_free(&radio->hdl); 49262306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 49362306a36Sopenharmony_ci kfree(radio->buffer); 49462306a36Sopenharmony_ci kfree(radio); 49562306a36Sopenharmony_ci} 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci/* check if the device is present and register with v4l and usb if it is */ 49862306a36Sopenharmony_cistatic int usb_amradio_probe(struct usb_interface *intf, 49962306a36Sopenharmony_ci const struct usb_device_id *id) 50062306a36Sopenharmony_ci{ 50162306a36Sopenharmony_ci struct amradio_device *radio; 50262306a36Sopenharmony_ci int retval; 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci radio = kzalloc(sizeof(struct amradio_device), GFP_KERNEL); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci if (!radio) { 50762306a36Sopenharmony_ci dev_err(&intf->dev, "kmalloc for amradio_device failed\n"); 50862306a36Sopenharmony_ci retval = -ENOMEM; 50962306a36Sopenharmony_ci goto err; 51062306a36Sopenharmony_ci } 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci if (!radio->buffer) { 51562306a36Sopenharmony_ci dev_err(&intf->dev, "kmalloc for radio->buffer failed\n"); 51662306a36Sopenharmony_ci retval = -ENOMEM; 51762306a36Sopenharmony_ci goto err_nobuf; 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); 52162306a36Sopenharmony_ci if (retval < 0) { 52262306a36Sopenharmony_ci dev_err(&intf->dev, "couldn't register v4l2_device\n"); 52362306a36Sopenharmony_ci goto err_v4l2; 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci v4l2_ctrl_handler_init(&radio->hdl, 1); 52762306a36Sopenharmony_ci v4l2_ctrl_new_std(&radio->hdl, &usb_amradio_ctrl_ops, 52862306a36Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 52962306a36Sopenharmony_ci if (radio->hdl.error) { 53062306a36Sopenharmony_ci retval = radio->hdl.error; 53162306a36Sopenharmony_ci dev_err(&intf->dev, "couldn't register control\n"); 53262306a36Sopenharmony_ci goto err_ctrl; 53362306a36Sopenharmony_ci } 53462306a36Sopenharmony_ci mutex_init(&radio->lock); 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci radio->v4l2_dev.ctrl_handler = &radio->hdl; 53762306a36Sopenharmony_ci radio->v4l2_dev.release = usb_amradio_release; 53862306a36Sopenharmony_ci strscpy(radio->vdev.name, radio->v4l2_dev.name, 53962306a36Sopenharmony_ci sizeof(radio->vdev.name)); 54062306a36Sopenharmony_ci radio->vdev.v4l2_dev = &radio->v4l2_dev; 54162306a36Sopenharmony_ci radio->vdev.fops = &usb_amradio_fops; 54262306a36Sopenharmony_ci radio->vdev.ioctl_ops = &usb_amradio_ioctl_ops; 54362306a36Sopenharmony_ci radio->vdev.release = video_device_release_empty; 54462306a36Sopenharmony_ci radio->vdev.lock = &radio->lock; 54562306a36Sopenharmony_ci radio->vdev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER | 54662306a36Sopenharmony_ci V4L2_CAP_HW_FREQ_SEEK; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci radio->usbdev = interface_to_usbdev(intf); 54962306a36Sopenharmony_ci radio->intf = intf; 55062306a36Sopenharmony_ci usb_set_intfdata(intf, &radio->v4l2_dev); 55162306a36Sopenharmony_ci radio->curfreq = 95.16 * FREQ_MUL; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci video_set_drvdata(&radio->vdev, radio); 55462306a36Sopenharmony_ci retval = usb_amradio_init(radio); 55562306a36Sopenharmony_ci if (retval) 55662306a36Sopenharmony_ci goto err_vdev; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, 55962306a36Sopenharmony_ci radio_nr); 56062306a36Sopenharmony_ci if (retval < 0) { 56162306a36Sopenharmony_ci dev_err(&intf->dev, "could not register video device\n"); 56262306a36Sopenharmony_ci goto err_vdev; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci return 0; 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_cierr_vdev: 56862306a36Sopenharmony_ci v4l2_ctrl_handler_free(&radio->hdl); 56962306a36Sopenharmony_cierr_ctrl: 57062306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 57162306a36Sopenharmony_cierr_v4l2: 57262306a36Sopenharmony_ci kfree(radio->buffer); 57362306a36Sopenharmony_cierr_nobuf: 57462306a36Sopenharmony_ci kfree(radio); 57562306a36Sopenharmony_cierr: 57662306a36Sopenharmony_ci return retval; 57762306a36Sopenharmony_ci} 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci/* USB Device ID List */ 58062306a36Sopenharmony_cistatic const struct usb_device_id usb_amradio_device_table[] = { 58162306a36Sopenharmony_ci { USB_DEVICE_AND_INTERFACE_INFO(USB_AMRADIO_VENDOR, USB_AMRADIO_PRODUCT, 58262306a36Sopenharmony_ci USB_CLASS_HID, 0, 0) }, 58362306a36Sopenharmony_ci { } /* Terminating entry */ 58462306a36Sopenharmony_ci}; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, usb_amradio_device_table); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci/* USB subsystem interface */ 58962306a36Sopenharmony_cistatic struct usb_driver usb_amradio_driver = { 59062306a36Sopenharmony_ci .name = MR800_DRIVER_NAME, 59162306a36Sopenharmony_ci .probe = usb_amradio_probe, 59262306a36Sopenharmony_ci .disconnect = usb_amradio_disconnect, 59362306a36Sopenharmony_ci .suspend = usb_amradio_suspend, 59462306a36Sopenharmony_ci .resume = usb_amradio_resume, 59562306a36Sopenharmony_ci .reset_resume = usb_amradio_resume, 59662306a36Sopenharmony_ci .id_table = usb_amradio_device_table, 59762306a36Sopenharmony_ci}; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_cimodule_usb_driver(usb_amradio_driver); 600