summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2021-07-21 11:58:26 +1000
committerDave Airlie <airlied@redhat.com>2021-07-21 11:58:28 +1000
commit588b3eee528873d73bf777f329d35b2e65e24777 (patch)
tree5c93b3d1c08f208fb959bfbffa584b00175e3910 /drivers/gpu/drm/bridge
parentb4d7049acebf959e80d11611cd104bc5360fd13b (diff)
parent17a1837d07be38d957af453e08788edbe1f9343a (diff)
Merge tag 'drm-misc-next-2021-07-16' of git://anongit.freedesktop.org/drm/drm-misc into drm-next
drm-misc-next for v5.15: UAPI Changes: Cross-subsystem Changes: - udmabuf: Add support for mapping hugepages - Add dma-buf stats to sysfs. - Assorted fixes to fbdev/omap2. - dma-buf: Document DMA_BUF_IOCTL_SYNC - Improve dma-buf non-dynamic exporter expectations better. - Add module parameters for dma-buf size and list limit. - Add HDMI codec support to vc4, to replace vc4's own codec. - Document dma-buf implicit fencing rules. - dma_resv_test_signaled test_all handling. Core Changes: - Extract i915's eDP backlight code into DRM helpers. - Assorted docbook updates. - Rework drm_dp_aux documentation. - Add support for the DP aux bus. - Shrink dma-fence-chain slightly. - Add alloc/free helpers for dma-fence-chain. - Assorted fixes to TTM., drm/of, bridge - drm_gem_plane_helper_prepare/cleanup_fb is now the default for gem drivers. - Small fix for scheduler completion. - Remove use of drm_device.irq_enabled. - Print the driver name to dmesg when registering framebuffer. - Export drm/gem's shadow plane handling, and use it in vkms. - Assorted small fixes. Driver Changes: - Add eDP backlight to nouveau. - Assorted fixes and cleanups to nouveau, panfrost, vmwgfx, anx7625, amdgpu, gma500, radeon, mgag200, vgem, vc4, vkms, omapdrm. - Add support for Samsung DB7430, Samsung ATNA33XC20, EDT ETMV570G2DHU, EDT ETM0350G0DH6, Innolux EJ030NA panels. - Fix some simple pannels missing bus_format and connector types. - Add mks-guest-stats instrumentation support to vmwgfx. - Merge i915-ttm topic branch. - Make s6e63m0 panel use Mipi-DBI helpers. - Add detect() supoprt for AST. - Use interrupts for hotplug on vc4. - vmwgfx is now moved to drm-misc-next, as sroland is no longer a maintainer for now. - vmwgfx now uses copies of vmware's internal device headers. - Slowly convert ti-sn65dsi83 over to atomic. - Rework amdgpu dma-resv handling. - Fix virtio fencing for planes. - Ensure amdgpu can always evict to SYSTEM. - Many drivers fixed for implicit fencing rules. - Set default prepare/cleanup fb for tiny, vram and simple helpers too. - Rework panfrost gpu reset and related serialization. - Update VKMS todo list. - Make bochs a tiny gpu driver, and use vram helper. - Use linux irq interfaces instead of drm_irq in some drivers. - Add support for Raspberry Pi Pico to GUD. Signed-off-by: Dave Airlie <airlied@redhat.com> # gpg: Signature made Fri 16 Jul 2021 21:06:04 AEST # gpg: using RSA key B97BD6A80CAC4981091AE547FE558C72A67013C3 # gpg: Good signature from "Maarten Lankhorst <maarten.lankhorst@linux.intel.com>" [expired] # gpg: aka "Maarten Lankhorst <maarten@debian.org>" [expired] # gpg: aka "Maarten Lankhorst <maarten.lankhorst@canonical.com>" [expired] # gpg: Note: This key has expired! # Primary key fingerprint: B97B D6A8 0CAC 4981 091A E547 FE55 8C72 A670 13C3 From: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/444811c3-cbec-e9d5-9a6b-9632eda7962a@linux.intel.com
Diffstat (limited to 'drivers/gpu/drm/bridge')
-rw-r--r--drivers/gpu/drm/bridge/Kconfig1
-rw-r--r--drivers/gpu/drm/bridge/analogix/anx7625.c5
-rw-r--r--drivers/gpu/drm/bridge/nwl-dsi.c6
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi83.c200
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi86.c744
5 files changed, 517 insertions, 439 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 85b673613687..431b6e12a81f 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -303,6 +303,7 @@ config DRM_TI_SN65DSI86
select DRM_PANEL
select DRM_MIPI_DSI
select AUXILIARY_BUS
+ select DRM_DP_AUX_BUS
help
Texas Instruments SN65DSI86 DSI to eDP Bridge driver
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c
index 7519b7a0f29d..a3d82377066b 100644
--- a/drivers/gpu/drm/bridge/analogix/anx7625.c
+++ b/drivers/gpu/drm/bridge/analogix/anx7625.c
@@ -1730,7 +1730,6 @@ static int __maybe_unused anx7625_suspend(struct device *dev)
if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) {
anx7625_runtime_pm_suspend(dev);
disable_irq(ctx->pdata.intp_irq);
- flush_workqueue(ctx->workqueue);
}
return 0;
@@ -1790,7 +1789,8 @@ static int anx7625_i2c_probe(struct i2c_client *client,
platform->pdata.intp_irq = client->irq;
if (platform->pdata.intp_irq) {
INIT_WORK(&platform->work, anx7625_work_func);
- platform->workqueue = create_workqueue("anx7625_work");
+ platform->workqueue = alloc_workqueue("anx7625_work",
+ WQ_FREEZABLE | WQ_MEM_RECLAIM, 1);
if (!platform->workqueue) {
DRM_DEV_ERROR(dev, "fail to create work queue\n");
ret = -ENOMEM;
@@ -1874,6 +1874,7 @@ static const struct of_device_id anx_match_table[] = {
{.compatible = "analogix,anx7625",},
{},
};
+MODULE_DEVICE_TABLE(of, anx_match_table);
static struct i2c_driver anx7625_driver = {
.driver = {
diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c
index 873995f0a741..d7986067eefe 100644
--- a/drivers/gpu/drm/bridge/nwl-dsi.c
+++ b/drivers/gpu/drm/bridge/nwl-dsi.c
@@ -48,12 +48,6 @@ enum transfer_direction {
#define NWL_DSI_ENDPOINT_LCDIF 0
#define NWL_DSI_ENDPOINT_DCSS 1
-struct nwl_dsi_plat_clk_config {
- const char *id;
- struct clk *clk;
- bool present;
-};
-
struct nwl_dsi_transfer {
const struct mipi_dsi_msg *msg;
struct mipi_dsi_packet packet;
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
index 750f2172ef08..a32f70bc68ea 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -137,7 +137,6 @@ enum sn65dsi83_model {
struct sn65dsi83 {
struct drm_bridge bridge;
- struct drm_display_mode mode;
struct device *dev;
struct regmap *regmap;
struct device_node *host_node;
@@ -147,8 +146,6 @@ struct sn65dsi83 {
int dsi_lanes;
bool lvds_dual_link;
bool lvds_dual_link_even_odd_swap;
- bool lvds_format_24bpp;
- bool lvds_format_jeida;
};
static const struct regmap_range sn65dsi83_readable_ranges[] = {
@@ -291,7 +288,8 @@ err_dsi_attach:
return ret;
}
-static void sn65dsi83_pre_enable(struct drm_bridge *bridge)
+static void sn65dsi83_atomic_pre_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
@@ -306,7 +304,8 @@ static void sn65dsi83_pre_enable(struct drm_bridge *bridge)
usleep_range(1000, 1100);
}
-static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx)
+static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx,
+ const struct drm_display_mode *mode)
{
/*
* The encoding of the LVDS_CLK_RANGE is as follows:
@@ -322,7 +321,7 @@ static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx)
* the clock to 25..154 MHz, the range calculation can be simplified
* as follows:
*/
- int mode_clock = ctx->mode.clock;
+ int mode_clock = mode->clock;
if (ctx->lvds_dual_link)
mode_clock /= 2;
@@ -330,7 +329,8 @@ static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx)
return (mode_clock - 12500) / 25000;
}
-static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx)
+static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx,
+ const struct drm_display_mode *mode)
{
/*
* The encoding of the CHA_DSI_CLK_RANGE is as follows:
@@ -346,7 +346,7 @@ static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx)
* DSI_CLK = mode clock * bpp / dsi_data_lanes / 2
* the 2 is there because the bus is DDR.
*/
- return DIV_ROUND_UP(clamp((unsigned int)ctx->mode.clock *
+ return DIV_ROUND_UP(clamp((unsigned int)mode->clock *
mipi_dsi_pixel_format_to_bpp(ctx->dsi->format) /
ctx->dsi_lanes / 2, 40000U, 500000U), 5000U);
}
@@ -364,23 +364,73 @@ static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx)
return dsi_div - 1;
}
-static void sn65dsi83_enable(struct drm_bridge *bridge)
+static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+ struct drm_atomic_state *state = old_bridge_state->base.state;
+ const struct drm_bridge_state *bridge_state;
+ const struct drm_crtc_state *crtc_state;
+ const struct drm_display_mode *mode;
+ struct drm_connector *connector;
+ struct drm_crtc *crtc;
+ bool lvds_format_24bpp;
+ bool lvds_format_jeida;
unsigned int pval;
+ __le16 le16val;
u16 val;
int ret;
+ /* Get the LVDS format from the bridge state. */
+ bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
+
+ switch (bridge_state->output_bus_cfg.format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ lvds_format_24bpp = false;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = true;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ break;
+ default:
+ /*
+ * Some bridges still don't set the correct
+ * LVDS bus pixel format, use SPWG24 default
+ * format until those are fixed.
+ */
+ lvds_format_24bpp = true;
+ lvds_format_jeida = false;
+ dev_warn(ctx->dev,
+ "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
+ bridge_state->output_bus_cfg.format);
+ break;
+ }
+
+ /*
+ * Retrieve the CRTC adjusted mode. This requires a little dance to go
+ * from the bridge to the encoder, to the connector and to the CRTC.
+ */
+ connector = drm_atomic_get_new_connector_for_encoder(state,
+ bridge->encoder);
+ crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+ crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ mode = &crtc_state->adjusted_mode;
+
/* Clear reset, disable PLL */
regmap_write(ctx->regmap, REG_RC_RESET, 0x00);
regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
/* Reference clock derived from DSI link clock. */
regmap_write(ctx->regmap, REG_RC_LVDS_PLL,
- REG_RC_LVDS_PLL_LVDS_CLK_RANGE(sn65dsi83_get_lvds_range(ctx)) |
+ REG_RC_LVDS_PLL_LVDS_CLK_RANGE(sn65dsi83_get_lvds_range(ctx, mode)) |
REG_RC_LVDS_PLL_HS_CLK_SRC_DPHY);
regmap_write(ctx->regmap, REG_DSI_CLK,
- REG_DSI_CLK_CHA_DSI_CLK_RANGE(sn65dsi83_get_dsi_range(ctx)));
+ REG_DSI_CLK_CHA_DSI_CLK_RANGE(sn65dsi83_get_dsi_range(ctx, mode)));
regmap_write(ctx->regmap, REG_RC_DSI_CLK,
REG_RC_DSI_CLK_DSI_CLK_DIVIDER(sn65dsi83_get_dsi_div(ctx)));
@@ -394,20 +444,20 @@ static void sn65dsi83_enable(struct drm_bridge *bridge)
regmap_write(ctx->regmap, REG_DSI_EQ, 0x00);
/* Set up sync signal polarity. */
- val = (ctx->mode.flags & DRM_MODE_FLAG_NHSYNC ?
+ val = (mode->flags & DRM_MODE_FLAG_NHSYNC ?
REG_LVDS_FMT_HS_NEG_POLARITY : 0) |
- (ctx->mode.flags & DRM_MODE_FLAG_NVSYNC ?
+ (mode->flags & DRM_MODE_FLAG_NVSYNC ?
REG_LVDS_FMT_VS_NEG_POLARITY : 0);
/* Set up bits-per-pixel, 18bpp or 24bpp. */
- if (ctx->lvds_format_24bpp) {
+ if (lvds_format_24bpp) {
val |= REG_LVDS_FMT_CHA_24BPP_MODE;
if (ctx->lvds_dual_link)
val |= REG_LVDS_FMT_CHB_24BPP_MODE;
}
/* Set up LVDS format, JEIDA/Format 1 or SPWG/Format 2 */
- if (ctx->lvds_format_jeida) {
+ if (lvds_format_jeida) {
val |= REG_LVDS_FMT_CHA_24BPP_FORMAT1;
if (ctx->lvds_dual_link)
val |= REG_LVDS_FMT_CHB_24BPP_FORMAT1;
@@ -426,29 +476,29 @@ static void sn65dsi83_enable(struct drm_bridge *bridge)
REG_LVDS_LANE_CHB_LVDS_TERM);
regmap_write(ctx->regmap, REG_LVDS_CM, 0x00);
- val = cpu_to_le16(ctx->mode.hdisplay);
+ le16val = cpu_to_le16(mode->hdisplay);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_ACTIVE_LINE_LENGTH_LOW,
- &val, 2);
- val = cpu_to_le16(ctx->mode.vdisplay);
+ &le16val, 2);
+ le16val = cpu_to_le16(mode->vdisplay);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_VERTICAL_DISPLAY_SIZE_LOW,
- &val, 2);
+ &le16val, 2);
/* 32 + 1 pixel clock to ensure proper operation */
- val = cpu_to_le16(32 + 1);
- regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &val, 2);
- val = cpu_to_le16(ctx->mode.hsync_end - ctx->mode.hsync_start);
+ le16val = cpu_to_le16(32 + 1);
+ regmap_bulk_write(ctx->regmap, REG_VID_CHA_SYNC_DELAY_LOW, &le16val, 2);
+ le16val = cpu_to_le16(mode->hsync_end - mode->hsync_start);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_HSYNC_PULSE_WIDTH_LOW,
- &val, 2);
- val = cpu_to_le16(ctx->mode.vsync_end - ctx->mode.vsync_start);
+ &le16val, 2);
+ le16val = cpu_to_le16(mode->vsync_end - mode->vsync_start);
regmap_bulk_write(ctx->regmap, REG_VID_CHA_VSYNC_PULSE_WIDTH_LOW,
- &val, 2);
+ &le16val, 2);
regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_BACK_PORCH,
- ctx->mode.htotal - ctx->mode.hsync_end);
+ mode->htotal - mode->hsync_end);
regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_BACK_PORCH,
- ctx->mode.vtotal - ctx->mode.vsync_end);
+ mode->vtotal - mode->vsync_end);
regmap_write(ctx->regmap, REG_VID_CHA_HORIZONTAL_FRONT_PORCH,
- ctx->mode.hsync_start - ctx->mode.hdisplay);
+ mode->hsync_start - mode->hdisplay);
regmap_write(ctx->regmap, REG_VID_CHA_VERTICAL_FRONT_PORCH,
- ctx->mode.vsync_start - ctx->mode.vdisplay);
+ mode->vsync_start - mode->vdisplay);
regmap_write(ctx->regmap, REG_VID_CHA_TEST_PATTERN, 0x00);
/* Enable PLL */
@@ -472,7 +522,8 @@ static void sn65dsi83_enable(struct drm_bridge *bridge)
regmap_write(ctx->regmap, REG_IRQ_STAT, pval);
}
-static void sn65dsi83_disable(struct drm_bridge *bridge)
+static void sn65dsi83_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
@@ -481,7 +532,8 @@ static void sn65dsi83_disable(struct drm_bridge *bridge)
regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
}
-static void sn65dsi83_post_disable(struct drm_bridge *bridge)
+static void sn65dsi83_atomic_post_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
@@ -503,70 +555,44 @@ sn65dsi83_mode_valid(struct drm_bridge *bridge,
return MODE_OK;
}
-static void sn65dsi83_mode_set(struct drm_bridge *bridge,
- const struct drm_display_mode *mode,
- const struct drm_display_mode *adj)
-{
- struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+#define MAX_INPUT_SEL_FORMATS 1
- ctx->mode = *adj;
-}
-
-static bool sn65dsi83_mode_fixup(struct drm_bridge *bridge,
- const struct drm_display_mode *mode,
- struct drm_display_mode *adj)
+static u32 *
+sn65dsi83_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ u32 output_fmt,
+ unsigned int *num_input_fmts)
{
- struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
- u32 input_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *ddev = encoder->dev;
- struct drm_connector *connector;
+ u32 *input_fmts;
- /* The DSI format is always RGB888_1X24 */
- list_for_each_entry(connector, &ddev->mode_config.connector_list, head) {
- switch (connector->display_info.bus_formats[0]) {
- case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
- ctx->lvds_format_24bpp = false;
- ctx->lvds_format_jeida = true;
- break;
- case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
- ctx->lvds_format_24bpp = true;
- ctx->lvds_format_jeida = true;
- break;
- case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
- ctx->lvds_format_24bpp = true;
- ctx->lvds_format_jeida = false;
- break;
- default:
- /*
- * Some bridges still don't set the correct
- * LVDS bus pixel format, use SPWG24 default
- * format until those are fixed.
- */
- ctx->lvds_format_24bpp = true;
- ctx->lvds_format_jeida = false;
- dev_warn(ctx->dev,
- "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n",
- connector->display_info.bus_formats[0]);
- break;
- }
+ *num_input_fmts = 0;
- drm_display_info_set_bus_formats(&connector->display_info,
- &input_bus_format, 1);
- }
+ input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ /* This is the DSI-end bus format */
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+ *num_input_fmts = 1;
- return true;
+ return input_fmts;
}
static const struct drm_bridge_funcs sn65dsi83_funcs = {
- .attach = sn65dsi83_attach,
- .pre_enable = sn65dsi83_pre_enable,
- .enable = sn65dsi83_enable,
- .disable = sn65dsi83_disable,
- .post_disable = sn65dsi83_post_disable,
- .mode_valid = sn65dsi83_mode_valid,
- .mode_set = sn65dsi83_mode_set,
- .mode_fixup = sn65dsi83_mode_fixup,
+ .attach = sn65dsi83_attach,
+ .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,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_get_input_bus_fmts = sn65dsi83_atomic_get_input_bus_fmts,
};
static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index 45a2969afb2b..9bf889302bcc 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -23,6 +23,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.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>
@@ -116,6 +117,7 @@
* 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.
*
* @dev: Pointer to the top level (i2c) device.
* @regmap: Regmap for accessing i2c.
@@ -124,9 +126,8 @@
* @connector: Our connector.
* @host_node: Remote DSI node.
* @dsi: Our MIPI DSI source.
- * @edid: Detected EDID of eDP panel.
* @refclk: Our reference clock.
- * @panel: Our panel.
+ * @next_bridge: The bridge on the eDP side.
* @enable_gpio: The GPIO we toggle to enable the bridge.
* @supplies: Data for bulk enabling/disabling our regulators.
* @dp_lanes: Count of dp_lanes we're using.
@@ -148,17 +149,17 @@
struct ti_sn65dsi86 {
struct auxiliary_device bridge_aux;
struct auxiliary_device gpio_aux;
+ struct auxiliary_device aux_aux;
struct device *dev;
struct regmap *regmap;
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct drm_connector connector;
- struct edid *edid;
struct device_node *host_node;
struct mipi_dsi_device *dsi;
struct clk *refclk;
- struct drm_panel *panel;
+ struct drm_bridge *next_bridge;
struct gpio_desc *enable_gpio;
struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM];
int dp_lanes;
@@ -393,35 +394,222 @@ static void ti_sn65dsi86_debugfs_init(struct ti_sn65dsi86 *pdata)
debugfs_create_file("status", 0600, debugfs, pdata, &status_fops);
}
-/* Connector funcs */
-static struct ti_sn65dsi86 *
-connector_to_ti_sn65dsi86(struct drm_connector *connector)
+/* -----------------------------------------------------------------------------
+ * Auxiliary Devices (*not* AUX)
+ */
+
+static void ti_sn65dsi86_uninit_aux(void *data)
{
- return container_of(connector, struct ti_sn65dsi86, connector);
+ auxiliary_device_uninit(data);
}
-static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
+static void ti_sn65dsi86_delete_aux(void *data)
{
- struct ti_sn65dsi86 *pdata = connector_to_ti_sn65dsi86(connector);
- struct edid *edid = pdata->edid;
- int num, ret;
+ auxiliary_device_delete(data);
+}
- if (!edid) {
- pm_runtime_get_sync(pdata->dev);
- edid = pdata->edid = drm_get_edid(connector, &pdata->aux.ddc);
- pm_runtime_put_autosuspend(pdata->dev);
+/*
+ * AUX bus docs say that a non-NULL release is mandatory, but it makes no
+ * sense for the model used here where all of the aux devices are allocated
+ * in the single shared structure. We'll use this noop as a workaround.
+ */
+static void ti_sn65dsi86_noop(struct device *dev) {}
+
+static int ti_sn65dsi86_add_aux_device(struct ti_sn65dsi86 *pdata,
+ struct auxiliary_device *aux,
+ const char *name)
+{
+ struct device *dev = pdata->dev;
+ int ret;
+
+ aux->name = name;
+ aux->dev.parent = dev;
+ aux->dev.release = ti_sn65dsi86_noop;
+ device_set_of_node_from_dev(&aux->dev, dev);
+ ret = auxiliary_device_init(aux);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, ti_sn65dsi86_uninit_aux, aux);
+ if (ret)
+ return ret;
+
+ ret = auxiliary_device_add(aux);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, ti_sn65dsi86_delete_aux, aux);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * AUX Adapter
+ */
+
+static struct ti_sn65dsi86 *aux_to_ti_sn65dsi86(struct drm_dp_aux *aux)
+{
+ return container_of(aux, struct ti_sn65dsi86, aux);
+}
+
+static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ struct ti_sn65dsi86 *pdata = aux_to_ti_sn65dsi86(aux);
+ u32 request = msg->request & ~(DP_AUX_I2C_MOT | DP_AUX_I2C_WRITE_STATUS_UPDATE);
+ u32 request_val = AUX_CMD_REQ(msg->request);
+ u8 *buf = msg->buffer;
+ unsigned int len = msg->size;
+ unsigned int val;
+ int ret;
+ u8 addr_len[SN_AUX_LENGTH_REG + 1 - SN_AUX_ADDR_19_16_REG];
+
+ if (len > SN_AUX_MAX_PAYLOAD_BYTES)
+ return -EINVAL;
+
+ pm_runtime_get_sync(pdata->dev);
+ mutex_lock(&pdata->comms_mutex);
+
+ /*
+ * If someone tries to do a DDC over AUX transaction before pre_enable()
+ * on a device without a dedicated reference clock then we just can't
+ * do it. Fail right away. This prevents non-refclk users from reading
+ * the EDID before enabling the panel but such is life.
+ */
+ if (!pdata->comms_enabled) {
+ ret = -EIO;
+ goto exit;
}
- if (edid && drm_edid_is_valid(edid)) {
- ret = drm_connector_update_edid_property(connector, edid);
- if (!ret) {
- num = drm_add_edid_modes(connector, edid);
- if (num)
- return num;
+ switch (request) {
+ case DP_AUX_NATIVE_WRITE:
+ case DP_AUX_I2C_WRITE:
+ case DP_AUX_NATIVE_READ:
+ case DP_AUX_I2C_READ:
+ regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val);
+ /* Assume it's good */
+ msg->reply = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ BUILD_BUG_ON(sizeof(addr_len) != sizeof(__be32));
+ put_unaligned_be32((msg->address & SN_AUX_ADDR_MASK) << 8 | len,
+ addr_len);
+ regmap_bulk_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, addr_len,
+ ARRAY_SIZE(addr_len));
+
+ if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE)
+ regmap_bulk_write(pdata->regmap, SN_AUX_WDATA_REG(0), buf, len);
+
+ /* Clear old status bits before start so we don't get confused */
+ regmap_write(pdata->regmap, SN_AUX_CMD_STATUS_REG,
+ AUX_IRQ_STATUS_NAT_I2C_FAIL |
+ AUX_IRQ_STATUS_AUX_RPLY_TOUT |
+ AUX_IRQ_STATUS_AUX_SHORT);
+
+ regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val | AUX_CMD_SEND);
+
+ /* Zero delay loop because i2c transactions are slow already */
+ ret = regmap_read_poll_timeout(pdata->regmap, SN_AUX_CMD_REG, val,
+ !(val & AUX_CMD_SEND), 0, 50 * 1000);
+ if (ret)
+ goto exit;
+
+ ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val);
+ if (ret)
+ goto exit;
+
+ if (val & AUX_IRQ_STATUS_AUX_RPLY_TOUT) {
+ /*
+ * The hardware tried the message seven times per the DP spec
+ * but it hit a timeout. We ignore defers here because they're
+ * handled in hardware.
+ */
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+
+ if (val & AUX_IRQ_STATUS_AUX_SHORT) {
+ ret = regmap_read(pdata->regmap, SN_AUX_LENGTH_REG, &len);
+ if (ret)
+ goto exit;
+ } else if (val & AUX_IRQ_STATUS_NAT_I2C_FAIL) {
+ switch (request) {
+ case DP_AUX_I2C_WRITE:
+ case DP_AUX_I2C_READ:
+ msg->reply |= DP_AUX_I2C_REPLY_NACK;
+ break;
+ case DP_AUX_NATIVE_READ:
+ case DP_AUX_NATIVE_WRITE:
+ msg->reply |= DP_AUX_NATIVE_REPLY_NACK;
+ break;
}
+ len = 0;
+ goto exit;
}
- return drm_panel_get_modes(pdata->panel, connector);
+ if (request != DP_AUX_NATIVE_WRITE && request != DP_AUX_I2C_WRITE && len != 0)
+ ret = regmap_bulk_read(pdata->regmap, SN_AUX_RDATA_REG(0), buf, len);
+
+exit:
+ mutex_unlock(&pdata->comms_mutex);
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+
+ if (ret)
+ return ret;
+ return len;
+}
+
+static int ti_sn_aux_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+ int ret;
+
+ pdata->aux.name = "ti-sn65dsi86-aux";
+ pdata->aux.dev = &adev->dev;
+ pdata->aux.transfer = ti_sn_aux_transfer;
+ drm_dp_aux_init(&pdata->aux);
+
+ ret = devm_of_dp_aux_populate_ep_devices(&pdata->aux);
+ if (ret)
+ return ret;
+
+ /*
+ * The eDP to MIPI bridge parts don't work until the AUX channel is
+ * setup so we don't add it in the main driver probe, we add it now.
+ */
+ return ti_sn65dsi86_add_aux_device(pdata, &pdata->bridge_aux, "bridge");
+}
+
+static const struct auxiliary_device_id ti_sn_aux_id_table[] = {
+ { .name = "ti_sn65dsi86.aux", },
+ {},
+};
+
+static struct auxiliary_driver ti_sn_aux_driver = {
+ .name = "aux",
+ .probe = ti_sn_aux_probe,
+ .id_table = ti_sn_aux_id_table,
+};
+
+/* -----------------------------------------------------------------------------
+ * DRM Connector Operations
+ */
+
+static struct ti_sn65dsi86 *
+connector_to_ti_sn65dsi86(struct drm_connector *connector)
+{
+ return container_of(connector, struct ti_sn65dsi86, connector);
+}
+
+static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
+{
+ struct ti_sn65dsi86 *pdata = connector_to_ti_sn65dsi86(connector);
+
+ return drm_bridge_get_modes(pdata->next_bridge, connector);
}
static enum drm_mode_status
@@ -448,23 +636,32 @@ static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static struct ti_sn65dsi86 *bridge_to_ti_sn65dsi86(struct drm_bridge *bridge)
+static int ti_sn_bridge_connector_init(struct ti_sn65dsi86 *pdata)
{
- return container_of(bridge, struct ti_sn65dsi86, bridge);
-}
+ int ret;
-static int ti_sn65dsi86_parse_regulators(struct ti_sn65dsi86 *pdata)
-{
- unsigned int i;
- const char * const ti_sn_bridge_supply_names[] = {
- "vcca", "vcc", "vccio", "vpll",
- };
+ ret = drm_connector_init(pdata->bridge.dev, &pdata->connector,
+ &ti_sn_bridge_connector_funcs,
+ DRM_MODE_CONNECTOR_eDP);
+ if (ret) {
+ DRM_ERROR("Failed to initialize connector with drm\n");
+ return ret;
+ }
- for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++)
- pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
+ drm_connector_helper_add(&pdata->connector,
+ &ti_sn_bridge_connector_helper_funcs);
+ drm_connector_attach_encoder(&pdata->connector, pdata->bridge.encoder);
- return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM,
- pdata->supplies);
+ return 0;
+}
+
+/*------------------------------------------------------------------------------
+ * DRM Bridge
+ */
+
+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,
@@ -491,17 +688,9 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
return ret;
}
- ret = drm_connector_init(bridge->dev, &pdata->connector,
- &ti_sn_bridge_connector_funcs,
- DRM_MODE_CONNECTOR_eDP);
- if (ret) {
- DRM_ERROR("Failed to initialize connector with drm\n");
+ ret = ti_sn_bridge_connector_init(pdata);
+ if (ret < 0)
goto err_conn_init;
- }
-
- drm_connector_helper_add(&pdata->connector,
- &ti_sn_bridge_connector_helper_funcs);
- drm_connector_attach_encoder(&pdata->connector, bridge->encoder);
/*
* TODO: ideally finding host resource and dsi dev registration needs
@@ -547,8 +736,16 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
}
pdata->dsi = dsi;
+ /* Attach the next bridge */
+ ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
+ &pdata->bridge, flags);
+ if (ret < 0)
+ goto err_dsi_detach;
+
return 0;
+err_dsi_detach:
+ mipi_dsi_detach(dsi);
err_dsi_attach:
mipi_dsi_device_unregister(dsi);
err_dsi_host:
@@ -567,8 +764,6 @@ static void ti_sn_bridge_disable(struct drm_bridge *bridge)
{
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
- drm_panel_disable(pdata->panel);
-
/* disable video stream */
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, 0);
/* semi auto link training mode OFF */
@@ -633,9 +828,9 @@ static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn65dsi86 *pdata)
return i;
}
-static void ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata,
- bool rate_valid[])
+static unsigned int ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata)
{
+ unsigned int valid_rates = 0;
unsigned int rate_per_200khz;
unsigned int rate_mhz;
u8 dpcd_val;
@@ -675,13 +870,13 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata,
j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
j++) {
if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
- rate_valid[j] = true;
+ valid_rates |= BIT(j);
}
}
for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
- if (rate_valid[i])
- return;
+ if (valid_rates & BIT(i))
+ return valid_rates;
}
DRM_DEV_ERROR(pdata->dev,
"No matching eDP rates in table; falling back\n");
@@ -703,15 +898,17 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata,
(int)dpcd_val);
fallthrough;
case DP_LINK_BW_5_4:
- rate_valid[7] = 1;
+ valid_rates |= BIT(7);
fallthrough;
case DP_LINK_BW_2_7:
- rate_valid[4] = 1;
+ valid_rates |= BIT(4);
fallthrough;
case DP_LINK_BW_1_62:
- rate_valid[1] = 1;
+ valid_rates |= BIT(1);
break;
}
+
+ return valid_rates;
}
static void ti_sn_bridge_set_video_timings(struct ti_sn65dsi86 *pdata)
@@ -829,8 +1026,8 @@ exit:
static void ti_sn_bridge_enable(struct drm_bridge *bridge)
{
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
- bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { };
const char *last_err_str = "No supported DP rate";
+ unsigned int valid_rates;
int dp_rate_idx;
unsigned int val;
int ret = -EINVAL;
@@ -869,13 +1066,13 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
val);
- ti_sn_bridge_read_valid_rates(pdata, rate_valid);
+ valid_rates = ti_sn_bridge_read_valid_rates(pdata);
/* Train until we run out of rates */
for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata);
dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
dp_rate_idx++) {
- if (!rate_valid[dp_rate_idx])
+ if (!(valid_rates & BIT(dp_rate_idx)))
continue;
ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
@@ -893,8 +1090,6 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
/* enable video stream */
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE,
VSTREAM_ENABLE);
-
- drm_panel_enable(pdata->panel);
}
static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge)
@@ -905,16 +1100,12 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge)
if (!pdata->refclk)
ti_sn65dsi86_enable_comms(pdata);
-
- drm_panel_prepare(pdata->panel);
}
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge)
{
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
- drm_panel_unprepare(pdata->panel);
-
if (!pdata->refclk)
ti_sn65dsi86_disable_comms(pdata);
@@ -930,137 +1121,135 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
.post_disable = ti_sn_bridge_post_disable,
};
-static struct ti_sn65dsi86 *aux_to_ti_sn65dsi86(struct drm_dp_aux *aux)
-{
- return container_of(aux, struct ti_sn65dsi86, aux);
-}
-
-static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux,
- struct drm_dp_aux_msg *msg)
+static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata,
+ struct device_node *np)
{
- struct ti_sn65dsi86 *pdata = aux_to_ti_sn65dsi86(aux);
- u32 request = msg->request & ~(DP_AUX_I2C_MOT | DP_AUX_I2C_WRITE_STATUS_UPDATE);
- u32 request_val = AUX_CMD_REQ(msg->request);
- u8 *buf = msg->buffer;
- unsigned int len = msg->size;
- unsigned int val;
- int ret;
- u8 addr_len[SN_AUX_LENGTH_REG + 1 - SN_AUX_ADDR_19_16_REG];
-
- if (len > SN_AUX_MAX_PAYLOAD_BYTES)
- return -EINVAL;
-
- pm_runtime_get_sync(pdata->dev);
- mutex_lock(&pdata->comms_mutex);
+ u32 lane_assignments[SN_MAX_DP_LANES] = { 0, 1, 2, 3 };
+ u32 lane_polarities[SN_MAX_DP_LANES] = { };
+ struct device_node *endpoint;
+ u8 ln_assign = 0;
+ u8 ln_polrs = 0;
+ int dp_lanes;
+ int i;
/*
- * If someone tries to do a DDC over AUX transaction before pre_enable()
- * on a device without a dedicated reference clock then we just can't
- * do it. Fail right away. This prevents non-refclk users from reading
- * the EDID before enabling the panel but such is life.
+ * Read config from the device tree about lane remapping and lane
+ * polarities. These are optional and we assume identity map and
+ * normal polarity if nothing is specified. It's OK to specify just
+ * data-lanes but not lane-polarities but not vice versa.
+ *
+ * Error checking is light (we just make sure we don't crash or
+ * buffer overrun) and we assume dts is well formed and specifying
+ * mappings that the hardware supports.
*/
- if (!pdata->comms_enabled) {
- ret = -EIO;
- goto exit;
+ endpoint = of_graph_get_endpoint_by_regs(np, 1, -1);
+ dp_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+ if (dp_lanes > 0 && dp_lanes <= SN_MAX_DP_LANES) {
+ of_property_read_u32_array(endpoint, "data-lanes",
+ lane_assignments, dp_lanes);
+ of_property_read_u32_array(endpoint, "lane-polarities",
+ lane_polarities, dp_lanes);
+ } else {
+ dp_lanes = SN_MAX_DP_LANES;
}
+ of_node_put(endpoint);
- switch (request) {
- case DP_AUX_NATIVE_WRITE:
- case DP_AUX_I2C_WRITE:
- case DP_AUX_NATIVE_READ:
- case DP_AUX_I2C_READ:
- regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val);
- /* Assume it's good */
- msg->reply = 0;
- break;
- default:
- ret = -EINVAL;
- goto exit;
+ /*
+ * Convert into register format. Loop over all lanes even if
+ * data-lanes had fewer elements so that we nicely initialize
+ * the LN_ASSIGN register.
+ */
+ for (i = SN_MAX_DP_LANES - 1; i >= 0; i--) {
+ ln_assign = ln_assign << LN_ASSIGN_WIDTH | lane_assignments[i];
+ ln_polrs = ln_polrs << 1 | lane_polarities[i];
}
- BUILD_BUG_ON(sizeof(addr_len) != sizeof(__be32));
- put_unaligned_be32((msg->address & SN_AUX_ADDR_MASK) << 8 | len,
- addr_len);
- regmap_bulk_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, addr_len,
- ARRAY_SIZE(addr_len));
+ /* Stash in our struct for when we power on */
+ pdata->dp_lanes = dp_lanes;
+ pdata->ln_assign = ln_assign;
+ pdata->ln_polrs = ln_polrs;
+}
- if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE)
- regmap_bulk_write(pdata->regmap, SN_AUX_WDATA_REG(0), buf, len);
+static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)
+{
+ struct device_node *np = pdata->dev->of_node;
- /* Clear old status bits before start so we don't get confused */
- regmap_write(pdata->regmap, SN_AUX_CMD_STATUS_REG,
- AUX_IRQ_STATUS_NAT_I2C_FAIL |
- AUX_IRQ_STATUS_AUX_RPLY_TOUT |
- AUX_IRQ_STATUS_AUX_SHORT);
+ pdata->host_node = of_graph_get_remote_node(np, 0, 0);
- regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val | AUX_CMD_SEND);
+ if (!pdata->host_node) {
+ DRM_ERROR("remote dsi host node not found\n");
+ return -ENODEV;
+ }
- /* Zero delay loop because i2c transactions are slow already */
- ret = regmap_read_poll_timeout(pdata->regmap, SN_AUX_CMD_REG, val,
- !(val & AUX_CMD_SEND), 0, 50 * 1000);
- if (ret)
- goto exit;
+ return 0;
+}
- ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val);
- if (ret)
- goto exit;
+static int ti_sn_bridge_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+ struct device_node *np = pdata->dev->of_node;
+ struct drm_panel *panel;
+ int ret;
- if (val & AUX_IRQ_STATUS_AUX_RPLY_TOUT) {
- /*
- * The hardware tried the message seven times per the DP spec
- * but it hit a timeout. We ignore defers here because they're
- * handled in hardware.
- */
- ret = -ETIMEDOUT;
- goto exit;
- }
+ ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
+ if (ret)
+ return dev_err_probe(&adev->dev, ret,
+ "could not find any panel node\n");
- if (val & AUX_IRQ_STATUS_AUX_SHORT) {
- ret = regmap_read(pdata->regmap, SN_AUX_LENGTH_REG, &len);
- if (ret)
- goto exit;
- } else if (val & AUX_IRQ_STATUS_NAT_I2C_FAIL) {
- switch (request) {
- case DP_AUX_I2C_WRITE:
- case DP_AUX_I2C_READ:
- msg->reply |= DP_AUX_I2C_REPLY_NACK;
- break;
- case DP_AUX_NATIVE_READ:
- case DP_AUX_NATIVE_WRITE:
- msg->reply |= DP_AUX_NATIVE_REPLY_NACK;
- break;
- }
- len = 0;
- goto exit;
+ pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev, panel);
+ if (IS_ERR(pdata->next_bridge)) {
+ DRM_ERROR("failed to create panel bridge\n");
+ return PTR_ERR(pdata->next_bridge);
}
- if (request != DP_AUX_NATIVE_WRITE && request != DP_AUX_I2C_WRITE && len != 0)
- ret = regmap_bulk_read(pdata->regmap, SN_AUX_RDATA_REG(0), buf, len);
-
-exit:
- mutex_unlock(&pdata->comms_mutex);
- pm_runtime_mark_last_busy(pdata->dev);
- pm_runtime_put_autosuspend(pdata->dev);
+ ti_sn_bridge_parse_lanes(pdata, np);
+ ret = ti_sn_bridge_parse_dsi_host(pdata);
if (ret)
return ret;
- return len;
+
+ pdata->bridge.funcs = &ti_sn_bridge_funcs;
+ pdata->bridge.of_node = np;
+
+ drm_bridge_add(&pdata->bridge);
+
+ return 0;
}
-static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)
+static void ti_sn_bridge_remove(struct auxiliary_device *adev)
{
- struct device_node *np = pdata->dev->of_node;
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
- pdata->host_node = of_graph_get_remote_node(np, 0, 0);
+ if (!pdata)
+ return;
- if (!pdata->host_node) {
- DRM_ERROR("remote dsi host node not found\n");
- return -ENODEV;
+ if (pdata->dsi) {
+ mipi_dsi_detach(pdata->dsi);
+ mipi_dsi_device_unregister(pdata->dsi);
}
- return 0;
+ drm_bridge_remove(&pdata->bridge);
+
+ of_node_put(pdata->host_node);
}
+static const struct auxiliary_device_id ti_sn_bridge_id_table[] = {
+ { .name = "ti_sn65dsi86.bridge", },
+ {},
+};
+
+static struct auxiliary_driver ti_sn_bridge_driver = {
+ .name = "bridge",
+ .probe = ti_sn_bridge_probe,
+ .remove = ti_sn_bridge_remove,
+ .id_table = ti_sn_bridge_id_table,
+};
+
+/* -----------------------------------------------------------------------------
+ * GPIO Controller
+ */
+
#if defined(CONFIG_OF_GPIO)
static int tn_sn_bridge_of_xlate(struct gpio_chip *chip,
@@ -1265,171 +1454,27 @@ static inline void ti_sn_gpio_unregister(void) {}
#endif
-static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata,
- struct device_node *np)
-{
- u32 lane_assignments[SN_MAX_DP_LANES] = { 0, 1, 2, 3 };
- u32 lane_polarities[SN_MAX_DP_LANES] = { };
- struct device_node *endpoint;
- u8 ln_assign = 0;
- u8 ln_polrs = 0;
- int dp_lanes;
- int i;
-
- /*
- * Read config from the device tree about lane remapping and lane
- * polarities. These are optional and we assume identity map and
- * normal polarity if nothing is specified. It's OK to specify just
- * data-lanes but not lane-polarities but not vice versa.
- *
- * Error checking is light (we just make sure we don't crash or
- * buffer overrun) and we assume dts is well formed and specifying
- * mappings that the hardware supports.
- */
- endpoint = of_graph_get_endpoint_by_regs(np, 1, -1);
- dp_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
- if (dp_lanes > 0 && dp_lanes <= SN_MAX_DP_LANES) {
- of_property_read_u32_array(endpoint, "data-lanes",
- lane_assignments, dp_lanes);
- of_property_read_u32_array(endpoint, "lane-polarities",
- lane_polarities, dp_lanes);
- } else {
- dp_lanes = SN_MAX_DP_LANES;
- }
- of_node_put(endpoint);
-
- /*
- * Convert into register format. Loop over all lanes even if
- * data-lanes had fewer elements so that we nicely initialize
- * the LN_ASSIGN register.
- */
- for (i = SN_MAX_DP_LANES - 1; i >= 0; i--) {
- ln_assign = ln_assign << LN_ASSIGN_WIDTH | lane_assignments[i];
- ln_polrs = ln_polrs << 1 | lane_polarities[i];
- }
-
- /* Stash in our struct for when we power on */
- pdata->dp_lanes = dp_lanes;
- pdata->ln_assign = ln_assign;
- pdata->ln_polrs = ln_polrs;
-}
-
-static int ti_sn_bridge_probe(struct auxiliary_device *adev,
- const struct auxiliary_device_id *id)
-{
- struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
- struct device_node *np = pdata->dev->of_node;
- int ret;
-
- ret = drm_of_find_panel_or_bridge(np, 1, 0, &pdata->panel, NULL);
- if (ret) {
- DRM_ERROR("could not find any panel node\n");
- return ret;
- }
-
- ti_sn_bridge_parse_lanes(pdata, np);
-
- ret = ti_sn_bridge_parse_dsi_host(pdata);
- if (ret)
- return ret;
-
- pdata->aux.name = "ti-sn65dsi86-aux";
- pdata->aux.dev = pdata->dev;
- pdata->aux.transfer = ti_sn_aux_transfer;
- drm_dp_aux_init(&pdata->aux);
-
- pdata->bridge.funcs = &ti_sn_bridge_funcs;
- pdata->bridge.of_node = np;
-
- drm_bridge_add(&pdata->bridge);
-
- return 0;
-}
-
-static void ti_sn_bridge_remove(struct auxiliary_device *adev)
-{
- struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
-
- if (!pdata)
- return;
-
- if (pdata->dsi) {
- mipi_dsi_detach(pdata->dsi);
- mipi_dsi_device_unregister(pdata->dsi);
- }
-
- kfree(pdata->edid);
-
- drm_bridge_remove(&pdata->bridge);
-
- of_node_put(pdata->host_node);
-}
-
-static const struct auxiliary_device_id ti_sn_bridge_id_table[] = {
- { .name = "ti_sn65dsi86.bridge", },
- {},
-};
-
-static struct auxiliary_driver ti_sn_bridge_driver = {
- .name = "bridge",
- .probe = ti_sn_bridge_probe,
- .remove = ti_sn_bridge_remove,
- .id_table = ti_sn_bridge_id_table,
-};
+/* -----------------------------------------------------------------------------
+ * Probe & Remove
+ */
static void ti_sn65dsi86_runtime_disable(void *data)
{
pm_runtime_disable(data);
}
-static void ti_sn65dsi86_uninit_aux(void *data)
-{
- auxiliary_device_uninit(data);
-}
-
-static void ti_sn65dsi86_delete_aux(void *data)
-{
- auxiliary_device_delete(data);
-}
-
-/*
- * AUX bus docs say that a non-NULL release is mandatory, but it makes no
- * sense for the model used here where all of the aux devices are allocated
- * in the single shared structure. We'll use this noop as a workaround.
- */
-static void ti_sn65dsi86_noop(struct device *dev) {}
-
-static int ti_sn65dsi86_add_aux_device(struct ti_sn65dsi86 *pdata,
- struct auxiliary_device *aux,
- const char *name)
+static int ti_sn65dsi86_parse_regulators(struct ti_sn65dsi86 *pdata)
{
- struct device *dev = pdata->dev;
- int ret;
-
- /*
- * NOTE: It would be nice to set the "of_node" of our children to be
- * the same "of_node"" that the top-level component has. That doesn't
- * work, though, since pinctrl will try (and fail) to reserve the
- * pins again. Until that gets sorted out the children will just need
- * to look at the of_node of the main device.
- */
-
- aux->name = name;
- aux->dev.parent = dev;
- aux->dev.release = ti_sn65dsi86_noop;
- ret = auxiliary_device_init(aux);
- if (ret)
- return ret;
- ret = devm_add_action_or_reset(dev, ti_sn65dsi86_uninit_aux, aux);
- if (ret)
- return ret;
+ unsigned int i;
+ const char * const ti_sn_bridge_supply_names[] = {
+ "vcca", "vcc", "vccio", "vpll",
+ };
- ret = auxiliary_device_add(aux);
- if (ret)
- return ret;
- ret = devm_add_action_or_reset(dev, ti_sn65dsi86_delete_aux, aux);
+ for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++)
+ pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
- return ret;
+ return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM,
+ pdata->supplies);
}
static int ti_sn65dsi86_probe(struct i2c_client *client,
@@ -1454,27 +1499,24 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
pdata->regmap = devm_regmap_init_i2c(client,
&ti_sn65dsi86_regmap_config);
- if (IS_ERR(pdata->regmap)) {
- DRM_ERROR("regmap i2c init failed\n");
- return PTR_ERR(pdata->regmap);
- }
+ if (IS_ERR(pdata->regmap))
+ return dev_err_probe(dev, PTR_ERR(pdata->regmap),
+ "regmap i2c init failed\n");
- pdata->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
- if (IS_ERR(pdata->enable_gpio)) {
- DRM_ERROR("failed to get enable gpio from DT\n");
- ret = PTR_ERR(pdata->enable_gpio);
- return ret;
- }
+ pdata->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(pdata->enable_gpio))
+ return dev_err_probe(dev, PTR_ERR(pdata->enable_gpio),
+ "failed to get enable gpio from DT\n");
ret = ti_sn65dsi86_parse_regulators(pdata);
- if (ret) {
- DRM_ERROR("failed to parse regulators\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to parse regulators\n");
pdata->refclk = devm_clk_get_optional(dev, "refclk");
if (IS_ERR(pdata->refclk))
- return PTR_ERR(pdata->refclk);
+ return dev_err_probe(dev, PTR_ERR(pdata->refclk),
+ "failed to get reference clock\n");
pm_runtime_enable(dev);
ret = devm_add_action_or_reset(dev, ti_sn65dsi86_runtime_disable, dev);
@@ -1490,10 +1532,11 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
* motiviation here is to solve the chicken-and-egg problem of probe
* 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. There will soon be other devices (DDC I2C bus, PWM)
- * that 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.
+ * 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.
*/
if (IS_ENABLED(CONFIG_OF_GPIO)) {
@@ -1502,7 +1545,13 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
return ret;
}
- return ti_sn65dsi86_add_aux_device(pdata, &pdata->bridge_aux, "bridge");
+ /*
+ * 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
+ * AUX channel is there and this is a very simple solution to the
+ * dependency problem.
+ */
+ return ti_sn65dsi86_add_aux_device(pdata, &pdata->aux_aux, "aux");
}
static struct i2c_device_id ti_sn65dsi86_id[] = {
@@ -1539,12 +1588,18 @@ static int __init ti_sn65dsi86_init(void)
if (ret)
goto err_main_was_registered;
- ret = auxiliary_driver_register(&ti_sn_bridge_driver);
+ ret = auxiliary_driver_register(&ti_sn_aux_driver);
if (ret)
goto err_gpio_was_registered;
+ ret = auxiliary_driver_register(&ti_sn_bridge_driver);
+ if (ret)
+ goto err_aux_was_registered;
+
return 0;
+err_aux_was_registered:
+ auxiliary_driver_unregister(&ti_sn_aux_driver);
err_gpio_was_registered:
ti_sn_gpio_unregister();
err_main_was_registered:
@@ -1557,6 +1612,7 @@ module_init(ti_sn65dsi86_init);
static void __exit ti_sn65dsi86_exit(void)
{
auxiliary_driver_unregister(&ti_sn_bridge_driver);
+ auxiliary_driver_unregister(&ti_sn_aux_driver);
ti_sn_gpio_unregister();
i2c_del_driver(&ti_sn65dsi86_driver);
}