diff options
Diffstat (limited to 'drivers/media/platform/mxc/capture/mxc_vadc.c')
-rw-r--r-- | drivers/media/platform/mxc/capture/mxc_vadc.c | 830 |
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"); |