[PATCH 34/38] media: imx: Add support for ADV7180 Video Decoder

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This driver is based on adv7180.c from Freescale imx_3.10.17_1.0.0_beta
branch, modified heavily for code cleanup and converted from int-device
to subdev.

Signed-off-by: Steve Longerbeam <steve_longerbeam@xxxxxxxxxx>
---
 drivers/staging/media/imx/capture/Kconfig   |    7 +
 drivers/staging/media/imx/capture/Makefile  |    1 +
 drivers/staging/media/imx/capture/adv7180.c | 1533 +++++++++++++++++++++++++++
 include/uapi/linux/v4l2-controls.h          |    4 +
 include/uapi/media/Kbuild                   |    1 +
 include/uapi/media/adv718x.h                |   42 +
 6 files changed, 1588 insertions(+)
 create mode 100644 drivers/staging/media/imx/capture/adv7180.c
 create mode 100644 include/uapi/media/adv718x.h

diff --git a/drivers/staging/media/imx/capture/Kconfig b/drivers/staging/media/imx/capture/Kconfig
index 42d87db..6897192 100644
--- a/drivers/staging/media/imx/capture/Kconfig
+++ b/drivers/staging/media/imx/capture/Kconfig
@@ -32,4 +32,11 @@ config IMX_CAMERA_OV5640_MIPI
        ---help---
          MIPI CSI-2 OV5640 Camera support.
 
+config IMX_CAMERA_ADV7180
+       tristate "Analog Devices ADV7180 Video Decoder support"
+       depends on VIDEO_IMX_CAMERA
+       default y
+       ---help---
+         Analog Devices ADV7180 Video Decoder support.
+
 endmenu
diff --git a/drivers/staging/media/imx/capture/Makefile b/drivers/staging/media/imx/capture/Makefile
index 07633be..c8f891a 100644
--- a/drivers/staging/media/imx/capture/Makefile
+++ b/drivers/staging/media/imx/capture/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_IMX_MIPI_CSI2) += mipi-csi2.o
 obj-$(CONFIG_IMX_VIDEO_SWITCH) += imx-video-switch.o
 obj-$(CONFIG_IMX_CAMERA_OV5640_MIPI) += ov5640-mipi.o
 obj-$(CONFIG_IMX_CAMERA_OV5642) += ov5642.o
