/* * tda1997x.c -- MXC video capture driver for i.MX boards with * tda1997x HDMI receiver * * Copyright (C) 2013 Gateworks Corporation * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* The tda1997x-core driver manages the i2c interface with the device. * This driver merely calls into that driver for current video parameters * when necessary. The platform data for the core driver contains the * video output bus configuration that configures the video port * mapping between the tda1997x and a i.MX6 CSI parallel bus for 8bit * bt656 with embedded syncs as this is the only video format compatible * between the tda1997x and the i.MX6 CSI. * * see for details */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "v4l2-int-device.h" #include "mxc_v4l2_capture.h" /*! * Maintains the information on the current state of the sensor. */ struct sensor { struct sensor_data sen; tda1997x_videofmt_t vidfmt; } tda1997x_data; /*********************************************************************** * mxc_v4l2_capture interface. ***********************************************************************/ /*********************************************************************** * IOCTL Functions from v4l2_int_ioctl_desc. ***********************************************************************/ /*! * ioctl_g_ifparm - V4L2 sensor interface handler for vidioc_int_g_ifparm_num * s: pointer to standard V4L2 device structure * p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure * * Gets slave interface parameters. * Calculates the required xclk value to support the requested * clock parameters in p. This value is returned in the p * parameter. * * vidioc_int_g_ifparm returns platform-specific information about the * interface settings used by the sensor. * * Called on open. */ static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) { struct sensor *sensor = s->priv; tda1997x_vidout_fmt_t fmt; if (s == NULL) { pr_err(" ERROR!! no slave device set!\n"); return -ENODEV; } if (tda1997x_get_vidout_fmt(&fmt)) return -ENODEV; pr_debug("%s: %dx%d%c@%dfps\n", __func__, fmt.width, fmt.height, fmt.interlaced?'i':'p', fmt.fps); /* Initialize structure to 0s then set any non-0 values. */ memset(p, 0, sizeof(*p)); p->if_type = V4L2_IF_TYPE_BT656; /* This is the only possibility. */ if (sensor->vidfmt == VIDEOFMT_422_SMP) { /* YCbCr 4:2:2 semi-planar */ p->u.bt656.nobt_vs_inv = 0; p->u.bt656.nobt_hs_inv = 1; p->u.bt656.bt_sync_correct = 1; p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_16BIT; p->u.bt656.clock_curr = -1; /* gated clock mode */ } else if (sensor->vidfmt == VIDEOFMT_422_CCIR) { /* YCbCr 4:2:2 CCIR656 */ p->u.bt656.mode = V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT; p->u.bt656.clock_curr = (fmt.interlaced)?0:1; } return 0; } /*! * Sets the device power. * * s pointer to the device device * on if 1, power is to be turned on. 0 means power is to be turned off * * ioctl_s_power - V4L2 sensor interface handler for vidioc_int_s_power_num * @s: pointer to standard V4L2 device structure * @on: power state to which device is to be set * * Sets devices power state to requrested state, if possible. * This is called on open, close, suspend and resume. static int ioctl_s_power(struct v4l2_int_device *s, int on) { struct sensor *sensor = s->priv; if (on && !sensor->sen.on) { } else if (!on && sensor->sen.on) { } sensor->sen.on = on; return 0; } */ /*! * ioctl_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl * @s: pointer to standard V4L2 device structure * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure * * Returns the sensor's video CAPTURE parameters. */ static int ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) { struct sensor *sensor = s->priv; struct v4l2_captureparm *cparm = &a->parm.capture; switch (a->type) { /* These are all the possible cases. */ case V4L2_BUF_TYPE_VIDEO_CAPTURE: pr_debug(" type is V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); memset(a, 0, sizeof(*a)); a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; cparm->capability = sensor->sen.streamcap.capability; cparm->timeperframe = sensor->sen.streamcap.timeperframe; cparm->capturemode = sensor->sen.streamcap.capturemode; break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: case V4L2_BUF_TYPE_VIDEO_OVERLAY: case V4L2_BUF_TYPE_VBI_CAPTURE: case V4L2_BUF_TYPE_VBI_OUTPUT: case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: break; default: pr_debug("ioctl_g_parm:type is unknown %d\n", a->type); break; } return 0; } /*! * ioctl_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl * @s: pointer to standard V4L2 device structure * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure * * Configures the sensor to use the input parameters, if possible. If * not possible, reverts to the old parameters and returns the * appropriate error code. * * This driver cannot change these settings. */ static int ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) { switch (a->type) { /* These are all the possible cases. */ case V4L2_BUF_TYPE_VIDEO_CAPTURE: case V4L2_BUF_TYPE_VIDEO_OUTPUT: case V4L2_BUF_TYPE_VIDEO_OVERLAY: case V4L2_BUF_TYPE_VBI_CAPTURE: case V4L2_BUF_TYPE_VBI_OUTPUT: case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: break; default: pr_debug(" type is unknown - %d\n", a->type); break; } return 0; } /*! * ioctl_g_fmt_cap - V4L2 sensor interface handler for ioctl_g_fmt_cap * @s: pointer to standard V4L2 device structure * @f: pointer to standard V4L2 v4l2_format structure * * Returns the sensor's current pixel format in the v4l2_format * parameter. */ static int ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) { struct sensor *sensor = s->priv; tda1997x_vidout_fmt_t fmt; if (tda1997x_get_vidout_fmt(&fmt)) return -ENODEV; pr_debug("%s: %dx%d%c@%dfps\n", __func__, fmt.width, fmt.height, fmt.interlaced?'i':'p', fmt.fps); sensor->sen.pix.height = fmt.height; sensor->sen.pix.width = fmt.width; sensor->sen.streamcap.timeperframe.denominator = fmt.fps; sensor->sen.streamcap.timeperframe.numerator = 1; switch (f->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: pr_debug(" Returning size of %dx%d\n", sensor->sen.pix.width, sensor->sen.pix.height); f->fmt.pix = sensor->sen.pix; pr_debug(" Returning format of %s\n", (char*)&f->fmt.pix.pixelformat); break; case V4L2_BUF_TYPE_PRIVATE: { } break; default: f->fmt.pix = sensor->sen.pix; break; } return 0; } /*! * ioctl_enum_framesizes - V4L2 sensor interface handler for * VIDIOC_ENUM_FRAMESIZES ioctl * @s: pointer to standard V4L2 device structure * @fsize: standard V4L2 VIDIOC_ENUM_FRAMESIZES ioctl structure * * Return 0 if successful, otherwise -EINVAL. */ static int ioctl_enum_framesizes(struct v4l2_int_device *s, struct v4l2_frmsizeenum *fsize) { struct sensor *sensor = s->priv; tda1997x_vidout_fmt_t fmt; if (fsize->index >= 1) return -EINVAL; if (tda1997x_get_vidout_fmt(&fmt)) return -ENODEV; pr_debug("%s: %dx%d%c@%dfps\n", __func__, fmt.height, fmt.width, fmt.interlaced?'i':'p', fmt.fps); sensor->sen.pix.height = fmt.height; sensor->sen.pix.width = fmt.width; fsize->discrete.width = sensor->sen.pix.width; fsize->discrete.height = sensor->sen.pix.height; return 0; } /*! * ioctl_g_chip_ident - V4L2 sensor interface handler for * VIDIOC_DBG_G_CHIP_IDENT ioctl * @s: pointer to standard V4L2 device structure * @id: pointer to int * * Return 0. */ static int ioctl_g_chip_ident(struct v4l2_int_device *s, int *id) { struct sensor *sensor = s->priv; ((struct v4l2_dbg_chip_ident *)id)->match.type = V4L2_CHIP_MATCH_I2C_DRIVER; if (sensor->vidfmt == VIDEOFMT_422_SMP) { /* YCbCr 4:2:2 semi-planar */ strcpy(((struct v4l2_dbg_chip_ident *)id)->match.name, "tda1997x_decoder_yuv422"); } else strcpy(((struct v4l2_dbg_chip_ident *)id)->match.name, "tda1997x_decoder_bt656"); ((struct v4l2_dbg_chip_ident *)id)->ident = V4L2_IDENT_TDA19971; return 0; } /*! * This structure defines all the ioctls for this module. */ static struct v4l2_int_ioctl_desc tda1997x_ioctl_desc[] = { /* {vidioc_int_s_power_num, (v4l2_int_ioctl_func*)ioctl_s_power}, */ {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func*)ioctl_g_ifparm}, {vidioc_int_g_fmt_cap_num, (v4l2_int_ioctl_func*)ioctl_g_fmt_cap}, /*! * If the requested format is supported, configures the HW to use that * format, returns error code if format not supported or HW can't be * correctly configured. */ {vidioc_int_g_parm_num, (v4l2_int_ioctl_func*)ioctl_g_parm}, {vidioc_int_s_parm_num, (v4l2_int_ioctl_func*)ioctl_s_parm}, {vidioc_int_enum_framesizes_num, (v4l2_int_ioctl_func *) ioctl_enum_framesizes}, {vidioc_int_g_chip_ident_num, (v4l2_int_ioctl_func *)ioctl_g_chip_ident}, }; static struct v4l2_int_slave tda1997x_slave = { .ioctls = tda1997x_ioctl_desc, .num_ioctls = ARRAY_SIZE(tda1997x_ioctl_desc), }; static struct v4l2_int_device tda1997x_int_device = { .module = THIS_MODULE, .name = "tda1997x-video", .type = v4l2_int_type_slave, .u = { .slave = &tda1997x_slave, }, }; static int tda1997x_video_probe(struct platform_device *pdev) { struct sensor *sens = &tda1997x_data; struct pinctrl *pinctrl; struct device *dev = &pdev->dev; struct regmap *gpr; tda1997x_vidout_fmt_t fmt; int ret; dev_dbg(dev, "%s\n", __func__); /* pinctrl */ pinctrl = devm_pinctrl_get_select_default(dev); if (IS_ERR(pinctrl)) { dev_err(dev, "setup pinctrl failed\n"); return PTR_ERR(pinctrl); } /* Set initial values for the sensor struct. */ memset(sens, 0, sizeof(tda1997x_data)); if (tda1997x_get_vidout_fmt(&fmt)) return -ENODEV; sens->vidfmt = fmt.sensor_vidfmt; sens->sen.streamcap.timeperframe.denominator = 0; sens->sen.streamcap.timeperframe.numerator = 0; sens->sen.pix.width = 0; sens->sen.pix.height = 0; if (sens->vidfmt == VIDEOFMT_422_SMP) sens->sen.pix.pixelformat = IPU_PIX_FMT_GENERIC_16; else sens->sen.pix.pixelformat = V4L2_PIX_FMT_UYVY; sens->sen.on = true; ret = of_property_read_u32(dev->of_node, "csi_id", &(sens->sen.csi)); if (ret) { dev_err(dev, "csi_id invalid\n"); return ret; } ret = of_property_read_u32(dev->of_node, "ipu_id", &(sens->sen.ipu_id)); if (ret) { dev_err(dev, "ipu_id invalid\n"); return ret; } gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); if (!IS_ERR(gpr)) { if (of_machine_is_compatible("fsl,imx6q")) { int mask = sens->sen.csi ? (1 << 20) : (1 << 19); regmap_update_bits(gpr, IOMUXC_GPR1, mask, mask); } else if (of_machine_is_compatible("fsl,imx6dl")) { int mask = sens->sen.csi ? (7 << 3) : (7 << 0); int val = sens->sen.csi ? (4 << 3) : (4 << 0); regmap_update_bits(gpr, IOMUXC_GPR13, mask, val); } } else { pr_err("%s: failed to find fsl,imx6q-iomux-gpr regmap\n", __func__); } dev_dbg(dev, "IPU%d_CSI%d\n", sens->sen.ipu_id + 1, sens->sen.csi); dev_dbg(dev, "type is %d (expect %d)\n", tda1997x_int_device.type, v4l2_int_type_slave); dev_dbg(dev, "num ioctls is %d\n", tda1997x_int_device.u.slave->num_ioctls); /* This function attaches this structure to the /dev/video device */ tda1997x_int_device.priv = sens; ret = v4l2_int_device_register(&tda1997x_int_device); return ret; } static int tda1997x_video_remove(struct platform_device *pdev) { v4l2_int_device_unregister(&tda1997x_int_device); return 0; } static const struct of_device_id tda1997x_video_driver_ids[] = { { .compatible = "fsl,imx-tda1997x-video", }, { /* sentinel */ } }; static struct platform_driver tda1997x_video_driver = { .driver = { .owner = THIS_MODULE, .name = "imx-tda1997x-video", .of_match_table = tda1997x_video_driver_ids, }, .probe = tda1997x_video_probe, .remove = tda1997x_video_remove, }; module_platform_driver(tda1997x_video_driver); MODULE_AUTHOR("Tim Harvey "); MODULE_DESCRIPTION("TDA1997X hdmi receiver MXC video capture driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:imx-tda1997x-video");