/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_fmt(struct v4l2_subdev *sd, unsigned index, enum v4l2_mbus_pixelcode *code) { /* support only one format */ if (index >= 1) return -EINVAL; *code = V4L2_MBUS_FMT_AYUV8_1X32; return 0; } static int vadc_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) { struct vadc_state *state = to_state(sd); fmt->code = V4L2_MBUS_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_enum_framesizes(struct v4l2_subdev *sd, struct v4l2_frmsizeenum *fsize) { struct vadc_state *state = to_state(sd); if (fsize->index >= 1) return -EINVAL; fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; fsize->discrete.width = state->fmt->active_width; fsize->discrete.height = state->fmt->active_height; return 0; } static int vadc_enum_frameintervals(struct v4l2_subdev *sd, struct v4l2_frmivalenum *fival) { struct vadc_state *state = to_state(sd); if (fival->index < 0 || fival->index >= 1) return -EINVAL; fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; fival->discrete.numerator = 1; fival->discrete.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, .enum_mbus_fmt = vadc_enum_mbus_fmt, .try_mbus_fmt = vadc_mbus_fmt, .g_mbus_fmt = vadc_mbus_fmt, .s_mbus_fmt = vadc_mbus_fmt, .enum_framesizes = vadc_enum_framesizes, .enum_frameintervals = vadc_enum_frameintervals, .s_parm = vadc_s_parm, }; static const struct v4l2_subdev_core_ops vadc_core_ops = { .g_std = vadc_g_std, }; static const struct v4l2_subdev_ops vadc_ops = { .core = &vadc_core_ops, .video = &vadc_video_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");