| /* |
| * 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 <linux/irq.h> |
| #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 void mxc_hdmi_cec_init(struct mxc_hdmi_rx_dev *hdmi_rx); |
| |
| 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->i2s_clk = devm_clk_get(dev, "i2s_clk"); |
| if (IS_ERR(hdmi_rx->i2s_clk)) { |
| dev_err(dev, "failed to get hdmi rx i2s clk\n"); |
| return PTR_ERR(hdmi_rx->i2s_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->i2s_clk); |
| if (ret < 0) { |
| dev_err(dev, "%s, pre i2s_clk 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->i2s_clk); |
| clk_disable_unprepare(hdmi_rx->spdif_clk); |
| clk_disable_unprepare(hdmi_rx->pxl_link_clk); |
| } |
| |
| static void mxc_hdmi_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_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a) |
| { |
| struct mxc_hdmi_rx_dev *hdmi_rx = imx_sd_to_hdmi(sd); |
| struct device *dev = &hdmi_rx->pdev->dev; |
| |
| dev_dbg(dev, "%s\n", __func__); |
| |
| return 0; |
| } |
| |
| 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; |
| |
| if (hdmi_rx->cable_plugin == false) { |
| dev_warn(&hdmi_rx->pdev->dev, "No Cable Connected!\n"); |
| return -EINVAL; |
| } |
| |
| 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__); |
| if (hdmi_rx->cable_plugin == false) { |
| dev_warn(&hdmi_rx->pdev->dev, "No Cable Connected!\n"); |
| return -EINVAL; |
| } |
| |
| mxc_hdmi_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, |
| .s_parm = mxc_hdmi_s_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 || hdmi_rx->cable_plugin == false) |
| 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 > 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 (hdmi_rx->cable_plugin == false) { |
| dev_warn(&hdmi_rx->pdev->dev, "No Cable Connected!\n"); |
| return -EINVAL; |
| } |
| |
| 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); |
| struct device *dev = &hdmi_rx->pdev->dev; |
| |
| dev_dbg(dev, "%s\n", __func__); |
| |
| 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; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0x4 + mem->ss_base; |
| __raw_writel(addr >> 12, off_addr); |
| temp = __raw_readl((volatile u32 *)tmp_addr); |
| |
| *value = temp; |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8qm_hdp_write(struct hdp_mem *mem, u32 addr, u32 value) |
| { |
| void *tmp_addr; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0x4 + mem->ss_base;; |
| __raw_writel(addr >> 12, off_addr); |
| |
| __raw_writel(value, (volatile u32 *) tmp_addr); |
| mutex_unlock(&mem->mutex); |
| |
| return 0; |
| } |
| |
| static int imx8qm_hdp_sread(struct hdp_mem *mem, u32 addr, u32 *value) |
| { |
| u32 temp; |
| void *tmp_addr; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0xc + mem->ss_base; |
| __raw_writel(addr >> 12, off_addr); |
| |
| temp = __raw_readl((volatile u32 *)tmp_addr); |
| *value = temp; |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8qm_hdp_swrite(struct hdp_mem *mem, u32 addr, u32 value) |
| { |
| void *tmp_addr; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0xc + mem->ss_base; |
| __raw_writel(addr >> 12, off_addr); |
| __raw_writel(value, (volatile u32 *)tmp_addr); |
| mutex_unlock(&mem->mutex); |
| |
| 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; |
| } |
| |
| #ifdef CONFIG_IMX_HDP_CEC |
| static void mxc_hdmi_cec_init(struct mxc_hdmi_rx_dev *hdmi_rx) |
| { |
| state_struct *state = &hdmi_rx->state; |
| struct imx_cec_dev *cec = &hdmi_rx->cec; |
| u32 clk_rate; |
| |
| memset(cec, 0, sizeof(struct imx_cec_dev)); |
| |
| CDN_API_GetClock(state, &clk_rate); |
| cec->clk_div = clk_rate * 10; |
| cec->dev = &hdmi_rx->pdev->dev; |
| cec->mem = &hdmi_rx->mem; |
| cec->rw = &imx8qm_rw; |
| } |
| #endif |
| |
| |
| int mxc_hdmi_init(struct mxc_hdmi_rx_dev *hdmi_rx) |
| { |
| sc_err_t sciErr; |
| |
| 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; |
| } |
| |
| |
| return 0; |
| } |
| |
| static void hpd5v_work_func(struct work_struct *work) |
| { |
| struct mxc_hdmi_rx_dev *hdmi_rx = container_of(work, struct mxc_hdmi_rx_dev, |
| hpd5v_work.work); |
| char event_string[32]; |
| char *envp[] = { event_string, NULL }; |
| u8 sts; |
| u8 hpd; |
| |
| /* Check cable states before enable irq */ |
| hdmirx_get_hpd_state(&hdmi_rx->state, &hpd); |
| if (hpd == 1) { |
| pr_info("HDMI RX Cable Plug In\n"); |
| |
| CDN_API_MainControl_blocking(&hdmi_rx->state, 1, &sts); |
| hdmirx_hotplug_trigger(&hdmi_rx->state); |
| hdmirx_startup(&hdmi_rx->state); |
| enable_irq(hdmi_rx->irq[HPD5V_IRQ_OUT]); |
| sprintf(event_string, "EVENT=hdmirxin"); |
| kobject_uevent_env(&hdmi_rx->pdev->dev.kobj, KOBJ_CHANGE, envp); |
| hdmi_rx->cable_plugin = true; |
| #ifdef CONFIG_IMX_HDP_CEC |
| if (hdmi_rx->is_cec) { |
| mxc_hdmi_cec_init(hdmi_rx); |
| imx_cec_register(&hdmi_rx->cec); |
| hdmi_rx->cec_running = true; |
| } |
| #endif |
| } else if (hpd == 0){ |
| pr_info("HDMI RX Cable Plug Out\n"); |
| hdmirx_stop(&hdmi_rx->state); |
| #ifdef CONFIG_IMX_HDP_CEC |
| if (hdmi_rx->is_cec && hdmi_rx->cec_running) { |
| imx_cec_unregister(&hdmi_rx->cec); |
| hdmi_rx->cec_running = false; |
| } |
| #endif |
| hdmirx_phy_pix_engine_reset(&hdmi_rx->state); |
| sprintf(event_string, "EVENT=hdmirxout"); |
| kobject_uevent_env(&hdmi_rx->pdev->dev.kobj, KOBJ_CHANGE, envp); |
| enable_irq(hdmi_rx->irq[HPD5V_IRQ_IN]); |
| CDN_API_MainControl_blocking(&hdmi_rx->state, 0, &sts); |
| hdmi_rx->cable_plugin = false; |
| } else |
| pr_warn("HDMI RX Cable State unknow\n"); |
| |
| } |
| |
| #define HOTPLUG_DEBOUNCE_MS 200 |
| static irqreturn_t mxc_hdp5v_irq_thread(int irq, void *data) |
| { |
| struct mxc_hdmi_rx_dev *hdmi_rx = data; |
| |
| disable_irq_nosync(irq); |
| |
| mod_delayed_work(system_wq, &hdmi_rx->hpd5v_work, |
| msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int mxc_hdmi_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct mxc_hdmi_rx_dev *hdmi_rx; |
| struct resource *res; |
| u8 hpd; |
| 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; |
| |
| mutex_init(&hdmi_rx->mem.mutex); |
| /* 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; |
| } |
| |
| hdmi_rx->irq[HPD5V_IRQ_IN] = platform_get_irq_byname(pdev, "plug_in"); |
| if (hdmi_rx->irq[HPD5V_IRQ_IN] < 0) |
| dev_info(&pdev->dev, "No plug_in irq number\n"); |
| |
| hdmi_rx->irq[HPD5V_IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out"); |
| if (hdmi_rx->irq[HPD5V_IRQ_OUT] < 0) |
| dev_info(&pdev->dev, "No plug_out irq number\n"); |
| |
| INIT_DELAYED_WORK(&hdmi_rx->hpd5v_work, hpd5v_work_func); |
| |
| |
| 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); |
| } |
| |
| hdmi_rx->is_cec = of_property_read_bool(pdev->dev.of_node, "fsl,cec"); |
| |
| 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); |
| if (ret < 0) { |
| dev_err(dev, "mxc hdmi init failed\n"); |
| goto failed; |
| } |
| ret = hdmirx_init(&hdmi_rx->state); |
| if (ret < 0) { |
| dev_err(dev, "mxc hdmi rx init failed\n"); |
| goto failed; |
| } |
| |
| /* Check cable states before enable irq */ |
| hdmirx_get_hpd_state(&hdmi_rx->state, &hpd); |
| |
| /* Enable Hotplug Detect IRQ thread */ |
| if (hdmi_rx->irq[HPD5V_IRQ_IN] > 0) { |
| irq_set_status_flags(hdmi_rx->irq[HPD5V_IRQ_IN], IRQ_NOAUTOEN); |
| ret = devm_request_threaded_irq(dev, hdmi_rx->irq[HPD5V_IRQ_IN], |
| NULL, mxc_hdp5v_irq_thread, |
| IRQF_ONESHOT, dev_name(dev), hdmi_rx); |
| if (ret) { |
| dev_err(&pdev->dev, "can't claim irq %d\n", |
| hdmi_rx->irq[HPD5V_IRQ_IN]); |
| goto failed; |
| } |
| /* Cable Disconnedted, enable Plug in IRQ */ |
| if (hpd == 0) { |
| enable_irq(hdmi_rx->irq[HPD5V_IRQ_IN]); |
| hdmi_rx->cable_plugin = false; |
| } |
| } |
| if (hdmi_rx->irq[HPD5V_IRQ_OUT] > 0) { |
| irq_set_status_flags(hdmi_rx->irq[HPD5V_IRQ_OUT], IRQ_NOAUTOEN); |
| ret = devm_request_threaded_irq(dev, hdmi_rx->irq[HPD5V_IRQ_OUT], |
| NULL, mxc_hdp5v_irq_thread, |
| IRQF_ONESHOT, dev_name(dev), hdmi_rx); |
| if (ret) { |
| dev_err(&pdev->dev, "can't claim irq %d\n", |
| hdmi_rx->irq[HPD5V_IRQ_OUT]); |
| goto failed; |
| } |
| if (hpd == 1) { |
| hdmirx_hotplug_trigger(&hdmi_rx->state); |
| hdmirx_startup(&hdmi_rx->state); |
| /* Cable Connected, enable Plug out IRQ */ |
| enable_irq(hdmi_rx->irq[HPD5V_IRQ_OUT]); |
| hdmi_rx->cable_plugin = true; |
| } |
| } |
| |
| mxc_hdmi_rx_register_audio_driver(dev); |
| |
| dev_info(dev, "iMX8 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); |
| dev_info(dev, "mxc hdmi rx probe failed\n"); |
| return ret; |
| } |
| |
| static int mxc_hdmi_remove(struct platform_device *pdev) |
| { |
| struct mxc_hdmi_rx_dev *hdmi_rx = platform_get_drvdata(pdev); |
| state_struct *state = &hdmi_rx->state; |
| struct device *dev = &pdev->dev; |
| u8 sts; |
| |
| dev_dbg(dev, "%s\n", __func__); |
| v4l2_async_unregister_subdev(&hdmi_rx->sd); |
| media_entity_cleanup(&hdmi_rx->sd.entity); |
| |
| #ifdef CONFIG_IMX_HDP_CEC |
| if (hdmi_rx->is_cec) |
| imx_cec_unregister(&hdmi_rx->cec); |
| #endif |
| |
| /* Reset HDMI RX PHY */ |
| CDN_API_HDMIRX_Stop_blocking(state); |
| CDN_API_MainControl_blocking(state, 0, &sts); |
| |
| mxc_hdmi_clock_disable(hdmi_rx); |
| pm_runtime_put_sync(dev); |
| 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); |