2013-04-25 19:16:14

by Jon Arne Jørgensen

[permalink] [raw]
Subject: [RFC V2 0/3] Add a driver for Somagic smi2021

This is the 2nd version of a RFC for a driver for the Somagic SMI2021.
The first version was sendt on 14th of Mars 2013, and can be found here:
http://www.spinics.net/lists/kernel/msg1499018.html

The smi2021 is the usb controller for a range of video capture devices
branded as EasyCap.

The device consists of three major components.
* smi2021 is the usb controller.
* gm7113c is a saa7113 clone for video A/D conversion.
* cs5340 is an audio A/D converter.

The smi2021 chip is in most configurations dependent of some firmware to work.
The biggest change from the last version of this RFC is that I've included
the bootloader module that was responsible for the firmware upload into
the main driver module.

I've also made some changes to the saa7115 module to handle the gm7113c chip.

V4L2-Compliance:

Driver Info:
Driver name : smi2021
Card type : smi2021
Bus info : usb-0000:00:1d.0-1.1
Driver version: 3.9.0
Capabilities : 0x85000001
Video Capture
Read/Write
Streaming
Device Capabilities
Device Caps : 0x05000001
Video Capture
Read/Write
Streaming

Compliance test for device /dev/video1 (not using libv4l2):

Required ioctls:
test VIDIOC_QUERYCAP: OK

Allow for multiple opens:
test second video open: OK
test VIDIOC_QUERYCAP: OK
test VIDIOC_G/S_PRIORITY: OK

Debug ioctls:
test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported)
test VIDIOC_LOG_STATUS: OK

Input ioctls:
test VIDIOC_G/S_TUNER: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 2 Audio Inputs: 0 Tuners: 0

Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0

Control ioctls:
test VIDIOC_QUERYCTRL/MENU: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 7 Private Controls: 0

Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)

Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK
test VIDIOC_G/S_PARM: OK
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK
test VIDIOC_TRY_FMT: OK
test VIDIOC_S_FMT: OK
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)

Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)

Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK

Total: 36, Succeeded: 36, Failed: 0, Warnings: 0

[smi2021] Add gm7113c chip to the saa7115 driver
[smi2021] This is the smi2021 driver
[smi2021] Add smi2021 driver to buildsystem

drivers/media/i2c/saa7115.c | 61 ++-
drivers/media/usb/Kconfig | 1 +
drivers/media/usb/Makefile | 1 +
drivers/media/usb/smi2021/Kconfig | 11 +
drivers/media/usb/smi2021/Makefile | 10 +
drivers/media/usb/smi2021/smi2021.h | 278 +++++++++++++
drivers/media/usb/smi2021/smi2021_audio.c | 380 +++++++++++++++++
drivers/media/usb/smi2021/smi2021_bootloader.c | 261 ++++++++++++
drivers/media/usb/smi2021/smi2021_i2c.c | 137 +++++++
drivers/media/usb/smi2021/smi2021_main.c | 431 ++++++++++++++++++++
drivers/media/usb/smi2021/smi2021_v4l2.c | 542 ++++++++++++++++++++++++
drivers/media/usb/smi2021/smi2021_video.c | 544 +++++++++++++++++++++++++
include/media/v4l2-chip-ident.h | 3 +
13 files changed, 2650 insertions(+), 10 deletions(-)
create mode 100644 drivers/media/usb/smi2021/Kconfig
create mode 100644 drivers/media/usb/smi2021/Makefile
create mode 100644 drivers/media/usb/smi2021/smi2021.h
create mode 100644 drivers/media/usb/smi2021/smi2021_audio.c
create mode 100644 drivers/media/usb/smi2021/smi2021_bootloader.c
create mode 100644 drivers/media/usb/smi2021/smi2021_i2c.c
create mode 100644 drivers/media/usb/smi2021/smi2021_main.c
create mode 100644 drivers/media/usb/smi2021/smi2021_v4l2.c
create mode 100644 drivers/media/usb/smi2021/smi2021_video.c

Comments are welcome.

Best regards,
Jon Arne Jørgensen


2013-04-25 19:15:28

by Jon Arne Jørgensen

[permalink] [raw]
Subject: [RFC V2 3/3] [smi2021] Add smi2021 driver to buildsystem


Signed-off-by: Jon Arne Jørgensen <[email protected]>
---
drivers/media/usb/Kconfig | 1 +
drivers/media/usb/Makefile | 1 +
drivers/media/usb/smi2021/Kconfig | 11 +++++++++++
drivers/media/usb/smi2021/Makefile | 10 ++++++++++
4 files changed, 23 insertions(+)
create mode 100644 drivers/media/usb/smi2021/Kconfig
create mode 100644 drivers/media/usb/smi2021/Makefile

diff --git a/drivers/media/usb/Kconfig b/drivers/media/usb/Kconfig
index 0a7d520..dec0383 100644
--- a/drivers/media/usb/Kconfig
+++ b/drivers/media/usb/Kconfig
@@ -26,6 +26,7 @@ source "drivers/media/usb/hdpvr/Kconfig"
source "drivers/media/usb/tlg2300/Kconfig"
source "drivers/media/usb/usbvision/Kconfig"
source "drivers/media/usb/stk1160/Kconfig"
+source "drivers/media/usb/smi2021/Kconfig"
endif

if (MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT)
diff --git a/drivers/media/usb/Makefile b/drivers/media/usb/Makefile
index 7f51d7e..932a6ba 100644
--- a/drivers/media/usb/Makefile
+++ b/drivers/media/usb/Makefile
@@ -20,3 +20,4 @@ obj-$(CONFIG_VIDEO_STK1160) += stk1160/
obj-$(CONFIG_VIDEO_CX231XX) += cx231xx/
obj-$(CONFIG_VIDEO_TM6000) += tm6000/
obj-$(CONFIG_VIDEO_EM28XX) += em28xx/
+obj-$(CONFIG_VIDEO_SMI2021) += smi2021/
diff --git a/drivers/media/usb/smi2021/Kconfig b/drivers/media/usb/smi2021/Kconfig
new file mode 100644
index 0000000..6a6fb8a
--- /dev/null
+++ b/drivers/media/usb/smi2021/Kconfig
@@ -0,0 +1,11 @@
+config VIDEO_SMI2021
+ tristate "Somagic SMI2021 USB video/audio capture support"
+ depends on VIDEO_DEV && I2C && SND && USB
+ select VIDEOBUF2_VMALLOC
+ select VIDEO_SAA711X
+ select SND_PCM
+ help
+ This is a video4linux driver for SMI2021 based video capture devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called smi2021
diff --git a/drivers/media/usb/smi2021/Makefile b/drivers/media/usb/smi2021/Makefile
new file mode 100644
index 0000000..8a62f02
--- /dev/null
+++ b/drivers/media/usb/smi2021/Makefile
@@ -0,0 +1,10 @@
+smi2021-y := smi2021_main.o \
+ smi2021_bootloader.o \
+ smi2021_v4l2.o \
+ smi2021_video.o \
+ smi2021_i2c.o \
+ smi2021_audio.o \
+
+obj-$(CONFIG_VIDEO_SMI2021) += smi2021.o
+
+ccflags-y += -Idrivers/media/i2c
--
1.8.2.1

2013-04-25 19:15:47

by Jon Arne Jørgensen

[permalink] [raw]
Subject: [RFC V2 2/3] [smi2021] This is the smi2021 driver


Signed-off-by: Jon Arne Jørgensen <[email protected]>
---
drivers/media/usb/smi2021/smi2021.h | 278 +++++++++++++
drivers/media/usb/smi2021/smi2021_audio.c | 380 +++++++++++++++++
drivers/media/usb/smi2021/smi2021_bootloader.c | 261 ++++++++++++
drivers/media/usb/smi2021/smi2021_i2c.c | 137 +++++++
drivers/media/usb/smi2021/smi2021_main.c | 431 ++++++++++++++++++++
drivers/media/usb/smi2021/smi2021_v4l2.c | 542 ++++++++++++++++++++++++
drivers/media/usb/smi2021/smi2021_video.c | 544 +++++++++++++++++++++++++
7 files changed, 2573 insertions(+)
create mode 100644 drivers/media/usb/smi2021/smi2021.h
create mode 100644 drivers/media/usb/smi2021/smi2021_audio.c
create mode 100644 drivers/media/usb/smi2021/smi2021_bootloader.c
create mode 100644 drivers/media/usb/smi2021/smi2021_i2c.c
create mode 100644 drivers/media/usb/smi2021/smi2021_main.c
create mode 100644 drivers/media/usb/smi2021/smi2021_v4l2.c
create mode 100644 drivers/media/usb/smi2021/smi2021_video.c

