(vsync_pos ? 0 : VC4_HD_VID_CTL_VSYNC_LOW) |
(hsync_pos ? 0 : VC4_HD_VID_CTL_HSYNC_LOW));
- HDMI_WRITE(HDMI_VID_CTL,
- HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_BLANKPIX);
+ HDMI_WRITE(HDMI_VID_CTL,
+ HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_BLANKPIX);
+
+ if (vc4_encoder->hdmi_monitor) {
+ HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
+ HDMI_READ(HDMI_SCHEDULER_CONTROL) |
+ VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
+
+ spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+ ret = wait_for(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+ VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE, 1000);
+ WARN_ONCE(ret, "Timeout waiting for "
+ "VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
+ } else {
+ HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
+ HDMI_READ(HDMI_RAM_PACKET_CONFIG) &
+ ~(VC4_HDMI_RAM_PACKET_ENABLE));
+ HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
+ HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+ ~VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
+
+ spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+
+ ret = wait_for(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+ VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE), 1000);
+ WARN_ONCE(ret, "Timeout waiting for "
+ "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
+ }
+
+ if (vc4_encoder->hdmi_monitor) {
+ spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
+ WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
+ VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE));
+ HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
+ HDMI_READ(HDMI_SCHEDULER_CONTROL) |
+ VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT);
+
+ HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
+ VC4_HDMI_RAM_PACKET_ENABLE);
+
+ spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+ vc4_hdmi->output_enabled = true;
+
+ vc4_hdmi_set_infoframes(encoder);
+ }
+
+ vc4_hdmi_recenter_fifo(vc4_hdmi);
+ vc4_hdmi_enable_scrambling(encoder);
+
+ mutex_unlock(&vc4_hdmi->mutex);
+}
+
+static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
+ struct vc4_hdmi_connector_state *vc4_state =
+ conn_state_to_vc4_hdmi_conn_state(conn_state);
+
+ mutex_lock(&vc4_hdmi->mutex);
+ vc4_hdmi->output_bpc = vc4_state->output_bpc;
+ vc4_hdmi->output_format = vc4_state->output_format;
+ vc4_hdmi->broadcast_rgb = vc4_state->broadcast_rgb;
+ memcpy(&vc4_hdmi->saved_adjusted_mode,
+ &crtc_state->adjusted_mode,
+ sizeof(vc4_hdmi->saved_adjusted_mode));
+ mutex_unlock(&vc4_hdmi->mutex);
+}
+
+static bool
+vc4_hdmi_sink_supports_format_bpc(const struct vc4_hdmi *vc4_hdmi,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode,
+ unsigned int format, unsigned int bpc)
+{
+ struct drm_device *dev = vc4_hdmi->connector.dev;
+ u8 vic = drm_match_cea_mode(mode);
+
+ if (vic == 1 && bpc != 8) {
+ drm_dbg(dev, "VIC1 requires a bpc of 8, got %u\n", bpc);
+ return false;
+ }
+
+ if (!info->is_hdmi &&
+ (format != VC4_HDMI_OUTPUT_RGB || bpc != 8)) {
+ drm_dbg(dev, "DVI Monitors require an RGB output at 8 bpc\n");
+ return false;
+ }
+
+ switch (format) {
+ case VC4_HDMI_OUTPUT_RGB:
+ drm_dbg(dev, "RGB Format, checking the constraints.\n");
+
- if (bpc == 10 && !(info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30)) {
++ if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
+ drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
+ return false;
+ }
+
- if (bpc == 12 && !(info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_36)) {
++ if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
+ drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
+ return false;
+ }
+
+ drm_dbg(dev, "RGB format supported in that configuration.\n");
+
+ return true;
+
+ case VC4_HDMI_OUTPUT_YUV422:
+ drm_dbg(dev, "YUV422 format, checking the constraints.\n");
+
+ if (!(info->color_formats & DRM_COLOR_FORMAT_YCRCB422)) {
+ drm_dbg(dev, "Sink doesn't support YUV422.\n");
+ return false;
+ }
+
+ if (bpc != 12) {
+ drm_dbg(dev, "YUV422 only supports 12 bpc.\n");
+ return false;
+ }
+
+ drm_dbg(dev, "YUV422 format supported in that configuration.\n");
+
+ return true;
+
+ case VC4_HDMI_OUTPUT_YUV444:
+ drm_dbg(dev, "YUV444 format, checking the constraints.\n");
+
+ if (!(info->color_formats & DRM_COLOR_FORMAT_YCRCB444)) {
+ drm_dbg(dev, "Sink doesn't support YUV444.\n");
+ return false;
+ }
+
- if (bpc == 10 && !(info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_30)) {
++ if (bpc == 10 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30)) {
+ drm_dbg(dev, "10 BPC but sink doesn't support Deep Color 30.\n");
+ return false;
+ }
+
- if (bpc == 12 && !(info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_36)) {
++ if (bpc == 12 && !(info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_36)) {
+ drm_dbg(dev, "12 BPC but sink doesn't support Deep Color 36.\n");
+ return false;
+ }
+
+ drm_dbg(dev, "YUV444 format supported in that configuration.\n");
+
+ return true;
+ }
+
+ return false;
+}
+
+static enum drm_mode_status
+vc4_hdmi_encoder_clock_valid(const struct vc4_hdmi *vc4_hdmi,
+ unsigned long long clock)
+{
+ const struct drm_connector *connector = &vc4_hdmi->connector;
+ const struct drm_display_info *info = &connector->display_info;
+
+ if (clock > vc4_hdmi->variant->max_pixel_clock)
+ return MODE_CLOCK_HIGH;
+
+ if (vc4_hdmi->disable_4kp60 && clock > HDMI_14_MAX_TMDS_CLK)
+ return MODE_CLOCK_HIGH;
+
+ if (info->max_tmds_clock && clock > (info->max_tmds_clock * 1000))
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static unsigned long long
+vc4_hdmi_encoder_compute_mode_clock(const struct drm_display_mode *mode,
+ unsigned int bpc,
+ enum vc4_hdmi_output_format fmt)
+{
+ unsigned long long clock = mode->clock * 1000;
+
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+ clock = clock * 2;
+
+ if (fmt == VC4_HDMI_OUTPUT_YUV422)
+ bpc = 8;
+
+ return clock * bpc / 8;
+}
+
+static int
+vc4_hdmi_encoder_compute_clock(const struct vc4_hdmi *vc4_hdmi,
+ struct vc4_hdmi_connector_state *vc4_state,
+ const struct drm_display_mode *mode,
+ unsigned int bpc, unsigned int fmt)
+{
+ unsigned long long clock;
+
+ clock = vc4_hdmi_encoder_compute_mode_clock(mode, bpc, fmt);
+ if (vc4_hdmi_encoder_clock_valid(vc4_hdmi, clock) != MODE_OK)
+ return -EINVAL;
+
+ vc4_state->pixel_rate = clock;
+
+ return 0;
+}
+
+static int
+vc4_hdmi_encoder_compute_format(const struct vc4_hdmi *vc4_hdmi,
+ struct vc4_hdmi_connector_state *vc4_state,
+ const struct drm_display_mode *mode,
+ unsigned int bpc)
+{
+ struct drm_device *dev = vc4_hdmi->connector.dev;
+ const struct drm_connector *connector = &vc4_hdmi->connector;
+ const struct drm_display_info *info = &connector->display_info;
+ unsigned int format;
- if (vc4_encoder->hdmi_monitor) {
- HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
- HDMI_READ(HDMI_SCHEDULER_CONTROL) |
- VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
+ drm_dbg(dev, "Trying with an RGB output\n");
- ret = wait_for(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
- VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE, 1000);
- WARN_ONCE(ret, "Timeout waiting for "
- "VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
- } else {
- HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
- HDMI_READ(HDMI_RAM_PACKET_CONFIG) &
- ~(VC4_HDMI_RAM_PACKET_ENABLE));
- HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
- HDMI_READ(HDMI_SCHEDULER_CONTROL) &
- ~VC4_HDMI_SCHEDULER_CONTROL_MODE_HDMI);
+ format = VC4_HDMI_OUTPUT_RGB;
+ if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
+ int ret;
- ret = wait_for(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
- VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE), 1000);
- WARN_ONCE(ret, "Timeout waiting for "
- "!VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE\n");
+ ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
+ mode, bpc, format);
+ if (!ret) {
+ vc4_state->output_format = format;
+ return 0;
+ }
}
- if (vc4_encoder->hdmi_monitor) {
- WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) &
- VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE));
- HDMI_WRITE(HDMI_SCHEDULER_CONTROL,
- HDMI_READ(HDMI_SCHEDULER_CONTROL) |
- VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT);
+ drm_dbg(dev, "Failed, Trying with an YUV422 output\n");
- HDMI_WRITE(HDMI_RAM_PACKET_CONFIG,
- VC4_HDMI_RAM_PACKET_ENABLE);
+ format = VC4_HDMI_OUTPUT_YUV422;
+ if (vc4_hdmi_sink_supports_format_bpc(vc4_hdmi, info, mode, format, bpc)) {
+ int ret;
- vc4_hdmi_set_infoframes(encoder);
+ ret = vc4_hdmi_encoder_compute_clock(vc4_hdmi, vc4_state,
+ mode, bpc, format);
+ if (!ret) {
+ vc4_state->output_format = format;
+ return 0;
+ }
}
- vc4_hdmi_recenter_fifo(vc4_hdmi);
- vc4_hdmi_enable_scrambling(encoder);
+ drm_dbg(dev, "Failed. No Format Supported for that bpc count.\n");
+
+ return -EINVAL;
}
-static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder)
+static int
+vc4_hdmi_encoder_compute_config(const struct vc4_hdmi *vc4_hdmi,
+ struct vc4_hdmi_connector_state *vc4_state,
+ const struct drm_display_mode *mode)
{
+ struct drm_device *dev = vc4_hdmi->connector.dev;
+ struct drm_connector_state *conn_state = &vc4_state->base;
+ unsigned int max_bpc = clamp_t(unsigned int, conn_state->max_bpc, 8, 12);
+ unsigned int bpc;
+ int ret;
+
+ for (bpc = max_bpc; bpc >= 8; bpc -= 2) {
+ drm_dbg(dev, "Trying with a %d bpc output\n", bpc);
+
+ ret = vc4_hdmi_encoder_compute_format(vc4_hdmi, vc4_state,
+ mode, bpc);
+ if (ret)
+ continue;
+
+ vc4_state->output_bpc = bpc;
+
+ drm_dbg(dev,
+ "Mode %ux%u @ %uHz: Found configuration: bpc: %u, fmt: %s, clock: %llu\n",
+ mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode),
+ vc4_state->output_bpc,
+ vc4_hdmi_output_fmt_str(vc4_state->output_format),
+ vc4_state->pixel_rate);
+
+ break;
+ }
+
+ return ret;
}
#define WIFI_2_4GHz_CH1_MIN_FREQ 2400000000ULL