Commit 66e914c0 authored by Martin Kepplinger's avatar Martin Kepplinger
Browse files

Merge branch '5.13.1/librem5_hdmi_dp' into 5.13.1/librem5__integration_byzantium

parents daa35046 dcd16ea0
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause))
%YAML 1.2
---
$id: http://devicetree.org/schemas/display/bridge/cdns,mhdp.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Cadence MHDP TX Encoder
maintainers:
- Sandor Yu <Sandoryu@nxp.com>
description: |
Cadence MHDP Controller supports one or more of the protocols,
such as HDMI and DisplayPort.
Each protocol requires a different FW binaries.
This document defines device tree properties for the Cadence MHDP Encoder
(CDNS MHDP TX). It doesn't constitue a device tree binding
specification by itself but is meant to be referenced by platform-specific
device tree bindings.
When referenced from platform device tree bindings the properties defined in
this document are defined as follows. The platform device tree bindings are
responsible for defining whether each property is required or optional.
properties:
reg:
maxItems: 1
description: Memory mapped base address and length of the MHDP TX registers.
interrupts:
maxItems: 2
interrupt-names:
- const: plug_in
description: Hotplug detect interrupter for cable plugin event.
- const: plug_out
description: Hotplug detect interrupter for cable plugout event.
port:
type: object
description: |
The connectivity of the MHDP TX with the rest of the system is
expressed in using ports as specified in the device graph bindings defined
in Documentation/devicetree/bindings/graph.txt. The numbering of the ports
is platform-specific.
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/display/bridge/mhdp.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Cadence MHDP Encoder
maintainers:
- Sandor Yu <Sandoryu@nxp.com>
description: |
The MHDP transmitter is a Cadence HD Display TX controller IP
with a companion PHY IP.
The MHDP supports one or more of the protocols,
such as HDMI(1.4 & 2.0), DisplayPort(1.2).
switching between the two modes (HDMI and DisplayPort)
requires reloading the appropriate FW
These DT bindings follow the Cadence MHDP TX bindings defined in
Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml with the
following device-specific properties.
Properties:
compatible:
enum:
- nxp,imx8mq-cdns-hdmi
- nxp,imx8mq-cdns-dp
reg: See cdns,mhdp.yaml.
interrupts: See cdns,mhdp.yaml.
interrupt-names: See cdns,mhdp.yaml.
ports: See cdns,mhdp.yaml.
Required:
- compatible
- reg
- interrupts
- interrupt-names
- ports
Example:
- |
mhdp: mhdp@32c00000 {
compatible = "nxp,imx8mq-cdns-hdmi";
reg = <0x32c00000 0x100000>;
interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "plug_in", "plug_out";
ports {
mhdp_in: endpoint {
remote-endpoint = <&dcss_out>;
};
};
};
......@@ -296,6 +296,18 @@ opp-800M {
};
};
/delete-node/ &dcss_dsi_out;
/delete-node/ &mipi_dsi_dcss_in;
&dcss {
status = "okay";
port@0 {
dcss_hdmi_out: endpoint {
remote-endpoint = <&hdmi_in>;
};
};
};
&dphy {
status = "okay";
};
......@@ -332,6 +344,17 @@ spk-mute-hog {
};
};
&hdmi {
compatible = "nxp,imx8mq-cdns-hdmi";
lane-mapping = <0xe4>;
status = "okay";
port@0 {
hdmi_in: endpoint {
remote-endpoint = <&dcss_hdmi_out>;
};
};
};
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
......
......@@ -91,10 +91,11 @@ chg-en {
default-state = "off";
};
chg-otg-en {
label = "chg_otg_en";
gpios = <&gpio3 4 GPIO_ACTIVE_HIGH>;
flash-strobe {
label = "flash_strobe";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
default-state = "off";
linux,default-trigger = "flash";
};
flash-strobe {
......@@ -517,6 +518,11 @@ pinctrl_charger: chargergrp {
fsl,pins = <
/* CHG_EN */
MX8MQ_IOMUXC_NAND_CE1_B_GPIO3_IO2 0x3
>;
};
pinctrl_otg: otggrp {
fsl,pins = <
/* CHG_OTG_OUT_EN */
MX8MQ_IOMUXC_NAND_CE3_B_GPIO3_IO4 0x3
>;
......@@ -1033,6 +1039,10 @@ typec_pd: usb-pd@3f {
interrupt-names = "irq";
connector {
compatible = "usb-c-connector";
label = "USB-C";
data-role = "dual";
ports {
#address-cells = <1>;
#size-cells = <0>;
......@@ -1428,7 +1438,7 @@ bq25895: charger@6a {
compatible = "ti,bq25895", "ti,bq25890";
reg = <0x6a>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_charger_in>;
pinctrl-0 = <&pinctrl_charger_in>, <&pinctrl_otg>;
interrupt-parent = <&gpio3>;
interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
phys = <&usb3_phy0>;
......@@ -1440,6 +1450,7 @@ bq25895: charger@6a {
ti,vinmin-threshold = <3900000>; /* uV */
monitored-battery = <&bat>;
power-supplies = <&typec_pd>;
otg-gpios = <&gpio3 4 GPIO_ACTIVE_HIGH>;
};
};
......@@ -1632,6 +1643,10 @@ &usb_dwc3_0 {
#size-cells = <0>;
dr_mode = "otg";
snps,dis_u3_susphy_quirk;
hnp-disable;
srp-disable;
adp-disable;
usb-role-switch;
status = "okay";
port@0 {
......
......@@ -1083,7 +1083,13 @@ mipi_dsi_lcdif_in: endpoint@0 {
reg = <0>;
remote-endpoint = <&lcdif_mipi_dsi>;
};
mipi_dsi_dcss_in: endpoint@1 {
reg = <1>;
remote-endpoint = <&dcss_mipi_dsi>;
};
};
};
};
......@@ -1354,6 +1360,45 @@ bus@32c00000 { /* AIPS4 */
#size-cells = <1>;
ranges = <0x32c00000 0x32c00000 0x400000>;
hdmi: hdmi@32c00000 {
reg = <0x32c00000 0x100000>,
<0x32e40000 0x40000>;
interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "plug_in", "plug_out";
status = "disabled";
};
dcss: display-controller@32e00000 {
compatible = "nxp,imx8mq-dcss";
reg = <0x32e00000 0x2d000>, <0x32e2f000 0x1000>;
interrupts = <6>, <8>, <9>;
interrupt-names = "ctxld", "ctxld_kick", "vblank";
interrupt-parent = <&irqsteer>;
clocks = <&clk IMX8MQ_CLK_DISP_APB_ROOT>,
<&clk IMX8MQ_CLK_DISP_AXI_ROOT>,
<&clk IMX8MQ_CLK_DISP_RTRM_ROOT>,
<&clk IMX8MQ_VIDEO2_PLL_OUT>,
<&clk IMX8MQ_CLK_DISP_DTRC>;
clock-names = "apb", "axi", "rtrm", "pix", "dtrc";
assigned-clocks = <&clk IMX8MQ_CLK_DISP_AXI>,
<&clk IMX8MQ_CLK_DISP_RTRM>,
<&clk IMX8MQ_VIDEO2_PLL1_REF_SEL>;
assigned-clock-parents = <&clk IMX8MQ_SYS1_PLL_800M>,
<&clk IMX8MQ_SYS1_PLL_800M>,
<&clk IMX8MQ_CLK_27M>;
assigned-clock-rates = <800000000>,
<400000000>;
interconnects = <&noc IMX8MQ_ICM_DCSS &noc IMX8MQ_ICS_DRAM>;
interconnect-names = "dram";
status = "disabled";
dcss_dsi_out: port@0 {
dcss_mipi_dsi: endpoint {
remote-endpoint = <&mipi_dsi_dcss_in>;
};
};
};
irqsteer: interrupt-controller@32e2d000 {
compatible = "fsl,imx8m-irqsteer", "fsl,imx-irqsteer";
reg = <0x32e2d000 0x1000>;
......
......@@ -535,6 +535,9 @@ CONFIG_VIDEO_OV5640=m
CONFIG_VIDEO_S5K3L6XX=m
CONFIG_VIDEO_DW9714=m
CONFIG_DRM=y
CONFIG_DRM_DP_AUX_CHARDEV=y
CONFIG_DRM_LOAD_EDID_FIRMWARE=y
CONFIG_DRM_DP_CEC=y
CONFIG_DRM_I2C_CH7006=m
CONFIG_DRM_I2C_SIL164=m
CONFIG_DRM_I2C_NXP_TDA9950=m
......
......@@ -25,7 +25,7 @@ static u32 share_count_sai6;
static u32 share_count_dcss;
static u32 share_count_nand;
static const char * const pll_ref_sels[] = { "osc_25m", "osc_27m", "dummy", "dummy", };
static const char * const pll_ref_sels[] = { "osc_25m", "osc_27m", "phy_27m", "dummy", };
static const char * const arm_pll_bypass_sels[] = {"arm_pll", "arm_pll_ref_sel", };
static const char * const gpu_pll_bypass_sels[] = {"gpu_pll", "gpu_pll_ref_sel", };
static const char * const vpu_pll_bypass_sels[] = {"vpu_pll", "vpu_pll_ref_sel", };
......@@ -304,6 +304,7 @@ static int imx8mq_clocks_probe(struct platform_device *pdev)
hws[IMX8MQ_CLK_EXT2] = imx_obtain_fixed_clk_hw(np, "clk_ext2");
hws[IMX8MQ_CLK_EXT3] = imx_obtain_fixed_clk_hw(np, "clk_ext3");
hws[IMX8MQ_CLK_EXT4] = imx_obtain_fixed_clk_hw(np, "clk_ext4");
hws[IMX8MQ_CLK_PHY_27MHZ] = imx_clk_hw_fixed("phy_27m", 27000000);
np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-anatop");
base = of_iomap(np, 0);
......
......@@ -22,3 +22,19 @@ config DRM_CDNS_MHDP8546_J721E
initializes the J721E Display Port and sets up the
clock and data muxes.
endif
config DRM_CDNS_MHDP
tristate "Cadence MHDP COMMON API driver"
select DRM_KMS_HELPER
select DRM_PANEL_BRIDGE
depends on OF
help
Support Cadence MHDP API library.
config DRM_CDNS_DP
tristate "Cadence DP DRM driver"
depends on DRM_CDNS_MHDP
config DRM_CDNS_HDMI
tristate "Cadence HDMI DRM driver"
depends on DRM_CDNS_MHDP
......@@ -2,3 +2,7 @@
obj-$(CONFIG_DRM_CDNS_MHDP8546) += cdns-mhdp8546.o
cdns-mhdp8546-y := cdns-mhdp8546-core.o
cdns-mhdp8546-$(CONFIG_DRM_CDNS_MHDP8546_J721E) += cdns-mhdp8546-j721e.o
obj-$(CONFIG_DRM_CDNS_MHDP) += cdns_mhdp_drmcore.o
cdns_mhdp_drmcore-y := cdns-mhdp-common.o cdns-mhdp-audio.o cdns-mhdp-dp.o cdns-mhdp-hdmi.o
cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_DP) += cdns-dp-core.o
cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI) += cdns-hdmi-core.o
// SPDX-License-Identifier: GPL-2.0-only
/*
* Cadence Display Port Interface (DP) driver
*
* Copyright (C) 2019-2020 NXP Semiconductor, Inc.
*
*/
#include <drm/bridge/cdns-mhdp.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder_slave.h>
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_print.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include "cdns-mhdp-common.h"
#define CDNS_EXTCON_ONLY
/*
* This function only implements native DPDC reads and writes
*/
static ssize_t dp_aux_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
{
struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev);
bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
int ret;
/* Ignore address only message */
if ((msg->size == 0) || (msg->buffer == NULL)) {
msg->reply = native ?
DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
return msg->size;
}
if (!native) {
dev_err(mhdp->dev, "%s: only native messages supported\n", __func__);
return -EINVAL;
}
/* msg sanity check */
if (msg->size > DP_AUX_MAX_PAYLOAD_BYTES) {
dev_err(mhdp->dev, "%s: invalid msg: size(%zu), request(%x)\n",
__func__, msg->size, (unsigned int)msg->request);
return -EINVAL;
}
if (msg->request == DP_AUX_NATIVE_WRITE) {
const u8 *buf = msg->buffer;
int i;
for (i = 0; i < msg->size; ++i) {
ret = cdns_mhdp_dpcd_write(mhdp,
msg->address + i, buf[i]);
if (!ret)
continue;
DRM_DEV_ERROR(mhdp->dev, "Failed to write DPCD\n");
return ret;
}
msg->reply = DP_AUX_NATIVE_REPLY_ACK;
return msg->size;
}
if (msg->request == DP_AUX_NATIVE_READ) {
ret = cdns_mhdp_dpcd_read(mhdp, msg->address, msg->buffer, msg->size);
if (ret < 0)
return -EIO;
msg->reply = DP_AUX_NATIVE_REPLY_ACK;
return msg->size;
}
return 0;
}
static int dp_aux_init(struct cdns_mhdp_device *mhdp,
struct device *dev)
{
int ret;
mhdp->dp.aux.name = "imx_dp_aux";
mhdp->dp.aux.dev = dev;
mhdp->dp.aux.transfer = dp_aux_transfer;
ret = drm_dp_aux_register(&mhdp->dp.aux);
return ret;
}
static int dp_aux_destroy(struct cdns_mhdp_device *mhdp)
{
drm_dp_aux_unregister(&mhdp->dp.aux);
return 0;
}
static void dp_pixel_clk_reset(struct cdns_mhdp_device *mhdp)
{
u32 val;
/* reset pixel clk */
val = cdns_mhdp_reg_read(mhdp, SOURCE_HDTX_CAR);
cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val & 0xFD);
cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val);
}
static void cdns_dp_mode_set(struct cdns_mhdp_device *mhdp)
{
int ret;
cdns_mhdp_plat_call(mhdp, pclk_rate);
/* delay for DP FW stable after pixel clock relock */
msleep(50);
dp_pixel_clk_reset(mhdp);
/* Get DP Caps */
ret = drm_dp_dpcd_read(&mhdp->dp.aux, DP_DPCD_REV, mhdp->dp.dpcd,
DP_RECEIVER_CAP_SIZE);
if (ret < 0) {
DRM_ERROR("Failed to get caps %d\n", ret);
return;
}
mhdp->dp.rate = drm_dp_max_link_rate(mhdp->dp.dpcd);
mhdp->dp.num_lanes = drm_dp_max_lane_count(mhdp->dp.dpcd);
/* check the max link rate */
if (mhdp->dp.rate > CDNS_DP_MAX_LINK_RATE)
mhdp->dp.rate = CDNS_DP_MAX_LINK_RATE;
/* Initialize link rate/num_lanes as panel max link rate/max_num_lanes */
cdns_mhdp_plat_call(mhdp, phy_set);
/* Video off */
ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE);
if (ret) {
DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret);
return;
}
/* Line swaping */
mhdp->lane_mapping = mhdp->plat_data->lane_mapping;
cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | mhdp->lane_mapping);
/* Set DP host capability */
ret = cdns_mhdp_set_host_cap(mhdp);
if (ret) {
DRM_DEV_ERROR(mhdp->dev, "Failed to set host cap %d\n", ret);
return;
}
ret = cdns_mhdp_config_video(mhdp);
if (ret) {
DRM_DEV_ERROR(mhdp->dev, "Failed to config video %d\n", ret);
return;
}
return;
}
/* -----------------------------------------------------------------------------
* DP TX Setup
*/
static enum drm_connector_status
cdns_dp_connector_detect(struct drm_connector *connector, bool force)
{
struct cdns_mhdp_device *mhdp = container_of(connector,
struct cdns_mhdp_device, connector.base);
u8 hpd;
hpd = cdns_mhdp_read_hpd(mhdp);
DRM_INFO("Connector status: %d", hpd);
if (hpd == 1)
/* Cable Connected */
return connector_status_connected;
else if (hpd == 0)
/* Cable Disconnedted */
return connector_status_disconnected;
else {
/* Cable status unknown */
DRM_INFO("Unknow cable status, hdp=%u\n", hpd);
return connector_status_unknown;
}
}
static int cdns_dp_connector_get_modes(struct drm_connector *connector)
{
struct cdns_mhdp_device *mhdp = container_of(connector,
struct cdns_mhdp_device, connector.base);
int num_modes = 0;
struct edid *edid;
pm_runtime_get_sync(mhdp->dev);
edid = drm_do_get_edid(&mhdp->connector.base,
cdns_mhdp_get_edid_block, mhdp);
if (edid) {
dev_info(mhdp->dev, "%x,%x,%x,%x,%x,%x,%x,%x\n",
edid->header[0], edid->header[1],
edid->header[2], edid->header[3],
edid->header[4], edid->header[5],
edid->header[6], edid->header[7]);
drm_connector_update_edid_property(connector, edid);
num_modes = drm_add_edid_modes(connector, edid);
kfree(edid);
}
if (num_modes == 0)
DRM_ERROR("Invalid edid\n");
pm_runtime_put(mhdp->dev);
return num_modes;
}
static const struct drm_connector_funcs cdns_dp_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = cdns_dp_connector_detect,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const struct drm_connector_helper_funcs cdns_dp_connector_helper_funcs = {
.get_modes = cdns_dp_connector_get_modes,
};
static int cdns_dp_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct cdns_mhdp_device *mhdp = bridge->driver_private;
struct drm_encoder *encoder = bridge->encoder;
struct drm_connector *connector = &mhdp->connector.base;
connector->interlace_allowed = 1;
connector->polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_helper_add(connector, &cdns_dp_connector_helper_funcs);
drm_connector_init(bridge->dev, connector, &cdns_dp_connector_funcs,
DRM_MODE_CONNECTOR_DisplayPort);
drm_connector_attach_encoder(connector, encoder);
return 0;
}
static enum drm_mode_status
cdns_dp_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
enum drm_mode_status mode_status = MODE_OK;
/* We don't support double-clocked modes */
if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
mode->flags & DRM_MODE_FLAG_INTERLACE)
return MODE_BAD;
/* MAX support pixel clock rate 594MHz */
if (mode->clock > 594000)