162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com> 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Original author: 662306a36Sopenharmony_ci * Ben Collins <bcollins@ubuntu.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Additional work by: 962306a36Sopenharmony_ci * John Brooks <john.brooks@bluecherry.net> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/kthread.h> 1562306a36Sopenharmony_ci#include <linux/freezer.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 1862306a36Sopenharmony_ci#include <media/v4l2-common.h> 1962306a36Sopenharmony_ci#include <media/v4l2-event.h> 2062306a36Sopenharmony_ci#include <media/videobuf2-v4l2.h> 2162306a36Sopenharmony_ci#include <media/videobuf2-dma-contig.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include "solo6x10.h" 2462306a36Sopenharmony_ci#include "solo6x10-tw28.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* Image size is two fields, SOLO_HW_BPL is one horizontal line in hardware */ 2762306a36Sopenharmony_ci#define SOLO_HW_BPL 2048 2862306a36Sopenharmony_ci#define solo_vlines(__solo) (__solo->video_vsize * 2) 2962306a36Sopenharmony_ci#define solo_image_size(__solo) (solo_bytesperline(__solo) * \ 3062306a36Sopenharmony_ci solo_vlines(__solo)) 3162306a36Sopenharmony_ci#define solo_bytesperline(__solo) (__solo->video_hsize * 2) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define MIN_VID_BUFFERS 2 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic inline void erase_on(struct solo_dev *solo_dev) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON); 3862306a36Sopenharmony_ci solo_dev->erasing = 1; 3962306a36Sopenharmony_ci solo_dev->frame_blank = 0; 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic inline int erase_off(struct solo_dev *solo_dev) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci if (!solo_dev->erasing) 4562306a36Sopenharmony_ci return 0; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci /* First time around, assert erase off */ 4862306a36Sopenharmony_ci if (!solo_dev->frame_blank) 4962306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, 0); 5062306a36Sopenharmony_ci /* Keep the erasing flag on for 8 frames minimum */ 5162306a36Sopenharmony_ci if (solo_dev->frame_blank++ >= 8) 5262306a36Sopenharmony_ci solo_dev->erasing = 0; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci return 1; 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_civoid solo_video_in_isr(struct solo_dev *solo_dev) 5862306a36Sopenharmony_ci{ 5962306a36Sopenharmony_ci wake_up_interruptible_all(&solo_dev->disp_thread_wait); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic void solo_win_setup(struct solo_dev *solo_dev, u8 ch, 6362306a36Sopenharmony_ci int sx, int sy, int ex, int ey, int scale) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci if (ch >= solo_dev->nr_chans) 6662306a36Sopenharmony_ci return; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci /* Here, we just keep window/channel the same */ 6962306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(ch), 7062306a36Sopenharmony_ci SOLO_VI_WIN_CHANNEL(ch) | 7162306a36Sopenharmony_ci SOLO_VI_WIN_SX(sx) | 7262306a36Sopenharmony_ci SOLO_VI_WIN_EX(ex) | 7362306a36Sopenharmony_ci SOLO_VI_WIN_SCALE(scale)); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(ch), 7662306a36Sopenharmony_ci SOLO_VI_WIN_SY(sy) | 7762306a36Sopenharmony_ci SOLO_VI_WIN_EY(ey)); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic int solo_v4l2_ch_ext_4up(struct solo_dev *solo_dev, u8 idx, int on) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci u8 ch = idx * 4; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (ch >= solo_dev->nr_chans) 8562306a36Sopenharmony_ci return -EINVAL; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (!on) { 8862306a36Sopenharmony_ci u8 i; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci for (i = ch; i < ch + 4; i++) 9162306a36Sopenharmony_ci solo_win_setup(solo_dev, i, solo_dev->video_hsize, 9262306a36Sopenharmony_ci solo_vlines(solo_dev), 9362306a36Sopenharmony_ci solo_dev->video_hsize, 9462306a36Sopenharmony_ci solo_vlines(solo_dev), 0); 9562306a36Sopenharmony_ci return 0; 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* Row 1 */ 9962306a36Sopenharmony_ci solo_win_setup(solo_dev, ch, 0, 0, solo_dev->video_hsize / 2, 10062306a36Sopenharmony_ci solo_vlines(solo_dev) / 2, 3); 10162306a36Sopenharmony_ci solo_win_setup(solo_dev, ch + 1, solo_dev->video_hsize / 2, 0, 10262306a36Sopenharmony_ci solo_dev->video_hsize, solo_vlines(solo_dev) / 2, 3); 10362306a36Sopenharmony_ci /* Row 2 */ 10462306a36Sopenharmony_ci solo_win_setup(solo_dev, ch + 2, 0, solo_vlines(solo_dev) / 2, 10562306a36Sopenharmony_ci solo_dev->video_hsize / 2, solo_vlines(solo_dev), 3); 10662306a36Sopenharmony_ci solo_win_setup(solo_dev, ch + 3, solo_dev->video_hsize / 2, 10762306a36Sopenharmony_ci solo_vlines(solo_dev) / 2, solo_dev->video_hsize, 10862306a36Sopenharmony_ci solo_vlines(solo_dev), 3); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci return 0; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int solo_v4l2_ch_ext_16up(struct solo_dev *solo_dev, int on) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci int sy, ysize, hsize, i; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (!on) { 11862306a36Sopenharmony_ci for (i = 0; i < 16; i++) 11962306a36Sopenharmony_ci solo_win_setup(solo_dev, i, solo_dev->video_hsize, 12062306a36Sopenharmony_ci solo_vlines(solo_dev), 12162306a36Sopenharmony_ci solo_dev->video_hsize, 12262306a36Sopenharmony_ci solo_vlines(solo_dev), 0); 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci ysize = solo_vlines(solo_dev) / 4; 12762306a36Sopenharmony_ci hsize = solo_dev->video_hsize / 4; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci for (sy = 0, i = 0; i < 4; i++, sy += ysize) { 13062306a36Sopenharmony_ci solo_win_setup(solo_dev, i * 4, 0, sy, hsize, 13162306a36Sopenharmony_ci sy + ysize, 5); 13262306a36Sopenharmony_ci solo_win_setup(solo_dev, (i * 4) + 1, hsize, sy, 13362306a36Sopenharmony_ci hsize * 2, sy + ysize, 5); 13462306a36Sopenharmony_ci solo_win_setup(solo_dev, (i * 4) + 2, hsize * 2, sy, 13562306a36Sopenharmony_ci hsize * 3, sy + ysize, 5); 13662306a36Sopenharmony_ci solo_win_setup(solo_dev, (i * 4) + 3, hsize * 3, sy, 13762306a36Sopenharmony_ci solo_dev->video_hsize, sy + ysize, 5); 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int solo_v4l2_ch(struct solo_dev *solo_dev, u8 ch, int on) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci u8 ext_ch; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci if (ch < solo_dev->nr_chans) { 14862306a36Sopenharmony_ci solo_win_setup(solo_dev, ch, on ? 0 : solo_dev->video_hsize, 14962306a36Sopenharmony_ci on ? 0 : solo_vlines(solo_dev), 15062306a36Sopenharmony_ci solo_dev->video_hsize, solo_vlines(solo_dev), 15162306a36Sopenharmony_ci on ? 1 : 0); 15262306a36Sopenharmony_ci return 0; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (ch >= solo_dev->nr_chans + solo_dev->nr_ext) 15662306a36Sopenharmony_ci return -EINVAL; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci ext_ch = ch - solo_dev->nr_chans; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* 4up's first */ 16162306a36Sopenharmony_ci if (ext_ch < 4) 16262306a36Sopenharmony_ci return solo_v4l2_ch_ext_4up(solo_dev, ext_ch, on); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* Remaining case is 16up for 16-port */ 16562306a36Sopenharmony_ci return solo_v4l2_ch_ext_16up(solo_dev, on); 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic int solo_v4l2_set_ch(struct solo_dev *solo_dev, u8 ch) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci if (ch >= solo_dev->nr_chans + solo_dev->nr_ext) 17162306a36Sopenharmony_ci return -EINVAL; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci erase_on(solo_dev); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci solo_v4l2_ch(solo_dev, solo_dev->cur_disp_ch, 0); 17662306a36Sopenharmony_ci solo_v4l2_ch(solo_dev, ch, 1); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci solo_dev->cur_disp_ch = ch; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci return 0; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic void solo_fillbuf(struct solo_dev *solo_dev, 18462306a36Sopenharmony_ci struct vb2_buffer *vb) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 18762306a36Sopenharmony_ci dma_addr_t addr; 18862306a36Sopenharmony_ci unsigned int fdma_addr; 18962306a36Sopenharmony_ci int error = -1; 19062306a36Sopenharmony_ci int i; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci addr = vb2_dma_contig_plane_dma_addr(vb, 0); 19362306a36Sopenharmony_ci if (!addr) 19462306a36Sopenharmony_ci goto finish_buf; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (erase_off(solo_dev)) { 19762306a36Sopenharmony_ci void *p = vb2_plane_vaddr(vb, 0); 19862306a36Sopenharmony_ci int image_size = solo_image_size(solo_dev); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci for (i = 0; i < image_size; i += 2) { 20162306a36Sopenharmony_ci ((u8 *)p)[i] = 0x80; 20262306a36Sopenharmony_ci ((u8 *)p)[i + 1] = 0x00; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci error = 0; 20562306a36Sopenharmony_ci } else { 20662306a36Sopenharmony_ci fdma_addr = SOLO_DISP_EXT_ADDR + (solo_dev->old_write * 20762306a36Sopenharmony_ci (SOLO_HW_BPL * solo_vlines(solo_dev))); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci error = solo_p2m_dma_t(solo_dev, 0, addr, fdma_addr, 21062306a36Sopenharmony_ci solo_bytesperline(solo_dev), 21162306a36Sopenharmony_ci solo_vlines(solo_dev), SOLO_HW_BPL); 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cifinish_buf: 21562306a36Sopenharmony_ci if (!error) { 21662306a36Sopenharmony_ci vb2_set_plane_payload(vb, 0, 21762306a36Sopenharmony_ci solo_vlines(solo_dev) * solo_bytesperline(solo_dev)); 21862306a36Sopenharmony_ci vbuf->sequence = solo_dev->sequence++; 21962306a36Sopenharmony_ci vb->timestamp = ktime_get_ns(); 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci vb2_buffer_done(vb, error ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic void solo_thread_try(struct solo_dev *solo_dev) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci struct solo_vb2_buf *vb; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci /* Only "break" from this loop if slock is held, otherwise 23062306a36Sopenharmony_ci * just return. */ 23162306a36Sopenharmony_ci for (;;) { 23262306a36Sopenharmony_ci unsigned int cur_write; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci cur_write = SOLO_VI_STATUS0_PAGE( 23562306a36Sopenharmony_ci solo_reg_read(solo_dev, SOLO_VI_STATUS0)); 23662306a36Sopenharmony_ci if (cur_write == solo_dev->old_write) 23762306a36Sopenharmony_ci return; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci spin_lock(&solo_dev->slock); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (list_empty(&solo_dev->vidq_active)) 24262306a36Sopenharmony_ci break; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci vb = list_first_entry(&solo_dev->vidq_active, struct solo_vb2_buf, 24562306a36Sopenharmony_ci list); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci solo_dev->old_write = cur_write; 24862306a36Sopenharmony_ci list_del(&vb->list); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci spin_unlock(&solo_dev->slock); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci solo_fillbuf(solo_dev, &vb->vb.vb2_buf); 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci assert_spin_locked(&solo_dev->slock); 25662306a36Sopenharmony_ci spin_unlock(&solo_dev->slock); 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic int solo_thread(void *data) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci struct solo_dev *solo_dev = data; 26262306a36Sopenharmony_ci DECLARE_WAITQUEUE(wait, current); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci set_freezable(); 26562306a36Sopenharmony_ci add_wait_queue(&solo_dev->disp_thread_wait, &wait); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci for (;;) { 26862306a36Sopenharmony_ci long timeout = schedule_timeout_interruptible(HZ); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if (timeout == -ERESTARTSYS || kthread_should_stop()) 27162306a36Sopenharmony_ci break; 27262306a36Sopenharmony_ci solo_thread_try(solo_dev); 27362306a36Sopenharmony_ci try_to_freeze(); 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci remove_wait_queue(&solo_dev->disp_thread_wait, &wait); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci return 0; 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic int solo_start_thread(struct solo_dev *solo_dev) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci int ret = 0; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci solo_dev->kthread = kthread_run(solo_thread, solo_dev, SOLO6X10_NAME "_disp"); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci if (IS_ERR(solo_dev->kthread)) { 28862306a36Sopenharmony_ci ret = PTR_ERR(solo_dev->kthread); 28962306a36Sopenharmony_ci solo_dev->kthread = NULL; 29062306a36Sopenharmony_ci return ret; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci return ret; 29562306a36Sopenharmony_ci} 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistatic void solo_stop_thread(struct solo_dev *solo_dev) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci if (!solo_dev->kthread) 30062306a36Sopenharmony_ci return; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN); 30362306a36Sopenharmony_ci kthread_stop(solo_dev->kthread); 30462306a36Sopenharmony_ci solo_dev->kthread = NULL; 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_cistatic int solo_queue_setup(struct vb2_queue *q, 30862306a36Sopenharmony_ci unsigned int *num_buffers, unsigned int *num_planes, 30962306a36Sopenharmony_ci unsigned int sizes[], struct device *alloc_devs[]) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci struct solo_dev *solo_dev = vb2_get_drv_priv(q); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci sizes[0] = solo_image_size(solo_dev); 31462306a36Sopenharmony_ci *num_planes = 1; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (*num_buffers < MIN_VID_BUFFERS) 31762306a36Sopenharmony_ci *num_buffers = MIN_VID_BUFFERS; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci return 0; 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_cistatic int solo_start_streaming(struct vb2_queue *q, unsigned int count) 32362306a36Sopenharmony_ci{ 32462306a36Sopenharmony_ci struct solo_dev *solo_dev = vb2_get_drv_priv(q); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci solo_dev->sequence = 0; 32762306a36Sopenharmony_ci return solo_start_thread(solo_dev); 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cistatic void solo_stop_streaming(struct vb2_queue *q) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci struct solo_dev *solo_dev = vb2_get_drv_priv(q); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci solo_stop_thread(solo_dev); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci spin_lock(&solo_dev->slock); 33762306a36Sopenharmony_ci while (!list_empty(&solo_dev->vidq_active)) { 33862306a36Sopenharmony_ci struct solo_vb2_buf *buf = list_entry( 33962306a36Sopenharmony_ci solo_dev->vidq_active.next, 34062306a36Sopenharmony_ci struct solo_vb2_buf, list); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci list_del(&buf->list); 34362306a36Sopenharmony_ci vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci spin_unlock(&solo_dev->slock); 34662306a36Sopenharmony_ci INIT_LIST_HEAD(&solo_dev->vidq_active); 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_cistatic void solo_buf_queue(struct vb2_buffer *vb) 35062306a36Sopenharmony_ci{ 35162306a36Sopenharmony_ci struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 35262306a36Sopenharmony_ci struct vb2_queue *vq = vb->vb2_queue; 35362306a36Sopenharmony_ci struct solo_dev *solo_dev = vb2_get_drv_priv(vq); 35462306a36Sopenharmony_ci struct solo_vb2_buf *solo_vb = 35562306a36Sopenharmony_ci container_of(vbuf, struct solo_vb2_buf, vb); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci spin_lock(&solo_dev->slock); 35862306a36Sopenharmony_ci list_add_tail(&solo_vb->list, &solo_dev->vidq_active); 35962306a36Sopenharmony_ci spin_unlock(&solo_dev->slock); 36062306a36Sopenharmony_ci wake_up_interruptible(&solo_dev->disp_thread_wait); 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_cistatic const struct vb2_ops solo_video_qops = { 36462306a36Sopenharmony_ci .queue_setup = solo_queue_setup, 36562306a36Sopenharmony_ci .buf_queue = solo_buf_queue, 36662306a36Sopenharmony_ci .start_streaming = solo_start_streaming, 36762306a36Sopenharmony_ci .stop_streaming = solo_stop_streaming, 36862306a36Sopenharmony_ci .wait_prepare = vb2_ops_wait_prepare, 36962306a36Sopenharmony_ci .wait_finish = vb2_ops_wait_finish, 37062306a36Sopenharmony_ci}; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_cistatic int solo_querycap(struct file *file, void *priv, 37362306a36Sopenharmony_ci struct v4l2_capability *cap) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci strscpy(cap->driver, SOLO6X10_NAME, sizeof(cap->driver)); 37662306a36Sopenharmony_ci strscpy(cap->card, "Softlogic 6x10", sizeof(cap->card)); 37762306a36Sopenharmony_ci return 0; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic int solo_enum_ext_input(struct solo_dev *solo_dev, 38162306a36Sopenharmony_ci struct v4l2_input *input) 38262306a36Sopenharmony_ci{ 38362306a36Sopenharmony_ci int ext = input->index - solo_dev->nr_chans; 38462306a36Sopenharmony_ci unsigned int nup, first; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci if (ext >= solo_dev->nr_ext) 38762306a36Sopenharmony_ci return -EINVAL; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci nup = (ext == 4) ? 16 : 4; 39062306a36Sopenharmony_ci first = (ext & 3) << 2; /* first channel in the n-up */ 39162306a36Sopenharmony_ci snprintf(input->name, sizeof(input->name), 39262306a36Sopenharmony_ci "Multi %d-up (cameras %d-%d)", 39362306a36Sopenharmony_ci nup, first + 1, first + nup); 39462306a36Sopenharmony_ci /* Possible outputs: 39562306a36Sopenharmony_ci * Multi 4-up (cameras 1-4) 39662306a36Sopenharmony_ci * Multi 4-up (cameras 5-8) 39762306a36Sopenharmony_ci * Multi 4-up (cameras 9-12) 39862306a36Sopenharmony_ci * Multi 4-up (cameras 13-16) 39962306a36Sopenharmony_ci * Multi 16-up (cameras 1-16) 40062306a36Sopenharmony_ci */ 40162306a36Sopenharmony_ci return 0; 40262306a36Sopenharmony_ci} 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_cistatic int solo_enum_input(struct file *file, void *priv, 40562306a36Sopenharmony_ci struct v4l2_input *input) 40662306a36Sopenharmony_ci{ 40762306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci if (input->index >= solo_dev->nr_chans) { 41062306a36Sopenharmony_ci int ret = solo_enum_ext_input(solo_dev, input); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci if (ret < 0) 41362306a36Sopenharmony_ci return ret; 41462306a36Sopenharmony_ci } else { 41562306a36Sopenharmony_ci snprintf(input->name, sizeof(input->name), "Camera %d", 41662306a36Sopenharmony_ci input->index + 1); 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci /* We can only check this for normal inputs */ 41962306a36Sopenharmony_ci if (!tw28_get_video_status(solo_dev, input->index)) 42062306a36Sopenharmony_ci input->status = V4L2_IN_ST_NO_SIGNAL; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci input->type = V4L2_INPUT_TYPE_CAMERA; 42462306a36Sopenharmony_ci input->std = solo_dev->vfd->tvnorms; 42562306a36Sopenharmony_ci return 0; 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cistatic int solo_set_input(struct file *file, void *priv, unsigned int index) 42962306a36Sopenharmony_ci{ 43062306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 43162306a36Sopenharmony_ci int ret = solo_v4l2_set_ch(solo_dev, index); 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (!ret) { 43462306a36Sopenharmony_ci while (erase_off(solo_dev)) 43562306a36Sopenharmony_ci /* Do nothing */; 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci return ret; 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic int solo_get_input(struct file *file, void *priv, unsigned int *index) 44262306a36Sopenharmony_ci{ 44362306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci *index = solo_dev->cur_disp_ch; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci return 0; 44862306a36Sopenharmony_ci} 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_cistatic int solo_enum_fmt_cap(struct file *file, void *priv, 45162306a36Sopenharmony_ci struct v4l2_fmtdesc *f) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci if (f->index) 45462306a36Sopenharmony_ci return -EINVAL; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci f->pixelformat = V4L2_PIX_FMT_UYVY; 45762306a36Sopenharmony_ci return 0; 45862306a36Sopenharmony_ci} 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_cistatic int solo_try_fmt_cap(struct file *file, void *priv, 46162306a36Sopenharmony_ci struct v4l2_format *f) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 46462306a36Sopenharmony_ci struct v4l2_pix_format *pix = &f->fmt.pix; 46562306a36Sopenharmony_ci int image_size = solo_image_size(solo_dev); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci if (pix->pixelformat != V4L2_PIX_FMT_UYVY) 46862306a36Sopenharmony_ci return -EINVAL; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci pix->width = solo_dev->video_hsize; 47162306a36Sopenharmony_ci pix->height = solo_vlines(solo_dev); 47262306a36Sopenharmony_ci pix->sizeimage = image_size; 47362306a36Sopenharmony_ci pix->field = V4L2_FIELD_INTERLACED; 47462306a36Sopenharmony_ci pix->pixelformat = V4L2_PIX_FMT_UYVY; 47562306a36Sopenharmony_ci pix->colorspace = V4L2_COLORSPACE_SMPTE170M; 47662306a36Sopenharmony_ci return 0; 47762306a36Sopenharmony_ci} 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic int solo_set_fmt_cap(struct file *file, void *priv, 48062306a36Sopenharmony_ci struct v4l2_format *f) 48162306a36Sopenharmony_ci{ 48262306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci if (vb2_is_busy(&solo_dev->vidq)) 48562306a36Sopenharmony_ci return -EBUSY; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci /* For right now, if it doesn't match our running config, 48862306a36Sopenharmony_ci * then fail */ 48962306a36Sopenharmony_ci return solo_try_fmt_cap(file, priv, f); 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_cistatic int solo_get_fmt_cap(struct file *file, void *priv, 49362306a36Sopenharmony_ci struct v4l2_format *f) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 49662306a36Sopenharmony_ci struct v4l2_pix_format *pix = &f->fmt.pix; 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci pix->width = solo_dev->video_hsize; 49962306a36Sopenharmony_ci pix->height = solo_vlines(solo_dev); 50062306a36Sopenharmony_ci pix->pixelformat = V4L2_PIX_FMT_UYVY; 50162306a36Sopenharmony_ci pix->field = V4L2_FIELD_INTERLACED; 50262306a36Sopenharmony_ci pix->sizeimage = solo_image_size(solo_dev); 50362306a36Sopenharmony_ci pix->colorspace = V4L2_COLORSPACE_SMPTE170M; 50462306a36Sopenharmony_ci pix->bytesperline = solo_bytesperline(solo_dev); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci return 0; 50762306a36Sopenharmony_ci} 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_cistatic int solo_g_std(struct file *file, void *priv, v4l2_std_id *i) 51062306a36Sopenharmony_ci{ 51162306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) 51462306a36Sopenharmony_ci *i = V4L2_STD_NTSC_M; 51562306a36Sopenharmony_ci else 51662306a36Sopenharmony_ci *i = V4L2_STD_PAL; 51762306a36Sopenharmony_ci return 0; 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ciint solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz) 52162306a36Sopenharmony_ci{ 52262306a36Sopenharmony_ci int i; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci /* Make sure all video nodes are idle */ 52562306a36Sopenharmony_ci if (vb2_is_busy(&solo_dev->vidq)) 52662306a36Sopenharmony_ci return -EBUSY; 52762306a36Sopenharmony_ci for (i = 0; i < solo_dev->nr_chans; i++) 52862306a36Sopenharmony_ci if (vb2_is_busy(&solo_dev->v4l2_enc[i]->vidq)) 52962306a36Sopenharmony_ci return -EBUSY; 53062306a36Sopenharmony_ci solo_dev->video_type = is_50hz ? SOLO_VO_FMT_TYPE_PAL : 53162306a36Sopenharmony_ci SOLO_VO_FMT_TYPE_NTSC; 53262306a36Sopenharmony_ci /* Reconfigure for the new standard */ 53362306a36Sopenharmony_ci solo_disp_init(solo_dev); 53462306a36Sopenharmony_ci solo_enc_init(solo_dev); 53562306a36Sopenharmony_ci solo_tw28_init(solo_dev); 53662306a36Sopenharmony_ci for (i = 0; i < solo_dev->nr_chans; i++) 53762306a36Sopenharmony_ci solo_update_mode(solo_dev->v4l2_enc[i]); 53862306a36Sopenharmony_ci return solo_v4l2_set_ch(solo_dev, solo_dev->cur_disp_ch); 53962306a36Sopenharmony_ci} 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_cistatic int solo_s_std(struct file *file, void *priv, v4l2_std_id std) 54262306a36Sopenharmony_ci{ 54362306a36Sopenharmony_ci struct solo_dev *solo_dev = video_drvdata(file); 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci return solo_set_video_type(solo_dev, std & V4L2_STD_625_50); 54662306a36Sopenharmony_ci} 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cistatic int solo_s_ctrl(struct v4l2_ctrl *ctrl) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci struct solo_dev *solo_dev = 55162306a36Sopenharmony_ci container_of(ctrl->handler, struct solo_dev, disp_hdl); 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci switch (ctrl->id) { 55462306a36Sopenharmony_ci case V4L2_CID_MOTION_TRACE: 55562306a36Sopenharmony_ci if (ctrl->val) { 55662306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 55762306a36Sopenharmony_ci SOLO_VI_MOTION_Y_ADD | 55862306a36Sopenharmony_ci SOLO_VI_MOTION_Y_VALUE(0x20) | 55962306a36Sopenharmony_ci SOLO_VI_MOTION_CB_VALUE(0x10) | 56062306a36Sopenharmony_ci SOLO_VI_MOTION_CR_VALUE(0x10)); 56162306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 56262306a36Sopenharmony_ci SOLO_VI_MOTION_CR_ADD | 56362306a36Sopenharmony_ci SOLO_VI_MOTION_Y_VALUE(0x10) | 56462306a36Sopenharmony_ci SOLO_VI_MOTION_CB_VALUE(0x80) | 56562306a36Sopenharmony_ci SOLO_VI_MOTION_CR_VALUE(0x10)); 56662306a36Sopenharmony_ci } else { 56762306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0); 56862306a36Sopenharmony_ci solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0); 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci return 0; 57162306a36Sopenharmony_ci default: 57262306a36Sopenharmony_ci break; 57362306a36Sopenharmony_ci } 57462306a36Sopenharmony_ci return -EINVAL; 57562306a36Sopenharmony_ci} 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_cistatic const struct v4l2_file_operations solo_v4l2_fops = { 57862306a36Sopenharmony_ci .owner = THIS_MODULE, 57962306a36Sopenharmony_ci .open = v4l2_fh_open, 58062306a36Sopenharmony_ci .release = vb2_fop_release, 58162306a36Sopenharmony_ci .read = vb2_fop_read, 58262306a36Sopenharmony_ci .poll = vb2_fop_poll, 58362306a36Sopenharmony_ci .mmap = vb2_fop_mmap, 58462306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 58562306a36Sopenharmony_ci}; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = { 58862306a36Sopenharmony_ci .vidioc_querycap = solo_querycap, 58962306a36Sopenharmony_ci .vidioc_s_std = solo_s_std, 59062306a36Sopenharmony_ci .vidioc_g_std = solo_g_std, 59162306a36Sopenharmony_ci /* Input callbacks */ 59262306a36Sopenharmony_ci .vidioc_enum_input = solo_enum_input, 59362306a36Sopenharmony_ci .vidioc_s_input = solo_set_input, 59462306a36Sopenharmony_ci .vidioc_g_input = solo_get_input, 59562306a36Sopenharmony_ci /* Video capture format callbacks */ 59662306a36Sopenharmony_ci .vidioc_enum_fmt_vid_cap = solo_enum_fmt_cap, 59762306a36Sopenharmony_ci .vidioc_try_fmt_vid_cap = solo_try_fmt_cap, 59862306a36Sopenharmony_ci .vidioc_s_fmt_vid_cap = solo_set_fmt_cap, 59962306a36Sopenharmony_ci .vidioc_g_fmt_vid_cap = solo_get_fmt_cap, 60062306a36Sopenharmony_ci /* Streaming I/O */ 60162306a36Sopenharmony_ci .vidioc_reqbufs = vb2_ioctl_reqbufs, 60262306a36Sopenharmony_ci .vidioc_querybuf = vb2_ioctl_querybuf, 60362306a36Sopenharmony_ci .vidioc_qbuf = vb2_ioctl_qbuf, 60462306a36Sopenharmony_ci .vidioc_dqbuf = vb2_ioctl_dqbuf, 60562306a36Sopenharmony_ci .vidioc_streamon = vb2_ioctl_streamon, 60662306a36Sopenharmony_ci .vidioc_streamoff = vb2_ioctl_streamoff, 60762306a36Sopenharmony_ci /* Logging and events */ 60862306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 60962306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 61062306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 61162306a36Sopenharmony_ci}; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_cistatic const struct video_device solo_v4l2_template = { 61462306a36Sopenharmony_ci .name = SOLO6X10_NAME, 61562306a36Sopenharmony_ci .fops = &solo_v4l2_fops, 61662306a36Sopenharmony_ci .ioctl_ops = &solo_v4l2_ioctl_ops, 61762306a36Sopenharmony_ci .minor = -1, 61862306a36Sopenharmony_ci .release = video_device_release, 61962306a36Sopenharmony_ci .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL, 62062306a36Sopenharmony_ci .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | 62162306a36Sopenharmony_ci V4L2_CAP_STREAMING, 62262306a36Sopenharmony_ci}; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops solo_ctrl_ops = { 62562306a36Sopenharmony_ci .s_ctrl = solo_s_ctrl, 62662306a36Sopenharmony_ci}; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_cistatic const struct v4l2_ctrl_config solo_motion_trace_ctrl = { 62962306a36Sopenharmony_ci .ops = &solo_ctrl_ops, 63062306a36Sopenharmony_ci .id = V4L2_CID_MOTION_TRACE, 63162306a36Sopenharmony_ci .name = "Motion Detection Trace", 63262306a36Sopenharmony_ci .type = V4L2_CTRL_TYPE_BOOLEAN, 63362306a36Sopenharmony_ci .max = 1, 63462306a36Sopenharmony_ci .step = 1, 63562306a36Sopenharmony_ci}; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ciint solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr) 63862306a36Sopenharmony_ci{ 63962306a36Sopenharmony_ci int ret; 64062306a36Sopenharmony_ci int i; 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci init_waitqueue_head(&solo_dev->disp_thread_wait); 64362306a36Sopenharmony_ci spin_lock_init(&solo_dev->slock); 64462306a36Sopenharmony_ci mutex_init(&solo_dev->lock); 64562306a36Sopenharmony_ci INIT_LIST_HEAD(&solo_dev->vidq_active); 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci solo_dev->vfd = video_device_alloc(); 64862306a36Sopenharmony_ci if (!solo_dev->vfd) 64962306a36Sopenharmony_ci return -ENOMEM; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci *solo_dev->vfd = solo_v4l2_template; 65262306a36Sopenharmony_ci solo_dev->vfd->v4l2_dev = &solo_dev->v4l2_dev; 65362306a36Sopenharmony_ci solo_dev->vfd->queue = &solo_dev->vidq; 65462306a36Sopenharmony_ci solo_dev->vfd->lock = &solo_dev->lock; 65562306a36Sopenharmony_ci v4l2_ctrl_handler_init(&solo_dev->disp_hdl, 1); 65662306a36Sopenharmony_ci v4l2_ctrl_new_custom(&solo_dev->disp_hdl, &solo_motion_trace_ctrl, NULL); 65762306a36Sopenharmony_ci if (solo_dev->disp_hdl.error) { 65862306a36Sopenharmony_ci ret = solo_dev->disp_hdl.error; 65962306a36Sopenharmony_ci goto fail; 66062306a36Sopenharmony_ci } 66162306a36Sopenharmony_ci solo_dev->vfd->ctrl_handler = &solo_dev->disp_hdl; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci video_set_drvdata(solo_dev->vfd, solo_dev); 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci solo_dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 66662306a36Sopenharmony_ci solo_dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; 66762306a36Sopenharmony_ci solo_dev->vidq.ops = &solo_video_qops; 66862306a36Sopenharmony_ci solo_dev->vidq.mem_ops = &vb2_dma_contig_memops; 66962306a36Sopenharmony_ci solo_dev->vidq.drv_priv = solo_dev; 67062306a36Sopenharmony_ci solo_dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; 67162306a36Sopenharmony_ci solo_dev->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM; 67262306a36Sopenharmony_ci solo_dev->vidq.buf_struct_size = sizeof(struct solo_vb2_buf); 67362306a36Sopenharmony_ci solo_dev->vidq.lock = &solo_dev->lock; 67462306a36Sopenharmony_ci solo_dev->vidq.dev = &solo_dev->pdev->dev; 67562306a36Sopenharmony_ci ret = vb2_queue_init(&solo_dev->vidq); 67662306a36Sopenharmony_ci if (ret < 0) 67762306a36Sopenharmony_ci goto fail; 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci /* Cycle all the channels and clear */ 68062306a36Sopenharmony_ci for (i = 0; i < solo_dev->nr_chans; i++) { 68162306a36Sopenharmony_ci solo_v4l2_set_ch(solo_dev, i); 68262306a36Sopenharmony_ci while (erase_off(solo_dev)) 68362306a36Sopenharmony_ci /* Do nothing */; 68462306a36Sopenharmony_ci } 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci /* Set the default display channel */ 68762306a36Sopenharmony_ci solo_v4l2_set_ch(solo_dev, 0); 68862306a36Sopenharmony_ci while (erase_off(solo_dev)) 68962306a36Sopenharmony_ci /* Do nothing */; 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci ret = video_register_device(solo_dev->vfd, VFL_TYPE_VIDEO, nr); 69262306a36Sopenharmony_ci if (ret < 0) 69362306a36Sopenharmony_ci goto fail; 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)", 69662306a36Sopenharmony_ci SOLO6X10_NAME, solo_dev->vfd->num); 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci dev_info(&solo_dev->pdev->dev, "Display as /dev/video%d with %d inputs (%d extended)\n", 69962306a36Sopenharmony_ci solo_dev->vfd->num, 70062306a36Sopenharmony_ci solo_dev->nr_chans, solo_dev->nr_ext); 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci return 0; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_cifail: 70562306a36Sopenharmony_ci video_device_release(solo_dev->vfd); 70662306a36Sopenharmony_ci v4l2_ctrl_handler_free(&solo_dev->disp_hdl); 70762306a36Sopenharmony_ci solo_dev->vfd = NULL; 70862306a36Sopenharmony_ci return ret; 70962306a36Sopenharmony_ci} 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_civoid solo_v4l2_exit(struct solo_dev *solo_dev) 71262306a36Sopenharmony_ci{ 71362306a36Sopenharmony_ci if (solo_dev->vfd == NULL) 71462306a36Sopenharmony_ci return; 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci video_unregister_device(solo_dev->vfd); 71762306a36Sopenharmony_ci v4l2_ctrl_handler_free(&solo_dev->disp_hdl); 71862306a36Sopenharmony_ci solo_dev->vfd = NULL; 71962306a36Sopenharmony_ci} 720