summaryrefslogtreecommitdiff
path: root/drivers/media/platform/imx8/hdmi/mxc-hdmi-rx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/imx8/hdmi/mxc-hdmi-rx.c')
-rw-r--r--drivers/media/platform/imx8/hdmi/mxc-hdmi-rx.c874
1 files changed, 874 insertions, 0 deletions
diff --git a/drivers/media/platform/imx8/hdmi/mxc-hdmi-rx.c b/drivers/media/platform/imx8/hdmi/mxc-hdmi-rx.c
new file mode 100644
index 000000000000..6cd53c929d88
--- /dev/null
+++ b/drivers/media/platform/imx8/hdmi/mxc-hdmi-rx.c
@@ -0,0 +1,874 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * 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.
+ */
+
+#include "mxc-hdmi-rx.h"
+#include "API_AFE_ss28fdsoi_hdmirx.h"
+
+#define MXC_HDMI_DRIVER_NAME "iMX HDMI RX"
+#define MXC_HDMI_MIN_WIDTH 640
+#define MXC_HDMI_MAX_WIDTH 3840
+#define MXC_HDMI_MIN_HEIGHT 480
+#define MXC_HDMI_MAX_HEIGHT 2160
+
+/* V4L2_DV_BT_CEA_640X480P59_94 */
+#define MXC_HDMI_MIN_PIXELCLOCK 24000000
+/* V4L2_DV_BT_CEA_3840X2160P30 */
+#define MXC_HDMI_MAX_PIXELCLOCK 297000000
+
+#define imx_sd_to_hdmi(sd) container_of(sd, struct mxc_hdmi_rx_dev, sd)
+
+static const struct v4l2_dv_timings_cap mxc_hdmi_timings_cap = {
+ .type = V4L2_DV_BT_656_1120,
+ /* keep this initialization for compatibility with GCC < 4.4.6 */
+ .reserved = { 0 },
+
+ V4L2_INIT_BT_TIMINGS(MXC_HDMI_MIN_WIDTH, MXC_HDMI_MAX_WIDTH,
+ MXC_HDMI_MIN_HEIGHT, MXC_HDMI_MAX_HEIGHT,
+ MXC_HDMI_MIN_PIXELCLOCK,
+ MXC_HDMI_MAX_PIXELCLOCK,
+ V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT,
+ V4L2_DV_BT_CAP_PROGRESSIVE)
+};
+
+struct mxc_hdmi_rx_dev_video_standards
+mxc_hdmi_video_standards[] = {
+ {V4L2_DV_BT_CEA_640X480P59_94, 1, 0, 60},
+ {V4L2_DV_BT_CEA_720X480P59_94, 2, 0, 60},
+ {V4L2_DV_BT_CEA_720X480P59_94, 3, 0, 60},
+ {V4L2_DV_BT_CEA_720X576P50, 18, 0, 50},
+ {V4L2_DV_BT_CEA_720X576P50, 17, 0, 50},
+ {V4L2_DV_BT_CEA_1280X720P60, 4, 0, 60},
+ {V4L2_DV_BT_CEA_1280X720P50, 19, 0, 50},
+ {V4L2_DV_BT_CEA_1280X720P30, 62, 0, 30},
+ {V4L2_DV_BT_CEA_1280X720P25, 61, 0, 25},
+ {V4L2_DV_BT_CEA_1280X720P24, 60, 0, 24},
+ {V4L2_DV_BT_CEA_1920X1080P60, 16, 0, 60},
+ {V4L2_DV_BT_CEA_1920X1080P50, 31, 0, 50},
+ {V4L2_DV_BT_CEA_1920X1080P30, 34, 0, 30},
+ {V4L2_DV_BT_CEA_1920X1080P25, 33, 0, 25},
+ {V4L2_DV_BT_CEA_1920X1080P24, 32, 0, 24},
+ {V4L2_DV_BT_CEA_3840X2160P24, 93, 3, 24},
+ {V4L2_DV_BT_CEA_3840X2160P25, 94, 2, 25},
+ {V4L2_DV_BT_CEA_3840X2160P30, 95, 1, 30},
+ {V4L2_DV_BT_CEA_4096X2160P24, 98, 4, 24},
+ {V4L2_DV_BT_CEA_4096X2160P25, 99, 0, 25},
+ {V4L2_DV_BT_CEA_4096X2160P30, 100, 0, 30},
+ /* SVGA */
+ {V4L2_DV_BT_DMT_800X600P56, 0x0, 0, 56},
+ {V4L2_DV_BT_DMT_800X600P60, 0x0, 0, 60},
+ {V4L2_DV_BT_DMT_800X600P72, 0x0, 0, 72},
+ {V4L2_DV_BT_DMT_800X600P75, 0x0, 0, 75},
+ {V4L2_DV_BT_DMT_800X600P85, 0x0, 0, 85},
+ /* SXGA */
+ {V4L2_DV_BT_DMT_1280X1024P60, 0x0, 0, 60},
+ {V4L2_DV_BT_DMT_1280X1024P75, 0x0, 0, 75},
+ /* VGA */
+ { V4L2_DV_BT_DMT_640X480P72, 0x0, 0, 72},
+ { V4L2_DV_BT_DMT_640X480P75, 0x0, 0, 75},
+ { V4L2_DV_BT_DMT_640X480P85, 0x0, 0, 85},
+ /* XGA */
+ { V4L2_DV_BT_DMT_1024X768P60, 0x0, 0, 60},
+ { V4L2_DV_BT_DMT_1024X768P70, 0x0, 0, 70},
+ { V4L2_DV_BT_DMT_1024X768P75, 0x0, 0, 75},
+ { V4L2_DV_BT_DMT_1024X768P85, 0x0, 0, 85},
+ /* UXGA */
+ { V4L2_DV_BT_DMT_1600X1200P60, 0x0, 0, 60},
+};
+
+int mxc_hdmi_frame_timing(struct mxc_hdmi_rx_dev *hdmi_rx)
+{
+ struct mxc_hdmi_rx_dev_video_standards *stds;
+ u32 i, vic, hdmi_vic;
+
+ vic = hdmi_rx->vic_code;
+ hdmi_vic = hdmi_rx->hdmi_vic;
+ stds = mxc_hdmi_video_standards;
+
+ if (vic == 0 && hdmi_vic != 0) {
+ for (i = 0; i < ARRAY_SIZE(mxc_hdmi_video_standards); i++) {
+ if (stds[i].hdmi_vic == hdmi_vic) {
+ hdmi_rx->timings = &stds[i];
+ return true;
+ }
+ }
+ } else if (vic > 109) {
+ dev_err(&hdmi_rx->pdev->dev,
+ "Unsupported mode vic=%d, hdmi_vic=%d\n", vic, hdmi_vic);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mxc_hdmi_video_standards); i++) {
+ if (stds[i].vic == vic) {
+ hdmi_rx->timings = &stds[i];
+ return true;
+ }
+ }
+
+ if (i >= ARRAY_SIZE(mxc_hdmi_video_standards))
+ return -EINVAL;
+
+ return true;
+}
+
+static int mxc_hdmi_clock_init(struct mxc_hdmi_rx_dev *hdmi_rx)
+{
+ struct device *dev = &hdmi_rx->pdev->dev;
+
+ hdmi_rx->ref_clk = devm_clk_get(dev, "ref_clk");
+ if (IS_ERR(hdmi_rx->ref_clk)) {
+ dev_err(dev, "failed to get hdmi rx ref clk\n");
+ return PTR_ERR(hdmi_rx->ref_clk);
+ }
+
+ hdmi_rx->pxl_clk = devm_clk_get(dev, "pxl_clk");
+ if (IS_ERR(hdmi_rx->pxl_clk)) {
+ dev_err(dev, "failed to get hdmi rx pxl clk\n");
+ return PTR_ERR(hdmi_rx->pxl_clk);
+ }
+ hdmi_rx->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(hdmi_rx->pclk)) {
+ dev_err(dev, "failed to get hdmi rx pclk\n");
+ return PTR_ERR(hdmi_rx->pclk);
+ }
+
+ hdmi_rx->sclk = devm_clk_get(dev, "sclk");
+ if (IS_ERR(hdmi_rx->sclk)) {
+ dev_err(dev, "failed to get hdmi rx sclk\n");
+ return PTR_ERR(hdmi_rx->sclk);
+ }
+
+ hdmi_rx->enc_clk = devm_clk_get(dev, "enc_clk");
+ if (IS_ERR(hdmi_rx->enc_clk)) {
+ dev_err(dev, "failed to get hdmi rx enc clk\n");
+ return PTR_ERR(hdmi_rx->enc_clk);
+ }
+
+ hdmi_rx->spdif_clk = devm_clk_get(dev, "spdif_clk");
+ if (IS_ERR(hdmi_rx->spdif_clk)) {
+ dev_err(dev, "failed to get hdmi rx spdif clk\n");
+ return PTR_ERR(hdmi_rx->spdif_clk);
+ }
+
+ hdmi_rx->pxl_link_clk = devm_clk_get(dev, "pxl_link_clk");
+ if (IS_ERR(hdmi_rx->pxl_link_clk)) {
+ dev_err(dev, "failed to get hdmi rx pixel link clk\n");
+ return PTR_ERR(hdmi_rx->pxl_link_clk);
+ }
+
+ return 0;
+}
+
+static int mxc_hdmi_clock_enable(struct mxc_hdmi_rx_dev *hdmi_rx)
+{
+ struct device *dev = &hdmi_rx->pdev->dev;
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+ ret = clk_prepare_enable(hdmi_rx->ref_clk);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre ref_clk error %d\n", __func__, ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(hdmi_rx->pxl_clk);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre pxl_clk error %d\n", __func__, ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(hdmi_rx->enc_clk);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre enc_clk error %d\n", __func__, ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(hdmi_rx->sclk);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre sclk error %d\n", __func__, ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(hdmi_rx->pclk);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre pclk error %d\n", __func__, ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(hdmi_rx->spdif_clk);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre spdif_clk error %d\n", __func__, ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(hdmi_rx->pxl_link_clk);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre pxl_link_clk error %d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mxc_hdmi_clock_disable(struct mxc_hdmi_rx_dev *hdmi_rx)
+{
+ dev_dbg(&hdmi_rx->pdev->dev, "%s\n", __func__);
+
+ clk_disable_unprepare(hdmi_rx->ref_clk);
+ clk_disable_unprepare(hdmi_rx->pxl_clk);
+ clk_disable_unprepare(hdmi_rx->enc_clk);
+ clk_disable_unprepare(hdmi_rx->sclk);
+ clk_disable_unprepare(hdmi_rx->pclk);
+ clk_disable_unprepare(hdmi_rx->spdif_clk);
+ clk_disable_unprepare(hdmi_rx->pxl_link_clk);
+}
+
+static void imx8qm_pixel_link_encoder(struct mxc_hdmi_rx_dev *hdmi_rx)
+{
+ u32 val;
+
+ switch (hdmi_rx->pixel_encoding) {
+ case PIXEL_ENCODING_YUV422:
+ val = 3;
+ break;
+ case PIXEL_ENCODING_YUV420:
+ val = 4;
+ break;
+ case PIXEL_ENCODING_YUV444:
+ val = 2;
+ break;
+ case PIXEL_ENCODING_RGB:
+ val = 0;
+ break;
+ default:
+ val = 0x6;
+ }
+
+ /* HDMI RX H/Vsync Polarity */
+ if (hdmi_rx->timings->timings.bt.polarities & V4L2_DV_VSYNC_POS_POL)
+ val |= 1 << PL_ENC_CTL_PXL_VCP;
+ if (hdmi_rx->timings->timings.bt.polarities & V4L2_DV_HSYNC_POS_POL)
+ val |= 1 << PL_ENC_CTL_PXL_HCP;
+
+ writel(val, hdmi_rx->mem.ss_base);
+}
+
+/* -----------------------------------------------------------------------------
+ * v4l2_subdev_video_ops
+ */
+
+static int mxc_hdmi_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a)
+{
+ struct v4l2_captureparm *cparm = &a->parm.capture;
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+ int ret = 0;
+
+ switch (a->type) {
+ /* This is the only case currently handled. */
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ memset(a, 0, sizeof(*a));
+ a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ cparm->timeperframe.denominator = hdmi_rx->timings->fps;
+ cparm->timeperframe.numerator = 1;
+ ret = 0;
+ break;
+
+ /* These are all the possible cases. */
+ 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:
+ ret = -EINVAL;
+ break;
+ default:
+ pr_debug(" type is unknown - %d\n", a->type);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int mxc_hdmi_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+ u32 val;
+
+ dev_dbg(&hdmi_rx->pdev->dev, "%s\n", __func__);
+ imx8qm_pixel_link_encoder(hdmi_rx);
+
+ if (enable) {
+ val = readl(hdmi_rx->mem.ss_base);
+ val |= 1 << PL_ENC_CTL_PXL_EN;
+ writel(val, hdmi_rx->mem.ss_base);
+ mdelay(17);
+
+ val = readl(hdmi_rx->mem.ss_base);
+ val |= 1 << PL_ENC_CTL_PXL_VAL;
+ writel(val, hdmi_rx->mem.ss_base);
+ } else {
+ val = readl(hdmi_rx->mem.ss_base);
+ val |= ~(1 << PL_ENC_CTL_PXL_VAL);
+ writel(val, hdmi_rx->mem.ss_base);
+ mdelay(17);
+
+ val = readl(hdmi_rx->mem.ss_base);
+ val |= ~(1 << PL_ENC_CTL_PXL_EN);
+ writel(val, hdmi_rx->mem.ss_base);
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops imx_video_ops_hdmi = {
+ .s_stream = mxc_hdmi_s_stream,
+ .g_parm = mxc_hdmi_g_parm,
+};
+
+/* -----------------------------------------------------------------------------
+ * Media Operations
+ */
+
+static const struct media_entity_operations hdmi_media_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/* -----------------------------------------------------------------------------
+ * v4l2_subdev_pad_ops
+ */
+static int mxc_hdmi_enum_framesizes(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+
+ if (fse->index > 1)
+ return -EINVAL;
+
+ fse->min_width = hdmi_rx->timings->timings.bt.width;
+ fse->max_width = fse->min_width;
+
+ fse->min_height = hdmi_rx->timings->timings.bt.height;
+ fse->max_height = fse->min_height;
+ return 0;
+}
+static int mxc_hdmi_enum_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+
+ if (fie->index < 0 || fie->index > 8)
+ return -EINVAL;
+
+ if (fie->width == 0 || fie->height == 0 ||
+ fie->code == 0) {
+ pr_warning("Please assign pixel format, width and height.\n");
+ return -EINVAL;
+ }
+
+ fie->interval.numerator = 1;
+
+ /* TODO Reserved to extension */
+ fie->interval.denominator = hdmi_rx->timings->fps;
+ return 0;
+}
+
+static int mxc_hdmi_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index != 0)
+ return -EINVAL;
+
+ code->code = MEDIA_BUS_FMT_RGB888_1X24;
+
+ return 0;
+}
+
+static int mxc_hdmi_get_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *sdformat)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+ struct v4l2_mbus_framefmt *mbusformat = &sdformat->format;
+
+ if (sdformat->pad != MXC_HDMI_RX_PAD_SOURCE)
+ return -EINVAL;
+
+ switch (hdmi_rx->pixel_encoding) {
+ case PIXEL_ENCODING_YUV422:
+ mbusformat->code = MEDIA_BUS_FMT_YUYV8_1X16;
+ break;
+ case PIXEL_ENCODING_YUV420:
+ mbusformat->code = MEDIA_BUS_FMT_UV8_1X8;
+ break;
+ case PIXEL_ENCODING_YUV444:
+ mbusformat->code = MEDIA_BUS_FMT_AYUV8_1X32;
+ break;
+ default:
+ mbusformat->code = MEDIA_BUS_FMT_RGB888_1X24;
+ }
+
+ mbusformat->width = hdmi_rx->timings->timings.bt.width;
+ mbusformat->height = hdmi_rx->timings->timings.bt.height;
+ mbusformat->colorspace = V4L2_COLORSPACE_JPEG;
+
+ return 0;
+}
+
+static int mxc_hdmi_get_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+
+ memset(edid->reserved, 0, sizeof(edid->reserved));
+
+ if (!hdmi_rx->edid.present)
+ return -ENODATA;
+
+ if (edid->start_block == 0 && edid->blocks == 0) {
+ edid->blocks = hdmi_rx->edid.blocks;
+ return 0;
+ }
+
+ if (edid->start_block >= hdmi_rx->edid.blocks)
+ return -EINVAL;
+
+ if (edid->start_block + edid->blocks > hdmi_rx->edid.blocks)
+ edid->blocks = hdmi_rx->edid.blocks - edid->start_block;
+
+ memcpy(edid->edid, hdmi_rx->edid.edid + edid->start_block * 128,
+ edid->blocks * 128);
+
+ return 0;
+}
+
+static int mxc_hdmi_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+ state_struct *state = &hdmi_rx->state;
+ int err, i;
+
+ memset(edid->reserved, 0, sizeof(edid->reserved));
+
+ if (edid->start_block != 0)
+ return -EINVAL;
+
+ if (edid->blocks == 0) {
+ /* Default EDID */
+ hdmi_rx->edid.blocks = 2;
+ hdmi_rx->edid.present = true;
+ } else if (edid->blocks > 4) {
+ edid->blocks = 4;
+ return -E2BIG;
+ } else {
+ memcpy(hdmi_rx->edid.edid, edid->edid, 128 * edid->blocks);
+ hdmi_rx->edid.blocks = edid->blocks;
+ hdmi_rx->edid.present = true;
+ }
+
+ for (i = 0; i < hdmi_rx->edid.blocks; i++) {
+ /* EDID block */
+ err = CDN_API_HDMIRX_SET_EDID_blocking(state, 0, i, &hdmi_rx->edid.edid[i * 128]);
+ if (err != CDN_OK) {
+ v4l2_err(sd, "error %d writing edid pad %d\n", err, edid->pad);
+ return -err;
+ }
+ }
+
+ return 0;
+}
+
+static bool mxc_hdmi_check_dv_timings(const struct v4l2_dv_timings *timings,
+ void *hdl)
+{
+ const struct mxc_hdmi_rx_dev_video_standards *stds =
+ mxc_hdmi_video_standards;
+ u32 i;
+
+ for (i = 0; stds[i].timings.bt.width; i++)
+ if (v4l2_match_dv_timings(timings, &stds[i].timings, 0, false))
+ return true;
+
+ return false;
+}
+
+static int mxc_hdmi_enum_dv_timings(struct v4l2_subdev *sd,
+ struct v4l2_enum_dv_timings *timings)
+{
+ return v4l2_enum_dv_timings_cap(timings, &mxc_hdmi_timings_cap,
+ mxc_hdmi_check_dv_timings, NULL);
+}
+
+static int mxc_hdmi_dv_timings_cap(struct v4l2_subdev *sd,
+ struct v4l2_dv_timings_cap *cap)
+{
+ *cap = mxc_hdmi_timings_cap;
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops imx_pad_ops_hdmi = {
+ .enum_mbus_code = mxc_hdmi_enum_mbus_code,
+ .enum_frame_size = mxc_hdmi_enum_framesizes,
+ .enum_frame_interval = mxc_hdmi_enum_frame_interval,
+ .get_fmt = mxc_hdmi_get_format,
+ .get_edid = mxc_hdmi_get_edid,
+ .set_edid = mxc_hdmi_set_edid,
+ .dv_timings_cap = mxc_hdmi_dv_timings_cap,
+ .enum_dv_timings = mxc_hdmi_enum_dv_timings,
+};
+
+static int mxc_hdmi_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd);
+ state_struct *state = &hdmi_rx->state;
+ struct device *dev = &hdmi_rx->pdev->dev;
+ u8 sts;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (on) {
+ pm_runtime_get_sync(dev);
+ hdmirx_startup(&hdmi_rx->state);
+ } else {
+ /* Reset HDMI RX PHY */
+ CDN_API_HDMIRX_Stop_blocking(state);
+ CDN_API_MainControl_blocking(state, 0, &sts);
+ pm_runtime_put_sync(dev);
+ }
+
+ return 0;
+}
+static struct v4l2_subdev_core_ops imx_core_ops_hdmi = {
+ .s_power = mxc_hdmi_s_power,
+};
+
+/* -----------------------------------------------------------------------------
+ * v4l2_subdev_ops
+ */
+static const struct v4l2_subdev_ops imx_ops_hdmi = {
+ .core = &imx_core_ops_hdmi,
+ .video = &imx_video_ops_hdmi,
+ .pad = &imx_pad_ops_hdmi,
+};
+
+void imx8qm_hdmi_phy_reset(state_struct *state, u8 reset)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = state_to_mxc_hdmirx(state);
+ sc_err_t sciErr;
+
+ dev_dbg(&hdmi_rx->pdev->dev, "%s\n", __func__);
+ /* set the pixel link mode and pixel type */
+ sciErr = sc_misc_set_control(hdmi_rx->ipcHndl, SC_R_HDMI_RX, SC_C_PHY_RESET, reset);
+ if (sciErr != SC_ERR_NONE)
+ DRM_ERROR("SC_R_HDMI PHY reset failed %d!\n", sciErr);
+}
+
+static int imx8qm_hdp_read(struct hdp_mem *mem, u32 addr, u32 *value)
+{
+ u32 temp;
+ void *tmp_addr = (addr & 0xfff) + mem->regs_base;
+ void *off_addr = 0x4 + mem->ss_base;;
+
+ __raw_writel(addr >> 12, off_addr);
+ temp = __raw_readl((volatile u32 *)tmp_addr);
+
+ *value = temp;
+ return 0;
+}
+
+static int imx8qm_hdp_write(struct hdp_mem *mem, u32 addr, u32 value)
+{
+ void *tmp_addr = (addr & 0xfff) + mem->regs_base;
+ void *off_addr = 0x4 + mem->ss_base;;
+
+ __raw_writel(addr >> 12, off_addr);
+
+ __raw_writel(value, (volatile u32 *) tmp_addr);
+
+ return 0;
+}
+
+static int imx8qm_hdp_sread(struct hdp_mem *mem, u32 addr, u32 *value)
+{
+ u32 temp;
+ void *tmp_addr = (addr & 0xfff) + mem->regs_base;
+ void *off_addr = 0xc + mem->ss_base;;
+
+ __raw_writel(addr >> 12, off_addr);
+
+ temp = __raw_readl((volatile u32 *)tmp_addr);
+ *value = temp;
+ return 0;
+}
+
+static int imx8qm_hdp_swrite(struct hdp_mem *mem, u32 addr, u32 value)
+{
+ void *tmp_addr = (addr & 0xfff) + mem->regs_base;
+ void *off_addr = 0xc + mem->ss_base;
+
+ __raw_writel(addr >> 12, off_addr);
+ __raw_writel(value, (volatile u32 *)tmp_addr);
+
+ return 0;
+}
+static struct hdp_rw_func imx8qm_rw = {
+ .read_reg = imx8qm_hdp_read,
+ .write_reg = imx8qm_hdp_write,
+ .sread_reg = imx8qm_hdp_sread,
+ .swrite_reg = imx8qm_hdp_swrite,
+};
+
+static void mxc_hdmi_state_init(struct mxc_hdmi_rx_dev *hdmi_rx)
+{
+ state_struct *state = &hdmi_rx->state;
+
+ memset(state, 0, sizeof(state_struct));
+ mutex_init(&state->mutex);
+
+ state->mem = &hdmi_rx->mem;
+ state->rw = &imx8qm_rw;
+}
+
+int mxc_hdmi_init(struct mxc_hdmi_rx_dev *hdmi_rx)
+{
+ sc_err_t sciErr;
+ u32 ret = 0;
+
+ dev_dbg(&hdmi_rx->pdev->dev, "%s\n", __func__);
+ mxc_hdmi_state_init(hdmi_rx);
+
+ sciErr = sc_ipc_getMuID(&hdmi_rx->mu_id);
+ if (sciErr != SC_ERR_NONE) {
+ DRM_ERROR("Cannot obtain MU ID\n");
+ return -EINVAL;
+ }
+
+ sciErr = sc_ipc_open(&hdmi_rx->ipcHndl, hdmi_rx->mu_id);
+ if (sciErr != SC_ERR_NONE) {
+ DRM_ERROR("sc_ipc_open failed! (sciError = %d)\n", sciErr);
+ return -EINVAL;
+ }
+
+ ret = hdmi_rx_init(&hdmi_rx->state);
+
+ return ret;
+}
+
+static int mxc_hdmi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mxc_hdmi_rx_dev *hdmi_rx;
+ struct resource *res;
+ int ret = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+ hdmi_rx = devm_kzalloc(dev, sizeof(*hdmi_rx), GFP_KERNEL);
+ if (!hdmi_rx)
+ return -ENOMEM;
+
+ hdmi_rx->pdev = pdev;
+
+ spin_lock_init(&hdmi_rx->slock);
+
+ /* register map */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hdmi_rx->mem.regs_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(hdmi_rx->mem.regs_base)) {
+ dev_err(dev, "Failed to get HDMI RX CTRL base register\n");
+ return -EINVAL;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ hdmi_rx->mem.ss_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(hdmi_rx->mem.ss_base)) {
+ dev_err(dev, "Failed to get HDMI RX CRS base register\n");
+ return -EINVAL;
+ }
+
+#if 0
+ /* TODO B0 */
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (res == NULL) {
+ dev_warn(dev, "Failed to get IRQ resource\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(dev, res->start, mxc_hdmi_irq_handler,
+ 0, dev_name(dev), mxc_hdmi);
+ if (ret < 0) {
+ dev_err(dev, "failed to install irq (%d)\n", ret);
+ return -EINVAL;
+ }
+#endif
+
+ v4l2_subdev_init(&hdmi_rx->sd, &imx_ops_hdmi);
+ /* sd.dev may use by match_of */
+ hdmi_rx->sd.dev = dev;
+
+ /* the owner is the same as the i2c_client's driver owner */
+ hdmi_rx->sd.owner = THIS_MODULE;
+ hdmi_rx->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ hdmi_rx->sd.entity.function = MEDIA_ENT_F_IO_DTV;
+
+ /* This allows to retrieve the platform device id by the host driver */
+ v4l2_set_subdevdata(&hdmi_rx->sd, pdev);
+
+ /* initialize name */
+ snprintf(hdmi_rx->sd.name, sizeof(hdmi_rx->sd.name), "%s",
+ MXC_HDMI_RX_SUBDEV_NAME);
+
+ hdmi_rx->pads[MXC_HDMI_RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ hdmi_rx->pads[MXC_HDMI_RX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ hdmi_rx->sd.entity.ops = &hdmi_media_ops;
+ ret = media_entity_pads_init(&hdmi_rx->sd.entity,
+ MXC_HDMI_RX_PADS_NUM, hdmi_rx->pads);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, hdmi_rx);
+ ret = v4l2_async_register_subdev(&hdmi_rx->sd);
+ if (ret < 0) {
+ dev_err(dev,
+ "%s--Async register failed, ret=%d\n", __func__, ret);
+ media_entity_cleanup(&hdmi_rx->sd.entity);
+ }
+
+ mxc_hdmi_clock_init(hdmi_rx);
+
+ hdmi_rx->flags = MXC_HDMI_RX_PM_POWERED;
+
+ mxc_hdmi_clock_enable(hdmi_rx);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+ ret = mxc_hdmi_init(hdmi_rx);
+ pm_runtime_put_sync(dev);
+ if (ret) {
+ dev_info(dev, "mxc hdmi rx init failed\n");
+ goto failed;
+ }
+
+ dev_info(dev, "mxc hdmi rx probe successfully\n");
+
+ return ret;
+failed:
+ v4l2_async_unregister_subdev(&hdmi_rx->sd);
+ media_entity_cleanup(&hdmi_rx->sd.entity);
+
+ mxc_hdmi_clock_disable(hdmi_rx);
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static int mxc_hdmi_remove(struct platform_device *pdev)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+ v4l2_async_unregister_subdev(&hdmi_rx->sd);
+ media_entity_cleanup(&hdmi_rx->sd.entity);
+
+ mxc_hdmi_clock_disable(hdmi_rx);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mxc_hdmi_pm_suspend(struct device *dev)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+ if ((hdmi_rx->flags & MXC_HDMI_RX_PM_SUSPENDED) ||
+ (hdmi_rx->flags & MXC_HDMI_RX_RUNTIME_SUSPEND))
+ return 0;
+
+ mxc_hdmi_clock_disable(hdmi_rx);
+ hdmi_rx->flags |= MXC_HDMI_RX_PM_SUSPENDED;
+ hdmi_rx->flags &= ~MXC_HDMI_RX_PM_POWERED;
+
+ return 0;
+}
+
+static int mxc_hdmi_pm_resume(struct device *dev)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = dev_get_drvdata(dev);
+ int ret;
+
+ dev_dbg(dev, "%s\n", __func__);
+ if (hdmi_rx->flags & MXC_HDMI_RX_PM_POWERED)
+ return 0;
+
+ hdmi_rx->flags |= MXC_HDMI_RX_PM_POWERED;
+ hdmi_rx->flags &= ~MXC_HDMI_RX_PM_SUSPENDED;
+
+ ret = mxc_hdmi_clock_enable(hdmi_rx);
+ return (ret) ? -EAGAIN : 0;
+}
+#endif
+
+static int mxc_hdmi_runtime_suspend(struct device *dev)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+ if (hdmi_rx->flags & MXC_HDMI_RX_RUNTIME_SUSPEND)
+ return 0;
+
+ if (hdmi_rx->flags & MXC_HDMI_RX_PM_POWERED) {
+ mxc_hdmi_clock_disable(hdmi_rx);
+ hdmi_rx->flags |= MXC_HDMI_RX_RUNTIME_SUSPEND;
+ hdmi_rx->flags &= ~MXC_HDMI_RX_PM_POWERED;
+ }
+ return 0;
+}
+
+static int mxc_hdmi_runtime_resume(struct device *dev)
+{
+ struct mxc_hdmi_rx_dev *hdmi_rx = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+ if (hdmi_rx->flags & MXC_HDMI_RX_PM_POWERED)
+ return 0;
+
+ if (hdmi_rx->flags & MXC_HDMI_RX_RUNTIME_SUSPEND) {
+ mxc_hdmi_clock_enable(hdmi_rx);
+ hdmi_rx->flags |= MXC_HDMI_RX_PM_POWERED;
+ hdmi_rx->flags &= ~MXC_HDMI_RX_RUNTIME_SUSPEND;
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops mxc_hdmi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mxc_hdmi_pm_suspend, mxc_hdmi_pm_resume)
+ SET_RUNTIME_PM_OPS(mxc_hdmi_runtime_suspend, mxc_hdmi_runtime_resume, NULL)
+};
+
+static const struct of_device_id mxc_hdmi_of_match[] = {
+ {.compatible = "fsl,imx-hdmi-rx",},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mxc_hdmi_of_match);
+
+static struct platform_driver mxc_hdmi_driver = {
+ .probe = mxc_hdmi_probe,
+ .remove = mxc_hdmi_remove,
+ .driver = {
+ .of_match_table = mxc_hdmi_of_match,
+ .name = MXC_HDMI_RX_DRIVER_NAME,
+ .pm = &mxc_hdmi_pm_ops,
+ }
+};
+
+module_platform_driver(mxc_hdmi_driver);