summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r--drivers/gpu/drm/bridge/Kconfig39
-rw-r--r--drivers/gpu/drm/bridge/Makefile2
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511.h27
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_cec.c116
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_drv.c26
-rw-r--r--drivers/gpu/drm/bridge/analogix/Kconfig10
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix-anx6345.c2
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c2
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c2
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix_dp_core.c38
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix_dp_core.h2
-rw-r--r--drivers/gpu/drm/bridge/analogix/anx7625.c66
-rw-r--r--drivers/gpu/drm/bridge/analogix/anx7625.h4
-rw-r--r--drivers/gpu/drm/bridge/cadence/Kconfig4
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c4
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h2
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c2
-rw-r--r--drivers/gpu/drm/bridge/chipone-icn6211.c596
-rw-r--r--drivers/gpu/drm/bridge/display-connector.c15
-rw-r--r--drivers/gpu/drm/bridge/fsl-ldb.c342
-rw-r--r--drivers/gpu/drm/bridge/ite-it6505.c33
-rw-r--r--drivers/gpu/drm/bridge/ite-it66121.c629
-rw-r--r--drivers/gpu/drm/bridge/lontium-lt9211.c802
-rw-r--r--drivers/gpu/drm/bridge/lontium-lt9611.c49
-rw-r--r--drivers/gpu/drm/bridge/nwl-dsi.c30
-rw-r--r--drivers/gpu/drm/bridge/nxp-ptn3460.c7
-rw-r--r--drivers/gpu/drm/bridge/panel.c6
-rw-r--r--drivers/gpu/drm/bridge/parade-ps8622.c7
-rw-r--r--drivers/gpu/drm/bridge/parade-ps8640.c13
-rw-r--r--drivers/gpu/drm/bridge/synopsys/Kconfig12
-rw-r--r--drivers/gpu/drm/bridge/synopsys/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c197
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.c192
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.h16
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c51
-rw-r--r--drivers/gpu/drm/bridge/tc358762.c10
-rw-r--r--drivers/gpu/drm/bridge/tc358764.c104
-rw-r--r--drivers/gpu/drm/bridge/tc358767.c589
-rw-r--r--drivers/gpu/drm/bridge/tc358775.c13
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi83.c17
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi86.c12
-rw-r--r--drivers/gpu/drm/bridge/ti-tfp410.c12
42 files changed, 3593 insertions, 510 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 2145b08f9534..307b135da2f6 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -32,6 +32,7 @@ config DRM_CHIPONE_ICN6211
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_PANEL_BRIDGE
+ select REGMAP_I2C
help
ICN6211 is MIPI-DSI/RGB Converter bridge from chipone.
@@ -74,9 +75,21 @@ config DRM_DISPLAY_CONNECTOR
on ARM-based platforms. Saying Y here when this driver is not needed
will not cause any issue.
+config DRM_FSL_LDB
+ tristate "Freescale i.MX8MP LDB bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select DRM_PANEL_BRIDGE
+ help
+ Support for i.MX8MP DPI-to-LVDS on-SoC encoder.
+
config DRM_ITE_IT6505
tristate "ITE IT6505 DisplayPort bridge"
depends on OF
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HDCP_HELPER
+ select DRM_DISPLAY_HELPER
+ select DRM_DP_AUX_BUS
select DRM_KMS_HELPER
select DRM_DP_HELPER
select EXTCON
@@ -99,6 +112,19 @@ config DRM_LONTIUM_LT8912B
Say M here if you want to support this hardware as a module.
The module will be named "lontium-lt8912b".
+config DRM_LONTIUM_LT9211
+ tristate "Lontium LT9211 DSI/LVDS/DPI bridge"
+ depends on OF
+ select DRM_PANEL_BRIDGE
+ select DRM_KMS_HELPER
+ select DRM_MIPI_DSI
+ select REGMAP_I2C
+ help
+ Driver for Lontium LT9211 Single/Dual-Link DSI/LVDS or Single DPI
+ input to Single-link/Dual-Link DSI/LVDS or Single DPI output bridge
+ chip.
+ Please say Y if you have such hardware.
+
config DRM_LONTIUM_LT9611
tristate "Lontium LT9611 DSI/HDMI bridge"
select SND_SOC_HDMI_CODEC if SND_SOC
@@ -191,8 +217,9 @@ config DRM_PARADE_PS8622
config DRM_PARADE_PS8640
tristate "Parade PS8640 MIPI DSI to eDP Converter"
depends on OF
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_DP_AUX_BUS
- select DRM_DP_HELPER
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_PANEL
@@ -263,9 +290,11 @@ config DRM_TOSHIBA_TC358764
config DRM_TOSHIBA_TC358767
tristate "Toshiba TC358767 eDP bridge"
depends on OF
- select DRM_DP_HELPER
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_KMS_HELPER
select REGMAP_I2C
+ select DRM_MIPI_DSI
select DRM_PANEL
help
Toshiba TC358767 eDP bridge chip driver.
@@ -283,7 +312,8 @@ config DRM_TOSHIBA_TC358768
config DRM_TOSHIBA_TC358775
tristate "Toshiba TC358775 DSI/LVDS bridge"
depends on OF
- select DRM_DP_HELPER
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_KMS_HELPER
select REGMAP_I2C
select DRM_PANEL
@@ -311,7 +341,8 @@ config DRM_TI_SN65DSI83
config DRM_TI_SN65DSI86
tristate "TI SN65DSI86 DSI to eDP bridge"
depends on OF
- select DRM_DP_HELPER
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_KMS_HELPER
select REGMAP_I2C
select DRM_PANEL
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 425844c30495..f6c0a95de549 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -4,8 +4,10 @@ obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
+obj-$(CONFIG_DRM_FSL_LDB) += fsl-ldb.o
obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
+obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h
index 6a882891d91c..9e3bb8a8ee40 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511.h
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h
@@ -209,10 +209,16 @@
#define ADV7511_REG_CEC_TX_ENABLE 0x11
#define ADV7511_REG_CEC_TX_RETRY 0x12
#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14
-#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15
-#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16
-#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25
-#define ADV7511_REG_CEC_RX_ENABLE 0x26
+#define ADV7511_REG_CEC_RX1_FRAME_HDR 0x15
+#define ADV7511_REG_CEC_RX1_FRAME_DATA0 0x16
+#define ADV7511_REG_CEC_RX1_FRAME_LEN 0x25
+#define ADV7511_REG_CEC_RX_STATUS 0x26
+#define ADV7511_REG_CEC_RX2_FRAME_HDR 0x27
+#define ADV7511_REG_CEC_RX2_FRAME_DATA0 0x28
+#define ADV7511_REG_CEC_RX2_FRAME_LEN 0x37
+#define ADV7511_REG_CEC_RX3_FRAME_HDR 0x38
+#define ADV7511_REG_CEC_RX3_FRAME_DATA0 0x39
+#define ADV7511_REG_CEC_RX3_FRAME_LEN 0x48
#define ADV7511_REG_CEC_RX_BUFFERS 0x4a
#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b
#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c
@@ -220,6 +226,18 @@
#define ADV7511_REG_CEC_CLK_DIV 0x4e
#define ADV7511_REG_CEC_SOFT_RESET 0x50
+static const u8 ADV7511_REG_CEC_RX_FRAME_HDR[] = {
+ ADV7511_REG_CEC_RX1_FRAME_HDR,
+ ADV7511_REG_CEC_RX2_FRAME_HDR,
+ ADV7511_REG_CEC_RX3_FRAME_HDR,
+};
+
+static const u8 ADV7511_REG_CEC_RX_FRAME_LEN[] = {
+ ADV7511_REG_CEC_RX1_FRAME_LEN,
+ ADV7511_REG_CEC_RX2_FRAME_LEN,
+ ADV7511_REG_CEC_RX3_FRAME_LEN,
+};
+
#define ADV7533_REG_CEC_OFFSET 0x70
enum adv7511_input_clock {
@@ -335,6 +353,7 @@ struct adv7511 {
struct regmap *regmap;
struct regmap *regmap_cec;
+ unsigned int reg_cec_offset;
enum drm_connector_status status;
bool powered;
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
index 28d9becc939c..399f625a50c8 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
@@ -17,12 +17,12 @@
#define ADV7511_INT1_CEC_MASK \
(ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \
- ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1)
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1 | \
+ ADV7511_INT1_CEC_RX_READY2 | ADV7511_INT1_CEC_RX_READY3)
static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
unsigned int val;
if (regmap_read(adv7511->regmap_cec,
@@ -71,26 +71,16 @@ static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
}
}
-void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
+static void adv7511_cec_rx(struct adv7511 *adv7511, int rx_buf)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
- const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
- ADV7511_INT1_CEC_TX_ARBIT_LOST |
- ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ unsigned int offset = adv7511->reg_cec_offset;
struct cec_msg msg = {};
unsigned int len;
unsigned int val;
u8 i;
- if (irq1 & irq_tx_mask)
- adv_cec_tx_raw_status(adv7511, irq1);
-
- if (!(irq1 & ADV7511_INT1_CEC_RX_READY1))
- return;
-
if (regmap_read(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len))
+ ADV7511_REG_CEC_RX_FRAME_LEN[rx_buf] + offset, &len))
return;
msg.len = len & 0x1f;
@@ -103,23 +93,80 @@ void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
for (i = 0; i < msg.len; i++) {
regmap_read(adv7511->regmap_cec,
- i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val);
+ i + ADV7511_REG_CEC_RX_FRAME_HDR[rx_buf] + offset,
+ &val);
msg.msg[i] = val;
}
- /* toggle to re-enable rx 1 */
- regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 1);
- regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ /* Toggle RX Ready Clear bit to re-enable this RX buffer */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf),
+ BIT(rx_buf));
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf), 0);
+
cec_received_msg(adv7511->cec_adap, &msg);
}
+void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
+{
+ unsigned int offset = adv7511->reg_cec_offset;
+ const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
+ ADV7511_INT1_CEC_TX_ARBIT_LOST |
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ const u32 irq_rx_mask = ADV7511_INT1_CEC_RX_READY1 |
+ ADV7511_INT1_CEC_RX_READY2 |
+ ADV7511_INT1_CEC_RX_READY3;
+ unsigned int rx_status;
+ int rx_order[3] = { -1, -1, -1 };
+ int i;
+
+ if (irq1 & irq_tx_mask)
+ adv_cec_tx_raw_status(adv7511, irq1);
+
+ if (!(irq1 & irq_rx_mask))
+ return;
+
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_STATUS + offset, &rx_status))
+ return;
+
+ /*
+ * ADV7511_REG_CEC_RX_STATUS[5:0] contains the reception order of RX
+ * buffers 0, 1, and 2 in bits [1:0], [3:2], and [5:4] respectively.
+ * The values are to be interpreted as follows:
+ *
+ * 0 = buffer unused
+ * 1 = buffer contains oldest received frame (if applicable)
+ * 2 = buffer contains second oldest received frame (if applicable)
+ * 3 = buffer contains third oldest received frame (if applicable)
+ *
+ * Fill rx_order with the sequence of RX buffer indices to
+ * read from in order, where -1 indicates that there are no
+ * more buffers to process.
+ */
+ for (i = 0; i < 3; i++) {
+ unsigned int timestamp = (rx_status >> (2 * i)) & 0x3;
+
+ if (timestamp)
+ rx_order[timestamp - 1] = i;
+ }
+
+ /* Read CEC RX buffers in the appropriate order as prescribed above */
+ for (i = 0; i < 3; i++) {
+ int rx_buf = rx_order[i];
+
+ if (rx_buf < 0)
+ break;
+
+ adv7511_cec_rx(adv7511, rx_buf);
+ }
+}
+
static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
if (adv7511->i2c_cec == NULL)
return -EIO;
@@ -129,11 +176,11 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
0x03, 0x01);
- /* legacy mode and clear all rx buffers */
+ /* non-legacy mode and clear all rx buffers */
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x0f);
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08);
/* initially disable tx */
regmap_update_bits(adv7511->regmap_cec,
ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0);
@@ -141,7 +188,7 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
/* tx: ready */
/* tx: arbitration lost */
/* tx: retry timeout */
- /* rx: ready 1 */
+ /* rx: ready 1-3 */
regmap_update_bits(adv7511->regmap,
ADV7511_REG_INT_ENABLE(1), 0x3f,
ADV7511_INT1_CEC_MASK);
@@ -165,8 +212,7 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
unsigned int i, free_idx = ADV7511_MAX_ADDRS;
if (!adv7511->cec_enabled_adap)
@@ -235,8 +281,7 @@ static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct adv7511 *adv7511 = cec_get_drvdata(adap);
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
u8 len = msg->len;
unsigned int i;
@@ -289,8 +334,7 @@ static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
{
- unsigned int offset = adv7511->type == ADV7533 ?
- ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int offset = adv7511->reg_cec_offset;
int ret = adv7511_cec_parse_dt(dev, adv7511);
if (ret)
@@ -310,9 +354,9 @@ int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_SOFT_RESET + offset, 0x00);
- /* legacy mode */
+ /* non-legacy mode - use all three RX buffers */
regmap_write(adv7511->regmap_cec,
- ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00);
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08);
regmap_write(adv7511->regmap_cec,
ADV7511_REG_CEC_CLK_DIV + offset,
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
index 005bf18682ff..5bb9300040dd 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
@@ -1027,14 +1027,19 @@ static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg)
struct i2c_client *i2c = to_i2c_client(dev);
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
- if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
- reg -= ADV7533_REG_CEC_OFFSET;
+ reg -= adv7511->reg_cec_offset;
switch (reg) {
- case ADV7511_REG_CEC_RX_FRAME_HDR:
- case ADV7511_REG_CEC_RX_FRAME_DATA0...
- ADV7511_REG_CEC_RX_FRAME_DATA0 + 14:
- case ADV7511_REG_CEC_RX_FRAME_LEN:
+ case ADV7511_REG_CEC_RX1_FRAME_HDR:
+ case ADV7511_REG_CEC_RX1_FRAME_DATA0 ... ADV7511_REG_CEC_RX1_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX1_FRAME_LEN:
+ case ADV7511_REG_CEC_RX2_FRAME_HDR:
+ case ADV7511_REG_CEC_RX2_FRAME_DATA0 ... ADV7511_REG_CEC_RX2_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX2_FRAME_LEN:
+ case ADV7511_REG_CEC_RX3_FRAME_HDR:
+ case ADV7511_REG_CEC_RX3_FRAME_DATA0 ... ADV7511_REG_CEC_RX3_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX3_FRAME_LEN:
+ case ADV7511_REG_CEC_RX_STATUS:
case ADV7511_REG_CEC_RX_BUFFERS:
case ADV7511_REG_CEC_TX_LOW_DRV_CNT:
return true;
@@ -1073,6 +1078,8 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv)
ret = adv7533_patch_cec_registers(adv);
if (ret)
goto err;
+
+ adv->reg_cec_offset = ADV7533_REG_CEC_OFFSET;
}
return 0;
@@ -1292,8 +1299,10 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
goto err_unregister_cec;
adv7511->bridge.funcs = &adv7511_bridge_funcs;
- adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID
- | DRM_BRIDGE_OP_HPD;
+ adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID;
+ if (adv7511->i2c_main->irq)
+ adv7511->bridge.ops |= DRM_BRIDGE_OP_HPD;
+
adv7511->bridge.of_node = dev->of_node;
adv7511->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
@@ -1313,6 +1322,7 @@ err_unregister_audio:
adv7511_audio_exit(adv7511);
drm_bridge_remove(&adv7511->bridge);
err_unregister_cec:
+ cec_unregister_adapter(adv7511->cec_adap);
i2c_unregister_device(adv7511->i2c_cec);
clk_disable_unprepare(adv7511->cec_clk);
err_i2c_unregister_packet:
diff --git a/drivers/gpu/drm/bridge/analogix/Kconfig b/drivers/gpu/drm/bridge/analogix/Kconfig
index cc0aa6572d98..173dada218ec 100644
--- a/drivers/gpu/drm/bridge/analogix/Kconfig
+++ b/drivers/gpu/drm/bridge/analogix/Kconfig
@@ -3,7 +3,8 @@ config DRM_ANALOGIX_ANX6345
tristate "Analogix ANX6345 bridge"
depends on OF
select DRM_ANALOGIX_DP
- select DRM_DP_HELPER
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_KMS_HELPER
select REGMAP_I2C
help
@@ -15,7 +16,8 @@ config DRM_ANALOGIX_ANX6345
config DRM_ANALOGIX_ANX78XX
tristate "Analogix ANX78XX bridge"
select DRM_ANALOGIX_DP
- select DRM_DP_HELPER
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_KMS_HELPER
select REGMAP_I2C
help
@@ -32,8 +34,10 @@ config DRM_ANALOGIX_ANX7625
tristate "Analogix Anx7625 MIPI to DP interface support"
depends on DRM
depends on OF
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HDCP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_DP_AUX_BUS
- select DRM_DP_HELPER
select DRM_MIPI_DSI
help
ANX7625 is an ultra-low power 4K mobile HD transmitter
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
index 94e56a2e91f2..ae3d6e9a606c 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c
@@ -18,11 +18,11 @@
#include <linux/regulator/consumer.h>
#include <linux/types.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c
index 2768b41c48e9..d2fc8676fab6 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c
@@ -18,10 +18,10 @@
#include <linux/regulator/consumer.h>
#include <linux/types.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c
index e8297168bfef..b1e482994ffe 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c
@@ -7,8 +7,8 @@
*/
#include <linux/regmap.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_print.h>
#include "analogix-i2c-dptx.h"
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
index eb590fb8e8d0..b97f6e8f0f6b 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
@@ -1119,9 +1119,7 @@ static int analogix_dp_get_modes(struct drm_connector *connector)
return 0;
}
- pm_runtime_get_sync(dp->dev);
edid = drm_get_edid(connector, &dp->aux.ddc);
- pm_runtime_put(dp->dev);
if (edid) {
drm_connector_update_edid_property(&dp->connector,
edid);
@@ -1632,8 +1630,20 @@ static ssize_t analogix_dpaux_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
{
struct analogix_dp_device *dp = to_dp(aux);
+ int ret;
+
+ pm_runtime_get_sync(dp->dev);
+
+ ret = analogix_dp_detect_hpd(dp);
+ if (ret)
+ goto out;
- return analogix_dp_transfer(dp, msg);
+ ret = analogix_dp_transfer(dp, msg);
+out:
+ pm_runtime_mark_last_busy(dp->dev);
+ pm_runtime_put_autosuspend(dp->dev);
+
+ return ret;
}
struct analogix_dp_device *
@@ -1698,8 +1708,10 @@ analogix_dp_probe(struct device *dev, struct analogix_dp_plat_data *plat_data)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dp->reg_base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(dp->reg_base))
- return ERR_CAST(dp->reg_base);
+ if (IS_ERR(dp->reg_base)) {
+ ret = PTR_ERR(dp->reg_base);
+ goto err_disable_clk;
+ }
dp->force_hpd = of_property_read_bool(dev->of_node, "force-hpd");
@@ -1711,7 +1723,8 @@ analogix_dp_probe(struct device *dev, struct analogix_dp_plat_data *plat_data)
if (IS_ERR(dp->hpd_gpiod)) {
dev_err(dev, "error getting HDP GPIO: %ld\n",
PTR_ERR(dp->hpd_gpiod));
- return ERR_CAST(dp->hpd_gpiod);
+ ret = PTR_ERR(dp->hpd_gpiod);
+ goto err_disable_clk;
}
if (dp->hpd_gpiod) {
@@ -1731,7 +1744,8 @@ analogix_dp_probe(struct device *dev, struct analogix_dp_plat_data *plat_data)
if (dp->irq == -ENXIO) {
dev_err(&pdev->dev, "failed to get irq\n");
- return ERR_PTR(-ENODEV);
+ ret = -ENODEV;
+ goto err_disable_clk;
}
ret = devm_request_threaded_irq(&pdev->dev, dp->irq,
@@ -1740,11 +1754,15 @@ analogix_dp_probe(struct device *dev, struct analogix_dp_plat_data *plat_data)
irq_flags, "analogix-dp", dp);
if (ret) {
dev_err(&pdev->dev, "failed to request irq\n");
- return ERR_PTR(ret);
+ goto err_disable_clk;
}
disable_irq(dp->irq);
return dp;
+
+err_disable_clk:
+ clk_disable_unprepare(dp->clock);
+ return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(analogix_dp_probe);
@@ -1764,6 +1782,8 @@ int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev)
if (ret)
return ret;
+ pm_runtime_use_autosuspend(dp->dev);
+ pm_runtime_set_autosuspend_delay(dp->dev, 100);
pm_runtime_enable(dp->dev);
ret = analogix_dp_create_bridge(drm_dev, dp);
@@ -1775,6 +1795,7 @@ int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev)
return 0;
err_disable_pm_runtime:
+ pm_runtime_dont_use_autosuspend(dp->dev);
pm_runtime_disable(dp->dev);
drm_dp_aux_unregister(&dp->aux);
@@ -1793,6 +1814,7 @@ void analogix_dp_unbind(struct analogix_dp_device *dp)
}
drm_dp_aux_unregister(&dp->aux);
+ pm_runtime_dont_use_autosuspend(dp->dev);
pm_runtime_disable(dp->dev);
}
EXPORT_SYMBOL_GPL(analogix_dp_unbind);
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
index 32665203a6ae..433f2d7efa0c 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h
@@ -9,8 +9,8 @@
#ifndef _ANALOGIX_DP_CORE_H
#define _ANALOGIX_DP_CORE_H
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_crtc.h>
-#include <drm/dp/drm_dp_helper.h>
#define DP_TIMEOUT_LOOP_COUNT 100
#define MAX_CR_LOOP 5
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c
index 31ecf5626f1d..53a5da6c49dd 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.c
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.c
@@ -21,13 +21,13 @@
#include <linux/of_graph.h>
#include <linux/of_platform.h>
+#include <drm/display/drm_dp_aux_bus.h>
+#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdcp_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc_helper.h>
-#include <drm/dp/drm_dp_aux_bus.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_edid.h>
-#include <drm/drm_hdcp.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
@@ -874,7 +874,10 @@ static int anx7625_hdcp_enable(struct anx7625_data *ctx)
}
/* Read downstream capability */
- anx7625_aux_trans(ctx, DP_AUX_NATIVE_READ, 0x68028, 1, &bcap);
+ ret = anx7625_aux_trans(ctx, DP_AUX_NATIVE_READ, 0x68028, 1, &bcap);
+ if (ret < 0)
+ return ret;
+
if (!(bcap & 0x01)) {
pr_warn("downstream not support HDCP 1.4, cap(%x).\n", bcap);
return 0;
@@ -921,12 +924,20 @@ static void anx7625_dp_start(struct anx7625_data *ctx)
{
int ret;
struct device *dev = &ctx->client->dev;
+ u8 data;
if (!ctx->display_timing_valid) {
DRM_DEV_ERROR(dev, "mipi not set display timing yet.\n");
return;
}
+ dev_dbg(dev, "set downstream sink into normal\n");
+ /* Downstream sink enter into normal mode */
+ data = 1;
+ ret = anx7625_aux_trans(ctx, DP_AUX_NATIVE_WRITE, 0x000600, 1, &data);
+ if (ret < 0)
+ dev_err(dev, "IO error : set sink into normal mode fail\n");
+
/* Disable HDCP */
anx7625_write_and(ctx, ctx->i2c.rx_p1_client, 0xee, 0x9f);
@@ -1475,12 +1486,12 @@ static void anx7625_dp_adjust_swing(struct anx7625_data *ctx)
for (i = 0; i < ctx->pdata.dp_lane0_swing_reg_cnt; i++)
anx7625_reg_write(ctx, ctx->i2c.tx_p1_client,
DP_TX_LANE0_SWING_REG0 + i,
- ctx->pdata.lane0_reg_data[i] & 0xFF);
+ ctx->pdata.lane0_reg_data[i]);
for (i = 0; i < ctx->pdata.dp_lane1_swing_reg_cnt; i++)
anx7625_reg_write(ctx, ctx->i2c.tx_p1_client,
DP_TX_LANE1_SWING_REG0 + i,
- ctx->pdata.lane1_reg_data[i] & 0xFF);
+ ctx->pdata.lane1_reg_data[i]);
}
static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on)
@@ -1587,8 +1598,8 @@ static int anx7625_get_swing_setting(struct device *dev,
num_regs = DP_TX_SWING_REG_CNT;
pdata->dp_lane0_swing_reg_cnt = num_regs;
- of_property_read_u32_array(dev->of_node, "analogix,lane0-swing",
- pdata->lane0_reg_data, num_regs);
+ of_property_read_u8_array(dev->of_node, "analogix,lane0-swing",
+ pdata->lane0_reg_data, num_regs);
}
if (of_get_property(dev->of_node,
@@ -1597,8 +1608,8 @@ static int anx7625_get_swing_setting(struct device *dev,
num_regs = DP_TX_SWING_REG_CNT;
pdata->dp_lane1_swing_reg_cnt = num_regs;
- of_property_read_u32_array(dev->of_node, "analogix,lane1-swing",
- pdata->lane1_reg_data, num_regs);
+ of_property_read_u8_array(dev->of_node, "analogix,lane1-swing",
+ pdata->lane1_reg_data, num_regs);
}
return 0;
@@ -1608,8 +1619,6 @@ static int anx7625_parse_dt(struct device *dev,
struct anx7625_platform_data *pdata)
{
struct device_node *np = dev->of_node, *ep0;
- struct drm_panel *panel;
- int ret;
int bus_type, mipi_lanes;
anx7625_get_swing_setting(dev, pdata);
@@ -1646,18 +1655,14 @@ static int anx7625_parse_dt(struct device *dev,
if (of_property_read_bool(np, "analogix,audio-enable"))
pdata->audio_en = 1;
- ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
- if (ret < 0) {
- if (ret == -ENODEV)
+ pdata->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
+ if (IS_ERR(pdata->panel_bridge)) {
+ if (PTR_ERR(pdata->panel_bridge) == -ENODEV)
return 0;
- return ret;
- }
- if (!panel)
- return -ENODEV;
- pdata->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
- if (IS_ERR(pdata->panel_bridge))
return PTR_ERR(pdata->panel_bridge);
+ }
+
DRM_DEV_DEBUG_DRIVER(dev, "get panel node.\n");
return 0;
@@ -1927,14 +1932,14 @@ static int anx7625_audio_get_eld(struct device *dev, void *data,
struct anx7625_data *ctx = dev_get_drvdata(dev);
if (!ctx->connector) {
- dev_err(dev, "connector not initial\n");
- return -EINVAL;
+ /* Pass en empty ELD if connector not available */
+ memset(buf, 0, len);
+ } else {
+ dev_dbg(dev, "audio copy eld\n");
+ memcpy(buf, ctx->connector->eld,
+ min(sizeof(ctx->connector->eld), len));
}
- dev_dbg(dev, "audio copy eld\n");
- memcpy(buf, ctx->connector->eld,
- min(sizeof(ctx->connector->eld), len));
-
return 0;
}
@@ -2011,7 +2016,8 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
- MIPI_DSI_MODE_VIDEO_HSE;
+ MIPI_DSI_MODE_VIDEO_HSE |
+ MIPI_DSI_HS_PKT_END_ALIGNED;
ret = devm_mipi_dsi_attach(dev, dsi);
if (ret) {
@@ -2654,7 +2660,7 @@ static int anx7625_i2c_probe(struct i2c_client *client,
if (ret) {
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(dev, "fail to parse DT : %d\n", ret);
- return ret;
+ goto free_wq;
}
if (anx7625_register_i2c_dummy_clients(platform, client) != 0) {
@@ -2669,7 +2675,7 @@ static int anx7625_i2c_probe(struct i2c_client *client,
pm_suspend_ignore_children(dev, true);
ret = devm_add_action_or_reset(dev, anx7625_runtime_disable, dev);
if (ret)
- return ret;
+ goto free_wq;
if (!platform->pdata.low_power_mode) {
anx7625_disable_pd_protocol(platform);
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h
index edbbfe410a56..e257a84db962 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.h
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.h
@@ -426,9 +426,9 @@ struct anx7625_platform_data {
int mipi_lanes;
int audio_en;
int dp_lane0_swing_reg_cnt;
- int lane0_reg_data[DP_TX_SWING_REG_CNT];
+ u8 lane0_reg_data[DP_TX_SWING_REG_CNT];
int dp_lane1_swing_reg_cnt;
- int lane1_reg_data[DP_TX_SWING_REG_CNT];
+ u8 lane1_reg_data[DP_TX_SWING_REG_CNT];
u32 low_power_mode;
struct device_node *mipi_host_node;
};
diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig
index de697bade05e..1d06182bea71 100644
--- a/drivers/gpu/drm/bridge/cadence/Kconfig
+++ b/drivers/gpu/drm/bridge/cadence/Kconfig
@@ -1,7 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_CDNS_MHDP8546
tristate "Cadence DPI/DP bridge"
- select DRM_DP_HELPER
+ select DRM_DISPLAY_DP_HELPER
+ select DRM_DISPLAY_HDCP_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_KMS_HELPER
select DRM_PANEL_BRIDGE
depends on OF
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
index ac18e15aa167..67f0f444b4e8 100644
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c
@@ -35,14 +35,14 @@
#include <linux/slab.h>
#include <linux/wait.h>
+#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdcp_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc_helper.h>
-#include <drm/dp/drm_dp_helper.h>
-#include <drm/drm_hdcp.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h
index fc77f987c835..bedddd510d17 100644
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h
@@ -15,9 +15,9 @@
#include <linux/mutex.h>
#include <linux/spinlock.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
-#include <drm/dp/drm_dp_helper.h>
struct clk;
struct device;
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c
index fccd6fbcc257..946212a95598 100644
--- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c
@@ -11,7 +11,7 @@
#include <asm/unaligned.h>
-#include <drm/drm_hdcp.h>
+#include <drm/display/drm_hdcp_helper.h>
#include "cdns-mhdp8546-hdcp.h"
diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c
index d9b7f48b99fb..47dea657a752 100644
--- a/drivers/gpu/drm/bridge/chipone-icn6211.c
+++ b/drivers/gpu/drm/bridge/chipone-icn6211.c
@@ -11,12 +11,25 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
-#include <video/mipi_display.h>
-
+#define VENDOR_ID 0x00
+#define DEVICE_ID_H 0x01
+#define DEVICE_ID_L 0x02
+#define VERSION_ID 0x03
+#define FIRMWARE_VERSION 0x08
+#define CONFIG_FINISH 0x09
+#define PD_CTRL(n) (0x0a + ((n) & 0x3)) /* 0..3 */
+#define RST_CTRL(n) (0x0e + ((n) & 0x1)) /* 0..1 */
+#define SYS_CTRL(n) (0x10 + ((n) & 0x7)) /* 0..4 */
+#define RGB_DRV(n) (0x18 + ((n) & 0x3)) /* 0..3 */
+#define RGB_DLY(n) (0x1c + ((n) & 0x1)) /* 0..1 */
+#define RGB_TEST_CTRL 0x1e
+#define ATE_PLL_EN 0x1f
#define HACTIVE_LI 0x20
#define VACTIVE_LI 0x21
#define VACTIVE_HACTIVE_HI 0x22
@@ -24,19 +37,186 @@
#define HSYNC_LI 0x24
#define HBP_LI 0x25
#define HFP_HSW_HBP_HI 0x26
+#define HFP_HSW_HBP_HI_HFP(n) (((n) & 0x300) >> 4)
+#define HFP_HSW_HBP_HI_HS(n) (((n) & 0x300) >> 6)
+#define HFP_HSW_HBP_HI_HBP(n) (((n) & 0x300) >> 8)
#define VFP 0x27
#define VSYNC 0x28
#define VBP 0x29
+#define BIST_POL 0x2a
+#define BIST_POL_BIST_MODE(n) (((n) & 0xf) << 4)
+#define BIST_POL_BIST_GEN BIT(3)
+#define BIST_POL_HSYNC_POL BIT(2)
+#define BIST_POL_VSYNC_POL BIT(1)
+#define BIST_POL_DE_POL BIT(0)
+#define BIST_RED 0x2b
+#define BIST_GREEN 0x2c
+#define BIST_BLUE 0x2d
+#define BIST_CHESS_X 0x2e
+#define BIST_CHESS_Y 0x2f
+#define BIST_CHESS_XY_H 0x30
+#define BIST_FRAME_TIME_L 0x31
+#define BIST_FRAME_TIME_H 0x32
+#define FIFO_MAX_ADDR_LOW 0x33
+#define SYNC_EVENT_DLY 0x34
+#define HSW_MIN 0x35
+#define HFP_MIN 0x36
+#define LOGIC_RST_NUM 0x37
+#define OSC_CTRL(n) (0x48 + ((n) & 0x7)) /* 0..5 */
+#define BG_CTRL 0x4e
+#define LDO_PLL 0x4f
+#define PLL_CTRL(n) (0x50 + ((n) & 0xf)) /* 0..15 */
+#define PLL_CTRL_6_EXTERNAL 0x90
+#define PLL_CTRL_6_MIPI_CLK 0x92
+#define PLL_CTRL_6_INTERNAL 0x93
+#define PLL_REM(n) (0x60 + ((n) & 0x3)) /* 0..2 */
+#define PLL_DIV(n) (0x63 + ((n) & 0x3)) /* 0..2 */
+#define PLL_FRAC(n) (0x66 + ((n) & 0x3)) /* 0..2 */
+#define PLL_INT(n) (0x69 + ((n) & 0x1)) /* 0..1 */
+#define PLL_REF_DIV 0x6b
+#define PLL_REF_DIV_P(n) ((n) & 0xf)
+#define PLL_REF_DIV_Pe BIT(4)
+#define PLL_REF_DIV_S(n) (((n) & 0x7) << 5)
+#define PLL_SSC_P(n) (0x6c + ((n) & 0x3)) /* 0..2 */
+#define PLL_SSC_STEP(n) (0x6f + ((n) & 0x3)) /* 0..2 */
+#define PLL_SSC_OFFSET(n) (0x72 + ((n) & 0x3)) /* 0..3 */
+#define GPIO_OEN 0x79
+#define MIPI_CFG_PW 0x7a
+#define MIPI_CFG_PW_CONFIG_DSI 0xc1
+#define MIPI_CFG_PW_CONFIG_I2C 0x3e
+#define GPIO_SEL(n) (0x7b + ((n) & 0x1)) /* 0..1 */
+#define IRQ_SEL 0x7d
+#define DBG_SEL 0x7e
+#define DBG_SIGNAL 0x7f
+#define MIPI_ERR_VECTOR_L 0x80
+#define MIPI_ERR_VECTOR_H 0x81
+#define MIPI_ERR_VECTOR_EN_L 0x82
+#define MIPI_ERR_VECTOR_EN_H 0x83
+#define MIPI_MAX_SIZE_L 0x84
+#define MIPI_MAX_SIZE_H 0x85
+#define DSI_CTRL 0x86
+#define DSI_CTRL_UNKNOWN 0x28
+#define DSI_CTRL_DSI_LANES(n) ((n) & 0x3)
+#define MIPI_PN_SWAP 0x87
+#define MIPI_PN_SWAP_CLK BIT(4)
+#define MIPI_PN_SWAP_D(n) BIT((n) & 0x3)
+#define MIPI_SOT_SYNC_BIT_(n) (0x88 + ((n) & 0x1)) /* 0..1 */
+#define MIPI_ULPS_CTRL 0x8a
+#define MIPI_CLK_CHK_VAR 0x8e
+#define MIPI_CLK_CHK_INI 0x8f
+#define MIPI_T_TERM_EN 0x90
+#define MIPI_T_HS_SETTLE 0x91
+#define MIPI_T_TA_SURE_PRE 0x92
+#define MIPI_T_LPX_SET 0x94
+#define MIPI_T_CLK_MISS 0x95
+#define MIPI_INIT_TIME_L 0x96
+#define MIPI_INIT_TIME_H 0x97
+#define MIPI_T_CLK_TERM_EN 0x99
+#define MIPI_T_CLK_SETTLE 0x9a
+#define MIPI_TO_HS_RX_L 0x9e
+#define MIPI_TO_HS_RX_H 0x9f
+#define MIPI_PHY_(n) (0xa0 + ((n) & 0x7)) /* 0..5 */
+#define MIPI_PD_RX 0xb0
+#define MIPI_PD_TERM 0xb1
+#define MIPI_PD_HSRX 0xb2
+#define MIPI_PD_LPTX 0xb3
+#define MIPI_PD_LPRX 0xb4
+#define MIPI_PD_CK_LANE 0xb5
+#define MIPI_FORCE_0 0xb6
+#define MIPI_RST_CTRL 0xb7
+#define MIPI_RST_NUM 0xb8
+#define MIPI_DBG_SET_(n) (0xc0 + ((n) & 0xf)) /* 0..9 */
+#define MIPI_DBG_SEL 0xe0
+#define MIPI_DBG_DATA 0xe1
+#define MIPI_ATE_TEST_SEL 0xe2
+#define MIPI_ATE_STATUS_(n) (0xe3 + ((n) & 0x1)) /* 0..1 */
+#define MIPI_ATE_STATUS_1 0xe4
+#define ICN6211_MAX_REGISTER MIPI_ATE_STATUS(1)
struct chipone {
struct device *dev;
+ struct regmap *regmap;
+ struct i2c_client *client;
struct drm_bridge bridge;
struct drm_display_mode mode;
struct drm_bridge *panel_bridge;
+ struct mipi_dsi_device *dsi;
struct gpio_desc *enable_gpio;
struct regulator *vdd1;
struct regulator *vdd2;
struct regulator *vdd3;
+ bool interface_i2c;
+};
+
+static const struct regmap_range chipone_dsi_readable_ranges[] = {
+ regmap_reg_range(VENDOR_ID, VERSION_ID),
+ regmap_reg_range(FIRMWARE_VERSION, PLL_SSC_OFFSET(3)),
+ regmap_reg_range(GPIO_OEN, MIPI_ULPS_CTRL),
+ regmap_reg_range(MIPI_CLK_CHK_VAR, MIPI_T_TA_SURE_PRE),
+ regmap_reg_range(MIPI_T_LPX_SET, MIPI_INIT_TIME_H),
+ regmap_reg_range(MIPI_T_CLK_TERM_EN, MIPI_T_CLK_SETTLE),
+ regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY_(5)),
+ regmap_reg_range(MIPI_PD_RX, MIPI_RST_NUM),
+ regmap_reg_range(MIPI_DBG_SET_(0), MIPI_DBG_SET_(9)),
+ regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS_(1)),
+};
+
+static const struct regmap_access_table chipone_dsi_readable_table = {
+ .yes_ranges = chipone_dsi_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(chipone_dsi_readable_ranges),
+};
+
+static const struct regmap_range chipone_dsi_writeable_ranges[] = {
+ regmap_reg_range(CONFIG_FINISH, PLL_SSC_OFFSET(3)),
+ regmap_reg_range(GPIO_OEN, MIPI_ULPS_CTRL),
+ regmap_reg_range(MIPI_CLK_CHK_VAR, MIPI_T_TA_SURE_PRE),
+ regmap_reg_range(MIPI_T_LPX_SET, MIPI_INIT_TIME_H),
+ regmap_reg_range(MIPI_T_CLK_TERM_EN, MIPI_T_CLK_SETTLE),
+ regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY_(5)),
+ regmap_reg_range(MIPI_PD_RX, MIPI_RST_NUM),
+ regmap_reg_range(MIPI_DBG_SET_(0), MIPI_DBG_SET_(9)),
+ regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS_(1)),
+};
+
+static const struct regmap_access_table chipone_dsi_writeable_table = {
+ .yes_ranges = chipone_dsi_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(chipone_dsi_writeable_ranges),
+};
+
+static const struct regmap_config chipone_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &chipone_dsi_readable_table,
+ .wr_table = &chipone_dsi_writeable_table,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = MIPI_ATE_STATUS_(1),
+};
+
+static int chipone_dsi_read(void *context,
+ const void *reg, size_t reg_size,
+ void *val, size_t val_size)
+{
+ struct mipi_dsi_device *dsi = context;
+ const u16 reg16 = (val_size << 8) | *(u8 *)reg;
+ int ret;
+
+ ret = mipi_dsi_generic_read(dsi, &reg16, 2, val, val_size);
+
+ return ret == val_size ? 0 : -EINVAL;
+}
+
+static int chipone_dsi_write(void *context, const void *data, size_t count)
+{
+ struct mipi_dsi_device *dsi = context;
+
+ return mipi_dsi_generic_write(dsi, data, 2);
+}
+
+static const struct regmap_bus chipone_dsi_regmap_bus = {
+ .read = chipone_dsi_read,
+ .write = chipone_dsi_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
+ .val_format_endian_default = REGMAP_ENDIAN_NATIVE,
};
static inline struct chipone *bridge_to_chipone(struct drm_bridge *bridge)
@@ -44,70 +224,202 @@ static inline struct chipone *bridge_to_chipone(struct drm_bridge *bridge)
return container_of(bridge, struct chipone, bridge);
}
-static inline int chipone_dsi_write(struct chipone *icn, const void *seq,
- size_t len)
+static void chipone_readb(struct chipone *icn, u8 reg, u8 *val)
{
- struct mipi_dsi_device *dsi = to_mipi_dsi_device(icn->dev);
+ int ret, pval;
+
+ ret = regmap_read(icn->regmap, reg, &pval);
- return mipi_dsi_generic_write(dsi, seq, len);
+ *val = ret ? 0 : pval & 0xff;
}
-#define ICN6211_DSI(icn, seq...) \
- { \
- const u8 d[] = { seq }; \
- chipone_dsi_write(icn, d, ARRAY_SIZE(d)); \
+static int chipone_writeb(struct chipone *icn, u8 reg, u8 val)
+{
+ return regmap_write(icn->regmap, reg, val);
+}
+
+static void chipone_configure_pll(struct chipone *icn,
+ const struct drm_display_mode *mode)
+{
+ unsigned int best_p = 0, best_m = 0, best_s = 0;
+ unsigned int mode_clock = mode->clock * 1000;
+ unsigned int delta, min_delta = 0xffffffff;
+ unsigned int freq_p, freq_s, freq_out;
+ unsigned int p_min, p_max;
+ unsigned int p, m, s;
+ unsigned int fin;
+ bool best_p_pot;
+ u8 ref_div;
+
+ /*
+ * DSI byte clock frequency (input into PLL) is calculated as:
+ * DSI_CLK = mode clock * bpp / dsi_data_lanes / 8
+ *
+ * DPI pixel clock frequency (output from PLL) is mode clock.
+ *
+ * The chip contains fractional PLL which works as follows:
+ * DPI_CLK = ((DSI_CLK / P) * M) / S
+ * P is pre-divider, register PLL_REF_DIV[3:0] is 1:n divider
+ * register PLL_REF_DIV[4] is extra 1:2 divider
+ * M is integer multiplier, register PLL_INT(0) is multiplier
+ * S is post-divider, register PLL_REF_DIV[7:5] is 2^(n+1) divider
+ *
+ * It seems the PLL input clock after applying P pre-divider have
+ * to be lower than 20 MHz.
+ */
+ fin = mode_clock * mipi_dsi_pixel_format_to_bpp(icn->dsi->format) /
+ icn->dsi->lanes / 8; /* in Hz */
+
+ /* Minimum value of P predivider for PLL input in 5..20 MHz */
+ p_min = clamp(DIV_ROUND_UP(fin, 20000000), 1U, 31U);
+ p_max = clamp(fin / 5000000, 1U, 31U);
+
+ for (p = p_min; p < p_max; p++) { /* PLL_REF_DIV[4,3:0] */
+ if (p > 16 && p & 1) /* P > 16 uses extra /2 */
+ continue;
+ freq_p = fin / p;
+ if (freq_p == 0) /* Divider too high */
+ break;
+
+ for (s = 0; s < 0x7; s++) { /* PLL_REF_DIV[7:5] */
+ freq_s = freq_p / BIT(s + 1);
+ if (freq_s == 0) /* Divider too high */
+ break;
+
+ m = mode_clock / freq_s;
+
+ /* Multiplier is 8 bit */
+ if (m > 0xff)
+ continue;
+
+ /* Limit PLL VCO frequency to 1 GHz */
+ freq_out = (fin * m) / p;
+ if (freq_out > 1000000000)
+ continue;
+
+ /* Apply post-divider */
+ freq_out /= BIT(s + 1);
+
+ delta = abs(mode_clock - freq_out);
+ if (delta < min_delta) {
+ best_p = p;
+ best_m = m;
+ best_s = s;
+ min_delta = delta;
+ }
+ }
}
+ best_p_pot = !(best_p & 1);
+
+ dev_dbg(icn->dev,
+ "PLL: P[3:0]=%d P[4]=2*%d M=%d S[7:5]=2^%d delta=%d => DSI f_in=%d Hz ; DPI f_out=%d Hz\n",
+ best_p >> best_p_pot, best_p_pot, best_m, best_s + 1,
+ min_delta, fin, (fin * best_m) / (best_p << (best_s + 1)));
+
+ ref_div = PLL_REF_DIV_P(best_p >> best_p_pot) | PLL_REF_DIV_S(best_s);
+ if (best_p_pot) /* Prefer /2 pre-divider */
+ ref_div |= PLL_REF_DIV_Pe;
+
+ /* Clock source selection fixed to MIPI DSI clock lane */
+ chipone_writeb(icn, PLL_CTRL(6), PLL_CTRL_6_MIPI_CLK);
+ chipone_writeb(icn, PLL_REF_DIV, ref_div);
+ chipone_writeb(icn, PLL_INT(0), best_m);
+}
+
static void chipone_atomic_enable(struct drm_bridge *bridge,
struct drm_bridge_state *old_bridge_state)
{
struct chipone *icn = bridge_to_chipone(bridge);
+ struct drm_atomic_state *state = old_bridge_state->base.state;
struct drm_display_mode *mode = &icn->mode;
+ const struct drm_bridge_state *bridge_state;
+ u16 hfp, hbp, hsync;
+ u32 bus_flags;
+ u8 pol, id[4];
+
+ chipone_readb(icn, VENDOR_ID, id);
+ chipone_readb(icn, DEVICE_ID_H, id + 1);
+ chipone_readb(icn, DEVICE_ID_L, id + 2);
+ chipone_readb(icn, VERSION_ID, id + 3);
+
+ dev_dbg(icn->dev,
+ "Chip IDs: Vendor=0x%02x Device=0x%02x:0x%02x Version=0x%02x\n",
+ id[0], id[1], id[2], id[3]);
+
+ if (id[0] != 0xc1 || id[1] != 0x62 || id[2] != 0x11) {
+ dev_dbg(icn->dev, "Invalid Chip IDs, aborting configuration\n");
+ return;
+ }
- ICN6211_DSI(icn, 0x7a, 0xc1);
+ /* Get the DPI flags from the bridge state. */
+ bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+ bus_flags = bridge_state->output_bus_cfg.flags;
- ICN6211_DSI(icn, HACTIVE_LI, mode->hdisplay & 0xff);
+ if (icn->interface_i2c)
+ chipone_writeb(icn, MIPI_CFG_PW, MIPI_CFG_PW_CONFIG_I2C);
+ else
+ chipone_writeb(icn, MIPI_CFG_PW, MIPI_CFG_PW_CONFIG_DSI);
- ICN6211_DSI(icn, VACTIVE_LI, mode->vdisplay & 0xff);
+ chipone_writeb(icn, HACTIVE_LI, mode->hdisplay & 0xff);
- /**
+ chipone_writeb(icn, VACTIVE_LI, mode->vdisplay & 0xff);
+
+ /*
* lsb nibble: 2nd nibble of hdisplay
* msb nibble: 2nd nibble of vdisplay
*/
- ICN6211_DSI(icn, VACTIVE_HACTIVE_HI,
- ((mode->hdisplay >> 8) & 0xf) |
- (((mode->vdisplay >> 8) & 0xf) << 4));
+ chipone_writeb(icn, VACTIVE_HACTIVE_HI,
+ ((mode->hdisplay >> 8) & 0xf) |
+ (((mode->vdisplay >> 8) & 0xf) << 4));
+
+ hfp = mode->hsync_start - mode->hdisplay;
+ hsync = mode->hsync_end - mode->hsync_start;
+ hbp = mode->htotal - mode->hsync_end;
- ICN6211_DSI(icn, HFP_LI, mode->hsync_start - mode->hdisplay);
+ chipone_writeb(icn, HFP_LI, hfp & 0xff);
+ chipone_writeb(icn, HSYNC_LI, hsync & 0xff);
+ chipone_writeb(icn, HBP_LI, hbp & 0xff);
+ /* Top two bits of Horizontal Front porch/Sync/Back porch */
+ chipone_writeb(icn, HFP_HSW_HBP_HI,
+ HFP_HSW_HBP_HI_HFP(hfp) |
+ HFP_HSW_HBP_HI_HS(hsync) |
+ HFP_HSW_HBP_HI_HBP(hbp));
- ICN6211_DSI(icn, HSYNC_LI, mode->hsync_end - mode->hsync_start);
+ chipone_writeb(icn, VFP, mode->vsync_start - mode->vdisplay);
- ICN6211_DSI(icn, HBP_LI, mode->htotal - mode->hsync_end);
+ chipone_writeb(icn, VSYNC, mode->vsync_end - mode->vsync_start);
- ICN6211_DSI(icn, HFP_HSW_HBP_HI, 0x00);
+ chipone_writeb(icn, VBP, mode->vtotal - mode->vsync_end);
- ICN6211_DSI(icn, VFP, mode->vsync_start - mode->vdisplay);
+ /* dsi specific sequence */
+ chipone_writeb(icn, SYNC_EVENT_DLY, 0x80);
+ chipone_writeb(icn, HFP_MIN, hfp & 0xff);
- ICN6211_DSI(icn, VSYNC, mode->vsync_end - mode->vsync_start);
+ /* DSI data lane count */
+ chipone_writeb(icn, DSI_CTRL,
+ DSI_CTRL_UNKNOWN | DSI_CTRL_DSI_LANES(icn->dsi->lanes - 1));
- ICN6211_DSI(icn, VBP, mode->vtotal - mode->vsync_end);
+ chipone_writeb(icn, MIPI_PD_CK_LANE, 0xa0);
+ chipone_writeb(icn, PLL_CTRL(12), 0xff);
+ chipone_writeb(icn, MIPI_PN_SWAP, 0x00);
- /* dsi specific sequence */
- ICN6211_DSI(icn, MIPI_DCS_SET_TEAR_OFF, 0x80);
- ICN6211_DSI(icn, MIPI_DCS_SET_ADDRESS_MODE, 0x28);
- ICN6211_DSI(icn, 0xb5, 0xa0);
- ICN6211_DSI(icn, 0x5c, 0xff);
- ICN6211_DSI(icn, MIPI_DCS_SET_COLUMN_ADDRESS, 0x01);
- ICN6211_DSI(icn, MIPI_DCS_GET_POWER_SAVE, 0x92);
- ICN6211_DSI(icn, 0x6b, 0x71);
- ICN6211_DSI(icn, 0x69, 0x2b);
- ICN6211_DSI(icn, MIPI_DCS_ENTER_SLEEP_MODE, 0x40);
- ICN6211_DSI(icn, MIPI_DCS_EXIT_SLEEP_MODE, 0x98);
+ /* DPI HS/VS/DE polarity */
+ pol = ((mode->flags & DRM_MODE_FLAG_PHSYNC) ? BIST_POL_HSYNC_POL : 0) |
+ ((mode->flags & DRM_MODE_FLAG_PVSYNC) ? BIST_POL_VSYNC_POL : 0) |
+ ((bus_flags & DRM_BUS_FLAG_DE_HIGH) ? BIST_POL_DE_POL : 0);
+ chipone_writeb(icn, BIST_POL, pol);
+
+ /* Configure PLL settings */
+ chipone_configure_pll(icn, mode);
+
+ chipone_writeb(icn, SYS_CTRL(0), 0x40);
+ chipone_writeb(icn, SYS_CTRL(1), 0x88);
/* icn6211 specific sequence */
- ICN6211_DSI(icn, 0xb6, 0x20);
- ICN6211_DSI(icn, 0x51, 0x20);
- ICN6211_DSI(icn, 0x09, 0x10);
+ chipone_writeb(icn, MIPI_FORCE_0, 0x20);
+ chipone_writeb(icn, PLL_CTRL(1), 0x20);
+ chipone_writeb(icn, CONFIG_FINISH, 0x10);
usleep_range(10000, 11000);
}
@@ -168,6 +480,81 @@ static void chipone_mode_set(struct drm_bridge *bridge,
struct chipone *icn = bridge_to_chipone(bridge);
drm_mode_copy(&icn->mode, adjusted_mode);
+};
+
+static int chipone_dsi_attach(struct chipone *icn)
+{
+ struct mipi_dsi_device *dsi = icn->dsi;
+ struct device *dev = icn->dev;
+ struct device_node *endpoint;
+ int dsi_lanes, ret;
+
+ endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+ dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+ of_node_put(endpoint);
+
+ /*
+ * If the 'data-lanes' property does not exist in DT or is invalid,
+ * default to previously hard-coded behavior, which was 4 data lanes.
+ */
+ if (dsi_lanes >= 1 && dsi_lanes <= 4)
+ icn->dsi->lanes = dsi_lanes;
+ else
+ icn->dsi->lanes = 4;
+
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0)
+ dev_err(icn->dev, "failed to attach dsi\n");
+
+ return ret;
+}
+
+static int chipone_dsi_host_attach(struct chipone *icn)
+{
+ struct device *dev = icn->dev;
+ struct device_node *host_node;
+ struct device_node *endpoint;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ int ret = 0;
+
+ const struct mipi_dsi_device_info info = {
+ .type = "chipone",
+ .channel = 0,
+ .node = NULL,
+ };
+
+ endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+ host_node = of_graph_get_remote_port_parent(endpoint);
+ of_node_put(endpoint);
+
+ if (!host_node)
+ return -EINVAL;
+
+ host = of_find_mipi_dsi_host_by_node(host_node);
+ of_node_put(host_node);
+ if (!host) {
+ dev_err(dev, "failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi)) {
+ return dev_err_probe(dev, PTR_ERR(dsi),
+ "failed to create dsi device\n");
+ }
+
+ icn->dsi = dsi;
+
+ ret = chipone_dsi_attach(icn);
+ if (ret < 0)
+ mipi_dsi_device_unregister(dsi);
+
+ return ret;
}
static int chipone_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags)
@@ -177,6 +564,32 @@ static int chipone_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flag
return drm_bridge_attach(bridge->encoder, icn->panel_bridge, bridge, flags);
}
+#define MAX_INPUT_SEL_FORMATS 1
+
+static u32 *
+chipone_atomic_get_input_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;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ /* This is the DSI-end bus format */
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+ *num_input_fmts = 1;
+
+ return input_fmts;
+}
+
static const struct drm_bridge_funcs chipone_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -186,6 +599,7 @@ static const struct drm_bridge_funcs chipone_bridge_funcs = {
.atomic_post_disable = chipone_atomic_post_disable,
.mode_set = chipone_mode_set,
.attach = chipone_attach,
+ .atomic_get_input_bus_fmts = chipone_atomic_get_input_bus_fmts,
};
static int chipone_parse_dt(struct chipone *icn)
@@ -233,9 +647,8 @@ static int chipone_parse_dt(struct chipone *icn)
return 0;
}
-static int chipone_probe(struct mipi_dsi_device *dsi)
+static int chipone_common_probe(struct device *dev, struct chipone **icnr)
{
- struct device *dev = &dsi->dev;
struct chipone *icn;
int ret;
@@ -243,7 +656,6 @@ static int chipone_probe(struct mipi_dsi_device *dsi)
if (!icn)
return -ENOMEM;
- mipi_dsi_set_drvdata(dsi, icn);
icn->dev = dev;
ret = chipone_parse_dt(icn);
@@ -254,22 +666,66 @@ static int chipone_probe(struct mipi_dsi_device *dsi)
icn->bridge.type = DRM_MODE_CONNECTOR_DPI;
icn->bridge.of_node = dev->of_node;
- drm_bridge_add(&icn->bridge);
+ *icnr = icn;
- dsi->lanes = 4;
- dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ return ret;
+}
- ret = mipi_dsi_attach(dsi);
- if (ret < 0) {
+static int chipone_dsi_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct chipone *icn;
+ int ret;
+
+ ret = chipone_common_probe(dev, &icn);
+ if (ret)
+ return ret;
+
+ icn->regmap = devm_regmap_init(dev, &chipone_dsi_regmap_bus,
+ dsi, &chipone_regmap_config);
+ if (IS_ERR(icn->regmap))
+ return PTR_ERR(icn->regmap);
+
+ icn->interface_i2c = false;
+ icn->dsi = dsi;
+
+ mipi_dsi_set_drvdata(dsi, icn);
+
+ drm_bridge_add(&icn->bridge);
+
+ ret = chipone_dsi_attach(icn);
+ if (ret)
drm_bridge_remove(&icn->bridge);
- dev_err(dev, "failed to attach dsi\n");
- }
return ret;
}
-static int chipone_remove(struct mipi_dsi_device *dsi)
+static int chipone_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct chipone *icn;
+ int ret;
+
+ ret = chipone_common_probe(dev, &icn);
+ if (ret)
+ return ret;
+
+ icn->regmap = devm_regmap_init_i2c(client, &chipone_regmap_config);
+ if (IS_ERR(icn->regmap))
+ return PTR_ERR(icn->regmap);
+
+ icn->interface_i2c = true;
+ icn->client = client;
+ dev_set_drvdata(dev, icn);
+ i2c_set_clientdata(client, icn);
+
+ drm_bridge_add(&icn->bridge);
+
+ return chipone_dsi_host_attach(icn);
+}
+
+static int chipone_dsi_remove(struct mipi_dsi_device *dsi)
{
struct chipone *icn = mipi_dsi_get_drvdata(dsi);
@@ -285,16 +741,48 @@ static const struct of_device_id chipone_of_match[] = {
};
MODULE_DEVICE_TABLE(of, chipone_of_match);
-static struct mipi_dsi_driver chipone_driver = {
- .probe = chipone_probe,
- .remove = chipone_remove,
+static struct mipi_dsi_driver chipone_dsi_driver = {
+ .probe = chipone_dsi_probe,
+ .remove = chipone_dsi_remove,
.driver = {
.name = "chipone-icn6211",
.owner = THIS_MODULE,
.of_match_table = chipone_of_match,
},
};
-module_mipi_dsi_driver(chipone_driver);
+
+static struct i2c_device_id chipone_i2c_id[] = {
+ { "chipone,icn6211" },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, chipone_i2c_id);
+
+static struct i2c_driver chipone_i2c_driver = {
+ .probe = chipone_i2c_probe,
+ .id_table = chipone_i2c_id,
+ .driver = {
+ .name = "chipone-icn6211-i2c",
+ .of_match_table = chipone_of_match,
+ },
+};
+
+static int __init chipone_init(void)
+{
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_register(&chipone_dsi_driver);
+
+ return i2c_add_driver(&chipone_i2c_driver);
+}
+module_init(chipone_init);
+
+static void __exit chipone_exit(void)
+{
+ i2c_del_driver(&chipone_i2c_driver);
+
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_unregister(&chipone_dsi_driver);
+}
+module_exit(chipone_exit);
MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>");
MODULE_DESCRIPTION("Chipone ICN6211 MIPI-DSI to RGB Converter Bridge");
diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
index d24f5b90feab..e4d52a7e31b7 100644
--- a/drivers/gpu/drm/bridge/display-connector.c
+++ b/drivers/gpu/drm/bridge/display-connector.c
@@ -24,6 +24,7 @@ struct display_connector {
int hpd_irq;
struct regulator *dp_pwr;
+ struct gpio_desc *ddc_en;
};
static inline struct display_connector *
@@ -345,6 +346,17 @@ static int display_connector_probe(struct platform_device *pdev)
}
}
+ /* enable DDC */
+ if (type == DRM_MODE_CONNECTOR_HDMIA) {
+ conn->ddc_en = devm_gpiod_get_optional(&pdev->dev, "ddc-en",
+ GPIOD_OUT_HIGH);
+
+ if (IS_ERR(conn->ddc_en)) {
+ dev_err(&pdev->dev, "Couldn't get ddc-en gpio\n");
+ return PTR_ERR(conn->ddc_en);
+ }
+ }
+
conn->bridge.funcs = &display_connector_bridge_funcs;
conn->bridge.of_node = pdev->dev.of_node;
@@ -373,6 +385,9 @@ static int display_connector_remove(struct platform_device *pdev)
{
struct display_connector *conn = platform_get_drvdata(pdev);
+ if (conn->ddc_en)
+ gpiod_set_value(conn->ddc_en, 0);
+
if (conn->dp_pwr)
regulator_disable(conn->dp_pwr);
diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c
new file mode 100644
index 000000000000..b2675c769a55
--- /dev/null
+++ b/drivers/gpu/drm/bridge/fsl-ldb.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2022 Marek Vasut <marex@denx.de>
+ */
+
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#define LDB_CTRL 0x5c
+#define LDB_CTRL_CH0_ENABLE BIT(0)
+#define LDB_CTRL_CH0_DI_SELECT BIT(1)
+#define LDB_CTRL_CH1_ENABLE BIT(2)
+#define LDB_CTRL_CH1_DI_SELECT BIT(3)
+#define LDB_CTRL_SPLIT_MODE BIT(4)
+#define LDB_CTRL_CH0_DATA_WIDTH BIT(5)
+#define LDB_CTRL_CH0_BIT_MAPPING BIT(6)
+#define LDB_CTRL_CH1_DATA_WIDTH BIT(7)
+#define LDB_CTRL_CH1_BIT_MAPPING BIT(8)
+#define LDB_CTRL_DI0_VSYNC_POLARITY BIT(9)
+#define LDB_CTRL_DI1_VSYNC_POLARITY BIT(10)
+#define LDB_CTRL_REG_CH0_FIFO_RESET BIT(11)
+#define LDB_CTRL_REG_CH1_FIFO_RESET BIT(12)
+#define LDB_CTRL_ASYNC_FIFO_ENABLE BIT(24)
+#define LDB_CTRL_ASYNC_FIFO_THRESHOLD_MASK GENMASK(27, 25)
+
+#define LVDS_CTRL 0x128
+#define LVDS_CTRL_CH0_EN BIT(0)
+#define LVDS_CTRL_CH1_EN BIT(1)
+#define LVDS_CTRL_VBG_EN BIT(2)
+#define LVDS_CTRL_HS_EN BIT(3)
+#define LVDS_CTRL_PRE_EMPH_EN BIT(4)
+#define LVDS_CTRL_PRE_EMPH_ADJ(n) (((n) & 0x7) << 5)
+#define LVDS_CTRL_PRE_EMPH_ADJ_MASK GENMASK(7, 5)
+#define LVDS_CTRL_CM_ADJ(n) (((n) & 0x7) << 8)
+#define LVDS_CTRL_CM_ADJ_MASK GENMASK(10, 8)
+#define LVDS_CTRL_CC_ADJ(n) (((n) & 0x7) << 11)
+#define LVDS_CTRL_CC_ADJ_MASK GENMASK(13, 11)
+#define LVDS_CTRL_SLEW_ADJ(n) (((n) & 0x7) << 14)
+#define LVDS_CTRL_SLEW_ADJ_MASK GENMASK(16, 14)
+#define LVDS_CTRL_VBG_ADJ(n) (((n) & 0x7) << 17)
+#define LVDS_CTRL_VBG_ADJ_MASK GENMASK(19, 17)
+
+struct fsl_ldb {
+ struct device *dev;
+ struct drm_bridge bridge;
+ struct drm_bridge *panel_bridge;
+ struct clk *clk;
+ struct regmap *regmap;
+ bool lvds_dual_link;
+};
+
+static inline struct fsl_ldb *to_fsl_ldb(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct fsl_ldb, bridge);
+}
+
+static int fsl_ldb_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+
+ return drm_bridge_attach(bridge->encoder, fsl_ldb->panel_bridge,
+ bridge, flags);
+}
+
+static int fsl_ldb_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ /* Invert DE signal polarity. */
+ bridge_state->input_bus_cfg.flags &= ~(DRM_BUS_FLAG_DE_LOW |
+ DRM_BUS_FLAG_DE_HIGH);
+ if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_LOW)
+ bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_HIGH;
+ else if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_HIGH)
+ bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_LOW;
+
+ return 0;
+}
+
+static void fsl_ldb_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+ struct drm_atomic_state *state = old_bridge_state->base.state;
+ const struct drm_bridge_state *bridge_state;
+ const struct drm_crtc_state *crtc_state;
+ const struct drm_display_mode *mode;
+ struct drm_connector *connector;
+ struct drm_crtc *crtc;
+ bool lvds_format_24bpp;
+ bool lvds_format_jeida;
+ u32 reg;
+
+ /* Get the LVDS format from the bridge state. */
+ bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+
+ switch (bridge_state->output_bus_cfg.format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ lvds_format_24bpp = false;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ break;
+ default:
+ /*
+ * Some bridges still don't set the correct LVDS bus pixel
+ * format, use SPWG24 default format until those are fixed.
+ */
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ dev_warn(fsl_ldb->dev,
+ "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
+ bridge_state->output_bus_cfg.format);
+ break;
+ }
+
+ /*
+ * Retrieve the CRTC adjusted mode. This requires a little dance to go
+ * from the bridge to the encoder, to the connector and to the CRTC.
+ */
+ connector = drm_atomic_get_new_connector_for_encoder(state,
+ bridge->encoder);
+ crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+ crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ mode = &crtc_state->adjusted_mode;
+
+ if (fsl_ldb->lvds_dual_link)
+ clk_set_rate(fsl_ldb->clk, mode->clock * 3500);
+ else
+ clk_set_rate(fsl_ldb->clk, mode->clock * 7000);
+ clk_prepare_enable(fsl_ldb->clk);
+
+ /* Program LDB_CTRL */
+ reg = LDB_CTRL_CH0_ENABLE;
+
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_CH1_ENABLE;
+
+ if (lvds_format_24bpp) {
+ reg |= LDB_CTRL_CH0_DATA_WIDTH;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_CH1_DATA_WIDTH;
+ }
+
+ if (lvds_format_jeida) {
+ reg |= LDB_CTRL_CH0_BIT_MAPPING;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_CH1_BIT_MAPPING;
+ }
+
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC) {
+ reg |= LDB_CTRL_DI0_VSYNC_POLARITY;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LDB_CTRL_DI1_VSYNC_POLARITY;
+ }
+
+ regmap_write(fsl_ldb->regmap, LDB_CTRL, reg);
+
+ /* Program LVDS_CTRL */
+ reg = LVDS_CTRL_CC_ADJ(2) | LVDS_CTRL_PRE_EMPH_EN |
+ LVDS_CTRL_PRE_EMPH_ADJ(3) | LVDS_CTRL_VBG_EN;
+ regmap_write(fsl_ldb->regmap, LVDS_CTRL, reg);
+
+ /* Wait for VBG to stabilize. */
+ usleep_range(15, 20);
+
+ reg |= LVDS_CTRL_CH0_EN;
+ if (fsl_ldb->lvds_dual_link)
+ reg |= LVDS_CTRL_CH1_EN;
+
+ regmap_write(fsl_ldb->regmap, LVDS_CTRL, reg);
+}
+
+static void fsl_ldb_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+
+ /* Stop both channels. */
+ regmap_write(fsl_ldb->regmap, LVDS_CTRL, 0);
+ regmap_write(fsl_ldb->regmap, LDB_CTRL, 0);
+
+ clk_disable_unprepare(fsl_ldb->clk);
+}
+
+#define MAX_INPUT_SEL_FORMATS 1
+static u32 *
+fsl_ldb_atomic_get_input_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;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+ *num_input_fmts = MAX_INPUT_SEL_FORMATS;
+
+ return input_fmts;
+}
+
+static enum drm_mode_status
+fsl_ldb_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
+
+ if (mode->clock > (fsl_ldb->lvds_dual_link ? 80000 : 160000))
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static const struct drm_bridge_funcs funcs = {
+ .attach = fsl_ldb_attach,
+ .atomic_check = fsl_ldb_atomic_check,
+ .atomic_enable = fsl_ldb_atomic_enable,
+ .atomic_disable = fsl_ldb_atomic_disable,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_get_input_bus_fmts = fsl_ldb_atomic_get_input_bus_fmts,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .mode_valid = fsl_ldb_mode_valid,
+};
+
+static int fsl_ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *panel_node;
+ struct device_node *port1, *port2;
+ struct drm_panel *panel;
+ struct fsl_ldb *fsl_ldb;
+ int dual_link;
+
+ fsl_ldb = devm_kzalloc(dev, sizeof(*fsl_ldb), GFP_KERNEL);
+ if (!fsl_ldb)
+ return -ENOMEM;
+
+ fsl_ldb->dev = &pdev->dev;
+ fsl_ldb->bridge.funcs = &funcs;
+ fsl_ldb->bridge.of_node = dev->of_node;
+
+ fsl_ldb->clk = devm_clk_get(dev, "ldb");
+ if (IS_ERR(fsl_ldb->clk))
+ return PTR_ERR(fsl_ldb->clk);
+
+ fsl_ldb->regmap = syscon_node_to_regmap(dev->of_node->parent);
+ if (IS_ERR(fsl_ldb->regmap))
+ return PTR_ERR(fsl_ldb->regmap);
+
+ /* Locate the panel DT node. */
+ panel_node = of_graph_get_remote_node(dev->of_node, 1, 0);
+ if (!panel_node)
+ return -ENXIO;
+
+ panel = of_drm_find_panel(panel_node);
+ of_node_put(panel_node);
+ if (IS_ERR(panel))
+ return PTR_ERR(panel);
+
+ fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ if (IS_ERR(fsl_ldb->panel_bridge))
+ return PTR_ERR(fsl_ldb->panel_bridge);
+
+ /* Determine whether this is dual-link configuration */
+ port1 = of_graph_get_port_by_id(dev->of_node, 1);
+ port2 = of_graph_get_port_by_id(dev->of_node, 2);
+ dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);
+ of_node_put(port1);
+ of_node_put(port2);
+
+ if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) {
+ dev_err(dev, "LVDS channel pixel swap not supported.\n");
+ return -EINVAL;
+ }
+
+ if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS)
+ fsl_ldb->lvds_dual_link = true;
+
+ platform_set_drvdata(pdev, fsl_ldb);
+
+ drm_bridge_add(&fsl_ldb->bridge);
+
+ return 0;
+}
+
+static int fsl_ldb_remove(struct platform_device *pdev)
+{
+ struct fsl_ldb *fsl_ldb = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&fsl_ldb->bridge);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_ldb_match[] = {
+ { .compatible = "fsl,imx8mp-ldb", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, fsl_ldb_match);
+
+static struct platform_driver fsl_ldb_driver = {
+ .probe = fsl_ldb_probe,
+ .remove = fsl_ldb_remove,
+ .driver = {
+ .name = "fsl-ldb",
+ .of_match_table = fsl_ldb_match,
+ },
+};
+module_platform_driver(fsl_ldb_driver);
+
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("Freescale i.MX8MP LDB");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c
index f2f101220ade..4b673c4792d7 100644
--- a/drivers/gpu/drm/bridge/ite-it6505.c
+++ b/drivers/gpu/drm/bridge/ite-it6505.c
@@ -21,13 +21,13 @@
#include <crypto/hash.h>
-#include <drm/dp/drm_dp_helper.h>
+#include <drm/display/drm_dp_helper.h>
+#include <drm/display/drm_hdcp_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
-#include <drm/drm_hdcp.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
@@ -737,8 +737,9 @@ static int it6505_drm_dp_link_probe(struct drm_dp_aux *aux,
return 0;
}
-static int it6505_drm_dp_link_power_up(struct drm_dp_aux *aux,
- struct it6505_drm_dp_link *link)
+static int it6505_drm_dp_link_set_power(struct drm_dp_aux *aux,
+ struct it6505_drm_dp_link *link,
+ u8 mode)
{
u8 value;
int err;
@@ -752,18 +753,20 @@ static int it6505_drm_dp_link_power_up(struct drm_dp_aux *aux,
return err;
value &= ~DP_SET_POWER_MASK;
- value |= DP_SET_POWER_D0;
+ value |= mode;
err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
if (err < 0)
return err;
- /*
- * According to the DP 1.1 specification, a "Sink Device must exit the
- * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
- * Control Field" (register 0x600).
- */
- usleep_range(1000, 2000);
+ if (mode == DP_SET_POWER_D0) {
+ /*
+ * According to the DP 1.1 specification, a "Sink Device must
+ * exit the power saving state within 1 ms" (Section 2.5.3.1,
+ * Table 5-52, "Sink Control Field" (register 0x600).
+ */
+ usleep_range(1000, 2000);
+ }
return 0;
}
@@ -2624,7 +2627,8 @@ static enum drm_connector_status it6505_detect(struct it6505 *it6505)
if (it6505_get_sink_hpd_status(it6505)) {
it6505_aux_on(it6505);
it6505_drm_dp_link_probe(&it6505->aux, &it6505->link);
- it6505_drm_dp_link_power_up(&it6505->aux, &it6505->link);
+ it6505_drm_dp_link_set_power(&it6505->aux, &it6505->link,
+ DP_SET_POWER_D0);
it6505->auto_train_retry = AUTO_TRAIN_RETRY;
if (it6505->dpcd[0] == 0) {
@@ -2960,8 +2964,11 @@ static void it6505_bridge_atomic_disable(struct drm_bridge *bridge,
DRM_DEV_DEBUG_DRIVER(dev, "start");
- if (it6505->powered)
+ if (it6505->powered) {
it6505_video_disable(it6505);
+ it6505_drm_dp_link_set_power(&it6505->aux, &it6505->link,
+ DP_SET_POWER_D3);
+ }
}
static enum drm_connector_status
diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c
index 69288cf894b9..448c58e60c11 100644
--- a/drivers/gpu/drm/bridge/ite-it66121.c
+++ b/drivers/gpu/drm/bridge/ite-it66121.c
@@ -27,6 +27,8 @@
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
+#include <sound/hdmi-codec.h>
+
#define IT66121_VENDOR_ID0_REG 0x00
#define IT66121_VENDOR_ID1_REG 0x01
#define IT66121_DEVICE_ID0_REG 0x02
@@ -155,6 +157,9 @@
#define IT66121_AV_MUTE_ON BIT(0)
#define IT66121_AV_MUTE_BLUESCR BIT(1)
+#define IT66121_PKT_CTS_CTRL_REG 0xC5
+#define IT66121_PKT_CTS_CTRL_SEL BIT(1)
+
#define IT66121_PKT_GEN_CTRL_REG 0xC6
#define IT66121_PKT_GEN_CTRL_ON BIT(0)
#define IT66121_PKT_GEN_CTRL_RPT BIT(1)
@@ -202,6 +207,89 @@
#define IT66121_EDID_SLEEP_US 20000
#define IT66121_EDID_TIMEOUT_US 200000
#define IT66121_EDID_FIFO_SIZE 32
+
+#define IT66121_CLK_CTRL0_REG 0x58
+#define IT66121_CLK_CTRL0_AUTO_OVER_SAMPLING BIT(4)
+#define IT66121_CLK_CTRL0_EXT_MCLK_MASK GENMASK(3, 2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_128FS (0 << 2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_256FS BIT(2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_512FS (2 << 2)
+#define IT66121_CLK_CTRL0_EXT_MCLK_1024FS (3 << 2)
+#define IT66121_CLK_CTRL0_AUTO_IPCLK BIT(0)
+#define IT66121_CLK_STATUS1_REG 0x5E
+#define IT66121_CLK_STATUS2_REG 0x5F
+
+#define IT66121_AUD_CTRL0_REG 0xE0
+#define IT66121_AUD_SWL (3 << 6)
+#define IT66121_AUD_16BIT (0 << 6)
+#define IT66121_AUD_18BIT BIT(6)
+#define IT66121_AUD_20BIT (2 << 6)
+#define IT66121_AUD_24BIT (3 << 6)
+#define IT66121_AUD_SPDIFTC BIT(5)
+#define IT66121_AUD_SPDIF BIT(4)
+#define IT66121_AUD_I2S (0 << 4)
+#define IT66121_AUD_EN_I2S3 BIT(3)
+#define IT66121_AUD_EN_I2S2 BIT(2)
+#define IT66121_AUD_EN_I2S1 BIT(1)
+#define IT66121_AUD_EN_I2S0 BIT(0)
+#define IT66121_AUD_CTRL0_AUD_SEL BIT(4)
+
+#define IT66121_AUD_CTRL1_REG 0xE1
+#define IT66121_AUD_FIFOMAP_REG 0xE2
+#define IT66121_AUD_CTRL3_REG 0xE3
+#define IT66121_AUD_SRCVALID_FLAT_REG 0xE4
+#define IT66121_AUD_FLAT_SRC0 BIT(4)
+#define IT66121_AUD_FLAT_SRC1 BIT(5)
+#define IT66121_AUD_FLAT_SRC2 BIT(6)
+#define IT66121_AUD_FLAT_SRC3 BIT(7)
+#define IT66121_AUD_HDAUDIO_REG 0xE5
+
+#define IT66121_AUD_PKT_CTS0_REG 0x130
+#define IT66121_AUD_PKT_CTS1_REG 0x131
+#define IT66121_AUD_PKT_CTS2_REG 0x132
+#define IT66121_AUD_PKT_N0_REG 0x133
+#define IT66121_AUD_PKT_N1_REG 0x134
+#define IT66121_AUD_PKT_N2_REG 0x135
+
+#define IT66121_AUD_CHST_MODE_REG 0x191
+#define IT66121_AUD_CHST_CAT_REG 0x192
+#define IT66121_AUD_CHST_SRCNUM_REG 0x193
+#define IT66121_AUD_CHST_CHTNUM_REG 0x194
+#define IT66121_AUD_CHST_CA_FS_REG 0x198
+#define IT66121_AUD_CHST_OFS_WL_REG 0x199
+
+#define IT66121_AUD_PKT_CTS_CNT0_REG 0x1A0
+#define IT66121_AUD_PKT_CTS_CNT1_REG 0x1A1
+#define IT66121_AUD_PKT_CTS_CNT2_REG 0x1A2
+
+#define IT66121_AUD_FS_22P05K 0x4
+#define IT66121_AUD_FS_44P1K 0x0
+#define IT66121_AUD_FS_88P2K 0x8
+#define IT66121_AUD_FS_176P4K 0xC
+#define IT66121_AUD_FS_24K 0x6
+#define IT66121_AUD_FS_48K 0x2
+#define IT66121_AUD_FS_96K 0xA
+#define IT66121_AUD_FS_192K 0xE
+#define IT66121_AUD_FS_768K 0x9
+#define IT66121_AUD_FS_32K 0x3
+#define IT66121_AUD_FS_OTHER 0x1
+
+#define IT66121_AUD_SWL_21BIT 0xD
+#define IT66121_AUD_SWL_24BIT 0xB
+#define IT66121_AUD_SWL_23BIT 0x9
+#define IT66121_AUD_SWL_22BIT 0x5
+#define IT66121_AUD_SWL_20BIT 0x3
+#define IT66121_AUD_SWL_17BIT 0xC
+#define IT66121_AUD_SWL_19BIT 0x8
+#define IT66121_AUD_SWL_18BIT 0x4
+#define IT66121_AUD_SWL_16BIT 0x2
+#define IT66121_AUD_SWL_NOT_INDICATED 0x0
+
+#define IT66121_VENDOR_ID0 0x54
+#define IT66121_VENDOR_ID1 0x49
+#define IT66121_DEVICE_ID0 0x12
+#define IT66121_DEVICE_ID1 0x06
+#define IT66121_DEVICE_MASK 0x0F
#define IT66121_AFE_CLK_HIGH 80000 /* Khz */
struct it66121_ctx {
@@ -216,6 +304,13 @@ struct it66121_ctx {
u32 bus_width;
struct mutex lock; /* Protects fields below and device registers */
struct hdmi_avi_infoframe hdmi_avi_infoframe;
+ struct {
+ struct platform_device *pdev;
+ u8 ch_enable;
+ u8 fs;
+ u8 swl;
+ bool auto_cts;
+ } audio;
};
static const struct regmap_range_cfg it66121_regmap_banks[] = {
@@ -227,7 +322,7 @@ static const struct regmap_range_cfg it66121_regmap_banks[] = {
.selector_mask = 0x1,
.selector_shift = 0,
.window_start = 0x00,
- .window_len = 0x130,
+ .window_len = 0x100,
},
};
@@ -886,6 +981,536 @@ unlock:
return IRQ_HANDLED;
}
+static int it661221_set_chstat(struct it66121_ctx *ctx, u8 iec60958_chstat[])
+{
+ int ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CHST_MODE_REG, iec60958_chstat[0] & 0x7C);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CHST_CAT_REG, iec60958_chstat[1]);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CHST_SRCNUM_REG, iec60958_chstat[2] & 0x0F);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CHST_CHTNUM_REG,
+ (iec60958_chstat[2] >> 4) & 0x0F);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CHST_CA_FS_REG, iec60958_chstat[3]);
+ if (ret)
+ return ret;
+
+ return regmap_write(ctx->regmap, IT66121_AUD_CHST_OFS_WL_REG, iec60958_chstat[4]);
+}
+
+static int it661221_set_lpcm_audio(struct it66121_ctx *ctx, u8 audio_src_num, u8 audio_swl)
+{
+ int ret;
+ unsigned int audio_enable = 0;
+ unsigned int audio_format = 0;
+
+ switch (audio_swl) {
+ case 16:
+ audio_enable |= IT66121_AUD_16BIT;
+ break;
+ case 18:
+ audio_enable |= IT66121_AUD_18BIT;
+ break;
+ case 20:
+ audio_enable |= IT66121_AUD_20BIT;
+ break;
+ case 24:
+ default:
+ audio_enable |= IT66121_AUD_24BIT;
+ break;
+ }
+
+ audio_format |= 0x40;
+ switch (audio_src_num) {
+ case 4:
+ audio_enable |= IT66121_AUD_EN_I2S3 | IT66121_AUD_EN_I2S2 |
+ IT66121_AUD_EN_I2S1 | IT66121_AUD_EN_I2S0;
+ break;
+ case 3:
+ audio_enable |= IT66121_AUD_EN_I2S2 | IT66121_AUD_EN_I2S1 |
+ IT66121_AUD_EN_I2S0;
+ break;
+ case 2:
+ audio_enable |= IT66121_AUD_EN_I2S1 | IT66121_AUD_EN_I2S0;
+ break;
+ case 1:
+ default:
+ audio_format &= ~0x40;
+ audio_enable |= IT66121_AUD_EN_I2S0;
+ break;
+ }
+
+ audio_format |= 0x01;
+ ctx->audio.ch_enable = audio_enable;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CTRL0_REG, audio_enable & 0xF0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CTRL1_REG, audio_format);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_FIFOMAP_REG, 0xE4);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CTRL3_REG, 0x00);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_SRCVALID_FLAT_REG, 0x00);
+ if (ret)
+ return ret;
+
+ return regmap_write(ctx->regmap, IT66121_AUD_HDAUDIO_REG, 0x00);
+}
+
+static int it661221_set_ncts(struct it66121_ctx *ctx, u8 fs)
+{
+ int ret;
+ unsigned int n;
+
+ switch (fs) {
+ case IT66121_AUD_FS_32K:
+ n = 4096;
+ break;
+ case IT66121_AUD_FS_44P1K:
+ n = 6272;
+ break;
+ case IT66121_AUD_FS_48K:
+ n = 6144;
+ break;
+ case IT66121_AUD_FS_88P2K:
+ n = 12544;
+ break;
+ case IT66121_AUD_FS_96K:
+ n = 12288;
+ break;
+ case IT66121_AUD_FS_176P4K:
+ n = 25088;
+ break;
+ case IT66121_AUD_FS_192K:
+ n = 24576;
+ break;
+ case IT66121_AUD_FS_768K:
+ n = 24576;
+ break;
+ default:
+ n = 6144;
+ break;
+ }
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_PKT_N0_REG, (u8)((n) & 0xFF));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_PKT_N1_REG, (u8)((n >> 8) & 0xFF));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_PKT_N2_REG, (u8)((n >> 16) & 0xF));
+ if (ret)
+ return ret;
+
+ if (ctx->audio.auto_cts) {
+ u8 loop_cnt = 255;
+ u8 cts_stable_cnt = 0;
+ unsigned int sum_cts = 0;
+ unsigned int cts = 0;
+ unsigned int last_cts = 0;
+ unsigned int diff;
+ unsigned int val;
+
+ while (loop_cnt--) {
+ msleep(30);
+ regmap_read(ctx->regmap, IT66121_AUD_PKT_CTS_CNT2_REG, &val);
+ cts = val << 12;
+ regmap_read(ctx->regmap, IT66121_AUD_PKT_CTS_CNT1_REG, &val);
+ cts |= val << 4;
+ regmap_read(ctx->regmap, IT66121_AUD_PKT_CTS_CNT0_REG, &val);
+ cts |= val >> 4;
+ if (cts == 0) {
+ continue;
+ } else {
+ if (last_cts > cts)
+ diff = last_cts - cts;
+ else
+ diff = cts - last_cts;
+ last_cts = cts;
+ if (diff < 5) {
+ cts_stable_cnt++;
+ sum_cts += cts;
+ } else {
+ cts_stable_cnt = 0;
+ sum_cts = 0;
+ continue;
+ }
+
+ if (cts_stable_cnt >= 32) {
+ last_cts = (sum_cts >> 5);
+ break;
+ }
+ }
+ }
+
+ regmap_write(ctx->regmap, IT66121_AUD_PKT_CTS0_REG, (u8)((last_cts) & 0xFF));
+ regmap_write(ctx->regmap, IT66121_AUD_PKT_CTS1_REG, (u8)((last_cts >> 8) & 0xFF));
+ regmap_write(ctx->regmap, IT66121_AUD_PKT_CTS2_REG, (u8)((last_cts >> 16) & 0x0F));
+ }
+
+ ret = regmap_write(ctx->regmap, 0xF8, 0xC3);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, 0xF8, 0xA5);
+ if (ret)
+ return ret;
+
+ if (ctx->audio.auto_cts) {
+ ret = regmap_write_bits(ctx->regmap, IT66121_PKT_CTS_CTRL_REG,
+ IT66121_PKT_CTS_CTRL_SEL,
+ 1);
+ } else {
+ ret = regmap_write_bits(ctx->regmap, IT66121_PKT_CTS_CTRL_REG,
+ IT66121_PKT_CTS_CTRL_SEL,
+ 0);
+ }
+
+ if (ret)
+ return ret;
+
+ return regmap_write(ctx->regmap, 0xF8, 0xFF);
+}
+
+static int it661221_audio_output_enable(struct it66121_ctx *ctx, bool enable)
+{
+ int ret;
+
+ if (enable) {
+ ret = regmap_write_bits(ctx->regmap, IT66121_SW_RST_REG,
+ IT66121_SW_RST_AUD | IT66121_SW_RST_AREF,
+ 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(ctx->regmap, IT66121_AUD_CTRL0_REG,
+ IT66121_AUD_EN_I2S3 | IT66121_AUD_EN_I2S2 |
+ IT66121_AUD_EN_I2S1 | IT66121_AUD_EN_I2S0,
+ ctx->audio.ch_enable);
+ } else {
+ ret = regmap_write_bits(ctx->regmap, IT66121_AUD_CTRL0_REG,
+ IT66121_AUD_EN_I2S3 | IT66121_AUD_EN_I2S2 |
+ IT66121_AUD_EN_I2S1 | IT66121_AUD_EN_I2S0,
+ ctx->audio.ch_enable & 0xF0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write_bits(ctx->regmap, IT66121_SW_RST_REG,
+ IT66121_SW_RST_AUD | IT66121_SW_RST_AREF,
+ IT66121_SW_RST_AUD | IT66121_SW_RST_AREF);
+ }
+
+ return ret;
+}
+
+static int it661221_audio_ch_enable(struct it66121_ctx *ctx, bool enable)
+{
+ int ret;
+
+ if (enable) {
+ ret = regmap_write(ctx->regmap, IT66121_AUD_SRCVALID_FLAT_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CTRL0_REG, ctx->audio.ch_enable);
+ } else {
+ ret = regmap_write(ctx->regmap, IT66121_AUD_CTRL0_REG, ctx->audio.ch_enable & 0xF0);
+ }
+
+ return ret;
+}
+
+static int it66121_audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ u8 fs;
+ u8 swl;
+ int ret;
+ struct it66121_ctx *ctx = dev_get_drvdata(dev);
+ static u8 iec60958_chstat[5];
+ unsigned int channels = params->channels;
+ unsigned int sample_rate = params->sample_rate;
+ unsigned int sample_width = params->sample_width;
+
+ mutex_lock(&ctx->lock);
+ dev_dbg(dev, "%s: %u, %u, %u, %u\n", __func__,
+ daifmt->fmt, sample_rate, sample_width, channels);
+
+ switch (daifmt->fmt) {
+ case HDMI_I2S:
+ dev_dbg(dev, "Using HDMI I2S\n");
+ break;
+ default:
+ dev_err(dev, "Invalid or unsupported DAI format %d\n", daifmt->fmt);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ // Set audio clock recovery (N/CTS)
+ ret = regmap_write(ctx->regmap, IT66121_CLK_CTRL0_REG,
+ IT66121_CLK_CTRL0_AUTO_OVER_SAMPLING |
+ IT66121_CLK_CTRL0_EXT_MCLK_256FS |
+ IT66121_CLK_CTRL0_AUTO_IPCLK);
+ if (ret)
+ goto out;
+
+ ret = regmap_write_bits(ctx->regmap, IT66121_AUD_CTRL0_REG,
+ IT66121_AUD_CTRL0_AUD_SEL, 0); // remove spdif selection
+ if (ret)
+ goto out;
+
+ switch (sample_rate) {
+ case 44100L:
+ fs = IT66121_AUD_FS_44P1K;
+ break;
+ case 88200L:
+ fs = IT66121_AUD_FS_88P2K;
+ break;
+ case 176400L:
+ fs = IT66121_AUD_FS_176P4K;
+ break;
+ case 32000L:
+ fs = IT66121_AUD_FS_32K;
+ break;
+ case 48000L:
+ fs = IT66121_AUD_FS_48K;
+ break;
+ case 96000L:
+ fs = IT66121_AUD_FS_96K;
+ break;
+ case 192000L:
+ fs = IT66121_AUD_FS_192K;
+ break;
+ case 768000L:
+ fs = IT66121_AUD_FS_768K;
+ break;
+ default:
+ fs = IT66121_AUD_FS_48K;
+ break;
+ }
+
+ ctx->audio.fs = fs;
+ ret = it661221_set_ncts(ctx, fs);
+ if (ret) {
+ dev_err(dev, "Failed to set N/CTS: %d\n", ret);
+ goto out;
+ }
+
+ // Set audio format register (except audio channel enable)
+ ret = it661221_set_lpcm_audio(ctx, (channels + 1) / 2, sample_width);
+ if (ret) {
+ dev_err(dev, "Failed to set LPCM audio: %d\n", ret);
+ goto out;
+ }
+
+ // Set audio channel status
+ iec60958_chstat[0] = 0;
+ if ((channels + 1) / 2 == 1)
+ iec60958_chstat[0] |= 0x1;
+ iec60958_chstat[0] &= ~(1 << 1);
+ iec60958_chstat[1] = 0;
+ iec60958_chstat[2] = (channels + 1) / 2;
+ iec60958_chstat[2] |= (channels << 4) & 0xF0;
+ iec60958_chstat[3] = fs;
+
+ switch (sample_width) {
+ case 21L:
+ swl = IT66121_AUD_SWL_21BIT;
+ break;
+ case 24L:
+ swl = IT66121_AUD_SWL_24BIT;
+ break;
+ case 23L:
+ swl = IT66121_AUD_SWL_23BIT;
+ break;
+ case 22L:
+ swl = IT66121_AUD_SWL_22BIT;
+ break;
+ case 20L:
+ swl = IT66121_AUD_SWL_20BIT;
+ break;
+ case 17L:
+ swl = IT66121_AUD_SWL_17BIT;
+ break;
+ case 19L:
+ swl = IT66121_AUD_SWL_19BIT;
+ break;
+ case 18L:
+ swl = IT66121_AUD_SWL_18BIT;
+ break;
+ case 16L:
+ swl = IT66121_AUD_SWL_16BIT;
+ break;
+ default:
+ swl = IT66121_AUD_SWL_NOT_INDICATED;
+ break;
+ }
+
+ iec60958_chstat[4] = (((~fs) << 4) & 0xF0) | swl;
+ ret = it661221_set_chstat(ctx, iec60958_chstat);
+ if (ret) {
+ dev_err(dev, "Failed to set channel status: %d\n", ret);
+ goto out;
+ }
+
+ // Enable audio channel enable while input clock stable (if SPDIF).
+ ret = it661221_audio_ch_enable(ctx, true);
+ if (ret) {
+ dev_err(dev, "Failed to enable audio channel: %d\n", ret);
+ goto out;
+ }
+
+ ret = regmap_write_bits(ctx->regmap, IT66121_INT_MASK1_REG,
+ IT66121_INT_MASK1_AUD_OVF,
+ 0);
+ if (ret)
+ goto out;
+
+ dev_dbg(dev, "HDMI audio enabled.\n");
+out:
+ mutex_unlock(&ctx->lock);
+
+ return ret;
+}
+
+static int it66121_audio_startup(struct device *dev, void *data)
+{
+ int ret;
+ struct it66121_ctx *ctx = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ mutex_lock(&ctx->lock);
+ ret = it661221_audio_output_enable(ctx, true);
+ if (ret)
+ dev_err(dev, "Failed to enable audio output: %d\n", ret);
+
+ mutex_unlock(&ctx->lock);
+
+ return ret;
+}
+
+static void it66121_audio_shutdown(struct device *dev, void *data)
+{
+ int ret;
+ struct it66121_ctx *ctx = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ mutex_lock(&ctx->lock);
+ ret = it661221_audio_output_enable(ctx, false);
+ if (ret)
+ dev_err(dev, "Failed to disable audio output: %d\n", ret);
+
+ mutex_unlock(&ctx->lock);
+}
+
+static int it66121_audio_mute(struct device *dev, void *data,
+ bool enable, int direction)
+{
+ int ret;
+ struct it66121_ctx *ctx = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "%s: enable=%s, direction=%d\n",
+ __func__, enable ? "true" : "false", direction);
+
+ mutex_lock(&ctx->lock);
+
+ if (enable) {
+ ret = regmap_write_bits(ctx->regmap, IT66121_AUD_SRCVALID_FLAT_REG,
+ IT66121_AUD_FLAT_SRC0 | IT66121_AUD_FLAT_SRC1 |
+ IT66121_AUD_FLAT_SRC2 | IT66121_AUD_FLAT_SRC3,
+ IT66121_AUD_FLAT_SRC0 | IT66121_AUD_FLAT_SRC1 |
+ IT66121_AUD_FLAT_SRC2 | IT66121_AUD_FLAT_SRC3);
+ } else {
+ ret = regmap_write_bits(ctx->regmap, IT66121_AUD_SRCVALID_FLAT_REG,
+ IT66121_AUD_FLAT_SRC0 | IT66121_AUD_FLAT_SRC1 |
+ IT66121_AUD_FLAT_SRC2 | IT66121_AUD_FLAT_SRC3,
+ 0);
+ }
+
+ mutex_unlock(&ctx->lock);
+
+ return ret;
+}
+
+static int it66121_audio_get_eld(struct device *dev, void *data,
+ u8 *buf, size_t len)
+{
+ struct it66121_ctx *ctx = dev_get_drvdata(dev);
+
+ mutex_lock(&ctx->lock);
+
+ memcpy(buf, ctx->connector->eld,
+ min(sizeof(ctx->connector->eld), len));
+
+ mutex_unlock(&ctx->lock);
+
+ return 0;
+}
+
+static const struct hdmi_codec_ops it66121_audio_codec_ops = {
+ .hw_params = it66121_audio_hw_params,
+ .audio_startup = it66121_audio_startup,
+ .audio_shutdown = it66121_audio_shutdown,
+ .mute_stream = it66121_audio_mute,
+ .get_eld = it66121_audio_get_eld,
+ .no_capture_mute = 1,
+};
+
+static int it66121_audio_codec_init(struct it66121_ctx *ctx, struct device *dev)
+{
+ struct hdmi_codec_pdata codec_data = {
+ .ops = &it66121_audio_codec_ops,
+ .i2s = 1, /* Only i2s support for now */
+ .spdif = 0,
+ .max_i2s_channels = 8,
+ };
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (!of_property_read_bool(dev->of_node, "#sound-dai-cells")) {
+ dev_info(dev, "No \"#sound-dai-cells\", no audio\n");
+ return 0;
+ }
+
+ ctx->audio.pdev = platform_device_register_data(dev,
+ HDMI_CODEC_DRV_NAME,
+ PLATFORM_DEVID_AUTO,
+ &codec_data,
+ sizeof(codec_data));
+
+ if (IS_ERR(ctx->audio.pdev)) {
+ dev_err(dev, "Failed to initialize HDMI audio codec: %d\n",
+ PTR_ERR_OR_ZERO(ctx->audio.pdev));
+ }
+
+ return PTR_ERR_OR_ZERO(ctx->audio.pdev);
+}
+
static int it66121_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -988,6 +1613,8 @@ static int it66121_probe(struct i2c_client *client,
return ret;
}
+ it66121_audio_codec_init(ctx, dev);
+
drm_bridge_add(&ctx->bridge);
dev_info(ctx->dev, "IT66121 revision %d probed\n", revision_id);
diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c
new file mode 100644
index 000000000000..e92821fbc639
--- /dev/null
+++ b/drivers/gpu/drm/bridge/lontium-lt9211.c
@@ -0,0 +1,802 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Lontium LT9211 bridge driver
+ *
+ * LT9211 is capable of converting:
+ * 2xDSI/2xLVDS/1xDPI -> 2xDSI/2xLVDS/1xDPI
+ * Currently supported is:
+ * 1xDSI -> 1xLVDS
+ *
+ * Copyright (C) 2022 Marek Vasut <marex@denx.de>
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#define REG_PAGE_CONTROL 0xff
+#define REG_CHIPID0 0x8100
+#define REG_CHIPID0_VALUE 0x18
+#define REG_CHIPID1 0x8101
+#define REG_CHIPID1_VALUE 0x01
+#define REG_CHIPID2 0x8102
+#define REG_CHIPID2_VALUE 0xe3
+
+#define REG_DSI_LANE 0xd000
+/* DSI lane count - 0 means 4 lanes ; 1, 2, 3 means 1, 2, 3 lanes. */
+#define REG_DSI_LANE_COUNT(n) ((n) & 3)
+
+struct lt9211 {
+ struct drm_bridge bridge;
+ struct device *dev;
+ struct regmap *regmap;
+ struct mipi_dsi_device *dsi;
+ struct drm_bridge *panel_bridge;
+ struct gpio_desc *reset_gpio;
+ struct regulator *vccio;
+ bool lvds_dual_link;
+ bool lvds_dual_link_even_odd_swap;
+};
+
+static const struct regmap_range lt9211_rw_ranges[] = {
+ regmap_reg_range(0xff, 0xff),
+ regmap_reg_range(0x8100, 0x816b),
+ regmap_reg_range(0x8200, 0x82aa),
+ regmap_reg_range(0x8500, 0x85ff),
+ regmap_reg_range(0x8600, 0x86a0),
+ regmap_reg_range(0x8700, 0x8746),
+ regmap_reg_range(0xd000, 0xd0a7),
+ regmap_reg_range(0xd400, 0xd42c),
+ regmap_reg_range(0xd800, 0xd838),
+ regmap_reg_range(0xd9c0, 0xd9d5),
+};
+
+static const struct regmap_access_table lt9211_rw_table = {
+ .yes_ranges = lt9211_rw_ranges,
+ .n_yes_ranges = ARRAY_SIZE(lt9211_rw_ranges),
+};
+
+static const struct regmap_range_cfg lt9211_range = {
+ .name = "lt9211",
+ .range_min = 0x0000,
+ .range_max = 0xda00,
+ .selector_reg = REG_PAGE_CONTROL,
+ .selector_mask = 0xff,
+ .selector_shift = 0,
+ .window_start = 0,
+ .window_len = 0x100,
+};
+
+static const struct regmap_config lt9211_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &lt9211_rw_table,
+ .wr_table = &lt9211_rw_table,
+ .volatile_table = &lt9211_rw_table,
+ .ranges = &lt9211_range,
+ .num_ranges = 1,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = 0xda00,
+};
+
+static struct lt9211 *bridge_to_lt9211(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct lt9211, bridge);
+}
+
+static int lt9211_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct lt9211 *ctx = bridge_to_lt9211(bridge);
+
+ return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
+ &ctx->bridge, flags);
+}
+
+static int lt9211_read_chipid(struct lt9211 *ctx)
+{
+ u8 chipid[3];
+ int ret;
+
+ /* Read Chip ID registers and verify the chip can communicate. */
+ ret = regmap_bulk_read(ctx->regmap, REG_CHIPID0, chipid, 3);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Failed to read Chip ID: %d\n", ret);
+ return ret;
+ }
+
+ /* Test for known Chip ID. */
+ if (chipid[0] != REG_CHIPID0_VALUE || chipid[1] != REG_CHIPID1_VALUE ||
+ chipid[2] != REG_CHIPID2_VALUE) {
+ dev_err(ctx->dev, "Unknown Chip ID: 0x%02x 0x%02x 0x%02x\n",
+ chipid[0], chipid[1], chipid[2]);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lt9211_system_init(struct lt9211 *ctx)
+{
+ const struct reg_sequence lt9211_system_init_seq[] = {
+ { 0x8201, 0x18 },
+ { 0x8606, 0x61 },
+ { 0x8607, 0xa8 },
+ { 0x8714, 0x08 },
+ { 0x8715, 0x00 },
+ { 0x8718, 0x0f },
+ { 0x8722, 0x08 },
+ { 0x8723, 0x00 },
+ { 0x8726, 0x0f },
+ { 0x810b, 0xfe },
+ };
+
+ return regmap_multi_reg_write(ctx->regmap, lt9211_system_init_seq,
+ ARRAY_SIZE(lt9211_system_init_seq));
+}
+
+static int lt9211_configure_rx(struct lt9211 *ctx)
+{
+ const struct reg_sequence lt9211_rx_phy_seq[] = {
+ { 0x8202, 0x44 },
+ { 0x8204, 0xa0 },
+ { 0x8205, 0x22 },
+ { 0x8207, 0x9f },
+ { 0x8208, 0xfc },
+ /* ORR with 0xf8 here to enable DSI DN/DP swap. */
+ { 0x8209, 0x01 },
+ { 0x8217, 0x0c },
+ { 0x8633, 0x1b },
+ };
+
+ const struct reg_sequence lt9211_rx_cal_reset_seq[] = {
+ { 0x8120, 0x7f },
+ { 0x8120, 0xff },
+ };
+
+ const struct reg_sequence lt9211_rx_dig_seq[] = {
+ { 0x8630, 0x85 },
+ /* 0x8588: BIT 6 set = MIPI-RX, BIT 4 unset = LVDS-TX */
+ { 0x8588, 0x40 },
+ { 0x85ff, 0xd0 },
+ { REG_DSI_LANE, REG_DSI_LANE_COUNT(ctx->dsi->lanes) },
+ { 0xd002, 0x05 },
+ };
+
+ const struct reg_sequence lt9211_rx_div_reset_seq[] = {
+ { 0x810a, 0xc0 },
+ { 0x8120, 0xbf },
+ };
+
+ const struct reg_sequence lt9211_rx_div_clear_seq[] = {
+ { 0x810a, 0xc1 },
+ { 0x8120, 0xff },
+ };
+
+ int ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_phy_seq,
+ ARRAY_SIZE(lt9211_rx_phy_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_cal_reset_seq,
+ ARRAY_SIZE(lt9211_rx_cal_reset_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_dig_seq,
+ ARRAY_SIZE(lt9211_rx_dig_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_reset_seq,
+ ARRAY_SIZE(lt9211_rx_div_reset_seq));
+ if (ret)
+ return ret;
+
+ usleep_range(10000, 15000);
+
+ return regmap_multi_reg_write(ctx->regmap, lt9211_rx_div_clear_seq,
+ ARRAY_SIZE(lt9211_rx_div_clear_seq));
+}
+
+static int lt9211_autodetect_rx(struct lt9211 *ctx,
+ const struct drm_display_mode *mode)
+{
+ u16 width, height;
+ u32 byteclk;
+ u8 buf[5];
+ u8 format;
+ u8 bc[3];
+ int ret;
+
+ /* Measure ByteClock frequency. */
+ ret = regmap_write(ctx->regmap, 0x8600, 0x01);
+ if (ret)
+ return ret;
+
+ /* Give the chip time to lock onto RX stream. */
+ msleep(100);
+
+ /* Read the ByteClock frequency from the chip. */
+ ret = regmap_bulk_read(ctx->regmap, 0x8608, bc, sizeof(bc));
+ if (ret)
+ return ret;
+
+ /* RX ByteClock in kHz */
+ byteclk = ((bc[0] & 0xf) << 16) | (bc[1] << 8) | bc[2];
+
+ /* Width/Height/Format Auto-detection */
+ ret = regmap_bulk_read(ctx->regmap, 0xd082, buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ width = (buf[0] << 8) | buf[1];
+ height = (buf[3] << 8) | buf[4];
+ format = buf[2] & 0xf;
+
+ if (format == 0x3) { /* YUV422 16bit */
+ width /= 2;
+ } else if (format == 0xa) { /* RGB888 24bit */
+ width /= 3;
+ } else {
+ dev_err(ctx->dev, "Unsupported DSI pixel format 0x%01x\n",
+ format);
+ return -EINVAL;
+ }
+
+ if (width != mode->hdisplay) {
+ dev_err(ctx->dev,
+ "RX: Detected DSI width (%d) does not match mode hdisplay (%d)\n",
+ width, mode->hdisplay);
+ return -EINVAL;
+ }
+
+ if (height != mode->vdisplay) {
+ dev_err(ctx->dev,
+ "RX: Detected DSI height (%d) does not match mode vdisplay (%d)\n",
+ height, mode->vdisplay);
+ return -EINVAL;
+ }
+
+ dev_dbg(ctx->dev, "RX: %dx%d format=0x%01x byteclock=%d kHz\n",
+ width, height, format, byteclk);
+
+ return 0;
+}
+
+static int lt9211_configure_timing(struct lt9211 *ctx,
+ const struct drm_display_mode *mode)
+{
+ const struct reg_sequence lt9211_timing[] = {
+ { 0xd00d, (mode->vtotal >> 8) & 0xff },
+ { 0xd00e, mode->vtotal & 0xff },
+ { 0xd00f, (mode->vdisplay >> 8) & 0xff },
+ { 0xd010, mode->vdisplay & 0xff },
+ { 0xd011, (mode->htotal >> 8) & 0xff },
+ { 0xd012, mode->htotal & 0xff },
+ { 0xd013, (mode->hdisplay >> 8) & 0xff },
+ { 0xd014, mode->hdisplay & 0xff },
+ { 0xd015, (mode->vsync_end - mode->vsync_start) & 0xff },
+ { 0xd016, (mode->hsync_end - mode->hsync_start) & 0xff },
+ { 0xd017, ((mode->vsync_start - mode->vdisplay) >> 8) & 0xff },
+ { 0xd018, (mode->vsync_start - mode->vdisplay) & 0xff },
+ { 0xd019, ((mode->hsync_start - mode->hdisplay) >> 8) & 0xff },
+ { 0xd01a, (mode->hsync_start - mode->hdisplay) & 0xff },
+ };
+
+ return regmap_multi_reg_write(ctx->regmap, lt9211_timing,
+ ARRAY_SIZE(lt9211_timing));
+}
+
+static int lt9211_configure_plls(struct lt9211 *ctx,
+ const struct drm_display_mode *mode)
+{
+ const struct reg_sequence lt9211_pcr_seq[] = {
+ { 0xd026, 0x17 },
+ { 0xd027, 0xc3 },
+ { 0xd02d, 0x30 },
+ { 0xd031, 0x10 },
+ { 0xd023, 0x20 },
+ { 0xd038, 0x02 },
+ { 0xd039, 0x10 },
+ { 0xd03a, 0x20 },
+ { 0xd03b, 0x60 },
+ { 0xd03f, 0x04 },
+ { 0xd040, 0x08 },
+ { 0xd041, 0x10 },
+ { 0x810b, 0xee },
+ { 0x810b, 0xfe },
+ };
+
+ unsigned int pval;
+ int ret;
+
+ /* DeSSC PLL reference clock is 25 MHz XTal. */
+ ret = regmap_write(ctx->regmap, 0x822d, 0x48);
+ if (ret)
+ return ret;
+
+ if (mode->clock < 44000) {
+ ret = regmap_write(ctx->regmap, 0x8235, 0x83);
+ } else if (mode->clock < 88000) {
+ ret = regmap_write(ctx->regmap, 0x8235, 0x82);
+ } else if (mode->clock < 176000) {
+ ret = regmap_write(ctx->regmap, 0x8235, 0x81);
+ } else {
+ dev_err(ctx->dev,
+ "Unsupported mode clock (%d kHz) above 176 MHz.\n",
+ mode->clock);
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ /* Wait for the DeSSC PLL to stabilize. */
+ msleep(100);
+
+ ret = regmap_multi_reg_write(ctx->regmap, lt9211_pcr_seq,
+ ARRAY_SIZE(lt9211_pcr_seq));
+ if (ret)
+ return ret;
+
+ /* PCR stability test takes seconds. */
+ ret = regmap_read_poll_timeout(ctx->regmap, 0xd087, pval, pval & 0x8,
+ 20000, 10000000);
+ if (ret)
+ dev_err(ctx->dev, "PCR unstable, ret=%i\n", ret);
+
+ return ret;
+}
+
+static int lt9211_configure_tx(struct lt9211 *ctx, bool jeida,
+ bool bpp24, bool de)
+{
+ const struct reg_sequence system_lt9211_tx_phy_seq[] = {
+ /* DPI output disable */
+ { 0x8262, 0x00 },
+ /* BIT(7) is LVDS dual-port */
+ { 0x823b, 0x38 | (ctx->lvds_dual_link ? BIT(7) : 0) },
+ { 0x823e, 0x92 },
+ { 0x823f, 0x48 },
+ { 0x8240, 0x31 },
+ { 0x8243, 0x80 },
+ { 0x8244, 0x00 },
+ { 0x8245, 0x00 },
+ { 0x8249, 0x00 },
+ { 0x824a, 0x01 },
+ { 0x824e, 0x00 },
+ { 0x824f, 0x00 },
+ { 0x8250, 0x00 },
+ { 0x8253, 0x00 },
+ { 0x8254, 0x01 },
+ /* LVDS channel order, Odd:Even 0x10..A:B, 0x40..B:A */
+ { 0x8646, ctx->lvds_dual_link_even_odd_swap ? 0x40 : 0x10 },
+ { 0x8120, 0x7b },
+ { 0x816b, 0xff },
+ };
+
+ const struct reg_sequence system_lt9211_tx_dig_seq[] = {
+ { 0x8559, 0x40 | (jeida ? BIT(7) : 0) |
+ (de ? BIT(5) : 0) | (bpp24 ? BIT(4) : 0) },
+ { 0x855a, 0xaa },
+ { 0x855b, 0xaa },
+ { 0x855c, ctx->lvds_dual_link ? BIT(0) : 0 },
+ { 0x85a1, 0x77 },
+ { 0x8640, 0x40 },
+ { 0x8641, 0x34 },
+ { 0x8642, 0x10 },
+ { 0x8643, 0x23 },
+ { 0x8644, 0x41 },
+ { 0x8645, 0x02 },
+ };
+
+ const struct reg_sequence system_lt9211_tx_pll_seq[] = {
+ /* TX PLL power down */
+ { 0x8236, 0x01 },
+ { 0x8237, ctx->lvds_dual_link ? 0x2a : 0x29 },
+ { 0x8238, 0x06 },
+ { 0x8239, 0x30 },
+ { 0x823a, 0x8e },
+ { 0x8737, 0x14 },
+ { 0x8713, 0x00 },
+ { 0x8713, 0x80 },
+ };
+
+ unsigned int pval;
+ int ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_phy_seq,
+ ARRAY_SIZE(system_lt9211_tx_phy_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_dig_seq,
+ ARRAY_SIZE(system_lt9211_tx_dig_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_multi_reg_write(ctx->regmap, system_lt9211_tx_pll_seq,
+ ARRAY_SIZE(system_lt9211_tx_pll_seq));
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(ctx->regmap, 0x871f, pval, pval & 0x80,
+ 10000, 1000000);
+ if (ret) {
+ dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read_poll_timeout(ctx->regmap, 0x8720, pval, pval & 0x80,
+ 10000, 1000000);
+ if (ret) {
+ dev_err(ctx->dev, "TX PLL unstable, ret=%i\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void lt9211_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct lt9211 *ctx = bridge_to_lt9211(bridge);
+ struct drm_atomic_state *state = old_bridge_state->base.state;
+ const struct drm_bridge_state *bridge_state;
+ const struct drm_crtc_state *crtc_state;
+ const struct drm_display_mode *mode;
+ struct drm_connector *connector;
+ struct drm_crtc *crtc;
+ bool lvds_format_24bpp;
+ bool lvds_format_jeida;
+ u32 bus_flags;
+ int ret;
+
+ ret = regulator_enable(ctx->vccio);
+ if (ret) {
+ dev_err(ctx->dev, "Failed to enable vccio: %d\n", ret);
+ return;
+ }
+
+ /* Deassert reset */
+ gpiod_set_value(ctx->reset_gpio, 1);
+ usleep_range(20000, 21000); /* Very long post-reset delay. */
+
+ /* Get the LVDS format from the bridge state. */
+ bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+ bus_flags = bridge_state->output_bus_cfg.flags;
+
+ switch (bridge_state->output_bus_cfg.format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ lvds_format_24bpp = false;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ break;
+ default:
+ /*
+ * Some bridges still don't set the correct
+ * LVDS bus pixel format, use SPWG24 default
+ * format until those are fixed.
+ */
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ dev_warn(ctx->dev,
+ "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
+ bridge_state->output_bus_cfg.format);
+ break;
+ }
+
+ /*
+ * Retrieve the CRTC adjusted mode. This requires a little dance to go
+ * from the bridge to the encoder, to the connector and to the CRTC.
+ */
+ connector = drm_atomic_get_new_connector_for_encoder(state,
+ bridge->encoder);
+ crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+ crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ mode = &crtc_state->adjusted_mode;
+
+ ret = lt9211_read_chipid(ctx);
+ if (ret)
+ return;
+
+ ret = lt9211_system_init(ctx);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_rx(ctx);
+ if (ret)
+ return;
+
+ ret = lt9211_autodetect_rx(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_timing(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_plls(ctx, mode);
+ if (ret)
+ return;
+
+ ret = lt9211_configure_tx(ctx, lvds_format_jeida, lvds_format_24bpp,
+ bus_flags & DRM_BUS_FLAG_DE_HIGH);
+ if (ret)
+ return;
+
+ dev_dbg(ctx->dev, "LT9211 enabled.\n");
+}
+
+static void lt9211_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct lt9211 *ctx = bridge_to_lt9211(bridge);
+ int ret;
+
+ /*
+ * Put the chip in reset, pull nRST line low,
+ * and assure lengthy 10ms reset low timing.
+ */
+ gpiod_set_value(ctx->reset_gpio, 0);
+ usleep_range(10000, 11000); /* Very long reset duration. */
+
+ ret = regulator_disable(ctx->vccio);
+ if (ret)
+ dev_err(ctx->dev, "Failed to disable vccio: %d\n", ret);
+
+ regcache_mark_dirty(ctx->regmap);
+}
+
+static enum drm_mode_status
+lt9211_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ /* LVDS output clock range 25..176 MHz */
+ if (mode->clock < 25000)
+ return MODE_CLOCK_LOW;
+ if (mode->clock > 176000)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+#define MAX_INPUT_SEL_FORMATS 1
+
+static u32 *
+lt9211_atomic_get_input_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;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ /* This is the DSI-end bus format */
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+ *num_input_fmts = 1;
+
+ return input_fmts;
+}
+
+static const struct drm_bridge_funcs lt9211_funcs = {
+ .attach = lt9211_attach,
+ .mode_valid = lt9211_mode_valid,
+ .atomic_enable = lt9211_atomic_enable,
+ .atomic_disable = lt9211_atomic_disable,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_get_input_bus_fmts = lt9211_atomic_get_input_bus_fmts,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+};
+
+static int lt9211_parse_dt(struct lt9211 *ctx)
+{
+ struct device_node *port2, *port3;
+ struct drm_bridge *panel_bridge;
+ struct device *dev = ctx->dev;
+ struct drm_panel *panel;
+ int dual_link;
+ int ret;
+
+ ctx->vccio = devm_regulator_get(dev, "vccio");
+ if (IS_ERR(ctx->vccio))
+ return dev_err_probe(dev, PTR_ERR(ctx->vccio),
+ "Failed to get supply 'vccio'\n");
+
+ ctx->lvds_dual_link = false;
+ ctx->lvds_dual_link_even_odd_swap = false;
+
+ port2 = of_graph_get_port_by_id(dev->of_node, 2);
+ port3 = of_graph_get_port_by_id(dev->of_node, 3);
+ dual_link = drm_of_lvds_get_dual_link_pixel_order(port2, port3);
+ of_node_put(port2);
+ of_node_put(port3);
+
+ if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) {
+ ctx->lvds_dual_link = true;
+ /* Odd pixels to LVDS Channel A, even pixels to B */
+ ctx->lvds_dual_link_even_odd_swap = false;
+ } else if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) {
+ ctx->lvds_dual_link = true;
+ /* Even pixels to LVDS Channel A, odd pixels to B */
+ ctx->lvds_dual_link_even_odd_swap = true;
+ }
+
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, &panel_bridge);
+ if (ret < 0)
+ return ret;
+ if (panel) {
+ panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ if (IS_ERR(panel_bridge))
+ return PTR_ERR(panel_bridge);
+ }
+
+ ctx->panel_bridge = panel_bridge;
+
+ return 0;
+}
+
+static int lt9211_host_attach(struct lt9211 *ctx)
+{
+ const struct mipi_dsi_device_info info = {
+ .type = "lt9211",
+ .channel = 0,
+ .node = NULL,
+ };
+ struct device *dev = ctx->dev;
+ struct device_node *host_node;
+ struct device_node *endpoint;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ int dsi_lanes;
+ int ret;
+
+ endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
+ dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+ host_node = of_graph_get_remote_port_parent(endpoint);
+ host = of_find_mipi_dsi_host_by_node(host_node);
+ of_node_put(host_node);
+ of_node_put(endpoint);
+
+ if (!host)
+ return -EPROBE_DEFER;
+
+ if (dsi_lanes < 0 || dsi_lanes > 4)
+ return -EINVAL;
+
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ if (IS_ERR(dsi))
+ return dev_err_probe(dev, PTR_ERR(dsi),
+ "failed to create dsi device\n");
+
+ ctx->dsi = dsi;
+
+ dsi->lanes = dsi_lanes;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_VIDEO_HSE;
+
+ ret = devm_mipi_dsi_attach(dev, dsi);
+ if (ret < 0) {
+ dev_err(dev, "failed to attach dsi to host: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lt9211_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct lt9211 *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+
+ /*
+ * Put the chip in reset, pull nRST line low,
+ * and assure lengthy 10ms reset low timing.
+ */
+ ctx->reset_gpio = devm_gpiod_get_optional(ctx->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio))
+ return PTR_ERR(ctx->reset_gpio);
+
+ usleep_range(10000, 11000); /* Very long reset duration. */
+
+ ret = lt9211_parse_dt(ctx);
+ if (ret)
+ return ret;
+
+ ctx->regmap = devm_regmap_init_i2c(client, &lt9211_regmap_config);
+ if (IS_ERR(ctx->regmap))
+ return PTR_ERR(ctx->regmap);
+
+ dev_set_drvdata(dev, ctx);
+ i2c_set_clientdata(client, ctx);
+
+ ctx->bridge.funcs = &lt9211_funcs;
+ ctx->bridge.of_node = dev->of_node;
+ drm_bridge_add(&ctx->bridge);
+
+ ret = lt9211_host_attach(ctx);
+ if (ret)
+ drm_bridge_remove(&ctx->bridge);
+
+ return ret;
+}
+
+static int lt9211_remove(struct i2c_client *client)
+{
+ struct lt9211 *ctx = i2c_get_clientdata(client);
+
+ drm_bridge_remove(&ctx->bridge);
+
+ return 0;
+}
+
+static struct i2c_device_id lt9211_id[] = {
+ { "lontium,lt9211" },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, lt9211_id);
+
+static const struct of_device_id lt9211_match_table[] = {
+ { .compatible = "lontium,lt9211" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, lt9211_match_table);
+
+static struct i2c_driver lt9211_driver = {
+ .probe = lt9211_probe,
+ .remove = lt9211_remove,
+ .id_table = lt9211_id,
+ .driver = {
+ .name = "lt9211",
+ .of_match_table = lt9211_match_table,
+ },
+};
+module_i2c_driver(lt9211_driver);
+
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
+MODULE_DESCRIPTION("Lontium LT9211 DSI/LVDS/DPI bridge driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c
index 63df2e8a8abc..7ef8fe5abc12 100644
--- a/drivers/gpu/drm/bridge/lontium-lt9611.c
+++ b/drivers/gpu/drm/bridge/lontium-lt9611.c
@@ -700,7 +700,9 @@ lt9611_connector_mode_valid(struct drm_connector *connector,
}
/* bridge funcs */
-static void lt9611_bridge_enable(struct drm_bridge *bridge)
+static void
+lt9611_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
@@ -721,7 +723,9 @@ static void lt9611_bridge_enable(struct drm_bridge *bridge)
regmap_write(lt9611->regmap, 0x8130, 0xea);
}
-static void lt9611_bridge_disable(struct drm_bridge *bridge)
+static void
+lt9611_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
int ret;
@@ -856,7 +860,9 @@ static void lt9611_bridge_pre_enable(struct drm_bridge *bridge)
lt9611->sleep = false;
}
-static void lt9611_bridge_post_disable(struct drm_bridge *bridge)
+static void
+lt9611_bridge_atomic_post_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
@@ -916,16 +922,47 @@ static void lt9611_bridge_hpd_enable(struct drm_bridge *bridge)
lt9611_enable_hpd_interrupts(lt9611);
}
+#define MAX_INPUT_SEL_FORMATS 1
+
+static u32 *
+lt9611_atomic_get_input_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;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ /* This is the DSI-end bus format */
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+ *num_input_fmts = 1;
+
+ return input_fmts;
+}
+
static const struct drm_bridge_funcs lt9611_bridge_funcs = {
.attach = lt9611_bridge_attach,
.mode_valid = lt9611_bridge_mode_valid,
- .enable = lt9611_bridge_enable,
- .disable = lt9611_bridge_disable,
- .post_disable = lt9611_bridge_post_disable,
.mode_set = lt9611_bridge_mode_set,
.detect = lt9611_bridge_detect,
.get_edid = lt9611_bridge_get_edid,
.hpd_enable = lt9611_bridge_hpd_enable,
+
+ .atomic_enable = lt9611_bridge_atomic_enable,
+ .atomic_disable = lt9611_bridge_atomic_disable,
+ .atomic_post_disable = lt9611_bridge_atomic_post_disable,
+ .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,
+ .atomic_get_input_bus_fmts = lt9611_atomic_get_input_bus_fmts,
};
static int lt9611_parse_dt(struct device *dev,
diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c
index d5945501a5ee..ad74e6558eb3 100644
--- a/drivers/gpu/drm/bridge/nwl-dsi.c
+++ b/drivers/gpu/drm/bridge/nwl-dsi.c
@@ -26,7 +26,6 @@
#include <drm/drm_bridge.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
-#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <video/mipi_display.h>
@@ -853,7 +852,7 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
/* Save the new desired phy config */
memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
- memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode));
+ drm_mode_copy(&dsi->mode, adjusted_mode);
drm_mode_debug_printmodeline(adjusted_mode);
if (pm_runtime_resume_and_get(dev) < 0)
@@ -910,32 +909,14 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge,
{
struct nwl_dsi *dsi = bridge_to_dsi(bridge);
struct drm_bridge *panel_bridge;
- struct drm_panel *panel;
- int ret;
- ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel,
- &panel_bridge);
- if (ret)
- return ret;
-
- if (panel) {
- panel_bridge = drm_panel_bridge_add(panel);
- if (IS_ERR(panel_bridge))
- return PTR_ERR(panel_bridge);
- }
-
- if (!panel_bridge)
- return -EPROBE_DEFER;
+ panel_bridge = devm_drm_of_get_bridge(dsi->dev, dsi->dev->of_node, 1, 0);
+ if (IS_ERR(panel_bridge))
+ return PTR_ERR(panel_bridge);
return drm_bridge_attach(bridge->encoder, panel_bridge, bridge, flags);
}
-static void nwl_dsi_bridge_detach(struct drm_bridge *bridge)
-{ struct nwl_dsi *dsi = bridge_to_dsi(bridge);
-
- drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0);
-}
-
static u32 *nwl_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
@@ -981,7 +962,6 @@ static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
.mode_set = nwl_dsi_bridge_mode_set,
.mode_valid = nwl_dsi_bridge_mode_valid,
.attach = nwl_dsi_bridge_attach,
- .detach = nwl_dsi_bridge_detach,
};
static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
@@ -1153,7 +1133,7 @@ MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids);
static const struct soc_device_attribute nwl_dsi_quirks_match[] = {
{ .soc_id = "i.MX8MQ", .revision = "2.0",
.data = (void *)E11418_HS_MODE_QUIRK },
- { /* sentinel. */ },
+ { /* sentinel. */ }
};
static int nwl_dsi_probe(struct platform_device *pdev)
diff --git a/drivers/gpu/drm/bridge/nxp-ptn3460.c b/drivers/gpu/drm/bridge/nxp-ptn3460.c
index e941c1132598..1ab91f4e057b 100644
--- a/drivers/gpu/drm/bridge/nxp-ptn3460.c
+++ b/drivers/gpu/drm/bridge/nxp-ptn3460.c
@@ -263,7 +263,6 @@ static int ptn3460_probe(struct i2c_client *client,
struct device *dev = &client->dev;
struct ptn3460_bridge *ptn_bridge;
struct drm_bridge *panel_bridge;
- struct drm_panel *panel;
int ret;
ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
@@ -271,11 +270,7 @@ static int ptn3460_probe(struct i2c_client *client,
return -ENOMEM;
}
- ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, &panel, NULL);
- if (ret)
- return ret;
-
- panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 5be057575183..0ee563eb2b6f 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -83,6 +83,12 @@ static int panel_bridge_attach(struct drm_bridge *bridge,
drm_connector_attach_encoder(&panel_bridge->connector,
bridge->encoder);
+ if (bridge->dev->registered) {
+ if (connector->funcs->reset)
+ connector->funcs->reset(connector);
+ drm_connector_register(connector);
+ }
+
return 0;
}
diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c
index 614b19f0f1b7..37b308850b4e 100644
--- a/drivers/gpu/drm/bridge/parade-ps8622.c
+++ b/drivers/gpu/drm/bridge/parade-ps8622.c
@@ -452,18 +452,13 @@ static int ps8622_probe(struct i2c_client *client,
struct device *dev = &client->dev;
struct ps8622_bridge *ps8622;
struct drm_bridge *panel_bridge;
- struct drm_panel *panel;
int ret;
ps8622 = devm_kzalloc(dev, sizeof(*ps8622), GFP_KERNEL);
if (!ps8622)
return -ENOMEM;
- ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, &panel, NULL);
- if (ret)
- return ret;
-
- panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c
index 3f17337ee389..edb939b14c04 100644
--- a/drivers/gpu/drm/bridge/parade-ps8640.c
+++ b/drivers/gpu/drm/bridge/parade-ps8640.c
@@ -13,9 +13,9 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <drm/display/drm_dp_aux_bus.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_bridge.h>
-#include <drm/dp/drm_dp_aux_bus.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
@@ -589,7 +589,6 @@ static int ps8640_probe(struct i2c_client *client)
struct device *dev = &client->dev;
struct device_node *np = dev->of_node;
struct ps8640 *ps_bridge;
- struct drm_panel *panel;
int ret;
u32 i;
@@ -674,13 +673,7 @@ static int ps8640_probe(struct i2c_client *client)
devm_of_dp_aux_populate_ep_devices(&ps_bridge->aux);
/* port@1 is ps8640 output port */
- ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
- if (ret < 0)
- return ret;
- if (!panel)
- return -ENODEV;
-
- ps_bridge->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ ps_bridge->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
if (IS_ERR(ps_bridge->panel_bridge))
return PTR_ERR(ps_bridge->panel_bridge);
diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
index 21a1be3ced0f..15fc182d05ef 100644
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_DW_HDMI
tristate
+ select DRM_DISPLAY_HDMI_HELPER
+ select DRM_DISPLAY_HELPER
select DRM_KMS_HELPER
select REGMAP_MMIO
select CEC_CORE if CEC_NOTIFIER
@@ -25,6 +27,16 @@ config DRM_DW_HDMI_I2S_AUDIO
Support the I2S Audio interface which is part of the Synopsys
Designware HDMI block.
+config DRM_DW_HDMI_GP_AUDIO
+ tristate "Synopsys Designware GP Audio interface"
+ depends on DRM_DW_HDMI && SND
+ select SND_PCM
+ select SND_PCM_ELD
+ select SND_PCM_IEC958
+ help
+ Support the GP Audio interface which is part of the Synopsys
+ Designware HDMI block.
+
config DRM_DW_HDMI_CEC
tristate "Synopsis Designware CEC interface"
depends on DRM_DW_HDMI
diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
index 91d746ad5de1..ce715562e9e5 100644
--- a/drivers/gpu/drm/bridge/synopsys/Makefile
+++ b/drivers/gpu/drm/bridge/synopsys/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c
new file mode 100644
index 000000000000..557966239677
--- /dev/null
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * dw-hdmi-gp-audio.c
+ *
+ * Copyright 2020-2022 NXP
+ */
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_connector.h>
+
+#include <sound/hdmi-codec.h>
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_drm_eld.h>
+#include <sound/pcm_iec958.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "dw-hdmi-audio.h"
+
+#define DRIVER_NAME "dw-hdmi-gp-audio"
+#define DRV_NAME "hdmi-gp-audio"
+
+struct snd_dw_hdmi {
+ struct dw_hdmi_audio_data data;
+ struct platform_device *audio_pdev;
+ unsigned int pos;
+};
+
+struct dw_hdmi_channel_conf {
+ u8 conf1;
+ u8 ca;
+};
+
+/*
+ * The default mapping of ALSA channels to HDMI channels and speaker
+ * allocation bits. Note that we can't do channel remapping here -
+ * channels must be in the same order.
+ *
+ * Mappings for alsa-lib pcm/surround*.conf files:
+ *
+ * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1
+ * Channels 2 4 6 6 6 8
+ *
+ * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel:
+ *
+ * Number of ALSA channels
+ * ALSA Channel 2 3 4 5 6 7 8
+ * 0 FL:0 = = = = = =
+ * 1 FR:1 = = = = = =
+ * 2 FC:3 RL:4 LFE:2 = = =
+ * 3 RR:5 RL:4 FC:3 = =
+ * 4 RR:5 RL:4 = =
+ * 5 RR:5 = =
+ * 6 RC:6 =
+ * 7 RLC/FRC RLC/FRC
+ */
+static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = {
+ { 0x03, 0x00 }, /* FL,FR */
+ { 0x0b, 0x02 }, /* FL,FR,FC */
+ { 0x33, 0x08 }, /* FL,FR,RL,RR */
+ { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */
+ { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */
+ { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */
+ { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */
+};
+
+static int audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+ u8 ca;
+
+ dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate);
+
+ ca = default_hdmi_channel_config[params->channels - 2].ca;
+
+ dw_hdmi_set_channel_count(dw->data.hdmi, params->channels);
+ dw_hdmi_set_channel_allocation(dw->data.hdmi, ca);
+
+ dw_hdmi_set_sample_non_pcm(dw->data.hdmi,
+ params->iec.status[0] & IEC958_AES0_NONAUDIO);
+ dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width);
+
+ return 0;
+}
+
+static void audio_shutdown(struct device *dev, void *data)
+{
+}
+
+static int audio_mute_stream(struct device *dev, void *data,
+ bool enable, int direction)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+
+ if (!enable)
+ dw_hdmi_audio_enable(dw->data.hdmi);
+ else
+ dw_hdmi_audio_disable(dw->data.hdmi);
+
+ return 0;
+}
+
+static int audio_get_eld(struct device *dev, void *data,
+ u8 *buf, size_t len)
+{
+ struct dw_hdmi_audio_data *audio = data;
+ u8 *eld;
+
+ eld = audio->get_eld(audio->hdmi);
+ if (eld)
+ memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
+ else
+ /* Pass en empty ELD if connector not available */
+ memset(buf, 0, len);
+
+ return 0;
+}
+
+static int audio_hook_plugged_cb(struct device *dev, void *data,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+
+ return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev);
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+ .hw_params = audio_hw_params,
+ .audio_shutdown = audio_shutdown,
+ .mute_stream = audio_mute_stream,
+ .get_eld = audio_get_eld,
+ .hook_plugged_cb = audio_hook_plugged_cb,
+};
+
+static int snd_dw_hdmi_probe(struct platform_device *pdev)
+{
+ struct dw_hdmi_audio_data *data = pdev->dev.platform_data;
+ struct snd_dw_hdmi *dw;
+
+ const struct hdmi_codec_pdata codec_data = {
+ .i2s = 1,
+ .spdif = 0,
+ .ops = &audio_codec_ops,
+ .max_i2s_channels = 8,
+ .data = data,
+ };
+
+ dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL);
+ if (!dw)
+ return -ENOMEM;
+
+ dw->data = *data;
+
+ platform_set_drvdata(pdev, dw);
+
+ dw->audio_pdev = platform_device_register_data(&pdev->dev,
+ HDMI_CODEC_DRV_NAME, 1,
+ &codec_data,
+ sizeof(codec_data));
+
+ return PTR_ERR_OR_ZERO(dw->audio_pdev);
+}
+
+static int snd_dw_hdmi_remove(struct platform_device *pdev)
+{
+ struct snd_dw_hdmi *dw = platform_get_drvdata(pdev);
+
+ platform_device_unregister(dw->audio_pdev);
+
+ return 0;
+}
+
+static struct platform_driver snd_dw_hdmi_driver = {
+ .probe = snd_dw_hdmi_probe,
+ .remove = snd_dw_hdmi_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+module_platform_driver(snd_dw_hdmi_driver);
+
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_DESCRIPTION("Synopsys Designware HDMI GPA ALSA interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 4befc104d220..3e1be9894ed1 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -25,14 +25,14 @@
#include <uapi/linux/videodev2.h>
#include <drm/bridge/dw_hdmi.h>
+#include <drm/display/drm_hdmi_helper.h>
+#include <drm/display/drm_scdc_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
-#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
-#include <drm/drm_scdc_helper.h>
#include "dw-hdmi-audio.h"
#include "dw-hdmi-cec.h"
@@ -191,7 +191,10 @@ struct dw_hdmi {
spinlock_t audio_lock;
struct mutex audio_mutex;
+ unsigned int sample_non_pcm;
+ unsigned int sample_width;
unsigned int sample_rate;
+ unsigned int channels;
unsigned int audio_cts;
unsigned int audio_n;
bool audio_enable;
@@ -589,6 +592,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 4096;
else if (pixel_clk == 74176000 || pixel_clk == 148352000)
n = 11648;
+ else if (pixel_clk == 297000000)
+ n = 3072;
else
n = 4096;
n *= mult;
@@ -601,6 +606,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 17836;
else if (pixel_clk == 148352000)
n = 8918;
+ else if (pixel_clk == 297000000)
+ n = 4704;
else
n = 6272;
n *= mult;
@@ -615,6 +622,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 11648;
else if (pixel_clk == 148352000)
n = 5824;
+ else if (pixel_clk == 297000000)
+ n = 5120;
else
n = 6144;
n *= mult;
@@ -659,8 +668,8 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID);
- /* Only compute CTS when using internal AHB audio */
- if (config3 & HDMI_CONFIG3_AHBAUDDMA) {
+ /* Compute CTS when using internal AHB audio or General Parallel audio*/
+ if ((config3 & HDMI_CONFIG3_AHBAUDDMA) || (config3 & HDMI_CONFIG3_GPAUD)) {
/*
* Compute the CTS value from the N value. Note that CTS and N
* can be up to 20 bits in total, so we need 64-bit math. Also
@@ -702,6 +711,22 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi)
mutex_unlock(&hdmi->audio_mutex);
}
+void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width)
+{
+ mutex_lock(&hdmi->audio_mutex);
+ hdmi->sample_width = width;
+ mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_width);
+
+void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm)
+{
+ mutex_lock(&hdmi->audio_mutex);
+ hdmi->sample_non_pcm = non_pcm;
+ mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_non_pcm);
+
void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate)
{
mutex_lock(&hdmi->audio_mutex);
@@ -717,6 +742,7 @@ void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt)
u8 layout;
mutex_lock(&hdmi->audio_mutex);
+ hdmi->channels = cnt;
/*
* For >2 channel PCM audio, we need to select layout 1
@@ -765,6 +791,89 @@ static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi)
return hdmi->curr_conn->eld;
}
+static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi)
+{
+ const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
+ int sample_freq = 0x2, org_sample_freq = 0xD;
+ int ch_mask = BIT(hdmi->channels) - 1;
+
+ switch (hdmi->sample_rate) {
+ case 32000:
+ sample_freq = 0x03;
+ org_sample_freq = 0x0C;
+ break;
+ case 44100:
+ sample_freq = 0x00;
+ org_sample_freq = 0x0F;
+ break;
+ case 48000:
+ sample_freq = 0x02;
+ org_sample_freq = 0x0D;
+ break;
+ case 88200:
+ sample_freq = 0x08;
+ org_sample_freq = 0x07;
+ break;
+ case 96000:
+ sample_freq = 0x0A;
+ org_sample_freq = 0x05;
+ break;
+ case 176400:
+ sample_freq = 0x0C;
+ org_sample_freq = 0x03;
+ break;
+ case 192000:
+ sample_freq = 0x0E;
+ org_sample_freq = 0x01;
+ break;
+ default:
+ break;
+ }
+
+ hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
+ hdmi_enable_audio_clk(hdmi, true);
+
+ hdmi_writeb(hdmi, 0x1, HDMI_FC_AUDSCHNLS0);
+ hdmi_writeb(hdmi, hdmi->channels, HDMI_FC_AUDSCHNLS2);
+ hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS3);
+ hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS4);
+ hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS5);
+ hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS6);
+ hdmi_writeb(hdmi, (0x3 << 4) | sample_freq, HDMI_FC_AUDSCHNLS7);
+ hdmi_writeb(hdmi, (org_sample_freq << 4) | 0xb, HDMI_FC_AUDSCHNLS8);
+
+ hdmi_writeb(hdmi, ch_mask, HDMI_GP_CONF1);
+ hdmi_writeb(hdmi, 0x02, HDMI_GP_CONF2);
+ hdmi_writeb(hdmi, 0x01, HDMI_GP_CONF0);
+
+ hdmi_modb(hdmi, 0x3, 0x3, HDMI_FC_DATAUTO3);
+
+ /* hbr */
+ if (hdmi->sample_rate == 192000 && hdmi->channels == 8 &&
+ hdmi->sample_width == 32 && hdmi->sample_non_pcm)
+ hdmi_modb(hdmi, 0x01, 0x01, HDMI_GP_CONF2);
+
+ if (pdata->enable_audio)
+ pdata->enable_audio(hdmi,
+ hdmi->channels,
+ hdmi->sample_width,
+ hdmi->sample_rate,
+ hdmi->sample_non_pcm);
+}
+
+static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi)
+{
+ const struct dw_hdmi_plat_data *pdata = hdmi->plat_data;
+
+ hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0);
+
+ hdmi_modb(hdmi, 0, 0x3, HDMI_FC_DATAUTO3);
+ if (pdata->disable_audio)
+ pdata->disable_audio(hdmi);
+
+ hdmi_enable_audio_clk(hdmi, false);
+}
+
static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi)
{
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
@@ -1108,6 +1217,8 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP;
struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data;
u8 val, vp_conf;
+ u8 clear_gcp_auto = 0;
+
if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) ||
hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) ||
@@ -1117,6 +1228,7 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
case 8:
color_depth = 4;
output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
+ clear_gcp_auto = 1;
break;
case 10:
color_depth = 5;
@@ -1136,6 +1248,7 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
case 0:
case 8:
remap_size = HDMI_VP_REMAP_YCC422_16bit;
+ clear_gcp_auto = 1;
break;
case 10:
remap_size = HDMI_VP_REMAP_YCC422_20bit;
@@ -1160,6 +1273,19 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK);
hdmi_writeb(hdmi, val, HDMI_VP_PR_CD);
+ /* HDMI1.4b specification section 6.5.3:
+ * Source shall only send GCPs with non-zero CD to sinks
+ * that indicate support for Deep Color.
+ * GCP only transmit CD and do not handle AVMUTE, PP norDefault_Phase (yet).
+ * Disable Auto GCP when 24-bit color for sinks that not support Deep Color.
+ */
+ val = hdmi_readb(hdmi, HDMI_FC_DATAUTO3);
+ if (clear_gcp_auto == 1)
+ val &= ~HDMI_FC_DATAUTO3_GCP_AUTO;
+ else
+ val |= HDMI_FC_DATAUTO3_GCP_AUTO;
+ hdmi_writeb(hdmi, val, HDMI_FC_DATAUTO3);
+
hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE,
HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF);
@@ -1357,13 +1483,21 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable)
HDMI_PHY_CONF0_SELDIPIF_MASK);
}
-void dw_hdmi_phy_reset(struct dw_hdmi *hdmi)
+void dw_hdmi_phy_gen1_reset(struct dw_hdmi *hdmi)
+{
+ /* PHY reset. The reset signal is active low on Gen1 PHYs. */
+ hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
+ hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen1_reset);
+
+void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi)
{
/* PHY reset. The reset signal is active high on Gen2 PHYs. */
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
}
-EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset);
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_reset);
void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address)
{
@@ -1517,7 +1651,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi,
if (phy->has_svsret)
dw_hdmi_phy_enable_svsret(hdmi, 1);
- dw_hdmi_phy_reset(hdmi);
+ dw_hdmi_phy_gen2_reset(hdmi);
hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
@@ -2086,30 +2220,21 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
* then write one of the FC registers several times.
*
* The number of iterations matters and depends on the HDMI TX revision
- * (and possibly on the platform). So far i.MX6Q (v1.30a), i.MX6DL
- * (v1.31a) and multiple Allwinner SoCs (v1.32a) have been identified
- * as needing the workaround, with 4 iterations for v1.30a and 1
- * iteration for others.
- * The Amlogic Meson GX SoCs (v2.01a) have been identified as needing
- * the workaround with a single iteration.
- * The Rockchip RK3288 SoC (v2.00a) and RK3328/RK3399 SoCs (v2.11a) have
- * been identified as needing the workaround with a single iteration.
+ * (and possibly on the platform).
+ * 4 iterations for i.MX6Q(v1.30a) and 1 iteration for others.
+ * i.MX6DL (v1.31a), Allwinner SoCs (v1.32a), Rockchip RK3288 SoC (v2.00a),
+ * Amlogic Meson GX SoCs (v2.01a), RK3328/RK3399 SoCs (v2.11a)
+ * and i.MX8MPlus (v2.13a) have been identified as needing the workaround
+ * with a single iteration.
*/
switch (hdmi->version) {
case 0x130a:
count = 4;
break;
- case 0x131a:
- case 0x132a:
- case 0x200a:
- case 0x201a:
- case 0x211a:
- case 0x212a:
+ default:
count = 1;
break;
- default:
- return;
}
/* TMDS software reset */
@@ -2830,7 +2955,7 @@ static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
mutex_lock(&hdmi->mutex);
/* Store the display mode for plugin/DKMS poweron events */
- memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode));
+ drm_mode_copy(&hdmi->previous_mode, mode);
mutex_unlock(&hdmi->mutex);
}
@@ -3242,6 +3367,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
hdmi->plat_data = plat_data;
hdmi->dev = dev;
hdmi->sample_rate = 48000;
+ hdmi->channels = 2;
hdmi->disabled = true;
hdmi->rxsense = true;
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
@@ -3465,6 +3591,24 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
+ } else if (iores && config3 & HDMI_CONFIG3_GPAUD) {
+ struct dw_hdmi_audio_data audio;
+
+ audio.phys = iores->start;
+ audio.base = hdmi->regs;
+ audio.irq = irq;
+ audio.hdmi = hdmi;
+ audio.get_eld = hdmi_audio_get_eld;
+
+ hdmi->enable_audio = dw_hdmi_gp_audio_enable;
+ hdmi->disable_audio = dw_hdmi_gp_audio_disable;
+
+ pdevinfo.name = "dw-hdmi-gp-audio";
+ pdevinfo.id = PLATFORM_DEVID_NONE;
+ pdevinfo.data = &audio;
+ pdevinfo.size_data = sizeof(audio);
+ pdevinfo.dma_mask = DMA_BIT_MASK(32);
+ hdmi->audio = platform_device_register_full(&pdevinfo);
}
if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) {
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
index 1999db05bc3b..af43a0414b78 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
@@ -158,8 +158,17 @@
#define HDMI_FC_SPDDEVICEINF 0x1062
#define HDMI_FC_AUDSCONF 0x1063
#define HDMI_FC_AUDSSTAT 0x1064
-#define HDMI_FC_AUDSCHNLS7 0x106e
-#define HDMI_FC_AUDSCHNLS8 0x106f
+#define HDMI_FC_AUDSV 0x1065
+#define HDMI_FC_AUDSU 0x1066
+#define HDMI_FC_AUDSCHNLS0 0x1067
+#define HDMI_FC_AUDSCHNLS1 0x1068
+#define HDMI_FC_AUDSCHNLS2 0x1069
+#define HDMI_FC_AUDSCHNLS3 0x106A
+#define HDMI_FC_AUDSCHNLS4 0x106B
+#define HDMI_FC_AUDSCHNLS5 0x106C
+#define HDMI_FC_AUDSCHNLS6 0x106D
+#define HDMI_FC_AUDSCHNLS7 0x106E
+#define HDMI_FC_AUDSCHNLS8 0x106F
#define HDMI_FC_DATACH0FILL 0x1070
#define HDMI_FC_DATACH1FILL 0x1071
#define HDMI_FC_DATACH2FILL 0x1072
@@ -850,6 +859,9 @@ enum {
HDMI_FC_DATAUTO0_VSD_MASK = 0x08,
HDMI_FC_DATAUTO0_VSD_OFFSET = 3,
+/* FC_DATAUTO3 field values */
+ HDMI_FC_DATAUTO3_GCP_AUTO = 0x04,
+
/* PHY_CONF0 field values */
HDMI_PHY_CONF0_PDZ_MASK = 0x80,
HDMI_PHY_CONF0_PDZ_OFFSET = 7,
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
index 11d20b8638cd..b2efecf7d160 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
@@ -246,7 +246,6 @@ struct dw_mipi_dsi {
struct clk *pclk;
- bool device_found;
unsigned int lane_mbps; /* per lane */
u32 channel;
u32 lanes;
@@ -310,37 +309,12 @@ static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg)
return readl(dsi->base + reg);
}
-static int dw_mipi_dsi_panel_or_bridge(struct dw_mipi_dsi *dsi,
- struct device_node *node)
-{
- struct drm_bridge *bridge;
- struct drm_panel *panel;
- int ret;
-
- ret = drm_of_find_panel_or_bridge(node, 1, 0, &panel, &bridge);
- if (ret)
- return ret;
-
- if (panel) {
- bridge = drm_panel_bridge_add_typed(panel,
- DRM_MODE_CONNECTOR_DSI);
- if (IS_ERR(bridge))
- return PTR_ERR(bridge);
- }
-
- dsi->panel_bridge = bridge;
-
- if (!dsi->panel_bridge)
- return -EPROBE_DEFER;
-
- return 0;
-}
-
static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
{
struct dw_mipi_dsi *dsi = host_to_dsi(host);
const struct dw_mipi_dsi_plat_data *pdata = dsi->plat_data;
+ struct drm_bridge *bridge;
int ret;
if (device->lanes > dsi->plat_data->max_data_lanes) {
@@ -354,13 +328,13 @@ static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
dsi->format = device->format;
dsi->mode_flags = device->mode_flags;
- if (!dsi->device_found) {
- ret = dw_mipi_dsi_panel_or_bridge(dsi, host->dev->of_node);
- if (ret)
- return ret;
+ bridge = devm_drm_of_get_bridge(dsi->dev, dsi->dev->of_node, 1, 0);
+ if (IS_ERR(bridge))
+ return PTR_ERR(bridge);
- dsi->device_found = true;
- }
+ dsi->panel_bridge = bridge;
+
+ drm_bridge_add(&dsi->bridge);
if (pdata->host_ops && pdata->host_ops->attach) {
ret = pdata->host_ops->attach(pdata->priv_data, device);
@@ -1021,16 +995,6 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge,
/* Set the encoder type as caller does not know it */
bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;
- if (!dsi->device_found) {
- int ret;
-
- ret = dw_mipi_dsi_panel_or_bridge(dsi, dsi->dev->of_node);
- if (ret)
- return ret;
-
- dsi->device_found = true;
- }
-
/* Attach the panel-bridge to the dsi bridge */
return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
flags);
@@ -1217,7 +1181,6 @@ __dw_mipi_dsi_probe(struct platform_device *pdev,
#ifdef CONFIG_OF
dsi->bridge.of_node = pdev->dev.of_node;
#endif
- drm_bridge_add(&dsi->bridge);
return dsi;
}
diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c
index 1bfdfc6affaf..40439da4db49 100644
--- a/drivers/gpu/drm/bridge/tc358762.c
+++ b/drivers/gpu/drm/bridge/tc358762.c
@@ -61,7 +61,6 @@
struct tc358762 {
struct device *dev;
struct drm_bridge bridge;
- struct drm_connector connector;
struct regulator *regulator;
struct drm_bridge *panel_bridge;
bool pre_enabled;
@@ -179,15 +178,8 @@ static int tc358762_parse_dt(struct tc358762 *ctx)
{
struct drm_bridge *panel_bridge;
struct device *dev = ctx->dev;
- struct drm_panel *panel;
- int ret;
-
- ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
- if (ret)
- return ret;
-
- panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c
index c1e35bdf9232..dca41ed32f8a 100644
--- a/drivers/gpu/drm/bridge/tc358764.c
+++ b/drivers/gpu/drm/bridge/tc358764.c
@@ -16,14 +16,9 @@
#include <video/mipi_display.h>
#include <drm/drm_atomic_helper.h>
-#include <drm/drm_bridge.h>
-#include <drm/drm_crtc.h>
-#include <drm/drm_fb_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
-#include <drm/drm_panel.h>
#include <drm/drm_print.h>
-#include <drm/drm_probe_helper.h>
#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end))
#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
@@ -153,10 +148,9 @@ static const char * const tc358764_supplies[] = {
struct tc358764 {
struct device *dev;
struct drm_bridge bridge;
- struct drm_connector connector;
+ struct drm_bridge *next_bridge;
struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)];
struct gpio_desc *gpio_reset;
- struct drm_panel *panel;
int error;
};
@@ -210,12 +204,6 @@ static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge)
return container_of(bridge, struct tc358764, bridge);
}
-static inline
-struct tc358764 *connector_to_tc358764(struct drm_connector *connector)
-{
- return container_of(connector, struct tc358764, connector);
-}
-
static int tc358764_init(struct tc358764 *ctx)
{
u32 v = 0;
@@ -278,43 +266,11 @@ static void tc358764_reset(struct tc358764 *ctx)
usleep_range(1000, 2000);
}
-static int tc358764_get_modes(struct drm_connector *connector)
-{
- struct tc358764 *ctx = connector_to_tc358764(connector);
-
- return drm_panel_get_modes(ctx->panel, connector);
-}
-
-static const
-struct drm_connector_helper_funcs tc358764_connector_helper_funcs = {
- .get_modes = tc358764_get_modes,
-};
-
-static const struct drm_connector_funcs tc358764_connector_funcs = {
- .fill_modes = drm_helper_probe_single_connector_modes,
- .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 void tc358764_disable(struct drm_bridge *bridge)
-{
- struct tc358764 *ctx = bridge_to_tc358764(bridge);
- int ret = drm_panel_disable(bridge_to_tc358764(bridge)->panel);
-
- if (ret < 0)
- dev_err(ctx->dev, "error disabling panel (%d)\n", ret);
-}
-
static void tc358764_post_disable(struct drm_bridge *bridge)
{
struct tc358764 *ctx = bridge_to_tc358764(bridge);
int ret;
- ret = drm_panel_unprepare(ctx->panel);
- if (ret < 0)
- dev_err(ctx->dev, "error unpreparing panel (%d)\n", ret);
tc358764_reset(ctx);
usleep_range(10000, 15000);
ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
@@ -335,72 +291,25 @@ static void tc358764_pre_enable(struct drm_bridge *bridge)
ret = tc358764_init(ctx);
if (ret < 0)
dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
- ret = drm_panel_prepare(ctx->panel);
- if (ret < 0)
- dev_err(ctx->dev, "error preparing panel (%d)\n", ret);
-}
-
-static void tc358764_enable(struct drm_bridge *bridge)
-{
- struct tc358764 *ctx = bridge_to_tc358764(bridge);
- int ret = drm_panel_enable(ctx->panel);
-
- if (ret < 0)
- dev_err(ctx->dev, "error enabling panel (%d)\n", ret);
}
static int tc358764_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct tc358764 *ctx = bridge_to_tc358764(bridge);
- struct drm_device *drm = bridge->dev;
- int ret;
-
- if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
- DRM_ERROR("Fix bridge driver to make connector optional!");
- return -EINVAL;
- }
-
- ctx->connector.polled = DRM_CONNECTOR_POLL_HPD;
- ret = drm_connector_init(drm, &ctx->connector,
- &tc358764_connector_funcs,
- DRM_MODE_CONNECTOR_LVDS);
- if (ret) {
- DRM_ERROR("Failed to initialize connector\n");
- return ret;
- }
-
- drm_connector_helper_add(&ctx->connector,
- &tc358764_connector_helper_funcs);
- drm_connector_attach_encoder(&ctx->connector, bridge->encoder);
- ctx->connector.funcs->reset(&ctx->connector);
- drm_connector_register(&ctx->connector);
-
- return 0;
-}
-
-static void tc358764_detach(struct drm_bridge *bridge)
-{
- struct tc358764 *ctx = bridge_to_tc358764(bridge);
- drm_connector_unregister(&ctx->connector);
- ctx->panel = NULL;
- drm_connector_put(&ctx->connector);
+ return drm_bridge_attach(bridge->encoder, ctx->next_bridge, bridge, flags);
}
static const struct drm_bridge_funcs tc358764_bridge_funcs = {
- .disable = tc358764_disable,
.post_disable = tc358764_post_disable,
- .enable = tc358764_enable,
.pre_enable = tc358764_pre_enable,
.attach = tc358764_attach,
- .detach = tc358764_detach,
};
static int tc358764_parse_dt(struct tc358764 *ctx)
{
struct device *dev = ctx->dev;
- int ret;
ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(ctx->gpio_reset)) {
@@ -408,12 +317,11 @@ static int tc358764_parse_dt(struct tc358764 *ctx)
return PTR_ERR(ctx->gpio_reset);
}
- ret = drm_of_find_panel_or_bridge(ctx->dev->of_node, 1, 0, &ctx->panel,
- NULL);
- if (ret && ret != -EPROBE_DEFER)
- dev_err(dev, "cannot find panel (%d)\n", ret);
+ ctx->next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0);
+ if (IS_ERR(ctx->next_bridge))
+ return PTR_ERR(ctx->next_bridge);
- return ret;
+ return 0;
}
static int tc358764_configure_regulators(struct tc358764 *ctx)
diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c
index c23e0abc65e8..485717c8f0b4 100644
--- a/drivers/gpu/drm/bridge/tc358767.c
+++ b/drivers/gpu/drm/bridge/tc358767.c
@@ -1,6 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * tc358767 eDP bridge driver
+ * TC358767/TC358867/TC9595 DSI/DPI-to-DPI/(e)DP bridge driver
+ *
+ * The TC358767/TC358867/TC9595 can operate in multiple modes.
+ * The following modes are supported:
+ * DPI->(e)DP -- supported
+ * DSI->DPI .... supported
+ * DSI->(e)DP .. NOT supported
*
* Copyright (C) 2016 CogentEmbedded Inc
* Author: Andrey Gusakov <andrey.gusakov@cogentembedded.com>
@@ -25,10 +31,11 @@
#include <linux/regmap.h>
#include <linux/slab.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_edid.h>
+#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
@@ -36,7 +43,35 @@
/* Registers */
-/* Display Parallel Interface */
+/* PPI layer registers */
+#define PPI_STARTPPI 0x0104 /* START control bit */
+#define PPI_LPTXTIMECNT 0x0114 /* LPTX timing signal */
+#define LPX_PERIOD 3
+#define PPI_LANEENABLE 0x0134
+#define PPI_TX_RX_TA 0x013c
+#define TTA_GET 0x40000
+#define TTA_SURE 6
+#define PPI_D0S_ATMR 0x0144
+#define PPI_D1S_ATMR 0x0148
+#define PPI_D0S_CLRSIPOCOUNT 0x0164 /* Assertion timer for Lane 0 */
+#define PPI_D1S_CLRSIPOCOUNT 0x0168 /* Assertion timer for Lane 1 */
+#define PPI_D2S_CLRSIPOCOUNT 0x016c /* Assertion timer for Lane 2 */
+#define PPI_D3S_CLRSIPOCOUNT 0x0170 /* Assertion timer for Lane 3 */
+#define PPI_START_FUNCTION BIT(0)
+
+/* DSI layer registers */
+#define DSI_STARTDSI 0x0204 /* START control bit of DSI-TX */
+#define DSI_LANEENABLE 0x0210 /* Enables each lane */
+#define DSI_RX_START BIT(0)
+
+/* Lane enable PPI and DSI register bits */
+#define LANEENABLE_CLEN BIT(0)
+#define LANEENABLE_L0EN BIT(1)
+#define LANEENABLE_L1EN BIT(2)
+#define LANEENABLE_L2EN BIT(1)
+#define LANEENABLE_L3EN BIT(2)
+
+/* Display Parallel Input Interface */
#define DPIPXLFMT 0x0440
#define VS_POL_ACTIVE_LOW (1 << 10)
#define HS_POL_ACTIVE_LOW (1 << 9)
@@ -48,6 +83,14 @@
#define DPI_BPP_RGB666 (1 << 0)
#define DPI_BPP_RGB565 (2 << 0)
+/* Display Parallel Output Interface */
+#define POCTRL 0x0448
+#define POCTRL_S2P BIT(7)
+#define POCTRL_PCLK_POL BIT(3)
+#define POCTRL_VS_POL BIT(2)
+#define POCTRL_HS_POL BIT(1)
+#define POCTRL_DE_POL BIT(0)
+
/* Video Path */
#define VPCTRL0 0x0450
#define VSDELAY GENMASK(31, 20)
@@ -247,6 +290,9 @@ struct tc_data {
struct drm_bridge *panel_bridge;
struct drm_connector connector;
+ struct mipi_dsi_device *dsi;
+ u8 dsi_lanes;
+
/* link settings */
struct tc_edp_link link;
@@ -469,10 +515,24 @@ static int tc_pxl_pll_en(struct tc_data *tc, u32 refclk, u32 pixelclock)
int mul, best_mul = 1;
int delta, best_delta;
int ext_div[] = {1, 2, 3, 5, 7};
+ int clk_min, clk_max;
int best_pixelclock = 0;
int vco_hi = 0;
u32 pxl_pllparam;
+ /*
+ * refclk * mul / (ext_pre_div * pre_div) should be in range:
+ * - DPI ..... 0 to 100 MHz
+ * - (e)DP ... 150 to 650 MHz
+ */
+ if (tc->bridge.type == DRM_MODE_CONNECTOR_DPI) {
+ clk_min = 0;
+ clk_max = 100000000;
+ } else {
+ clk_min = 150000000;
+ clk_max = 650000000;
+ }
+
dev_dbg(tc->dev, "PLL: requested %d pixelclock, ref %d\n", pixelclock,
refclk);
best_delta = pixelclock;
@@ -499,11 +559,7 @@ static int tc_pxl_pll_en(struct tc_data *tc, u32 refclk, u32 pixelclock)
continue;
clk = (refclk / ext_div[i_pre] / div) * mul;
- /*
- * refclk * mul / (ext_pre_div * pre_div)
- * should be in the 150 to 650 MHz range
- */
- if ((clk > 650000000) || (clk < 150000000))
+ if ((clk > clk_max) || (clk < clk_min))
continue;
clk = clk / ext_div[i_post];
@@ -656,6 +712,12 @@ static int tc_aux_link_setup(struct tc_data *tc)
if (ret)
goto err;
+ /* Register DP AUX channel */
+ tc->aux.name = "TC358767 AUX i2c adapter";
+ tc->aux.dev = tc->dev;
+ tc->aux.transfer = tc_aux_transfer;
+ drm_dp_aux_init(&tc->aux);
+
return 0;
err:
dev_err(tc->dev, "tc_aux_link_setup failed: %d\n", ret);
@@ -728,33 +790,16 @@ err_dpcd_read:
return ret;
}
-static int tc_set_video_mode(struct tc_data *tc,
- const struct drm_display_mode *mode)
+static int tc_set_common_video_mode(struct tc_data *tc,
+ const struct drm_display_mode *mode)
{
- int ret;
- int vid_sync_dly;
- int max_tu_symbol;
-
int left_margin = mode->htotal - mode->hsync_end;
int right_margin = mode->hsync_start - mode->hdisplay;
int hsync_len = mode->hsync_end - mode->hsync_start;
int upper_margin = mode->vtotal - mode->vsync_end;
int lower_margin = mode->vsync_start - mode->vdisplay;
int vsync_len = mode->vsync_end - mode->vsync_start;
- u32 dp0_syncval;
- u32 bits_per_pixel = 24;
- u32 in_bw, out_bw;
-
- /*
- * Recommended maximum number of symbols transferred in a transfer unit:
- * DIV_ROUND_UP((input active video bandwidth in bytes) * tu_size,
- * (output active video bandwidth in bytes))
- * Must be less than tu_size.
- */
-
- in_bw = mode->clock * bits_per_pixel / 8;
- out_bw = tc->link.num_lanes * tc->link.rate;
- max_tu_symbol = DIV_ROUND_UP(in_bw * TU_SIZE_RECOMMENDED, out_bw);
+ int ret;
dev_dbg(tc->dev, "set mode %dx%d\n",
mode->hdisplay, mode->vdisplay);
@@ -812,8 +857,49 @@ static int tc_set_video_mode(struct tc_data *tc,
FIELD_PREP(COLOR_B, 99) |
ENI2CFILTER |
FIELD_PREP(COLOR_BAR_MODE, COLOR_BAR_MODE_BARS));
- if (ret)
- return ret;
+
+ return ret;
+}
+
+static int tc_set_dpi_video_mode(struct tc_data *tc,
+ const struct drm_display_mode *mode)
+{
+ u32 value = POCTRL_S2P;
+
+ if (tc->mode.flags & DRM_MODE_FLAG_NHSYNC)
+ value |= POCTRL_HS_POL;
+
+ if (tc->mode.flags & DRM_MODE_FLAG_NVSYNC)
+ value |= POCTRL_VS_POL;
+
+ return regmap_write(tc->regmap, POCTRL, value);
+}
+
+static int tc_set_edp_video_mode(struct tc_data *tc,
+ const struct drm_display_mode *mode)
+{
+ int ret;
+ int vid_sync_dly;
+ int max_tu_symbol;
+
+ int left_margin = mode->htotal - mode->hsync_end;
+ int hsync_len = mode->hsync_end - mode->hsync_start;
+ int upper_margin = mode->vtotal - mode->vsync_end;
+ int vsync_len = mode->vsync_end - mode->vsync_start;
+ u32 dp0_syncval;
+ u32 bits_per_pixel = 24;
+ u32 in_bw, out_bw;
+
+ /*
+ * Recommended maximum number of symbols transferred in a transfer unit:
+ * DIV_ROUND_UP((input active video bandwidth in bytes) * tu_size,
+ * (output active video bandwidth in bytes))
+ * Must be less than tu_size.
+ */
+
+ in_bw = mode->clock * bits_per_pixel / 8;
+ out_bw = tc->link.num_lanes * tc->link.rate;
+ max_tu_symbol = DIV_ROUND_UP(in_bw * TU_SIZE_RECOMMENDED, out_bw);
/* DP Main Stream Attributes */
vid_sync_dly = hsync_len + left_margin + mode->hdisplay;
@@ -863,10 +949,7 @@ static int tc_set_video_mode(struct tc_data *tc,
FIELD_PREP(MAX_TU_SYMBOL, max_tu_symbol) |
FIELD_PREP(TU_SIZE, TU_SIZE_RECOMMENDED) |
BPC_8);
- if (ret)
- return ret;
-
- return 0;
+ return ret;
}
static int tc_wait_link_training(struct tc_data *tc)
@@ -1164,7 +1247,86 @@ static int tc_main_link_disable(struct tc_data *tc)
return regmap_write(tc->regmap, DP0CTL, 0);
}
-static int tc_stream_enable(struct tc_data *tc)
+static int tc_dpi_stream_enable(struct tc_data *tc)
+{
+ int ret;
+ u32 value;
+
+ dev_dbg(tc->dev, "enable video stream\n");
+
+ /* Setup PLL */
+ ret = tc_set_syspllparam(tc);
+ if (ret)
+ return ret;
+
+ /*
+ * Initially PLLs are in bypass. Force PLL parameter update,
+ * disable PLL bypass, enable PLL
+ */
+ ret = tc_pllupdate(tc, DP0_PLLCTRL);
+ if (ret)
+ return ret;
+
+ ret = tc_pllupdate(tc, DP1_PLLCTRL);
+ if (ret)
+ return ret;
+
+ /* Pixel PLL must always be enabled for DPI mode */
+ ret = tc_pxl_pll_en(tc, clk_get_rate(tc->refclk),
+ 1000 * tc->mode.clock);
+ if (ret)
+ return ret;
+
+ regmap_write(tc->regmap, PPI_D0S_CLRSIPOCOUNT, 3);
+ regmap_write(tc->regmap, PPI_D1S_CLRSIPOCOUNT, 3);
+ regmap_write(tc->regmap, PPI_D2S_CLRSIPOCOUNT, 3);
+ regmap_write(tc->regmap, PPI_D3S_CLRSIPOCOUNT, 3);
+ regmap_write(tc->regmap, PPI_D0S_ATMR, 0);
+ regmap_write(tc->regmap, PPI_D1S_ATMR, 0);
+ regmap_write(tc->regmap, PPI_TX_RX_TA, TTA_GET | TTA_SURE);
+ regmap_write(tc->regmap, PPI_LPTXTIMECNT, LPX_PERIOD);
+
+ value = ((LANEENABLE_L0EN << tc->dsi_lanes) - LANEENABLE_L0EN) |
+ LANEENABLE_CLEN;
+ regmap_write(tc->regmap, PPI_LANEENABLE, value);
+ regmap_write(tc->regmap, DSI_LANEENABLE, value);
+
+ ret = tc_set_common_video_mode(tc, &tc->mode);
+ if (ret)
+ return ret;
+
+ ret = tc_set_dpi_video_mode(tc, &tc->mode);
+ if (ret)
+ return ret;
+
+ /* Set input interface */
+ value = DP0_AUDSRC_NO_INPUT;
+ if (tc_test_pattern)
+ value |= DP0_VIDSRC_COLOR_BAR;
+ else
+ value |= DP0_VIDSRC_DSI_RX;
+ ret = regmap_write(tc->regmap, SYSCTRL, value);
+ if (ret)
+ return ret;
+
+ usleep_range(120, 150);
+
+ regmap_write(tc->regmap, PPI_STARTPPI, PPI_START_FUNCTION);
+ regmap_write(tc->regmap, DSI_STARTDSI, DSI_RX_START);
+
+ return 0;
+}
+
+static int tc_dpi_stream_disable(struct tc_data *tc)
+{
+ dev_dbg(tc->dev, "disable video stream\n");
+
+ tc_pxl_pll_dis(tc);
+
+ return 0;
+}
+
+static int tc_edp_stream_enable(struct tc_data *tc)
{
int ret;
u32 value;
@@ -1179,7 +1341,11 @@ static int tc_stream_enable(struct tc_data *tc)
return ret;
}
- ret = tc_set_video_mode(tc, &tc->mode);
+ ret = tc_set_common_video_mode(tc, &tc->mode);
+ if (ret)
+ return ret;
+
+ ret = tc_set_edp_video_mode(tc, &tc->mode);
if (ret)
return ret;
@@ -1219,7 +1385,7 @@ static int tc_stream_enable(struct tc_data *tc)
return 0;
}
-static int tc_stream_disable(struct tc_data *tc)
+static int tc_edp_stream_disable(struct tc_data *tc)
{
int ret;
@@ -1234,7 +1400,37 @@ static int tc_stream_disable(struct tc_data *tc)
return 0;
}
-static void tc_bridge_enable(struct drm_bridge *bridge)
+static void
+tc_dpi_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+
+{
+ struct tc_data *tc = bridge_to_tc(bridge);
+ int ret;
+
+ ret = tc_dpi_stream_enable(tc);
+ if (ret < 0) {
+ dev_err(tc->dev, "main link stream start error: %d\n", ret);
+ tc_main_link_disable(tc);
+ return;
+ }
+}
+
+static void
+tc_dpi_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct tc_data *tc = bridge_to_tc(bridge);
+ int ret;
+
+ ret = tc_dpi_stream_disable(tc);
+ if (ret < 0)
+ dev_err(tc->dev, "main link stream stop error: %d\n", ret);
+}
+
+static void
+tc_edp_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct tc_data *tc = bridge_to_tc(bridge);
int ret;
@@ -1251,7 +1447,7 @@ static void tc_bridge_enable(struct drm_bridge *bridge)
return;
}
- ret = tc_stream_enable(tc);
+ ret = tc_edp_stream_enable(tc);
if (ret < 0) {
dev_err(tc->dev, "main link stream start error: %d\n", ret);
tc_main_link_disable(tc);
@@ -1259,12 +1455,14 @@ static void tc_bridge_enable(struct drm_bridge *bridge)
}
}
-static void tc_bridge_disable(struct drm_bridge *bridge)
+static void
+tc_edp_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct tc_data *tc = bridge_to_tc(bridge);
int ret;
- ret = tc_stream_disable(tc);
+ ret = tc_edp_stream_disable(tc);
if (ret < 0)
dev_err(tc->dev, "main link stream stop error: %d\n", ret);
@@ -1285,9 +1483,57 @@ static bool tc_bridge_mode_fixup(struct drm_bridge *bridge,
return true;
}
-static enum drm_mode_status tc_mode_valid(struct drm_bridge *bridge,
- const struct drm_display_info *info,
- const struct drm_display_mode *mode)
+static int tc_common_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ const unsigned int max_khz)
+{
+ tc_bridge_mode_fixup(bridge, &crtc_state->mode,
+ &crtc_state->adjusted_mode);
+
+ if (crtc_state->adjusted_mode.clock > max_khz)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int tc_dpi_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ /* DSI->DPI interface clock limitation: upto 100 MHz */
+ return tc_common_atomic_check(bridge, bridge_state, crtc_state,
+ conn_state, 100000);
+}
+
+static int tc_edp_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ /* DPI->(e)DP interface clock limitation: upto 154 MHz */
+ return tc_common_atomic_check(bridge, bridge_state, crtc_state,
+ conn_state, 154000);
+}
+
+static enum drm_mode_status
+tc_dpi_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ /* DPI interface clock limitation: upto 100 MHz */
+ if (mode->clock > 100000)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static enum drm_mode_status
+tc_edp_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
{
struct tc_data *tc = bridge_to_tc(bridge);
u32 req, avail;
@@ -1312,7 +1558,7 @@ static void tc_bridge_mode_set(struct drm_bridge *bridge,
{
struct tc_data *tc = bridge_to_tc(bridge);
- tc->mode = *mode;
+ drm_mode_copy(&tc->mode, mode);
}
static struct edid *tc_get_edid(struct drm_bridge *bridge,
@@ -1395,8 +1641,20 @@ static const struct drm_connector_funcs tc_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static int tc_bridge_attach(struct drm_bridge *bridge,
- enum drm_bridge_attach_flags flags)
+static int tc_dpi_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct tc_data *tc = bridge_to_tc(bridge);
+
+ if (!tc->panel_bridge)
+ return 0;
+
+ return drm_bridge_attach(tc->bridge.encoder, tc->panel_bridge,
+ &tc->bridge, flags);
+}
+
+static int tc_edp_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
{
u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
struct tc_data *tc = bridge_to_tc(bridge);
@@ -1448,21 +1706,64 @@ aux_unregister:
return ret;
}
-static void tc_bridge_detach(struct drm_bridge *bridge)
+static void tc_edp_bridge_detach(struct drm_bridge *bridge)
{
drm_dp_aux_unregister(&bridge_to_tc(bridge)->aux);
}
-static const struct drm_bridge_funcs tc_bridge_funcs = {
- .attach = tc_bridge_attach,
- .detach = tc_bridge_detach,
- .mode_valid = tc_mode_valid,
+#define MAX_INPUT_SEL_FORMATS 1
+
+static u32 *
+tc_dpi_atomic_get_input_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;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ /* This is the DSI-end bus format */
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+ *num_input_fmts = 1;
+
+ return input_fmts;
+}
+
+static const struct drm_bridge_funcs tc_dpi_bridge_funcs = {
+ .attach = tc_dpi_bridge_attach,
+ .mode_valid = tc_dpi_mode_valid,
.mode_set = tc_bridge_mode_set,
- .enable = tc_bridge_enable,
- .disable = tc_bridge_disable,
+ .atomic_check = tc_dpi_atomic_check,
+ .atomic_enable = tc_dpi_bridge_atomic_enable,
+ .atomic_disable = tc_dpi_bridge_atomic_disable,
+ .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,
+ .atomic_get_input_bus_fmts = tc_dpi_atomic_get_input_bus_fmts,
+};
+
+static const struct drm_bridge_funcs tc_edp_bridge_funcs = {
+ .attach = tc_edp_bridge_attach,
+ .detach = tc_edp_bridge_detach,
+ .mode_valid = tc_edp_mode_valid,
+ .mode_set = tc_bridge_mode_set,
+ .atomic_check = tc_edp_atomic_check,
+ .atomic_enable = tc_edp_bridge_atomic_enable,
+ .atomic_disable = tc_edp_bridge_atomic_disable,
.mode_fixup = tc_bridge_mode_fixup,
.detect = tc_bridge_detect,
.get_edid = tc_get_edid,
+ .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,
};
static bool tc_readable_reg(struct device *dev, unsigned int reg)
@@ -1549,18 +1850,87 @@ static irqreturn_t tc_irq_handler(int irq, void *arg)
return IRQ_HANDLED;
}
-static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
+static int tc_mipi_dsi_host_attach(struct tc_data *tc)
{
- struct device *dev = &client->dev;
+ struct device *dev = tc->dev;
+ struct device_node *host_node;
+ struct device_node *endpoint;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ const struct mipi_dsi_device_info info = {
+ .type = "tc358767",
+ .channel = 0,
+ .node = NULL,
+ };
+ int dsi_lanes, ret;
+
+ endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
+ dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+ host_node = of_graph_get_remote_port_parent(endpoint);
+ host = of_find_mipi_dsi_host_by_node(host_node);
+ of_node_put(host_node);
+ of_node_put(endpoint);
+
+ if (dsi_lanes < 0 || dsi_lanes > 4)
+ return -EINVAL;
+
+ if (!host)
+ return -EPROBE_DEFER;
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi))
+ return dev_err_probe(dev, PTR_ERR(dsi),
+ "failed to create dsi device\n");
+
+ tc->dsi = dsi;
+
+ tc->dsi_lanes = dsi_lanes;
+ dsi->lanes = tc->dsi_lanes;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "failed to attach dsi to host: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tc_probe_dpi_bridge_endpoint(struct tc_data *tc)
+{
+ struct device *dev = tc->dev;
struct drm_panel *panel;
- struct tc_data *tc;
int ret;
- tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL);
- if (!tc)
- return -ENOMEM;
+ /* port@1 is the DPI input/output port */
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
+ if (ret && ret != -ENODEV)
+ return ret;
- tc->dev = dev;
+ if (panel) {
+ struct drm_bridge *panel_bridge;
+
+ panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ if (IS_ERR(panel_bridge))
+ return PTR_ERR(panel_bridge);
+
+ tc->panel_bridge = panel_bridge;
+ tc->bridge.type = DRM_MODE_CONNECTOR_DPI;
+ tc->bridge.funcs = &tc_dpi_bridge_funcs;
+
+ return 0;
+ }
+
+ return ret;
+}
+
+static int tc_probe_edp_bridge_endpoint(struct tc_data *tc)
+{
+ struct device *dev = tc->dev;
+ struct drm_panel *panel;
+ int ret;
/* port@2 is the output port */
ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, NULL);
@@ -1580,6 +1950,76 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
tc->bridge.type = DRM_MODE_CONNECTOR_DisplayPort;
}
+ tc->bridge.funcs = &tc_edp_bridge_funcs;
+ if (tc->hpd_pin >= 0)
+ tc->bridge.ops |= DRM_BRIDGE_OP_DETECT;
+ tc->bridge.ops |= DRM_BRIDGE_OP_EDID;
+
+ return 0;
+}
+
+static int tc_probe_bridge_endpoint(struct tc_data *tc)
+{
+ struct device *dev = tc->dev;
+ struct of_endpoint endpoint;
+ struct device_node *node = NULL;
+ const u8 mode_dpi_to_edp = BIT(1) | BIT(2);
+ const u8 mode_dpi_to_dp = BIT(1);
+ const u8 mode_dsi_to_edp = BIT(0) | BIT(2);
+ const u8 mode_dsi_to_dp = BIT(0);
+ const u8 mode_dsi_to_dpi = BIT(0) | BIT(1);
+ u8 mode = 0;
+
+ /*
+ * Determine bridge configuration.
+ *
+ * Port allocation:
+ * port@0 - DSI input
+ * port@1 - DPI input/output
+ * port@2 - eDP output
+ *
+ * Possible connections:
+ * DPI -> port@1 -> port@2 -> eDP :: [port@0 is not connected]
+ * DSI -> port@0 -> port@2 -> eDP :: [port@1 is not connected]
+ * DSI -> port@0 -> port@1 -> DPI :: [port@2 is not connected]
+ */
+
+ for_each_endpoint_of_node(dev->of_node, node) {
+ of_graph_parse_endpoint(node, &endpoint);
+ if (endpoint.port > 2)
+ return -EINVAL;
+
+ mode |= BIT(endpoint.port);
+ }
+
+ if (mode == mode_dpi_to_edp || mode == mode_dpi_to_dp)
+ return tc_probe_edp_bridge_endpoint(tc);
+ else if (mode == mode_dsi_to_dpi)
+ return tc_probe_dpi_bridge_endpoint(tc);
+ else if (mode == mode_dsi_to_edp || mode == mode_dsi_to_dp)
+ dev_warn(dev, "The mode DSI-to-(e)DP is not supported!\n");
+ else
+ dev_warn(dev, "Invalid mode (0x%x) is not supported!\n", mode);
+
+ return -EINVAL;
+}
+
+static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct tc_data *tc;
+ int ret;
+
+ tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL);
+ if (!tc)
+ return -ENOMEM;
+
+ tc->dev = dev;
+
+ ret = tc_probe_bridge_endpoint(tc);
+ if (ret)
+ return ret;
+
/* Shut down GPIO is optional */
tc->sd_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH);
if (IS_ERR(tc->sd_gpio))
@@ -1686,26 +2126,25 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
}
}
- ret = tc_aux_link_setup(tc);
- if (ret)
- return ret;
-
- /* Register DP AUX channel */
- tc->aux.name = "TC358767 AUX i2c adapter";
- tc->aux.dev = tc->dev;
- tc->aux.transfer = tc_aux_transfer;
- drm_dp_aux_init(&tc->aux);
-
- tc->bridge.funcs = &tc_bridge_funcs;
- if (tc->hpd_pin >= 0)
- tc->bridge.ops |= DRM_BRIDGE_OP_DETECT;
- tc->bridge.ops |= DRM_BRIDGE_OP_EDID;
+ if (tc->bridge.type != DRM_MODE_CONNECTOR_DPI) { /* (e)DP output */
+ ret = tc_aux_link_setup(tc);
+ if (ret)
+ return ret;
+ }
tc->bridge.of_node = dev->of_node;
drm_bridge_add(&tc->bridge);
i2c_set_clientdata(client, tc);
+ if (tc->bridge.type == DRM_MODE_CONNECTOR_DPI) { /* DPI output */
+ ret = tc_mipi_dsi_host_attach(tc);
+ if (ret) {
+ drm_bridge_remove(&tc->bridge);
+ return ret;
+ }
+ }
+
return 0;
}
diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c
index 695af3badcc7..62a7ef352daa 100644
--- a/drivers/gpu/drm/bridge/tc358775.c
+++ b/drivers/gpu/drm/bridge/tc358775.c
@@ -19,10 +19,10 @@
#include <asm/unaligned.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_crtc_helper.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
@@ -649,7 +649,6 @@ static int tc_attach_host(struct tc_data *tc)
static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
- struct drm_panel *panel;
struct tc_data *tc;
int ret;
@@ -660,14 +659,8 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
tc->dev = dev;
tc->i2c = client;
- ret = drm_of_find_panel_or_bridge(dev->of_node, TC358775_LVDS_OUT0,
- 0, &panel, NULL);
- if (ret < 0)
- return ret;
- if (!panel)
- return -ENODEV;
-
- tc->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
+ tc->panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node,
+ TC358775_LVDS_OUT0, 0);
if (IS_ERR(tc->panel_bridge))
return PTR_ERR(tc->panel_bridge);
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
index 19daaddd29a4..2831f0813c3a 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -488,6 +488,11 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
/* Clear all errors that got asserted during initialization. */
regmap_read(ctx->regmap, REG_IRQ_STAT, &pval);
regmap_write(ctx->regmap, REG_IRQ_STAT, pval);
+
+ usleep_range(10000, 12000);
+ regmap_read(ctx->regmap, REG_IRQ_STAT, &pval);
+ if (pval)
+ dev_err(ctx->dev, "Unexpected link status 0x%02x\n", pval);
}
static void sn65dsi83_atomic_disable(struct drm_bridge *bridge,
@@ -565,7 +570,6 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
struct drm_bridge *panel_bridge;
struct device *dev = ctx->dev;
struct device_node *endpoint;
- struct drm_panel *panel;
int ret;
endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
@@ -605,15 +609,10 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
}
}
- ret = drm_of_find_panel_or_bridge(dev->of_node, 2, 0, &panel, &panel_bridge);
- if (ret < 0)
+ panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0);
+ if (IS_ERR(panel_bridge)) {
+ ret = PTR_ERR(panel_bridge);
goto err_put_node;
- if (panel) {
- panel_bridge = devm_drm_panel_bridge_add(dev, panel);
- if (IS_ERR(panel_bridge)) {
- ret = PTR_ERR(panel_bridge);
- goto err_put_node;
- }
}
ctx->panel_bridge = panel_bridge;
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index fb6c588b0f71..8cad662de9bb 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -23,12 +23,12 @@
#include <asm/unaligned.h>
+#include <drm/display/drm_dp_aux_bus.h>
+#include <drm/display/drm_dp_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
-#include <drm/dp/drm_dp_aux_bus.h>
-#include <drm/dp/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
@@ -1188,15 +1188,9 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
{
struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
struct device_node *np = pdata->dev->of_node;
- struct drm_panel *panel;
int ret;
- ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
- if (ret)
- return dev_err_probe(&adev->dev, ret,
- "could not find any panel node\n");
-
- pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev, panel);
+ pdata->next_bridge = devm_drm_of_get_bridge(pdata->dev, np, 1, 0);
if (IS_ERR(pdata->next_bridge)) {
DRM_ERROR("failed to create panel bridge\n");
return PTR_ERR(pdata->next_bridge);
diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c
index ba3fa2a9b8a4..756b3e6e776b 100644
--- a/drivers/gpu/drm/bridge/ti-tfp410.c
+++ b/drivers/gpu/drm/bridge/ti-tfp410.c
@@ -341,13 +341,11 @@ static int tfp410_init(struct device *dev, bool i2c)
return 0;
}
-static int tfp410_fini(struct device *dev)
+static void tfp410_fini(struct device *dev)
{
struct tfp410 *dvi = dev_get_drvdata(dev);
drm_bridge_remove(&dvi->bridge);
-
- return 0;
}
static int tfp410_probe(struct platform_device *pdev)
@@ -357,7 +355,9 @@ static int tfp410_probe(struct platform_device *pdev)
static int tfp410_remove(struct platform_device *pdev)
{
- return tfp410_fini(&pdev->dev);
+ tfp410_fini(&pdev->dev);
+
+ return 0;
}
static const struct of_device_id tfp410_match[] = {
@@ -394,7 +394,9 @@ static int tfp410_i2c_probe(struct i2c_client *client,
static int tfp410_i2c_remove(struct i2c_client *client)
{
- return tfp410_fini(&client->dev);
+ tfp410_fini(&client->dev);
+
+ return 0;
}
static const struct i2c_device_id tfp410_i2c_ids[] = {