diff --git a/drivers/media/usb/smi2021/smi2021.h b/drivers/media/usb/smi2021/smi2021.h
new file mode 100644
index 0000000..bf2235a
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021.h
@@ -0,0 +1,278 @@
+/*******************************************************************************
+ * smi2021.h *
+ * *
+ * USB Driver for SMI2021 - EasyCap *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#ifndef SMI2021_H
+#define SMI2021_H
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/types.h>
+#include <linux/spinlock_types.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+
+#include <media/v4l2-event.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-chip-ident.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-vmalloc.h>
+#include <media/saa7115.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+
+#define SMI2021_DRIVER_VERSION "0.1"
+
+/* For ISOC */
+#define SMI2021_MAX_PKT_SIZE 3072
+#define SMI2021_ISOC_PACKETS 10 /* 64 */
+#define SMI2021_ISOC_BUFS 4 /* 16 */
+#define SMI2021_ISOC_EP 0x82
+
+/* General USB control setup */
+#define SMI2021_USB_REQUEST 0x01
+#define SMI2021_USB_INDEX 0x00
+#define SMI2021_USB_PIPE_OUT 0x00
+#define SMI2021_USB_PIPE_IN 0x80
+
+/* Hardware constants */
+#define SMI2021_HW_STATE_HEAD 0x01
+#define SMI2021_HW_STATE_STANDBY 0x03
+#define SMI2021_HW_STATE_CAPTURE 0x05
+
+#define SMI2021_CTRL_HEAD 0x0b
+
+/* Flags passed to the device in control transfers */
+#define SMI2021_DATA_TYPE_FLAG_I2C 0x80
+#define SMI2021_DATA_TYPE_FLAG_WRITE 0x40
+#define SMI2021_DATA_TYPE_FLAG_READ 0x20
+#define SMI2021_DATA_TYPE_FLAG_PREPARE_READ 0x04
+
+#define SMI2021_DATA_OFFSET_FLAG_SMI 0x80
+#define SMI2021_DATA_OFFSET_SIZE_MASK 0x0f
+
+/* smi2021 ports */
+#define SMI2021_DDR_PORT 0x3a
+#define SMI2021_DATA_PORT 0x3b
+#define SMI2021_RESET_PIN 0x80
+#define SMI2021_AUDIO_PORT 0x1740
+#define SMI2021_AUDIO_ENABLE 0x1d
+
+/* General video constants */
+#define SMI2021_BYTES_PER_LINE 1440
+#define SMI2021_PAL_LINES 576
+#define SMI2021_NTSC_LINES 486
+
+/* Timing Referance Codes, see saa7113 datasheet */
+#define SMI2021_TRC_EAV 0x10
+#define SMI2021_TRC_VBI 0x20
+#define SMI2021_TRC_FIELD_2 0x40
+#define SMI2021_TRC 0x80
+
+#ifdef DEBUG
+#define smi2021_dbg(fmt, args...) \
+ pr_debug("smi2021::%s: " fmt, __func__, \
+ ##args)
+#else
+#define smi2021_dbg(fmt, args...)
+#endif
+
+#define smi2021_info(fmt, args...) \
+ pr_info("smi2021::%s: " fmt, \
+ __func__, ##args)
+
+#define smi2021_warn(fmt, args...) \
+ pr_warn("smi2021::%s: " fmt, \
+ __func__, ##args)
+
+#define smi2021_err(fmt, args...) \
+ pr_err("smi2021::%s: " fmt, \
+ __func__, ##args)
+
+enum smi2021_sync {
+ HSYNC,
+ SYNCZ1,
+ SYNCZ2,
+ TRC
+};
+
+enum smi2021_config {
+ SMI2021_DUAL_INPUT,
+ SMI2021_QUAD_INPUT
+};
+
+/* Structs passed on USB for device setup */
+
+struct smi2021_set_hw_state {
+ u8 head;
+ u8 state;
+} __packed;
+
+struct smi2021_ctrl {
+ u8 head;
+ u8 i2c_addr;
+ u8 bm_data_type;
+ u8 bm_data_offset;
+ u8 size;
+ union data {
+ u8 val;
+ struct i2c_data {
+ u8 reg;
+ u8 val;
+ } __packed i2c_data;
+ struct smi_data {
+ u8 reg_hi;
+ u8 reg_lo;
+ u8 val;
+ } __packed smi_data;
+ } __packed data;
+ u8 reserved[5];
+} __packed;
+
+/* Buffer for one video frame */
+struct smi2021_buffer {
+ /* Common vb2 stuff, must be first */
+ struct vb2_buffer vb;
+ struct list_head list;
+
+ void *mem;
+ unsigned int length;
+
+ bool second_field;
+ bool in_blank;
+ unsigned int pos;
+
+ /* ActiveVideo - Line counter */
+ u16 trc_av;
+};
+
+struct smi2021_isoc_ctl {
+ int max_pkt_size;
+ int num_bufs;
+ struct urb **urb;
+ char **transfer_buffer;
+ struct smi2021_buffer *buf;
+};
+
+
+struct smi2021_fmt {
+ char *name;
+ u32 fourcc;
+ int depth;
+};
+
+struct smi2021_input {
+ char *name;
+ int type;
+};
+
+struct smi2021_dev {
+ struct v4l2_device v4l2_dev;
+ struct video_device vdev;
+ struct v4l2_ctrl_handler ctrl_handler;
+
+ struct v4l2_subdev *sd_saa7113;
+
+ struct usb_device *udev;
+ struct device *dev;
+
+ /* Capture buffer queue */
+ struct vb2_queue vb_vidq;
+
+ /* ISOC control struct */
+ struct list_head avail_bufs;
+ struct smi2021_isoc_ctl isoc_ctl;
+
+ enum smi2021_config device_cfg;
+
+ int width; /* frame width */
+ int height; /* frame height */
+ unsigned int ctl_input; /* selected input */
+ v4l2_std_id norm; /* current norm */
+ const struct smi2021_fmt *fmt; /* selected format */
+ unsigned int buf_count; /* for video buffers */
+
+ /* i2c i/o */
+ struct i2c_adapter i2c_adap;
+ struct i2c_client i2c_client;
+
+ struct mutex v4l2_lock;
+ struct mutex vb_queue_lock;
+ spinlock_t buf_lock;
+
+ enum smi2021_sync sync_state;
+
+ /* audio */
+ struct snd_card *snd_card;
+ struct snd_pcm_substream *pcm_substream;
+
+ unsigned int pcm_write_ptr;
+ unsigned int pcm_complete_samples;
+
+ u8 pcm_read_offset;
+ struct work_struct adev_capture_trigger;
+ atomic_t adev_capturing;
+};
+
+/* Provided by smi2021_bootloader.c */
+int smi2021_bootloader_probe(struct usb_interface *intf,
+ const struct usb_device_id *devid);
+void smi2021_bootloader_disconnect(struct usb_interface *intf);
+
+/* Provided by smi2021_main.c */
+int smi2021_write_reg(struct smi2021_dev *dev, u8 addr, u16 reg, u8 val);
+int smi2021_read_reg(struct smi2021_dev *dev, u8 addr, u16 reg, u8 *val);
+int smi2021_set_hw_state(struct smi2021_dev *dev, u8 state);
+int smi2021_enable_audio(struct smi2021_dev *dev);
+
+/* Provided by smi2021_v4l2.c */
+int smi2021_vb2_setup(struct smi2021_dev *dev);
+int smi2021_video_register(struct smi2021_dev *dev);
+void smi2021_clear_queue(struct smi2021_dev *dev);
+
+/* Provided by smi2021_video.c */
+int smi2021_alloc_isoc(struct smi2021_dev *dev);
+void smi2021_free_isoc(struct smi2021_dev *dev);
+void smi2021_cancel_isoc(struct smi2021_dev *dev);
+void smi2021_uninit_isoc(struct smi2021_dev *dev);
+
+/* Provided by smi2021_i2c.c */
+int smi2021_i2c_register(struct smi2021_dev *dev);
+int smi2021_i2c_unregister(struct smi2021_dev *dev);
+
+/* Provided by smi2021_audio.c */
+int smi2021_snd_register(struct smi2021_dev *dev);
+void smi2021_snd_unregister(struct smi2021_dev *dev);
+void smi2021_audio(struct smi2021_dev *dev, u8 *data, int len);
+#endif /* SMI2021_H */
diff --git a/drivers/media/usb/smi2021/smi2021_audio.c b/drivers/media/usb/smi2021/smi2021_audio.c
new file mode 100644
index 0000000..9637153
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_audio.c
@@ -0,0 +1,380 @@
+/*******************************************************************************
+ * smi2021_audio.c *
+ * *
+ * USB Driver for SMI2021 - EasyCap *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include "smi2021.h"
+
+static void pcm_buffer_free(struct snd_pcm_substream *substream)
+{
+ vfree(substream->runtime->dma_area);
+ substream->runtime->dma_area = NULL;
+ substream->runtime->dma_bytes = 0;
+}
+
+static int pcm_buffer_alloc(struct snd_pcm_substream *substream, int size)
+{
+ if (substream->runtime->dma_area) {
+ if (substream->runtime->dma_bytes > size)
+ return 0;
+ pcm_buffer_free(substream);
+ }
+
+ substream->runtime->dma_area = vmalloc(size);
+ if (substream->runtime->dma_area == NULL)
+ return -ENOMEM;
+
+ substream->runtime->dma_bytes = size;
+
+ return 0;
+}
+
+static const struct snd_pcm_hardware smi2021_pcm_hw = {
+ .info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH,
+
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 992, /* 32640 */ /* 15296 */
+ .period_bytes_max = 15872, /* 65280 */
+ .periods_min = 1, /* 1 */
+ .periods_max = 16, /* 2 */
+ .buffer_bytes_max = 65280, /* 65280 */
+};
+
+static int smi2021_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int rc;
+
+ rc = snd_pcm_hw_constraint_pow2(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (rc < 0)
+ return rc;
+
+ dev->pcm_substream = substream;
+
+ runtime->hw = smi2021_pcm_hw;
+ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+ smi2021_dbg("PCM device open!\n");
+
+ return 0;
+}
+
+static int smi2021_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+ smi2021_dbg("PCM device closing\n");
+
+ if (atomic_read(&dev->adev_capturing)) {
+ atomic_set(&dev->adev_capturing, 0);
+ schedule_work(&dev->adev_capture_trigger);
+ }
+ return 0;
+
+}
+
+
+static int smi2021_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int size, rc;
+ size = params_period_bytes(hw_params) * params_periods(hw_params);
+
+ rc = pcm_buffer_alloc(substream, size);
+ if (rc < 0)
+ return rc;
+
+
+ return 0;
+}
+
+static int smi2021_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+
+ if (atomic_read(&dev->adev_capturing)) {
+ atomic_set(&dev->adev_capturing, 0);
+ schedule_work(&dev->adev_capture_trigger);
+ }
+
+ pcm_buffer_free(substream);
+ return 0;
+}
+
+static int smi2021_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+
+ dev->pcm_complete_samples = 0;
+ dev->pcm_read_offset = 0;
+ dev->pcm_write_ptr = 0;
+
+ return 0;
+}
+
+static void capture_trigger(struct work_struct *work)
+{
+ struct smi2021_dev *dev = container_of(work, struct smi2021_dev,
+ adev_capture_trigger);
+
+ if (atomic_read(&dev->adev_capturing))
+ smi2021_write_reg(dev, 0, 0x1740, 0x1d);
+ else
+ smi2021_write_reg(dev, 0, 0x1740, 0x00);
+}
+
+/* This callback is ATOMIC, must not sleep */
+static int smi2021_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ atomic_set(&dev->adev_capturing, 1);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ atomic_set(&dev->adev_capturing, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ schedule_work(&dev->adev_capture_trigger);
+
+ return 0;
+}
+
+static snd_pcm_uframes_t smi2021_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+ return dev->pcm_write_ptr / 8;
+}
+
+static struct page *smi2021_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+ unsigned long offset)
+{
+ void *pageptr = subs->runtime->dma_area + offset;
+
+ return vmalloc_to_page(pageptr);
+}
+
+static struct snd_pcm_ops smi2021_pcm_ops = {
+ .open = smi2021_pcm_open,
+ .close = smi2021_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = smi2021_pcm_hw_params,
+ .hw_free = smi2021_pcm_hw_free,
+ .prepare = smi2021_pcm_prepare,
+ .trigger = smi2021_pcm_trigger,
+ .pointer = smi2021_pcm_pointer,
+ .page = smi2021_pcm_get_vmalloc_page,
+};
+
+int smi2021_snd_register(struct smi2021_dev *dev)
+{
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ int rc = 0;
+
+ rc = snd_card_create(SNDRV_DEFAULT_IDX1, "smi2021 Audio", THIS_MODULE,
+ 0, &card);
+ if (rc < 0)
+ return rc;
+
+ rc = snd_pcm_new(card, "smi2021 Audio", 0, 0, 1, &pcm);
+ if (rc < 0)
+ goto err_free_card;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &smi2021_pcm_ops);
+ pcm->info_flags = 0;
+ pcm->private_data = dev;
+ strcpy(pcm->name, "Somagic smi2021 Capture");
+
+ strcpy(card->driver, "smi2021-Audio");
+ strcpy(card->shortname, "smi2021 Audio");
+ strcpy(card->longname, "Somagic smi2021 Audio");
+
+ INIT_WORK(&dev->adev_capture_trigger, capture_trigger);
+
+ rc = snd_card_register(card);
+ if (rc < 0)
+ goto err_free_card;
+
+ dev->snd_card = card;
+
+ return 0;
+
+err_free_card:
+ snd_card_free(card);
+ return rc;
+}
+
+void smi2021_snd_unregister(struct smi2021_dev *dev)
+{
+ if (!dev)
+ return;
+
+ if (!dev->snd_card)
+ return;
+
+ snd_card_free(dev->snd_card);
+ dev->snd_card = NULL;
+}
+
+void smi2021_audio(struct smi2021_dev *dev, u8 *data, int len)
+{
+ struct snd_pcm_runtime *runtime;
+ u8 offset;
+ int new_offset = 0;
+
+ int skip;
+ unsigned int stride, oldptr, headptr;
+
+ int diff = 0;
+ int samples = 0;
+ bool period_elapsed = false;
+
+
+ if (!dev->udev)
+ return;
+
+ if (atomic_read(&dev->adev_capturing) == 0)
+ return;
+
+ if (!dev->pcm_substream)
+ return;
+
+ runtime = dev->pcm_substream->runtime;
+ if (!runtime || !runtime->dma_area)
+ return;
+
+ offset = dev->pcm_read_offset;
+ stride = runtime->frame_bits >> 3;
+
+ if (stride == 0)
+ return;
+
+ diff = dev->pcm_write_ptr;
+
+ /* Check that the end of the last buffer was correct.
+ * If not correct, we mark any partial frames in buffer as complete
+ */
+ headptr = dev->pcm_write_ptr - offset - 4;
+ if (dev->pcm_write_ptr > 10 && runtime->dma_area[headptr] != 0x00) {
+ skip = stride - (dev->pcm_write_ptr % stride);
+ snd_pcm_stream_lock(dev->pcm_substream);
+ dev->pcm_write_ptr += skip;
+
+ if (dev->pcm_write_ptr >= runtime->dma_bytes)
+ dev->pcm_write_ptr -= runtime->dma_bytes;
+
+ snd_pcm_stream_unlock(dev->pcm_substream);
+ offset = dev->pcm_read_offset = 0;
+ }
+ /* The device is actually sending 24Bit pcm data
+ * with 0x00 as the header byte before each sample.
+ * We look for this byte to make sure we did not
+ * loose any bytes during transfer.
+ */
+ while (len > stride && (data[offset] != 0x00 ||
+ data[offset + (stride / 2)] != 0x00)) {
+ new_offset++;
+ data++;
+ len--;
+ }
+
+ if (len <= stride) {
+ /* We exhausted the buffer looking for 0x00 */
+ dev->pcm_read_offset = 0;
+ return;
+ }
+ if (new_offset != 0) {
+ /* This buffer can not be appended to the current buffer,
+ * so we mark any partial frames in the buffer as complete.
+ */
+ skip = stride - (dev->pcm_write_ptr % stride);
+ snd_pcm_stream_lock(dev->pcm_substream);
+ dev->pcm_write_ptr += skip;
+
+ if (dev->pcm_write_ptr >= runtime->dma_bytes)
+ dev->pcm_write_ptr -= runtime->dma_bytes;
+
+ snd_pcm_stream_unlock(dev->pcm_substream);
+
+ offset = dev->pcm_read_offset = new_offset % (stride / 2);
+
+ }
+
+ oldptr = dev->pcm_write_ptr;
+ if (oldptr + len >= runtime->dma_bytes) {
+ unsigned int cnt = runtime->dma_bytes - oldptr;
+ memcpy(runtime->dma_area + oldptr, data, cnt);
+ memcpy(runtime->dma_area, data + cnt, len - cnt);
+ } else {
+ memcpy(runtime->dma_area + oldptr, data, len);
+ }
+
+ snd_pcm_stream_lock(dev->pcm_substream);
+ dev->pcm_write_ptr += len;
+
+ if (dev->pcm_write_ptr >= runtime->dma_bytes)
+ dev->pcm_write_ptr -= runtime->dma_bytes;
+
+ samples = dev->pcm_write_ptr - diff;
+ if (samples < 0)
+ samples += runtime->dma_bytes;
+
+ samples /= (stride / 2);
+
+ dev->pcm_complete_samples += samples;
+ if (dev->pcm_complete_samples / 2 >= runtime->period_size) {
+ dev->pcm_complete_samples -= runtime->period_size * 2;
+ period_elapsed = true;
+ }
+ snd_pcm_stream_unlock(dev->pcm_substream);
+
+ if (period_elapsed)
+ snd_pcm_period_elapsed(dev->pcm_substream);
+
+}
diff --git a/drivers/media/usb/smi2021/smi2021_bootloader.c b/drivers/media/usb/smi2021/smi2021_bootloader.c
new file mode 100644
index 0000000..ac95acf
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_bootloader.c
@@ -0,0 +1,261 @@
+/*******************************************************************************
+ * smi2021_bootloader.c *
+ * *
+ * USB Driver for SMI2021 - EasyCAP *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include "smi2021.h"
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+
+#define FIRMWARE_CHUNK_SIZE 62
+#define FIRMWARE_HEADER_SIZE 2
+
+#define FIRMWARE_CHUNK_HEAD_0 0x05
+#define FIRMWARE_CHUNK_HEAD_1 0xff
+#define FIRMWARE_HW_READY_STATE 0x07
+
+#define SMI2021_3C_FIRMWARE "smi2021_3c.bin"
+#define SMI2021_3E_FIRMWARE "smi2021_3e.bin"
+#define SMI2021_3F_FIRMWARE "smi2021_3f.bin"
+
+static unsigned int firmware_version;
+module_param(firmware_version, int, 0644);
+MODULE_PARM_DESC(firmware_version,
+ "Firmware version to be uploaded to device\n"
+ "if there are more than one firmware present");
+
+struct smi2021_firmware {
+ int id;
+ const char *name;
+ int found;
+};
+
+struct smi2021_firmware available_fw[] = {
+ {
+ .id = 0x3c,
+ .name = SMI2021_3C_FIRMWARE,
+ },
+ {
+ .id = 0x3e,
+ .name = SMI2021_3E_FIRMWARE,
+ },
+ {
+ .id = 0x3f,
+ .name = SMI2021_3F_FIRMWARE,
+ }
+};
+
+static const struct firmware *firmware[ARRAY_SIZE(available_fw)];
+static int firmwares = -1;
+
+static int smi2021_load_firmware(struct usb_device *udev,
+ const struct firmware *firmware)
+{
+ int i, size, rc;
+ struct smi2021_set_hw_state *hw_state;
+ u8 *chunk;
+
+ size = FIRMWARE_CHUNK_SIZE + FIRMWARE_HEADER_SIZE;
+ chunk = kzalloc(size, GFP_KERNEL);
+
+ if (chunk == NULL) {
+ dev_err(&udev->dev,
+ "could not allocate space for firmware chunk\n");
+ rc = -ENOMEM;
+ goto end_out;
+ }
+
+ hw_state = kzalloc(sizeof(*hw_state), GFP_KERNEL);
+ if (hw_state == NULL) {
+ dev_err(&udev->dev, "could not allocate space for usb data\n");
+ rc = -ENOMEM;
+ goto free_out;
+ }
+
+ if (firmware == NULL) {
+ dev_err(&udev->dev, "firmware is NULL\n");
+ rc = -ENODEV;
+ goto free_out;
+ }
+
+ if (firmware->size % FIRMWARE_CHUNK_SIZE) {
+ dev_err(&udev->dev, "firmware has wrong size\n");
+ rc = -ENODEV;
+ goto free_out;
+ }
+
+ rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, SMI2021_USB_PIPE_IN),
+ SMI2021_USB_REQUEST,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ SMI2021_HW_STATE_HEAD, SMI2021_USB_INDEX,
+ hw_state, sizeof(*hw_state), 1000);
+
+ if (rc < 0) {
+ dev_err(&udev->dev,
+ "could not check if device is ready for firmware upload: %d\n",
+ rc);
+ goto free_out;
+ }
+ if (hw_state->state != FIRMWARE_HW_READY_STATE) {
+ dev_err(&udev->dev,
+ "device is not ready for firmware upload: %d\n", rc);
+ goto free_out;
+ }
+
+ chunk[0] = FIRMWARE_CHUNK_HEAD_0;
+ chunk[1] = FIRMWARE_CHUNK_HEAD_1;
+
+ for (i = 0; i < firmware->size / FIRMWARE_CHUNK_SIZE; i++) {
+ memcpy(chunk + FIRMWARE_HEADER_SIZE,
+ firmware->data + (i * FIRMWARE_CHUNK_SIZE),
+ FIRMWARE_CHUNK_SIZE);
+
+ rc = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, SMI2021_USB_PIPE_OUT),
+ SMI2021_USB_REQUEST,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ FIRMWARE_CHUNK_HEAD_0, SMI2021_USB_INDEX,
+ chunk, size, 1000);
+ if (rc < 0) {
+ dev_err(&udev->dev, "firmware upload failed: %d\n",
+ rc);
+ goto free_out;
+ }
+ }
+
+ hw_state->head = FIRMWARE_HW_READY_STATE;
+ hw_state->state = 0x00;
+ rc = usb_control_msg(udev, usb_sndctrlpipe(udev, SMI2021_USB_PIPE_OUT),
+ SMI2021_USB_REQUEST,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ FIRMWARE_HW_READY_STATE, SMI2021_USB_INDEX,
+ hw_state, sizeof(*hw_state), 1000);
+
+ if (rc < 0) {
+ dev_err(&udev->dev, "device failed to ack firmware: %d\n", rc);
+ goto free_out;
+ }
+
+ rc = 0;
+
+free_out:
+ kfree(chunk);
+ kfree(hw_state);
+end_out:
+ return rc;
+}
+
+static int smi2021_choose_firmware(struct usb_device *udev)
+{
+ int i, found, id;
+ for (i = 0; i < ARRAY_SIZE(available_fw); i++) {
+ found = available_fw[i].found;
+ id = available_fw[i].id;
+ if (firmware_version == id && found >= 0) {
+ dev_info(&udev->dev, "uploading firmware for 0x%x\n",
+ id);
+ return smi2021_load_firmware(udev, firmware[found]);
+ }
+ }
+
+ dev_info(&udev->dev,
+ "could not decide what firmware to upload, user action required\n");
+ return 0;
+}
+
+int smi2021_bootloader_probe(struct usb_interface *intf,
+ const struct usb_device_id *devid)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ int rc, i;
+
+ /* Check what firmwares are available in the system */
+ for (i = 0; i < ARRAY_SIZE(available_fw); i++) {
+ dev_info(&udev->dev, "Looking for: %s\n", available_fw[i].name);
+ rc = request_firmware(&firmware[firmwares + 1],
+ available_fw[i].name, &udev->dev);
+
+ if (rc == 0) {
+ firmwares++;
+ available_fw[i].found = firmwares;
+ dev_info(&udev->dev, "Found firmware for 0x00%x\n",
+ available_fw[i].id);
+ } else if (rc == -ENOENT) {
+ available_fw[i].found = -1;
+ } else {
+ dev_err(&udev->dev,
+ "request_firmware failed with: %d\n", rc);
+ goto err_out;
+ }
+ }
+
+ if (firmwares < 0) {
+ dev_err(&udev->dev,
+ "could not find any firmware for this device\n");
+ goto no_dev;
+ } else if (firmwares == 0) {
+ rc = smi2021_load_firmware(udev, firmware[0]);
+ if (rc < 0)
+ goto err_out;
+ } else {
+ smi2021_choose_firmware(udev);
+ }
+
+ return 0;
+
+no_dev:
+ rc = -ENODEV;
+err_out:
+ return rc;
+}
+
+void smi2021_bootloader_disconnect(struct usb_interface *intf)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(available_fw); i++) {
+ if (available_fw[i].found >= 0) {
+ dev_info(&udev->dev, "Releasing firmware for 0x00%x\n",
+ available_fw[i].id);
+ release_firmware(firmware[available_fw[i].found]);
+ firmware[available_fw[i].found] = NULL;
+ available_fw[i].found = -1;
+ }
+ }
+ firmwares = -1;
+
+}
+
+MODULE_FIRMWARE(SMI2021_3C_FIRMWARE);
+MODULE_FIRMWARE(SMI2021_3E_FIRMWARE);
+MODULE_FIRMWARE(SMI2021_3F_FIRMWARE);
+
diff --git a/drivers/media/usb/smi2021/smi2021_i2c.c b/drivers/media/usb/smi2021/smi2021_i2c.c
new file mode 100644
index 0000000..5d25eae
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_i2c.c
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * smi2021_i2c.c *
+ * *
+ * USB Driver for SMI2021 - EasyCAP *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include "smi2021.h"
+
+static unsigned int i2c_debug;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#define dprint_i2c(fmt, args...) \
+do { \
+ if (i2c_debug) \
+ pr_debug("smi2021[i2c]::%s: " fmt, __func__, ##args); \
+} while (0)
+
+
+static int i2c_xfer(struct i2c_adapter *i2c_adap,
+ struct i2c_msg msgs[], int num)
+{
+ struct smi2021_dev *dev = i2c_adap->algo_data;
+
+ switch (num) {
+ case 2: /* Read reg */
+ if (msgs[0].len != 1 || msgs[1].len != 1) {
+ dprint_i2c("both messages must be 1 byte\n");
+ goto err_out;
+ }
+
+ if ((msgs[1].flags & I2C_M_RD) != I2C_M_RD) {
+ dprint_i2c("last message should have rd flag\n");
+ goto err_out;
+ }
+ smi2021_read_reg(dev, msgs[0].addr, msgs[0].buf[0],
+ msgs[1].buf);
+ break;
+ case 1: /* Write reg */
+ if (msgs[0].len == 0) {
+ break;
+ } else if (msgs[0].len != 2) {
+ dprint_i2c("unsupported len\n");
+ goto err_out;
+ }
+ if (msgs[0].buf[0] == 0) {
+ /* We don't handle writing to addr 0x00 */
+ break;
+ }
+ dprint_i2c("Set reg 0x%x to 0x%x\n",
+ msgs[0].buf[0], msgs[0].buf[1]);
+ smi2021_write_reg(dev, msgs[0].addr, msgs[0].buf[0],
+ msgs[0].buf[1]);
+ break;
+ default:
+ dprint_i2c("driver can only handle 1 or 2 messages\n");
+ goto err_out;
+ }
+ return num;
+
+err_out:
+ return -EOPNOTSUPP;
+}
+
+static u32 functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm algo = {
+ .master_xfer = i2c_xfer,
+ .functionality = functionality,
+};
+
+static struct i2c_adapter adap_template = {
+ .owner = THIS_MODULE,
+ .name = "smi2021_easycap_dc60",
+ .algo = &algo,
+};
+
+static struct i2c_client client_template = {
+ .name = "smi2021 internal",
+};
+
+int smi2021_i2c_register(struct smi2021_dev *dev)
+{
+ int rc;
+
+ dev->i2c_adap = adap_template;
+ dev->i2c_adap.dev.parent = dev->dev;
+ strcpy(dev->i2c_adap.name, "smi2021");
+ dev->i2c_adap.algo_data = dev;
+
+ i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev);
+
+ rc = i2c_add_adapter(&dev->i2c_adap);
+ if (rc < 0) {
+ smi2021_err("can't add i2c adapter, errno: %d\n", rc);
+ return rc;
+ }
+
+ dev->i2c_client = client_template;
+ dev->i2c_client.adapter = &dev->i2c_adap;
+
+ smi2021_dbg("Registered i2c adapter\n");
+ return 0;
+}
+
+int smi2021_i2c_unregister(struct smi2021_dev *dev)
+{
+ i2c_del_adapter(&dev->i2c_adap);
+ return 0;
+}
diff --git a/drivers/media/usb/smi2021/smi2021_main.c b/drivers/media/usb/smi2021/smi2021_main.c
new file mode 100644
index 0000000..7c5315f
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_main.c
@@ -0,0 +1,431 @@
+/*******************************************************************************
+ * smi2021_main.c *
+ * *
+ * USB Driver for SMI2021 - EasyCAP *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include "smi2021.h"
+
+#define VENDOR_ID 0x1c88
+#define BOOTLOADER_ID 0x0007
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jon Arne Jørgensen <jonjon.arnearne--a.t--gmail.com>");
+MODULE_DESCRIPTION("SMI2021 - EasyCap");
+MODULE_VERSION(SMI2021_DRIVER_VERSION);
+
+static const struct usb_device_id smi2021_usb_device_id_table[] = {
+ { USB_DEVICE(VENDOR_ID, BOOTLOADER_ID) },
+ { USB_DEVICE(VENDOR_ID, 0x003c) },
+ { USB_DEVICE(VENDOR_ID, 0x003d) },
+ { USB_DEVICE(VENDOR_ID, 0x003e) },
+ { USB_DEVICE(VENDOR_ID, 0x003f) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, smi2021_usb_device_id_table);
+
+/******************************************************************************/
+/* */
+/* Write to saa7113 */
+/* */
+/******************************************************************************/
+
+inline int transfer_usb_ctrl(struct smi2021_dev *dev,
+ struct smi2021_ctrl *data, int len)
+{
+ return usb_control_msg(dev->udev,
+ usb_sndctrlpipe(dev->udev, SMI2021_USB_PIPE_OUT),
+ SMI2021_USB_REQUEST,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ SMI2021_CTRL_HEAD, SMI2021_USB_INDEX,
+ data, len, 1000);
+}
+
+int smi2021_write_reg(struct smi2021_dev *dev, u8 addr, u16 reg, u8 val)
+{
+ int rc;
+ struct smi2021_ctrl *data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (dev->udev == NULL) {
+ rc = -ENODEV;
+ goto out;
+ }
+
+ data->head = SMI2021_CTRL_HEAD;
+ data->i2c_addr = addr;
+ data->size = sizeof(val);
+
+ if (addr) {
+ /* This is I2C data for the saa7113 chip */
+ data->bm_data_type = SMI2021_DATA_TYPE_FLAG_I2C |
+ SMI2021_DATA_TYPE_FLAG_WRITE;
+ data->bm_data_offset =
+ sizeof(u8) & SMI2021_DATA_OFFSET_SIZE_MASK;
+
+ data->data.i2c_data.reg = reg;
+ data->data.i2c_data.val = val;
+ } else {
+ /* This is register settings for the smi2021 chip */
+ data->bm_data_offset = SMI2021_DATA_OFFSET_FLAG_SMI |
+ (sizeof(reg) & SMI2021_DATA_OFFSET_SIZE_MASK);
+
+ data->data.smi_data.reg_lo = __cpu_to_le16(reg) & 0xff;
+ data->data.smi_data.reg_hi = __cpu_to_le16(reg) >> 8;
+ data->data.smi_data.val = val;
+ }
+
+ rc = transfer_usb_ctrl(dev, data, sizeof(*data));
+ if (rc < 0) {
+ smi2021_warn("write failed on register 0x%x, errno: %d\n",
+ reg, rc);
+ }
+
+out:
+ kfree(data);
+ return rc;
+
+}
+
+int smi2021_read_reg(struct smi2021_dev *dev, u8 addr, u16 reg, u8 *val)
+{
+ int rc;
+ struct smi2021_ctrl *data = kzalloc(sizeof(*data), GFP_KERNEL);
+
+ if (data == NULL) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (dev->udev == NULL) {
+ rc = -ENODEV;
+ goto out;
+ }
+
+ data->head = SMI2021_CTRL_HEAD;
+ data->i2c_addr = addr;
+ data->bm_data_type = SMI2021_DATA_TYPE_FLAG_I2C |
+ SMI2021_DATA_TYPE_FLAG_PREPARE_READ;
+ data->size = sizeof(*val);
+ data->data.i2c_data.reg = reg;
+
+ *val = 0;
+
+ rc = transfer_usb_ctrl(dev, data, sizeof(*data));
+ if (rc < 0) {
+ smi2021_warn(
+ "1st pass failing to read reg 0x%x, usb-errno: %d\n",
+ reg, rc);
+ goto out;
+ }
+
+ data->bm_data_type = SMI2021_DATA_TYPE_FLAG_I2C |
+ SMI2021_DATA_TYPE_FLAG_READ;
+
+ rc = transfer_usb_ctrl(dev, data, sizeof(*data));
+ if (rc < 0) {
+ smi2021_warn(
+ "2nd pass failing to read reg 0x%x, usb-errno: %d\n",
+ reg, rc);
+ goto out;
+ }
+
+ memset(data, 0, sizeof(*data));
+ rc = usb_control_msg(dev->udev,
+ usb_rcvctrlpipe(dev->udev, SMI2021_USB_PIPE_IN),
+ SMI2021_USB_REQUEST,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ SMI2021_CTRL_HEAD, SMI2021_USB_INDEX,
+ data, sizeof(*data), 1000);
+ if (rc < 0) {
+ smi2021_warn("Failed to read reg 0x%x, usb-errno: %d\n",
+ reg, rc);
+ goto out;
+ }
+
+ *val = data->data.val;
+
+out:
+ kfree(data);
+ return rc;
+}
+
+static void smi2021_reset_device(struct smi2021_dev *dev)
+{
+ /* Set reset pin to output */
+ smi2021_write_reg(dev, 0, SMI2021_DDR_PORT, SMI2021_RESET_PIN);
+
+ /* Toggle pin */
+ smi2021_write_reg(dev, 0, SMI2021_DATA_PORT, SMI2021_RESET_PIN);
+ smi2021_write_reg(dev, 0, SMI2021_DATA_PORT, 0x00);
+}
+
+
+int smi2021_set_hw_state(struct smi2021_dev *dev, u8 state)
+{
+ int rc;
+ struct smi2021_set_hw_state *hw_state;
+
+ if (dev->udev == NULL)
+ return -ENODEV;
+
+ hw_state = kzalloc(sizeof(*hw_state), GFP_KERNEL);
+ if (hw_state == NULL) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ if (state == SMI2021_HW_STATE_STANDBY)
+ usb_set_interface(dev->udev, 0, 0);
+
+ hw_state->head = SMI2021_HW_STATE_HEAD;
+ hw_state->state = state;
+
+ rc = usb_control_msg(dev->udev,
+ usb_sndctrlpipe(dev->udev, SMI2021_USB_PIPE_OUT),
+ SMI2021_USB_REQUEST,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ SMI2021_HW_STATE_HEAD, SMI2021_USB_INDEX,
+ hw_state, sizeof(*hw_state), 1000);
+
+ if (rc < 0) {
+ smi2021_err("usb_control_msg returned: %d\n", rc);
+ goto out;
+ }
+
+ smi2021_dbg("Sent %lu bytes setup packet: 0x%x 0x%x\n",
+ sizeof(*hw_state), *(u8 *)hw_state,
+ *(((u8 *)hw_state)+1));
+
+ if (state == SMI2021_HW_STATE_CAPTURE) {
+ rc = usb_set_interface(dev->udev, 0, 2);
+ smi2021_dbg("%d: Interface set for capture\n", rc);
+ }
+
+
+
+out:
+ kfree(hw_state);
+ return rc;
+}
+
+static void release_v4l2_dev(struct v4l2_device *v4l2_dev)
+{
+ struct smi2021_dev *dev = container_of(v4l2_dev, struct smi2021_dev,
+ v4l2_dev);
+ smi2021_dbg("Releasing all resources\n");
+
+ smi2021_i2c_unregister(dev);
+
+ v4l2_ctrl_handler_free(&dev->ctrl_handler);
+ v4l2_device_unregister(&dev->v4l2_dev);
+ kfree(dev);
+}
+
+#define hb_mult(w_max_packet_size) (1 + (((w_max_packet_size) >> 11) & 0x03))
+
+static int smi2021_scan_usb(struct usb_interface *intf, struct usb_device *udev)
+{
+ int i, e, ifnum, sizedescr, size;
+ const struct usb_endpoint_descriptor *desc;
+ ifnum = intf->altsetting[0].desc.bInterfaceNumber;
+
+ for (i = 0; i < intf->num_altsetting; i++) {
+ for (e = 0; e < intf->altsetting[i].desc.bNumEndpoints; e++) {
+ desc = &intf->altsetting[i].endpoint[e].desc;
+ sizedescr = le16_to_cpu(desc->wMaxPacketSize);
+ size = sizedescr & 0x7ff;
+
+ if (udev->speed == USB_SPEED_HIGH)
+ size = size * hb_mult(sizedescr);
+ }
+ }
+ return 0;
+}
+
+/******************************************************************************/
+/* */
+/* DEVICE - PROBE & DISCONNECT */
+/* */
+/******************************************************************************/
+static int smi2021_usb_probe(struct usb_interface *intf,
+ const struct usb_device_id *devid)
+{
+ int rc;
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct smi2021_dev *dev;
+
+ if (udev == (struct usb_device *)NULL) {
+ smi2021_err("device is NULL\n");
+ return -EFAULT;
+ }
+
+ if (udev->descriptor.idProduct == BOOTLOADER_ID)
+ return smi2021_bootloader_probe(intf, devid);
+
+ smi2021_scan_usb(intf, udev);
+
+ dev = kzalloc(sizeof(struct smi2021_dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+
+ dev->udev = udev;
+ dev->dev = &intf->dev;
+ usb_set_intfdata(intf, dev);
+
+ switch (udev->descriptor.idProduct) {
+ case 0x003e:
+ case 0x003f:
+ dev->device_cfg = SMI2021_QUAD_INPUT;
+ break;
+ case 0x003c:
+ case 0x003d:
+ default:
+ dev->device_cfg = SMI2021_DUAL_INPUT;
+ }
+
+ /* Initialize videobuf2 stuff */
+ rc = smi2021_vb2_setup(dev);
+ if (rc < 0)
+ goto free_err;
+
+ spin_lock_init(&dev->buf_lock);
+ mutex_init(&dev->v4l2_lock);
+ mutex_init(&dev->vb_queue_lock);
+
+ rc = v4l2_ctrl_handler_init(&dev->ctrl_handler, 0);
+ if (rc) {
+ smi2021_err("v4l2_ctrl_handler_init failed with: %d\n", rc);
+ goto free_err;
+ }
+
+ dev->v4l2_dev.release = release_v4l2_dev;
+ dev->v4l2_dev.ctrl_handler = &dev->ctrl_handler;
+ rc = v4l2_device_register(dev->dev, &dev->v4l2_dev);
+ if (rc) {
+ smi2021_err("v4l2_device_register failed with %d\n", rc);
+ goto free_ctrl;
+ }
+
+ smi2021_reset_device(dev);
+
+ rc = smi2021_i2c_register(dev);
+ if (rc < 0)
+ goto unreg_v4l2;
+
+ dev->sd_saa7113 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+ "gm7113c", 0x4a, NULL);
+
+ smi2021_dbg("Driver version %s successfully loaded\n",
+ SMI2021_DRIVER_VERSION);
+
+ v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0);
+ v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
+
+ rc = smi2021_snd_register(dev);
+ if (rc < 0)
+ goto unreg_i2c;
+
+ rc = smi2021_video_register(dev);
+ if (rc < 0)
+ goto unreg_snd;
+
+ return 0;
+
+unreg_snd:
+ smi2021_snd_unregister(dev);
+unreg_i2c:
+ smi2021_i2c_unregister(dev);
+unreg_v4l2:
+ v4l2_device_unregister(&dev->v4l2_dev);
+free_ctrl:
+ v4l2_ctrl_handler_free(&dev->ctrl_handler);
+free_err:
+ kfree(dev);
+
+ return rc;
+}
+
+static void smi2021_usb_disconnect(struct usb_interface *intf)
+{
+
+ struct smi2021_dev *dev;
+ struct usb_device *udev = interface_to_usbdev(intf);
+
+ if (udev == (struct usb_device *)NULL) {
+ smi2021_err("device is NULL\n");
+ return;
+ }
+
+ if (udev->descriptor.idProduct == BOOTLOADER_ID)
+ return smi2021_bootloader_disconnect(intf);
+
+ dev = usb_get_intfdata(intf);
+ usb_set_intfdata(intf, NULL);
+
+ mutex_lock(&dev->vb_queue_lock);
+ mutex_lock(&dev->v4l2_lock);
+
+ smi2021_uninit_isoc(dev);
+ smi2021_clear_queue(dev);
+
+ video_unregister_device(&dev->vdev);
+ v4l2_device_disconnect(&dev->v4l2_dev);
+
+ /* This way current users can detect device is gone */
+ dev->udev = NULL;
+
+ mutex_unlock(&dev->v4l2_lock);
+ mutex_unlock(&dev->vb_queue_lock);
+
+ smi2021_snd_unregister(dev);
+
+ /*
+ * This calls release_v4l2_dev if it's the last reference.
+ * Otherwise, the release is postponed until there are no users left.
+ */
+ v4l2_device_put(&dev->v4l2_dev);
+}
+
+/******************************************************************************/
+/* */
+/* MODULE - INIT & EXIT */
+/* */
+/******************************************************************************/
+
+struct usb_driver smi2021_usb_driver = {
+ .name = "smi2021",
+ .id_table = smi2021_usb_device_id_table,
+ .probe = smi2021_usb_probe,
+ .disconnect = smi2021_usb_disconnect
+};
+
+module_usb_driver(smi2021_usb_driver);
diff --git a/drivers/media/usb/smi2021/smi2021_v4l2.c b/drivers/media/usb/smi2021/smi2021_v4l2.c
new file mode 100644
index 0000000..90a838b
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_v4l2.c
@@ -0,0 +1,542 @@
+/*******************************************************************************
+ * smi2021_v4l2.c *
+ * *
+ * USB Driver for smi2021 - EasyCap *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include "smi2021.h"
+
+static const struct smi2021_fmt format[] = {
+ {
+ .name = "16bpp YU2, 4:2:2, packed",
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .depth = 16,
+ }
+};
+
+static const struct smi2021_input dual_input[] = {
+ {
+ .name = "Composite",
+ .type = SAA7115_COMPOSITE0,
+ },
+ {
+ .name = "S-Video",
+ .type = SAA7115_SVIDEO1,
+ }
+};
+
+static const struct smi2021_input quad_input[] = {
+ {
+ .name = "Composite 0",
+ .type = SAA7115_COMPOSITE0,
+ },
+ {
+ .name = "Composite 1",
+ .type = SAA7115_COMPOSITE1,
+ },
+ {
+ .name = "Composite 2",
+ .type = SAA7115_COMPOSITE2,
+ },
+ {
+ .name = "Composite 3",
+ .type = SAA7115_COMPOSITE3,
+ },
+};
+
+static const struct smi2021_input *input = dual_input;
+static int inputs = ARRAY_SIZE(dual_input);
+
+static void smi2021_set_input(struct smi2021_dev *dev)
+{
+ v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing,
+ input[dev->ctl_input].type, 0, 0);
+}
+
+/* Must be called with v4l2_lock hold */
+static void smi2021_stop_hw(struct smi2021_dev *dev)
+{
+ int rc;
+
+ rc = smi2021_set_hw_state(dev, SMI2021_HW_STATE_STANDBY);
+ if (rc < 0)
+ smi2021_err("Could not stop device!\n");
+
+ v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
+}
+
+static struct v4l2_file_operations smi2021_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+/******************************************************************************/
+/* */
+/* Vidioc IOCTLS */
+/* */
+/******************************************************************************/
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index != 0)
+ return -EINVAL;
+
+ strlcpy(f->description, format[f->index].name, sizeof(f->description));
+ f->pixelformat = format[f->index].fourcc;
+
+ return 0;
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ strcpy(cap->driver, "smi2021");
+ strcpy(cap->card, "smi2021");
+ usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+ cap->device_caps =
+ V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_STREAMING |
+ V4L2_CAP_READWRITE;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ f->fmt.pix.pixelformat = dev->fmt->fourcc;
+ f->fmt.pix.width = dev->width;
+ f->fmt.pix.height = dev->height;
+ f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+ f->fmt.pix.bytesperline = dev->width * 2;
+ f->fmt.pix.sizeimage = dev->height * f->fmt.pix.bytesperline;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ f->fmt.pix.priv = 0;
+
+ return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ f->fmt.pix.pixelformat = dev->fmt->fourcc;
+ f->fmt.pix.width = dev->width;
+ f->fmt.pix.height = dev->height;
+ f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+ f->fmt.pix.bytesperline = dev->width * 2;
+ f->fmt.pix.sizeimage = dev->height * f->fmt.pix.bytesperline;
+ f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+ f->fmt.pix.priv = 0;
+
+ return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+ struct vb2_queue *q = &dev->vb_vidq;
+
+ if (vb2_is_busy(q))
+ return -EBUSY;
+
+ vidioc_try_fmt_vid_cap(file, priv, f);
+ return 0;
+}
+
+static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *norm)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ v4l2_device_call_all(&dev->v4l2_dev, 0, video, querystd, norm);
+ return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ *norm = dev->norm;
+ return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+ struct vb2_queue *q = &dev->vb_vidq;
+
+ if (vb2_is_busy(q))
+ return -EBUSY;
+
+ if (!dev->udev)
+ return -ENODEV;
+
+ dev->norm = *norm;
+ if (dev->norm & V4L2_STD_525_60) {
+ dev->width = SMI2021_BYTES_PER_LINE / 2;
+ dev->height = SMI2021_NTSC_LINES;
+ } else if (dev->norm & V4L2_STD_625_50) {
+ dev->width = SMI2021_BYTES_PER_LINE / 2;
+ dev->height = SMI2021_PAL_LINES;
+ } else {
+ smi2021_err("Invalid standard\n");
+ return -EINVAL;
+ }
+
+ v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, dev->norm);
+ return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ if (i->index >= inputs)
+ return -EINVAL;
+
+ strlcpy(i->name, input[i->index].name, sizeof(i->name));
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ i->std = dev->vdev.tvnorms;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ *i = dev->ctl_input;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+ struct smi2021_dev *dev = video_drvdata(file);
+
+ if (i >= inputs)
+ return -EINVAL;
+
+ dev->ctl_input = i;
+ smi2021_set_input(dev);
+
+ return 0;
+}
+
+static int vidioc_g_chip_ident(struct file *file, void *priv,
+ struct v4l2_dbg_chip_ident *chip)
+{
+ switch (chip->match.type) {
+ case V4L2_CHIP_MATCH_HOST:
+ chip->ident = V4L2_IDENT_NONE;
+ chip->revision = 0;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct v4l2_ioctl_ops smi2021_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+ .vidioc_querystd = vidioc_querystd,
+ .vidioc_g_std = vidioc_g_std,
+ .vidioc_s_std = vidioc_s_std,
+ .vidioc_enum_input = vidioc_enum_input,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+
+ /* vb2 handle these */
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_g_chip_ident = vidioc_g_chip_ident,
+
+};
+
+/******************************************************************************/
+/* */
+/* Videobuf2 operations */
+/* */
+/******************************************************************************/
+static int queue_setup(struct vb2_queue *vq,
+ const struct v4l2_format *v4l2_fmt,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct smi2021_dev *dev = vb2_get_drv_priv(vq);
+ unsigned long size;
+
+ size = dev->width * dev->height * 2;
+
+ *nbuffers = clamp_t(unsigned int, *nbuffers, 4, 16);
+ smi2021_dbg("Requesting %u buffers\n", *nbuffers);
+
+ /* Packed color format */
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ unsigned long flags;
+ struct smi2021_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
+ struct smi2021_buffer *buf = container_of(vb, struct smi2021_buffer, vb);
+
+ spin_lock_irqsave(&dev->buf_lock, flags);
+ if (!dev->udev) {
+ /*
+ * If the device is disconnected return the buffer to userspace
+ * directly. The next QBUF call will fail with -ENODEV.
+ */
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ } else {
+ buf->mem = vb2_plane_vaddr(vb, 0);
+ buf->length = vb2_plane_size(vb, 0);
+
+ buf->pos = 0;
+ buf->trc_av = 0;
+ buf->in_blank = true;
+ buf->second_field = false;
+
+ if (buf->length < dev->width * dev->height * 2)
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ else
+ list_add_tail(&buf->list, &dev->avail_bufs);
+ }
+ spin_unlock_irqrestore(&dev->buf_lock, flags);
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct smi2021_dev *dev = vb2_get_drv_priv(vq);
+ int rc, i;
+
+ if (!dev->udev)
+ return -ENODEV;
+
+ dev->sync_state = HSYNC;
+ dev->buf_count = 0;
+
+ if (mutex_lock_interruptible(&dev->v4l2_lock))
+ return -ERESTARTSYS;
+
+ v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 1);
+
+ rc = smi2021_set_hw_state(dev, SMI2021_HW_STATE_CAPTURE);
+ if (rc < 0) {
+ smi2021_err("can't start hw\n");
+ goto out_unlock;
+ }
+
+ smi2021_write_reg(dev, 0, SMI2021_AUDIO_PORT, SMI2021_AUDIO_ENABLE);
+
+ if (!dev->isoc_ctl.num_bufs) {
+ rc = smi2021_alloc_isoc(dev);
+ if (rc < 0)
+ goto out_stop_hw;
+ }
+
+ /* submit urbs and enable IRQ */
+ for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+ rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_KERNEL);
+ if (rc) {
+ smi2021_err("can't submit urb[%d] (%d)\n", i, rc);
+ goto out_uninit;
+ }
+ }
+
+ mutex_unlock(&dev->v4l2_lock);
+ return 0;
+
+out_uninit:
+ smi2021_uninit_isoc(dev);
+out_stop_hw:
+ usb_set_interface(dev->udev, 0, 0);
+ smi2021_clear_queue(dev);
+
+out_unlock:
+ mutex_unlock(&dev->v4l2_lock);
+
+ return rc;
+}
+
+static int stop_streaming(struct vb2_queue *vq)
+{
+ struct smi2021_dev *dev = vb2_get_drv_priv(vq);
+ /* HACK: Stop the audio subsystem,
+ * without this, the pcm middle-layer will hang waiting for more data.
+ *
+ * Is there a better way to do this?
+ */
+ if (dev->pcm_substream && dev->pcm_substream->runtime) {
+ struct snd_pcm_runtime *runtime = dev->pcm_substream->runtime;
+ if (runtime->status) {
+ runtime->status->state = SNDRV_PCM_STATE_DRAINING;
+ wake_up(&runtime->sleep);
+ }
+ }
+
+ if (mutex_lock_interruptible(&dev->v4l2_lock))
+ return -ERESTARTSYS;
+
+ smi2021_cancel_isoc(dev);
+ smi2021_free_isoc(dev);
+ smi2021_stop_hw(dev);
+ smi2021_clear_queue(dev);
+ smi2021_dbg("Streaming stopped!\n");
+
+ mutex_unlock(&dev->v4l2_lock);
+
+ return 0;
+}
+
+static struct vb2_ops smi2021_video_qops = {
+ .queue_setup = queue_setup,
+ .buf_queue = buffer_queue,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static struct video_device v4l2_template = {
+ .name = "easycap_smi2021_dc60",
+ .tvnorms = V4L2_STD_ALL,
+ .fops = &smi2021_fops,
+ .ioctl_ops = &smi2021_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+/* Must be called with both v4l2_lock and vb_queue_lock hold */
+void smi2021_clear_queue(struct smi2021_dev *dev)
+{
+ struct smi2021_buffer *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->buf_lock, flags);
+ while (!list_empty(&dev->avail_bufs)) {
+ buf = list_first_entry(&dev->avail_bufs,
+ struct smi2021_buffer, list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ }
+ dev->isoc_ctl.buf = NULL;
+ spin_unlock_irqrestore(&dev->buf_lock, flags);
+}
+
+int smi2021_vb2_setup(struct smi2021_dev *dev)
+{
+ int rc;
+ struct vb2_queue *q;
+
+ q = &dev->vb_vidq;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR;
+ q->drv_priv = dev;
+ q->buf_struct_size = sizeof(struct smi2021_buffer);
+ q->ops = &smi2021_video_qops;
+ q->mem_ops = &vb2_vmalloc_memops;
+
+ rc = vb2_queue_init(q);
+ if (rc < 0)
+ return rc;
+
+ /* Initialize video dma queue */
+ INIT_LIST_HEAD(&dev->avail_bufs);
+
+ return 0;
+}
+
+int smi2021_video_register(struct smi2021_dev *dev)
+{
+ int rc;
+
+ dev->vdev = v4l2_template;
+ dev->vdev.queue = &dev->vb_vidq;
+
+ dev->vdev.lock = &dev->v4l2_lock;
+ dev->vdev.queue->lock = &dev->vb_queue_lock;
+
+ dev->vdev.v4l2_dev = &dev->v4l2_dev;
+ set_bit(V4L2_FL_USE_FH_PRIO, &dev->vdev.flags);
+
+ /* PAL is default */
+ dev->norm = V4L2_STD_PAL;
+ dev->width = SMI2021_BYTES_PER_LINE / 2;
+ dev->height = SMI2021_PAL_LINES;
+
+ if (dev->device_cfg == SMI2021_DUAL_INPUT) {
+ input = dual_input;
+ inputs = ARRAY_SIZE(dual_input);
+ } else {
+ input = quad_input;
+ inputs = ARRAY_SIZE(quad_input);
+ }
+
+ dev->fmt = &format[0];
+
+ v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, dev->norm);
+ smi2021_set_input(dev);
+
+ video_set_drvdata(&dev->vdev, dev);
+ rc = video_register_device(&dev->vdev, VFL_TYPE_GRABBER, -1);
+ if (rc < 0) {
+ smi2021_err("video_register_device failed %d\n", rc);
+ return rc;
+ }
+
+ v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
+ video_device_node_name(&dev->vdev));
+
+ return 0;
+}
diff --git a/drivers/media/usb/smi2021/smi2021_video.c b/drivers/media/usb/smi2021/smi2021_video.c
new file mode 100644
index 0000000..4a71f20
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_video.c
@@ -0,0 +1,544 @@
+/*******************************************************************************
+ * smi2021_video.c *
+ * *
+ * USB Driver for SMI2021 - EasyCAP *
+ * *****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include "smi2021.h"
+
+static void print_usb_err(struct smi2021_dev *dev, int packet, int status)
+{
+ char *errmsg;
+
+ switch (status) {
+ case -ENOENT:
+ errmsg = "unlinked synchronuously";
+ break;
+ case -ECONNRESET:
+ errmsg = "unlinked asynchronuously";
+ break;
+ case -ENOSR:
+ errmsg = "Buffer error (overrun)";
+ break;
+ case -EPIPE:
+ errmsg = "Stalled (device not responding)";
+ break;
+ case -EOVERFLOW:
+ errmsg = "Babble (bad cable?)";
+ break;
+ case -EPROTO:
+ errmsg = "Bit-stuff error (bad cable?)";
+ break;
+ case -EILSEQ:
+ errmsg = "CRC/Timeout (could be anything)";
+ break;
+ case -ETIME:
+ errmsg = "Device does not respond";
+ break;
+ case -EXDEV:
+ errmsg = "Trying to capture from unconnected input?";
+ break;
+ default:
+ errmsg = "Unknown";
+ }
+
+ if (packet < 0) {
+ printk_ratelimited(KERN_WARNING "Urb status %d [%s]\n",
+ status, errmsg);
+ } else {
+ printk_ratelimited(KERN_INFO "URB packet %d, status %d [%s]\n",
+ packet, status, errmsg);
+ }
+}
+
+static struct smi2021_buffer *smi2021_next_buffer(struct smi2021_dev *dev)
+{
+ struct smi2021_buffer *buf = NULL;
+ unsigned long flags = 0;
+
+ WARN_ON(dev->isoc_ctl.buf);
+
+ spin_lock_irqsave(&dev->buf_lock, flags);
+ if (!list_empty(&dev->avail_bufs)) {
+ buf = list_first_entry(&dev->avail_bufs, struct smi2021_buffer,
+ list);
+ list_del(&buf->list);
+#ifdef DEBUG
+ } else if (printk_ratelimit()) {
+ smi2021_dbg("No buffers in queue!\n");
+#endif
+ }
+ spin_unlock_irqrestore(&dev->buf_lock, flags);
+
+ return buf;
+}
+
+static void smi2021_buffer_done(struct smi2021_dev *dev)
+{
+ struct smi2021_buffer *buf = dev->isoc_ctl.buf;
+
+ dev->buf_count++;
+ v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
+ buf->vb.v4l2_buf.sequence = dev->buf_count >> 1;
+ buf->vb.v4l2_buf.field = V4L2_FIELD_INTERLACED;
+ buf->vb.v4l2_buf.flags |= V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+ if (buf->pos < dev->width * dev->height * 2) {
+ buf->vb.v4l2_buf.bytesused = 0;
+ vb2_set_plane_payload(&buf->vb, 0, 0);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
+ } else {
+ buf->vb.v4l2_buf.bytesused = buf->pos;
+ vb2_set_plane_payload(&buf->vb, 0, buf->pos);
+ vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
+ }
+ dev->isoc_ctl.buf = NULL;
+}
+
+static void copy_video(struct smi2021_dev *dev, u8 p)
+{
+ struct smi2021_buffer *buf = dev->isoc_ctl.buf;
+
+ int lines_per_field = dev->height / 2;
+ int line = 0;
+ int pos_in_line = 0;
+ unsigned int offset = 0;
+ u8 *dst;
+
+ if (buf == NULL)
+ return;
+
+ if (buf->in_blank)
+ return;
+
+ if (buf->pos >= buf->length) {
+ if (buf->second_field == 0) {
+ /* We are probably trying to capture from
+ * a unconnected input
+ */
+ smi2021_buffer_done(dev);
+ } else {
+ printk_ratelimited(KERN_WARNING
+ "Buffer overflow!, max: %d bytes, av_lines_found: %d, second_field: %d\n",
+ buf->length, buf->trc_av,
+ buf->second_field);
+ }
+ return;
+ }
+
+ pos_in_line = buf->pos % SMI2021_BYTES_PER_LINE;
+ line = buf->pos / SMI2021_BYTES_PER_LINE;
+ if (line >= lines_per_field)
+ line -= lines_per_field;
+
+ if (line != buf->trc_av - 1) {
+ /* Keep video synchronized.
+ * The device will sometimes give us to many bytes
+ * for a line, before we get a new TRC.
+ * We just drop these bytes */
+ return;
+ }
+
+ if (buf->second_field)
+ offset += SMI2021_BYTES_PER_LINE;
+
+ offset += (SMI2021_BYTES_PER_LINE * line * 2) + pos_in_line;
+
+ /* Will this ever happen? */
+ if (offset >= buf->length) {
+ printk_ratelimited(KERN_INFO
+ "Offset calculation error, field: %d, line: %d, pos_in_line: %d\n",
+ buf->second_field, line, pos_in_line);
+ return;
+ }
+
+ dst = buf->mem + offset;
+ *dst = p;
+ buf->pos++;
+}
+
+#define is_sav(trc) \
+ ((trc & SMI2021_TRC_EAV) == 0x00)
+#define is_field2(trc) \
+ ((trc & SMI2021_TRC_FIELD_2) == SMI2021_TRC_FIELD_2)
+#define is_active_video(trc) \
+ ((trc & SMI2021_TRC_VBI) == 0x00)
+/*
+ * Parse the TRC.
+ * Grab a new buffer from the queue if don't have one
+ * and we are recieving the start of a video frame.
+ *
+ * Mark video buffers as done if we have one full frame.
+ */
+static void parse_trc(struct smi2021_dev *dev, u8 trc)
+{
+ struct smi2021_buffer *buf = dev->isoc_ctl.buf;
+ int lines_per_field = dev->height / 2;
+ int line = 0;
+
+ if (buf == NULL) {
+ if (!is_sav(trc))
+ return;
+
+ if (!is_active_video(trc))
+ return;
+
+ if (is_field2(trc))
+ return;
+
+ buf = smi2021_next_buffer(dev);
+ if (buf == NULL)
+ return;
+
+ dev->isoc_ctl.buf = buf;
+ }
+
+ if (is_sav(trc)) {
+ /* Start of VBI or ACTIVE VIDEO */
+ if (is_active_video(trc)) {
+ buf->in_blank = false;
+ buf->trc_av++;
+ } else {
+ /* VBI */
+ buf->in_blank = true;
+ }
+
+ if (!buf->second_field && is_field2(trc)) {
+ line = buf->pos / SMI2021_BYTES_PER_LINE;
+ if (line < lines_per_field)
+ goto buf_done;
+
+ buf->second_field = true;
+ buf->trc_av = 0;
+ }
+
+ if (buf->second_field && !is_field2(trc))
+ goto buf_done;
+ } else {
+ /* End of VBI or ACTIVE VIDEO */
+ buf->in_blank = true;
+ }
+
+ return;
+
+buf_done:
+ smi2021_buffer_done(dev);
+}
+
+/*
+ * Scan the saa7113 Active video data.
+ * This data is:
+ * 4 bytes header (0xff 0x00 0x00 [TRC/SAV])
+ * 1440 bytes of UYUV Video data
+ * 4 bytes footer (0xff 0x00 0x00 [TRC/EAV])
+ *
+ * TRC = Time Reference Code.
+ * SAV = Start Active Video.
+ * EAV = End Active Video.
+ * This is described in the saa7113 datasheet.
+ */
+static void parse_video(struct smi2021_dev *dev, u8 *p, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ switch (dev->sync_state) {
+ case HSYNC:
+ if (p[i] == 0xff)
+ dev->sync_state = SYNCZ1;
+ else
+ copy_video(dev, p[i]);
+ break;
+ case SYNCZ1:
+ if (p[i] == 0x00) {
+ dev->sync_state = SYNCZ2;
+ } else {
+ dev->sync_state = HSYNC;
+ copy_video(dev, 0xff);
+ copy_video(dev, p[i]);
+ }
+ break;
+ case SYNCZ2:
+ if (p[i] == 0x00) {
+ dev->sync_state = TRC;
+ } else {
+ dev->sync_state = HSYNC;
+ copy_video(dev, 0xff);
+ copy_video(dev, 0x00);
+ copy_video(dev, p[i]);
+ }
+ break;
+ case TRC:
+ dev->sync_state = HSYNC;
+ parse_trc(dev, p[i]);
+ break;
+ }
+ }
+
+}
+/*
+ *
+ * The device delivers data in chunks of 0x400 bytes.
+ * The four first bytes is a magic header to identify the chunks.
+ * 0xaa 0xaa 0x00 0x00 = saa7113 Active Video Data
+ * 0xaa 0xaa 0x00 0x01 = PCM - 24Bit 2 Channel audio data
+ */
+static void process_packet(struct smi2021_dev *dev, u8 *p, int len)
+{
+ int i;
+ u32 *header;
+
+ if (len % 0x400 != 0) {
+ printk_ratelimited(KERN_INFO "smi2021::%s: len: %d\n",
+ __func__, len);
+ return;
+ }
+
+ for (i = 0; i < len; i += 0x400) {
+ header = (u32 *)(p + i);
+ switch (*header) {
+ case cpu_to_be32(0xaaaa0000): {
+ parse_video(dev, p+i+4, 0x400-4);
+ break;
+ }
+ case cpu_to_be32(0xaaaa0001): {
+ smi2021_audio(dev, p+i+4, 0x400-4);
+ break;
+ }
+ default: {
+ /* Nothing */
+ }
+ }
+ }
+}
+
+/*
+ * Interrupt called by URB callback
+ */
+static void smi2021_isoc_isr(struct urb *urb)
+{
+ int i, rc, status, len;
+ struct smi2021_dev *dev = urb->context;
+ u8 *p;
+
+ switch (urb->status) {
+ case 0:
+ break;
+ case -ECONNRESET: /* kill */
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* uvc driver frees the queue here */
+ return;
+ default:
+ smi2021_err("urb error! status %d\n", urb->status);
+ return;
+ }
+
+ if (urb->status < 0)
+ print_usb_err(dev, -1, status);
+
+ if (dev == NULL) {
+ smi2021_warn("called with null device\n");
+ return;
+ }
+
+ for (i = 0; i < urb->number_of_packets; i++) {
+
+ status = urb->iso_frame_desc[i].status;
+
+ if (status < 0) {
+ print_usb_err(dev, i, status);
+ continue;
+ }
+
+ p = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+ len = urb->iso_frame_desc[i].actual_length;
+ process_packet(dev, p, len);
+ }
+
+ for (i = 0; i < urb->number_of_packets; i++) {
+ urb->iso_frame_desc[i].status = 0;
+ urb->iso_frame_desc[i].actual_length = 0;
+ }
+
+ rc = usb_submit_urb(urb, GFP_ATOMIC);
+ if (rc)
+ smi2021_err("urb re-submit failed (%d)\n", rc);
+}
+
+/*
+ * Cancel urbs
+ * This function can not be called in atomic context
+ */
+void smi2021_cancel_isoc(struct smi2021_dev *dev)
+{
+ int i, num_bufs = dev->isoc_ctl.num_bufs;
+ if (!num_bufs)
+ return;
+
+ smi2021_dbg("killing %d urbs...\n", num_bufs);
+
+ for (i = 0; i < num_bufs; i++)
+ usb_kill_urb(dev->isoc_ctl.urb[i]);
+
+ smi2021_dbg("all urbs killed\n");
+
+}
+
+/*
+ * Releases urb and transfer buffers
+ * Obviously, associated urb must be killed before releasing it
+ */
+void smi2021_free_isoc(struct smi2021_dev *dev)
+{
+ struct urb *urb;
+ int i, num_bufs = dev->isoc_ctl.num_bufs;
+
+ smi2021_dbg("freeing %d urb buffers...\n", num_bufs);
+
+ for (i = 0; i < num_bufs; i++) {
+ urb = dev->isoc_ctl.urb[i];
+ if (urb) {
+ if (dev->isoc_ctl.transfer_buffer[i]) {
+#ifndef CONFIG_DMA_NONCOHERENT
+ usb_free_coherent(dev->udev,
+ urb->transfer_buffer_length,
+ dev->isoc_ctl.transfer_buffer[i],
+ urb->transfer_dma);
+#else
+ kfree(dev->isoc_ctl.transfer_buffer[i]);
+#endif
+ }
+ usb_free_urb(urb);
+ dev->isoc_ctl.urb[i] = NULL;
+ }
+ dev->isoc_ctl.transfer_buffer[i] = NULL;
+ }
+
+ kfree(dev->isoc_ctl.urb);
+ kfree(dev->isoc_ctl.transfer_buffer);
+
+ dev->isoc_ctl.urb = NULL;
+ dev->isoc_ctl.transfer_buffer = NULL;
+ dev->isoc_ctl.num_bufs = 0;
+
+ smi2021_dbg("all urb buffers freed\n");
+}
+
+/*
+ * Helper for canceling and freeing urbs
+ * This function can not be called in atomic context
+ */
+void smi2021_uninit_isoc(struct smi2021_dev *dev)
+{
+ smi2021_cancel_isoc(dev);
+ smi2021_free_isoc(dev);
+}
+
+
+int smi2021_alloc_isoc(struct smi2021_dev *dev)
+{
+ struct urb *urb;
+ int i, j, k, sb_size, max_packets, num_bufs;
+
+ if (dev->isoc_ctl.num_bufs)
+ smi2021_uninit_isoc(dev);
+
+ num_bufs = SMI2021_ISOC_BUFS;
+ max_packets = SMI2021_ISOC_PACKETS;
+ sb_size = max_packets * SMI2021_MAX_PKT_SIZE;
+
+ dev->isoc_ctl.buf = NULL;
+ dev->isoc_ctl.max_pkt_size = SMI2021_MAX_PKT_SIZE;
+ dev->isoc_ctl.urb = kzalloc(sizeof(void *) * num_bufs, GFP_KERNEL);
+ if (!dev->isoc_ctl.urb) {
+ smi2021_err("out of memory for urb array\n");
+ return -ENOMEM;
+ }
+
+ dev->isoc_ctl.transfer_buffer = kzalloc(sizeof(void *) * num_bufs,
+ GFP_KERNEL);
+ if (!dev->isoc_ctl.transfer_buffer) {
+ smi2021_err("out of memory for usb transfer\n");
+ kfree(dev->isoc_ctl.urb);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < num_bufs; i++) {
+ urb = usb_alloc_urb(max_packets, GFP_KERNEL);
+ if (!urb) {
+ smi2021_err("connot allocate urb[%d]\n", i);
+ goto free_i_bufs;
+ }
+ dev->isoc_ctl.urb[i] = urb;
+#ifndef CONFIG_DMA_NONCOHERENT
+ dev->isoc_ctl.transfer_buffer[i] = usb_alloc_coherent(
+ dev->udev, sb_size, GFP_KERNEL,
+ &urb->transfer_dma);
+#else
+ dev->isoc_ctl.transfer_buffer[i] = kmalloc(sb_size,
+ GFP_KERNEL);
+#endif
+ if (!dev->isoc_ctl.transfer_buffer[i]) {
+ smi2021_err("cannot alloc %d bytes for tx[%d] buffer",
+ sb_size, i);
+ goto free_i_bufs;
+ }
+ /* Do not leak kernel data */
+ memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size);
+
+ urb->dev = dev->udev;
+ urb->pipe = usb_rcvisocpipe(dev->udev, SMI2021_ISOC_EP);
+ urb->transfer_buffer = dev->isoc_ctl.transfer_buffer[i];
+ urb->transfer_buffer_length = sb_size;
+ urb->complete = smi2021_isoc_isr;
+ urb->context = dev;
+ urb->interval = 1;
+ urb->start_frame = 0;
+ urb->number_of_packets = max_packets;
+#ifndef CONFIG_DMA_NONCOHERENT
+ urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+#else
+ urb->transfer_flags = URB_ISO_ASAP;
+#endif
+ k = 0;
+ for (j = 0; j < max_packets; j++) {
+ urb->iso_frame_desc[j].offset = k;
+ urb->iso_frame_desc[j].length =
+ dev->isoc_ctl.max_pkt_size;
+ k += dev->isoc_ctl.max_pkt_size;
+ }
+ }
+ smi2021_dbg("urbs allocated\n");
+ dev->isoc_ctl.num_bufs = num_bufs;
+ return 0;
+
+free_i_bufs:
+ dev->isoc_ctl.num_bufs = i+1;
+ smi2021_free_isoc(dev);
+ return -ENOMEM;
+}
--
1.8.2.1

