aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/bridge
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/gpu/drm/bridge/Kconfig1
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511.h1
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_drv.c15
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7533.c20
-rw-r--r--drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c14
-rw-r--r--drivers/gpu/drm/bridge/analogix/anx7625.c495
-rw-r--r--drivers/gpu/drm/bridge/analogix/anx7625.h23
-rw-r--r--drivers/gpu/drm/bridge/display-connector.c88
-rw-r--r--drivers/gpu/drm/bridge/lontium-lt8912b.c31
-rw-r--r--drivers/gpu/drm/bridge/lontium-lt9611.c66
-rw-r--r--drivers/gpu/drm/bridge/lontium-lt9611uxc.c65
-rw-r--r--drivers/gpu/drm/bridge/lvds-codec.c36
-rw-r--r--drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c40
-rw-r--r--drivers/gpu/drm/bridge/parade-ps8640.c347
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c10
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h4
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c9
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.c13
-rw-r--r--drivers/gpu/drm/bridge/tc358768.c94
-rw-r--r--drivers/gpu/drm/bridge/tc358775.c50
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi83.c128
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi86.c475
22 files changed, 1480 insertions, 545 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 431b6e12a81f..61db5a66b493 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -182,6 +182,7 @@ config DRM_PARADE_PS8622
config DRM_PARADE_PS8640
tristate "Parade PS8640 MIPI DSI to eDP Converter"
depends on OF
+ select DRM_DP_AUX_BUS
select DRM_KMS_HELPER
select DRM_MIPI_DSI
select DRM_PANEL
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h
index 05e3abb5a0c9..592ecfcf00ca 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511.h
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h
@@ -401,7 +401,6 @@ void adv7533_mode_set(struct adv7511 *adv, const struct drm_display_mode *mode);
int adv7533_patch_registers(struct adv7511 *adv);
int adv7533_patch_cec_registers(struct adv7511 *adv);
int adv7533_attach_dsi(struct adv7511 *adv);
-void adv7533_detach_dsi(struct adv7511 *adv);
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
index 76555ae64e9c..f8e5da148599 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
@@ -910,9 +910,6 @@ static int adv7511_bridge_attach(struct drm_bridge *bridge,
return ret;
}
- if (adv->type == ADV7533 || adv->type == ADV7535)
- ret = adv7533_attach_dsi(adv);
-
if (adv->i2c_main->irq)
regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0),
ADV7511_INT0_HPD);
@@ -1288,8 +1285,18 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
drm_bridge_add(&adv7511->bridge);
adv7511_audio_init(dev, adv7511);
+
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535) {
+ ret = adv7533_attach_dsi(adv7511);
+ if (ret)
+ goto err_unregister_audio;
+ }
+
return 0;
+err_unregister_audio:
+ adv7511_audio_exit(adv7511);
+ drm_bridge_remove(&adv7511->bridge);
err_unregister_cec:
i2c_unregister_device(adv7511->i2c_cec);
clk_disable_unprepare(adv7511->cec_clk);
@@ -1307,8 +1314,6 @@ static int adv7511_remove(struct i2c_client *i2c)
{
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
- if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
- adv7533_detach_dsi(adv7511);
i2c_unregister_device(adv7511->i2c_cec);
clk_disable_unprepare(adv7511->cec_clk);
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c
index 59d718bde8c4..eb7579dec40a 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7533.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c
@@ -153,11 +153,10 @@ int adv7533_attach_dsi(struct adv7511 *adv)
return -EPROBE_DEFER;
}
- dsi = mipi_dsi_device_register_full(host, &info);
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) {
dev_err(dev, "failed to create dsi device\n");
- ret = PTR_ERR(dsi);
- goto err_dsi_device;
+ return PTR_ERR(dsi);
}
adv->dsi = dsi;
@@ -167,24 +166,13 @@ int adv7533_attach_dsi(struct adv7511 *adv)
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
- ret = mipi_dsi_attach(dsi);
+ ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n");
- goto err_dsi_attach;
+ return ret;
}
return 0;
-
-err_dsi_attach:
- mipi_dsi_device_unregister(dsi);
-err_dsi_device:
- return ret;
-}
-
-void adv7533_detach_dsi(struct adv7511 *adv)
-{
- mipi_dsi_detach(adv->dsi);
- mipi_dsi_device_unregister(adv->dsi);
}
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c
index cab6c8b92efd..6a4f20fccf84 100644
--- a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c
+++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c
@@ -998,11 +998,21 @@ int analogix_dp_send_psr_spd(struct analogix_dp_device *dp,
if (!blocking)
return 0;
+ /*
+ * db[1]!=0: entering PSR, wait for fully active remote frame buffer.
+ * db[1]==0: exiting PSR, wait for either
+ * (a) ACTIVE_RESYNC - the sink "must display the
+ * incoming active frames from the Source device with no visible
+ * glitches and/or artifacts", even though timings may still be
+ * re-synchronizing; or
+ * (b) INACTIVE - the transition is fully complete.
+ */
ret = readx_poll_timeout(analogix_dp_get_psr_status, dp, psr_status,
psr_status >= 0 &&
((vsc->db[1] && psr_status == DP_PSR_SINK_ACTIVE_RFB) ||
- (!vsc->db[1] && psr_status == DP_PSR_SINK_INACTIVE)), 1500,
- DP_TIMEOUT_PSR_LOOP_MS * 1000);
+ (!vsc->db[1] && (psr_status == DP_PSR_SINK_ACTIVE_RESYNC ||
+ psr_status == DP_PSR_SINK_INACTIVE))),
+ 1500, DP_TIMEOUT_PSR_LOOP_MS * 1000);
if (ret) {
dev_warn(dp->dev, "Failed to apply PSR %d\n", ret);
return ret;
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c
index 1a871f6b6822..2346dbcc505f 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.c
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.c
@@ -32,6 +32,8 @@
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
+#include <media/v4l2-fwnode.h>
+#include <sound/hdmi-codec.h>
#include <video/display_timing.h>
#include "anx7625.h"
@@ -166,6 +168,20 @@ static int anx7625_write_and_or(struct anx7625_data *ctx,
offset, (val & and_mask) | (or_mask));
}
+static int anx7625_config_bit_matrix(struct anx7625_data *ctx)
+{
+ int i, ret;
+
+ ret = anx7625_write_or(ctx, ctx->i2c.tx_p2_client,
+ AUDIO_CONTROL_REGISTER, 0x80);
+ for (i = 0; i < 13; i++)
+ ret |= anx7625_reg_write(ctx, ctx->i2c.tx_p2_client,
+ VIDEO_BIT_MATRIX_12 + i,
+ 0x18 + i);
+
+ return ret;
+}
+
static int anx7625_read_ctrl_status_p0(struct anx7625_data *ctx)
{
return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, AP_AUX_CTRL_STATUS);
@@ -191,10 +207,10 @@ static int wait_aux_op_finish(struct anx7625_data *ctx)
AP_AUX_CTRL_STATUS);
if (val < 0 || (val & 0x0F)) {
DRM_DEV_ERROR(dev, "aux status %02x\n", val);
- val = -EIO;
+ return -EIO;
}
- return val;
+ return 0;
}
static int anx7625_video_mute_control(struct anx7625_data *ctx,
@@ -221,38 +237,6 @@ static int anx7625_video_mute_control(struct anx7625_data *ctx,
return ret;
}
-static int anx7625_config_audio_input(struct anx7625_data *ctx)
-{
- struct device *dev = &ctx->client->dev;
- int ret;
-
- /* Channel num */
- ret = anx7625_reg_write(ctx, ctx->i2c.tx_p2_client,
- AUDIO_CHANNEL_STATUS_6, I2S_CH_2 << 5);
-
- /* FS */
- ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
- AUDIO_CHANNEL_STATUS_4,
- 0xf0, AUDIO_FS_48K);
- /* Word length */
- ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
- AUDIO_CHANNEL_STATUS_5,
- 0xf0, AUDIO_W_LEN_24_24MAX);
- /* I2S */
- ret |= anx7625_write_or(ctx, ctx->i2c.tx_p2_client,
- AUDIO_CHANNEL_STATUS_6, I2S_SLAVE_MODE);
- ret |= anx7625_write_and(ctx, ctx->i2c.tx_p2_client,
- AUDIO_CONTROL_REGISTER, ~TDM_TIMING_MODE);
- /* Audio change flag */
- ret |= anx7625_write_or(ctx, ctx->i2c.rx_p0_client,
- AP_AV_STATUS, AP_AUDIO_CHG);
-
- if (ret < 0)
- DRM_DEV_ERROR(dev, "fail to config audio.\n");
-
- return ret;
-}
-
/* Reduction of fraction a/b */
static void anx7625_reduction_of_a_fraction(unsigned long *a, unsigned long *b)
{
@@ -431,7 +415,7 @@ static int anx7625_dsi_video_timing_config(struct anx7625_data *ctx)
ret |= anx7625_write_and(ctx, ctx->i2c.rx_p1_client,
MIPI_LANE_CTRL_0, 0xfc);
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client,
- MIPI_LANE_CTRL_0, 3);
+ MIPI_LANE_CTRL_0, ctx->pdata.mipi_lanes - 1);
/* Htotal */
htotal = ctx->dt.hactive.min + ctx->dt.hfront_porch.min +
@@ -615,6 +599,76 @@ static int anx7625_dsi_config(struct anx7625_data *ctx)
return ret;
}
+static int anx7625_api_dpi_config(struct anx7625_data *ctx)
+{
+ struct device *dev = &ctx->client->dev;
+ u16 freq = ctx->dt.pixelclock.min / 1000;
+ int ret;
+
+ /* configure pixel clock */
+ ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client,
+ PIXEL_CLOCK_L, freq & 0xFF);
+ ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client,
+ PIXEL_CLOCK_H, (freq >> 8));
+
+ /* set DPI mode */
+ /* set to DPI PLL module sel */
+ ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p1_client,
+ MIPI_DIGITAL_PLL_9, 0x20);
+ /* power down MIPI */
+ ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p1_client,
+ MIPI_LANE_CTRL_10, 0x08);
+ /* enable DPI mode */
+ ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p1_client,
+ MIPI_DIGITAL_PLL_18, 0x1C);
+ /* set first edge */
+ ret |= anx7625_reg_write(ctx, ctx->i2c.tx_p2_client,
+ VIDEO_CONTROL_0, 0x06);
+ if (ret < 0)
+ DRM_DEV_ERROR(dev, "IO error : dpi phy set failed.\n");
+
+ return ret;
+}
+
+static int anx7625_dpi_config(struct anx7625_data *ctx)
+{
+ struct device *dev = &ctx->client->dev;
+ int ret;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "config dpi\n");
+
+ /* DSC disable */
+ ret = anx7625_write_and(ctx, ctx->i2c.rx_p0_client,
+ R_DSC_CTRL_0, ~DSC_EN);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "IO error : disable dsc failed.\n");
+ return ret;
+ }
+
+ ret = anx7625_config_bit_matrix(ctx);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "config bit matrix failed.\n");
+ return ret;
+ }
+
+ ret = anx7625_api_dpi_config(ctx);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "mipi phy(dpi) setup failed.\n");
+ return ret;
+ }
+
+ /* set MIPI RX EN */
+ ret = anx7625_write_or(ctx, ctx->i2c.rx_p0_client,
+ AP_AV_STATUS, AP_MIPI_RX_EN);
+ /* clear mute flag */
+ ret |= anx7625_write_and(ctx, ctx->i2c.rx_p0_client,
+ AP_AV_STATUS, (u8)~AP_MIPI_MUTE);
+ if (ret < 0)
+ DRM_DEV_ERROR(dev, "IO error : enable mipi rx failed.\n");
+
+ return ret;
+}
+
static void anx7625_dp_start(struct anx7625_data *ctx)
{
int ret;
@@ -625,9 +679,10 @@ static void anx7625_dp_start(struct anx7625_data *ctx)
return;
}
- anx7625_config_audio_input(ctx);
-
- ret = anx7625_dsi_config(ctx);
+ if (ctx->pdata.is_dpi)
+ ret = anx7625_dpi_config(ctx);
+ else
+ ret = anx7625_dsi_config(ctx);
if (ret < 0)
DRM_DEV_ERROR(dev, "MIPI phy setup error.\n");
@@ -795,7 +850,7 @@ static int sp_tx_edid_read(struct anx7625_data *ctx,
int count, blocks_num;
u8 pblock_buf[MAX_DPCD_BUFFER_SIZE];
u8 i, j;
- u8 g_edid_break = 0;
+ int g_edid_break = 0;
int ret;
struct device *dev = &ctx->client->dev;
@@ -826,7 +881,7 @@ static int sp_tx_edid_read(struct anx7625_data *ctx,
g_edid_break = edid_read(ctx, offset,
pblock_buf);
- if (g_edid_break)
+ if (g_edid_break < 0)
break;
memcpy(&pedid_blocks_buf[offset],
@@ -1075,6 +1130,7 @@ static void anx7625_start_dp_work(struct anx7625_data *ctx)
return;
}
+ ctx->hpd_status = 1;
ctx->hpd_high_cnt++;
/* Not support HDCP */
@@ -1084,8 +1140,10 @@ static void anx7625_start_dp_work(struct anx7625_data *ctx)
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xec, 0x10);
/* Interrupt for DRM */
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xff, 0x01);
- if (ret < 0)
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "fail to setting HDCP/auth\n");
return;
+ }
ret = anx7625_reg_read(ctx, ctx->i2c.rx_p1_client, 0x86);
if (ret < 0)
@@ -1104,6 +1162,10 @@ static void anx7625_hpd_polling(struct anx7625_data *ctx)
int ret, val;
struct device *dev = &ctx->client->dev;
+ /* Interrupt mode, no need poll HPD status, just return */
+ if (ctx->pdata.intp_irq)
+ return;
+
ret = readx_poll_timeout(anx7625_read_hpd_status_p0,
ctx, val,
((val & HPD_STATUS) || (val < 0)),
@@ -1131,6 +1193,21 @@ static void anx7625_remove_edid(struct anx7625_data *ctx)
ctx->slimport_edid_p.edid_block_num = -1;
}
+static void anx7625_dp_adjust_swing(struct anx7625_data *ctx)
+{
+ int i;
+
+ 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);
+
+ 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);
+}
+
static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on)
{
struct device *dev = &ctx->client->dev;
@@ -1146,9 +1223,8 @@ static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on)
} else {
DRM_DEV_DEBUG_DRIVER(dev, " HPD high\n");
anx7625_start_dp_work(ctx);
+ anx7625_dp_adjust_swing(ctx);
}
-
- ctx->hpd_status = 1;
}
static int anx7625_hpd_change_detect(struct anx7625_data *ctx)
@@ -1225,20 +1301,75 @@ static irqreturn_t anx7625_intr_hpd_isr(int irq, void *data)
return IRQ_HANDLED;
}
+static int anx7625_get_swing_setting(struct device *dev,
+ struct anx7625_platform_data *pdata)
+{
+ int num_regs;
+
+ if (of_get_property(dev->of_node,
+ "analogix,lane0-swing", &num_regs)) {
+ if (num_regs > DP_TX_SWING_REG_CNT)
+ 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);
+ }
+
+ if (of_get_property(dev->of_node,
+ "analogix,lane1-swing", &num_regs)) {
+ if (num_regs > DP_TX_SWING_REG_CNT)
+ 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);
+ }
+
+ return 0;
+}
+
static int anx7625_parse_dt(struct device *dev,
struct anx7625_platform_data *pdata)
{
- struct device_node *np = dev->of_node;
+ 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);
+ pdata->is_dpi = 1; /* default dpi mode */
pdata->mipi_host_node = of_graph_get_remote_node(np, 0, 0);
if (!pdata->mipi_host_node) {
DRM_DEV_ERROR(dev, "fail to get internal panel.\n");
return -ENODEV;
}
- DRM_DEV_DEBUG_DRIVER(dev, "found dsi host node.\n");
+ bus_type = V4L2_FWNODE_BUS_TYPE_PARALLEL;
+ mipi_lanes = MAX_LANES_SUPPORT;
+ ep0 = of_graph_get_endpoint_by_regs(np, 0, 0);
+ if (ep0) {
+ if (of_property_read_u32(ep0, "bus-type", &bus_type))
+ bus_type = 0;
+
+ mipi_lanes = of_property_count_u32_elems(ep0, "data-lanes");
+ }
+
+ if (bus_type == V4L2_FWNODE_BUS_TYPE_PARALLEL) /* bus type is Parallel(DSI) */
+ pdata->is_dpi = 0;
+
+ pdata->mipi_lanes = mipi_lanes;
+ if (pdata->mipi_lanes > MAX_LANES_SUPPORT || pdata->mipi_lanes <= 0)
+ pdata->mipi_lanes = MAX_LANES_SUPPORT;
+
+ if (pdata->is_dpi)
+ DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DPI host node.\n");
+ else
+ DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DSI host node.\n");
+
+ 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) {
@@ -1301,9 +1432,215 @@ static enum drm_connector_status anx7625_sink_detect(struct anx7625_data *ctx)
{
struct device *dev = &ctx->client->dev;
- DRM_DEV_DEBUG_DRIVER(dev, "sink detect, return connected\n");
+ DRM_DEV_DEBUG_DRIVER(dev, "sink detect\n");
- return connector_status_connected;
+ if (ctx->pdata.panel_bridge)
+ return connector_status_connected;
+
+ return ctx->hpd_status ? connector_status_connected :
+ connector_status_disconnected;
+}
+
+static int anx7625_audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *fmt,
+ struct hdmi_codec_params *params)
+{
+ struct anx7625_data *ctx = dev_get_drvdata(dev);
+ int wl, ch, rate;
+ int ret = 0;
+
+ if (fmt->fmt != HDMI_DSP_A) {
+ DRM_DEV_ERROR(dev, "only supports DSP_A\n");
+ return -EINVAL;
+ }
+
+ DRM_DEV_DEBUG_DRIVER(dev, "setting %d Hz, %d bit, %d channels\n",
+ params->sample_rate, params->sample_width,
+ params->cea.channels);
+
+ ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
+ AUDIO_CHANNEL_STATUS_6,
+ ~I2S_SLAVE_MODE,
+ TDM_SLAVE_MODE);
+
+ /* Word length */
+ switch (params->sample_width) {
+ case 16:
+ wl = AUDIO_W_LEN_16_20MAX;
+ break;
+ case 18:
+ wl = AUDIO_W_LEN_18_20MAX;
+ break;
+ case 20:
+ wl = AUDIO_W_LEN_20_20MAX;
+ break;
+ case 24:
+ wl = AUDIO_W_LEN_24_24MAX;
+ break;
+ default:
+ DRM_DEV_DEBUG_DRIVER(dev, "wordlength: %d bit not support",
+ params->sample_width);
+ return -EINVAL;
+ }
+ ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
+ AUDIO_CHANNEL_STATUS_5,
+ 0xf0, wl);
+
+ /* Channel num */
+ switch (params->cea.channels) {
+ case 2:
+ ch = I2S_CH_2;
+ break;
+ case 4:
+ ch = TDM_CH_4;
+ break;
+ case 6:
+ ch = TDM_CH_6;
+ break;
+ case 8:
+ ch = TDM_CH_8;
+ break;
+ default:
+ DRM_DEV_DEBUG_DRIVER(dev, "channel number: %d not support",
+ params->cea.channels);
+ return -EINVAL;
+ }
+ ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
+ AUDIO_CHANNEL_STATUS_6, 0x1f, ch << 5);
+ if (ch > I2S_CH_2)
+ ret |= anx7625_write_or(ctx, ctx->i2c.tx_p2_client,
+ AUDIO_CHANNEL_STATUS_6, AUDIO_LAYOUT);
+ else
+ ret |= anx7625_write_and(ctx, ctx->i2c.tx_p2_client,
+ AUDIO_CHANNEL_STATUS_6, ~AUDIO_LAYOUT);
+
+ /* FS */
+ switch (params->sample_rate) {
+ case 32000:
+ rate = AUDIO_FS_32K;
+ break;
+ case 44100:
+ rate = AUDIO_FS_441K;
+ break;
+ case 48000:
+ rate = AUDIO_FS_48K;
+ break;
+ case 88200:
+ rate = AUDIO_FS_882K;
+ break;
+ case 96000:
+ rate = AUDIO_FS_96K;
+ break;
+ case 176400:
+ rate = AUDIO_FS_1764K;
+ break;
+ case 192000:
+ rate = AUDIO_FS_192K;
+ break;
+ default:
+ DRM_DEV_DEBUG_DRIVER(dev, "sample rate: %d not support",
+ params->sample_rate);
+ return -EINVAL;
+ }
+ ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
+ AUDIO_CHANNEL_STATUS_4,
+ 0xf0, rate);
+ ret |= anx7625_write_or(ctx, ctx->i2c.rx_p0_client,
+ AP_AV_STATUS, AP_AUDIO_CHG);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "IO error : config audio.\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void anx7625_audio_shutdown(struct device *dev, void *data)
+{
+ DRM_DEV_DEBUG_DRIVER(dev, "stop audio\n");
+}
+
+static int anx7625_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
+ struct device_node *endpoint)
+{
+ struct of_endpoint of_ep;
+ int ret;
+
+ ret = of_graph_parse_endpoint(endpoint, &of_ep);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * HDMI sound should be located at external DPI port
+ * Didn't have good way to check where is internal(DSI)
+ * or external(DPI) bridge
+ */
+ return 0;
+}
+
+static void
+anx7625_audio_update_connector_status(struct anx7625_data *ctx,
+ enum drm_connector_status status)
+{
+ if (ctx->plugged_cb && ctx->codec_dev) {
+ ctx->plugged_cb(ctx->codec_dev,
+ status == connector_status_connected);
+ }
+}
+
+static int anx7625_audio_hook_plugged_cb(struct device *dev, void *data,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ struct anx7625_data *ctx = data;
+
+ ctx->plugged_cb = fn;
+ ctx->codec_dev = codec_dev;
+ anx7625_audio_update_connector_status(ctx, anx7625_sink_detect(ctx));
+
+ return 0;
+}
+
+static const struct hdmi_codec_ops anx7625_codec_ops = {
+ .hw_params = anx7625_audio_hw_params,
+ .audio_shutdown = anx7625_audio_shutdown,
+ .get_dai_id = anx7625_hdmi_i2s_get_dai_id,
+ .hook_plugged_cb = anx7625_audio_hook_plugged_cb,
+};
+
+static void anx7625_unregister_audio(struct anx7625_data *ctx)
+{
+ struct device *dev = &ctx->client->dev;
+
+ if (ctx->audio_pdev) {
+ platform_device_unregister(ctx->audio_pdev);
+ ctx->audio_pdev = NULL;
+ }
+
+ DRM_DEV_DEBUG_DRIVER(dev, "unbound to %s", HDMI_CODEC_DRV_NAME);
+}
+
+static int anx7625_register_audio(struct device *dev, struct anx7625_data *ctx)
+{
+ struct hdmi_codec_pdata codec_data = {
+ .ops = &anx7625_codec_ops,
+ .max_i2s_channels = 8,
+ .i2s = 1,
+ .data = ctx,
+ };
+
+ 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))
+ return PTR_ERR(ctx->audio_pdev);
+
+ DRM_DEV_DEBUG_DRIVER(dev, "bound to %s", HDMI_CODEC_DRV_NAME);
+
+ return 0;
}
static int anx7625_attach_dsi(struct anx7625_data *ctx)
@@ -1316,6 +1653,7 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
.channel = 0,
.node = NULL,
};
+ int ret;
DRM_DEV_DEBUG_DRIVER(dev, "attach dsi\n");
@@ -1325,22 +1663,22 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
return -EINVAL;
}
- dsi = mipi_dsi_device_register_full(host, &info);
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) {
DRM_DEV_ERROR(dev, "fail to create dsi device.\n");
return -EINVAL;
}
- dsi->lanes = 4;
+ dsi->lanes = ctx->pdata.mipi_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;
- if (mipi_dsi_attach(dsi) < 0) {
+ ret = devm_mipi_dsi_attach(dev, dsi);
+ if (ret) {
DRM_DEV_ERROR(dev, "fail to attach dsi to host.\n");
- mipi_dsi_device_unregister(dsi);
- return -EINVAL;
+ return ret;
}
ctx->dsi = dsi;
@@ -1350,16 +1688,6 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
return 0;
}
-static void anx7625_bridge_detach(struct drm_bridge *bridge)
-{
- struct anx7625_data *ctx = bridge_to_anx7625(bridge);
-
- if (ctx->dsi) {
- mipi_dsi_detach(ctx->dsi);
- mipi_dsi_device_unregister(ctx->dsi);
- }
-}
-
static int anx7625_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
@@ -1376,12 +1704,6 @@ static int anx7625_bridge_attach(struct drm_bridge *bridge,
return -ENODEV;
}
- err = anx7625_attach_dsi(ctx);
- if (err) {
- DRM_DEV_ERROR(dev, "Fail to attach to dsi : %d\n", err);
- return err;
- }
-
if (ctx->pdata.panel_bridge) {
err = drm_bridge_attach(bridge->encoder,
ctx->pdata.panel_bridge,
@@ -1475,6 +1797,10 @@ static bool anx7625_bridge_mode_fixup(struct drm_bridge *bridge,
DRM_DEV_DEBUG_DRIVER(dev, "drm mode fixup set\n");
+ /* No need fixup for external monitor */
+ if (!ctx->pdata.panel_bridge)
+ return true;
+
hsync = mode->hsync_end - mode->hsync_start;
hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end;
@@ -1624,7 +1950,6 @@ static struct edid *anx7625_bridge_get_edid(struct drm_bridge *bridge,
static const struct drm_bridge_funcs anx7625_bridge_funcs = {
.attach = anx7625_bridge_attach,
- .detach = anx7625_bridge_detach,
.disable = anx7625_bridge_disable,
.mode_valid = anx7625_bridge_mode_valid,
.mode_set = anx7625_bridge_mode_set,
@@ -1851,14 +2176,39 @@ static int anx7625_i2c_probe(struct i2c_client *client,
platform->bridge.funcs = &anx7625_bridge_funcs;
platform->bridge.of_node = client->dev.of_node;
- platform->bridge.ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
- platform->bridge.type = DRM_MODE_CONNECTOR_eDP;
+ platform->bridge.ops = DRM_BRIDGE_OP_EDID;
+ if (!platform->pdata.panel_bridge)
+ platform->bridge.ops |= DRM_BRIDGE_OP_HPD |
+ DRM_BRIDGE_OP_DETECT;
+ platform->bridge.type = platform->pdata.panel_bridge ?
+ DRM_MODE_CONNECTOR_eDP :
+ DRM_MODE_CONNECTOR_DisplayPort;
+
drm_bridge_add(&platform->bridge);
+ if (!platform->pdata.is_dpi) {
+ ret = anx7625_attach_dsi(platform);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Fail to attach to dsi : %d\n", ret);
+ goto unregister_bridge;
+ }
+ }
+
+ if (platform->pdata.audio_en)
+ anx7625_register_audio(dev, platform);
+
DRM_DEV_DEBUG_DRIVER(dev, "probe done\n");
return 0;
+unregister_bridge:
+ drm_bridge_remove(&platform->bridge);
+
+ if (!platform->pdata.low_power_mode)
+ pm_runtime_put_sync_suspend(&client->dev);
+
+ anx7625_unregister_i2c_dummy_clients(platform);
+
free_wq:
if (platform->workqueue)
destroy_workqueue(platform->workqueue);
@@ -1883,6 +2233,9 @@ static int anx7625_i2c_remove(struct i2c_client *client)
anx7625_unregister_i2c_dummy_clients(platform);
+ if (platform->pdata.audio_en)
+ anx7625_unregister_audio(platform);
+
kfree(platform);
return 0;
}
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.h b/drivers/gpu/drm/bridge/analogix/anx7625.h
index 6dcf64c703f9..3d79b6fb13c8 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.h
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.h
@@ -111,6 +111,7 @@
#define AUDIO_CHANNEL_STATUS_6 0xd5
#define TDM_SLAVE_MODE 0x10
#define I2S_SLAVE_MODE 0x08
+#define AUDIO_LAYOUT 0x01
#define AUDIO_CONTROL_REGISTER 0xe6
#define TDM_TIMING_MODE 0x08
@@ -141,12 +142,20 @@
#define HORIZONTAL_BACK_PORCH_H 0x22 /* Bit[7:4] are reserved */
/******** END of I2C Address 0x72 *********/
+
+/***************************************************************/
+/* Register definition of device address 0x7a */
+#define DP_TX_SWING_REG_CNT 0x14
+#define DP_TX_LANE0_SWING_REG0 0x00
+#define DP_TX_LANE1_SWING_REG0 0x14
+/******** END of I2C Address 0x7a *********/
+
/***************************************************************/
/* Register definition of device address 0x7e */
#define I2C_ADDR_7E_FLASH_CONTROLLER 0x7E
-#define FLASH_LOAD_STA 0x05
+#define FLASH_LOAD_STA 0x05
#define FLASH_LOAD_STA_CHK BIT(7)
#define XTAL_FRQ_SEL 0x3F
@@ -349,12 +358,21 @@ struct s_edid_data {
/***************** Display End *****************/
+#define MAX_LANES_SUPPORT 4
+
struct anx7625_platform_data {
struct gpio_desc *gpio_p_on;
struct gpio_desc *gpio_reset;
struct regulator_bulk_data supplies[3];
struct drm_bridge *panel_bridge;
int intp_irq;
+ int is_dpi;
+ int mipi_lanes;
+ int audio_en;
+ int dp_lane0_swing_reg_cnt;
+ int lane0_reg_data[DP_TX_SWING_REG_CNT];
+ int dp_lane1_swing_reg_cnt;
+ int lane1_reg_data[DP_TX_SWING_REG_CNT];
u32 low_power_mode;
struct device_node *mipi_host_node;
};
@@ -371,6 +389,7 @@ struct anx7625_i2c_client {
struct anx7625_data {
struct anx7625_platform_data pdata;
+ struct platform_device *audio_pdev;
int hpd_status;
int hpd_high_cnt;
/* Lock for work queue */
@@ -379,6 +398,8 @@ struct anx7625_data {
struct anx7625_i2c_client i2c;
struct i2c_client *last_client;
struct s_edid_data slimport_edid_p;
+ struct device *codec_dev;
+ hdmi_codec_plugged_cb plugged_cb;
struct work_struct work;
struct workqueue_struct *workqueue;
char edid_block;
diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c
index 05eb759da6fc..d24f5b90feab 100644
--- a/drivers/gpu/drm/bridge/display-connector.c
+++ b/drivers/gpu/drm/bridge/display-connector.c
@@ -13,6 +13,7 @@
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
+#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_edid.h>
@@ -87,10 +88,95 @@ static struct edid *display_connector_get_edid(struct drm_bridge *bridge,
return drm_get_edid(connector, conn->bridge.ddc);
}
+/*
+ * Since this bridge is tied to the connector, it acts like a passthrough,
+ * so concerning the output bus formats, either pass the bus formats from the
+ * previous bridge or return fallback data like done in the bridge function:
+ * drm_atomic_bridge_chain_select_bus_fmts().
+ * This supports negotiation if the bridge chain has all bits in place.
+ */
+static u32 *display_connector_get_output_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ unsigned int *num_output_fmts)
+{
+ struct drm_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge);
+ struct drm_bridge_state *prev_bridge_state;
+
+ if (!prev_bridge || !prev_bridge->funcs->atomic_get_output_bus_fmts) {
+ struct drm_connector *conn = conn_state->connector;
+ u32 *out_bus_fmts;
+
+ *num_output_fmts = 1;
+ out_bus_fmts = kmalloc(sizeof(*out_bus_fmts), GFP_KERNEL);
+ if (!out_bus_fmts)
+ return NULL;
+
+ if (conn->display_info.num_bus_formats &&
+ conn->display_info.bus_formats)
+ out_bus_fmts[0] = conn->display_info.bus_formats[0];
+ else
+ out_bus_fmts[0] = MEDIA_BUS_FMT_FIXED;
+
+ return out_bus_fmts;
+ }
+
+ prev_bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state,
+ prev_bridge);
+
+ return prev_bridge->funcs->atomic_get_output_bus_fmts(prev_bridge, prev_bridge_state,
+ crtc_state, conn_state,
+ num_output_fmts);
+}
+
+/*
+ * Since this bridge is tied to the connector, it acts like a passthrough,
+ * so concerning the input bus formats, either pass the bus formats from the
+ * previous bridge or MEDIA_BUS_FMT_FIXED (like select_bus_fmt_recursive())
+ * when atomic_get_input_bus_fmts is not supported.
+ * This supports negotiation if the bridge chain has all bits in place.
+ */
+static u32 *display_connector_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)
+{
+ struct drm_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge);
+ struct drm_bridge_state *prev_bridge_state;
+
+ if (!prev_bridge || !prev_bridge->funcs->atomic_get_input_bus_fmts) {
+ u32 *in_bus_fmts;
+
+ *num_input_fmts = 1;
+ in_bus_fmts = kmalloc(sizeof(*in_bus_fmts), GFP_KERNEL);
+ if (!in_bus_fmts)
+ return NULL;
+
+ in_bus_fmts[0] = MEDIA_BUS_FMT_FIXED;
+
+ return in_bus_fmts;
+ }
+
+ prev_bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state,
+ prev_bridge);
+
+ return prev_bridge->funcs->atomic_get_input_bus_fmts(prev_bridge, prev_bridge_state,
+ crtc_state, conn_state, output_fmt,
+ num_input_fmts);
+}
+
static const struct drm_bridge_funcs display_connector_bridge_funcs = {
.attach = display_connector_attach,
.detect = display_connector_detect,
.get_edid = display_connector_get_edid,
+ .atomic_get_output_bus_fmts = display_connector_get_output_bus_fmts,
+ .atomic_get_input_bus_fmts = display_connector_get_input_bus_fmts,
+ .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 irqreturn_t display_connector_hpd_irq(int irq, void *arg)
@@ -107,7 +193,7 @@ static int display_connector_probe(struct platform_device *pdev)
{
struct display_connector *conn;
unsigned int type;
- const char *label;
+ const char *label = NULL;
int ret;
conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL);
diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c
index 1b0c7eaf6c84..c642d1e02b2f 100644
--- a/drivers/gpu/drm/bridge/lontium-lt8912b.c
+++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c
@@ -472,11 +472,11 @@ static int lt8912_attach_dsi(struct lt8912 *lt)
return -EPROBE_DEFER;
}
- dsi = mipi_dsi_device_register_full(host, &info);
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) {
ret = PTR_ERR(dsi);
dev_err(dev, "failed to create dsi device (%d)\n", ret);
- goto err_dsi_device;
+ return ret;
}
lt->dsi = dsi;
@@ -489,24 +489,13 @@ static int lt8912_attach_dsi(struct lt8912 *lt)
MIPI_DSI_MODE_LPM |
MIPI_DSI_MODE_NO_EOT_PACKET;
- ret = mipi_dsi_attach(dsi);
+ ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n");
- goto err_dsi_attach;
+ return ret;
}
return 0;
-
-err_dsi_attach:
- mipi_dsi_device_unregister(dsi);
-err_dsi_device:
- return ret;
-}
-
-static void lt8912_detach_dsi(struct lt8912 *lt)
-{
- mipi_dsi_detach(lt->dsi);
- mipi_dsi_device_unregister(lt->dsi);
}
static int lt8912_bridge_connector_init(struct drm_bridge *bridge)
@@ -555,10 +544,6 @@ static int lt8912_bridge_attach(struct drm_bridge *bridge,
if (ret)
goto error;
- ret = lt8912_attach_dsi(lt);
- if (ret)
- goto error;
-
lt->is_attached = true;
return 0;
@@ -573,7 +558,6 @@ static void lt8912_bridge_detach(struct drm_bridge *bridge)
struct lt8912 *lt = bridge_to_lt8912(bridge);
if (lt->is_attached) {
- lt8912_detach_dsi(lt);
lt8912_hard_power_off(lt);
drm_connector_unregister(&lt->connector);
drm_connector_cleanup(&lt->connector);
@@ -718,8 +702,15 @@ static int lt8912_probe(struct i2c_client *client,
drm_bridge_add(&lt->bridge);
+ ret = lt8912_attach_dsi(lt);
+ if (ret)
+ goto err_attach;
+
return 0;
+err_attach:
+ drm_bridge_remove(&lt->bridge);
+ lt8912_free_i2c(lt);
err_i2c:
lt8912_put_dt(lt);
err_dt_parse:
diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c
index 29b1ce2140ab..dafb1b47c15f 100644
--- a/drivers/gpu/drm/bridge/lontium-lt9611.c
+++ b/drivers/gpu/drm/bridge/lontium-lt9611.c
@@ -586,7 +586,7 @@ lt9611_connector_detect(struct drm_connector *connector, bool force)
int connected = 0;
regmap_read(lt9611->regmap, 0x825e, &reg_val);
- connected = (reg_val & BIT(2));
+ connected = (reg_val & BIT(0));
lt9611->status = connected ? connector_status_connected :
connector_status_disconnected;
@@ -760,6 +760,7 @@ static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611,
const struct mipi_dsi_device_info info = { "lt9611", 0, NULL };
struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host;
+ struct device *dev = lt9611->dev;
int ret;
host = of_find_mipi_dsi_host_by_node(dsi_node);
@@ -768,7 +769,7 @@ static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611,
return ERR_PTR(-EPROBE_DEFER);
}
- dsi = mipi_dsi_device_register_full(host, &info);
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) {
dev_err(lt9611->dev, "failed to create dsi device\n");
return dsi;
@@ -779,29 +780,15 @@ static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611,
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_VIDEO_HSE;
- ret = mipi_dsi_attach(dsi);
+ ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
- dev_err(lt9611->dev, "failed to attach dsi to host\n");
- mipi_dsi_device_unregister(dsi);
+ dev_err(dev, "failed to attach dsi to host\n");
return ERR_PTR(ret);
}
return dsi;
}
-static void lt9611_bridge_detach(struct drm_bridge *bridge)
-{
- struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
-
- if (lt9611->dsi1) {
- mipi_dsi_detach(lt9611->dsi1);
- mipi_dsi_device_unregister(lt9611->dsi1);
- }
-
- mipi_dsi_detach(lt9611->dsi0);
- mipi_dsi_device_unregister(lt9611->dsi0);
-}
-
static int lt9611_connector_init(struct drm_bridge *bridge, struct lt9611 *lt9611)
{
int ret;
@@ -838,28 +825,7 @@ static int lt9611_bridge_attach(struct drm_bridge *bridge,
return ret;
}
- /* Attach primary DSI */
- lt9611->dsi0 = lt9611_attach_dsi(lt9611, lt9611->dsi0_node);
- if (IS_ERR(lt9611->dsi0))
- return PTR_ERR(lt9611->dsi0);
-
- /* Attach secondary DSI, if specified */
- if (lt9611->dsi1_node) {
- lt9611->dsi1 = lt9611_attach_dsi(lt9611, lt9611->dsi1_node);
- if (IS_ERR(lt9611->dsi1)) {
- ret = PTR_ERR(lt9611->dsi1);
- goto err_unregister_dsi0;
- }
- }
-
return 0;
-
-err_unregister_dsi0:
- lt9611_bridge_detach(bridge);
- drm_connector_cleanup(&lt9611->connector);
- mipi_dsi_device_unregister(lt9611->dsi0);
-
- return ret;
}
static enum drm_mode_status lt9611_bridge_mode_valid(struct drm_bridge *bridge,
@@ -926,7 +892,7 @@ static enum drm_connector_status lt9611_bridge_detect(struct drm_bridge *bridge)
int connected;
regmap_read(lt9611->regmap, 0x825e, &reg_val);
- connected = reg_val & BIT(2);
+ connected = reg_val & BIT(0);
lt9611->status = connected ? connector_status_connected :
connector_status_disconnected;
@@ -952,7 +918,6 @@ static void lt9611_bridge_hpd_enable(struct drm_bridge *bridge)
static const struct drm_bridge_funcs lt9611_bridge_funcs = {
.attach = lt9611_bridge_attach,
- .detach = lt9611_bridge_detach,
.mode_valid = lt9611_bridge_mode_valid,
.enable = lt9611_bridge_enable,
.disable = lt9611_bridge_disable,
@@ -1181,10 +1146,29 @@ static int lt9611_probe(struct i2c_client *client,
drm_bridge_add(&lt9611->bridge);
+ /* Attach primary DSI */
+ lt9611->dsi0 = lt9611_attach_dsi(lt9611, lt9611->dsi0_node);
+ if (IS_ERR(lt9611->dsi0)) {
+ ret = PTR_ERR(lt9611->dsi0);
+ goto err_remove_bridge;
+ }
+
+ /* Attach secondary DSI, if specified */
+ if (lt9611->dsi1_node) {
+ lt9611->dsi1 = lt9611_attach_dsi(lt9611, lt9611->dsi1_node);
+ if (IS_ERR(lt9611->dsi1)) {
+ ret = PTR_ERR(lt9611->dsi1);
+ goto err_remove_bridge;
+ }
+ }
+
lt9611_enable_hpd_interrupts(lt9611);
return lt9611_audio_init(dev, lt9611);
+err_remove_bridge:
+ drm_bridge_remove(&lt9611->bridge);
+
err_disable_regulators:
regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies);
diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c
index 010657ea7af7..33f9716da0ee 100644
--- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c
+++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c
@@ -258,17 +258,18 @@ static struct mipi_dsi_device *lt9611uxc_attach_dsi(struct lt9611uxc *lt9611uxc,
const struct mipi_dsi_device_info info = { "lt9611uxc", 0, NULL };
struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host;
+ struct device *dev = lt9611uxc->dev;
int ret;
host = of_find_mipi_dsi_host_by_node(dsi_node);
if (!host) {
- dev_err(lt9611uxc->dev, "failed to find dsi host\n");
+ dev_err(dev, "failed to find dsi host\n");
return ERR_PTR(-EPROBE_DEFER);
}
- dsi = mipi_dsi_device_register_full(host, &info);
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) {
- dev_err(lt9611uxc->dev, "failed to create dsi device\n");
+ dev_err(dev, "failed to create dsi device\n");
return dsi;
}
@@ -277,10 +278,9 @@ static struct mipi_dsi_device *lt9611uxc_attach_dsi(struct lt9611uxc *lt9611uxc,
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_VIDEO_HSE;
- ret = mipi_dsi_attach(dsi);
+ ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
- dev_err(lt9611uxc->dev, "failed to attach dsi to host\n");
- mipi_dsi_device_unregister(dsi);
+ dev_err(dev, "failed to attach dsi to host\n");
return ERR_PTR(ret);
}
@@ -355,19 +355,6 @@ static int lt9611uxc_connector_init(struct drm_bridge *bridge, struct lt9611uxc
return drm_connector_attach_encoder(&lt9611uxc->connector, bridge->encoder);
}
-static void lt9611uxc_bridge_detach(struct drm_bridge *bridge)
-{
- struct lt9611uxc *lt9611uxc = bridge_to_lt9611uxc(bridge);
-
- if (lt9611uxc->dsi1) {
- mipi_dsi_detach(lt9611uxc->dsi1);
- mipi_dsi_device_unregister(lt9611uxc->dsi1);
- }
-
- mipi_dsi_detach(lt9611uxc->dsi0);
- mipi_dsi_device_unregister(lt9611uxc->dsi0);
-}
-
static int lt9611uxc_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
@@ -380,27 +367,7 @@ static int lt9611uxc_bridge_attach(struct drm_bridge *bridge,
return ret;
}
- /* Attach primary DSI */
- lt9611uxc->dsi0 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi0_node);
- if (IS_ERR(lt9611uxc->dsi0))
- return PTR_ERR(lt9611uxc->dsi0);
-
- /* Attach secondary DSI, if specified */
- if (lt9611uxc->dsi1_node) {
- lt9611uxc->dsi1 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi1_node);
- if (IS_ERR(lt9611uxc->dsi1)) {
- ret = PTR_ERR(lt9611uxc->dsi1);
- goto err_unregister_dsi0;
- }
- }
-
return 0;
-
-err_unregister_dsi0:
- mipi_dsi_detach(lt9611uxc->dsi0);
- mipi_dsi_device_unregister(lt9611uxc->dsi0);
-
- return ret;
}
static enum drm_mode_status
@@ -544,7 +511,6 @@ static struct edid *lt9611uxc_bridge_get_edid(struct drm_bridge *bridge,
static const struct drm_bridge_funcs lt9611uxc_bridge_funcs = {
.attach = lt9611uxc_bridge_attach,
- .detach = lt9611uxc_bridge_detach,
.mode_valid = lt9611uxc_bridge_mode_valid,
.mode_set = lt9611uxc_bridge_mode_set,
.detect = lt9611uxc_bridge_detect,
@@ -980,8 +946,27 @@ retry:
drm_bridge_add(&lt9611uxc->bridge);
+ /* Attach primary DSI */
+ lt9611uxc->dsi0 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi0_node);
+ if (IS_ERR(lt9611uxc->dsi0)) {
+ ret = PTR_ERR(lt9611uxc->dsi0);
+ goto err_remove_bridge;
+ }
+
+ /* Attach secondary DSI, if specified */
+ if (lt9611uxc->dsi1_node) {
+ lt9611uxc->dsi1 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi1_node);
+ if (IS_ERR(lt9611uxc->dsi1)) {
+ ret = PTR_ERR(lt9611uxc->dsi1);
+ goto err_remove_bridge;
+ }
+ }
+
return lt9611uxc_audio_init(dev, lt9611uxc);
+err_remove_bridge:
+ drm_bridge_remove(&lt9611uxc->bridge);
+
err_disable_regulators:
regulator_bulk_disable(ARRAY_SIZE(lt9611uxc->supplies), lt9611uxc->supplies);
diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c
index ad460b96c0a3..702ea803a743 100644
--- a/drivers/gpu/drm/bridge/lvds-codec.c
+++ b/drivers/gpu/drm/bridge/lvds-codec.c
@@ -14,12 +14,14 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
+#include <drm/drm_of.h>
#include <drm/drm_panel.h>
struct lvds_codec {
struct device *dev;
struct drm_bridge bridge;
struct drm_bridge *panel_bridge;
+ struct drm_bridge_timings timings;
struct regulator *vcc;
struct gpio_desc *powerdown_gpio;
u32 connector_type;
@@ -118,7 +120,7 @@ static int lvds_codec_probe(struct platform_device *pdev)
struct device_node *bus_node;
struct drm_panel *panel;
struct lvds_codec *lvds_codec;
- const char *mapping;
+ u32 val;
int ret;
lvds_codec = devm_kzalloc(dev, sizeof(*lvds_codec), GFP_KERNEL);
@@ -174,32 +176,38 @@ static int lvds_codec_probe(struct platform_device *pdev)
return -ENXIO;
}
- ret = of_property_read_string(bus_node, "data-mapping",
- &mapping);
+ ret = drm_of_lvds_get_data_mapping(bus_node);
of_node_put(bus_node);
- if (ret < 0) {
+ if (ret == -ENODEV) {
dev_warn(dev, "missing 'data-mapping' DT property\n");
+ } else if (ret) {
+ dev_err(dev, "invalid 'data-mapping' DT property\n");
+ return ret;
} else {
- if (!strcmp(mapping, "jeida-18")) {
- lvds_codec->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
- } else if (!strcmp(mapping, "jeida-24")) {
- lvds_codec->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
- } else if (!strcmp(mapping, "vesa-24")) {
- lvds_codec->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
- } else {
- dev_err(dev, "invalid 'data-mapping' DT property\n");
- return -EINVAL;
- }
+ lvds_codec->bus_format = ret;
lvds_codec->bridge.funcs = &funcs_decoder;
}
}
/*
+ * Encoder might sample data on different clock edge than the display,
+ * for example OnSemi FIN3385 has a dedicated strapping pin to select
+ * the sampling edge.
+ */
+ if (lvds_codec->connector_type == DRM_MODE_CONNECTOR_LVDS &&
+ !of_property_read_u32(dev->of_node, "pclk-sample", &val)) {
+ lvds_codec->timings.input_bus_flags = val ?
+ DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE :
+ DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
+ }
+
+ /*
* The panel_bridge bridge is attached to the panel's of_node,
* but we need a bridge attached to our of_node for our user
* to look up.
*/
lvds_codec->bridge.of_node = dev->of_node;
+ lvds_codec->bridge.timings = &lvds_codec->timings;
drm_bridge_add(&lvds_codec->bridge);
platform_set_drvdata(pdev, lvds_codec);
diff --git a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
index d2808c4a6fb1..cce98bf2a4e7 100644
--- a/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
+++ b/drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c
@@ -306,19 +306,10 @@ out:
mutex_unlock(&ge_b850v3_lvds_dev_mutex);
}
-static int stdp4028_ge_b850v3_fw_probe(struct i2c_client *stdp4028_i2c,
- const struct i2c_device_id *id)
+static int ge_b850v3_register(void)
{
+ struct i2c_client *stdp4028_i2c = ge_b850v3_lvds_ptr->stdp4028_i2c;
struct device *dev = &stdp4028_i2c->dev;
- int ret;
-
- ret = ge_b850v3_lvds_init(dev);
-
- if (ret)
- return ret;
-
- ge_b850v3_lvds_ptr->stdp4028_i2c = stdp4028_i2c;
- i2c_set_clientdata(stdp4028_i2c, ge_b850v3_lvds_ptr);
/* drm bridge initialization */
ge_b850v3_lvds_ptr->bridge.funcs = &ge_b850v3_lvds_funcs;
@@ -343,6 +334,27 @@ static int stdp4028_ge_b850v3_fw_probe(struct i2c_client *stdp4028_i2c,
"ge-b850v3-lvds-dp", ge_b850v3_lvds_ptr);
}
+static int stdp4028_ge_b850v3_fw_probe(struct i2c_client *stdp4028_i2c,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &stdp4028_i2c->dev;
+ int ret;
+
+ ret = ge_b850v3_lvds_init(dev);
+
+ if (ret)
+ return ret;
+
+ ge_b850v3_lvds_ptr->stdp4028_i2c = stdp4028_i2c;
+ i2c_set_clientdata(stdp4028_i2c, ge_b850v3_lvds_ptr);
+
+ /* Only register after both bridges are probed */
+ if (!ge_b850v3_lvds_ptr->stdp2690_i2c)
+ return 0;
+
+ return ge_b850v3_register();
+}
+
static int stdp4028_ge_b850v3_fw_remove(struct i2c_client *stdp4028_i2c)
{
ge_b850v3_lvds_remove();
@@ -386,7 +398,11 @@ static int stdp2690_ge_b850v3_fw_probe(struct i2c_client *stdp2690_i2c,
ge_b850v3_lvds_ptr->stdp2690_i2c = stdp2690_i2c;
i2c_set_clientdata(stdp2690_i2c, ge_b850v3_lvds_ptr);
- return 0;
+ /* Only register after both bridges are probed */
+ if (!ge_b850v3_lvds_ptr->stdp4028_i2c)
+ return 0;
+
+ return ge_b850v3_register();
}
static int stdp2690_ge_b850v3_fw_remove(struct i2c_client *stdp2690_i2c)
diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c
index 3aaa90913bf8..818704bf5e86 100644
--- a/drivers/gpu/drm/bridge/parade-ps8640.c
+++ b/drivers/gpu/drm/bridge/parade-ps8640.c
@@ -9,10 +9,12 @@
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <drm/drm_bridge.h>
+#include <drm/drm_dp_aux_bus.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
@@ -100,7 +102,7 @@ struct ps8640 {
struct regulator_bulk_data supplies[2];
struct gpio_desc *gpio_reset;
struct gpio_desc *gpio_powerdown;
- bool powered;
+ bool pre_enabled;
};
static const struct regmap_config ps8640_regmap_config[] = {
@@ -148,8 +150,46 @@ static inline struct ps8640 *aux_to_ps8640(struct drm_dp_aux *aux)
return container_of(aux, struct ps8640, aux);
}
-static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
- struct drm_dp_aux_msg *msg)
+static bool ps8640_of_panel_on_aux_bus(struct device *dev)
+{
+ struct device_node *bus, *panel;
+
+ bus = of_get_child_by_name(dev->of_node, "aux-bus");
+ if (!bus)
+ return false;
+
+ panel = of_get_child_by_name(bus, "panel");
+ of_node_put(bus);
+ if (!panel)
+ return false;
+ of_node_put(panel);
+
+ return true;
+}
+
+static int ps8640_ensure_hpd(struct ps8640 *ps_bridge)
+{
+ struct regmap *map = ps_bridge->regmap[PAGE2_TOP_CNTL];
+ struct device *dev = &ps_bridge->page[PAGE2_TOP_CNTL]->dev;
+ int status;
+ int ret;
+
+ /*
+ * Apparently something about the firmware in the chip signals that
+ * HPD goes high by reporting GPIO9 as high (even though HPD isn't
+ * actually connected to GPIO9).
+ */
+ ret = regmap_read_poll_timeout(map, PAGE2_GPIO_H, status,
+ status & PS_GPIO9, 20 * 1000, 200 * 1000);
+
+ if (ret < 0)
+ dev_warn(dev, "HPD didn't go high: %d\n", ret);
+
+ return ret;
+}
+
+static ssize_t ps8640_aux_transfer_msg(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
{
struct ps8640 *ps_bridge = aux_to_ps8640(aux);
struct regmap *map = ps_bridge->regmap[PAGE0_DP_CNTL];
@@ -274,38 +314,49 @@ static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
return len;
}
-static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge,
- const enum ps8640_vdo_control ctrl)
+static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ struct ps8640 *ps_bridge = aux_to_ps8640(aux);
+ struct device *dev = &ps_bridge->page[PAGE0_DP_CNTL]->dev;
+ int ret;
+
+ pm_runtime_get_sync(dev);
+ ret = ps8640_ensure_hpd(ps_bridge);
+ if (!ret)
+ ret = ps8640_aux_transfer_msg(aux, msg);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+static void ps8640_bridge_vdo_control(struct ps8640 *ps_bridge,
+ const enum ps8640_vdo_control ctrl)
{
struct regmap *map = ps_bridge->regmap[PAGE3_DSI_CNTL1];
+ struct device *dev = &ps_bridge->page[PAGE3_DSI_CNTL1]->dev;
u8 vdo_ctrl_buf[] = { VDO_CTL_ADD, ctrl };
int ret;
ret = regmap_bulk_write(map, PAGE3_SET_ADD,
vdo_ctrl_buf, sizeof(vdo_ctrl_buf));
- if (ret < 0) {
- DRM_ERROR("failed to %sable VDO: %d\n",
- ctrl == ENABLE ? "en" : "dis", ret);
- return ret;
- }
-
- return 0;
+ if (ret < 0)
+ dev_err(dev, "failed to %sable VDO: %d\n",
+ ctrl == ENABLE ? "en" : "dis", ret);
}
-static void ps8640_bridge_poweron(struct ps8640 *ps_bridge)
+static int __maybe_unused ps8640_resume(struct device *dev)
{
- struct regmap *map = ps_bridge->regmap[PAGE2_TOP_CNTL];
- int ret, status;
-
- if (ps_bridge->powered)
- return;
+ struct ps8640 *ps_bridge = dev_get_drvdata(dev);
+ int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
ps_bridge->supplies);
if (ret < 0) {
- DRM_ERROR("cannot enable regulators %d\n", ret);
- return;
+ dev_err(dev, "cannot enable regulators %d\n", ret);
+ return ret;
}
gpiod_set_value(ps_bridge->gpio_powerdown, 0);
@@ -314,86 +365,78 @@ static void ps8640_bridge_poweron(struct ps8640 *ps_bridge)
gpiod_set_value(ps_bridge->gpio_reset, 0);
/*
- * Wait for the ps8640 embedded MCU to be ready
- * First wait 200ms and then check the MCU ready flag every 20ms
+ * Mystery 200 ms delay for the "MCU to be ready". It's unclear if
+ * this is truly necessary since the MCU will already signal that
+ * things are "good to go" by signaling HPD on "gpio 9". See
+ * ps8640_ensure_hpd(). For now we'll keep this mystery delay just in
+ * case.
*/
msleep(200);
- ret = regmap_read_poll_timeout(map, PAGE2_GPIO_H, status,
- status & PS_GPIO9, 20 * 1000, 200 * 1000);
-
- if (ret < 0) {
- DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", ret);
- goto err_regulators_disable;
- }
-
- msleep(50);
-
- /*
- * The Manufacturer Command Set (MCS) is a device dependent interface
- * intended for factory programming of the display module default
- * parameters. Once the display module is configured, the MCS shall be
- * disabled by the manufacturer. Once disabled, all MCS commands are
- * ignored by the display interface.
- */
-
- ret = regmap_update_bits(map, PAGE2_MCS_EN, MCS_EN, 0);
- if (ret < 0) {
- DRM_ERROR("failed write PAGE2_MCS_EN: %d\n", ret);
- goto err_regulators_disable;
- }
-
- /* Switch access edp panel's edid through i2c */
- ret = regmap_write(map, PAGE2_I2C_BYPASS, I2C_BYPASS_EN);
- if (ret < 0) {
- DRM_ERROR("failed write PAGE2_I2C_BYPASS: %d\n", ret);
- goto err_regulators_disable;
- }
-
- ps_bridge->powered = true;
-
- return;
-
-err_regulators_disable:
- regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
- ps_bridge->supplies);
+ return 0;
}
-static void ps8640_bridge_poweroff(struct ps8640 *ps_bridge)
+static int __maybe_unused ps8640_suspend(struct device *dev)
{
+ struct ps8640 *ps_bridge = dev_get_drvdata(dev);
int ret;
- if (!ps_bridge->powered)
- return;
-
gpiod_set_value(ps_bridge->gpio_reset, 1);
gpiod_set_value(ps_bridge->gpio_powerdown, 1);
ret = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
ps_bridge->supplies);
if (ret < 0)
- DRM_ERROR("cannot disable regulators %d\n", ret);
+ dev_err(dev, "cannot disable regulators %d\n", ret);
- ps_bridge->powered = false;
+ return ret;
}
+static const struct dev_pm_ops ps8640_pm_ops = {
+ SET_RUNTIME_PM_OPS(ps8640_suspend, ps8640_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
static void ps8640_pre_enable(struct drm_bridge *bridge)
{
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ struct regmap *map = ps_bridge->regmap[PAGE2_TOP_CNTL];
+ struct device *dev = &ps_bridge->page[PAGE0_DP_CNTL]->dev;
int ret;
- ps8640_bridge_poweron(ps_bridge);
+ pm_runtime_get_sync(dev);
+ ps8640_ensure_hpd(ps_bridge);
- ret = ps8640_bridge_vdo_control(ps_bridge, ENABLE);
+ /*
+ * The Manufacturer Command Set (MCS) is a device dependent interface
+ * intended for factory programming of the display module default
+ * parameters. Once the display module is configured, the MCS shall be
+ * disabled by the manufacturer. Once disabled, all MCS commands are
+ * ignored by the display interface.
+ */
+
+ ret = regmap_update_bits(map, PAGE2_MCS_EN, MCS_EN, 0);
if (ret < 0)
- ps8640_bridge_poweroff(ps_bridge);
+ dev_warn(dev, "failed write PAGE2_MCS_EN: %d\n", ret);
+
+ /* Switch access edp panel's edid through i2c */
+ ret = regmap_write(map, PAGE2_I2C_BYPASS, I2C_BYPASS_EN);
+ if (ret < 0)
+ dev_warn(dev, "failed write PAGE2_MCS_EN: %d\n", ret);
+
+ ps8640_bridge_vdo_control(ps_bridge, ENABLE);
+
+ ps_bridge->pre_enabled = true;
}
static void ps8640_post_disable(struct drm_bridge *bridge)
{
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
+ ps_bridge->pre_enabled = false;
+
ps8640_bridge_vdo_control(ps_bridge, DISABLE);
- ps8640_bridge_poweroff(ps_bridge);
+ pm_runtime_put_sync_suspend(&ps_bridge->page[PAGE0_DP_CNTL]->dev);
}
static int ps8640_bridge_attach(struct drm_bridge *bridge,
@@ -401,68 +444,21 @@ static int ps8640_bridge_attach(struct drm_bridge *bridge,
{
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
struct device *dev = &ps_bridge->page[0]->dev;
- struct device_node *in_ep, *dsi_node;
- struct mipi_dsi_device *dsi;
- struct mipi_dsi_host *host;
int ret;
- const struct mipi_dsi_device_info info = { .type = "ps8640",
- .channel = 0,
- .node = NULL,
- };
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL;
- /* port@0 is ps8640 dsi input port */
- in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
- if (!in_ep)
- return -ENODEV;
-
- dsi_node = of_graph_get_remote_port_parent(in_ep);
- of_node_put(in_ep);
- if (!dsi_node)
- return -ENODEV;
-
- host = of_find_mipi_dsi_host_by_node(dsi_node);
- of_node_put(dsi_node);
- if (!host)
- return -ENODEV;
-
- dsi = mipi_dsi_device_register_full(host, &info);
- if (IS_ERR(dsi)) {
- dev_err(dev, "failed to create dsi device\n");
- ret = PTR_ERR(dsi);
- return ret;
- }
-
- ps_bridge->dsi = dsi;
-
- dsi->host = host;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
- MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
- dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->lanes = NUM_MIPI_LANES;
- ret = mipi_dsi_attach(dsi);
- if (ret) {
- dev_err(dev, "failed to attach dsi device: %d\n", ret);
- goto err_dsi_attach;
- }
-
+ ps_bridge->aux.drm_dev = bridge->dev;
ret = drm_dp_aux_register(&ps_bridge->aux);
if (ret) {
dev_err(dev, "failed to register DP AUX channel: %d\n", ret);
- goto err_aux_register;
+ return ret;
}
/* Attach the panel-bridge to the dsi bridge */
return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge,
&ps_bridge->bridge, flags);
-
-err_aux_register:
- mipi_dsi_detach(dsi);
-err_dsi_attach:
- mipi_dsi_device_unregister(dsi);
- return ret;
}
static void ps8640_bridge_detach(struct drm_bridge *bridge)
@@ -474,7 +470,7 @@ static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
- bool poweroff = !ps_bridge->powered;
+ bool poweroff = !ps_bridge->pre_enabled;
struct edid *edid;
/*
@@ -504,6 +500,12 @@ static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,
return edid;
}
+static void ps8640_runtime_disable(void *data)
+{
+ pm_runtime_dont_use_autosuspend(data);
+ pm_runtime_disable(data);
+}
+
static const struct drm_bridge_funcs ps8640_bridge_funcs = {
.attach = ps8640_bridge_attach,
.detach = ps8640_bridge_detach,
@@ -512,6 +514,53 @@ static const struct drm_bridge_funcs ps8640_bridge_funcs = {
.pre_enable = ps8640_pre_enable,
};
+static int ps8640_bridge_host_attach(struct device *dev, struct ps8640 *ps_bridge)
+{
+ struct device_node *in_ep, *dsi_node;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ int ret;
+ const struct mipi_dsi_device_info info = { .type = "ps8640",
+ .channel = 0,
+ .node = NULL,
+ };
+
+ /* port@0 is ps8640 dsi input port */
+ in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
+ if (!in_ep)
+ return -ENODEV;
+
+ dsi_node = of_graph_get_remote_port_parent(in_ep);
+ of_node_put(in_ep);
+ if (!dsi_node)
+ return -ENODEV;
+
+ host = of_find_mipi_dsi_host_by_node(dsi_node);
+ of_node_put(dsi_node);
+ if (!host)
+ return -EPROBE_DEFER;
+
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ if (IS_ERR(dsi)) {
+ dev_err(dev, "failed to create dsi device\n");
+ return PTR_ERR(dsi);
+ }
+
+ ps_bridge->dsi = dsi;
+
+ dsi->host = host;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->lanes = NUM_MIPI_LANES;
+
+ ret = devm_mipi_dsi_attach(dev, dsi);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int ps8640_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
@@ -525,17 +574,6 @@ static int ps8640_probe(struct i2c_client *client)
if (!ps_bridge)
return -ENOMEM;
- /* 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);
- if (IS_ERR(ps_bridge->panel_bridge))
- return PTR_ERR(ps_bridge->panel_bridge);
-
ps_bridge->supplies[0].supply = "vdd33";
ps_bridge->supplies[1].supply = "vdd12";
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies),
@@ -558,9 +596,16 @@ static int ps8640_probe(struct i2c_client *client)
ps_bridge->bridge.funcs = &ps8640_bridge_funcs;
ps_bridge->bridge.of_node = dev->of_node;
- ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID;
ps_bridge->bridge.type = DRM_MODE_CONNECTOR_eDP;
+ /*
+ * In the device tree, if panel is listed under aux-bus of the bridge
+ * node, panel driver should be able to retrieve EDID by itself using
+ * aux-bus. So let's not set DRM_BRIDGE_OP_EDID here.
+ */
+ if (!ps8640_of_panel_on_aux_bus(&client->dev))
+ ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID;
+
ps_bridge->page[PAGE0_DP_CNTL] = client;
ps_bridge->regmap[PAGE0_DP_CNTL] = devm_regmap_init_i2c(client, ps8640_regmap_config);
@@ -587,9 +632,46 @@ static int ps8640_probe(struct i2c_client *client)
ps_bridge->aux.transfer = ps8640_aux_transfer;
drm_dp_aux_init(&ps_bridge->aux);
+ pm_runtime_enable(dev);
+ /*
+ * Powering on ps8640 takes ~300ms. To avoid wasting time on power
+ * cycling ps8640 too often, set autosuspend_delay to 1000ms to ensure
+ * the bridge wouldn't suspend in between each _aux_transfer_msg() call
+ * during EDID read (~20ms in my experiment) and in between the last
+ * _aux_transfer_msg() call during EDID read and the _pre_enable() call
+ * (~100ms in my experiment).
+ */
+ pm_runtime_set_autosuspend_delay(dev, 1000);
+ pm_runtime_use_autosuspend(dev);
+ pm_suspend_ignore_children(dev, true);
+ ret = devm_add_action_or_reset(dev, ps8640_runtime_disable, dev);
+ if (ret)
+ return ret;
+
+ 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);
+ if (IS_ERR(ps_bridge->panel_bridge))
+ return PTR_ERR(ps_bridge->panel_bridge);
+
drm_bridge_add(&ps_bridge->bridge);
+ ret = ps8640_bridge_host_attach(dev, ps_bridge);
+ if (ret)
+ goto err_bridge_remove;
+
return 0;
+
+err_bridge_remove:
+ drm_bridge_remove(&ps_bridge->bridge);
+ return ret;
}
static int ps8640_remove(struct i2c_client *client)
@@ -613,6 +695,7 @@ static struct i2c_driver ps8640_driver = {
.driver = {
.name = "ps8640",
.of_match_table = ps8640_match,
+ .pm = &ps8640_pm_ops,
},
};
module_i2c_driver(ps8640_driver);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
index d0db1acf11d7..7d2ed0ed2fe2 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c
@@ -320,13 +320,17 @@ static int dw_hdmi_open(struct snd_pcm_substream *substream)
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dw_hdmi *dw = substream->private_data;
void __iomem *base = dw->data.base;
+ u8 *eld;
int ret;
runtime->hw = dw_hdmi_hw;
- ret = snd_pcm_hw_constraint_eld(runtime, dw->data.eld);
- if (ret < 0)
- return ret;
+ eld = dw->data.get_eld(dw->data.hdmi);
+ if (eld) {
+ ret = snd_pcm_hw_constraint_eld(runtime, eld);
+ if (ret < 0)
+ return ret;
+ }
ret = snd_pcm_limit_hw_rates(runtime);
if (ret < 0)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
index cb07dc0da5a7..f72d27208ebe 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h
@@ -9,15 +9,15 @@ struct dw_hdmi_audio_data {
void __iomem *base;
int irq;
struct dw_hdmi *hdmi;
- u8 *eld;
+ u8 *(*get_eld)(struct dw_hdmi *hdmi);
};
struct dw_hdmi_i2s_audio_data {
struct dw_hdmi *hdmi;
- u8 *eld;
void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
u8 (*read)(struct dw_hdmi *hdmi, int offset);
+ u8 *(*get_eld)(struct dw_hdmi *hdmi);
};
#endif
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
index feb04f127b55..f50b47ac11a8 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c
@@ -135,8 +135,15 @@ static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
size_t len)
{
struct dw_hdmi_i2s_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);
- memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len));
return 0;
}
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index f08d0fded61f..54d8fdad395f 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -757,6 +757,14 @@ static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable)
hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS);
}
+static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi)
+{
+ if (!hdmi->curr_conn)
+ return NULL;
+
+ return hdmi->curr_conn->eld;
+}
+
static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi)
{
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
@@ -3413,6 +3421,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
hdmi->bridge.funcs = &dw_hdmi_bridge_funcs;
hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID
| DRM_BRIDGE_OP_HPD;
+ hdmi->bridge.interlace_allowed = true;
#ifdef CONFIG_OF
hdmi->bridge.of_node = pdev->dev.of_node;
#endif
@@ -3431,7 +3440,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
audio.base = hdmi->regs;
audio.irq = irq;
audio.hdmi = hdmi;
- audio.eld = hdmi->connector.eld;
+ audio.get_eld = hdmi_audio_get_eld;
hdmi->enable_audio = dw_hdmi_ahb_audio_enable;
hdmi->disable_audio = dw_hdmi_ahb_audio_disable;
@@ -3444,7 +3453,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
struct dw_hdmi_i2s_audio_data audio;
audio.hdmi = hdmi;
- audio.eld = hdmi->connector.eld;
+ audio.get_eld = hdmi_audio_get_eld;
audio.write = hdmi_writeb;
audio.read = hdmi_readb;
hdmi->enable_audio = dw_hdmi_i2s_audio_enable;
diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c
index a3db532bbdd1..fd585bf925fe 100644
--- a/drivers/gpu/drm/bridge/tc358768.c
+++ b/drivers/gpu/drm/bridge/tc358768.c
@@ -237,6 +237,10 @@ static void tc358768_hw_enable(struct tc358768_priv *priv)
if (priv->enabled)
return;
+ ret = clk_prepare_enable(priv->refclk);
+ if (ret < 0)
+ dev_err(priv->dev, "error enabling refclk (%d)\n", ret);
+
ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies);
if (ret < 0)
dev_err(priv->dev, "error enabling regulators (%d)\n", ret);
@@ -274,6 +278,8 @@ static void tc358768_hw_disable(struct tc358768_priv *priv)
if (ret < 0)
dev_err(priv->dev, "error disabling regulators (%d)\n", ret);
+ clk_disable_unprepare(priv->refclk);
+
priv->enabled = false;
}
@@ -625,12 +631,19 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
{
struct tc358768_priv *priv = bridge_to_tc358768(bridge);
struct mipi_dsi_device *dsi_dev = priv->output.dev;
+ unsigned long mode_flags = dsi_dev->mode_flags;
u32 val, val2, lptxcnt, hact, data_type;
const struct drm_display_mode *mode;
u32 dsibclk_nsk, dsiclk_nsk, ui_nsk, phy_delay_nsk;
- u32 dsiclk, dsibclk;
+ u32 dsiclk, dsibclk, video_start;
+ const u32 internal_delay = 40;
int ret, i;
+ if (mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
+ dev_warn_once(priv->dev, "Non-continuous mode unimplemented, falling back to continuous\n");
+ mode_flags &= ~MIPI_DSI_CLOCK_NON_CONTINUOUS;
+ }
+
tc358768_hw_enable(priv);
ret = tc358768_sw_reset(priv);
@@ -657,23 +670,27 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
case MIPI_DSI_FMT_RGB888:
val |= (0x3 << 4);
hact = mode->hdisplay * 3;
+ video_start = (mode->htotal - mode->hsync_start) * 3;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24;
break;
case MIPI_DSI_FMT_RGB666:
val |= (0x4 << 4);
hact = mode->hdisplay * 3;
+ video_start = (mode->htotal - mode->hsync_start) * 3;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18;
break;
case MIPI_DSI_FMT_RGB666_PACKED:
val |= (0x4 << 4) | BIT(3);
hact = mode->hdisplay * 18 / 8;
+ video_start = (mode->htotal - mode->hsync_start) * 18 / 8;
data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
break;
case MIPI_DSI_FMT_RGB565:
val |= (0x5 << 4);
hact = mode->hdisplay * 2;
+ video_start = (mode->htotal - mode->hsync_start) * 2;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16;
break;
default:
@@ -684,7 +701,8 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
}
/* VSDly[9:0] */
- tc358768_write(priv, TC358768_VSDLY, 1);
+ video_start = max(video_start, internal_delay + 1) - internal_delay;
+ tc358768_write(priv, TC358768_VSDLY, video_start);
tc358768_write(priv, TC358768_DATAFMT, val);
tc358768_write(priv, TC358768_DSITX_DT, data_type);
@@ -764,7 +782,7 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
val |= BIT(i + 1);
tc358768_write(priv, TC358768_HSTXVREGEN, val);
- if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+ if (!(mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
tc358768_write(priv, TC358768_TXOPTIONCNTRL, 0x1);
/* TXTAGOCNT[26:16] RXTASURECNT[10:0] */
@@ -772,31 +790,61 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1;
val2 = tc358768_ns_to_cnt(tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk),
dsibclk_nsk) - 2;
- val |= val2 << 16;
+ val = val << 16 | val2;
dev_dbg(priv->dev, "BTACNTRL1: 0x%x\n", val);
tc358768_write(priv, TC358768_BTACNTRL1, val);
/* START[0] */
tc358768_write(priv, TC358768_STARTCNTRL, 1);
- /* Set event mode */
- tc358768_write(priv, TC358768_DSI_EVENT, 1);
-
- /* vsw (+ vbp) */
- tc358768_write(priv, TC358768_DSI_VSW,
- mode->vtotal - mode->vsync_start);
- /* vbp (not used in event mode) */
- tc358768_write(priv, TC358768_DSI_VBPR, 0);
- /* vact */
- tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
-
- /* (hsw + hbp) * byteclk * ndl / pclk */
- val = (u32)div_u64((mode->htotal - mode->hsync_start) *
- ((u64)priv->dsiclk / 4) * priv->dsi_lanes,
- mode->clock * 1000);
- tc358768_write(priv, TC358768_DSI_HSW, val);
- /* hbp (not used in event mode) */
- tc358768_write(priv, TC358768_DSI_HBPR, 0);
+ if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+ /* Set pulse mode */
+ tc358768_write(priv, TC358768_DSI_EVENT, 0);
+
+ /* vact */
+ tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
+
+ /* vsw */
+ tc358768_write(priv, TC358768_DSI_VSW,
+ mode->vsync_end - mode->vsync_start);
+ /* vbp */
+ tc358768_write(priv, TC358768_DSI_VBPR,
+ mode->vtotal - mode->vsync_end);
+
+ /* hsw * byteclk * ndl / pclk */
+ val = (u32)div_u64((mode->hsync_end - mode->hsync_start) *
+ ((u64)priv->dsiclk / 4) * priv->dsi_lanes,
+ mode->clock * 1000);
+ tc358768_write(priv, TC358768_DSI_HSW, val);
+
+ /* hbp * byteclk * ndl / pclk */
+ val = (u32)div_u64((mode->htotal - mode->hsync_end) *
+ ((u64)priv->dsiclk / 4) * priv->dsi_lanes,
+ mode->clock * 1000);
+ tc358768_write(priv, TC358768_DSI_HBPR, val);
+ } else {
+ /* Set event mode */
+ tc358768_write(priv, TC358768_DSI_EVENT, 1);
+
+ /* vact */
+ tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
+
+ /* vsw (+ vbp) */
+ tc358768_write(priv, TC358768_DSI_VSW,
+ mode->vtotal - mode->vsync_start);
+ /* vbp (not used in event mode) */
+ tc358768_write(priv, TC358768_DSI_VBPR, 0);
+
+ /* (hsw + hbp) * byteclk * ndl / pclk */
+ val = (u32)div_u64((mode->htotal - mode->hsync_start) *
+ ((u64)priv->dsiclk / 4) * priv->dsi_lanes,
+ mode->clock * 1000);
+ tc358768_write(priv, TC358768_DSI_HSW, val);
+
+ /* hbp (not used in event mode) */
+ tc358768_write(priv, TC358768_DSI_HBPR, 0);
+ }
+
/* hact (bytes) */
tc358768_write(priv, TC358768_DSI_HACT, hact);
@@ -822,7 +870,7 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
if (!(dsi_dev->mode_flags & MIPI_DSI_MODE_LPM))
val |= TC358768_DSI_CONTROL_TXMD;
- if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+ if (!(mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
val |= TC358768_DSI_CONTROL_HSCKMD;
if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c
index 2272adcc5b4a..2c76331b251d 100644
--- a/drivers/gpu/drm/bridge/tc358775.c
+++ b/drivers/gpu/drm/bridge/tc358775.c
@@ -594,11 +594,26 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct tc_data *tc = bridge_to_tc(bridge);
+
+ /* Attach the panel-bridge to the dsi bridge */
+ return drm_bridge_attach(bridge->encoder, tc->panel_bridge,
+ &tc->bridge, flags);
+}
+
+static const struct drm_bridge_funcs tc_bridge_funcs = {
+ .attach = tc_bridge_attach,
+ .pre_enable = tc_bridge_pre_enable,
+ .enable = tc_bridge_enable,
+ .mode_valid = tc_mode_valid,
+ .post_disable = tc_bridge_post_disable,
+};
+
+static int tc_attach_host(struct tc_data *tc)
+{
struct device *dev = &tc->i2c->dev;
struct mipi_dsi_host *host;
struct mipi_dsi_device *dsi;
int ret;
-
const struct mipi_dsi_device_info info = { .type = "tc358775",
.channel = 0,
.node = NULL,
@@ -610,11 +625,10 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
return -EPROBE_DEFER;
}
- dsi = mipi_dsi_device_register_full(host, &info);
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) {
dev_err(dev, "failed to create dsi device\n");
- ret = PTR_ERR(dsi);
- goto err_dsi_device;
+ return PTR_ERR(dsi);
}
tc->dsi = dsi;
@@ -623,29 +637,15 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
- ret = mipi_dsi_attach(dsi);
+ ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n");
- goto err_dsi_attach;
+ return ret;
}
- /* Attach the panel-bridge to the dsi bridge */
- return drm_bridge_attach(bridge->encoder, tc->panel_bridge,
- &tc->bridge, flags);
-err_dsi_attach:
- mipi_dsi_device_unregister(dsi);
-err_dsi_device:
- return ret;
+ return 0;
}
-static const struct drm_bridge_funcs tc_bridge_funcs = {
- .attach = tc_bridge_attach,
- .pre_enable = tc_bridge_pre_enable,
- .enable = tc_bridge_enable,
- .mode_valid = tc_mode_valid,
- .post_disable = tc_bridge_post_disable,
-};
-
static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
@@ -709,7 +709,15 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
i2c_set_clientdata(client, tc);
+ ret = tc_attach_host(tc);
+ if (ret)
+ goto err_bridge_remove;
+
return 0;
+
+err_bridge_remove:
+ drm_bridge_remove(&tc->bridge);
+ return ret;
}
static int tc_remove(struct i2c_client *client)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
index ba1160ec6d6e..945f08de45f1 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -245,47 +245,9 @@ static int sn65dsi83_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
- struct device *dev = ctx->dev;
- struct mipi_dsi_device *dsi;
- struct mipi_dsi_host *host;
- int ret = 0;
-
- const struct mipi_dsi_device_info info = {
- .type = "sn65dsi83",
- .channel = 0,
- .node = NULL,
- };
-
- host = of_find_mipi_dsi_host_by_node(ctx->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");
- }
-
- ctx->dsi = dsi;
-
- dsi->lanes = ctx->dsi_lanes;
- dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
-
- ret = mipi_dsi_attach(dsi);
- if (ret < 0) {
- dev_err(dev, "failed to attach dsi to host\n");
- goto err_dsi_attach;
- }
return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
&ctx->bridge, flags);
-
-err_dsi_attach:
- mipi_dsi_device_unregister(dsi);
- return ret;
}
static void sn65dsi83_detach(struct drm_bridge *bridge)
@@ -295,28 +257,9 @@ static void sn65dsi83_detach(struct drm_bridge *bridge)
if (!ctx->dsi)
return;
- mipi_dsi_detach(ctx->dsi);
- mipi_dsi_device_unregister(ctx->dsi);
- drm_bridge_remove(&ctx->bridge);
ctx->dsi = NULL;
}
-static void sn65dsi83_atomic_pre_enable(struct drm_bridge *bridge,
- struct drm_bridge_state *old_bridge_state)
-{
- struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
-
- /*
- * Reset the chip, pull EN line low for t_reset=10ms,
- * then high for t_en=1ms.
- */
- regcache_mark_dirty(ctx->regmap);
- gpiod_set_value(ctx->enable_gpio, 0);
- usleep_range(10000, 11000);
- gpiod_set_value(ctx->enable_gpio, 1);
- usleep_range(1000, 1100);
-}
-
static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx,
const struct drm_display_mode *mode)
{
@@ -394,6 +337,10 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
u16 val;
int ret;
+ /* Deassert reset */
+ gpiod_set_value(ctx->enable_gpio, 1);
+ usleep_range(1000, 1100);
+
/* Get the LVDS format from the bridge state. */
bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
@@ -540,18 +487,11 @@ static void sn65dsi83_atomic_disable(struct drm_bridge *bridge,
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
- /* Clear reset, disable PLL */
- regmap_write(ctx->regmap, REG_RC_RESET, 0x00);
- regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
-}
-
-static void sn65dsi83_atomic_post_disable(struct drm_bridge *bridge,
- struct drm_bridge_state *old_bridge_state)
-{
- struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
-
- /* Put the chip in reset, pull EN line low. */
+ /* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */
gpiod_set_value(ctx->enable_gpio, 0);
+ usleep_range(10000, 11000);
+
+ regcache_mark_dirty(ctx->regmap);
}
static enum drm_mode_status
@@ -597,10 +537,8 @@ sn65dsi83_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
static const struct drm_bridge_funcs sn65dsi83_funcs = {
.attach = sn65dsi83_attach,
.detach = sn65dsi83_detach,
- .atomic_pre_enable = sn65dsi83_atomic_pre_enable,
.atomic_enable = sn65dsi83_atomic_enable,
.atomic_disable = sn65dsi83_atomic_disable,
- .atomic_post_disable = sn65dsi83_atomic_post_disable,
.mode_valid = sn65dsi83_mode_valid,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
@@ -664,6 +602,44 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
return 0;
}
+static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
+{
+ struct device *dev = ctx->dev;
+ struct mipi_dsi_device *dsi;
+ struct mipi_dsi_host *host;
+ const struct mipi_dsi_device_info info = {
+ .type = "sn65dsi83",
+ .channel = 0,
+ .node = NULL,
+ };
+ int ret;
+
+ host = of_find_mipi_dsi_host_by_node(ctx->host_node);
+ if (!host) {
+ dev_err(dev, "failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ 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 = ctx->dsi_lanes;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
+
+ 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 sn65dsi83_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -685,10 +661,13 @@ static int sn65dsi83_probe(struct i2c_client *client,
model = id->driver_data;
}
+ /* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */
ctx->enable_gpio = devm_gpiod_get(ctx->dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(ctx->enable_gpio))
return PTR_ERR(ctx->enable_gpio);
+ usleep_range(10000, 11000);
+
ret = sn65dsi83_parse_dt(ctx, model);
if (ret)
return ret;
@@ -704,13 +683,22 @@ static int sn65dsi83_probe(struct i2c_client *client,
ctx->bridge.of_node = dev->of_node;
drm_bridge_add(&ctx->bridge);
+ ret = sn65dsi83_host_attach(ctx);
+ if (ret)
+ goto err_remove_bridge;
+
return 0;
+
+err_remove_bridge:
+ drm_bridge_remove(&ctx->bridge);
+ return ret;
}
static int sn65dsi83_remove(struct i2c_client *client)
{
struct sn65dsi83 *ctx = i2c_get_clientdata(client);
+ drm_bridge_remove(&ctx->bridge);
of_node_put(ctx->host_node);
return 0;
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index 6154bed0af5b..dab8f76618f3 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -4,7 +4,9 @@
* datasheet: https://www.ti.com/lit/ds/symlink/sn65dsi86.pdf
*/
+#include <linux/atomic.h>
#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
@@ -15,6 +17,7 @@
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -91,6 +94,13 @@
#define SN_ML_TX_MODE_REG 0x96
#define ML_TX_MAIN_LINK_OFF 0
#define ML_TX_NORMAL_MODE BIT(0)
+#define SN_PWM_PRE_DIV_REG 0xA0
+#define SN_BACKLIGHT_SCALE_REG 0xA1
+#define BACKLIGHT_SCALE_MAX 0xFFFF
+#define SN_BACKLIGHT_REG 0xA3
+#define SN_PWM_EN_INV_REG 0xA5
+#define SN_PWM_INV_MASK BIT(0)
+#define SN_PWM_EN_MASK BIT(1)
#define SN_AUX_CMD_STATUS_REG 0xF4
#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
#define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
@@ -113,11 +123,14 @@
#define SN_LINK_TRAINING_TRIES 10
+#define SN_PWM_GPIO_IDX 3 /* 4th GPIO */
+
/**
* struct ti_sn65dsi86 - Platform data for ti-sn65dsi86 driver.
* @bridge_aux: AUX-bus sub device for MIPI-to-eDP bridge functionality.
* @gpio_aux: AUX-bus sub device for GPIO controller functionality.
* @aux_aux: AUX-bus sub device for eDP AUX channel functionality.
+ * @pwm_aux: AUX-bus sub device for PWM controller functionality.
*
* @dev: Pointer to the top level (i2c) device.
* @regmap: Regmap for accessing i2c.
@@ -145,11 +158,17 @@
* bitmap so we can do atomic ops on it without an extra
* lock so concurrent users of our 4 GPIOs don't stomp on
* each other's read-modify-write.
+ *
+ * @pchip: pwm_chip if the PWM is exposed.
+ * @pwm_enabled: Used to track if the PWM signal is currently enabled.
+ * @pwm_pin_busy: Track if GPIO4 is currently requested for GPIO or PWM.
+ * @pwm_refclk_freq: Cache for the reference clock input to the PWM.
*/
struct ti_sn65dsi86 {
struct auxiliary_device bridge_aux;
struct auxiliary_device gpio_aux;
struct auxiliary_device aux_aux;
+ struct auxiliary_device pwm_aux;
struct device *dev;
struct regmap *regmap;
@@ -172,6 +191,12 @@ struct ti_sn65dsi86 {
struct gpio_chip gchip;
DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS);
#endif
+#if defined(CONFIG_PWM)
+ struct pwm_chip pchip;
+ bool pwm_enabled;
+ atomic_t pwm_pin_busy;
+#endif
+ unsigned int pwm_refclk_freq;
};
static const struct regmap_range ti_sn65dsi86_volatile_ranges[] = {
@@ -188,13 +213,30 @@ static const struct regmap_config ti_sn65dsi86_regmap_config = {
.val_bits = 8,
.volatile_table = &ti_sn_bridge_volatile_table,
.cache_type = REGCACHE_NONE,
+ .max_register = 0xFF,
};
+static int __maybe_unused ti_sn65dsi86_read_u16(struct ti_sn65dsi86 *pdata,
+ unsigned int reg, u16 *val)
+{
+ u8 buf[2];
+ int ret;
+
+ ret = regmap_bulk_read(pdata->regmap, reg, buf, ARRAY_SIZE(buf));
+ if (ret)
+ return ret;
+
+ *val = buf[0] | (buf[1] << 8);
+
+ return 0;
+}
+
static void ti_sn65dsi86_write_u16(struct ti_sn65dsi86 *pdata,
unsigned int reg, u16 val)
{
- regmap_write(pdata->regmap, reg, val & 0xFF);
- regmap_write(pdata->regmap, reg + 1, val >> 8);
+ u8 buf[2] = { val & 0xff, val >> 8 };
+
+ regmap_bulk_write(pdata->regmap, reg, buf, ARRAY_SIZE(buf));
}
static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn65dsi86 *pdata)
@@ -253,6 +295,12 @@ static void ti_sn_bridge_set_refclk_freq(struct ti_sn65dsi86 *pdata)
regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK,
REFCLK_FREQ(i));
+
+ /*
+ * The PWM refclk is based on the value written to SN_DPPLL_SRC_REG,
+ * regardless of its actual sourcing.
+ */
+ pdata->pwm_refclk_freq = ti_sn_bridge_refclk_lut[i];
}
static void ti_sn65dsi86_enable_comms(struct ti_sn65dsi86 *pdata)
@@ -655,58 +703,24 @@ static struct ti_sn65dsi86 *bridge_to_ti_sn65dsi86(struct drm_bridge *bridge)
return container_of(bridge, struct ti_sn65dsi86, bridge);
}
-static int ti_sn_bridge_attach(struct drm_bridge *bridge,
- enum drm_bridge_attach_flags flags)
+static int ti_sn_attach_host(struct ti_sn65dsi86 *pdata)
{
- int ret, val;
- struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
+ int val;
struct mipi_dsi_host *host;
struct mipi_dsi_device *dsi;
+ struct device *dev = pdata->dev;
const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
.channel = 0,
.node = NULL,
- };
-
- if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
- DRM_ERROR("Fix bridge driver to make connector optional!");
- return -EINVAL;
- }
-
- pdata->aux.drm_dev = bridge->dev;
- ret = drm_dp_aux_register(&pdata->aux);
- if (ret < 0) {
- drm_err(bridge->dev, "Failed to register DP AUX channel: %d\n", ret);
- return ret;
- }
-
- ret = ti_sn_bridge_connector_init(pdata);
- if (ret < 0)
- goto err_conn_init;
+ };
- /*
- * TODO: ideally finding host resource and dsi dev registration needs
- * to be done in bridge probe. But some existing DSI host drivers will
- * wait for any of the drm_bridge/drm_panel to get added to the global
- * bridge/panel list, before completing their probe. So if we do the
- * dsi dev registration part in bridge probe, before populating in
- * the global bridge list, then it will cause deadlock as dsi host probe
- * will never complete, neither our bridge probe. So keeping it here
- * will satisfy most of the existing host drivers. Once the host driver
- * is fixed we can move the below code to bridge probe safely.
- */
host = of_find_mipi_dsi_host_by_node(pdata->host_node);
- if (!host) {
- DRM_ERROR("failed to find dsi host\n");
- ret = -ENODEV;
- goto err_dsi_host;
- }
+ if (!host)
+ return -EPROBE_DEFER;
- dsi = mipi_dsi_device_register_full(host, &info);
- if (IS_ERR(dsi)) {
- DRM_ERROR("failed to create dsi device\n");
- ret = PTR_ERR(dsi);
- goto err_dsi_host;
- }
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ if (IS_ERR(dsi))
+ return PTR_ERR(dsi);
/* TODO: setting to 4 MIPI lanes always for now */
dsi->lanes = 4;
@@ -714,18 +728,38 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
/* check if continuous dsi clock is required or not */
- pm_runtime_get_sync(pdata->dev);
+ pm_runtime_get_sync(dev);
regmap_read(pdata->regmap, SN_DPPLL_SRC_REG, &val);
- pm_runtime_put_autosuspend(pdata->dev);
+ pm_runtime_put_autosuspend(dev);
if (!(val & DPPLL_CLK_SRC_DSICLK))
dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
- ret = mipi_dsi_attach(dsi);
+ pdata->dsi = dsi;
+
+ return devm_mipi_dsi_attach(dev, dsi);
+}
+
+static int ti_sn_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
+ int ret;
+
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
+ pdata->aux.drm_dev = bridge->dev;
+ ret = drm_dp_aux_register(&pdata->aux);
if (ret < 0) {
- DRM_ERROR("failed to attach dsi to host\n");
- goto err_dsi_attach;
+ drm_err(bridge->dev, "Failed to register DP AUX channel: %d\n", ret);
+ return ret;
}
- pdata->dsi = dsi;
+
+ ret = ti_sn_bridge_connector_init(pdata);
+ if (ret < 0)
+ goto err_conn_init;
/* We never want the next bridge to *also* create a connector: */
flags |= DRM_BRIDGE_ATTACH_NO_CONNECTOR;
@@ -734,14 +768,10 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
&pdata->bridge, flags);
if (ret < 0)
- goto err_dsi_detach;
+ goto err_dsi_host;
return 0;
-err_dsi_detach:
- mipi_dsi_detach(dsi);
-err_dsi_attach:
- mipi_dsi_device_unregister(dsi);
err_dsi_host:
drm_connector_cleanup(&pdata->connector);
err_conn_init:
@@ -1227,7 +1257,17 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
drm_bridge_add(&pdata->bridge);
+ ret = ti_sn_attach_host(pdata);
+ if (ret) {
+ dev_err_probe(pdata->dev, ret, "failed to attach dsi host\n");
+ goto err_remove_bridge;
+ }
+
return 0;
+
+err_remove_bridge:
+ drm_bridge_remove(&pdata->bridge);
+ return ret;
}
static void ti_sn_bridge_remove(struct auxiliary_device *adev)
@@ -1237,11 +1277,6 @@ static void ti_sn_bridge_remove(struct auxiliary_device *adev)
if (!pdata)
return;
- if (pdata->dsi) {
- mipi_dsi_detach(pdata->dsi);
- mipi_dsi_device_unregister(pdata->dsi);
- }
-
drm_bridge_remove(&pdata->bridge);
of_node_put(pdata->host_node);
@@ -1260,9 +1295,287 @@ static struct auxiliary_driver ti_sn_bridge_driver = {
};
/* -----------------------------------------------------------------------------
- * GPIO Controller
+ * PWM Controller
*/
+#if defined(CONFIG_PWM)
+static int ti_sn_pwm_pin_request(struct ti_sn65dsi86 *pdata)
+{
+ return atomic_xchg(&pdata->pwm_pin_busy, 1) ? -EBUSY : 0;
+}
+
+static void ti_sn_pwm_pin_release(struct ti_sn65dsi86 *pdata)
+{
+ atomic_set(&pdata->pwm_pin_busy, 0);
+}
+
+static struct ti_sn65dsi86 *pwm_chip_to_ti_sn_bridge(struct pwm_chip *chip)
+{
+ return container_of(chip, struct ti_sn65dsi86, pchip);
+}
+
+static int ti_sn_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+
+ return ti_sn_pwm_pin_request(pdata);
+}
+
+static void ti_sn_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+
+ ti_sn_pwm_pin_release(pdata);
+}
+
+/*
+ * Limitations:
+ * - The PWM signal is not driven when the chip is powered down, or in its
+ * reset state and the driver does not implement the "suspend state"
+ * described in the documentation. In order to save power, state->enabled is
+ * interpreted as denoting if the signal is expected to be valid, and is used
+ * to determine if the chip needs to be kept powered.
+ * - Changing both period and duty_cycle is not done atomically, neither is the
+ * multi-byte register updates, so the output might briefly be undefined
+ * during update.
+ */
+static int ti_sn_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+ unsigned int pwm_en_inv;
+ unsigned int backlight;
+ unsigned int pre_div;
+ unsigned int scale;
+ u64 period_max;
+ u64 period;
+ int ret;
+
+ if (!pdata->pwm_enabled) {
+ ret = pm_runtime_get_sync(pdata->dev);
+ if (ret < 0) {
+ pm_runtime_put_sync(pdata->dev);
+ return ret;
+ }
+ }
+
+ if (state->enabled) {
+ if (!pdata->pwm_enabled) {
+ /*
+ * The chip might have been powered down while we
+ * didn't hold a PM runtime reference, so mux in the
+ * PWM function on the GPIO pin again.
+ */
+ ret = regmap_update_bits(pdata->regmap, SN_GPIO_CTRL_REG,
+ SN_GPIO_MUX_MASK << (2 * SN_PWM_GPIO_IDX),
+ SN_GPIO_MUX_SPECIAL << (2 * SN_PWM_GPIO_IDX));
+ if (ret) {
+ dev_err(pdata->dev, "failed to mux in PWM function\n");
+ goto out;
+ }
+ }
+
+ /*
+ * Per the datasheet the PWM frequency is given by:
+ *
+ * REFCLK_FREQ
+ * PWM_FREQ = -----------------------------------
+ * PWM_PRE_DIV * BACKLIGHT_SCALE + 1
+ *
+ * However, after careful review the author is convinced that
+ * the documentation has lost some parenthesis around
+ * "BACKLIGHT_SCALE + 1".
+ *
+ * With the period T_pwm = 1/PWM_FREQ this can be written:
+ *
+ * T_pwm * REFCLK_FREQ = PWM_PRE_DIV * (BACKLIGHT_SCALE + 1)
+ *
+ * In order to keep BACKLIGHT_SCALE within its 16 bits,
+ * PWM_PRE_DIV must be:
+ *
+ * T_pwm * REFCLK_FREQ
+ * PWM_PRE_DIV >= -------------------------
+ * BACKLIGHT_SCALE_MAX + 1
+ *
+ * To simplify the search and to favour higher resolution of
+ * the duty cycle over accuracy of the period, the lowest
+ * possible PWM_PRE_DIV is used. Finally the scale is
+ * calculated as:
+ *
+ * T_pwm * REFCLK_FREQ
+ * BACKLIGHT_SCALE = ---------------------- - 1
+ * PWM_PRE_DIV
+ *
+ * Here T_pwm is represented in seconds, so appropriate scaling
+ * to nanoseconds is necessary.
+ */
+
+ /* Minimum T_pwm is 1 / REFCLK_FREQ */
+ if (state->period <= NSEC_PER_SEC / pdata->pwm_refclk_freq) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Maximum T_pwm is 255 * (65535 + 1) / REFCLK_FREQ
+ * Limit period to this to avoid overflows
+ */
+ period_max = div_u64((u64)NSEC_PER_SEC * 255 * (65535 + 1),
+ pdata->pwm_refclk_freq);
+ period = min(state->period, period_max);
+
+ pre_div = DIV64_U64_ROUND_UP(period * pdata->pwm_refclk_freq,
+ (u64)NSEC_PER_SEC * (BACKLIGHT_SCALE_MAX + 1));
+ scale = div64_u64(period * pdata->pwm_refclk_freq, (u64)NSEC_PER_SEC * pre_div) - 1;
+
+ /*
+ * The documentation has the duty ratio given as:
+ *
+ * duty BACKLIGHT
+ * ------- = ---------------------
+ * period BACKLIGHT_SCALE + 1
+ *
+ * Solve for BACKLIGHT, substituting BACKLIGHT_SCALE according
+ * to definition above and adjusting for nanosecond
+ * representation of duty cycle gives us:
+ */
+ backlight = div64_u64(state->duty_cycle * pdata->pwm_refclk_freq,
+ (u64)NSEC_PER_SEC * pre_div);
+ if (backlight > scale)
+ backlight = scale;
+
+ ret = regmap_write(pdata->regmap, SN_PWM_PRE_DIV_REG, pre_div);
+ if (ret) {
+ dev_err(pdata->dev, "failed to update PWM_PRE_DIV\n");
+ goto out;
+ }
+
+ ti_sn65dsi86_write_u16(pdata, SN_BACKLIGHT_SCALE_REG, scale);
+ ti_sn65dsi86_write_u16(pdata, SN_BACKLIGHT_REG, backlight);
+ }
+
+ pwm_en_inv = FIELD_PREP(SN_PWM_EN_MASK, state->enabled) |
+ FIELD_PREP(SN_PWM_INV_MASK, state->polarity == PWM_POLARITY_INVERSED);
+ ret = regmap_write(pdata->regmap, SN_PWM_EN_INV_REG, pwm_en_inv);
+ if (ret) {
+ dev_err(pdata->dev, "failed to update PWM_EN/PWM_INV\n");
+ goto out;
+ }
+
+ pdata->pwm_enabled = state->enabled;
+out:
+
+ if (!pdata->pwm_enabled)
+ pm_runtime_put_sync(pdata->dev);
+
+ return ret;
+}
+
+static void ti_sn_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+ unsigned int pwm_en_inv;
+ unsigned int pre_div;
+ u16 backlight;
+ u16 scale;
+ int ret;
+
+ ret = regmap_read(pdata->regmap, SN_PWM_EN_INV_REG, &pwm_en_inv);
+ if (ret)
+ return;
+
+ ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_SCALE_REG, &scale);
+ if (ret)
+ return;
+
+ ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_REG, &backlight);
+ if (ret)
+ return;
+
+ ret = regmap_read(pdata->regmap, SN_PWM_PRE_DIV_REG, &pre_div);
+ if (ret)
+ return;
+
+ state->enabled = FIELD_GET(SN_PWM_EN_MASK, pwm_en_inv);
+ if (FIELD_GET(SN_PWM_INV_MASK, pwm_en_inv))
+ state->polarity = PWM_POLARITY_INVERSED;
+ else
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pre_div * (scale + 1),
+ pdata->pwm_refclk_freq);
+ state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pre_div * backlight,
+ pdata->pwm_refclk_freq);
+
+ if (state->duty_cycle > state->period)
+ state->duty_cycle = state->period;
+}
+static const struct pwm_ops ti_sn_pwm_ops = {
+ .request = ti_sn_pwm_request,
+ .free = ti_sn_pwm_free,
+ .apply = ti_sn_pwm_apply,
+ .get_state = ti_sn_pwm_get_state,
+ .owner = THIS_MODULE,
+};
+
+static int ti_sn_pwm_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+
+ pdata->pchip.dev = pdata->dev;
+ pdata->pchip.ops = &ti_sn_pwm_ops;
+ pdata->pchip.npwm = 1;
+ pdata->pchip.of_xlate = of_pwm_single_xlate;
+ pdata->pchip.of_pwm_n_cells = 1;
+
+ return pwmchip_add(&pdata->pchip);
+}
+
+static void ti_sn_pwm_remove(struct auxiliary_device *adev)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+
+ pwmchip_remove(&pdata->pchip);
+
+ if (pdata->pwm_enabled)
+ pm_runtime_put_sync(pdata->dev);
+}
+
+static const struct auxiliary_device_id ti_sn_pwm_id_table[] = {
+ { .name = "ti_sn65dsi86.pwm", },
+ {},
+};
+
+static struct auxiliary_driver ti_sn_pwm_driver = {
+ .name = "pwm",
+ .probe = ti_sn_pwm_probe,
+ .remove = ti_sn_pwm_remove,
+ .id_table = ti_sn_pwm_id_table,
+};
+
+static int __init ti_sn_pwm_register(void)
+{
+ return auxiliary_driver_register(&ti_sn_pwm_driver);
+}
+
+static void ti_sn_pwm_unregister(void)
+{
+ auxiliary_driver_unregister(&ti_sn_pwm_driver);
+}
+
+#else
+static inline int ti_sn_pwm_pin_request(struct ti_sn65dsi86 *pdata) { return 0; }
+static inline void ti_sn_pwm_pin_release(struct ti_sn65dsi86 *pdata) {}
+
+static inline int ti_sn_pwm_register(void) { return 0; }
+static inline void ti_sn_pwm_unregister(void) {}
+#endif
+
+/* -----------------------------------------------------------------------------
+ * GPIO Controller
+ */
#if defined(CONFIG_OF_GPIO)
static int tn_sn_bridge_of_xlate(struct gpio_chip *chip,
@@ -1395,10 +1708,25 @@ static int ti_sn_bridge_gpio_direction_output(struct gpio_chip *chip,
return ret;
}
+static int ti_sn_bridge_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
+
+ if (offset == SN_PWM_GPIO_IDX)
+ return ti_sn_pwm_pin_request(pdata);
+
+ return 0;
+}
+
static void ti_sn_bridge_gpio_free(struct gpio_chip *chip, unsigned int offset)
{
+ struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
+
/* We won't keep pm_runtime if we're input, so switch there on free */
ti_sn_bridge_gpio_direction_input(chip, offset);
+
+ if (offset == SN_PWM_GPIO_IDX)
+ ti_sn_pwm_pin_release(pdata);
}
static const char * const ti_sn_bridge_gpio_names[SN_NUM_GPIOS] = {
@@ -1420,6 +1748,7 @@ static int ti_sn_gpio_probe(struct auxiliary_device *adev,
pdata->gchip.owner = THIS_MODULE;
pdata->gchip.of_xlate = tn_sn_bridge_of_xlate;
pdata->gchip.of_gpio_n_cells = 2;
+ pdata->gchip.request = ti_sn_bridge_gpio_request;
pdata->gchip.free = ti_sn_bridge_gpio_free;
pdata->gchip.get_direction = ti_sn_bridge_gpio_get_direction;
pdata->gchip.direction_input = ti_sn_bridge_gpio_direction_input;
@@ -1546,10 +1875,9 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
* ordering. The bridge wants the panel to be there when it probes.
* The panel wants its HPD GPIO (provided by sn65dsi86 on some boards)
* when it probes. The panel and maybe backlight might want the DDC
- * bus. Soon the PWM provided by the bridge chip will have the same
- * problem. Having sub-devices allows the some sub devices to finish
- * probing even if others return -EPROBE_DEFER and gets us around the
- * problems.
+ * bus or the pwm_chip. Having sub-devices allows the some sub devices
+ * to finish probing even if others return -EPROBE_DEFER and gets us
+ * around the problems.
*/
if (IS_ENABLED(CONFIG_OF_GPIO)) {
@@ -1558,6 +1886,12 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
return ret;
}
+ if (IS_ENABLED(CONFIG_PWM)) {
+ ret = ti_sn65dsi86_add_aux_device(pdata, &pdata->pwm_aux, "pwm");
+ if (ret)
+ return ret;
+ }
+
/*
* NOTE: At the end of the AUX channel probe we'll add the aux device
* for the bridge. This is because the bridge can't be used until the
@@ -1601,10 +1935,14 @@ static int __init ti_sn65dsi86_init(void)
if (ret)
goto err_main_was_registered;
- ret = auxiliary_driver_register(&ti_sn_aux_driver);
+ ret = ti_sn_pwm_register();
if (ret)
goto err_gpio_was_registered;
+ ret = auxiliary_driver_register(&ti_sn_aux_driver);
+ if (ret)
+ goto err_pwm_was_registered;
+
ret = auxiliary_driver_register(&ti_sn_bridge_driver);
if (ret)
goto err_aux_was_registered;
@@ -1613,6 +1951,8 @@ static int __init ti_sn65dsi86_init(void)
err_aux_was_registered:
auxiliary_driver_unregister(&ti_sn_aux_driver);
+err_pwm_was_registered:
+ ti_sn_pwm_unregister();
err_gpio_was_registered:
ti_sn_gpio_unregister();
err_main_was_registered:
@@ -1626,6 +1966,7 @@ static void __exit ti_sn65dsi86_exit(void)
{
auxiliary_driver_unregister(&ti_sn_bridge_driver);
auxiliary_driver_unregister(&ti_sn_aux_driver);
+ ti_sn_pwm_unregister();
ti_sn_gpio_unregister();
i2c_del_driver(&ti_sn65dsi86_driver);
}