162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/kernel.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/init.h> 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci#include <linux/input.h> 1162306a36Sopenharmony_ci#include <linux/usb.h> 1262306a36Sopenharmony_ci#include <linux/hid.h> 1362306a36Sopenharmony_ci#include <linux/mutex.h> 1462306a36Sopenharmony_ci#include <linux/videodev2.h> 1562306a36Sopenharmony_ci#include <asm/unaligned.h> 1662306a36Sopenharmony_ci#include <media/v4l2-device.h> 1762306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 1862306a36Sopenharmony_ci#include <media/v4l2-ctrls.h> 1962306a36Sopenharmony_ci#include <media/v4l2-event.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * 'Thanko's Raremono' is a Japanese si4734-based AM/FM/SW USB receiver: 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * http://www.raremono.jp/product/484.html/ 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * The USB protocol has been reversed engineered using wireshark, initially 2762306a36Sopenharmony_ci * by Dinesh Ram <dinesh.ram@cern.ch> and finished by Hans Verkuil 2862306a36Sopenharmony_ci * <hverkuil@xs4all.nl>. 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * Sadly the firmware used in this product hides lots of goodies since the 3162306a36Sopenharmony_ci * si4734 has more features than are supported by the firmware. Oh well... 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* driver and module definitions */ 3562306a36Sopenharmony_ciMODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); 3662306a36Sopenharmony_ciMODULE_DESCRIPTION("Thanko's Raremono AM/FM/SW Receiver USB driver"); 3762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* 4062306a36Sopenharmony_ci * The Device announces itself as Cygnal Integrated Products, Inc. 4162306a36Sopenharmony_ci * 4262306a36Sopenharmony_ci * The vendor and product IDs (and in fact all other lsusb information as 4362306a36Sopenharmony_ci * well) are identical to the si470x Silicon Labs USB FM Radio Reference 4462306a36Sopenharmony_ci * Design board, even though this card has a si4734 device. Clearly the 4562306a36Sopenharmony_ci * designer of this product never bothered to change the USB IDs. 4662306a36Sopenharmony_ci */ 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* USB Device ID List */ 4962306a36Sopenharmony_cistatic const struct usb_device_id usb_raremono_device_table[] = { 5062306a36Sopenharmony_ci {USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) }, 5162306a36Sopenharmony_ci { } /* Terminating entry */ 5262306a36Sopenharmony_ci}; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, usb_raremono_device_table); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci#define BUFFER_LENGTH 64 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/* Timeout is set to a high value, could probably be reduced. Need more tests */ 5962306a36Sopenharmony_ci#define USB_TIMEOUT 10000 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/* Frequency limits in KHz */ 6262306a36Sopenharmony_ci#define FM_FREQ_RANGE_LOW 64000 6362306a36Sopenharmony_ci#define FM_FREQ_RANGE_HIGH 108000 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci#define AM_FREQ_RANGE_LOW 520 6662306a36Sopenharmony_ci#define AM_FREQ_RANGE_HIGH 1710 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci#define SW_FREQ_RANGE_LOW 2300 6962306a36Sopenharmony_ci#define SW_FREQ_RANGE_HIGH 26100 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cienum { BAND_FM, BAND_AM, BAND_SW }; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic const struct v4l2_frequency_band bands[] = { 7462306a36Sopenharmony_ci /* Band FM */ 7562306a36Sopenharmony_ci { 7662306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 7762306a36Sopenharmony_ci .index = 0, 7862306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 7962306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS, 8062306a36Sopenharmony_ci .rangelow = FM_FREQ_RANGE_LOW * 16, 8162306a36Sopenharmony_ci .rangehigh = FM_FREQ_RANGE_HIGH * 16, 8262306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_FM, 8362306a36Sopenharmony_ci }, 8462306a36Sopenharmony_ci /* Band AM */ 8562306a36Sopenharmony_ci { 8662306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 8762306a36Sopenharmony_ci .index = 1, 8862306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, 8962306a36Sopenharmony_ci .rangelow = AM_FREQ_RANGE_LOW * 16, 9062306a36Sopenharmony_ci .rangehigh = AM_FREQ_RANGE_HIGH * 16, 9162306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 9262306a36Sopenharmony_ci }, 9362306a36Sopenharmony_ci /* Band SW */ 9462306a36Sopenharmony_ci { 9562306a36Sopenharmony_ci .type = V4L2_TUNER_RADIO, 9662306a36Sopenharmony_ci .index = 2, 9762306a36Sopenharmony_ci .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS, 9862306a36Sopenharmony_ci .rangelow = SW_FREQ_RANGE_LOW * 16, 9962306a36Sopenharmony_ci .rangehigh = SW_FREQ_RANGE_HIGH * 16, 10062306a36Sopenharmony_ci .modulation = V4L2_BAND_MODULATION_AM, 10162306a36Sopenharmony_ci }, 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistruct raremono_device { 10562306a36Sopenharmony_ci struct usb_device *usbdev; 10662306a36Sopenharmony_ci struct usb_interface *intf; 10762306a36Sopenharmony_ci struct video_device vdev; 10862306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 10962306a36Sopenharmony_ci struct mutex lock; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci u8 *buffer; 11262306a36Sopenharmony_ci u32 band; 11362306a36Sopenharmony_ci unsigned curfreq; 11462306a36Sopenharmony_ci}; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic inline struct raremono_device *to_raremono_dev(struct v4l2_device *v4l2_dev) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci return container_of(v4l2_dev, struct raremono_device, v4l2_dev); 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/* Set frequency. */ 12262306a36Sopenharmony_cistatic int raremono_cmd_main(struct raremono_device *radio, unsigned band, unsigned freq) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci unsigned band_offset; 12562306a36Sopenharmony_ci int ret; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci switch (band) { 12862306a36Sopenharmony_ci case BAND_FM: 12962306a36Sopenharmony_ci band_offset = 1; 13062306a36Sopenharmony_ci freq /= 10; 13162306a36Sopenharmony_ci break; 13262306a36Sopenharmony_ci case BAND_AM: 13362306a36Sopenharmony_ci band_offset = 0; 13462306a36Sopenharmony_ci break; 13562306a36Sopenharmony_ci default: 13662306a36Sopenharmony_ci band_offset = 2; 13762306a36Sopenharmony_ci break; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci radio->buffer[0] = 0x04 + band_offset; 14062306a36Sopenharmony_ci radio->buffer[1] = freq >> 8; 14162306a36Sopenharmony_ci radio->buffer[2] = freq & 0xff; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), 14462306a36Sopenharmony_ci HID_REQ_SET_REPORT, 14562306a36Sopenharmony_ci USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, 14662306a36Sopenharmony_ci 0x0300 + radio->buffer[0], 2, 14762306a36Sopenharmony_ci radio->buffer, 3, USB_TIMEOUT); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci if (ret < 0) { 15062306a36Sopenharmony_ci dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); 15162306a36Sopenharmony_ci return ret; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci radio->curfreq = (band == BAND_FM) ? freq * 10 : freq; 15462306a36Sopenharmony_ci return 0; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci/* Handle unplugging the device. 15862306a36Sopenharmony_ci * We call video_unregister_device in any case. 15962306a36Sopenharmony_ci * The last function called in this procedure is 16062306a36Sopenharmony_ci * usb_raremono_device_release. 16162306a36Sopenharmony_ci */ 16262306a36Sopenharmony_cistatic void usb_raremono_disconnect(struct usb_interface *intf) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci struct raremono_device *radio = to_raremono_dev(usb_get_intfdata(intf)); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci dev_info(&intf->dev, "Thanko's Raremono disconnected\n"); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci mutex_lock(&radio->lock); 16962306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 17062306a36Sopenharmony_ci video_unregister_device(&radio->vdev); 17162306a36Sopenharmony_ci v4l2_device_disconnect(&radio->v4l2_dev); 17262306a36Sopenharmony_ci mutex_unlock(&radio->lock); 17362306a36Sopenharmony_ci v4l2_device_put(&radio->v4l2_dev); 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci/* 17762306a36Sopenharmony_ci * Linux Video interface 17862306a36Sopenharmony_ci */ 17962306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 18062306a36Sopenharmony_ci struct v4l2_capability *v) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct raremono_device *radio = video_drvdata(file); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci strscpy(v->driver, "radio-raremono", sizeof(v->driver)); 18562306a36Sopenharmony_ci strscpy(v->card, "Thanko's Raremono", sizeof(v->card)); 18662306a36Sopenharmony_ci usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); 18762306a36Sopenharmony_ci return 0; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_cistatic int vidioc_enum_freq_bands(struct file *file, void *priv, 19162306a36Sopenharmony_ci struct v4l2_frequency_band *band) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci if (band->tuner != 0) 19462306a36Sopenharmony_ci return -EINVAL; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (band->index >= ARRAY_SIZE(bands)) 19762306a36Sopenharmony_ci return -EINVAL; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci *band = bands[band->index]; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci return 0; 20262306a36Sopenharmony_ci} 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 20562306a36Sopenharmony_ci struct v4l2_tuner *v) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci struct raremono_device *radio = video_drvdata(file); 20862306a36Sopenharmony_ci int ret; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci if (v->index > 0) 21162306a36Sopenharmony_ci return -EINVAL; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci strscpy(v->name, "AM/FM/SW", sizeof(v->name)); 21462306a36Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 21562306a36Sopenharmony_ci V4L2_TUNER_CAP_FREQ_BANDS; 21662306a36Sopenharmony_ci v->rangelow = AM_FREQ_RANGE_LOW * 16; 21762306a36Sopenharmony_ci v->rangehigh = FM_FREQ_RANGE_HIGH * 16; 21862306a36Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; 21962306a36Sopenharmony_ci v->audmode = (radio->curfreq < FM_FREQ_RANGE_LOW) ? 22062306a36Sopenharmony_ci V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO; 22162306a36Sopenharmony_ci memset(radio->buffer, 1, BUFFER_LENGTH); 22262306a36Sopenharmony_ci ret = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), 22362306a36Sopenharmony_ci 1, 0xa1, 0x030d, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (ret < 0) { 22662306a36Sopenharmony_ci dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret); 22762306a36Sopenharmony_ci return ret; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci v->signal = ((radio->buffer[1] & 0xf) << 8 | radio->buffer[2]) << 4; 23062306a36Sopenharmony_ci return 0; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 23462306a36Sopenharmony_ci const struct v4l2_tuner *v) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci return v->index ? -EINVAL : 0; 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 24062306a36Sopenharmony_ci const struct v4l2_frequency *f) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci struct raremono_device *radio = video_drvdata(file); 24362306a36Sopenharmony_ci u32 freq; 24462306a36Sopenharmony_ci unsigned band; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 24762306a36Sopenharmony_ci return -EINVAL; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci if (f->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) * 8) 25062306a36Sopenharmony_ci band = BAND_FM; 25162306a36Sopenharmony_ci else if (f->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) * 8) 25262306a36Sopenharmony_ci band = BAND_AM; 25362306a36Sopenharmony_ci else 25462306a36Sopenharmony_ci band = BAND_SW; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci freq = clamp_t(u32, f->frequency, bands[band].rangelow, bands[band].rangehigh); 25762306a36Sopenharmony_ci return raremono_cmd_main(radio, band, freq / 16); 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 26162306a36Sopenharmony_ci struct v4l2_frequency *f) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci struct raremono_device *radio = video_drvdata(file); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci if (f->tuner != 0) 26662306a36Sopenharmony_ci return -EINVAL; 26762306a36Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 26862306a36Sopenharmony_ci f->frequency = radio->curfreq * 16; 26962306a36Sopenharmony_ci return 0; 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic void raremono_device_release(struct v4l2_device *v4l2_dev) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct raremono_device *radio = to_raremono_dev(v4l2_dev); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci kfree(radio->buffer); 27762306a36Sopenharmony_ci kfree(radio); 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci/* File system interface */ 28162306a36Sopenharmony_cistatic const struct v4l2_file_operations usb_raremono_fops = { 28262306a36Sopenharmony_ci .owner = THIS_MODULE, 28362306a36Sopenharmony_ci .open = v4l2_fh_open, 28462306a36Sopenharmony_ci .release = v4l2_fh_release, 28562306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 28662306a36Sopenharmony_ci}; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops usb_raremono_ioctl_ops = { 28962306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 29062306a36Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 29162306a36Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 29262306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 29362306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 29462306a36Sopenharmony_ci .vidioc_enum_freq_bands = vidioc_enum_freq_bands, 29562306a36Sopenharmony_ci}; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci/* check if the device is present and register with v4l and usb if it is */ 29862306a36Sopenharmony_cistatic int usb_raremono_probe(struct usb_interface *intf, 29962306a36Sopenharmony_ci const struct usb_device_id *id) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci struct raremono_device *radio; 30262306a36Sopenharmony_ci int retval = 0; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci radio = kzalloc(sizeof(*radio), GFP_KERNEL); 30562306a36Sopenharmony_ci if (!radio) 30662306a36Sopenharmony_ci return -ENOMEM; 30762306a36Sopenharmony_ci radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); 30862306a36Sopenharmony_ci if (!radio->buffer) { 30962306a36Sopenharmony_ci kfree(radio); 31062306a36Sopenharmony_ci return -ENOMEM; 31162306a36Sopenharmony_ci } 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci radio->usbdev = interface_to_usbdev(intf); 31462306a36Sopenharmony_ci radio->intf = intf; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci /* 31762306a36Sopenharmony_ci * This device uses the same USB IDs as the si470x SiLabs reference 31862306a36Sopenharmony_ci * design. So do an additional check: attempt to read the device ID 31962306a36Sopenharmony_ci * from the si470x: the lower 12 bits are 0x0242 for the si470x. The 32062306a36Sopenharmony_ci * Raremono always returns 0x0800 (the meaning of that is unknown, but 32162306a36Sopenharmony_ci * at least it works). 32262306a36Sopenharmony_ci * 32362306a36Sopenharmony_ci * We use this check to determine which device we are dealing with. 32462306a36Sopenharmony_ci */ 32562306a36Sopenharmony_ci msleep(20); 32662306a36Sopenharmony_ci retval = usb_control_msg(radio->usbdev, 32762306a36Sopenharmony_ci usb_rcvctrlpipe(radio->usbdev, 0), 32862306a36Sopenharmony_ci HID_REQ_GET_REPORT, 32962306a36Sopenharmony_ci USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, 33062306a36Sopenharmony_ci 1, 2, 33162306a36Sopenharmony_ci radio->buffer, 3, 500); 33262306a36Sopenharmony_ci if (retval != 3 || 33362306a36Sopenharmony_ci (get_unaligned_be16(&radio->buffer[1]) & 0xfff) == 0x0242) { 33462306a36Sopenharmony_ci dev_info(&intf->dev, "this is not Thanko's Raremono.\n"); 33562306a36Sopenharmony_ci retval = -ENODEV; 33662306a36Sopenharmony_ci goto free_mem; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci dev_info(&intf->dev, "Thanko's Raremono connected: (%04X:%04X)\n", 34062306a36Sopenharmony_ci id->idVendor, id->idProduct); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); 34362306a36Sopenharmony_ci if (retval < 0) { 34462306a36Sopenharmony_ci dev_err(&intf->dev, "couldn't register v4l2_device\n"); 34562306a36Sopenharmony_ci goto free_mem; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci mutex_init(&radio->lock); 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci strscpy(radio->vdev.name, radio->v4l2_dev.name, 35162306a36Sopenharmony_ci sizeof(radio->vdev.name)); 35262306a36Sopenharmony_ci radio->vdev.v4l2_dev = &radio->v4l2_dev; 35362306a36Sopenharmony_ci radio->vdev.fops = &usb_raremono_fops; 35462306a36Sopenharmony_ci radio->vdev.ioctl_ops = &usb_raremono_ioctl_ops; 35562306a36Sopenharmony_ci radio->vdev.lock = &radio->lock; 35662306a36Sopenharmony_ci radio->vdev.release = video_device_release_empty; 35762306a36Sopenharmony_ci radio->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 35862306a36Sopenharmony_ci radio->v4l2_dev.release = raremono_device_release; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci usb_set_intfdata(intf, &radio->v4l2_dev); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci video_set_drvdata(&radio->vdev, radio); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci raremono_cmd_main(radio, BAND_FM, 95160); 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); 36762306a36Sopenharmony_ci if (retval == 0) { 36862306a36Sopenharmony_ci dev_info(&intf->dev, "V4L2 device registered as %s\n", 36962306a36Sopenharmony_ci video_device_node_name(&radio->vdev)); 37062306a36Sopenharmony_ci return 0; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci dev_err(&intf->dev, "could not register video device\n"); 37362306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_cifree_mem: 37662306a36Sopenharmony_ci kfree(radio->buffer); 37762306a36Sopenharmony_ci kfree(radio); 37862306a36Sopenharmony_ci return retval; 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci/* USB subsystem interface */ 38262306a36Sopenharmony_cistatic struct usb_driver usb_raremono_driver = { 38362306a36Sopenharmony_ci .name = "radio-raremono", 38462306a36Sopenharmony_ci .probe = usb_raremono_probe, 38562306a36Sopenharmony_ci .disconnect = usb_raremono_disconnect, 38662306a36Sopenharmony_ci .id_table = usb_raremono_device_table, 38762306a36Sopenharmony_ci}; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_cimodule_usb_driver(usb_raremono_driver); 390