+obj-$(CONFIG_IMX_CAMERA_ADV7180) += adv7180.o
diff --git a/drivers/staging/media/imx/capture/adv7180.c b/drivers/staging/media/imx/capture/adv7180.c
new file mode 100644
index 0000000..297c543
--- /dev/null
+++ b/drivers/staging/media/imx/capture/adv7180.c
@@ -0,0 +1,1533 @@
+/*
+ * Analog Device ADV7180 video decoder driver
+ *
+ * Copyright (c) 2012-2014 Mentor Graphics Inc.
+ * Copyright 2005-2012 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/semaphore.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/wait.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-of.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/adv718x.h>
+
+struct adv7180_dev {
+	struct i2c_client *i2c_client;
+	struct device *dev;
+	struct v4l2_subdev sd;
+	struct v4l2_of_endpoint ep; /* the parsed DT endpoint info */
+	struct v4l2_ctrl_handler ctrl_hdl;
+	struct v4l2_mbus_framefmt fmt;
+	struct v4l2_captureparm streamcap;
+	int rev_id;
+	bool on;
+
+	bool locked;             /* locked to signal */
+
+	struct regulator *dvddio;
+	struct regulator *dvdd;
+	struct regulator *avdd;
+	struct regulator *pvdd;
+	int pwdn_gpio;
+
+	v4l2_std_id std_id;
+
+	/* Standard index of ADV7180. */
+	int video_idx;
+
+	/* Current analog input mux */
+	int current_input;
+
+	struct mutex mutex;
+};
+
+static inline struct adv7180_dev *to_adv7180_dev(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct adv7180_dev, sd);
+}
+
+static inline struct adv7180_dev *ctrl_to_adv7180_dev(struct v4l2_ctrl *ctrl)
+{
+	return container_of(ctrl->handler, struct adv7180_dev, ctrl_hdl);
+}
+
+/*! List of input video formats supported. The video formats is corresponding
+ * with v4l2 id in video_fmt_t
+ */
+enum {
+	ADV7180_NTSC = 0,	/*!< Locked on (M) NTSC video signal. */
+	ADV7180_PAL,		/*!< (B, G, H, I, N)PAL video signal. */
+};
+
+/*! Number of video standards supported (including 'not locked' signal). */
+#define ADV7180_STD_MAX		(ADV7180_PAL + 1)
+
+/* video standard info */
+struct adv7180_std_info {
+	unsigned long width;
+	unsigned long height;
+	struct v4l2_standard std;
+};
+
+static struct adv7180_std_info adv7180_std[] = {
+	[ADV7180_NTSC] = {
+		.width = 720,
+		.height = 480,
+		.std = {
+			.id = V4L2_STD_NTSC,
+			.name = "NTSC",
+			.framelines = 525,
+			.frameperiod = {
+				.numerator = 1001,
+				.denominator = 30000,
+			},
+		},
+	},
+	[ADV7180_PAL] = {
+		.width = 720,
+		.height = 576,
+		.std = {
+			.id = V4L2_STD_PAL,
+			.name = "PAL",
+			.framelines = 625,
+			.frameperiod = {
+				.numerator = 1,
+				.denominator = 25,
+			},
+		},
+	},
+};
+
+#define IF_NAME                    "adv7180"
+
+/* Main Register Map */
+#define ADV7180_INPUT_CTL              0x00	/* Input Control */
+#define ADV7180_EXT_OUT_CTL            0x04
+#define ADV7180_STATUS_1               0x10	/* Status #1 */
+#define   ADV7180_IN_LOCK              (1 << 0)
+#define   ADV7180_LOST_LOCK            (1 << 1)
+#define   ADV7180_FSC_LOCK             (1 << 2)
+#define   ADV7180_AD_RESULT_BIT        4
+#define   ADV7180_AD_RESULT_MASK       (0x7 << ADV7180_AD_RESULT_BIT)
+#define   ADV7180_AD_NTSC              0
+#define   ADV7180_AD_NTSC_4_43         1
+#define   ADV7180_AD_PAL_M             2
+#define   ADV7180_AD_PAL_60            3
+#define   ADV7180_AD_PAL               4
+#define   ADV7180_AD_SECAM             5
+#define   ADV7180_AD_PAL_N             6
+#define   ADV7180_AD_SECAM_525         7
+#define ADV7180_CONTRAST               0x08	/* Contrast */
+#define ADV7180_BRIGHTNESS             0x0a	/* Brightness */
+#define ADV7180_HUE_REG                0x0b	/* Signed, inverted */
+#define ADV7180_DEF_Y                  0x0c
+#define   ADV7180_DEF_VAL_EN           (1 << 0)
+#define   ADV7180_DEF_VAL_AUTO_EN      (1 << 1)
+#define   ADV7180_DEF_Y_BIT            2
+#define   ADV7180_DEF_Y_MASK           (0x3f << ADV7180_DEF_Y_BIT)
+#define ADV7180_DEF_C                  0x0d
+#define   ADV7180_DEF_CB_BIT           0
+#define   ADV7180_DEF_CB_MASK          (0xf << ADV7180_DEF_CB_BIT)
+#define   ADV7180_DEF_CR_BIT           4
+#define   ADV7180_DEF_CR_MASK          (0xf << ADV7180_DEF_CR_BIT)
+#define ADV7180_ADI_CTL_1              0x0e
+#define ADV7180_PWR_MNG                0x0f     /* Power Management */
+#define ADV7180_IDENT                  0x11	/* IDENT */
+#define ADV7180_ANALOG_CLAMP_CTL       0x14
+#define ADV7180_SHAP_FILTER_CTL_1      0x17
+#define ADV7180_VSYNC_FIELD_CTL_1      0x31	/* VSYNC Field Control #1 */
+#define ADV7180_MANUAL_WIN_CTL_1       0x3d	/* Manual Window Control 1 */
+#define ADV7180_MANUAL_WIN_CTL_2       0x3e     /* Manual Window Control 2 */
+#define ADV7180_MANUAL_WIN_CTL_3       0x3f     /* Manual Window Control 3 */
+#define ADV7180_CTI_DNR                0x4d
+#define   ADV7180_CTI_EN               (1 << 0) /* CTI enable */
+#define   ADV7180_CTI_AB_EN            (1 << 1) /* CTI Alpha Blend enable */
+#define   ADV7180_CTI_AB_BIT           2 /* CTI Alpha Blend sharpness */
+#define   ADV7180_CTI_AB_MASK          (0x3 << ADV7180_CTI_AB_BIT)
+#define   ADV7180_DNR_EN               (1 << 5) /* DNR enable */
+#define ADV7180_CTI_THRESH             0x4e /* CTI Chroma Amplitude Threshold */
+#define ADV7180_DNR_THRESH1            0x50
+#define ADV7180_LOCK_COUNT             0x51
+#define ADV7180_CVBS_TRIM              0x52
+#define ADV7180_SD_OFFSET_CB           0xe1	/* SD Offset Cb */
+#define ADV7180_SD_OFFSET_CR           0xe2	/* SD Offset Cr */
+#define ADV7180_SD_SATURATION_CB       0xe3	/* SD Saturation Cb */
+#define ADV7180_SD_SATURATION_CR       0xe4	/* SD Saturation Cr */
+#define ADV7180_DRIVE_STRENGTH         0xf4
+#define ADV7180_LUMA_PEAK_GAIN         0xfb
+#define ADV7180_DNR_THRESH2            0xfc
+/* Interrupt Register Map */
+#define ADV7180_INT_CONFIG_1           0x40     /* Interrupt Config 1 */
+#define ADV7180_INT_STATUS_1           0x42     /* Interrupt Status 1 (r/o) */
+#define   ADV7180_INT_SD_LOCK          (1 << 0)
+#define   ADV7180_INT_SD_UNLOCK        (1 << 1)
+#define ADV7180_INT_CLEAR_1            0x43     /* Interrupt Clear 1 (w/o) */
+#define ADV7180_INT_MASK_1             0x44     /* Interrupt Mask 1 */
+#define ADV7180_INT_RAW_STATUS_2       0x45   /* Interrupt Raw Status 2 (r/o) */
+#define  ADV7180_INT_SD_FIELD          (1 << 4)
+#define ADV7180_INT_STATUS_2           0x46     /* Interrupt Status 2 (r/o) */
+#define  ADV7180_INT_SD_FIELD_CHNG     (1 << 4)
+#define ADV7180_INT_CLEAR_2            0x47     /* Interrupt Clear 2 (w/o) */
+#define ADV7180_INT_MASK_2             0x48     /* Interrupt Mask 2 */
+#define ADV7180_INT_RAW_STATUS_3       0x49   /* Interrupt Raw Status 3 (r/o) */
+#define   ADV7180_INT_SD_V_LOCK        (1 << 1)
+#define ADV7180_INT_STATUS_3           0x4a   /* Interrupt Status 3 (r/o) */
+#define   ADV7180_INT_SD_V_LOCK_CHNG   (1 << 1)
+#define   ADV7180_INT_SD_H_LOCK_CHNG   (1 << 2)
+#define   ADV7180_INT_SD_AD_CHNG       (1 << 3)
+#define ADV7180_INT_CLEAR_3            0x4b     /* Interrupt Clear 3 (w/o) */
+#define ADV7180_INT_MASK_3             0x4c     /* Interrupt Mask 3 */
+
+struct adv7180_inputs_t {
+	const char *desc;   /* Analog input description */
+	u8 insel;           /* insel bits to select this input */
+};
+
+/* Analog Inputs on 64-Lead and 48-Lead LQFP */
+static const struct adv7180_inputs_t adv7180_inputs_64_48[] = {
+	{ .insel = 0x00, .desc = "ADV7180 Composite on Ain1" },
+	{ .insel = 0x01, .desc = "ADV7180 Composite on Ain2" },
+	{ .insel = 0x02, .desc = "ADV7180 Composite on Ain3" },
+	{ .insel = 0x03, .desc = "ADV7180 Composite on Ain4" },
+	{ .insel = 0x04, .desc = "ADV7180 Composite on Ain5" },
+	{ .insel = 0x05, .desc = "ADV7180 Composite on Ain6" },
+	{ .insel = 0x06, .desc = "ADV7180 Y/C on Ain1/4" },
+	{ .insel = 0x07, .desc = "ADV7180 Y/C on Ain2/5" },
+	{ .insel = 0x08, .desc = "ADV7180 Y/C on Ain3/6" },
+	{ .insel = 0x09, .desc = "ADV7180 YPbPr on Ain1/4/5" },
+	{ .insel = 0x0a, .desc = "ADV7180 YPbPr on Ain2/3/6" },
+};
+
+#define NUM_INPUTS_64_48 ARRAY_SIZE(adv7180_inputs_64_48)
+
+#if 0
+/*
+ * FIXME: there is no way to distinguish LQFP vs LFCSP chips, so
+ * we will just have to assume LQFP.
+ */
+/* Analog Inputs on 40-Lead and 32-Lead LFCSP */
+static const struct adv7180_inputs_t adv7180_inputs_40_32[] = {
+	{ .insel = 0x00, .desc = "ADV7180 Composite on Ain1" },
+	{ .insel = 0x03, .desc = "ADV7180 Composite on Ain2" },
+	{ .insel = 0x04, .desc = "ADV7180 Composite on Ain3" },
+	{ .insel = 0x06, .desc = "ADV7180 Y/C on Ain1/2" },
+	{ .insel = 0x09, .desc = "ADV7180 YPbPr on Ain1/2/3" },
+};
+
+#define NUM_INPUTS_40_32 ARRAY_SIZE(adv7180_inputs_40_32)
+#endif
+
+struct adv7180_reg_tbl_t {
+	u8 reg;
+	u8 val;
+};
+
+/*
+ * this is a special reg value inserted into a register table
+ * indicating a delay is required in msec given in val
+ */
+#define ADV_DELAY  255
+
+/*
+ * This is the original register set from Freescale's adv7180
+ * driver. Most of these registers and their values are undocumented,
+ * and do not conform with Analog Device's Register Settings
+ * Recommendations document for CVBS autodetect mode.
+ * Later edit: This table has been stripped of the entries specifying
+ * the same value as the register default value after reset (according
+ * to ADV7180 datasheet rev J), in order to decrease the device
+ * initialization time.
+ */
+static const struct adv7180_reg_tbl_t adv7180_fsl_init_reg[] = {
+	{ 0x02, 0x04 }, { 0x03, 0x00 }, { 0x04, 0x45 }, { 0x05, 0x00 },
+	{ 0x06, 0x02 }, { 0x13, 0x00 }, { 0x15, 0x00 }, { 0x16, 0x00 },
+	{ 0xF1, 0x19 }, { 0x1A, 0x00 }, { 0x1B, 0x00 }, { 0x1C, 0x00 },
+	{ 0x1D, 0x40 }, { 0x1E, 0x00 }, { 0x1F, 0x00 }, { 0x20, 0x00 },
+	{ 0x21, 0x00 }, { 0x22, 0x00 }, { 0x23, 0xC0 }, { 0x24, 0x00 },
+	{ 0x25, 0x00 }, { 0x26, 0x00 }, { 0x28, 0x00 }, { 0x29, 0x00 },
+	{ 0x2A, 0x00 }, { 0x2D, 0xF4 }, { 0x2E, 0x00 }, { 0x2F, 0xF0 },
+	{ 0x30, 0x00 }, { 0x31, 0x02 }, { 0x3B, 0x05 }, { 0x3C, 0x58 },
+	{ 0x3E, 0x64 }, { 0x3F, 0xE4 }, { 0x40, 0x90 }, { 0x42, 0x7E },
+	{ 0x43, 0xA4 }, { 0x44, 0xFF }, { 0x45, 0xB6 }, { 0x46, 0x12 },
+	{ 0x4F, 0x08 }, { 0x51, 0xA4 }, { 0x53, 0x4E }, { 0x54, 0x80 },
+	{ 0x55, 0x00 }, { 0x56, 0x10 }, { 0x57, 0x00 }, { 0x5A, 0x00 },
+	{ 0x5B, 0x00 }, { 0x5C, 0x00 }, { 0x5D, 0x00 }, { 0x5E, 0x00 },
+	{ 0x5F, 0x00 }, { 0x60, 0x00 }, { 0x61, 0x00 }, { 0x62, 0x20 },
+	{ 0x63, 0x00 }, { 0x64, 0x00 }, { 0x65, 0x00 }, { 0x66, 0x00 },
+	{ 0x67, 0x03 }, { 0x68, 0x01 }, { 0x69, 0x00 }, { 0x6A, 0x00 },
+	{ 0x6B, 0xC0 }, { 0x6C, 0x00 }, { 0x6D, 0x00 }, { 0x6E, 0x00 },
+	{ 0x6F, 0x00 }, { 0x70, 0x00 }, { 0x71, 0x00 }, { 0x72, 0x00 },
+	{ 0x73, 0x10 }, { 0x74, 0x04 }, { 0x75, 0x01 }, { 0x76, 0x00 },
+	{ 0x77, 0x3F }, { 0x78, 0xFF }, { 0x79, 0xFF }, { 0x7A, 0xFF },
+	{ 0x7B, 0x1E }, { 0x7C, 0xC0 }, { 0x7D, 0x00 }, { 0x7E, 0x00 },
+	{ 0x7F, 0x00 }, { 0x80, 0x00 }, { 0x81, 0xC0 }, { 0x82, 0x04 },
+	{ 0x83, 0x00 }, { 0x84, 0x0C }, { 0x85, 0x02 }, { 0x86, 0x03 },
+	{ 0x87, 0x63 }, { 0x88, 0x5A }, { 0x89, 0x08 }, { 0x8A, 0x10 },
+	{ 0x8B, 0x00 }, { 0x8C, 0x40 }, { 0x8D, 0x00 }, { 0x8E, 0x40 },
+	{ 0x90, 0x00 }, { 0x91, 0x50 }, { 0x92, 0x00 }, { 0x93, 0x00 },
+	{ 0x94, 0x00 }, { 0x95, 0x00 }, { 0x96, 0x00 }, { 0x97, 0xF0 },
+	{ 0x98, 0x00 }, { 0x99, 0x00 }, { 0x9A, 0x00 }, { 0x9B, 0x00 },
+	{ 0x9C, 0x00 }, { 0x9D, 0x00 }, { 0x9E, 0x00 }, { 0x9F, 0x00 },
+	{ 0xA0, 0x00 }, { 0xA1, 0x00 }, { 0xA2, 0x00 }, { 0xA3, 0x00 },
+	{ 0xA4, 0x00 }, { 0xA5, 0x00 }, { 0xA6, 0x00 }, { 0xA7, 0x00 },
+	{ 0xA8, 0x00 }, { 0xA9, 0x00 }, { 0xAA, 0x00 }, { 0xAB, 0x00 },
+	{ 0xAC, 0x00 }, { 0xAD, 0x00 }, { 0xAE, 0x60 }, { 0xAF, 0x00 },
+	{ 0xB0, 0x00 }, { 0xB1, 0x60 }, { 0xB3, 0x54 }, { 0xB4, 0x00 },
+	{ 0xB5, 0x00 }, { 0xB6, 0x00 }, { 0xB7, 0x13 }, { 0xB8, 0x03 },
+	{ 0xB9, 0x33 }, { 0xBF, 0x02 }, { 0xC0, 0x00 }, { 0xC1, 0x00 },
+	{ 0xC2, 0x00 }, { 0xC4, 0x00 }, { 0xC5, 0x81 }, { 0xC6, 0x00 },
+	{ 0xC7, 0x00 }, { 0xC8, 0x00 }, { 0xC9, 0x04 }, { 0xCC, 0x69 },
+	{ 0xCD, 0x00 }, { 0xCE, 0x01 }, { 0xCF, 0xB4 }, { 0xD0, 0x00 },
+	{ 0xD1, 0x10 }, { 0xD2, 0xFF }, { 0xD3, 0xFF }, { 0xD4, 0x7F },
+	{ 0xD5, 0x7F }, { 0xD6, 0x3E }, { 0xD7, 0x08 }, { 0xD8, 0x3C },
+	{ 0xD9, 0x08 }, { 0xDA, 0x3C }, { 0xDB, 0x9B }, { 0xDE, 0x00 },
+	{ 0xDF, 0x00 }, { 0xE0, 0x14 }, { 0xEE, 0x00 }, { 0xEF, 0x4A },
+	{ 0xF0, 0x44 }, { 0xF1, 0x0C }, { 0xF2, 0x32 }, { 0xF4, 0x3F },
+	{ 0xF5, 0xE0 }, { 0xF6, 0x69 }, { 0xF7, 0x10 }, { 0xFA, 0xFA }
+};
+
+/*
+ * This is Analog Device's Register Settings Recommendation for CVBS
+ * autodetect mode. It is here for future reference only, we don't use
+ * it yet because autodetect doesn't work very well when the chip is
+ * initialized with this set.
+ */
+static const struct adv7180_reg_tbl_t __maybe_unused
+adv7180_cvbs_autodetect[] = {
+	/* Set analog mux for Composite Ain1 */
+	{ ADV7180_INPUT_CTL, 0x00 },
+	/* ADI Required Write: Reset Clamp Circuitry */
+	{ ADV7180_ANALOG_CLAMP_CTL, 0x30 },
+	/* Enable SFL Output */
+	{ ADV7180_EXT_OUT_CTL, 0x57 },
+	/* Select SH1 Chroma Shaping Filter */
+	{ ADV7180_SHAP_FILTER_CTL_1, 0x41 },
+	/* Enable NEWAVMODE */
+	{ ADV7180_VSYNC_FIELD_CTL_1, 0x02 },
+	/* ADI Required Write: optimize windowing function Step 1,2,3 */
+	{ ADV7180_MANUAL_WIN_CTL_1, 0xA2 },
+	{ ADV7180_MANUAL_WIN_CTL_2, 0x6A },
+	{ ADV7180_MANUAL_WIN_CTL_3, 0xA0 },
+	/* ADI Required Write: Enable ADC step 1,2,3 */
+	{ ADV7180_ADI_CTL_1, 0x80 }, /* undocumented bit 7 */
+	{ 0x55, 0x81 },              /* undocumented register 0x55 */
+	{ ADV7180_ADI_CTL_1, 0x00 },
+	/* Recommended AFE I BIAS Setting for CVBS mode */
+	{ ADV7180_CVBS_TRIM, 0x0D },
+};
+
+#define ADV7180_VOLTAGE_ANALOG               1800000
+#define ADV7180_VOLTAGE_DIGITAL_CORE         1800000
+#define ADV7180_VOLTAGE_DIGITAL_IO           3300000
+#define ADV7180_VOLTAGE_PLL                  1800000
+
+static int adv7180_regulator_enable(struct adv7180_dev *sensor)
+{
+	struct device *dev = sensor->dev;
+	int ret = 0;
+
+	sensor->dvddio = devm_regulator_get(dev, "DOVDD");
+	if (!IS_ERR(sensor->dvddio)) {
+		regulator_set_voltage(sensor->dvddio,
+				      ADV7180_VOLTAGE_DIGITAL_IO,
+				      ADV7180_VOLTAGE_DIGITAL_IO);
+		ret = regulator_enable(sensor->dvddio);
+		if (ret) {
+			v4l2_err(&sensor->sd, "set io voltage failed\n");
+			return ret;
+		}
+	} else {
+		v4l2_warn(&sensor->sd, "cannot get io voltage\n");
+	}
+
+	sensor->dvdd = devm_regulator_get(dev, "DVDD");
+	if (!IS_ERR(sensor->dvdd)) {
+		regulator_set_voltage(sensor->dvdd,
+				      ADV7180_VOLTAGE_DIGITAL_CORE,
+				      ADV7180_VOLTAGE_DIGITAL_CORE);
+		ret = regulator_enable(sensor->dvdd);
+		if (ret) {
+			v4l2_err(&sensor->sd, "set core voltage failed\n");
+			return ret;
+		}
+	} else {
+		v4l2_warn(&sensor->sd, "cannot get core voltage\n");
+	}
+
+	sensor->avdd = devm_regulator_get(dev, "AVDD");
+	if (!IS_ERR(sensor->avdd)) {
+		regulator_set_voltage(sensor->avdd,
+				      ADV7180_VOLTAGE_ANALOG,
+				      ADV7180_VOLTAGE_ANALOG);
+		ret = regulator_enable(sensor->avdd);
+		if (ret) {
+			v4l2_err(&sensor->sd, "set analog voltage failed\n");
+			return ret;
+		}
+	} else {
+		v4l2_warn(&sensor->sd, "cannot get analog voltage\n");
+	}
+
+	sensor->pvdd = devm_regulator_get(dev, "PVDD");
+	if (!IS_ERR(sensor->pvdd)) {
+		regulator_set_voltage(sensor->pvdd,
+				      ADV7180_VOLTAGE_PLL,
+				      ADV7180_VOLTAGE_PLL);
+		ret = regulator_enable(sensor->pvdd);
+		if (ret) {
+			v4l2_err(&sensor->sd, "set pll voltage failed\n");
+			return ret;
+		}
+	} else {
+		v4l2_warn(&sensor->sd, "cannot get pll voltage\n");
+	}
+
+	return ret;
+}
+
+static void adv7180_regulator_disable(struct adv7180_dev *sensor)
+{
+	if (sensor->dvddio)
+		regulator_disable(sensor->dvddio);
+
+	if (sensor->dvdd)
+		regulator_disable(sensor->dvdd);
+
+	if (sensor->avdd)
+		regulator_disable(sensor->avdd);
+
+	if (sensor->pvdd)
+		regulator_disable(sensor->pvdd);
+}
+
+/***********************************************************************
+ * I2C transfer.
+ ***********************************************************************/
+
+/*! Read one register from a ADV7180 i2c slave device.
+ *
+ *  @param *reg		register in the device we wish to access.
+ *
+ *  @return		       0 if success, an error code otherwise.
+ */
+static int adv7180_read_reg(struct adv7180_dev *sensor, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(sensor->i2c_client, reg);
+	if (ret < 0) {
+		v4l2_err(&sensor->sd, "%s: read reg error: reg=%2x\n",
+			 __func__, reg);
+		return ret;
+	}
+
+	*val = ret;
+	return 0;
+}
+
+/*! Write one register of a ADV7180 i2c slave device.
+ *
+ *  @param *reg		register in the device we wish to access.
+ *
+ *  @return		       0 if success, an error code otherwise.
+ */
+static int adv7180_write_reg(struct adv7180_dev *sensor, u8 reg, u8 val)
+{
+	int ret = i2c_smbus_write_byte_data(sensor->i2c_client, reg, val);
+
+	if (ret < 0)
+		v4l2_err(&sensor->sd, "%s: write reg error:reg=%2x,val=%2x\n",
+			 __func__, reg, val);
+	return ret;
+}
+
+#define ADV7180_READ_REG(s, r, v) {				\
+		ret = adv7180_read_reg((s), (r), (v));		\
+		if (ret)					\
+			return ret;				\
+	}
+#define ADV7180_WRITE_REG(s, r, v) {				\
+		ret = adv7180_write_reg((s), (r), (v));		\
+		if (ret)					\
+			return ret;				\
+	}
+
+static int adv7180_load_reg_tbl(struct adv7180_dev *sensor,
+				const struct adv7180_reg_tbl_t *tbl, int n)
+{
+	int ret, i;
+
+	for (i = 0; i < n; i++) {
+		if (tbl[i].reg == ADV_DELAY) {
+			unsigned long usec = (unsigned long)tbl[i].val * 1000;
+
+			usleep_range(usec, usec + 1);
+			continue;
+		}
+
+		ADV7180_WRITE_REG(sensor, tbl[i].reg, tbl[i].val);
+	}
+
+	return 0;
+}
+
+/* Read AD_RESULT to get the autodetected video standard */
+static int adv7180_get_autodetect_std(struct adv7180_dev *sensor,
+				      u8 stat1, bool *status_change)
+{
+	int ad_result, idx = ADV7180_PAL;
+	v4l2_std_id std = V4L2_STD_PAL;
+
+	*status_change = false;
+
+	/*
+	 * When the chip loses lock, it continues to send data at whatever
+	 * standard was detected before, so leave the standard at the last
+	 * detected standard.
+	 */
+	if (!sensor->locked)
+		return 0; /* no status change */
+
+	ad_result = (stat1 & ADV7180_AD_RESULT_MASK) >> ADV7180_AD_RESULT_BIT;
+
+	switch (ad_result) {
+	case ADV7180_AD_PAL:
+		std = V4L2_STD_PAL;
+		idx = ADV7180_PAL;
+		break;
+	case ADV7180_AD_PAL_M:
+		std = V4L2_STD_PAL_M;
+		/* PAL M is very similar to NTSC (same lines/field) */
+		idx = ADV7180_NTSC;
+		break;
+	case ADV7180_AD_PAL_N:
+		std = V4L2_STD_PAL_N;
+		idx = ADV7180_PAL;
+		break;
+	case ADV7180_AD_PAL_60:
+		std = V4L2_STD_PAL_60;
+		/* PAL 60 has same lines as NTSC */
+		idx = ADV7180_NTSC;
+		break;
+	case ADV7180_AD_NTSC:
+		std = V4L2_STD_NTSC;
+		idx = ADV7180_NTSC;
+		break;
+	case ADV7180_AD_NTSC_4_43:
+		std = V4L2_STD_NTSC_443;
+		idx = ADV7180_NTSC;
+		break;
+	case ADV7180_AD_SECAM:
+		std = V4L2_STD_SECAM;
+		idx = ADV7180_PAL;
+		break;
+	case ADV7180_AD_SECAM_525:
+		/*
+		 * FIXME: could not find any info on "SECAM 525", assume
+		 * it is SECAM but with NTSC line standard.
+		 */
+		std = V4L2_STD_SECAM;
+		idx = ADV7180_NTSC;
+		break;
+	}
+
+	if (std != sensor->std_id) {
+		sensor->video_idx = idx;
+		sensor->std_id = std;
+		sensor->streamcap.timeperframe =
+			adv7180_std[sensor->video_idx].std.frameperiod;
+		sensor->fmt.width = adv7180_std[sensor->video_idx].width;
+		sensor->fmt.height = adv7180_std[sensor->video_idx].height;
+		*status_change = true;
+	}
+
+	return 0;
+}
+
+/* Update lock status */
+static int adv7180_update_lock_status(struct adv7180_dev *sensor,
+				      u8 stat1, bool *status_change)
+{
+	u8 int_stat1, int_stat3, int_raw_stat3;
+	u8 int_mask1, int_mask3;
+	int ret;
+
+	/* Switch to interrupt register map */
+	ADV7180_WRITE_REG(sensor, ADV7180_ADI_CTL_1, 0x20);
+
+	ADV7180_READ_REG(sensor, ADV7180_INT_MASK_1, &int_mask1);
+	ADV7180_READ_REG(sensor, ADV7180_INT_MASK_3, &int_mask3);
+
+	ADV7180_READ_REG(sensor, ADV7180_INT_STATUS_1, &int_stat1);
+	ADV7180_READ_REG(sensor, ADV7180_INT_STATUS_3, &int_stat3);
+
+	int_stat1 &= int_mask1;
+	int_stat3 &= int_mask3;
+
+	/* clear the interrupts */
+	ADV7180_WRITE_REG(sensor, ADV7180_INT_CLEAR_1, int_stat1);
+	ADV7180_WRITE_REG(sensor, ADV7180_INT_CLEAR_1, 0);
+	ADV7180_WRITE_REG(sensor, ADV7180_INT_CLEAR_3, int_stat3);
+	ADV7180_WRITE_REG(sensor, ADV7180_INT_CLEAR_3, 0);
+
+	ADV7180_READ_REG(sensor, ADV7180_INT_RAW_STATUS_3, &int_raw_stat3);
+
+	/* Switch back to normal register map */
+	ADV7180_WRITE_REG(sensor, ADV7180_ADI_CTL_1, 0x00);
+
+	*status_change = ((stat1 & ADV7180_LOST_LOCK) || int_stat1 ||
+			  int_stat3);
+
+	sensor->locked = ((stat1 & ADV7180_IN_LOCK) &&
+			  (stat1 & ADV7180_FSC_LOCK) &&
+			  (int_raw_stat3 & ADV7180_INT_SD_V_LOCK));
+
+	return 0;
+}
+
+static void adv7180_power(struct adv7180_dev *sensor, bool enable)
+{
+	if (enable && !sensor->on) {
+		if (gpio_is_valid(sensor->pwdn_gpio))
+			gpio_set_value_cansleep(sensor->pwdn_gpio, 1);
+
+		usleep_range(5000, 5001);
+		adv7180_write_reg(sensor, ADV7180_PWR_MNG, 0);
+	} else if (!enable && sensor->on) {
+		adv7180_write_reg(sensor, ADV7180_PWR_MNG, 0x24);
+
+		if (gpio_is_valid(sensor->pwdn_gpio))
+			gpio_set_value_cansleep(sensor->pwdn_gpio, 0);
+	}
+
+	sensor->on = enable;
+}
+
+/* threaded irq handler */
+static irqreturn_t adv7180_interrupt(int irq, void *dev_id)
+{
+	struct adv7180_dev *sensor = dev_id;
+	bool std_change, lock_status_change;
+	struct v4l2_event ev = {
+		.type = V4L2_EVENT_SOURCE_CHANGE,
+		.u.src_change.changes = 0,
+	};
+	u8 stat1;
+
+	mutex_lock(&sensor->mutex);
+
+	adv7180_read_reg(sensor, ADV7180_STATUS_1, &stat1);
+
+	adv7180_update_lock_status(sensor, stat1, &lock_status_change);
+	adv7180_get_autodetect_std(sensor, stat1, &std_change);
+
+	mutex_unlock(&sensor->mutex);
+
+	if (lock_status_change)
+		ev.u.src_change.changes |= V4L2_EVENT_SRC_CH_LOCK_STATUS;
+	if (std_change)
+		ev.u.src_change.changes |= V4L2_EVENT_SRC_CH_RESOLUTION;
+
+	if (ev.u.src_change.changes)
+		v4l2_subdev_notify_event(&sensor->sd, &ev);
+
+	return IRQ_HANDLED;
+}
+
+static const struct adv7180_inputs_t *
+adv7180_find_input(struct adv7180_dev *sensor, u32 insel)
+{
+	int i;
+
+	for (i = 0; i < NUM_INPUTS_64_48; i++) {
+		if (insel == adv7180_inputs_64_48[i].insel)
+			return &adv7180_inputs_64_48[i];
+	}
+
+	return NULL;
+}
+
+/* --------------- Subdev Operations --------------- */
+
+static int adv7180_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+	bool std_change, lsc;
+	int ret = 0;
+	u8 stat1;
+
+	mutex_lock(&sensor->mutex);
+
+	/*
+	 * If we have the ADV7180 irq, we can just return the currently
+	 * detected standard. Otherwise we have to poll the AD_RESULT
+	 * bits every time querystd() is called.
+	 */
+	if (!sensor->i2c_client->irq) {
+		adv7180_read_reg(sensor, ADV7180_STATUS_1, &stat1);
+
+		ret = adv7180_update_lock_status(sensor, stat1, &lsc);
+		if (ret)
+			goto unlock;
+		ret = adv7180_get_autodetect_std(sensor, stat1, &std_change);
+		if (ret)
+			goto unlock;
+	}
+
+	*std = sensor->std_id;
+
+unlock:
+	mutex_unlock(&sensor->mutex);
+	return ret;
+}
+
+static int adv7180_s_power(struct v4l2_subdev *sd, int on)
+{
+	return 0;
+}
+
+static int adv7180_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+	struct v4l2_captureparm *cparm = &a->parm.capture;
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	memset(a, 0, sizeof(*a));
+	a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	cparm->capability = sensor->streamcap.capability;
+	cparm->timeperframe = sensor->streamcap.timeperframe;
+	cparm->capturemode = sensor->streamcap.capturemode;
+
+	return 0;
+}
+
+static int adv7180_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a)
+{
+	return 0;
+}
+
+static int adv7180_get_fmt(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_pad_config *cfg,
+			   struct v4l2_subdev_format *format)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+	struct v4l2_mbus_framefmt *mf = &format->format;
+
+	if (format->pad)
+		return -EINVAL;
+
+	*mf = sensor->fmt;
+
+	return 0;
+}
+
+/*
+ * This driver autodetects a standard video mode, so we don't allow
+ * setting a mode, just return the current autodetected mode.
+ */
+static int adv7180_set_fmt(struct v4l2_subdev *sd,
+			   struct v4l2_subdev_pad_config *cfg,
+			   struct v4l2_subdev_format *format)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+	struct v4l2_mbus_framefmt *mf = &format->format;
+
+	if (format->pad)
+		return -EINVAL;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+		mf = &cfg->try_fmt;
+
+	*mf = sensor->fmt;
+
+	return 0;
+}
+
+
+/* Controls */
+
+static int adv7180_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct adv7180_dev *sensor = ctrl_to_adv7180_dev(ctrl);
+	int ret = 0;
+	u8 tmp;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		tmp = ctrl->val;
+		ADV7180_WRITE_REG(sensor, ADV7180_BRIGHTNESS, tmp);
+		break;
+	case V4L2_CID_CONTRAST:
+		tmp = ctrl->val;
+		ADV7180_WRITE_REG(sensor, ADV7180_CONTRAST, tmp);
+		break;
+	case V4L2_CID_SATURATION:
+		tmp = ctrl->val;
+		ADV7180_WRITE_REG(sensor, ADV7180_SD_SATURATION_CB, tmp);
+		ADV7180_WRITE_REG(sensor, ADV7180_SD_SATURATION_CR, tmp);
+		break;
+	case V4L2_CID_HUE:
+		tmp = ctrl->val;
+		/* Hue is inverted according to HSL chart */
+		ADV7180_WRITE_REG(sensor, ADV7180_HUE_REG, -tmp);
+		break;
+	case V4L2_CID_ADV718x_OFF_CR:
+		tmp = ctrl->val + 128;
+		ADV7180_WRITE_REG(sensor, ADV7180_SD_OFFSET_CR, tmp);
+		break;
+	case V4L2_CID_ADV718x_OFF_CB:
+		tmp = ctrl->val + 128;
+		ADV7180_WRITE_REG(sensor, ADV7180_SD_OFFSET_CB, tmp);
+		break;
+	case V4L2_CID_ADV718x_FREERUN_ENABLE:
+		ADV7180_READ_REG(sensor, ADV7180_DEF_Y, &tmp);
+		tmp &= ~ADV7180_DEF_VAL_AUTO_EN;
+		if (ctrl->val)
+			tmp |= ADV7180_DEF_VAL_AUTO_EN;
+		ADV7180_WRITE_REG(sensor, ADV7180_DEF_Y, tmp);
+		break;
+	case V4L2_CID_ADV718x_FORCE_FREERUN:
+		ADV7180_READ_REG(sensor, ADV7180_DEF_Y, &tmp);
+		tmp &= ~ADV7180_DEF_VAL_EN;
+		if (ctrl->val)
+			tmp |= ADV7180_DEF_VAL_EN;
+		ADV7180_WRITE_REG(sensor, ADV7180_DEF_Y, tmp);
+		break;
+	case V4L2_CID_ADV718x_FREERUN_Y:
+		ADV7180_READ_REG(sensor, ADV7180_DEF_Y, &tmp);
+		tmp &= ~ADV7180_DEF_Y_MASK;
+		tmp |= (ctrl->val << ADV7180_DEF_Y_BIT);
+		ADV7180_WRITE_REG(sensor, ADV7180_DEF_Y, tmp);
+		break;
+	case V4L2_CID_ADV718x_FREERUN_CB:
+		ADV7180_READ_REG(sensor, ADV7180_DEF_C, &tmp);
+		tmp &= ~ADV7180_DEF_CB_MASK;
+		tmp |= (ctrl->val << ADV7180_DEF_CB_BIT);
+		ADV7180_WRITE_REG(sensor, ADV7180_DEF_C, tmp);
+		break;
+	case V4L2_CID_ADV718x_FREERUN_CR:
+		ADV7180_READ_REG(sensor, ADV7180_DEF_C, &tmp);
+		tmp &= ~ADV7180_DEF_CR_MASK;
+		tmp |= (ctrl->val << ADV7180_DEF_CR_BIT);
+		ADV7180_WRITE_REG(sensor, ADV7180_DEF_C, tmp);
+		break;
+	case V4L2_CID_ADV718x_CTI_ENABLE:
+		ADV7180_READ_REG(sensor, ADV7180_CTI_DNR, &tmp);
+		tmp &= ~ADV7180_CTI_EN;
+		if (ctrl->val)
+			tmp |= ADV7180_CTI_EN;
+		ADV7180_WRITE_REG(sensor, ADV7180_CTI_DNR, tmp);
+		break;
+	case V4L2_CID_ADV718x_CTI_AB_ENABLE:
+		ADV7180_READ_REG(sensor, ADV7180_CTI_DNR, &tmp);
+		tmp &= ~ADV7180_CTI_AB_EN;
+		if (ctrl->val)
+			tmp |= ADV7180_CTI_AB_EN;
+		ADV7180_WRITE_REG(sensor, ADV7180_CTI_DNR, tmp);
+		break;
+	case V4L2_CID_ADV718x_CTI_AB:
+		ADV7180_READ_REG(sensor, ADV7180_CTI_DNR, &tmp);
+		tmp &= ~ADV7180_CTI_AB_MASK;
+		tmp |= (ctrl->val << ADV7180_CTI_AB_BIT);
+		ADV7180_WRITE_REG(sensor, ADV7180_CTI_DNR, tmp);
+		break;
+	case V4L2_CID_ADV718x_CTI_THRESH:
+		tmp = ctrl->val;
+		ADV7180_WRITE_REG(sensor, ADV7180_CTI_THRESH, tmp);
+		break;
+	case V4L2_CID_ADV718x_DNR_ENABLE:
+		ADV7180_READ_REG(sensor, ADV7180_CTI_DNR, &tmp);
+		tmp &= ~ADV7180_DNR_EN;
+		if (ctrl->val)
+			tmp |= ADV7180_DNR_EN;
+		ADV7180_WRITE_REG(sensor, ADV7180_CTI_DNR, tmp);
+		break;
+	case V4L2_CID_ADV718x_DNR_THRESH1:
+		tmp = ctrl->val;
+		ADV7180_WRITE_REG(sensor, ADV7180_DNR_THRESH1, tmp);
+		break;
+	case V4L2_CID_ADV718x_LUMA_PEAK_GAIN:
+		tmp = ctrl->val;
+		ADV7180_WRITE_REG(sensor, ADV7180_LUMA_PEAK_GAIN, tmp);
+		break;
+	case V4L2_CID_ADV718x_DNR_THRESH2:
+		tmp = ctrl->val;
+		ADV7180_WRITE_REG(sensor, ADV7180_DNR_THRESH2, tmp);
+		break;
+	default:
+		ret = -EPERM;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops adv7180_ctrl_ops = {
+	.s_ctrl = adv7180_s_ctrl,
+};
+
+/* supported standard controls */
+static const struct v4l2_ctrl_config adv7180_std_ctrl[] = {
+	{
+		.id = V4L2_CID_BRIGHTNESS,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Brightness",
+		.def =   0,
+		.min =   0,
+		.max = 255,
+		.step =  1,
+	}, {
+		.id = V4L2_CID_SATURATION,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Saturation",
+		.def = 128,
+		.min =   0,
+		.max = 255,
+		.step =  1,
+	}, {
+		.id = V4L2_CID_CONTRAST,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Contrast",
+		.def = 128,
+		.min =   0,
+		.max = 255,
+		.step =  1,
+	}, {
+		.id = V4L2_CID_HUE,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Hue",
+		.def =    0,
+		.min = -127,
+		.max =  128,
+		.step =   1,
+	},
+};
+
+#define ADV7180_NUM_STD_CONTROLS ARRAY_SIZE(adv7180_std_ctrl)
+
+/* supported custom controls */
+static const struct v4l2_ctrl_config adv7180_custom_ctrl[] = {
+	{
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_OFF_CR,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Offset Cr",
+		.def =    0, /*    0 mV offset applied to the Cr channel */
+		.min = -128, /* −312 mV offset applied to the Cr channel */
+		.max =  127, /* +312 mV offset applied to the Cr channel */
+		.step =   1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_OFF_CB,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Offset Cb",
+		.def =    0, /*    0 mV offset applied to the Cb channel */
+		.min = -128, /* −312 mV offset applied to the Cb channel */
+		.max =  127, /* +312 mV offset applied to the Cb channel */
+		.step =   1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_FREERUN_ENABLE,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "Freerun Enable",
+		.def =  1,
+		.min =  0,
+		.max =  1,
+		.step = 1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_FORCE_FREERUN,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "Force Freerun",
+		.def =  0,
+		.min =  0,
+		.max =  1,
+		.step = 1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_FREERUN_Y,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Freerun Y",
+		.def =  13,
+		.min =   0,
+		.max =  63,
+		.step =  1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_FREERUN_CB,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Freerun Cb",
+		.def =  12,
+		.min =   0,
+		.max =  15,
+		.step =  1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_FREERUN_CR,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Freerun Cr",
+		.def =   7,
+		.min =   0,
+		.max =  15,
+		.step =  1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_CTI_ENABLE,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "CTI Enable",
+		.def =  0,
+		.min =  0,
+		.max =  1,
+		.step = 1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_CTI_AB_ENABLE,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "CTI AB Enable",
+		.def =  0,
+		.min =  0,
+		.max =  1,
+		.step = 1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_CTI_AB,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "CTI AB Sharpness",
+		.def =  3,
+		.min =  0,
+		.max =  3,
+		.step = 1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_CTI_THRESH,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "CTI Threshold",
+		.def =   8,
+		.min =   0,
+		.max = 255,
+		.step =  1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_DNR_ENABLE,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "DNR Enable",
+		.def =  1,
+		.min =  0,
+		.max =  1,
+		.step = 1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_DNR_THRESH1,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "DNR1 Threshold",
+		.def =   8,
+		.min =   0,
+		.max = 255,
+		.step =  1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_LUMA_PEAK_GAIN,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Luma Peak Gain",
+		.def =  64,
+		.min =   0,
+		.max = 255,
+		.step =  1,
+	}, {
+		.ops = &adv7180_ctrl_ops,
+		.id = V4L2_CID_ADV718x_DNR_THRESH2,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "DNR2 Threshold",
+		.def =   4,
+		.min =   0,
+		.max = 255,
+		.step =  1,
+	},
+};
+
+#define ADV7180_NUM_CUSTOM_CONTROLS ARRAY_SIZE(adv7180_custom_ctrl)
+
+#define ADV7180_NUM_CONTROLS \
+	(ADV7180_NUM_STD_CONTROLS + ADV7180_NUM_CUSTOM_CONTROLS)
+
+static int adv7180_init_controls(struct adv7180_dev *sensor)
+{
+	const struct v4l2_ctrl_config *c;
+	int i, err;
+
+	v4l2_ctrl_handler_init(&sensor->ctrl_hdl, ADV7180_NUM_CONTROLS);
+
+	for (i = 0; i < ADV7180_NUM_STD_CONTROLS; i++) {
+		c = &adv7180_std_ctrl[i];
+
+		v4l2_ctrl_new_std(&sensor->ctrl_hdl, &adv7180_ctrl_ops,
+				  c->id, c->min, c->max, c->step, c->def);
+	}
+
+	for (i = 0; i < ADV7180_NUM_CUSTOM_CONTROLS; i++) {
+		c = &adv7180_custom_ctrl[i];
+
+		v4l2_ctrl_new_custom(&sensor->ctrl_hdl, c, NULL);
+	}
+
+	sensor->sd.ctrl_handler = &sensor->ctrl_hdl;
+	if (sensor->ctrl_hdl.error) {
+		err = sensor->ctrl_hdl.error;
+		goto error;
+	}
+
+	err = v4l2_ctrl_handler_setup(&sensor->ctrl_hdl);
+	if (err)
+		goto error;
+
+	return 0;
+
+ error:
+	v4l2_ctrl_handler_free(&sensor->ctrl_hdl);
+	v4l2_err(&sensor->sd, "%s: error %d\n", __func__, err);
+
+	return err;
+}
+
+static int adv7180_enum_frame_size(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_pad_config *cfg,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+
+	if (fse->pad)
+		return -EINVAL;
+	if (fse->index || fse->code != sensor->fmt.code)
+		return -EINVAL;
+
+	fse->min_width = adv7180_std[sensor->video_idx].width;
+	fse->max_width = fse->min_width;
+	fse->min_height = adv7180_std[sensor->video_idx].height;
+	fse->max_height = fse->min_height;
+
+	return 0;
+}
+
+static int adv7180_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+
+	mutex_lock(&sensor->mutex);
+
+	*status = 0;
+
+	if (sensor->on) {
+		if (!sensor->locked)
+			*status = V4L2_IN_ST_NO_SIGNAL | V4L2_IN_ST_NO_SYNC;
+	} else {
+		*status = V4L2_IN_ST_NO_POWER;
+	}
+
+	mutex_unlock(&sensor->mutex);
+
+	return 0;
+}
+
+static int adv7180_s_routing(struct v4l2_subdev *sd, u32 input,
+			     u32 output, u32 config)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+	const struct adv7180_inputs_t *advinput;
+	struct v4l2_event ev = {
+		.type = V4L2_EVENT_SOURCE_CHANGE,
+		.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+	};
+
+	advinput = adv7180_find_input(sensor, input);
+	if (!advinput)
+		return -EINVAL;
+
+	mutex_lock(&sensor->mutex);
+
+	if (input == sensor->current_input)
+		goto out;
+
+	adv7180_write_reg(sensor, ADV7180_INPUT_CTL, advinput->insel);
+
+	/* ADI Required Write: Reset Clamp Circuitry */
+	adv7180_write_reg(sensor, ADV7180_ANALOG_CLAMP_CTL, 0x30);
+
+	sensor->current_input = input;
+
+	v4l2_subdev_notify_event(&sensor->sd, &ev);
+out:
+	mutex_unlock(&sensor->mutex);
+	return 0;
+}
+
+static int adv7180_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_pad_config *cfg,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+
+	if (code->pad)
+		return -EINVAL;
+	if (code->index != 0)
+		return -EINVAL;
+
+	code->code = sensor->fmt.code;
+
+	return 0;
+}
+
+static int adv7180_g_mbus_config(struct v4l2_subdev *sd,
+				 struct v4l2_mbus_config *cfg)
+{
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+
+	cfg->type = V4L2_MBUS_BT656;
+	cfg->flags = sensor->ep.bus.parallel.flags;
+
+	return 0;
+}
+
+static int adv7180_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	return 0;
+}
+
+static int adv7180_subscribe_event(struct v4l2_subdev *sd,
+				   struct v4l2_fh *fh,
+				   struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_src_change_event_subdev_subscribe(sd, fh, sub);
+	case V4L2_EVENT_CTRL:
+		return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
+	default:
+		return -EINVAL;
+	}
+}
+
+static struct v4l2_subdev_core_ops adv7180_core_ops = {
+	.subscribe_event = adv7180_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+	.s_power = adv7180_s_power,
+	.g_ext_ctrls = v4l2_subdev_g_ext_ctrls,
+	.try_ext_ctrls = v4l2_subdev_try_ext_ctrls,
+	.s_ext_ctrls = v4l2_subdev_s_ext_ctrls,
+	.g_ctrl = v4l2_subdev_g_ctrl,
+	.s_ctrl = v4l2_subdev_s_ctrl,
+	.queryctrl = v4l2_subdev_queryctrl,
+	.querymenu = v4l2_subdev_querymenu,
+};
+
+static struct v4l2_subdev_video_ops adv7180_video_ops = {
+	.s_parm = adv7180_s_parm,
+	.g_parm = adv7180_g_parm,
+	.g_input_status = adv7180_g_input_status,
+	.s_routing = adv7180_s_routing,
+	.querystd = adv7180_querystd,
+	.g_mbus_config  = adv7180_g_mbus_config,
+	.s_stream = adv7180_s_stream,
+};
+
+static struct v4l2_subdev_pad_ops adv7180_pad_ops = {
+	.enum_mbus_code = adv7180_enum_mbus_code,
+	.get_fmt = adv7180_get_fmt,
+	.set_fmt = adv7180_set_fmt,
+	.enum_frame_size = adv7180_enum_frame_size,
+};
+
+static struct v4l2_subdev_ops adv7180_subdev_ops = {
+	.core = &adv7180_core_ops,
+	.video = &adv7180_video_ops,
+	.pad = &adv7180_pad_ops,
+};
+
+/***********************************************************************
+ * I2C client and driver.
+ ***********************************************************************/
+
+/*! ADV7180 Reset function.
+ *
+ *  @return		None.
+ */
+static int adv7180_hard_reset(struct adv7180_dev *sensor)
+{
+	/* assert reset bit */
+	adv7180_write_reg(sensor, ADV7180_PWR_MNG, 0x80);
+	usleep_range(5000, 5001);
+
+	return adv7180_load_reg_tbl(sensor, adv7180_fsl_init_reg,
+				    ARRAY_SIZE(adv7180_fsl_init_reg));
+}
+
+/*
+ * Enable the SD_UNLOCK and SD_AD_CHNG interrupts.
+ */
+static int adv7180_enable_interrupts(struct adv7180_dev *sensor)
+{
+	int ret;
+
+	/* Switch to interrupt register map */
+	ADV7180_WRITE_REG(sensor, ADV7180_ADI_CTL_1, 0x20);
+	/* INTRQ active low, active until cleared */
+	ADV7180_WRITE_REG(sensor, ADV7180_INT_CONFIG_1, 0xd5);
+	/* unmask SD_UNLOCK and SD_LOCK */
+	ADV7180_WRITE_REG(sensor, ADV7180_INT_MASK_1,
+			  ADV7180_INT_SD_UNLOCK | ADV7180_INT_SD_LOCK);
+	/* unmask SD_AD_CHNG and SD_V_LOCK_CHNG */
+	ADV7180_WRITE_REG(sensor, ADV7180_INT_MASK_3,
+			  ADV7180_INT_SD_AD_CHNG | ADV7180_INT_SD_V_LOCK_CHNG);
+	/* Switch back to normal register map */
+	ADV7180_WRITE_REG(sensor, ADV7180_ADI_CTL_1, 0x00);
+
+	return 0;
+}
+
+/*!
+ * ADV7180 I2C probe function.
+ * Function set in i2c_driver struct.
+ * Called by insmod.
+ *
+ *  @param *adapter	I2C adapter descriptor.
+ *
+ *  @return		Error code indicating success or failure.
+ */
+static int adv7180_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct device_node *endpoint;
+	struct adv7180_dev *sensor;
+	struct device_node *np;
+	const char *norm = "pal";
+	bool std_change, lsc;
+	u8 stat1, rev_id;
+	int ret = 0;
+
+	sensor = devm_kzalloc(&client->dev, sizeof(struct adv7180_dev),
+			      GFP_KERNEL);
+	if (!sensor)
+		return -ENOMEM;
+
+	sensor->dev = &client->dev;
+	np = sensor->dev->of_node;
+
+	ret = of_property_read_string(np, "default-std", &norm);
+	if (ret < 0 && ret != -EINVAL) {
+		dev_err(sensor->dev, "error reading default-std property!\n");
+		return ret;
+	}
+	if (!strcasecmp(norm, "pal")) {
+		sensor->std_id = V4L2_STD_PAL;
+		sensor->video_idx = ADV7180_PAL;
+		dev_info(sensor->dev, "defaulting to PAL!\n");
+	} else if (!strcasecmp(norm, "ntsc")) {
+		sensor->std_id = V4L2_STD_NTSC;
+		sensor->video_idx = ADV7180_NTSC;
+		dev_info(sensor->dev, "defaulting to NTSC!\n");
+	} else {
+		dev_err(sensor->dev, "invalid default-std value: '%s'!\n",
+			norm);
+		return -EINVAL;
+	}
+
+	/* Set initial values for the sensor struct. */
+	sensor->i2c_client = client;
+	sensor->streamcap.timeperframe =
+		adv7180_std[sensor->video_idx].std.frameperiod;
+	sensor->fmt.width = adv7180_std[sensor->video_idx].width;
+	sensor->fmt.height = adv7180_std[sensor->video_idx].height;
+	sensor->fmt.code = MEDIA_BUS_FMT_UYVY8_2X8;
+	sensor->fmt.field = V4L2_FIELD_SEQ_BT;
+
+	mutex_init(&sensor->mutex);
+
+	endpoint = of_graph_get_next_endpoint(np, NULL);
+	if (!endpoint) {
+		dev_err(sensor->dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	v4l2_of_parse_endpoint(endpoint, &sensor->ep);
+	if (sensor->ep.bus_type != V4L2_MBUS_BT656) {
+		dev_err(sensor->dev, "invalid bus type, must be bt.656\n");
+		return -EINVAL;
+	}
+	of_node_put(endpoint);
+
+	ret = of_get_named_gpio(np, "pwdn-gpio", 0);
+	if (gpio_is_valid(ret)) {
+		sensor->pwdn_gpio = ret;
+		ret = devm_gpio_request_one(sensor->dev,
+					    sensor->pwdn_gpio,
+					    GPIOF_OUT_INIT_HIGH,
+					    "adv7180_pwdn");
+		if (ret < 0) {
+			dev_err(sensor->dev,
+				"request for power down gpio failed\n");
+			return ret;
+		}
+	} else {
+		if (ret == -EPROBE_DEFER)
+			return ret;
+		/* assume a power-down gpio is not required */
+		sensor->pwdn_gpio = -1;
+	}
+
+	adv7180_regulator_enable(sensor);
+
+	/* Power on the chip */
+	adv7180_power(sensor, true);
+
+	/*! ADV7180 initialization. */
+	ret = adv7180_hard_reset(sensor);
+	if (ret) {
+		dev_err(sensor->dev, "hard reset failed!\n");
+		goto cleanup;
+	}
+
+	/*! Read the revision ID of the chip */
+	ret = adv7180_read_reg(sensor, ADV7180_IDENT, &rev_id);
+	if (ret < 0) {
+		dev_err(sensor->dev,
+			"failed to read ADV7180 IDENT register!\n");
+		ret = -ENODEV;
+		goto cleanup;
+	}
+	sensor->rev_id = rev_id;
+
+	dev_info(sensor->dev, "Analog Devices ADV7180 Rev 0x%02X detected!\n",
+		 sensor->rev_id);
+
+	v4l2_i2c_subdev_init(&sensor->sd, client, &adv7180_subdev_ops);
+
+	/* see if there is a signal lock already */
+	adv7180_read_reg(sensor, ADV7180_STATUS_1, &stat1);
+
+	ret = adv7180_update_lock_status(sensor, stat1, &lsc);
+	if (ret < 0)
+		goto cleanup;
+	ret = adv7180_get_autodetect_std(sensor, stat1, &std_change);
+	if (ret < 0)
+		goto cleanup;
+
+	if (sensor->i2c_client->irq) {
+		ret = request_threaded_irq(sensor->i2c_client->irq,
+					   NULL, adv7180_interrupt,
+					   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					   IF_NAME, sensor);
+		if (ret < 0) {
+			dev_err(sensor->dev, "Failed to register irq %d\n",
+				sensor->i2c_client->irq);
+			goto cleanup;
+		}
+
+		adv7180_enable_interrupts(sensor);
+
+		dev_info(sensor->dev, "Registered irq %d\n",
+			 sensor->i2c_client->irq);
+	}
+
+	ret = adv7180_init_controls(sensor);
+	if (ret)
+		goto irqfree;
+
+	ret = v4l2_async_register_subdev(&sensor->sd);
+	if (ret)
+		goto free_ctrls;
+
+	return 0;
+
+free_ctrls:
+	v4l2_ctrl_handler_free(&sensor->ctrl_hdl);
+irqfree:
+	if (sensor->i2c_client->irq)
+		free_irq(sensor->i2c_client->irq, sensor);
+cleanup:
+	adv7180_power(sensor, false);
+	adv7180_regulator_disable(sensor);
+	return ret;
+}
+
+/*!
+ * ADV7180 I2C detach function.
+ * Called on rmmod.
+ *
+ *  @param *client	struct i2c_client*.
+ *
+ *  @return		Error code indicating success or failure.
+ */
+static int adv7180_detach(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct adv7180_dev *sensor = to_adv7180_dev(sd);
+
+	if (sensor->i2c_client->irq)
+		free_irq(sensor->i2c_client->irq, sensor);
+
+	v4l2_async_unregister_subdev(&sensor->sd);
+	v4l2_device_unregister_subdev(sd);
+	v4l2_ctrl_handler_free(&sensor->ctrl_hdl);
+
+	/* Power off the chip */
+	adv7180_power(sensor, false);
+
+	adv7180_regulator_disable(sensor);
+
+	return 0;
+}
+
+static const struct i2c_device_id adv7180_id[] = {
+	{ "adv7180", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, adv7180_id);
+
+static const struct of_device_id adv7180_dt_ids[] = {
+	{ .compatible = "adi,adv7180" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, adv7180_dt_ids);
+
+static struct i2c_driver adv7180_driver = {
+	.driver = {
+		.name	= "adv7180",
+		.owner	= THIS_MODULE,
+		.of_match_table	= adv7180_dt_ids,
+	},
+	.id_table	= adv7180_id,
+	.probe		= adv7180_probe,
+	.remove		= adv7180_detach,
+};
+
+module_i2c_driver(adv7180_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Analog Devices ADV7180 Subdev driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 9343950..005c82b 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -184,6 +184,10 @@ enum v4l2_colorfx {
  * We reserve 16 controls for this driver. */
 #define V4L2_CID_USER_IMX_BASE			(V4L2_CID_USER_BASE + 0x1090)
 
+/* The base for the ADV718x sensor controls.
+ * We reserve 32 controls for this driver. */
+#define V4L2_CID_USER_ADV718X_BASE		(V4L2_CID_USER_BASE + 0x10a0)
+
 /* MPEG-class control IDs */
 /* The MPEG controls are applicable to all codec controls
  * and the 'MPEG' part of the define is historical */
diff --git a/include/uapi/media/Kbuild b/include/uapi/media/Kbuild
index fa78958..5b064a9 100644
--- a/include/uapi/media/Kbuild
+++ b/include/uapi/media/Kbuild
@@ -1,2 +1,3 @@
 # UAPI Header export list
 header-y += imx.h
+header-y += adv718x.h
diff --git a/include/uapi/media/adv718x.h b/include/uapi/media/adv718x.h
new file mode 100644
index 0000000..79abb7d
--- /dev/null
+++ b/include/uapi/media/adv718x.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2014-2015 Mentor Graphics Inc.
+ *
+ * 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
+ */
+
+#ifndef __UAPI_MEDIA_ADV718X_H__
+#define __UAPI_MEDIA_ADV718X_H__
+
+enum adv718x_ctrl_id {
+	V4L2_CID_ADV718x_OFF_CB = (V4L2_CID_USER_ADV718X_BASE + 0),
+	V4L2_CID_ADV718x_OFF_CR,
+	V4L2_CID_ADV718x_FREERUN_ENABLE,
+	V4L2_CID_ADV718x_FORCE_FREERUN,
+	V4L2_CID_ADV718x_FREERUN_Y,
+	V4L2_CID_ADV718x_FREERUN_CB,
+	V4L2_CID_ADV718x_FREERUN_CR,
+	/* Chroma Transient Improvement Controls */
+	V4L2_CID_ADV718x_CTI_ENABLE,
+	V4L2_CID_ADV718x_CTI_AB_ENABLE,
+	V4L2_CID_ADV718x_CTI_AB,
+	V4L2_CID_ADV718x_CTI_THRESH,
+	/* Digital Noise Reduction and Lumanance Peaking Gain Controls */
+	V4L2_CID_ADV718x_DNR_ENABLE,
+	V4L2_CID_ADV718x_DNR_THRESH1,
+	V4L2_CID_ADV718x_LUMA_PEAK_GAIN,
+	V4L2_CID_ADV718x_DNR_THRESH2,
+	/* ADV7182 specific controls */
+	V4L2_CID_ADV7182_FREERUN_PAT_SEL,
+	V4L2_CID_ADV7182_ACE_ENABLE,
+	V4L2_CID_ADV7182_ACE_LUMA_GAIN,
+	V4L2_CID_ADV7182_ACE_RESPONSE_SPEED,
+	V4L2_CID_ADV7182_ACE_CHROMA_GAIN,
+	V4L2_CID_ADV7182_ACE_CHROMA_MAX,
+	V4L2_CID_ADV7182_ACE_GAMMA_GAIN,
+	V4L2_CID_ADV7182_DITHER_ENABLE,
+};
+
+#endif
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux