162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2012 Hans Verkuil <hverkuil@xs4all.nl> 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci/* kernel includes */ 762306a36Sopenharmony_ci#include <linux/kernel.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/init.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/input.h> 1262306a36Sopenharmony_ci#include <linux/videodev2.h> 1362306a36Sopenharmony_ci#include <media/v4l2-device.h> 1462306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 1562306a36Sopenharmony_ci#include <media/v4l2-ctrls.h> 1662306a36Sopenharmony_ci#include <media/v4l2-event.h> 1762306a36Sopenharmony_ci#include <linux/usb.h> 1862306a36Sopenharmony_ci#include <linux/mutex.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* driver and module definitions */ 2162306a36Sopenharmony_ciMODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); 2262306a36Sopenharmony_ciMODULE_DESCRIPTION("Keene FM Transmitter driver"); 2362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* Actually, it advertises itself as a Logitech */ 2662306a36Sopenharmony_ci#define USB_KEENE_VENDOR 0x046d 2762306a36Sopenharmony_ci#define USB_KEENE_PRODUCT 0x0a0e 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* Probably USB_TIMEOUT should be modified in module parameter */ 3062306a36Sopenharmony_ci#define BUFFER_LENGTH 8 3162306a36Sopenharmony_ci#define USB_TIMEOUT 500 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* Frequency limits in MHz */ 3462306a36Sopenharmony_ci#define FREQ_MIN 76U 3562306a36Sopenharmony_ci#define FREQ_MAX 108U 3662306a36Sopenharmony_ci#define FREQ_MUL 16000U 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* USB Device ID List */ 3962306a36Sopenharmony_cistatic const struct usb_device_id usb_keene_device_table[] = { 4062306a36Sopenharmony_ci {USB_DEVICE_AND_INTERFACE_INFO(USB_KEENE_VENDOR, USB_KEENE_PRODUCT, 4162306a36Sopenharmony_ci USB_CLASS_HID, 0, 0) }, 4262306a36Sopenharmony_ci { } /* Terminating entry */ 4362306a36Sopenharmony_ci}; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, usb_keene_device_table); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistruct keene_device { 4862306a36Sopenharmony_ci struct usb_device *usbdev; 4962306a36Sopenharmony_ci struct usb_interface *intf; 5062306a36Sopenharmony_ci struct video_device vdev; 5162306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 5262306a36Sopenharmony_ci struct v4l2_ctrl_handler hdl; 5362306a36Sopenharmony_ci struct mutex lock; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci u8 *buffer; 5662306a36Sopenharmony_ci unsigned curfreq; 5762306a36Sopenharmony_ci u8 tx; 5862306a36Sopenharmony_ci u8 pa; 5962306a36Sopenharmony_ci bool stereo; 6062306a36Sopenharmony_ci bool muted; 6162306a36Sopenharmony_ci bool preemph_75_us; 6262306a36Sopenharmony_ci}; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic inline struct keene_device *to_keene_dev(struct v4l2_device *v4l2_dev) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci return container_of(v4l2_dev, struct keene_device, v4l2_dev); 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/* Set frequency (if non-0), PA, mute and turn on/off the FM transmitter. */ 7062306a36Sopenharmony_cistatic int keene_cmd_main(struct keene_device *radio, unsigned freq, bool play) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci unsigned short freq_send = freq ? (freq - 76 * 16000) / 800 : 0; 7362306a36Sopenharmony_ci int ret; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci radio->buffer[0] = 0x00; 7662306a36Sopenharmony_ci radio->buffer[1] = 0x50; 7762306a36Sopenharmony_ci radio->buffer[2] = (freq_send >> 8) & 0xff; 7862306a36Sopenharmony_ci radio->buffer[3] = freq_send & 0xff; 7962306a36Sopenharmony_ci radio->buffer[4] = radio->pa; 8062306a36Sopenharmony_ci /* If bit 4 is set, then tune to the frequency. 8162306a36Sopenharmony_ci If bit 3 is set, then unmute; if bit 2 is set, then mute. 8262306a36Sopenharmony_ci If bit 1 is set, then enter idle mode; if bit 0 is set, 8362306a36Sopenharmony_ci then enter transmit mode. 8462306a36Sopenharmony_ci */ 8562306a36Sopenharmony_ci radio->buffer[5] = (radio->muted ? 4 : 8) | (play ? 1 : 2) | 8662306a36Sopenharmony_ci (freq ? 0x10 : 0); 8762306a36Sopenharmony_ci radio->buffer[6] = 0x00; 8862306a36Sopenharmony_ci radio->buffer[7] = 0x00; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), 9162306a36Sopenharmony_ci 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (ret < 0) { 9462306a36Sopenharmony_ci dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); 9562306a36Sopenharmony_ci return ret; 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci if (freq) 9862306a36Sopenharmony_ci radio->curfreq = freq; 9962306a36Sopenharmony_ci return 0; 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci/* Set TX, stereo and preemphasis mode (50 us vs 75 us). */ 10362306a36Sopenharmony_cistatic int keene_cmd_set(struct keene_device *radio) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci int ret; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci radio->buffer[0] = 0x00; 10862306a36Sopenharmony_ci radio->buffer[1] = 0x51; 10962306a36Sopenharmony_ci radio->buffer[2] = radio->tx; 11062306a36Sopenharmony_ci /* If bit 0 is set, then transmit mono, otherwise stereo. 11162306a36Sopenharmony_ci If bit 2 is set, then enable 75 us preemphasis, otherwise 11262306a36Sopenharmony_ci it is 50 us. */ 11362306a36Sopenharmony_ci radio->buffer[3] = (radio->stereo ? 0 : 1) | (radio->preemph_75_us ? 4 : 0); 11462306a36Sopenharmony_ci radio->buffer[4] = 0x00; 11562306a36Sopenharmony_ci radio->buffer[5] = 0x00; 11662306a36Sopenharmony_ci radio->buffer[6] = 0x00; 11762306a36Sopenharmony_ci radio->buffer[7] = 0x00; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0), 12062306a36Sopenharmony_ci 9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci if (ret < 0) { 12362306a36Sopenharmony_ci dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret); 12462306a36Sopenharmony_ci return ret; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci return 0; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/* Handle unplugging the device. 13062306a36Sopenharmony_ci * We call video_unregister_device in any case. 13162306a36Sopenharmony_ci * The last function called in this procedure is 13262306a36Sopenharmony_ci * usb_keene_device_release. 13362306a36Sopenharmony_ci */ 13462306a36Sopenharmony_cistatic void usb_keene_disconnect(struct usb_interface *intf) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci mutex_lock(&radio->lock); 13962306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 14062306a36Sopenharmony_ci video_unregister_device(&radio->vdev); 14162306a36Sopenharmony_ci v4l2_device_disconnect(&radio->v4l2_dev); 14262306a36Sopenharmony_ci mutex_unlock(&radio->lock); 14362306a36Sopenharmony_ci v4l2_device_put(&radio->v4l2_dev); 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic int usb_keene_suspend(struct usb_interface *intf, pm_message_t message) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci return keene_cmd_main(radio, 0, false); 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic int usb_keene_resume(struct usb_interface *intf) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf)); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci mdelay(50); 15862306a36Sopenharmony_ci keene_cmd_set(radio); 15962306a36Sopenharmony_ci keene_cmd_main(radio, radio->curfreq, true); 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 16462306a36Sopenharmony_ci struct v4l2_capability *v) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct keene_device *radio = video_drvdata(file); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci strscpy(v->driver, "radio-keene", sizeof(v->driver)); 16962306a36Sopenharmony_ci strscpy(v->card, "Keene FM Transmitter", sizeof(v->card)); 17062306a36Sopenharmony_ci usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic int vidioc_g_modulator(struct file *file, void *priv, 17562306a36Sopenharmony_ci struct v4l2_modulator *v) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci struct keene_device *radio = video_drvdata(file); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (v->index > 0) 18062306a36Sopenharmony_ci return -EINVAL; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 18362306a36Sopenharmony_ci v->rangelow = FREQ_MIN * FREQ_MUL; 18462306a36Sopenharmony_ci v->rangehigh = FREQ_MAX * FREQ_MUL; 18562306a36Sopenharmony_ci v->txsubchans = radio->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; 18662306a36Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO; 18762306a36Sopenharmony_ci return 0; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_cistatic int vidioc_s_modulator(struct file *file, void *priv, 19162306a36Sopenharmony_ci const struct v4l2_modulator *v) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci struct keene_device *radio = video_drvdata(file); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (v->index > 0) 19662306a36Sopenharmony_ci return -EINVAL; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci radio->stereo = (v->txsubchans == V4L2_TUNER_SUB_STEREO); 19962306a36Sopenharmony_ci return keene_cmd_set(radio); 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 20362306a36Sopenharmony_ci const struct v4l2_frequency *f) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct keene_device *radio = video_drvdata(file); 20662306a36Sopenharmony_ci unsigned freq = f->frequency; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 20962306a36Sopenharmony_ci return -EINVAL; 21062306a36Sopenharmony_ci freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL); 21162306a36Sopenharmony_ci return keene_cmd_main(radio, freq, true); 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 21562306a36Sopenharmony_ci struct v4l2_frequency *f) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct keene_device *radio = video_drvdata(file); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci if (f->tuner != 0) 22062306a36Sopenharmony_ci return -EINVAL; 22162306a36Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 22262306a36Sopenharmony_ci f->frequency = radio->curfreq; 22362306a36Sopenharmony_ci return 0; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic int keene_s_ctrl(struct v4l2_ctrl *ctrl) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci static const u8 db2tx[] = { 22962306a36Sopenharmony_ci /* -15, -12, -9, -6, -3, 0 dB */ 23062306a36Sopenharmony_ci 0x03, 0x13, 0x02, 0x12, 0x22, 0x32, 23162306a36Sopenharmony_ci /* 3, 6, 9, 12, 15, 18 dB */ 23262306a36Sopenharmony_ci 0x21, 0x31, 0x20, 0x30, 0x40, 0x50 23362306a36Sopenharmony_ci }; 23462306a36Sopenharmony_ci struct keene_device *radio = 23562306a36Sopenharmony_ci container_of(ctrl->handler, struct keene_device, hdl); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci switch (ctrl->id) { 23862306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 23962306a36Sopenharmony_ci radio->muted = ctrl->val; 24062306a36Sopenharmony_ci return keene_cmd_main(radio, 0, true); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci case V4L2_CID_TUNE_POWER_LEVEL: 24362306a36Sopenharmony_ci /* To go from dBuV to the register value we apply the 24462306a36Sopenharmony_ci following formula: */ 24562306a36Sopenharmony_ci radio->pa = (ctrl->val - 71) * 100 / 62; 24662306a36Sopenharmony_ci return keene_cmd_main(radio, 0, true); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci case V4L2_CID_TUNE_PREEMPHASIS: 24962306a36Sopenharmony_ci radio->preemph_75_us = ctrl->val == V4L2_PREEMPHASIS_75_uS; 25062306a36Sopenharmony_ci return keene_cmd_set(radio); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci case V4L2_CID_AUDIO_COMPRESSION_GAIN: 25362306a36Sopenharmony_ci radio->tx = db2tx[(ctrl->val - (s32)ctrl->minimum) / (s32)ctrl->step]; 25462306a36Sopenharmony_ci return keene_cmd_set(radio); 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci return -EINVAL; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci/* File system interface */ 26062306a36Sopenharmony_cistatic const struct v4l2_file_operations usb_keene_fops = { 26162306a36Sopenharmony_ci .owner = THIS_MODULE, 26262306a36Sopenharmony_ci .open = v4l2_fh_open, 26362306a36Sopenharmony_ci .release = v4l2_fh_release, 26462306a36Sopenharmony_ci .poll = v4l2_ctrl_poll, 26562306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 26662306a36Sopenharmony_ci}; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops keene_ctrl_ops = { 26962306a36Sopenharmony_ci .s_ctrl = keene_s_ctrl, 27062306a36Sopenharmony_ci}; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops usb_keene_ioctl_ops = { 27362306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 27462306a36Sopenharmony_ci .vidioc_g_modulator = vidioc_g_modulator, 27562306a36Sopenharmony_ci .vidioc_s_modulator = vidioc_s_modulator, 27662306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 27762306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 27862306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 27962306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 28062306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 28162306a36Sopenharmony_ci}; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic void usb_keene_video_device_release(struct v4l2_device *v4l2_dev) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci struct keene_device *radio = to_keene_dev(v4l2_dev); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* free rest memory */ 28862306a36Sopenharmony_ci v4l2_ctrl_handler_free(&radio->hdl); 28962306a36Sopenharmony_ci kfree(radio->buffer); 29062306a36Sopenharmony_ci kfree(radio); 29162306a36Sopenharmony_ci} 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci/* check if the device is present and register with v4l and usb if it is */ 29462306a36Sopenharmony_cistatic int usb_keene_probe(struct usb_interface *intf, 29562306a36Sopenharmony_ci const struct usb_device_id *id) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci struct usb_device *dev = interface_to_usbdev(intf); 29862306a36Sopenharmony_ci struct keene_device *radio; 29962306a36Sopenharmony_ci struct v4l2_ctrl_handler *hdl; 30062306a36Sopenharmony_ci int retval = 0; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci /* 30362306a36Sopenharmony_ci * The Keene FM transmitter USB device has the same USB ID as 30462306a36Sopenharmony_ci * the Logitech AudioHub Speaker, but it should ignore the hid. 30562306a36Sopenharmony_ci * Check if the name is that of the Keene device. 30662306a36Sopenharmony_ci * If not, then someone connected the AudioHub and we shouldn't 30762306a36Sopenharmony_ci * attempt to handle this driver. 30862306a36Sopenharmony_ci * For reference: the product name of the AudioHub is 30962306a36Sopenharmony_ci * "AudioHub Speaker". 31062306a36Sopenharmony_ci */ 31162306a36Sopenharmony_ci if (dev->product && strcmp(dev->product, "B-LINK USB Audio ")) 31262306a36Sopenharmony_ci return -ENODEV; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci radio = kzalloc(sizeof(struct keene_device), GFP_KERNEL); 31562306a36Sopenharmony_ci if (radio) 31662306a36Sopenharmony_ci radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci if (!radio || !radio->buffer) { 31962306a36Sopenharmony_ci dev_err(&intf->dev, "kmalloc for keene_device failed\n"); 32062306a36Sopenharmony_ci kfree(radio); 32162306a36Sopenharmony_ci retval = -ENOMEM; 32262306a36Sopenharmony_ci goto err; 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci hdl = &radio->hdl; 32662306a36Sopenharmony_ci v4l2_ctrl_handler_init(hdl, 4); 32762306a36Sopenharmony_ci v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_MUTE, 32862306a36Sopenharmony_ci 0, 1, 1, 0); 32962306a36Sopenharmony_ci v4l2_ctrl_new_std_menu(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS, 33062306a36Sopenharmony_ci V4L2_PREEMPHASIS_75_uS, 1, V4L2_PREEMPHASIS_50_uS); 33162306a36Sopenharmony_ci v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_POWER_LEVEL, 33262306a36Sopenharmony_ci 84, 118, 1, 118); 33362306a36Sopenharmony_ci v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_COMPRESSION_GAIN, 33462306a36Sopenharmony_ci -15, 18, 3, 0); 33562306a36Sopenharmony_ci radio->pa = 118; 33662306a36Sopenharmony_ci radio->tx = 0x32; 33762306a36Sopenharmony_ci radio->stereo = true; 33862306a36Sopenharmony_ci if (hdl->error) { 33962306a36Sopenharmony_ci retval = hdl->error; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci v4l2_ctrl_handler_free(hdl); 34262306a36Sopenharmony_ci goto err_v4l2; 34362306a36Sopenharmony_ci } 34462306a36Sopenharmony_ci retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev); 34562306a36Sopenharmony_ci if (retval < 0) { 34662306a36Sopenharmony_ci dev_err(&intf->dev, "couldn't register v4l2_device\n"); 34762306a36Sopenharmony_ci goto err_v4l2; 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci mutex_init(&radio->lock); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci radio->v4l2_dev.ctrl_handler = hdl; 35362306a36Sopenharmony_ci radio->v4l2_dev.release = usb_keene_video_device_release; 35462306a36Sopenharmony_ci strscpy(radio->vdev.name, radio->v4l2_dev.name, 35562306a36Sopenharmony_ci sizeof(radio->vdev.name)); 35662306a36Sopenharmony_ci radio->vdev.v4l2_dev = &radio->v4l2_dev; 35762306a36Sopenharmony_ci radio->vdev.fops = &usb_keene_fops; 35862306a36Sopenharmony_ci radio->vdev.ioctl_ops = &usb_keene_ioctl_ops; 35962306a36Sopenharmony_ci radio->vdev.lock = &radio->lock; 36062306a36Sopenharmony_ci radio->vdev.release = video_device_release_empty; 36162306a36Sopenharmony_ci radio->vdev.vfl_dir = VFL_DIR_TX; 36262306a36Sopenharmony_ci radio->vdev.device_caps = V4L2_CAP_RADIO | V4L2_CAP_MODULATOR; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci radio->usbdev = interface_to_usbdev(intf); 36562306a36Sopenharmony_ci radio->intf = intf; 36662306a36Sopenharmony_ci usb_set_intfdata(intf, &radio->v4l2_dev); 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci video_set_drvdata(&radio->vdev, radio); 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci /* at least 11ms is needed in order to settle hardware */ 37162306a36Sopenharmony_ci msleep(20); 37262306a36Sopenharmony_ci keene_cmd_main(radio, 95.16 * FREQ_MUL, false); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1); 37562306a36Sopenharmony_ci if (retval < 0) { 37662306a36Sopenharmony_ci dev_err(&intf->dev, "could not register video device\n"); 37762306a36Sopenharmony_ci goto err_vdev; 37862306a36Sopenharmony_ci } 37962306a36Sopenharmony_ci v4l2_ctrl_handler_setup(hdl); 38062306a36Sopenharmony_ci dev_info(&intf->dev, "V4L2 device registered as %s\n", 38162306a36Sopenharmony_ci video_device_node_name(&radio->vdev)); 38262306a36Sopenharmony_ci return 0; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_cierr_vdev: 38562306a36Sopenharmony_ci v4l2_device_unregister(&radio->v4l2_dev); 38662306a36Sopenharmony_cierr_v4l2: 38762306a36Sopenharmony_ci kfree(radio->buffer); 38862306a36Sopenharmony_ci kfree(radio); 38962306a36Sopenharmony_cierr: 39062306a36Sopenharmony_ci return retval; 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci/* USB subsystem interface */ 39462306a36Sopenharmony_cistatic struct usb_driver usb_keene_driver = { 39562306a36Sopenharmony_ci .name = "radio-keene", 39662306a36Sopenharmony_ci .probe = usb_keene_probe, 39762306a36Sopenharmony_ci .disconnect = usb_keene_disconnect, 39862306a36Sopenharmony_ci .id_table = usb_keene_device_table, 39962306a36Sopenharmony_ci .suspend = usb_keene_suspend, 40062306a36Sopenharmony_ci .resume = usb_keene_resume, 40162306a36Sopenharmony_ci .reset_resume = usb_keene_resume, 40262306a36Sopenharmony_ci}; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_cimodule_usb_driver(usb_keene_driver); 40562306a36Sopenharmony_ci 406