2013-04-25 19:16:16

by Jon Arne Jørgensen

[permalink] [raw]
Subject: [RFC V2 1/3] [smi2021] Add gm7113c chip to the saa7115 driver

The somagic device uses the gm7113c chip to digitize analog video,
this is a clone of the saa7113 chip.

The gm7113c can't be identified over i2c, so I can't rely on
saa7115 autodetection.

Signed-off-by: Jon Arne Jørgensen <[email protected]>
---
drivers/media/i2c/saa7115.c | 61 ++++++++++++++++++++++++++++++++++-------
include/media/v4l2-chip-ident.h | 3 ++
2 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c
index 6b6788c..e93b50a 100644
--- a/drivers/media/i2c/saa7115.c
+++ b/drivers/media/i2c/saa7115.c
@@ -54,7 +54,7 @@

MODULE_DESCRIPTION("Philips SAA7111/SAA7113/SAA7114/SAA7115/SAA7118 video decoder driver");
MODULE_AUTHOR( "Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, "
- "Hans Verkuil, Mauro Carvalho Chehab");
+ "Hans Verkuil, Mauro Carvalho Chehab, Jon Arne Jørgensen");
MODULE_LICENSE("GPL");

static bool debug;
@@ -126,6 +126,7 @@ static int saa711x_has_reg(const int id, const u8 reg)
return 0;

