The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Signed-off-by: Rob Herring <robh@kernel.org> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Steven Price <steven.price@arm.com> Acked-by: Liviu Dudau <liviu.dudau@arm.com> Reviewed-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> Acked-by: Robert Foss <rfoss@kernel.org> Signed-off-by: Thierry Reding <treding@nvidia.com> Link: https://patchwork.freedesktop.org/patch/msgid/20230714174545.4056287-1-robh@kernel.org
478 lines
14 KiB
C
478 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2016 BayLibre, SAS
|
|
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
|
* Copyright (C) 2015 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/component.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include <media/cec-notifier.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_bridge.h>
|
|
#include <drm/drm_bridge_connector.h>
|
|
#include <drm/drm_device.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/drm_simple_kms_helper.h>
|
|
|
|
#include <linux/media-bus-format.h>
|
|
#include <linux/videodev2.h>
|
|
|
|
#include "meson_drv.h"
|
|
#include "meson_registers.h"
|
|
#include "meson_vclk.h"
|
|
#include "meson_venc.h"
|
|
#include "meson_encoder_hdmi.h"
|
|
|
|
struct meson_encoder_hdmi {
|
|
struct drm_encoder encoder;
|
|
struct drm_bridge bridge;
|
|
struct drm_bridge *next_bridge;
|
|
struct drm_connector *connector;
|
|
struct meson_drm *priv;
|
|
unsigned long output_bus_fmt;
|
|
struct cec_notifier *cec_notifier;
|
|
};
|
|
|
|
#define bridge_to_meson_encoder_hdmi(x) \
|
|
container_of(x, struct meson_encoder_hdmi, bridge)
|
|
|
|
static int meson_encoder_hdmi_attach(struct drm_bridge *bridge,
|
|
enum drm_bridge_attach_flags flags)
|
|
{
|
|
struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
|
|
|
|
return drm_bridge_attach(bridge->encoder, encoder_hdmi->next_bridge,
|
|
&encoder_hdmi->bridge, flags);
|
|
}
|
|
|
|
static void meson_encoder_hdmi_detach(struct drm_bridge *bridge)
|
|
{
|
|
struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
|
|
|
|
cec_notifier_conn_unregister(encoder_hdmi->cec_notifier);
|
|
encoder_hdmi->cec_notifier = NULL;
|
|
}
|
|
|
|
static void meson_encoder_hdmi_set_vclk(struct meson_encoder_hdmi *encoder_hdmi,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct meson_drm *priv = encoder_hdmi->priv;
|
|
int vic = drm_match_cea_mode(mode);
|
|
unsigned int phy_freq;
|
|
unsigned int vclk_freq;
|
|
unsigned int venc_freq;
|
|
unsigned int hdmi_freq;
|
|
|
|
vclk_freq = mode->clock;
|
|
|
|
/* For 420, pixel clock is half unlike venc clock */
|
|
if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24)
|
|
vclk_freq /= 2;
|
|
|
|
/* TMDS clock is pixel_clock * 10 */
|
|
phy_freq = vclk_freq * 10;
|
|
|
|
if (!vic) {
|
|
meson_vclk_setup(priv, MESON_VCLK_TARGET_DMT, phy_freq,
|
|
vclk_freq, vclk_freq, vclk_freq, false);
|
|
return;
|
|
}
|
|
|
|
/* 480i/576i needs global pixel doubling */
|
|
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
|
vclk_freq *= 2;
|
|
|
|
venc_freq = vclk_freq;
|
|
hdmi_freq = vclk_freq;
|
|
|
|
/* VENC double pixels for 1080i, 720p and YUV420 modes */
|
|
if (meson_venc_hdmi_venc_repeat(vic) ||
|
|
encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24)
|
|
venc_freq *= 2;
|
|
|
|
vclk_freq = max(venc_freq, hdmi_freq);
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
|
venc_freq /= 2;
|
|
|
|
dev_dbg(priv->dev, "vclk:%d phy=%d venc=%d hdmi=%d enci=%d\n",
|
|
phy_freq, vclk_freq, venc_freq, hdmi_freq,
|
|
priv->venc.hdmi_use_enci);
|
|
|
|
meson_vclk_setup(priv, MESON_VCLK_TARGET_HDMI, phy_freq, vclk_freq,
|
|
venc_freq, hdmi_freq, priv->venc.hdmi_use_enci);
|
|
}
|
|
|
|
static enum drm_mode_status meson_encoder_hdmi_mode_valid(struct drm_bridge *bridge,
|
|
const struct drm_display_info *display_info,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
|
|
struct meson_drm *priv = encoder_hdmi->priv;
|
|
bool is_hdmi2_sink = display_info->hdmi.scdc.supported;
|
|
unsigned int phy_freq;
|
|
unsigned int vclk_freq;
|
|
unsigned int venc_freq;
|
|
unsigned int hdmi_freq;
|
|
int vic = drm_match_cea_mode(mode);
|
|
enum drm_mode_status status;
|
|
|
|
dev_dbg(priv->dev, "Modeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode));
|
|
|
|
/* If sink does not support 540MHz, reject the non-420 HDMI2 modes */
|
|
if (display_info->max_tmds_clock &&
|
|
mode->clock > display_info->max_tmds_clock &&
|
|
!drm_mode_is_420_only(display_info, mode) &&
|
|
!drm_mode_is_420_also(display_info, mode))
|
|
return MODE_BAD;
|
|
|
|
/* Check against non-VIC supported modes */
|
|
if (!vic) {
|
|
status = meson_venc_hdmi_supported_mode(mode);
|
|
if (status != MODE_OK)
|
|
return status;
|
|
|
|
return meson_vclk_dmt_supported_freq(priv, mode->clock);
|
|
/* Check against supported VIC modes */
|
|
} else if (!meson_venc_hdmi_supported_vic(vic))
|
|
return MODE_BAD;
|
|
|
|
vclk_freq = mode->clock;
|
|
|
|
/* For 420, pixel clock is half unlike venc clock */
|
|
if (drm_mode_is_420_only(display_info, mode) ||
|
|
(!is_hdmi2_sink &&
|
|
drm_mode_is_420_also(display_info, mode)))
|
|
vclk_freq /= 2;
|
|
|
|
/* TMDS clock is pixel_clock * 10 */
|
|
phy_freq = vclk_freq * 10;
|
|
|
|
/* 480i/576i needs global pixel doubling */
|
|
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
|
vclk_freq *= 2;
|
|
|
|
venc_freq = vclk_freq;
|
|
hdmi_freq = vclk_freq;
|
|
|
|
/* VENC double pixels for 1080i, 720p and YUV420 modes */
|
|
if (meson_venc_hdmi_venc_repeat(vic) ||
|
|
drm_mode_is_420_only(display_info, mode) ||
|
|
(!is_hdmi2_sink &&
|
|
drm_mode_is_420_also(display_info, mode)))
|
|
venc_freq *= 2;
|
|
|
|
vclk_freq = max(venc_freq, hdmi_freq);
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
|
|
venc_freq /= 2;
|
|
|
|
dev_dbg(priv->dev, "%s: vclk:%d phy=%d venc=%d hdmi=%d\n",
|
|
__func__, phy_freq, vclk_freq, venc_freq, hdmi_freq);
|
|
|
|
return meson_vclk_vic_supported_freq(priv, phy_freq, vclk_freq);
|
|
}
|
|
|
|
static void meson_encoder_hdmi_atomic_enable(struct drm_bridge *bridge,
|
|
struct drm_bridge_state *bridge_state)
|
|
{
|
|
struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
|
|
struct drm_atomic_state *state = bridge_state->base.state;
|
|
unsigned int ycrcb_map = VPU_HDMI_OUTPUT_CBYCR;
|
|
struct meson_drm *priv = encoder_hdmi->priv;
|
|
struct drm_connector_state *conn_state;
|
|
const struct drm_display_mode *mode;
|
|
struct drm_crtc_state *crtc_state;
|
|
struct drm_connector *connector;
|
|
bool yuv420_mode = false;
|
|
int vic;
|
|
|
|
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
|
|
if (WARN_ON(!connector))
|
|
return;
|
|
|
|
conn_state = drm_atomic_get_new_connector_state(state, connector);
|
|
if (WARN_ON(!conn_state))
|
|
return;
|
|
|
|
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
|
|
if (WARN_ON(!crtc_state))
|
|
return;
|
|
|
|
mode = &crtc_state->adjusted_mode;
|
|
|
|
vic = drm_match_cea_mode(mode);
|
|
|
|
dev_dbg(priv->dev, "\"%s\" vic %d\n", mode->name, vic);
|
|
|
|
if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24) {
|
|
ycrcb_map = VPU_HDMI_OUTPUT_CRYCB;
|
|
yuv420_mode = true;
|
|
} else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16)
|
|
ycrcb_map = VPU_HDMI_OUTPUT_CRYCB;
|
|
|
|
/* VENC + VENC-DVI Mode setup */
|
|
meson_venc_hdmi_mode_set(priv, vic, ycrcb_map, yuv420_mode, mode);
|
|
|
|
/* VCLK Set clock */
|
|
meson_encoder_hdmi_set_vclk(encoder_hdmi, mode);
|
|
|
|
if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYYVYY8_0_5X24)
|
|
/* Setup YUV420 to HDMI-TX, no 10bit diphering */
|
|
writel_relaxed(2 | (2 << 2),
|
|
priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
|
|
else if (encoder_hdmi->output_bus_fmt == MEDIA_BUS_FMT_UYVY8_1X16)
|
|
/* Setup YUV422 to HDMI-TX, no 10bit diphering */
|
|
writel_relaxed(1 | (2 << 2),
|
|
priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
|
|
else
|
|
/* Setup YUV444 to HDMI-TX, no 10bit diphering */
|
|
writel_relaxed(0, priv->io_base + _REG(VPU_HDMI_FMT_CTRL));
|
|
|
|
dev_dbg(priv->dev, "%s\n", priv->venc.hdmi_use_enci ? "VENCI" : "VENCP");
|
|
|
|
if (priv->venc.hdmi_use_enci)
|
|
writel_relaxed(1, priv->io_base + _REG(ENCI_VIDEO_EN));
|
|
else
|
|
writel_relaxed(1, priv->io_base + _REG(ENCP_VIDEO_EN));
|
|
}
|
|
|
|
static void meson_encoder_hdmi_atomic_disable(struct drm_bridge *bridge,
|
|
struct drm_bridge_state *bridge_state)
|
|
{
|
|
struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
|
|
struct meson_drm *priv = encoder_hdmi->priv;
|
|
|
|
writel_bits_relaxed(0x3, 0,
|
|
priv->io_base + _REG(VPU_HDMI_SETTING));
|
|
|
|
writel_relaxed(0, priv->io_base + _REG(ENCI_VIDEO_EN));
|
|
writel_relaxed(0, priv->io_base + _REG(ENCP_VIDEO_EN));
|
|
}
|
|
|
|
static const u32 meson_encoder_hdmi_out_bus_fmts[] = {
|
|
MEDIA_BUS_FMT_YUV8_1X24,
|
|
MEDIA_BUS_FMT_UYVY8_1X16,
|
|
MEDIA_BUS_FMT_UYYVYY8_0_5X24,
|
|
};
|
|
|
|
static u32 *
|
|
meson_encoder_hdmi_get_inp_bus_fmts(struct drm_bridge *bridge,
|
|
struct drm_bridge_state *bridge_state,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_connector_state *conn_state,
|
|
u32 output_fmt,
|
|
unsigned int *num_input_fmts)
|
|
{
|
|
u32 *input_fmts = NULL;
|
|
int i;
|
|
|
|
*num_input_fmts = 0;
|
|
|
|
for (i = 0 ; i < ARRAY_SIZE(meson_encoder_hdmi_out_bus_fmts) ; ++i) {
|
|
if (output_fmt == meson_encoder_hdmi_out_bus_fmts[i]) {
|
|
*num_input_fmts = 1;
|
|
input_fmts = kcalloc(*num_input_fmts,
|
|
sizeof(*input_fmts),
|
|
GFP_KERNEL);
|
|
if (!input_fmts)
|
|
return NULL;
|
|
|
|
input_fmts[0] = output_fmt;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return input_fmts;
|
|
}
|
|
|
|
static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge,
|
|
struct drm_bridge_state *bridge_state,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_connector_state *conn_state)
|
|
{
|
|
struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
|
|
struct drm_connector_state *old_conn_state =
|
|
drm_atomic_get_old_connector_state(conn_state->state, conn_state->connector);
|
|
struct meson_drm *priv = encoder_hdmi->priv;
|
|
|
|
encoder_hdmi->output_bus_fmt = bridge_state->output_bus_cfg.format;
|
|
|
|
dev_dbg(priv->dev, "output_bus_fmt %lx\n", encoder_hdmi->output_bus_fmt);
|
|
|
|
if (!drm_connector_atomic_hdr_metadata_equal(old_conn_state, conn_state))
|
|
crtc_state->mode_changed = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge,
|
|
enum drm_connector_status status)
|
|
{
|
|
struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge);
|
|
struct edid *edid;
|
|
|
|
if (!encoder_hdmi->cec_notifier)
|
|
return;
|
|
|
|
if (status == connector_status_connected) {
|
|
edid = drm_bridge_get_edid(encoder_hdmi->next_bridge, encoder_hdmi->connector);
|
|
if (!edid)
|
|
return;
|
|
|
|
cec_notifier_set_phys_addr_from_edid(encoder_hdmi->cec_notifier, edid);
|
|
} else
|
|
cec_notifier_phys_addr_invalidate(encoder_hdmi->cec_notifier);
|
|
}
|
|
|
|
static const struct drm_bridge_funcs meson_encoder_hdmi_bridge_funcs = {
|
|
.attach = meson_encoder_hdmi_attach,
|
|
.detach = meson_encoder_hdmi_detach,
|
|
.mode_valid = meson_encoder_hdmi_mode_valid,
|
|
.hpd_notify = meson_encoder_hdmi_hpd_notify,
|
|
.atomic_enable = meson_encoder_hdmi_atomic_enable,
|
|
.atomic_disable = meson_encoder_hdmi_atomic_disable,
|
|
.atomic_get_input_bus_fmts = meson_encoder_hdmi_get_inp_bus_fmts,
|
|
.atomic_check = meson_encoder_hdmi_atomic_check,
|
|
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
|
.atomic_reset = drm_atomic_helper_bridge_reset,
|
|
};
|
|
|
|
int meson_encoder_hdmi_init(struct meson_drm *priv)
|
|
{
|
|
struct meson_encoder_hdmi *meson_encoder_hdmi;
|
|
struct platform_device *pdev;
|
|
struct device_node *remote;
|
|
int ret;
|
|
|
|
meson_encoder_hdmi = devm_kzalloc(priv->dev, sizeof(*meson_encoder_hdmi), GFP_KERNEL);
|
|
if (!meson_encoder_hdmi)
|
|
return -ENOMEM;
|
|
|
|
/* HDMI Transceiver Bridge */
|
|
remote = of_graph_get_remote_node(priv->dev->of_node, 1, 0);
|
|
if (!remote) {
|
|
dev_err(priv->dev, "HDMI transceiver device is disabled");
|
|
return 0;
|
|
}
|
|
|
|
meson_encoder_hdmi->next_bridge = of_drm_find_bridge(remote);
|
|
if (!meson_encoder_hdmi->next_bridge) {
|
|
dev_err(priv->dev, "Failed to find HDMI transceiver bridge\n");
|
|
ret = -EPROBE_DEFER;
|
|
goto err_put_node;
|
|
}
|
|
|
|
/* HDMI Encoder Bridge */
|
|
meson_encoder_hdmi->bridge.funcs = &meson_encoder_hdmi_bridge_funcs;
|
|
meson_encoder_hdmi->bridge.of_node = priv->dev->of_node;
|
|
meson_encoder_hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
|
|
meson_encoder_hdmi->bridge.interlace_allowed = true;
|
|
|
|
drm_bridge_add(&meson_encoder_hdmi->bridge);
|
|
|
|
meson_encoder_hdmi->priv = priv;
|
|
|
|
/* Encoder */
|
|
ret = drm_simple_encoder_init(priv->drm, &meson_encoder_hdmi->encoder,
|
|
DRM_MODE_ENCODER_TMDS);
|
|
if (ret) {
|
|
dev_err(priv->dev, "Failed to init HDMI encoder: %d\n", ret);
|
|
goto err_put_node;
|
|
}
|
|
|
|
meson_encoder_hdmi->encoder.possible_crtcs = BIT(0);
|
|
|
|
/* Attach HDMI Encoder Bridge to Encoder */
|
|
ret = drm_bridge_attach(&meson_encoder_hdmi->encoder, &meson_encoder_hdmi->bridge, NULL,
|
|
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
|
if (ret) {
|
|
dev_err(priv->dev, "Failed to attach bridge: %d\n", ret);
|
|
goto err_put_node;
|
|
}
|
|
|
|
/* Initialize & attach Bridge Connector */
|
|
meson_encoder_hdmi->connector = drm_bridge_connector_init(priv->drm,
|
|
&meson_encoder_hdmi->encoder);
|
|
if (IS_ERR(meson_encoder_hdmi->connector)) {
|
|
dev_err(priv->dev, "Unable to create HDMI bridge connector\n");
|
|
ret = PTR_ERR(meson_encoder_hdmi->connector);
|
|
goto err_put_node;
|
|
}
|
|
drm_connector_attach_encoder(meson_encoder_hdmi->connector,
|
|
&meson_encoder_hdmi->encoder);
|
|
|
|
/*
|
|
* We should have now in place:
|
|
* encoder->[hdmi encoder bridge]->[dw-hdmi bridge]->[display connector bridge]->[display connector]
|
|
*/
|
|
|
|
/*
|
|
* drm_connector_attach_max_bpc_property() requires the
|
|
* connector to have a state.
|
|
*/
|
|
drm_atomic_helper_connector_reset(meson_encoder_hdmi->connector);
|
|
|
|
if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXL) ||
|
|
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_GXM) ||
|
|
meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A))
|
|
drm_connector_attach_hdr_output_metadata_property(meson_encoder_hdmi->connector);
|
|
|
|
drm_connector_attach_max_bpc_property(meson_encoder_hdmi->connector, 8, 8);
|
|
|
|
/* Handle this here until handled by drm_bridge_connector_init() */
|
|
meson_encoder_hdmi->connector->ycbcr_420_allowed = true;
|
|
|
|
pdev = of_find_device_by_node(remote);
|
|
of_node_put(remote);
|
|
if (pdev) {
|
|
struct cec_connector_info conn_info;
|
|
struct cec_notifier *notifier;
|
|
|
|
cec_fill_conn_info_from_drm(&conn_info, meson_encoder_hdmi->connector);
|
|
|
|
notifier = cec_notifier_conn_register(&pdev->dev, NULL, &conn_info);
|
|
if (!notifier) {
|
|
put_device(&pdev->dev);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
meson_encoder_hdmi->cec_notifier = notifier;
|
|
}
|
|
|
|
priv->encoders[MESON_ENC_HDMI] = meson_encoder_hdmi;
|
|
|
|
dev_dbg(priv->dev, "HDMI encoder initialized\n");
|
|
|
|
return 0;
|
|
|
|
err_put_node:
|
|
of_node_put(remote);
|
|
return ret;
|
|
}
|
|
|
|
void meson_encoder_hdmi_remove(struct meson_drm *priv)
|
|
{
|
|
struct meson_encoder_hdmi *meson_encoder_hdmi;
|
|
|
|
if (priv->encoders[MESON_ENC_HDMI]) {
|
|
meson_encoder_hdmi = priv->encoders[MESON_ENC_HDMI];
|
|
drm_bridge_remove(&meson_encoder_hdmi->bridge);
|
|
drm_bridge_remove(meson_encoder_hdmi->next_bridge);
|
|
}
|
|
}
|