summaryrefslogtreecommitdiff
path: root/drivers/media/platform/mxc/capture/mxc_vadc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/mxc/capture/mxc_vadc.c')
-rw-r--r--drivers/media/platform/mxc/capture/mxc_vadc.c830
1 files changed, 830 insertions, 0 deletions
diff --git a/drivers/media/platform/mxc/capture/mxc_vadc.c b/drivers/media/platform/mxc/capture/mxc_vadc.c
new file mode 100644
index 000000000000..d1f2973f8ac2
--- /dev/null
+++ b/drivers/media/platform/mxc/capture/mxc_vadc.c
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2014-2015 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.
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/media-bus-format.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include "mxc_vadc.h"
+
+/* Resource names for the VADC driver. */
+#define VAFE_REGS_ADDR_RES_NAME "vadc-vafe"
+#define VDEC_REGS_ADDR_RES_NAME "vadc-vdec"
+
+#define reg32_write(addr, val) __raw_writel(val, addr)
+#define reg32_read(addr) __raw_readl(addr)
+#define reg32setbit(addr, bitpos) \
+ reg32_write((addr), (reg32_read((addr)) | (1<<(bitpos))))
+
+#define reg32clrbit(addr, bitpos) \
+ reg32_write((addr), (reg32_read((addr)) & (0xFFFFFFFF ^ (1<<(bitpos)))))
+
+#define GPC_CNTR 0x00
+#define IMX6SX_GPC_CNTR_VADC_ANALOG_OFF_MASK BIT(17)
+#define IMX6SX_GPC_CNTR_VADC_POWER_DOWN_MASK BIT(18)
+
+void __iomem *vafe_regbase;
+void __iomem *vdec_regbase;
+
+
+/* List of input video formats supported. The video formats is corresponding
+ * with v4l2 id in video_fmt
+ */
+enum video_fmt_idx {
+ VADC_NTSC = 0, /* Locked on (M) NTSC video signal. */
+ VADC_PAL, /* (B, G, H, I, N)PAL video signal. */
+};
+
+/* Number of video standards supported (including 'not locked' signal). */
+#define VADC_STD_MAX (VADC_PAL + 1)
+
+/* Video format structure. */
+struct video_fmt{
+ v4l2_std_id v4l2_std; /* Video for linux ID. */
+ char name[16]; /* Name (e.g., "NTSC", "PAL", etc.) */
+ u16 raw_width; /* Raw width. */
+ u16 raw_height; /* Raw height. */
+ u16 active_width; /* Active width. */
+ u16 active_height; /* Active height. */
+ u16 framerates;
+};
+
+/*
+ * Maintains the information on the current state of the sensor.
+ */
+struct vadc_state {
+ struct v4l2_device v4l2_dev;
+ struct v4l2_subdev sd;
+ struct video_fmt *fmt;
+
+ struct clk *vadc_clk;
+ struct clk *csi_clk;
+ struct regmap *gpr;
+ void __iomem *gpc_reg;
+
+ u32 vadc_in;
+ u32 csi_id;
+};
+
+static int vadc_querystd(struct v4l2_subdev *sd, v4l2_std_id *std);
+
+/* Description of video formats supported.
+ *
+ * PAL: raw=720x625, active=720x576.
+ * NTSC: raw=720x525, active=720x480.
+ */
+static struct video_fmt video_fmts[] = {
+ /* NTSC */
+ {
+ .v4l2_std = V4L2_STD_NTSC,
+ .name = "NTSC",
+ .raw_width = 720,
+ .raw_height = 525,
+ .active_width = 720,
+ .active_height = 480,
+ .framerates = 30,
+ },
+ /* (B, G, H, I, N) PAL */
+ {
+ .v4l2_std = V4L2_STD_PAL,
+ .name = "PAL",
+ .raw_width = 720,
+ .raw_height = 625,
+ .active_width = 720,
+ .active_height = 576,
+ .framerates = 25,
+ },
+};
+
+static void afe_voltage_clampingmode(void)
+{
+ reg32_write(AFE_CLAMP, 0x07);
+ reg32_write(AFE_CLMPAMP, 0x60);
+ reg32_write(AFE_CLMPDAT, 0xF0);
+}
+
+static void afe_alwayson_clampingmode(void)
+{
+ reg32_write(AFE_CLAMP, 0x15);
+ reg32_write(AFE_CLMPDAT, 0x08);
+ reg32_write(AFE_CLMPAMP, 0x00);
+}
+
+static void afe_init(void)
+{
+ pr_debug("%s\n", __func__);
+
+ reg32_write(AFE_PDBUF, 0x1f);
+ reg32_write(AFE_PDADC, 0x0f);
+ reg32_write(AFE_PDSARH, 0x01);
+ reg32_write(AFE_PDSARL, 0xff);
+ reg32_write(AFE_PDADCRFH, 0x01);
+ reg32_write(AFE_PDADCRFL, 0xff);
+ reg32_write(AFE_ICTRL, 0x3a);
+ reg32_write(AFE_ICTLSTG, 0x1e);
+
+ reg32_write(AFE_RCTRLSTG, 0x1e);
+ reg32_write(AFE_INPBUF, 0x035);
+ reg32_write(AFE_INPFLT, 0x02);
+ reg32_write(AFE_ADCDGN, 0x40);
+ reg32_write(AFE_TSTSEL, 0x10);
+
+ reg32_write(AFE_ACCTST, 0x07);
+
+ reg32_write(AFE_BGREG, 0x08);
+
+ reg32_write(AFE_ADCGN, 0x09);
+
+ /* set current controlled clamping
+ * always on, low current */
+ reg32_write(AFE_CLAMP, 0x11);
+ reg32_write(AFE_CLMPAMP, 0x08);
+}
+
+static void vdec_mode_timing_init(int std)
+{
+ if (std == V4L2_STD_NTSC) {
+ /* NTSC 720x480 */
+ reg32_write(VDEC_HACTS, 0x66);
+ reg32_write(VDEC_HACTE, 0x24);
+
+ reg32_write(VDEC_VACTS, 0x29);
+ reg32_write(VDEC_VACTE, 0x04);
+
+ /* set V Position */
+ reg32_write(VDEC_VRTPOS, 0x2);
+ } else if (std == V4L2_STD_PAL) {
+ /* PAL 720x576 */
+ reg32_write(VDEC_HACTS, 0x66);
+ reg32_write(VDEC_HACTE, 0x24);
+
+ reg32_write(VDEC_VACTS, 0x29);
+ reg32_write(VDEC_VACTE, 0x04);
+
+ /* set V Position */
+ reg32_write(VDEC_VRTPOS, 0x6);
+ } else
+ pr_debug("Error not support video mode\n");
+
+ /* set H Position */
+ reg32_write(VDEC_HZPOS, 0x60);
+
+ /* set H ignore start */
+ reg32_write(VDEC_HSIGS, 0xf8);
+
+ /* set H ignore end */
+ reg32_write(VDEC_HSIGE, 0x18);
+}
+
+/*
+* vdec_init()
+* Initialises the VDEC registers
+* Returns: nothing
+*/
+static void vdec_init(struct vadc_state *vadc)
+{
+ v4l2_std_id std;
+
+ pr_debug("%s\n", __func__);
+
+ /* Get work mode PAL or NTSC */
+ vadc_querystd(&vadc->sd, &std);
+
+ vdec_mode_timing_init(std);
+
+ /* vcr detect threshold high, automatic detections */
+ reg32_write(VDEC_VSCON2, 0);
+
+ reg32_write(VDEC_BASE + 0x110, 0x01);
+
+ /* set the noramp mode on the Hloop PLL. */
+ reg32_write(VDEC_BASE+(0x14*4), 0x10);
+
+ /* set the YC relative delay.*/
+ reg32_write(VDEC_YCDEL, 0x90);
+
+ /* setup the Hpll */
+ reg32_write(VDEC_BASE+(0x13*4), 0x13);
+
+ /* setup the 2d comb */
+ /* set the gain of the Hdetail output to 3
+ * set the notch alpha gain to 1 */
+ reg32_write(VDEC_CFC2, 0x34);
+
+ /* setup various 2d comb bits.*/
+ reg32_write(VDEC_BASE+(0x02*4), 0x01);
+ reg32_write(VDEC_BASE+(0x03*4), 0x18);
+ reg32_write(VDEC_BASE+(0x04*4), 0x34);
+
+ /* set the start of the burst gate */
+ reg32_write(VDEC_BRSTGT, 0x30);
+
+ /* set 1f motion gain */
+ reg32_write(VDEC_BASE+(0x0f*4), 0x20);
+
+ /* set the 1F chroma motion detector thresh
+ * for colour reverse detection */
+ reg32_write(VDEC_THSH1, 0x02);
+ reg32_write(VDEC_BASE+(0x4a*4), 0x20);
+ reg32_write(VDEC_BASE+(0x4b*4), 0x08);
+
+ reg32_write(VDEC_BASE+(0x4c*4), 0x08);
+
+ /* set the threshold for the narrow/wide adaptive chroma BW */
+ reg32_write(VDEC_BASE+(0x20*4), 0x20);
+
+ /* turn up the colour with the new colour gain reg */
+ /* hue: */
+ reg32_write(VDEC_HUE, 0x00);
+
+ /* cbgain: 22 B4 */
+ reg32_write(VDEC_CBGN, 0xb4);
+ /* cr gain 80 */
+ reg32_write(VDEC_CRGN, 0x80);
+ /* luma gain (contrast) */
+ reg32_write(VDEC_CNTR, 0x80);
+
+ /* setup the signed black level register, brightness */
+ reg32_write(VDEC_BRT, 0x00);
+
+ /* filter the standard detection
+ * enable the comb for the ntsc443 */
+ reg32_write(VDEC_STDDBG, 0x20);
+
+ /* setup chroma kill thresh for no chroma */
+ reg32_write(VDEC_CHBTH, 0x0);
+
+ /* set chroma loop to wider BW
+ * no set it to normal BW. i fixed the bw problem.*/
+ reg32_write(VDEC_YCDEL, 0x00);
+
+ /* set the compensation in the chroma loop for the Hloop
+ * set the ratio for the nonarithmetic 3d comb modes.*/
+ reg32_write(VDEC_BASE + (0x1d*4), 0x90);
+
+ /* set the threshold for the nonarithmetic mode for the 2d comb
+ * the higher the value the more Fc Fh offset
+ * we will tolerate before turning off the comb. */
+ reg32_write(VDEC_BASE + (0x33*4), 0xa0);
+
+ /* setup the bluescreen output colour */
+ reg32_write(VDEC_BASE + (0x3d*4), 35);
+ reg32_write(VDEC_BLSCRCR, 114);
+ reg32_write(VDEC_BLSCRCB, 212);
+
+ /* disable the active blanking */
+ reg32_write(VDEC_BASE + (0x15*4), 0x02);
+
+ /* setup the luma agc for automatic gain. */
+ reg32_write(VDEC_LMAGC2, 0x5e);
+ reg32_write(VDEC_LMAGC1, 0x81);
+
+ /* setup chroma agc */
+ reg32_write(VDEC_CHAGC2, 0xa0);
+ reg32_write(VDEC_CHAGC1, 0x01);
+
+ /* setup the MV thresh lower nibble
+ * setup the sync top cap, upper nibble */
+ reg32_write(VDEC_BASE + (0x3a*4), 0x80);
+ reg32_write(VDEC_SHPIMP, 0x00);
+
+ /* setup the vsync block */
+ reg32_write(VDEC_VSCON1, 0x87);
+
+ /* set the nosignal threshold
+ * set the vsync threshold */
+ reg32_write(VDEC_VSSGTH, 0x35);
+
+ /* set length for min hphase filter
+ * (or saturate limit if saturate is chosen) */
+ reg32_write(VDEC_BASE + (0x45*4), 0x40);
+
+ /* enable the internal resampler,
+ * select min filter not saturate for
+ * hphase noise filter for vcr detect.
+ * enable vcr pause mode different field lengths */
+ reg32_write(VDEC_BASE + (0x46*4), 0x90);
+
+ /* disable VCR detection, lock to the Hsync rather than the Vsync */
+ reg32_write(VDEC_VSCON2, 0x04);
+
+ /* set tiplevel goal for dc clamp. */
+ reg32_write(VDEC_BASE + (0x3c*4), 0xB0);
+
+ /* override SECAM detection and force SECAM off */
+ reg32_write(VDEC_BASE + (0x2f*4), 0x20);
+
+ /* Set r3d_hardblend in 3D control2 reg */
+ reg32_write(VDEC_BASE + (0x0c*4), 0x04);
+}
+
+/* set Input selector & input pull-downs */
+static void vadc_s_routing(int vadc_in)
+{
+ switch (vadc_in) {
+ case 0:
+ reg32_write(AFE_INPFLT, 0x02);
+ reg32_write(AFE_OFFDRV, 0x00);
+ reg32_write(AFE_INPCONFIG, 0x1e);
+ break;
+ case 1:
+ reg32_write(AFE_INPFLT, 0x02);
+ reg32_write(AFE_OFFDRV, 0x00);
+ reg32_write(AFE_INPCONFIG, 0x2d);
+ break;
+ case 2:
+ reg32_write(AFE_INPFLT, 0x02);
+ reg32_write(AFE_OFFDRV, 0x00);
+ reg32_write(AFE_INPCONFIG, 0x4b);
+ break;
+ case 3:
+ reg32_write(AFE_INPFLT, 0x02);
+ reg32_write(AFE_OFFDRV, 0x00);
+ reg32_write(AFE_INPCONFIG, 0x87);
+ break;
+ default:
+ pr_debug("error video input %d\n", vadc_in);
+ }
+}
+
+static void vadc_power_up(struct vadc_state *state)
+{
+ /* Power on vadc analog */
+ reg32clrbit(state->gpc_reg + GPC_CNTR, 17);
+
+ /* Power down vadc ext power */
+ reg32clrbit(state->gpc_reg + GPC_CNTR, 18);
+
+ /* software reset afe */
+ regmap_update_bits(state->gpr, IOMUXC_GPR1,
+ IMX6SX_GPR1_VADC_SW_RST_MASK,
+ IMX6SX_GPR1_VADC_SW_RST_RESET);
+
+ msleep(10);
+
+ /* clock config for vadc */
+ reg32_write(VDEC_BASE + 0x320, 0xe3);
+ reg32_write(VDEC_BASE + 0x324, 0x38);
+ reg32_write(VDEC_BASE + 0x328, 0x8e);
+ reg32_write(VDEC_BASE + 0x32c, 0x23);
+
+ /* Release reset bit */
+ regmap_update_bits(state->gpr, IOMUXC_GPR1,
+ IMX6SX_GPR1_VADC_SW_RST_MASK,
+ IMX6SX_GPR1_VADC_SW_RST_RELEASE);
+
+ /* Power on vadc ext power */
+ reg32setbit(state->gpc_reg + GPC_CNTR, 18);
+}
+
+static void vadc_power_down(struct vadc_state *state)
+{
+ /* Power down vadc analog */
+ reg32setbit(state->gpc_reg + GPC_CNTR, 17);
+
+ /* Power down vadc ext power */
+ reg32clrbit(state->gpc_reg + GPC_CNTR, 18);
+
+}
+static void vadc_init(struct vadc_state *vadc)
+{
+ pr_debug("%s\n", __func__);
+
+ vadc_power_up(vadc);
+
+ afe_init();
+
+ /* select Video Input 0-3 */
+ vadc_s_routing(vadc->vadc_in);
+
+ afe_voltage_clampingmode();
+
+ vdec_init(vadc);
+
+ /*
+ * current control loop will move sinewave input off below
+ * the bottom of the signal range visible
+ * when the testbus is viewed as magnitude,
+ * so have to break before this point while capturing ENOB data:
+ */
+ afe_alwayson_clampingmode();
+}
+
+static inline struct vadc_state *to_state(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct vadc_state, sd);
+}
+
+static int vadc_g_std(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+ struct vadc_state *state = to_state(sd);
+
+ *std = state->fmt->v4l2_std;
+ return 0;
+}
+
+/*!
+ * Return attributes of current video standard.
+ * Since this device autodetects the current standard, this function also
+ * sets the values that need to be changed if the standard changes.
+ * There is no set std equivalent function.
+ *
+ * @return None.
+ */
+static int vadc_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+ struct vadc_state *state = to_state(sd);
+ int mod;
+ int idx;
+ int i;
+
+ /* Read auto mode detected result */
+ printk(KERN_INFO"wait vadc auto detect video mode....\n");
+ for (i = 0; i < 10; i++) {
+ msleep(200);
+ mod = reg32_read(VDEC_VIDMOD);
+ /* Check video signal states */
+ if ((mod & VDEC_VIDMOD_SIGNAL_MASK)
+ == VDEC_VIDMOD_SIGNAL_DETECT)
+ break;
+ }
+ if (i == 10)
+ printk(KERN_INFO"Timeout detect video signal mod=0x%x\n", mod);
+
+ if ((mod & VDEC_VIDMOD_PAL_MASK) || (mod & VDEC_VIDMOD_M625_MASK))
+ idx = VADC_PAL;
+ else
+ idx = VADC_NTSC;
+
+ *std = video_fmts[idx].v4l2_std;
+ state->fmt = &video_fmts[idx];
+
+ printk(KERN_INFO"video mode %s\n", video_fmts[idx].name);
+ return 0;
+}
+
+static int vadc_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ /* support only one format */
+ if (code->pad || code->index >= 1)
+ return -EINVAL;
+
+ code->code = MEDIA_BUS_FMT_AYUV8_1X32;
+ return 0;
+}
+
+static int vadc_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct vadc_state *state = to_state(sd);
+ struct v4l2_mbus_framefmt *fmt = &format->format;
+
+ if (format->pad)
+ return -EINVAL;
+
+ fmt->code = MEDIA_BUS_FMT_AYUV8_1X32;
+ fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ fmt->field = V4L2_FIELD_INTERLACED;
+ fmt->width = 720;
+ fmt->height = state->fmt->v4l2_std & V4L2_STD_NTSC ? 480 : 576;
+
+ return 0;
+}
+
+static int vadc_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ return vadc_get_fmt(sd, sd_state, format);
+}
+
+static int vadc_enum_framesizes(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct vadc_state *state = to_state(sd);
+ if (fse->index >= 1)
+ return -EINVAL;
+
+ fse->min_width = state->fmt->active_width;
+ fse->max_width = state->fmt->active_width;
+ fse->min_height = state->fmt->active_height;
+ fse->max_height = state->fmt->active_height;
+
+ return 0;
+}
+static int vadc_enum_frameintervals(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct vadc_state *state = to_state(sd);
+
+ if (fie->index >= 1)
+ return -EINVAL;
+
+ fie->interval.numerator = 1;
+
+ fie->interval.denominator = state->fmt->framerates;
+
+ return 0;
+}
+
+static int vadc_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
+{
+ struct vadc_state *state = to_state(sd);
+
+ if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (parms->parm.capture.timeperframe.denominator
+ != state->fmt->framerates)
+ parms->parm.capture.timeperframe.denominator
+ = state->fmt->framerates;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops vadc_video_ops = {
+ .querystd = vadc_querystd,
+ .s_parm = vadc_s_parm,
+ .g_std = vadc_g_std,
+};
+
+
+static const struct v4l2_subdev_pad_ops vadc_pad_ops = {
+ .get_fmt = vadc_get_fmt,
+ .set_fmt = vadc_set_fmt,
+ .enum_mbus_code = vadc_enum_mbus_code,
+ .enum_frame_size = vadc_enum_framesizes,
+ .enum_frame_interval = vadc_enum_frameintervals,
+};
+
+static const struct v4l2_subdev_ops vadc_ops = {
+ .video = &vadc_video_ops,
+ .pad = &vadc_pad_ops,
+};
+
+static const struct of_device_id fsl_vadc_dt_ids[] = {
+ { .compatible = "fsl,imx6sx-vadc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_vadc_dt_ids);
+
+static int vadc_of_init(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *gpc_np;
+ struct vadc_state *state = platform_get_drvdata(pdev);
+ int csi_id;
+ int ret;
+
+ /* Get csi_id to setting vadc to csi mux in gpr */
+ ret = of_property_read_u32(np, "csi_id", &csi_id);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read of property csi_id\n");
+ return ret;
+ }
+
+ state->csi_id = csi_id;
+
+ /* remap GPR register */
+ state->gpr = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "gpr");
+ if (IS_ERR(state->gpr)) {
+ dev_dbg(&pdev->dev, "can not get gpr\n");
+ return -ENOMEM;
+ }
+
+ /* Configuration vadc-to-csi 0 or 1 */
+ if (csi_id) {
+ regmap_update_bits(state->gpr, IOMUXC_GPR5,
+ IMX6SX_GPR5_CSI2_MUX_CTRL_MASK,
+ IMX6SX_GPR5_CSI2_MUX_CTRL_CVD);
+ } else {
+ regmap_update_bits(state->gpr, IOMUXC_GPR5,
+ IMX6SX_GPR5_CSI1_MUX_CTRL_MASK,
+ IMX6SX_GPR5_CSI1_MUX_CTRL_CVD);
+ }
+
+ /* Get default vadc_in number */
+ ret = of_property_read_u32(np, "vadc_in", &state->vadc_in);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read of property vadc_in\n");
+ return ret;
+ }
+
+ /* map GPC register */
+ gpc_np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc");
+ state->gpc_reg = of_iomap(gpc_np, 0);
+ if (!state->gpc_reg) {
+ dev_err(&pdev->dev, "ioremap failed with gpc base\n");
+ goto error;
+ }
+
+ return ret;
+
+error:
+ iounmap(state->gpc_reg);
+ return ret;
+}
+
+static void vadc_v4l2_subdev_init(struct v4l2_subdev *sd,
+ struct platform_device *pdev,
+ const struct v4l2_subdev_ops *ops)
+{
+ struct vadc_state *state = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ v4l2_subdev_init(sd, ops);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ sd->owner = pdev->dev.driver->owner;
+ sd->dev = &pdev->dev;
+
+ /* initialize name */
+ snprintf(sd->name, sizeof(sd->name), "%s",
+ pdev->dev.driver->name);
+
+ v4l2_set_subdevdata(sd, state);
+
+ ret = v4l2_async_register_subdev(sd);
+ if (ret < 0)
+ dev_err(&pdev->dev, "%s--Async register faialed, ret=%d\n", __func__, ret);
+}
+
+static int vadc_probe(struct platform_device *pdev)
+{
+ struct vadc_state *state;
+ struct v4l2_subdev *sd;
+ struct resource *res;
+ int ret = 0;
+
+ state = devm_kzalloc(&pdev->dev, sizeof(struct vadc_state), GFP_KERNEL);
+ if (!state) {
+ dev_err(&pdev->dev, "Cannot allocate device data\n");
+ return -ENOMEM;
+ }
+
+ /* Set initial values for the sensor struct. */
+ state->fmt = &video_fmts[VADC_NTSC];
+
+ sd = &state->sd;
+
+ /* map vafe address */
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, VAFE_REGS_ADDR_RES_NAME);
+ if (!res) {
+ dev_err(&pdev->dev, "No vafe base address found.\n");
+ return -ENOMEM;
+ }
+ vafe_regbase = devm_ioremap_resource(&pdev->dev, res);
+ if (!vafe_regbase) {
+ dev_err(&pdev->dev, "ioremap failed with vafe base\n");
+ return -ENOMEM;
+ }
+
+ /* map vdec address */
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, VDEC_REGS_ADDR_RES_NAME);
+ if (!res) {
+ dev_err(&pdev->dev, "No vdec base address found.\n");
+ return -ENODEV;
+ }
+ vdec_regbase = devm_ioremap_resource(&pdev->dev, res);
+ if (!vdec_regbase) {
+ dev_err(&pdev->dev, "ioremap failed with vdec base\n");
+ return -ENOMEM;
+ }
+
+ /* Get clock */
+ state->vadc_clk = devm_clk_get(&pdev->dev, "vadc");
+ if (IS_ERR(state->vadc_clk)) {
+ ret = PTR_ERR(state->vadc_clk);
+ return ret;
+ }
+
+ state->csi_clk = devm_clk_get(&pdev->dev, "csi");
+ if (IS_ERR(state->csi_clk)) {
+ ret = PTR_ERR(state->csi_clk);
+ return ret;
+ }
+
+ /* clock */
+ clk_prepare_enable(state->csi_clk);
+ clk_prepare_enable(state->vadc_clk);
+
+ platform_set_drvdata(pdev, state);
+
+ vadc_v4l2_subdev_init(sd, pdev, &vadc_ops);
+
+ pm_runtime_enable(&pdev->dev);
+
+ pm_runtime_get_sync(&pdev->dev);
+ /* Init VADC */
+ ret = vadc_of_init(pdev);
+ if (ret < 0)
+ goto err;
+ vadc_init(state);
+
+ pr_info("vadc driver loaded\n");
+
+ return 0;
+err:
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ v4l2_async_unregister_subdev(&state->sd);
+ clk_disable_unprepare(state->csi_clk);
+ clk_disable_unprepare(state->vadc_clk);
+ return ret;
+}
+
+static int vadc_remove(struct platform_device *pdev)
+{
+ struct vadc_state *state = platform_get_drvdata(pdev);
+
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ v4l2_async_unregister_subdev(&state->sd);
+ clk_disable_unprepare(state->csi_clk);
+ clk_disable_unprepare(state->vadc_clk);
+
+ vadc_power_down(state);
+ return true;
+}
+
+static int vadc_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct vadc_state *state = platform_get_drvdata(pdev);
+
+ clk_disable(state->csi_clk);
+ clk_disable(state->vadc_clk);
+
+ vadc_power_down(state);
+
+ return 0;
+}
+
+static int vadc_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct vadc_state *state = platform_get_drvdata(pdev);
+
+ clk_enable(state->csi_clk);
+ clk_enable(state->vadc_clk);
+
+ vadc_init(state);
+ return 0;
+}
+
+static const struct dev_pm_ops vadc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(vadc_suspend, vadc_resume)
+};
+
+static struct platform_driver vadc_driver = {
+ .driver = {
+ .name = "fsl_vadc",
+ .of_match_table = of_match_ptr(fsl_vadc_dt_ids),
+ .pm = &vadc_pm_ops,
+ },
+ .probe = vadc_probe,
+ .remove = vadc_remove,
+};
+
+module_platform_driver(vadc_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("fsl VADC/VDEC driver");
+MODULE_LICENSE("GPL");