switch (id) {
+ case V4L2_IDENT_GM7113C:
case V4L2_IDENT_SAA7113:
return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) &&
reg != 0x5d && reg < 0x63;
@@ -292,7 +293,7 @@ static const unsigned char saa7115_cfg_reset_scaler[] = {
0x00, 0x00
};

-/* ============== SAA7715 VIDEO templates ============= */
+/* ============== SAA7115 VIDEO templates ============= */

static const unsigned char saa7115_cfg_60hz_video[] = {
R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
@@ -445,7 +446,27 @@ static const unsigned char saa7115_cfg_50hz_video[] = {
0x00, 0x00
};

-/* ============== SAA7715 VIDEO templates (end) ======= */
+/* ============== SAA7115 VIDEO templates (end) ======= */
+
+/* ============== GM7113C VIDEO templates ============= */
+
+static const unsigned char gm7113c_cfg_60hz_video[] = {
+ R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */
+ R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */
+
+ R_5A_V_OFF_FOR_SLICER, 0x06, /* standard 60hz value for ITU656 line counting */
+ 0x00, 0x00
+};
+
+static const unsigned char gm7113c_cfg_50hz_video[] = {
+ R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */
+ R_0E_CHROMA_CNTL_1, 0x07,
+
+ R_5A_V_OFF_FOR_SLICER, 0x03, /* standard 50hz value */
+ 0x00, 0x00
+};
+
+/* ============== GM7113C VIDEO templates (end) ======= */

static const unsigned char saa7115_cfg_vbi_on[] = {
R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
@@ -927,11 +948,17 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
// This works for NTSC-M, SECAM-L and the 50Hz PAL variants.
if (std & V4L2_STD_525_60) {
v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n");
- saa711x_writeregs(sd, saa7115_cfg_60hz_video);
+ if (state->ident == V4L2_IDENT_GM7113C)
+ saa711x_writeregs(sd, gm7113c_cfg_60hz_video);
+ else
+ saa711x_writeregs(sd, saa7115_cfg_60hz_video);
saa711x_set_size(sd, 720, 480);
} else {
v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n");
- saa711x_writeregs(sd, saa7115_cfg_50hz_video);
+ if (state->ident == V4L2_IDENT_GM7113C)
+ saa711x_writeregs(sd, gm7113c_cfg_50hz_video);
+ else
+ saa711x_writeregs(sd, saa7115_cfg_50hz_video);
saa711x_set_size(sd, 720, 576);
}

@@ -944,7 +971,8 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
011 NTSC N (3.58MHz) PAL M (3.58MHz)
100 reserved NTSC-Japan (3.58MHz)
*/
- if (state->ident <= V4L2_IDENT_SAA7113) {
+ if (state->ident <= V4L2_IDENT_SAA7113 ||
+ state->ident == V4L2_IDENT_GM7113C) {
u8 reg = saa711x_read(sd, R_0E_CHROMA_CNTL_1) & 0x8f;

if (std == V4L2_STD_PAL_M) {
@@ -1215,7 +1243,8 @@ static int saa711x_s_routing(struct v4l2_subdev *sd,
input, output);

/* saa7111/3 does not have these inputs */
- if (state->ident <= V4L2_IDENT_SAA7113 &&
+ if ((state->ident <= V4L2_IDENT_SAA7113 ||
+ state->ident == V4L2_IDENT_GM7113C) &&
(input == SAA7115_COMPOSITE4 ||
input == SAA7115_COMPOSITE5)) {
return -EINVAL;
@@ -1586,8 +1615,11 @@ static int saa711x_probe(struct i2c_client *client,

chip_id = name[5];

+
/* Check whether this chip is part of the saa711x series */
- if (memcmp(name + 1, "f711", 4)) {
+ if (memcmp(id->name + 1, "gm7113c", 7)) {
+ chip_id = 'c';
+ } else if (memcmp(name + 1, "f711", 4)) {
v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n",
client->addr << 1, name);
return -ENODEV;
@@ -1598,8 +1630,12 @@ static int saa711x_probe(struct i2c_client *client,
v4l_warn(client, "found saa711%c while %s was expected\n",
chip_id, id->name);
}
- snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
- v4l_info(client, "saa711%c found (%s) @ 0x%x (%s)\n", chip_id, name,
+ if (chip_id == 'c')
+ snprintf(client->name, sizeof(client->name), "%s", id->name);
+ else
+ snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
+
+ v4l_info(client, "%s found (%s) @ 0x%x (%s)\n", client->name, name,
client->addr << 1, client->adapter->name);

state = kzalloc(sizeof(struct saa711x_state), GFP_KERNEL);
@@ -1645,6 +1681,9 @@ static int saa711x_probe(struct i2c_client *client,
state->ident = V4L2_IDENT_SAA7111A;
}
break;
+ case 'c':
+ state->ident = V4L2_IDENT_GM7113C;
+ break;
case '3':
state->ident = V4L2_IDENT_SAA7113;
break;
@@ -1675,6 +1714,7 @@ static int saa711x_probe(struct i2c_client *client,
saa711x_writeregs(sd, saa7111_init);
break;
case V4L2_IDENT_SAA7113:
+ case V4L2_IDENT_GM7113C:
saa711x_writeregs(sd, saa7113_init);
break;
default:
@@ -1711,6 +1751,7 @@ static const struct i2c_device_id saa711x_id[] = {
{ "saa7114", 0 },
{ "saa7115", 0 },
{ "saa7118", 0 },
+ { "gm7113c", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, saa711x_id);
diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h
index 4ee125b..fc13d53 100644
--- a/include/media/v4l2-chip-ident.h
+++ b/include/media/v4l2-chip-ident.h
@@ -51,6 +51,9 @@ enum {
V4L2_IDENT_SAA7114 = 104,
V4L2_IDENT_SAA7115 = 105,
V4L2_IDENT_SAA7118 = 108,
+ /* This chip is a chinese clone of the saa7113 chip,
+ * with some minor changes/bugs */
+ V4L2_IDENT_GM7113C = 149,

/* module saa7127: reserved range 150-199 */
V4L2_IDENT_SAA7127 = 157,
--
1.8.2.1

2013-04-25 20:13:58

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC V2 1/3] [smi2021] Add gm7113c chip to the saa7115 driver

Em Thu, 25 Apr 2013 21:10:18 +0200
Jon Arne Jørgensen <[email protected]> escreveu:

> The somagic device uses the gm7113c chip to digitize analog video,
> this is a clone of the saa7113 chip.
>
> The gm7113c can't be identified over i2c, so I can't rely on
> saa7115 autodetection.
>
> Signed-off-by: Jon Arne Jørgensen <[email protected]>
> ---
> drivers/media/i2c/saa7115.c | 61 ++++++++++++++++++++++++++++++++++-------
> include/media/v4l2-chip-ident.h | 3 ++
> 2 files changed, 54 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c
> index 6b6788c..e93b50a 100644
> --- a/drivers/media/i2c/saa7115.c
> +++ b/drivers/media/i2c/saa7115.c
> @@ -54,7 +54,7 @@
>
> MODULE_DESCRIPTION("Philips SAA7111/SAA7113/SAA7114/SAA7115/SAA7118 video decoder driver");
> MODULE_AUTHOR( "Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, "
> - "Hans Verkuil, Mauro Carvalho Chehab");
> + "Hans Verkuil, Mauro Carvalho Chehab, Jon Arne Jørgensen");

Hi Jon,

I was told once by Greg KH that the minimal number of changes to be one
of the driver's authors is to change a significant amount of the code
(like 20% or more).

So, I prefer if you don't change it there.

> MODULE_LICENSE("GPL");
>
> static bool debug;
> @@ -126,6 +126,7 @@ static int saa711x_has_reg(const int id, const u8 reg)
> return 0;
>
> switch (id) {
> + case V4L2_IDENT_GM7113C:
> case V4L2_IDENT_SAA7113:
> return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) &&
> reg != 0x5d && reg < 0x63;
> @@ -292,7 +293,7 @@ static const unsigned char saa7115_cfg_reset_scaler[] = {
> 0x00, 0x00
> };
>
> -/* ============== SAA7715 VIDEO templates ============= */
> +/* ============== SAA7115 VIDEO templates ============= */
>
> static const unsigned char saa7115_cfg_60hz_video[] = {
> R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> @@ -445,7 +446,27 @@ static const unsigned char saa7115_cfg_50hz_video[] = {
> 0x00, 0x00
> };
>
> -/* ============== SAA7715 VIDEO templates (end) ======= */
> +/* ============== SAA7115 VIDEO templates (end) ======= */
> +
> +/* ============== GM7113C VIDEO templates ============= */
> +
> +static const unsigned char gm7113c_cfg_60hz_video[] = {
> + R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */
> + R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */
> +
> + R_5A_V_OFF_FOR_SLICER, 0x06, /* standard 60hz value for ITU656 line counting */
> + 0x00, 0x00
> +};
> +
> +static const unsigned char gm7113c_cfg_50hz_video[] = {
> + R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */
> + R_0E_CHROMA_CNTL_1, 0x07,
> +
> + R_5A_V_OFF_FOR_SLICER, 0x03, /* standard 50hz value */
> + 0x00, 0x00
> +};
> +
> +/* ============== GM7113C VIDEO templates (end) ======= */
>
> static const unsigned char saa7115_cfg_vbi_on[] = {
> R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> @@ -927,11 +948,17 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> // This works for NTSC-M, SECAM-L and the 50Hz PAL variants.
> if (std & V4L2_STD_525_60) {
> v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n");
> - saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> + if (state->ident == V4L2_IDENT_GM7113C)
> + saa711x_writeregs(sd, gm7113c_cfg_60hz_video);
> + else
> + saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> saa711x_set_size(sd, 720, 480);
> } else {
> v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n");
> - saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> + if (state->ident == V4L2_IDENT_GM7113C)
> + saa711x_writeregs(sd, gm7113c_cfg_50hz_video);
> + else
> + saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> saa711x_set_size(sd, 720, 576);
> }
>
> @@ -944,7 +971,8 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> 011 NTSC N (3.58MHz) PAL M (3.58MHz)
> 100 reserved NTSC-Japan (3.58MHz)
> */
> - if (state->ident <= V4L2_IDENT_SAA7113) {
> + if (state->ident <= V4L2_IDENT_SAA7113 ||
> + state->ident == V4L2_IDENT_GM7113C) {
> u8 reg = saa711x_read(sd, R_0E_CHROMA_CNTL_1) & 0x8f;
>
> if (std == V4L2_STD_PAL_M) {
> @@ -1215,7 +1243,8 @@ static int saa711x_s_routing(struct v4l2_subdev *sd,
> input, output);
>
> /* saa7111/3 does not have these inputs */
> - if (state->ident <= V4L2_IDENT_SAA7113 &&
> + if ((state->ident <= V4L2_IDENT_SAA7113 ||
> + state->ident == V4L2_IDENT_GM7113C) &&
> (input == SAA7115_COMPOSITE4 ||
> input == SAA7115_COMPOSITE5)) {
> return -EINVAL;
> @@ -1586,8 +1615,11 @@ static int i2c_client *client,
>
> chip_id = name[5];
>
> +
> /* Check whether this chip is part of the saa711x series */
> - if (memcmp(name + 1, "f711", 4)) {
> + if (memcmp(id->name + 1, "gm7113c", 7)) {
> + chip_id = 'c';

There are several issues on the above:
1) "id" may be NULL on autodetect mode;

2) Why are you adding 1 here?
It should be, instead id->name

3) memcmp returns 0 if matches. So, the test is wrong.
So, It should be instead:
if (!memcmp(id->name, "gm7113c", 7)) {

4) Also, while that works, it seems a little hackish...

> + } else if (memcmp(name + 1, "f711", 4)) {
> v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n",
> client->addr << 1, name);
> return -ENODEV;
> @@ -1598,8 +1630,12 @@ static int saa711x_probe(struct i2c_client *client,
> v4l_warn(client, "found saa711%c while %s was expected\n",
> chip_id, id->name);
> }
> - snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> - v4l_info(client, "saa711%c found (%s) @ 0x%x (%s)\n", chip_id, name,
> + if (chip_id == 'c')

especially by needing to add a weird if here.
See more below:

> + snprintf(client->name, sizeof(client->name), "%s", id->name);
> + else
> + snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> +
> + v4l_info(client, "%s found (%s) @ 0x%x (%s)\n", client->name, name,
> client->addr << 1, client->adapter->name);
>
> state = kzalloc(sizeof(struct saa711x_state), GFP_KERNEL);
> @@ -1645,6 +1681,9 @@ static int saa711x_probe(struct i2c_client *client,
> state->ident = V4L2_IDENT_SAA7111A;
> }
> break;
> + case 'c':
> + state->ident = V4L2_IDENT_GM7113C;
> + break;

The better would be to initialize state->ident earlier, together with memcmp.

Even better: please move the detection code into a separate
routine that would internally fill state->ident and client->name
and do what's needed to detect the chip.

That would be cleaner and will reduce a little bit the complexity
inside saa711x_probe.

Something like:

static int saa711x_detect_chip(struct i2c_client *client,
struct saa711x_state *state,
const struct i2c_device_id *id)
{
int i;
char chip_id, name[16];

/*
* Check for gm7113c (a saa7113 clone). Currently, there's no
* known way to autodetect it, so boards that use will need to
* explicitly fill the id->name field.
*/
if (id && !memcmp(id->name, "gm7113c", 7)) {
state->ident = V4L2_IDENT_GM7113C;
snprintf(client->name, sizeof(client->name), "%s", id->name);
return 0;
}

/* Check for Philips/NXP original chips */
for (i = 0; i < sizeof(name); i++) {
i2c_smbus_write_byte_data(client, 0, i);
name[i] = (i2c_smbus_read_byte_data(client, 0) & 0x0f) + '0';
if (name[i] > '9')
name[i] += 'a' - '9' - 1;
}
name[i] = '\0';

if (memcmp(name + 1, "f711", 4))
return -ENODEV;

chip_id = name[5];

snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);

/*
* Put here the code that fills state->ident for Philips/NXP chips
*/
...

return 0;
}

> case '3':
> state->ident = V4L2_IDENT_SAA7113;
> break;
> @@ -1675,6 +1714,7 @@ static int saa711x_probe(struct i2c_client *client,
> saa711x_writeregs(sd, saa7111_init);
> break;
> case V4L2_IDENT_SAA7113:
> + case V4L2_IDENT_GM7113C:
> saa711x_writeregs(sd, saa7113_init);
> break;
> default:
> @@ -1711,6 +1751,7 @@ static const struct i2c_device_id saa711x_id[] = {
> { "saa7114", 0 },
> { "saa7115", 0 },
> { "saa7118", 0 },
> + { "gm7113c", 0 },
> { }
> };
> MODULE_DEVICE_TABLE(i2c, saa711x_id);
> diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h
> index 4ee125b..fc13d53 100644
> --- a/include/media/v4l2-chip-ident.h
> +++ b/include/media/v4l2-chip-ident.h
> @@ -51,6 +51,9 @@ enum {
> V4L2_IDENT_SAA7114 = 104,
> V4L2_IDENT_SAA7115 = 105,
> V4L2_IDENT_SAA7118 = 108,
> + /* This chip is a chinese clone of the saa7113 chip,
> + * with some minor changes/bugs */
> + V4L2_IDENT_GM7113C = 149,
>
> /* module saa7127: reserved range 150-199 */
> V4L2_IDENT_SAA7127 = 157,


--

Cheers,
Mauro

2013-04-25 20:30:14

by Jon Arne Jørgensen

[permalink] [raw]
Subject: Re: [RFC V2 1/3] [smi2021] Add gm7113c chip to the saa7115 driver

On Thu, Apr 25, 2013 at 05:13:28PM -0300, Mauro Carvalho Chehab wrote:
> Em Thu, 25 Apr 2013 21:10:18 +0200
> Jon Arne Jørgensen <[email protected]> escreveu:
>
> > The somagic device uses the gm7113c chip to digitize analog video,
> > this is a clone of the saa7113 chip.
> >
> > The gm7113c can't be identified over i2c, so I can't rely on
> > saa7115 autodetection.
> >
> > Signed-off-by: Jon Arne Jørgensen <[email protected]>
> > ---
> > drivers/media/i2c/saa7115.c | 61 ++++++++++++++++++++++++++++++++++-------
> > include/media/v4l2-chip-ident.h | 3 ++
> > 2 files changed, 54 insertions(+), 10 deletions(-)
> >
> > diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c
> > index 6b6788c..e93b50a 100644
> > --- a/drivers/media/i2c/saa7115.c
> > +++ b/drivers/media/i2c/saa7115.c
> > @@ -54,7 +54,7 @@
> >
> > MODULE_DESCRIPTION("Philips SAA7111/SAA7113/SAA7114/SAA7115/SAA7118 video decoder driver");
> > MODULE_AUTHOR( "Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, "
> > - "Hans Verkuil, Mauro Carvalho Chehab");
> > + "Hans Verkuil, Mauro Carvalho Chehab, Jon Arne Jørgensen");
>
> Hi Jon,
>
> I was told once by Greg KH that the minimal number of changes to be one
> of the driver's authors is to change a significant amount of the code
> (like 20% or more).
>
> So, I prefer if you don't change it there.
>

Ok, no problem.

> > MODULE_LICENSE("GPL");
> >
> > static bool debug;
> > @@ -126,6 +126,7 @@ static int saa711x_has_reg(const int id, const u8 reg)
> > return 0;
> >
> > switch (id) {
> > + case V4L2_IDENT_GM7113C:
> > case V4L2_IDENT_SAA7113:
> > return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) &&
> > reg != 0x5d && reg < 0x63;
> > @@ -292,7 +293,7 @@ static const unsigned char saa7115_cfg_reset_scaler[] = {
> > 0x00, 0x00
> > };
> >
> > -/* ============== SAA7715 VIDEO templates ============= */
> > +/* ============== SAA7115 VIDEO templates ============= */
> >
> > static const unsigned char saa7115_cfg_60hz_video[] = {
> > R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> > @@ -445,7 +446,27 @@ static const unsigned char saa7115_cfg_50hz_video[] = {
> > 0x00, 0x00
> > };
> >
> > -/* ============== SAA7715 VIDEO templates (end) ======= */
> > +/* ============== SAA7115 VIDEO templates (end) ======= */
> > +
> > +/* ============== GM7113C VIDEO templates ============= */
> > +
> > +static const unsigned char gm7113c_cfg_60hz_video[] = {
> > + R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */
> > + R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */
> > +
> > + R_5A_V_OFF_FOR_SLICER, 0x06, /* standard 60hz value for ITU656 line counting */
> > + 0x00, 0x00
> > +};
> > +
> > +static const unsigned char gm7113c_cfg_50hz_video[] = {
> > + R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */
> > + R_0E_CHROMA_CNTL_1, 0x07,
> > +
> > + R_5A_V_OFF_FOR_SLICER, 0x03, /* standard 50hz value */
> > + 0x00, 0x00
> > +};
> > +
> > +/* ============== GM7113C VIDEO templates (end) ======= */
> >
> > static const unsigned char saa7115_cfg_vbi_on[] = {
> > R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> > @@ -927,11 +948,17 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> > // This works for NTSC-M, SECAM-L and the 50Hz PAL variants.
> > if (std & V4L2_STD_525_60) {
> > v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n");
> > - saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> > + if (state->ident == V4L2_IDENT_GM7113C)
> > + saa711x_writeregs(sd, gm7113c_cfg_60hz_video);
> > + else
> > + saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> > saa711x_set_size(sd, 720, 480);
> > } else {
> > v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n");
> > - saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> > + if (state->ident == V4L2_IDENT_GM7113C)
> > + saa711x_writeregs(sd, gm7113c_cfg_50hz_video);
> > + else
> > + saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> > saa711x_set_size(sd, 720, 576);
> > }
> >
> > @@ -944,7 +971,8 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> > 011 NTSC N (3.58MHz) PAL M (3.58MHz)
> > 100 reserved NTSC-Japan (3.58MHz)
> > */
> > - if (state->ident <= V4L2_IDENT_SAA7113) {
> > + if (state->ident <= V4L2_IDENT_SAA7113 ||
> > + state->ident == V4L2_IDENT_GM7113C) {
> > u8 reg = saa711x_read(sd, R_0E_CHROMA_CNTL_1) & 0x8f;
> >
> > if (std == V4L2_STD_PAL_M) {
> > @@ -1215,7 +1243,8 @@ static int saa711x_s_routing(struct v4l2_subdev *sd,
> > input, output);
> >
> > /* saa7111/3 does not have these inputs */
> > - if (state->ident <= V4L2_IDENT_SAA7113 &&
> > + if ((state->ident <= V4L2_IDENT_SAA7113 ||
> > + state->ident == V4L2_IDENT_GM7113C) &&
> > (input == SAA7115_COMPOSITE4 ||
> > input == SAA7115_COMPOSITE5)) {
> > return -EINVAL;
> > @@ -1586,8 +1615,11 @@ static int i2c_client *client,
> >
> > chip_id = name[5];
> >
> > +
> > /* Check whether this chip is part of the saa711x series */
> > - if (memcmp(name + 1, "f711", 4)) {
> > + if (memcmp(id->name + 1, "gm7113c", 7)) {
> > + chip_id = 'c';
>
> There are several issues on the above:
> 1) "id" may be NULL on autodetect mode;
>
> 2) Why are you adding 1 here?
> It should be, instead id->name
>
> 3) memcmp returns 0 if matches. So, the test is wrong.
> So, It should be instead:
> if (!memcmp(id->name, "gm7113c", 7)) {
>
> 4) Also, while that works, it seems a little hackish...
>

Oh, this is embarrassing.
I just tried to change as little as possible in this module to make the
device work.

You are completely right, it's just an ugly hack.

> > + } else if (memcmp(name + 1, "f711", 4)) {
> > v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n",
> > client->addr << 1, name);
> > return -ENODEV;
> > @@ -1598,8 +1630,12 @@ static int saa711x_probe(struct i2c_client *client,
> > v4l_warn(client, "found saa711%c while %s was expected\n",
> > chip_id, id->name);
> > }
> > - snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> > - v4l_info(client, "saa711%c found (%s) @ 0x%x (%s)\n", chip_id, name,
> > + if (chip_id == 'c')
>
> especially by needing to add a weird if here.
> See more below:
>
> > + snprintf(client->name, sizeof(client->name), "%s", id->name);
> > + else
> > + snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> > +
> > + v4l_info(client, "%s found (%s) @ 0x%x (%s)\n", client->name, name,
> > client->addr << 1, client->adapter->name);
> >
> > state = kzalloc(sizeof(struct saa711x_state), GFP_KERNEL);
> > @@ -1645,6 +1681,9 @@ static int saa711x_probe(struct i2c_client *client,
> > state->ident = V4L2_IDENT_SAA7111A;
> > }
> > break;
> > + case 'c':
> > + state->ident = V4L2_IDENT_GM7113C;
> > + break;
>
> The better would be to initialize state->ident earlier, together with memcmp.
>
> Even better: please move the detection code into a separate
> routine that would internally fill state->ident and client->name
> and do what's needed to detect the chip.
>
> That would be cleaner and will reduce a little bit the complexity
> inside saa711x_probe.
>
> Something like:
>
> static int saa711x_detect_chip(struct i2c_client *client,
> struct saa711x_state *state,
> const struct i2c_device_id *id)
> {
> int i;
> char chip_id, name[16];
>
> /*
> * Check for gm7113c (a saa7113 clone). Currently, there's no
> * known way to autodetect it, so boards that use will need to
> * explicitly fill the id->name field.
> */
> if (id && !memcmp(id->name, "gm7113c", 7)) {
> state->ident = V4L2_IDENT_GM7113C;
> snprintf(client->name, sizeof(client->name), "%s", id->name);
> return 0;
> }
>
> /* Check for Philips/NXP original chips */
> for (i = 0; i < sizeof(name); i++) {
> i2c_smbus_write_byte_data(client, 0, i);
> name[i] = (i2c_smbus_read_byte_data(client, 0) & 0x0f) + '0';
> if (name[i] > '9')
> name[i] += 'a' - '9' - 1;
> }
> name[i] = '\0';
>
> if (memcmp(name + 1, "f711", 4))
> return -ENODEV;
>
> chip_id = name[5];
>
> snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
>
> /*
> * Put here the code that fills state->ident for Philips/NXP chips
> */
> ...
>
> return 0;

Yes this seems to be a much better way to do it.
I will fix my code.

Thank you.

> }
>
> > case '3':
> > state->ident = V4L2_IDENT_SAA7113;
> > break;
> > @@ -1675,6 +1714,7 @@ static int saa711x_probe(struct i2c_client *client,
> > saa711x_writeregs(sd, saa7111_init);
> > break;
> > case V4L2_IDENT_SAA7113:
> > + case V4L2_IDENT_GM7113C:
> > saa711x_writeregs(sd, saa7113_init);
> > break;
> > default:
> > @@ -1711,6 +1751,7 @@ static const struct i2c_device_id saa711x_id[] = {
> > { "saa7114", 0 },
> > { "saa7115", 0 },
> > { "saa7118", 0 },
> > + { "gm7113c", 0 },
> > { }
> > };
> > MODULE_DEVICE_TABLE(i2c, saa711x_id);
> > diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h
> > index 4ee125b..fc13d53 100644
> > --- a/include/media/v4l2-chip-ident.h
> > +++ b/include/media/v4l2-chip-ident.h
> > @@ -51,6 +51,9 @@ enum {
> > V4L2_IDENT_SAA7114 = 104,
> > V4L2_IDENT_SAA7115 = 105,
> > V4L2_IDENT_SAA7118 = 108,
> > + /* This chip is a chinese clone of the saa7113 chip,
> > + * with some minor changes/bugs */
> > + V4L2_IDENT_GM7113C = 149,
> >
> > /* module saa7127: reserved range 150-199 */
> > V4L2_IDENT_SAA7127 = 157,
>
>
> --
>
> Cheers,
> Mauro
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2013-04-25 20:36:50

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC V2 1/3] [smi2021] Add gm7113c chip to the saa7115 driver

Em Thu, 25 Apr 2013 17:13:28 -0300
Mauro Carvalho Chehab <[email protected]> escreveu:

> Em Thu, 25 Apr 2013 21:10:18 +0200
> Jon Arne Jørgensen <[email protected]> escreveu:
>
> > The somagic device uses the gm7113c chip to digitize analog video,
> > this is a clone of the saa7113 chip.
> >
> > The gm7113c can't be identified over i2c, so I can't rely on
> > saa7115 autodetection.
> >
> > Signed-off-by: Jon Arne Jørgensen <[email protected]>
> > ---
> > drivers/media/i2c/saa7115.c | 61 ++++++++++++++++++++++++++++++++++-------
> > include/media/v4l2-chip-ident.h | 3 ++
> > 2 files changed, 54 insertions(+), 10 deletions(-)
> >
> > diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c
> > index 6b6788c..e93b50a 100644
> > --- a/drivers/media/i2c/saa7115.c
> > +++ b/drivers/media/i2c/saa7115.c
> > @@ -54,7 +54,7 @@
> >
> > MODULE_DESCRIPTION("Philips SAA7111/SAA7113/SAA7114/SAA7115/SAA7118 video decoder driver");
> > MODULE_AUTHOR( "Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, "
> > - "Hans Verkuil, Mauro Carvalho Chehab");
> > + "Hans Verkuil, Mauro Carvalho Chehab, Jon Arne Jørgensen");
>
> Hi Jon,
>
> I was told once by Greg KH that the minimal number of changes to be one
> of the driver's authors is to change a significant amount of the code
> (like 20% or more).
>
> So, I prefer if you don't change it there.
>
> > MODULE_LICENSE("GPL");
> >
> > static bool debug;
> > @@ -126,6 +126,7 @@ static int saa711x_has_reg(const int id, const u8 reg)
> > return 0;
> >
> > switch (id) {
> > + case V4L2_IDENT_GM7113C:
> > case V4L2_IDENT_SAA7113:
> > return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) &&
> > reg != 0x5d && reg < 0x63;
> > @@ -292,7 +293,7 @@ static const unsigned char saa7115_cfg_reset_scaler[] = {
> > 0x00, 0x00
> > };
> >
> > -/* ============== SAA7715 VIDEO templates ============= */
> > +/* ============== SAA7115 VIDEO templates ============= */
> >
> > static const unsigned char saa7115_cfg_60hz_video[] = {
> > R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> > @@ -445,7 +446,27 @@ static const unsigned char saa7115_cfg_50hz_video[] = {
> > 0x00, 0x00
> > };
> >
> > -/* ============== SAA7715 VIDEO templates (end) ======= */
> > +/* ============== SAA7115 VIDEO templates (end) ======= */
> > +
> > +/* ============== GM7113C VIDEO templates ============= */
> > +
> > +static const unsigned char gm7113c_cfg_60hz_video[] = {
> > + R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */
> > + R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */
> > +
> > + R_5A_V_OFF_FOR_SLICER, 0x06, /* standard 60hz value for ITU656 line counting */
> > + 0x00, 0x00
> > +};
> > +
> > +static const unsigned char gm7113c_cfg_50hz_video[] = {
> > + R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */
> > + R_0E_CHROMA_CNTL_1, 0x07,
> > +
> > + R_5A_V_OFF_FOR_SLICER, 0x03, /* standard 50hz value */
> > + 0x00, 0x00
> > +};
> > +
> > +/* ============== GM7113C VIDEO templates (end) ======= */
> >
> > static const unsigned char saa7115_cfg_vbi_on[] = {
> > R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> > @@ -927,11 +948,17 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> > // This works for NTSC-M, SECAM-L and the 50Hz PAL variants.
> > if (std & V4L2_STD_525_60) {
> > v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n");
> > - saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> > + if (state->ident == V4L2_IDENT_GM7113C)
> > + saa711x_writeregs(sd, gm7113c_cfg_60hz_video);
> > + else
> > + saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> > saa711x_set_size(sd, 720, 480);
> > } else {
> > v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n");
> > - saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> > + if (state->ident == V4L2_IDENT_GM7113C)
> > + saa711x_writeregs(sd, gm7113c_cfg_50hz_video);
> > + else
> > + saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> > saa711x_set_size(sd, 720, 576);
> > }
> >
> > @@ -944,7 +971,8 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> > 011 NTSC N (3.58MHz) PAL M (3.58MHz)
> > 100 reserved NTSC-Japan (3.58MHz)
> > */
> > - if (state->ident <= V4L2_IDENT_SAA7113) {
> > + if (state->ident <= V4L2_IDENT_SAA7113 ||
> > + state->ident == V4L2_IDENT_GM7113C) {
> > u8 reg = saa711x_read(sd, R_0E_CHROMA_CNTL_1) & 0x8f;
> >
> > if (std == V4L2_STD_PAL_M) {
> > @@ -1215,7 +1243,8 @@ static int saa711x_s_routing(struct v4l2_subdev *sd,
> > input, output);
> >
> > /* saa7111/3 does not have these inputs */
> > - if (state->ident <= V4L2_IDENT_SAA7113 &&
> > + if ((state->ident <= V4L2_IDENT_SAA7113 ||
> > + state->ident == V4L2_IDENT_GM7113C) &&
> > (input == SAA7115_COMPOSITE4 ||
> > input == SAA7115_COMPOSITE5)) {
> > return -EINVAL;
> > @@ -1586,8 +1615,11 @@ static int i2c_client *client,
> >
> > chip_id = name[5];
> >
> > +
> > /* Check whether this chip is part of the saa711x series */
> > - if (memcmp(name + 1, "f711", 4)) {
> > + if (memcmp(id->name + 1, "gm7113c", 7)) {
> > + chip_id = 'c';
>
> There are several issues on the above:
> 1) "id" may be NULL on autodetect mode;
>
> 2) Why are you adding 1 here?
> It should be, instead id->name
>
> 3) memcmp returns 0 if matches. So, the test is wrong.
> So, It should be instead:
> if (!memcmp(id->name, "gm7113c", 7)) {
>
> 4) Also, while that works, it seems a little hackish...
>
> > + } else if (memcmp(name + 1, "f711", 4)) {
> > v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n",
> > client->addr << 1, name);
> > return -ENODEV;
> > @@ -1598,8 +1630,12 @@ static int saa711x_probe(struct i2c_client *client,
> > v4l_warn(client, "found saa711%c while %s was expected\n",
> > chip_id, id->name);
> > }
> > - snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> > - v4l_info(client, "saa711%c found (%s) @ 0x%x (%s)\n", chip_id, name,
> > + if (chip_id == 'c')
>
> especially by needing to add a weird if here.
> See more below:
>
> > + snprintf(client->name, sizeof(client->name), "%s", id->name);
> > + else
> > + snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> > +
> > + v4l_info(client, "%s found (%s) @ 0x%x (%s)\n", client->name, name,
> > client->addr << 1, client->adapter->name);
> >
> > state = kzalloc(sizeof(struct saa711x_state), GFP_KERNEL);
> > @@ -1645,6 +1681,9 @@ static int saa711x_probe(struct i2c_client *client,
> > state->ident = V4L2_IDENT_SAA7111A;
> > }
> > break;
> > + case 'c':
> > + state->ident = V4L2_IDENT_GM7113C;
> > + break;
>
> The better would be to initialize state->ident earlier, together with memcmp.
>
> Even better: please move the detection code into a separate
> routine that would internally fill state->ident and client->name
> and do what's needed to detect the chip.
>
> That would be cleaner and will reduce a little bit the complexity
> inside saa711x_probe.
>
> Something like:
>
> static int saa711x_detect_chip(struct i2c_client *client,
> struct saa711x_state *state,
> const struct i2c_device_id *id)
> {
> int i;
> char chip_id, name[16];
>
> /*
> * Check for gm7113c (a saa7113 clone). Currently, there's no
> * known way to autodetect it, so boards that use will need to
> * explicitly fill the id->name field.
> */
> if (id && !memcmp(id->name, "gm7113c", 7)) {
> state->ident = V4L2_IDENT_GM7113C;
> snprintf(client->name, sizeof(client->name), "%s", id->name);
> return 0;
> }


Btw, not sure if you also googled for it, but there's a datasheet for this chip
at:
http://www.gotecom.com/up_files/download/64200700.pdf

>From what's there, there are 2 versions of this chip (no, I don't read
chinese, but Google translator also helps with this[1]).

[1] http://translate.google.com.br/translate?sl=zh-CN&tl=en&u=http%3A%2F%2Fwww.gotecom.com%2Fup_files%2Fdownload%2F64200700.pdf

5.2 I2C bus register details
5.2.1 Address 00H

Table 14 chip version (address 00H)
Function Logic value
ID07 ID07 ID07 ID07 ID07 ID07 ID07 ID07
Chip Version V1 0 0 0 1 X X X X
V2 0 0 1 0 X X X X

So, I think you should do something like:

chip_ver = 0;
for (i = 0; i < 4; i++) {
chip_ver = chip_ver << 1;
i2c_smbus_write_byte_data(client, 0, i);
chip_ver |= (i2c_smbus_read_byte_data(client, 0) & 0x80) ? 1 : 0;
}

and fill client-name with the version also, like:

snprintf(client->name, sizeof(client->name), "%s-%d", id->name, chip_ver);

As it may be important latter to handle the different versions, and/or
better handle bug reports related to some specific version of this chip.

> /* Check for Philips/NXP original chips */
> for (i = 0; i < sizeof(name); i++) {
> i2c_smbus_write_byte_data(client, 0, i);
> name[i] = (i2c_smbus_read_byte_data(client, 0) & 0x0f) + '0';
> if (name[i] > '9')
> name[i] += 'a' - '9' - 1;
> }
> name[i] = '\0';
>
> if (memcmp(name + 1, "f711", 4))
> return -ENODEV;
>
> chip_id = name[5];
>
> snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
>
> /*
> * Put here the code that fills state->ident for Philips/NXP chips
> */
> ...
>
> return 0;
> }
>
> > case '3':
> > state->ident = V4L2_IDENT_SAA7113;
> > break;
> > @@ -1675,6 +1714,7 @@ static int saa711x_probe(struct i2c_client *client,
> > saa711x_writeregs(sd, saa7111_init);
> > break;
> > case V4L2_IDENT_SAA7113:
> > + case V4L2_IDENT_GM7113C:
> > saa711x_writeregs(sd, saa7113_init);
> > break;
> > default:
> > @@ -1711,6 +1751,7 @@ static const struct i2c_device_id saa711x_id[] = {
> > { "saa7114", 0 },
> > { "saa7115", 0 },
> > { "saa7118", 0 },
> > + { "gm7113c", 0 },
> > { }
> > };
> > MODULE_DEVICE_TABLE(i2c, saa711x_id);
> > diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h
> > index 4ee125b..fc13d53 100644
> > --- a/include/media/v4l2-chip-ident.h
> > +++ b/include/media/v4l2-chip-ident.h
> > @@ -51,6 +51,9 @@ enum {
> > V4L2_IDENT_SAA7114 = 104,
> > V4L2_IDENT_SAA7115 = 105,
> > V4L2_IDENT_SAA7118 = 108,
> > + /* This chip is a chinese clone of the saa7113 chip,
> > + * with some minor changes/bugs */
> > + V4L2_IDENT_GM7113C = 149,
> >
> > /* module saa7127: reserved range 150-199 */
> > V4L2_IDENT_SAA7127 = 157,
>
>


--

Cheers,
Mauro

2013-04-25 20:40:18

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [RFC V2 1/3] [smi2021] Add gm7113c chip to the saa7115 driver

Em Thu, 25 Apr 2013 22:33:20 +0200
Jon Arne Jørgensen <[email protected]> escreveu:

> On Thu, Apr 25, 2013 at 05:13:28PM -0300, Mauro Carvalho Chehab wrote:
> > Em Thu, 25 Apr 2013 21:10:18 +0200
> > Jon Arne Jørgensen <[email protected]> escreveu:
> >
> > > /* Check whether this chip is part of the saa711x series */
> > > - if (memcmp(name + 1, "f711", 4)) {
> > > + if (memcmp(id->name + 1, "gm7113c", 7)) {
> > > + chip_id = 'c';
> >
> > There are several issues on the above:
> > 1) "id" may be NULL on autodetect mode;
> >
> > 2) Why are you adding 1 here?
> > It should be, instead id->name
> >
> > 3) memcmp returns 0 if matches. So, the test is wrong.
> > So, It should be instead:
> > if (!memcmp(id->name, "gm7113c", 7)) {
> >
> > 4) Also, while that works, it seems a little hackish...
> >
>
> Oh, this is embarrassing.
> I just tried to change as little as possible in this module to make the
> device work.

No problems. On my experience, quick hacks like that are the
ones that generally have more troubles ;)

> > Something like:
> >
> > static int saa711x_detect_chip(struct i2c_client *client,
> > struct saa711x_state *state,
> > const struct i2c_device_id *id)
> > {
> > int i;
> > char chip_id, name[16];
> >
> > /*
> > * Check for gm7113c (a saa7113 clone). Currently, there's no
> > * known way to autodetect it, so boards that use will need to
> > * explicitly fill the id->name field.
> > */
> > if (id && !memcmp(id->name, "gm7113c", 7)) {
> > state->ident = V4L2_IDENT_GM7113C;
> > snprintf(client->name, sizeof(client->name), "%s", id->name);
> > return 0;
> > }
> >
> > /* Check for Philips/NXP original chips */
> > for (i = 0; i < sizeof(name); i++) {
> > i2c_smbus_write_byte_data(client, 0, i);
> > name[i] = (i2c_smbus_read_byte_data(client, 0) & 0x0f) + '0';
> > if (name[i] > '9')
> > name[i] += 'a' - '9' - 1;
> > }
> > name[i] = '\0';
> >
> > if (memcmp(name + 1, "f711", 4))
> > return -ENODEV;
> >
> > chip_id = name[5];
> >
> > snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> >
> > /*
> > * Put here the code that fills state->ident for Philips/NXP chips
> > */
> > ...
> >
> > return 0;
>
> Yes this seems to be a much better way to do it.
> I will fix my code.

Thanks!
Mauro

2013-04-25 21:02:19

by Jon Arne Jørgensen

[permalink] [raw]
Subject: Re: [RFC V2 1/3] [smi2021] Add gm7113c chip to the saa7115 driver

On Thu, Apr 25, 2013 at 05:36:29PM -0300, Mauro Carvalho Chehab wrote:
> Em Thu, 25 Apr 2013 17:13:28 -0300
> Mauro Carvalho Chehab <[email protected]> escreveu:
>
> > Em Thu, 25 Apr 2013 21:10:18 +0200
> > Jon Arne Jørgensen <[email protected]> escreveu:
> >
> > > The somagic device uses the gm7113c chip to digitize analog video,
> > > this is a clone of the saa7113 chip.
> > >
> > > The gm7113c can't be identified over i2c, so I can't rely on
> > > saa7115 autodetection.
> > >
> > > Signed-off-by: Jon Arne Jørgensen <[email protected]>
> > > ---
> > > drivers/media/i2c/saa7115.c | 61 ++++++++++++++++++++++++++++++++++-------
> > > include/media/v4l2-chip-ident.h | 3 ++
> > > 2 files changed, 54 insertions(+), 10 deletions(-)
> > >
> > > diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c
> > > index 6b6788c..e93b50a 100644
> > > --- a/drivers/media/i2c/saa7115.c
> > > +++ b/drivers/media/i2c/saa7115.c
> > > @@ -54,7 +54,7 @@
> > >
> > > MODULE_DESCRIPTION("Philips SAA7111/SAA7113/SAA7114/SAA7115/SAA7118 video decoder driver");
> > > MODULE_AUTHOR( "Maxim Yevtyushkin, Kevin Thayer, Chris Kennedy, "
> > > - "Hans Verkuil, Mauro Carvalho Chehab");
> > > + "Hans Verkuil, Mauro Carvalho Chehab, Jon Arne Jørgensen");
> >
> > Hi Jon,
> >
> > I was told once by Greg KH that the minimal number of changes to be one
> > of the driver's authors is to change a significant amount of the code
> > (like 20% or more).
> >
> > So, I prefer if you don't change it there.
> >
> > > MODULE_LICENSE("GPL");
> > >
> > > static bool debug;
> > > @@ -126,6 +126,7 @@ static int saa711x_has_reg(const int id, const u8 reg)
> > > return 0;
> > >
> > > switch (id) {
> > > + case V4L2_IDENT_GM7113C:
> > > case V4L2_IDENT_SAA7113:
> > > return reg != 0x14 && (reg < 0x18 || reg > 0x1e) && (reg < 0x20 || reg > 0x3f) &&
> > > reg != 0x5d && reg < 0x63;
> > > @@ -292,7 +293,7 @@ static const unsigned char saa7115_cfg_reset_scaler[] = {
> > > 0x00, 0x00
> > > };
> > >
> > > -/* ============== SAA7715 VIDEO templates ============= */
> > > +/* ============== SAA7115 VIDEO templates ============= */
> > >
> > > static const unsigned char saa7115_cfg_60hz_video[] = {
> > > R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> > > @@ -445,7 +446,27 @@ static const unsigned char saa7115_cfg_50hz_video[] = {
> > > 0x00, 0x00
> > > };
> > >
> > > -/* ============== SAA7715 VIDEO templates (end) ======= */
> > > +/* ============== SAA7115 VIDEO templates (end) ======= */
> > > +
> > > +/* ============== GM7113C VIDEO templates ============= */
> > > +
> > > +static const unsigned char gm7113c_cfg_60hz_video[] = {
> > > + R_08_SYNC_CNTL, 0x68, /* 0xBO: auto detection, 0x68 = NTSC */
> > > + R_0E_CHROMA_CNTL_1, 0x07, /* video autodetection is on */
> > > +
> > > + R_5A_V_OFF_FOR_SLICER, 0x06, /* standard 60hz value for ITU656 line counting */
> > > + 0x00, 0x00
> > > +};
> > > +
> > > +static const unsigned char gm7113c_cfg_50hz_video[] = {
> > > + R_08_SYNC_CNTL, 0x28, /* 0x28 = PAL */
> > > + R_0E_CHROMA_CNTL_1, 0x07,
> > > +
> > > + R_5A_V_OFF_FOR_SLICER, 0x03, /* standard 50hz value */
> > > + 0x00, 0x00
> > > +};
> > > +
> > > +/* ============== GM7113C VIDEO templates (end) ======= */
> > >
> > > static const unsigned char saa7115_cfg_vbi_on[] = {
> > > R_80_GLOBAL_CNTL_1, 0x00, /* reset tasks */
> > > @@ -927,11 +948,17 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> > > // This works for NTSC-M, SECAM-L and the 50Hz PAL variants.
> > > if (std & V4L2_STD_525_60) {
> > > v4l2_dbg(1, debug, sd, "decoder set standard 60 Hz\n");
> > > - saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> > > + if (state->ident == V4L2_IDENT_GM7113C)
> > > + saa711x_writeregs(sd, gm7113c_cfg_60hz_video);
> > > + else
> > > + saa711x_writeregs(sd, saa7115_cfg_60hz_video);
> > > saa711x_set_size(sd, 720, 480);
> > > } else {
> > > v4l2_dbg(1, debug, sd, "decoder set standard 50 Hz\n");
> > > - saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> > > + if (state->ident == V4L2_IDENT_GM7113C)
> > > + saa711x_writeregs(sd, gm7113c_cfg_50hz_video);
> > > + else
> > > + saa711x_writeregs(sd, saa7115_cfg_50hz_video);
> > > saa711x_set_size(sd, 720, 576);
> > > }
> > >
> > > @@ -944,7 +971,8 @@ static void saa711x_set_v4lstd(struct v4l2_subdev *sd, v4l2_std_id std)
> > > 011 NTSC N (3.58MHz) PAL M (3.58MHz)
> > > 100 reserved NTSC-Japan (3.58MHz)
> > > */
> > > - if (state->ident <= V4L2_IDENT_SAA7113) {
> > > + if (state->ident <= V4L2_IDENT_SAA7113 ||
> > > + state->ident == V4L2_IDENT_GM7113C) {
> > > u8 reg = saa711x_read(sd, R_0E_CHROMA_CNTL_1) & 0x8f;
> > >
> > > if (std == V4L2_STD_PAL_M) {
> > > @@ -1215,7 +1243,8 @@ static int saa711x_s_routing(struct v4l2_subdev *sd,
> > > input, output);
> > >
> > > /* saa7111/3 does not have these inputs */
> > > - if (state->ident <= V4L2_IDENT_SAA7113 &&
> > > + if ((state->ident <= V4L2_IDENT_SAA7113 ||
> > > + state->ident == V4L2_IDENT_GM7113C) &&
> > > (input == SAA7115_COMPOSITE4 ||
> > > input == SAA7115_COMPOSITE5)) {
> > > return -EINVAL;
> > > @@ -1586,8 +1615,11 @@ static int i2c_client *client,
> > >
> > > chip_id = name[5];
> > >
> > > +
> > > /* Check whether this chip is part of the saa711x series */
> > > - if (memcmp(name + 1, "f711", 4)) {
> > > + if (memcmp(id->name + 1, "gm7113c", 7)) {
> > > + chip_id = 'c';
> >
> > There are several issues on the above:
> > 1) "id" may be NULL on autodetect mode;
> >
> > 2) Why are you adding 1 here?
> > It should be, instead id->name
> >
> > 3) memcmp returns 0 if matches. So, the test is wrong.
> > So, It should be instead:
> > if (!memcmp(id->name, "gm7113c", 7)) {
> >
> > 4) Also, while that works, it seems a little hackish...
> >
> > > + } else if (memcmp(name + 1, "f711", 4)) {
> > > v4l_dbg(1, debug, client, "chip found @ 0x%x (ID %s) does not match a known saa711x chip.\n",
> > > client->addr << 1, name);
> > > return -ENODEV;
> > > @@ -1598,8 +1630,12 @@ static int saa711x_probe(struct i2c_client *client,
> > > v4l_warn(client, "found saa711%c while %s was expected\n",
> > > chip_id, id->name);
> > > }
> > > - snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> > > - v4l_info(client, "saa711%c found (%s) @ 0x%x (%s)\n", chip_id, name,
> > > + if (chip_id == 'c')
> >
> > especially by needing to add a weird if here.
> > See more below:
> >
> > > + snprintf(client->name, sizeof(client->name), "%s", id->name);
> > > + else
> > > + snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> > > +
> > > + v4l_info(client, "%s found (%s) @ 0x%x (%s)\n", client->name, name,
> > > client->addr << 1, client->adapter->name);
> > >
> > > state = kzalloc(sizeof(struct saa711x_state), GFP_KERNEL);
> > > @@ -1645,6 +1681,9 @@ static int saa711x_probe(struct i2c_client *client,
> > > state->ident = V4L2_IDENT_SAA7111A;
> > > }
> > > break;
> > > + case 'c':
> > > + state->ident = V4L2_IDENT_GM7113C;
> > > + break;
> >
> > The better would be to initialize state->ident earlier, together with memcmp.
> >
> > Even better: please move the detection code into a separate
> > routine that would internally fill state->ident and client->name
> > and do what's needed to detect the chip.
> >
> > That would be cleaner and will reduce a little bit the complexity
> > inside saa711x_probe.
> >
> > Something like:
> >
> > static int saa711x_detect_chip(struct i2c_client *client,
> > struct saa711x_state *state,
> > const struct i2c_device_id *id)
> > {
> > int i;
> > char chip_id, name[16];
> >
> > /*
> > * Check for gm7113c (a saa7113 clone). Currently, there's no
> > * known way to autodetect it, so boards that use will need to
> > * explicitly fill the id->name field.
> > */
> > if (id && !memcmp(id->name, "gm7113c", 7)) {
> > state->ident = V4L2_IDENT_GM7113C;
> > snprintf(client->name, sizeof(client->name), "%s", id->name);
> > return 0;
> > }
>
>
> Btw, not sure if you also googled for it, but there's a datasheet for this chip
> at:
> http://www.gotecom.com/up_files/download/64200700.pdf
>
> >From what's there, there are 2 versions of this chip (no, I don't read
> chinese, but Google translator also helps with this[1]).
>
> [1] http://translate.google.com.br/translate?sl=zh-CN&tl=en&u=http%3A%2F%2Fwww.gotecom.com%2Fup_files%2Fdownload%2F64200700.pdf
>

I've googled, but gave up when I got chinese. Didn't know google
translate could handle it.

> 5.2 I2C bus register details
> 5.2.1 Address 00H
>
> Table 14 chip version (address 00H)
> Function Logic value
> ID07 ID07 ID07 ID07 ID07 ID07 ID07 ID07
> Chip Version V1 0 0 0 1 X X X X
> V2 0 0 1 0 X X X X
>
> So, I think you should do something like:
>
> chip_ver = 0;
> for (i = 0; i < 4; i++) {
> chip_ver = chip_ver << 1;
> i2c_smbus_write_byte_data(client, 0, i);
> chip_ver |= (i2c_smbus_read_byte_data(client, 0) & 0x80) ? 1 : 0;
> }
>
> and fill client-name with the version also, like:
>
> snprintf(client->name, sizeof(client->name), "%s-%d", id->name, chip_ver);
>
> As it may be important latter to handle the different versions, and/or
> better handle bug reports related to some specific version of this chip.
>

This is great, I will implement these checks for the device.

> > /* Check for Philips/NXP original chips */
> > for (i = 0; i < sizeof(name); i++) {
> > i2c_smbus_write_byte_data(client, 0, i);
> > name[i] = (i2c_smbus_read_byte_data(client, 0) & 0x0f) + '0';
> > if (name[i] > '9')
> > name[i] += 'a' - '9' - 1;
> > }
> > name[i] = '\0';
> >
> > if (memcmp(name + 1, "f711", 4))
> > return -ENODEV;
> >
> > chip_id = name[5];
> >
> > snprintf(client->name, sizeof(client->name), "saa711%c", chip_id);
> >
> > /*
> > * Put here the code that fills state->ident for Philips/NXP chips
> > */
> > ...
> >
> > return 0;
> > }
> >
> > > case '3':
> > > state->ident = V4L2_IDENT_SAA7113;
> > > break;
> > > @@ -1675,6 +1714,7 @@ static int saa711x_probe(struct i2c_client *client,
> > > saa711x_writeregs(sd, saa7111_init);
> > > break;
> > > case V4L2_IDENT_SAA7113:
> > > + case V4L2_IDENT_GM7113C:
> > > saa711x_writeregs(sd, saa7113_init);
> > > break;
> > > default:
> > > @@ -1711,6 +1751,7 @@ static const struct i2c_device_id saa711x_id[] = {
> > > { "saa7114", 0 },
> > > { "saa7115", 0 },
> > > { "saa7118", 0 },
> > > + { "gm7113c", 0 },
> > > { }
> > > };
> > > MODULE_DEVICE_TABLE(i2c, saa711x_id);
> > > diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h
> > > index 4ee125b..fc13d53 100644
> > > --- a/include/media/v4l2-chip-ident.h
> > > +++ b/include/media/v4l2-chip-ident.h
> > > @@ -51,6 +51,9 @@ enum {
> > > V4L2_IDENT_SAA7114 = 104,
> > > V4L2_IDENT_SAA7115 = 105,
> > > V4L2_IDENT_SAA7118 = 108,
> > > + /* This chip is a chinese clone of the saa7113 chip,
> > > + * with some minor changes/bugs */
> > > + V4L2_IDENT_GM7113C = 149,
> > >
> > > /* module saa7127: reserved range 150-199 */
> > > V4L2_IDENT_SAA7127 = 157,
> >
> >
>
>
> --
>
> Cheers,
> Mauro
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html

2013-06-13 12:20:30

by rafael.coutinho

[permalink] [raw]
Subject: Re: [RFC V2 1/3] [smi2021] Add gm7113c chip to the saa7115 driver

Hi Jon,

i'm having troubles to support an EMPia capture board with GM7113c chip and
I can't find any linux driver for that. Have you successfully made it work?
Is your driver code open?

Thanks in advance.



--
View this message in context: http://linux-kernel.2935.n7.nabble.com/RFC-V2-0-3-Add-a-driver-for-Somagic-smi2021-tp640313p666480.html
Sent from the Linux Kernel mailing list archive at Nabble.com.