diff options
Diffstat (limited to 'drivers/gpu/drm/bridge/nwl-dsi.c')
| -rw-r--r-- | drivers/gpu/drm/bridge/nwl-dsi.c | 231 |
1 files changed, 122 insertions, 109 deletions
diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c index b14d725bf609..2f7429b24fc2 100644 --- a/drivers/gpu/drm/bridge/nwl-dsi.c +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -7,24 +7,26 @@ */ #include <linux/bitfield.h> +#include <linux/bits.h> #include <linux/clk.h> #include <linux/irq.h> #include <linux/math64.h> #include <linux/mfd/syscon.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/mux/consumer.h> #include <linux/of.h> -#include <linux/of_platform.h> #include <linux/phy/phy.h> +#include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/reset.h> #include <linux/sys_soc.h> #include <linux/time64.h> +#include <drm/drm_atomic_state_helper.h> #include <drm/drm_bridge.h> #include <drm/drm_mipi_dsi.h> #include <drm/drm_of.h> -#include <drm/drm_panel.h> #include <drm/drm_print.h> #include <video/mipi_display.h> @@ -47,12 +49,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; @@ -70,7 +66,6 @@ struct nwl_dsi_transfer { struct nwl_dsi { struct drm_bridge bridge; struct mipi_dsi_host dsi_host; - struct drm_bridge *panel_bridge; struct device *dev; struct phy *phy; union phy_configure_opts phy_cfg; @@ -195,18 +190,15 @@ static u32 ps2bc(struct nwl_dsi *dsi, unsigned long long ps) u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); return DIV64_U64_ROUND_UP(ps * dsi->mode.clock * bpp, - dsi->lanes * 8 * NSEC_PER_SEC); + dsi->lanes * 8ULL * NSEC_PER_SEC); } /* * ui2bc - UI time periods to byte clock cycles */ -static u32 ui2bc(struct nwl_dsi *dsi, unsigned long long ui) +static u32 ui2bc(unsigned int ui) { - u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); - - return DIV64_U64_ROUND_UP(ui * dsi->lanes, - dsi->mode.clock * 1000 * bpp); + return DIV_ROUND_UP(ui, BITS_PER_BYTE); } /* @@ -237,12 +229,12 @@ static int nwl_dsi_config_host(struct nwl_dsi *dsi) } /* values in byte clock cycles */ - cycles = ui2bc(dsi, cfg->clk_pre); + cycles = ui2bc(cfg->clk_pre); DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_pre: 0x%x\n", cycles); nwl_dsi_write(dsi, NWL_DSI_CFG_T_PRE, cycles); cycles = ps2bc(dsi, cfg->lpx + cfg->clk_prepare + cfg->clk_zero); DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_tx_gap (pre): 0x%x\n", cycles); - cycles += ui2bc(dsi, cfg->clk_pre); + cycles += ui2bc(cfg->clk_pre); DRM_DEV_DEBUG_DRIVER(dsi->dev, "cfg_t_post: 0x%x\n", cycles); nwl_dsi_write(dsi, NWL_DSI_CFG_T_POST, cycles); cycles = ps2bc(dsi, cfg->hs_exit); @@ -297,13 +289,13 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi) nwl_dsi_write(dsi, NWL_DSI_INTERFACE_COLOR_CODING, NWL_DSI_DPI_24_BIT); nwl_dsi_write(dsi, NWL_DSI_PIXEL_FORMAT, color_format); - /* - * Adjusting input polarity based on the video mode results in - * a black screen so always pick active low: - */ nwl_dsi_write(dsi, NWL_DSI_VSYNC_POLARITY, + dsi->mode.flags & DRM_MODE_FLAG_PVSYNC ? + NWL_DSI_VSYNC_POLARITY_ACTIVE_HIGH : NWL_DSI_VSYNC_POLARITY_ACTIVE_LOW); nwl_dsi_write(dsi, NWL_DSI_HSYNC_POLARITY, + dsi->mode.flags & DRM_MODE_FLAG_PHSYNC ? + NWL_DSI_HSYNC_POLARITY_ACTIVE_HIGH : NWL_DSI_HSYNC_POLARITY_ACTIVE_LOW); burst_mode = (dsi->dsi_mode_flags & MIPI_DSI_MODE_VIDEO_BURST) && @@ -340,17 +332,13 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi) static int nwl_dsi_init_interrupts(struct nwl_dsi *dsi) { - u32 irq_enable; - - nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, 0xffffffff); - nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7); - - irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK | - NWL_DSI_RX_PKT_HDR_RCVD_MASK | - NWL_DSI_TX_FIFO_OVFLW_MASK | - NWL_DSI_HS_TX_TIMEOUT_MASK); + u32 irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK | + NWL_DSI_RX_PKT_HDR_RCVD_MASK | + NWL_DSI_TX_FIFO_OVFLW_MASK | + NWL_DSI_HS_TX_TIMEOUT_MASK); nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable); + nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK2, 0x7); return nwl_dsi_clear_error(dsi); } @@ -409,7 +397,6 @@ static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status) switch (data_type) { case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: - fallthrough; case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: if (xfer->msg->rx_len > 1) { /* read second byte */ @@ -418,7 +405,6 @@ static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status) } fallthrough; case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: - fallthrough; case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: if (xfer->msg->rx_len > 0) { /* read first byte */ @@ -663,7 +649,7 @@ static irqreturn_t nwl_dsi_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static int nwl_dsi_enable(struct nwl_dsi *dsi) +static int nwl_dsi_mode_set(struct nwl_dsi *dsi) { struct device *dev = dsi->dev; union phy_configure_opts *phy_cfg = &dsi->phy_cfg; @@ -680,6 +666,12 @@ static int nwl_dsi_enable(struct nwl_dsi *dsi) return ret; } + ret = phy_set_mode(dsi->phy, PHY_MODE_MIPI_DPHY); + if (ret < 0) { + DRM_DEV_ERROR(dev, "Failed to set DSI phy mode: %d\n", ret); + goto uninit_phy; + } + ret = phy_configure(dsi->phy, phy_cfg); if (ret < 0) { DRM_DEV_ERROR(dev, "Failed to configure DSI phy: %d\n", ret); @@ -744,7 +736,8 @@ static int nwl_dsi_disable(struct nwl_dsi *dsi) return 0; } -static void nwl_dsi_bridge_disable(struct drm_bridge *bridge) +static void nwl_dsi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_state *state) { struct nwl_dsi *dsi = bridge_to_dsi(bridge); int ret; @@ -805,19 +798,9 @@ static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi, return 0; } -static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - /* At least LCDIF + NWL needs active high sync */ - adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); - adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); - - return true; -} - static enum drm_mode_status nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, const struct drm_display_mode *mode) { struct nwl_dsi *dsi = bridge_to_dsi(bridge); @@ -832,6 +815,29 @@ nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } +static int nwl_dsi_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; + + /* At least LCDIF + NWL needs active high sync */ + adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC); + adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC); + + /* + * Do a full modeset if crtc_state->active is changed to be true. + * This ensures our ->mode_set() is called to get the DSI controller + * and the PHY ready to send DCS commands, when only the connector's + * DPMS is brought out of "Off" status. + */ + if (crtc_state->active_changed && crtc_state->active) + crtc_state->mode_changed = true; + + return 0; +} + static void nwl_dsi_bridge_mode_set(struct drm_bridge *bridge, const struct drm_display_mode *mode, @@ -847,58 +853,52 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge, if (ret < 0) return; - /* - * If hs clock is unchanged, we're all good - all parameters are - * derived from it atm. - */ - if (new_cfg.mipi_dphy.hs_clk_rate == dsi->phy_cfg.mipi_dphy.hs_clk_rate) - return; - phy_ref_rate = clk_get_rate(dsi->phy_ref_clk); DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate); /* Save the new desired phy config */ memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg)); - memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode)); + drm_mode_copy(&dsi->mode, adjusted_mode); drm_mode_debug_printmodeline(adjusted_mode); -} -static void nwl_dsi_bridge_pre_enable(struct drm_bridge *bridge) -{ - struct nwl_dsi *dsi = bridge_to_dsi(bridge); - int ret; - - pm_runtime_get_sync(dsi->dev); + if (pm_runtime_resume_and_get(dev) < 0) + return; if (clk_prepare_enable(dsi->lcdif_clk) < 0) - return; + goto runtime_put; if (clk_prepare_enable(dsi->core_clk) < 0) - return; + goto runtime_put; /* Step 1 from DSI reset-out instructions */ ret = reset_control_deassert(dsi->rst_pclk); if (ret < 0) { - DRM_DEV_ERROR(dsi->dev, "Failed to deassert PCLK: %d\n", ret); - return; + DRM_DEV_ERROR(dev, "Failed to deassert PCLK: %d\n", ret); + goto runtime_put; } /* Step 2 from DSI reset-out instructions */ - nwl_dsi_enable(dsi); + nwl_dsi_mode_set(dsi); /* Step 3 from DSI reset-out instructions */ ret = reset_control_deassert(dsi->rst_esc); if (ret < 0) { - DRM_DEV_ERROR(dsi->dev, "Failed to deassert ESC: %d\n", ret); - return; + DRM_DEV_ERROR(dev, "Failed to deassert ESC: %d\n", ret); + goto runtime_put; } ret = reset_control_deassert(dsi->rst_byte); if (ret < 0) { - DRM_DEV_ERROR(dsi->dev, "Failed to deassert BYTE: %d\n", ret); - return; + DRM_DEV_ERROR(dev, "Failed to deassert BYTE: %d\n", ret); + goto runtime_put; } + + return; + +runtime_put: + pm_runtime_put_sync(dev); } -static void nwl_dsi_bridge_enable(struct drm_bridge *bridge) +static void nwl_dsi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_state *state) { struct nwl_dsi *dsi = bridge_to_dsi(bridge); int ret; @@ -910,52 +910,64 @@ static void nwl_dsi_bridge_enable(struct drm_bridge *bridge) } static int nwl_dsi_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, enum drm_bridge_attach_flags flags) { struct nwl_dsi *dsi = bridge_to_dsi(bridge); struct drm_bridge *panel_bridge; - struct drm_panel *panel; - int ret; - if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { - DRM_ERROR("Fix bridge driver to make connector optional!"); - return -EINVAL; - } + panel_bridge = devm_drm_of_get_bridge(dsi->dev, dsi->dev->of_node, 1, 0); + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); - ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel, - &panel_bridge); - if (ret) - return ret; + return drm_bridge_attach(encoder, panel_bridge, bridge, flags); +} - if (panel) { - panel_bridge = drm_panel_bridge_add(panel); - if (IS_ERR(panel_bridge)) - return PTR_ERR(panel_bridge); - } - dsi->panel_bridge = panel_bridge; +static u32 *nwl_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts, input_fmt; - if (!dsi->panel_bridge) - return -EPROBE_DEFER; + *num_input_fmts = 0; - return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge, - flags); -} + switch (output_fmt) { + /* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */ + case MEDIA_BUS_FMT_FIXED: + input_fmt = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RGB565_1X16: + input_fmt = output_fmt; + break; + default: + return NULL; + } -static void nwl_dsi_bridge_detach(struct drm_bridge *bridge) -{ struct nwl_dsi *dsi = bridge_to_dsi(bridge); + input_fmts = kcalloc(1, sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + input_fmts[0] = input_fmt; + *num_input_fmts = 1; - drm_of_panel_bridge_remove(dsi->dev->of_node, 1, 0); + return input_fmts; } static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = { - .pre_enable = nwl_dsi_bridge_pre_enable, - .enable = nwl_dsi_bridge_enable, - .disable = nwl_dsi_bridge_disable, - .mode_fixup = nwl_dsi_bridge_mode_fixup, - .mode_set = nwl_dsi_bridge_mode_set, - .mode_valid = nwl_dsi_bridge_mode_valid, - .attach = nwl_dsi_bridge_attach, - .detach = nwl_dsi_bridge_detach, + .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_check = nwl_dsi_bridge_atomic_check, + .atomic_enable = nwl_dsi_bridge_atomic_enable, + .atomic_disable = nwl_dsi_bridge_atomic_disable, + .atomic_get_input_bus_fmts = nwl_bridge_atomic_get_input_bus_fmts, + .mode_set = nwl_dsi_bridge_mode_set, + .mode_valid = nwl_dsi_bridge_mode_valid, + .attach = nwl_dsi_bridge_attach, }; static int nwl_dsi_parse_dt(struct nwl_dsi *dsi) @@ -1127,7 +1139,7 @@ MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids); static const struct soc_device_attribute nwl_dsi_quirks_match[] = { { .soc_id = "i.MX8MQ", .revision = "2.0", .data = (void *)E11418_HS_MODE_QUIRK }, - { /* sentinel. */ }, + { /* sentinel. */ } }; static int nwl_dsi_probe(struct platform_device *pdev) @@ -1137,9 +1149,10 @@ static int nwl_dsi_probe(struct platform_device *pdev) struct nwl_dsi *dsi; int ret; - dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); - if (!dsi) - return -ENOMEM; + dsi = devm_drm_bridge_alloc(dev, struct nwl_dsi, bridge, + &nwl_dsi_bridge_funcs); + if (IS_ERR(dsi)) + return PTR_ERR(dsi); dsi->dev = dev; @@ -1168,15 +1181,16 @@ static int nwl_dsi_probe(struct platform_device *pdev) dsi->quirks = (uintptr_t)attr->data; dsi->bridge.driver_private = dsi; - dsi->bridge.funcs = &nwl_dsi_bridge_funcs; dsi->bridge.of_node = dev->of_node; dsi->bridge.timings = &nwl_dsi_timings; + dsi->bridge.type = DRM_MODE_CONNECTOR_DSI; dev_set_drvdata(dev, dsi); pm_runtime_enable(dev); ret = nwl_dsi_select_input(dsi); if (ret < 0) { + pm_runtime_disable(dev); mipi_dsi_host_unregister(&dsi->dsi_host); return ret; } @@ -1185,7 +1199,7 @@ static int nwl_dsi_probe(struct platform_device *pdev) return 0; } -static int nwl_dsi_remove(struct platform_device *pdev) +static void nwl_dsi_remove(struct platform_device *pdev) { struct nwl_dsi *dsi = platform_get_drvdata(pdev); @@ -1193,7 +1207,6 @@ static int nwl_dsi_remove(struct platform_device *pdev) mipi_dsi_host_unregister(&dsi->dsi_host); drm_bridge_remove(&dsi->bridge); pm_runtime_disable(&pdev->dev); - return 0; } static struct platform_driver nwl_dsi_driver = { |
