diff options
Diffstat (limited to 'drivers/gpu/drm/bridge')
44 files changed, 3977 insertions, 401 deletions
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 307b135da2f6..57946d80b02d 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -78,6 +78,7 @@ config DRM_DISPLAY_CONNECTOR config DRM_FSL_LDB tristate "Freescale i.MX8MP LDB bridge" depends on OF + depends on ARCH_MXC || COMPILE_TEST select DRM_KMS_HELPER select DRM_PANEL_BRIDGE help @@ -93,6 +94,8 @@ config DRM_ITE_IT6505 select DRM_KMS_HELPER select DRM_DP_HELPER select EXTCON + select CRYPTO + select CRYPTO_HASH help ITE IT6505 DisplayPort bridge chip driver. @@ -321,6 +324,22 @@ config DRM_TOSHIBA_TC358775 help Toshiba TC358775 DSI/LVDS bridge chip driver. +config DRM_TI_DLPC3433 + tristate "TI DLPC3433 Display controller" + depends on DRM && DRM_PANEL + depends on OF + select DRM_MIPI_DSI + help + TI DLPC3433 is a MIPI DSI based display controller bridge + for processing high resolution DMD based projectors. + + It has a flexible configuration of MIPI DSI and DPI signal + input that produces a DMD output in RGB565, RGB666, RGB888 + formats. + + It supports upto 720p resolution with 60 and 120 Hz refresh + rates. + config DRM_TI_TFP410 tristate "TI TFP410 DVI/HDMI bridge" depends on OF @@ -366,6 +385,8 @@ source "drivers/gpu/drm/bridge/adv7511/Kconfig" source "drivers/gpu/drm/bridge/cadence/Kconfig" +source "drivers/gpu/drm/bridge/imx/Kconfig" + source "drivers/gpu/drm/bridge/synopsys/Kconfig" endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index f6c0a95de549..1884803c6860 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o obj-$(CONFIG_DRM_TOSHIBA_TC358775) += tc358775.o obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ +obj-$(CONFIG_DRM_TI_DLPC3433) += ti-dlpc3433.o obj-$(CONFIG_DRM_TI_SN65DSI83) += ti-sn65dsi83.o obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o @@ -35,4 +36,5 @@ obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o obj-y += analogix/ obj-y += cadence/ +obj-y += imx/ obj-y += synopsys/ diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 9e3bb8a8ee40..a031a0cd1f18 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -226,18 +226,6 @@ #define ADV7511_REG_CEC_CLK_DIV 0x4e #define ADV7511_REG_CEC_SOFT_RESET 0x50 -static const u8 ADV7511_REG_CEC_RX_FRAME_HDR[] = { - ADV7511_REG_CEC_RX1_FRAME_HDR, - ADV7511_REG_CEC_RX2_FRAME_HDR, - ADV7511_REG_CEC_RX3_FRAME_HDR, -}; - -static const u8 ADV7511_REG_CEC_RX_FRAME_LEN[] = { - ADV7511_REG_CEC_RX1_FRAME_LEN, - ADV7511_REG_CEC_RX2_FRAME_LEN, - ADV7511_REG_CEC_RX3_FRAME_LEN, -}; - #define ADV7533_REG_CEC_OFFSET 0x70 enum adv7511_input_clock { diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c index 399f625a50c8..0b266f28f150 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c @@ -15,6 +15,18 @@ #include "adv7511.h" +static const u8 ADV7511_REG_CEC_RX_FRAME_HDR[] = { + ADV7511_REG_CEC_RX1_FRAME_HDR, + ADV7511_REG_CEC_RX2_FRAME_HDR, + ADV7511_REG_CEC_RX3_FRAME_HDR, +}; + +static const u8 ADV7511_REG_CEC_RX_FRAME_LEN[] = { + ADV7511_REG_CEC_RX1_FRAME_LEN, + ADV7511_REG_CEC_RX2_FRAME_LEN, + ADV7511_REG_CEC_RX3_FRAME_LEN, +}; + #define ADV7511_INT1_CEC_MASK \ (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1 | \ diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 5bb9300040dd..38bf28720f3a 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -1065,6 +1065,10 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv) ADV7511_CEC_I2C_ADDR_DEFAULT); if (IS_ERR(adv->i2c_cec)) return PTR_ERR(adv->i2c_cec); + + regmap_write(adv->regmap, ADV7511_REG_CEC_I2C_ADDR, + adv->i2c_cec->addr << 1); + i2c_set_clientdata(adv->i2c_cec, adv); adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, @@ -1271,9 +1275,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (ret) goto err_i2c_unregister_packet; - regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR, - adv7511->i2c_cec->addr << 1); - INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work); if (i2c->irq) { @@ -1392,10 +1393,21 @@ static struct i2c_driver adv7511_driver = { static int __init adv7511_init(void) { - if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) - mipi_dsi_driver_register(&adv7533_dsi_driver); + int ret; - return i2c_add_driver(&adv7511_driver); + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) { + ret = mipi_dsi_driver_register(&adv7533_dsi_driver); + if (ret) + return ret; + } + + ret = i2c_add_driver(&adv7511_driver); + if (ret) { + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&adv7533_dsi_driver); + } + + return ret; } module_init(adv7511_init); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 01c8b80e34ec..8aadcc0aa90b 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -24,6 +24,7 @@ #include <drm/drm_bridge.h> #include <drm/drm_crtc.h> #include <drm/drm_device.h> +#include <drm/drm_edid.h> #include <drm/drm_panel.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 53a5da6c49dd..d1f1d525aeb6 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -1443,23 +1443,24 @@ static int anx7625_read_hpd_status_p0(struct anx7625_data *ctx) return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, SYSTEM_STSTUS); } -static void anx7625_hpd_polling(struct anx7625_data *ctx) +static int _anx7625_hpd_polling(struct anx7625_data *ctx, + unsigned long wait_us) { int ret, val; struct device *dev = &ctx->client->dev; /* Interrupt mode, no need poll HPD status, just return */ if (ctx->pdata.intp_irq) - return; + return 0; ret = readx_poll_timeout(anx7625_read_hpd_status_p0, ctx, val, ((val & HPD_STATUS) || (val < 0)), - 5000, - 5000 * 100); + wait_us / 100, + wait_us); if (ret) { DRM_DEV_ERROR(dev, "no hpd.\n"); - return; + return ret; } DRM_DEV_DEBUG_DRIVER(dev, "system status: 0x%x. HPD raise up.\n", val); @@ -1472,6 +1473,23 @@ static void anx7625_hpd_polling(struct anx7625_data *ctx) if (!ctx->pdata.panel_bridge && ctx->bridge_attached) drm_helper_hpd_irq_event(ctx->bridge.dev); + + return 0; +} + +static int anx7625_wait_hpd_asserted(struct drm_dp_aux *aux, + unsigned long wait_us) +{ + struct anx7625_data *ctx = container_of(aux, struct anx7625_data, aux); + struct device *dev = &ctx->client->dev; + int ret; + + pm_runtime_get_sync(dev); + ret = _anx7625_hpd_polling(ctx, wait_us); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; } static void anx7625_remove_edid(struct anx7625_data *ctx) @@ -1623,29 +1641,30 @@ static int anx7625_parse_dt(struct device *dev, anx7625_get_swing_setting(dev, pdata); - pdata->is_dpi = 1; /* default dpi mode */ + pdata->is_dpi = 0; /* default dsi 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; } - bus_type = V4L2_FWNODE_BUS_TYPE_PARALLEL; + bus_type = 0; 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"); + mipi_lanes = drm_of_get_data_lanes_count(ep0, 1, MAX_LANES_SUPPORT); + of_node_put(ep0); } - if (bus_type == V4L2_FWNODE_BUS_TYPE_PARALLEL) /* bus type is Parallel(DSI) */ - pdata->is_dpi = 0; + if (bus_type == V4L2_FWNODE_BUS_TYPE_DPI) /* bus type is DPI */ + pdata->is_dpi = 1; - pdata->mipi_lanes = mipi_lanes; - if (pdata->mipi_lanes > MAX_LANES_SUPPORT || pdata->mipi_lanes <= 0) - pdata->mipi_lanes = MAX_LANES_SUPPORT; + pdata->mipi_lanes = MAX_LANES_SUPPORT; + if (mipi_lanes > 0) + pdata->mipi_lanes = mipi_lanes; if (pdata->is_dpi) DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DPI host node.\n"); @@ -1657,8 +1676,10 @@ static int anx7625_parse_dt(struct device *dev, pdata->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0); if (IS_ERR(pdata->panel_bridge)) { - if (PTR_ERR(pdata->panel_bridge) == -ENODEV) + if (PTR_ERR(pdata->panel_bridge) == -ENODEV) { + pdata->panel_bridge = NULL; return 0; + } return PTR_ERR(pdata->panel_bridge); } @@ -1738,6 +1759,7 @@ static struct edid *anx7625_get_edid(struct anx7625_data *ctx) } pm_runtime_get_sync(dev); + _anx7625_hpd_polling(ctx, 5000 * 100); edid_num = sp_tx_edid_read(ctx, p_edid->edid_raw_data); pm_runtime_put_sync(dev); @@ -2375,6 +2397,7 @@ static void anx7625_bridge_atomic_enable(struct drm_bridge *bridge, ctx->connector = connector; pm_runtime_get_sync(dev); + _anx7625_hpd_polling(ctx, 5000 * 100); anx7625_dp_start(ctx); } @@ -2433,82 +2456,44 @@ static const struct drm_bridge_funcs anx7625_bridge_funcs = { static int anx7625_register_i2c_dummy_clients(struct anx7625_data *ctx, struct i2c_client *client) { - int err = 0; + struct device *dev = &ctx->client->dev; - ctx->i2c.tx_p0_client = i2c_new_dummy_device(client->adapter, - TX_P0_ADDR >> 1); + ctx->i2c.tx_p0_client = devm_i2c_new_dummy_device(dev, client->adapter, + TX_P0_ADDR >> 1); if (IS_ERR(ctx->i2c.tx_p0_client)) return PTR_ERR(ctx->i2c.tx_p0_client); - ctx->i2c.tx_p1_client = i2c_new_dummy_device(client->adapter, - TX_P1_ADDR >> 1); - if (IS_ERR(ctx->i2c.tx_p1_client)) { - err = PTR_ERR(ctx->i2c.tx_p1_client); - goto free_tx_p0; - } + ctx->i2c.tx_p1_client = devm_i2c_new_dummy_device(dev, client->adapter, + TX_P1_ADDR >> 1); + if (IS_ERR(ctx->i2c.tx_p1_client)) + return PTR_ERR(ctx->i2c.tx_p1_client); - ctx->i2c.tx_p2_client = i2c_new_dummy_device(client->adapter, - TX_P2_ADDR >> 1); - if (IS_ERR(ctx->i2c.tx_p2_client)) { - err = PTR_ERR(ctx->i2c.tx_p2_client); - goto free_tx_p1; - } + ctx->i2c.tx_p2_client = devm_i2c_new_dummy_device(dev, client->adapter, + TX_P2_ADDR >> 1); + if (IS_ERR(ctx->i2c.tx_p2_client)) + return PTR_ERR(ctx->i2c.tx_p2_client); - ctx->i2c.rx_p0_client = i2c_new_dummy_device(client->adapter, - RX_P0_ADDR >> 1); - if (IS_ERR(ctx->i2c.rx_p0_client)) { - err = PTR_ERR(ctx->i2c.rx_p0_client); - goto free_tx_p2; - } + ctx->i2c.rx_p0_client = devm_i2c_new_dummy_device(dev, client->adapter, + RX_P0_ADDR >> 1); + if (IS_ERR(ctx->i2c.rx_p0_client)) + return PTR_ERR(ctx->i2c.rx_p0_client); - ctx->i2c.rx_p1_client = i2c_new_dummy_device(client->adapter, - RX_P1_ADDR >> 1); - if (IS_ERR(ctx->i2c.rx_p1_client)) { - err = PTR_ERR(ctx->i2c.rx_p1_client); - goto free_rx_p0; - } + ctx->i2c.rx_p1_client = devm_i2c_new_dummy_device(dev, client->adapter, + RX_P1_ADDR >> 1); + if (IS_ERR(ctx->i2c.rx_p1_client)) + return PTR_ERR(ctx->i2c.rx_p1_client); - ctx->i2c.rx_p2_client = i2c_new_dummy_device(client->adapter, - RX_P2_ADDR >> 1); - if (IS_ERR(ctx->i2c.rx_p2_client)) { - err = PTR_ERR(ctx->i2c.rx_p2_client); - goto free_rx_p1; - } + ctx->i2c.rx_p2_client = devm_i2c_new_dummy_device(dev, client->adapter, + RX_P2_ADDR >> 1); + if (IS_ERR(ctx->i2c.rx_p2_client)) + return PTR_ERR(ctx->i2c.rx_p2_client); - ctx->i2c.tcpc_client = i2c_new_dummy_device(client->adapter, - TCPC_INTERFACE_ADDR >> 1); - if (IS_ERR(ctx->i2c.tcpc_client)) { - err = PTR_ERR(ctx->i2c.tcpc_client); - goto free_rx_p2; - } + ctx->i2c.tcpc_client = devm_i2c_new_dummy_device(dev, client->adapter, + TCPC_INTERFACE_ADDR >> 1); + if (IS_ERR(ctx->i2c.tcpc_client)) + return PTR_ERR(ctx->i2c.tcpc_client); return 0; - -free_rx_p2: - i2c_unregister_device(ctx->i2c.rx_p2_client); -free_rx_p1: - i2c_unregister_device(ctx->i2c.rx_p1_client); -free_rx_p0: - i2c_unregister_device(ctx->i2c.rx_p0_client); -free_tx_p2: - i2c_unregister_device(ctx->i2c.tx_p2_client); -free_tx_p1: - i2c_unregister_device(ctx->i2c.tx_p1_client); -free_tx_p0: - i2c_unregister_device(ctx->i2c.tx_p0_client); - - return err; -} - -static void anx7625_unregister_i2c_dummy_clients(struct anx7625_data *ctx) -{ - i2c_unregister_device(ctx->i2c.tx_p0_client); - i2c_unregister_device(ctx->i2c.tx_p1_client); - i2c_unregister_device(ctx->i2c.tx_p2_client); - i2c_unregister_device(ctx->i2c.rx_p0_client); - i2c_unregister_device(ctx->i2c.rx_p1_client); - i2c_unregister_device(ctx->i2c.rx_p2_client); - i2c_unregister_device(ctx->i2c.tcpc_client); } static int __maybe_unused anx7625_runtime_pm_suspend(struct device *dev) @@ -2532,45 +2517,15 @@ static int __maybe_unused anx7625_runtime_pm_resume(struct device *dev) mutex_lock(&ctx->lock); anx7625_power_on_init(ctx); - anx7625_hpd_polling(ctx); mutex_unlock(&ctx->lock); return 0; } -static int __maybe_unused anx7625_resume(struct device *dev) -{ - struct anx7625_data *ctx = dev_get_drvdata(dev); - - if (!ctx->pdata.intp_irq) - return 0; - - if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) { - enable_irq(ctx->pdata.intp_irq); - anx7625_runtime_pm_resume(dev); - } - - return 0; -} - -static int __maybe_unused anx7625_suspend(struct device *dev) -{ - struct anx7625_data *ctx = dev_get_drvdata(dev); - - if (!ctx->pdata.intp_irq) - return 0; - - if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) { - anx7625_runtime_pm_suspend(dev); - disable_irq(ctx->pdata.intp_irq); - } - - return 0; -} - static const struct dev_pm_ops anx7625_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(anx7625_suspend, anx7625_resume) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) SET_RUNTIME_PM_OPS(anx7625_runtime_pm_suspend, anx7625_runtime_pm_resume, NULL) }; @@ -2653,15 +2608,8 @@ static int anx7625_i2c_probe(struct i2c_client *client, platform->aux.name = "anx7625-aux"; platform->aux.dev = dev; platform->aux.transfer = anx7625_aux_transfer; + platform->aux.wait_hpd_asserted = anx7625_wait_hpd_asserted; drm_dp_aux_init(&platform->aux); - devm_of_dp_aux_populate_ep_devices(&platform->aux); - - ret = anx7625_parse_dt(dev, pdata); - if (ret) { - if (ret != -EPROBE_DEFER) - DRM_DEV_ERROR(dev, "fail to parse DT : %d\n", ret); - goto free_wq; - } if (anx7625_register_i2c_dummy_clients(platform, client) != 0) { ret = -ENOMEM; @@ -2677,9 +2625,19 @@ static int anx7625_i2c_probe(struct i2c_client *client, if (ret) goto free_wq; + devm_of_dp_aux_populate_ep_devices(&platform->aux); + + ret = anx7625_parse_dt(dev, pdata); + if (ret) { + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "fail to parse DT : %d\n", ret); + goto free_wq; + } + if (!platform->pdata.low_power_mode) { anx7625_disable_pd_protocol(platform); pm_runtime_get_sync(dev); + _anx7625_hpd_polling(platform, 5000 * 100); } /* Add work function */ @@ -2720,8 +2678,6 @@ unregister_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); @@ -2751,8 +2707,6 @@ static int anx7625_i2c_remove(struct i2c_client *client) if (!platform->pdata.low_power_mode) pm_runtime_put_sync_suspend(&client->dev); - anx7625_unregister_i2c_dummy_clients(platform); - if (platform->pdata.audio_en) anx7625_unregister_audio(platform); diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c index 67f0f444b4e8..ab63e7b11944 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c @@ -26,6 +26,7 @@ #include <linux/io.h> #include <linux/iopoll.h> #include <linux/irq.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> @@ -43,6 +44,7 @@ #include <drm/drm_bridge.h> #include <drm/drm_connector.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_edid.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> diff --git a/drivers/gpu/drm/bridge/cdns-dsi.c b/drivers/gpu/drm/bridge/cdns-dsi.c index 829e1a144656..20bece84ff8c 100644 --- a/drivers/gpu/drm/bridge/cdns-dsi.c +++ b/drivers/gpu/drm/bridge/cdns-dsi.c @@ -462,6 +462,7 @@ struct cdns_dsi { struct reset_control *dsi_p_rst; struct clk *dsi_sys_clk; bool link_initialized; + bool phy_initialized; struct phy *dphy; }; @@ -711,11 +712,21 @@ static void cdns_dsi_bridge_disable(struct drm_bridge *bridge) pm_runtime_put(dsi->base.dev); } +static void cdns_dsi_bridge_post_disable(struct drm_bridge *bridge) +{ + struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); + struct cdns_dsi *dsi = input_to_dsi(input); + + pm_runtime_put(dsi->base.dev); +} + static void cdns_dsi_hs_init(struct cdns_dsi *dsi) { struct cdns_dsi_output *output = &dsi->output; u32 status; + if (dsi->phy_initialized) + return; /* * Power all internal DPHY blocks down and maintain their reset line * asserted before changing the DPHY config. @@ -739,6 +750,7 @@ static void cdns_dsi_hs_init(struct cdns_dsi *dsi) writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN | DPHY_D_RSTB(output->dev->lanes) | DPHY_C_RSTB, dsi->regs + MCTL_DPHY_CFG0); + dsi->phy_initialized = true; } static void cdns_dsi_init_link(struct cdns_dsi *dsi) @@ -914,11 +926,25 @@ static void cdns_dsi_bridge_enable(struct drm_bridge *bridge) writel(tmp, dsi->regs + MCTL_MAIN_EN); } +static void cdns_dsi_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge); + struct cdns_dsi *dsi = input_to_dsi(input); + + if (WARN_ON(pm_runtime_get_sync(dsi->base.dev) < 0)) + return; + + cdns_dsi_init_link(dsi); + cdns_dsi_hs_init(dsi); +} + static const struct drm_bridge_funcs cdns_dsi_bridge_funcs = { .attach = cdns_dsi_bridge_attach, .mode_valid = cdns_dsi_bridge_mode_valid, .disable = cdns_dsi_bridge_disable, + .pre_enable = cdns_dsi_bridge_pre_enable, .enable = cdns_dsi_bridge_enable, + .post_disable = cdns_dsi_bridge_post_disable, }; static int cdns_dsi_attach(struct mipi_dsi_host *host, diff --git a/drivers/gpu/drm/bridge/chipone-icn6211.c b/drivers/gpu/drm/bridge/chipone-icn6211.c index 47dea657a752..481c86b2406e 100644 --- a/drivers/gpu/drm/bridge/chipone-icn6211.c +++ b/drivers/gpu/drm/bridge/chipone-icn6211.c @@ -9,9 +9,12 @@ #include <drm/drm_print.h> #include <drm/drm_mipi_dsi.h> +#include <linux/bitfield.h> +#include <linux/bits.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/regmap.h> @@ -26,6 +29,11 @@ #define PD_CTRL(n) (0x0a + ((n) & 0x3)) /* 0..3 */ #define RST_CTRL(n) (0x0e + ((n) & 0x1)) /* 0..1 */ #define SYS_CTRL(n) (0x10 + ((n) & 0x7)) /* 0..4 */ +#define SYS_CTRL_1_CLK_PHASE_MSK GENMASK(5, 4) +#define CLK_PHASE_0 0 +#define CLK_PHASE_1_4 1 +#define CLK_PHASE_1_2 2 +#define CLK_PHASE_3_4 3 #define RGB_DRV(n) (0x18 + ((n) & 0x3)) /* 0..3 */ #define RGB_DLY(n) (0x1c + ((n) & 0x1)) /* 0..1 */ #define RGB_TEST_CTRL 0x1e @@ -100,7 +108,7 @@ #define MIPI_PN_SWAP 0x87 #define MIPI_PN_SWAP_CLK BIT(4) #define MIPI_PN_SWAP_D(n) BIT((n) & 0x3) -#define MIPI_SOT_SYNC_BIT_(n) (0x88 + ((n) & 0x1)) /* 0..1 */ +#define MIPI_SOT_SYNC_BIT(n) (0x88 + ((n) & 0x1)) /* 0..1 */ #define MIPI_ULPS_CTRL 0x8a #define MIPI_CLK_CHK_VAR 0x8e #define MIPI_CLK_CHK_INI 0x8f @@ -115,7 +123,7 @@ #define MIPI_T_CLK_SETTLE 0x9a #define MIPI_TO_HS_RX_L 0x9e #define MIPI_TO_HS_RX_H 0x9f -#define MIPI_PHY_(n) (0xa0 + ((n) & 0x7)) /* 0..5 */ +#define MIPI_PHY(n) (0xa0 + ((n) & 0x7)) /* 0..5 */ #define MIPI_PD_RX 0xb0 #define MIPI_PD_TERM 0xb1 #define MIPI_PD_HSRX 0xb2 @@ -125,13 +133,11 @@ #define MIPI_FORCE_0 0xb6 #define MIPI_RST_CTRL 0xb7 #define MIPI_RST_NUM 0xb8 -#define MIPI_DBG_SET_(n) (0xc0 + ((n) & 0xf)) /* 0..9 */ +#define MIPI_DBG_SET(n) (0xc0 + ((n) & 0xf)) /* 0..9 */ #define MIPI_DBG_SEL 0xe0 #define MIPI_DBG_DATA 0xe1 #define MIPI_ATE_TEST_SEL 0xe2 -#define MIPI_ATE_STATUS_(n) (0xe3 + ((n) & 0x1)) /* 0..1 */ -#define MIPI_ATE_STATUS_1 0xe4 -#define ICN6211_MAX_REGISTER MIPI_ATE_STATUS(1) +#define MIPI_ATE_STATUS(n) (0xe3 + ((n) & 0x1)) /* 0..1 */ struct chipone { struct device *dev; @@ -155,10 +161,10 @@ static const struct regmap_range chipone_dsi_readable_ranges[] = { regmap_reg_range(MIPI_CLK_CHK_VAR, MIPI_T_TA_SURE_PRE), regmap_reg_range(MIPI_T_LPX_SET, MIPI_INIT_TIME_H), regmap_reg_range(MIPI_T_CLK_TERM_EN, MIPI_T_CLK_SETTLE), - regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY_(5)), + regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY(5)), regmap_reg_range(MIPI_PD_RX, MIPI_RST_NUM), - regmap_reg_range(MIPI_DBG_SET_(0), MIPI_DBG_SET_(9)), - regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS_(1)), + regmap_reg_range(MIPI_DBG_SET(0), MIPI_DBG_SET(9)), + regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS(1)), }; static const struct regmap_access_table chipone_dsi_readable_table = { @@ -172,10 +178,10 @@ static const struct regmap_range chipone_dsi_writeable_ranges[] = { regmap_reg_range(MIPI_CLK_CHK_VAR, MIPI_T_TA_SURE_PRE), regmap_reg_range(MIPI_T_LPX_SET, MIPI_INIT_TIME_H), regmap_reg_range(MIPI_T_CLK_TERM_EN, MIPI_T_CLK_SETTLE), - regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY_(5)), + regmap_reg_range(MIPI_TO_HS_RX_L, MIPI_PHY(5)), regmap_reg_range(MIPI_PD_RX, MIPI_RST_NUM), - regmap_reg_range(MIPI_DBG_SET_(0), MIPI_DBG_SET_(9)), - regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS_(1)), + regmap_reg_range(MIPI_DBG_SET(0), MIPI_DBG_SET(9)), + regmap_reg_range(MIPI_DBG_SEL, MIPI_ATE_STATUS(1)), }; static const struct regmap_access_table chipone_dsi_writeable_table = { @@ -189,7 +195,7 @@ static const struct regmap_config chipone_regmap_config = { .rd_table = &chipone_dsi_readable_table, .wr_table = &chipone_dsi_writeable_table, .cache_type = REGCACHE_RBTREE, - .max_register = MIPI_ATE_STATUS_(1), + .max_register = MIPI_ATE_STATUS(1), }; static int chipone_dsi_read(void *context, @@ -336,7 +342,7 @@ static void chipone_atomic_enable(struct drm_bridge *bridge, const struct drm_bridge_state *bridge_state; u16 hfp, hbp, hsync; u32 bus_flags; - u8 pol, id[4]; + u8 pol, sys_ctrl_1, id[4]; chipone_readb(icn, VENDOR_ID, id); chipone_readb(icn, DEVICE_ID_H, id + 1); @@ -414,7 +420,14 @@ static void chipone_atomic_enable(struct drm_bridge *bridge, chipone_configure_pll(icn, mode); chipone_writeb(icn, SYS_CTRL(0), 0x40); - chipone_writeb(icn, SYS_CTRL(1), 0x88); + sys_ctrl_1 = 0x88; + + if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE) + sys_ctrl_1 |= FIELD_PREP(SYS_CTRL_1_CLK_PHASE_MSK, CLK_PHASE_0); + else + sys_ctrl_1 |= FIELD_PREP(SYS_CTRL_1_CLK_PHASE_MSK, CLK_PHASE_1_2); + + chipone_writeb(icn, SYS_CTRL(1), sys_ctrl_1); /* icn6211 specific sequence */ chipone_writeb(icn, MIPI_FORCE_0, 0x20); @@ -486,21 +499,18 @@ static int chipone_dsi_attach(struct chipone *icn) { struct mipi_dsi_device *dsi = icn->dsi; struct device *dev = icn->dev; - struct device_node *endpoint; int dsi_lanes, ret; - endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); - dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); - of_node_put(endpoint); + dsi_lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, 0, 1, 4); /* * If the 'data-lanes' property does not exist in DT or is invalid, * default to previously hard-coded behavior, which was 4 data lanes. */ - if (dsi_lanes >= 1 && dsi_lanes <= 4) - icn->dsi->lanes = dsi_lanes; - else + if (dsi_lanes < 0) icn->dsi->lanes = 4; + else + icn->dsi->lanes = dsi_lanes; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c index 486f405c2e16..ba060277c3fd 100644 --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -6,6 +6,7 @@ */ #include <linux/gpio/consumer.h> +#include <linux/i2c.h> #include <linux/module.h> #include <linux/regmap.h> diff --git a/drivers/gpu/drm/bridge/display-connector.c b/drivers/gpu/drm/bridge/display-connector.c index e4d52a7e31b7..9a12449ad7b8 100644 --- a/drivers/gpu/drm/bridge/display-connector.c +++ b/drivers/gpu/drm/bridge/display-connector.c @@ -6,6 +6,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/interrupt.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c index b2675c769a55..f9e0f8d99268 100644 --- a/drivers/gpu/drm/bridge/fsl-ldb.c +++ b/drivers/gpu/drm/bridge/fsl-ldb.c @@ -4,6 +4,7 @@ */ #include <linux/clk.h> +#include <linux/media-bus-format.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/of.h> @@ -74,22 +75,6 @@ static int fsl_ldb_attach(struct drm_bridge *bridge, bridge, flags); } -static int fsl_ldb_atomic_check(struct drm_bridge *bridge, - struct drm_bridge_state *bridge_state, - struct drm_crtc_state *crtc_state, - struct drm_connector_state *conn_state) -{ - /* Invert DE signal polarity. */ - bridge_state->input_bus_cfg.flags &= ~(DRM_BUS_FLAG_DE_LOW | - DRM_BUS_FLAG_DE_HIGH); - if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_LOW) - bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_HIGH; - else if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_HIGH) - bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_LOW; - - return 0; -} - static void fsl_ldb_atomic_enable(struct drm_bridge *bridge, struct drm_bridge_state *old_bridge_state) { @@ -153,7 +138,7 @@ static void fsl_ldb_atomic_enable(struct drm_bridge *bridge, reg = LDB_CTRL_CH0_ENABLE; if (fsl_ldb->lvds_dual_link) - reg |= LDB_CTRL_CH1_ENABLE; + reg |= LDB_CTRL_CH1_ENABLE | LDB_CTRL_SPLIT_MODE; if (lvds_format_24bpp) { reg |= LDB_CTRL_CH0_DATA_WIDTH; @@ -233,7 +218,7 @@ fsl_ldb_mode_valid(struct drm_bridge *bridge, { struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); - if (mode->clock > (fsl_ldb->lvds_dual_link ? 80000 : 160000)) + if (mode->clock > (fsl_ldb->lvds_dual_link ? 160000 : 80000)) return MODE_CLOCK_HIGH; return MODE_OK; @@ -241,7 +226,6 @@ fsl_ldb_mode_valid(struct drm_bridge *bridge, static const struct drm_bridge_funcs funcs = { .attach = fsl_ldb_attach, - .atomic_check = fsl_ldb_atomic_check, .atomic_enable = fsl_ldb_atomic_enable, .atomic_disable = fsl_ldb_atomic_disable, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, diff --git a/drivers/gpu/drm/bridge/imx/Kconfig b/drivers/gpu/drm/bridge/imx/Kconfig new file mode 100644 index 000000000000..608f47f41bcd --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/Kconfig @@ -0,0 +1,47 @@ +if ARCH_MXC || COMPILE_TEST + +config DRM_IMX8QM_LDB + tristate "Freescale i.MX8QM LVDS display bridge" + depends on OF + depends on COMMON_CLK + select DRM_KMS_HELPER + help + Choose this to enable the internal LVDS Display Bridge(LDB) found in + Freescale i.MX8qm processor. Official name of LDB is pixel mapper. + +config DRM_IMX8QXP_LDB + tristate "Freescale i.MX8QXP LVDS display bridge" + depends on OF + depends on COMMON_CLK + select DRM_KMS_HELPER + help + Choose this to enable the internal LVDS Display Bridge(LDB) found in + Freescale i.MX8qxp processor. Official name of LDB is pixel mapper. + +config DRM_IMX8QXP_PIXEL_COMBINER + tristate "Freescale i.MX8QM/QXP pixel combiner" + depends on OF + depends on COMMON_CLK + select DRM_KMS_HELPER + help + Choose this to enable pixel combiner found in + Freescale i.MX8qm/qxp processors. + +config DRM_IMX8QXP_PIXEL_LINK + tristate "Freescale i.MX8QM/QXP display pixel link" + depends on OF + depends on IMX_SCU + select DRM_KMS_HELPER + help + Choose this to enable display pixel link found in + Freescale i.MX8qm/qxp processors. + +config DRM_IMX8QXP_PIXEL_LINK_TO_DPI + tristate "Freescale i.MX8QXP pixel link to display pixel interface" + depends on OF + select DRM_KMS_HELPER + help + Choose this to enable pixel link to display pixel interface(PXL2DPI) + found in Freescale i.MX8qxp processor. + +endif # ARCH_MXC || COMPILE_TEST diff --git a/drivers/gpu/drm/bridge/imx/Makefile b/drivers/gpu/drm/bridge/imx/Makefile new file mode 100644 index 000000000000..aa90ec8d5433 --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/Makefile @@ -0,0 +1,9 @@ +imx8qm-ldb-objs := imx-ldb-helper.o imx8qm-ldb-drv.o +obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o + +imx8qxp-ldb-objs := imx-ldb-helper.o imx8qxp-ldb-drv.o +obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o + +obj-$(CONFIG_DRM_IMX8QXP_PIXEL_COMBINER) += imx8qxp-pixel-combiner.o +obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK) += imx8qxp-pixel-link.o +obj-$(CONFIG_DRM_IMX8QXP_PIXEL_LINK_TO_DPI) += imx8qxp-pxl2dpi.o diff --git a/drivers/gpu/drm/bridge/imx/imx-ldb-helper.c b/drivers/gpu/drm/bridge/imx/imx-ldb-helper.c new file mode 100644 index 000000000000..7338b84bc83d --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx-ldb-helper.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Sascha Hauer, Pengutronix + * Copyright 2019,2020,2022 NXP + */ + +#include <linux/media-bus-format.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/regmap.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> + +#include "imx-ldb-helper.h" + +bool ldb_channel_is_single_link(struct ldb_channel *ldb_ch) +{ + return ldb_ch->link_type == LDB_CH_SINGLE_LINK; +} + +bool ldb_channel_is_split_link(struct ldb_channel *ldb_ch) +{ + return ldb_ch->link_type == LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS || + ldb_ch->link_type == LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS; +} + +int ldb_bridge_atomic_check_helper(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + + ldb_ch->in_bus_format = bridge_state->input_bus_cfg.format; + ldb_ch->out_bus_format = bridge_state->output_bus_cfg.format; + + return 0; +} + +void ldb_bridge_mode_set_helper(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + bool is_split = ldb_channel_is_split_link(ldb_ch); + + if (is_split) + ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; + + switch (ldb_ch->out_bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + if (ldb_ch->chno == 0 || is_split) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24; + if (ldb_ch->chno == 1 || is_split) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + if (ldb_ch->chno == 0 || is_split) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 | + LDB_BIT_MAP_CH0_JEIDA; + if (ldb_ch->chno == 1 || is_split) + ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 | + LDB_BIT_MAP_CH1_JEIDA; + break; + } +} + +void ldb_bridge_enable_helper(struct drm_bridge *bridge) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + + /* + * Platform specific bridge drivers should set ldb_ctrl properly + * for the enablement, so just write the ctrl_reg here. + */ + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); +} + +void ldb_bridge_disable_helper(struct drm_bridge *bridge) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + bool is_split = ldb_channel_is_split_link(ldb_ch); + + if (ldb_ch->chno == 0 || is_split) + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + if (ldb_ch->chno == 1 || is_split) + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); +} + +int ldb_bridge_attach_helper(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + DRM_DEV_ERROR(ldb->dev, + "do not support creating a drm_connector\n"); + return -EINVAL; + } + + if (!bridge->encoder) { + DRM_DEV_ERROR(ldb->dev, "missing encoder\n"); + return -ENODEV; + } + + return drm_bridge_attach(bridge->encoder, + ldb_ch->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); +} + +int ldb_init_helper(struct ldb *ldb) +{ + struct device *dev = ldb->dev; + struct device_node *np = dev->of_node; + struct device_node *child; + int ret; + u32 i; + + ldb->regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(ldb->regmap)) { + ret = PTR_ERR(ldb->regmap); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret); + return ret; + } + + for_each_available_child_of_node(np, child) { + struct ldb_channel *ldb_ch; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i > MAX_LDB_CHAN_NUM - 1) { + ret = -EINVAL; + DRM_DEV_ERROR(dev, + "invalid channel node address: %u\n", i); + of_node_put(child); + return ret; + } + + ldb_ch = ldb->channel[i]; + ldb_ch->ldb = ldb; + ldb_ch->chno = i; + ldb_ch->is_available = true; + ldb_ch->np = child; + + ldb->available_ch_cnt++; + } + + return 0; +} + +int ldb_find_next_bridge_helper(struct ldb *ldb) +{ + struct device *dev = ldb->dev; + struct ldb_channel *ldb_ch; + int ret, i; + + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { + ldb_ch = ldb->channel[i]; + + if (!ldb_ch->is_available) + continue; + + ldb_ch->next_bridge = devm_drm_of_get_bridge(dev, ldb_ch->np, + 1, 0); + if (IS_ERR(ldb_ch->next_bridge)) { + ret = PTR_ERR(ldb_ch->next_bridge); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "failed to get next bridge: %d\n", + ret); + return ret; + } + } + + return 0; +} + +void ldb_add_bridge_helper(struct ldb *ldb, + const struct drm_bridge_funcs *bridge_funcs) +{ + struct ldb_channel *ldb_ch; + int i; + + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { + ldb_ch = ldb->channel[i]; + + if (!ldb_ch->is_available) + continue; + + ldb_ch->bridge.driver_private = ldb_ch; + ldb_ch->bridge.funcs = bridge_funcs; + ldb_ch->bridge.of_node = ldb_ch->np; + + drm_bridge_add(&ldb_ch->bridge); + } +} + +void ldb_remove_bridge_helper(struct ldb *ldb) +{ + struct ldb_channel *ldb_ch; + int i; + + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { + ldb_ch = ldb->channel[i]; + + if (!ldb_ch->is_available) + continue; + + drm_bridge_remove(&ldb_ch->bridge); + } +} diff --git a/drivers/gpu/drm/bridge/imx/imx-ldb-helper.h b/drivers/gpu/drm/bridge/imx/imx-ldb-helper.h new file mode 100644 index 000000000000..a0a5cde27fbc --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx-ldb-helper.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +/* + * Copyright 2019,2020,2022 NXP + */ + +#ifndef __IMX_LDB_HELPER__ +#define __IMX_LDB_HELPER__ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_bridge.h> +#include <drm/drm_device.h> +#include <drm/drm_encoder.h> +#include <drm/drm_modeset_helper_vtables.h> + +#define LDB_CH0_MODE_EN_TO_DI0 BIT(0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_CH1_MODE_EN_TO_DI0 BIT(2) +#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) +#define LDB_CH1_MODE_EN_MASK (3 << 2) +#define LDB_SPLIT_MODE_EN BIT(4) +#define LDB_DATA_WIDTH_CH0_24 BIT(5) +#define LDB_BIT_MAP_CH0_JEIDA BIT(6) +#define LDB_DATA_WIDTH_CH1_24 BIT(7) +#define LDB_BIT_MAP_CH1_JEIDA BIT(8) +#define LDB_DI0_VS_POL_ACT_LOW BIT(9) +#define LDB_DI1_VS_POL_ACT_LOW BIT(10) + +#define MAX_LDB_CHAN_NUM 2 + +enum ldb_channel_link_type { + LDB_CH_SINGLE_LINK, + LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS, + LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS, +}; + +struct ldb; + +struct ldb_channel { + struct ldb *ldb; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct device_node *np; + u32 chno; + bool is_available; + u32 in_bus_format; + u32 out_bus_format; + enum ldb_channel_link_type link_type; +}; + +struct ldb { + struct regmap *regmap; + struct device *dev; + struct ldb_channel *channel[MAX_LDB_CHAN_NUM]; + unsigned int ctrl_reg; + u32 ldb_ctrl; + unsigned int available_ch_cnt; +}; + +#define bridge_to_ldb_ch(b) container_of(b, struct ldb_channel, bridge) + +bool ldb_channel_is_single_link(struct ldb_channel *ldb_ch); +bool ldb_channel_is_split_link(struct ldb_channel *ldb_ch); + +int ldb_bridge_atomic_check_helper(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); + +void ldb_bridge_mode_set_helper(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode); + +void ldb_bridge_enable_helper(struct drm_bridge *bridge); + +void ldb_bridge_disable_helper(struct drm_bridge *bridge); + +int ldb_bridge_attach_helper(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags); + +int ldb_init_helper(struct ldb *ldb); + +int ldb_find_next_bridge_helper(struct ldb *ldb); + +void ldb_add_bridge_helper(struct ldb *ldb, + const struct drm_bridge_funcs *bridge_funcs); + +void ldb_remove_bridge_helper(struct ldb *ldb); + +#endif /* __IMX_LDB_HELPER__ */ diff --git a/drivers/gpu/drm/bridge/imx/imx8qm-ldb-drv.c b/drivers/gpu/drm/bridge/imx/imx8qm-ldb-drv.c new file mode 100644 index 000000000000..178af8d2d80b --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx8qm-ldb-drv.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020 NXP + */ + +#include <linux/clk.h> +#include <linux/media-bus-format.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> + +#include "imx-ldb-helper.h" + +#define LDB_CH0_10BIT_EN BIT(22) +#define LDB_CH1_10BIT_EN BIT(23) +#define LDB_CH0_DATA_WIDTH_24BIT BIT(24) +#define LDB_CH1_DATA_WIDTH_24BIT BIT(26) +#define LDB_CH0_DATA_WIDTH_30BIT (2 << 24) +#define LDB_CH1_DATA_WIDTH_30BIT (2 << 26) + +#define SS_CTRL 0x20 +#define CH_HSYNC_M(id) BIT(0 + ((id) * 2)) +#define CH_VSYNC_M(id) BIT(1 + ((id) * 2)) +#define CH_PHSYNC(id) BIT(0 + ((id) * 2)) +#define CH_PVSYNC(id) BIT(1 + ((id) * 2)) + +#define DRIVER_NAME "imx8qm-ldb" + +struct imx8qm_ldb_channel { + struct ldb_channel base; + struct phy *phy; +}; + +struct imx8qm_ldb { + struct ldb base; + struct device *dev; + struct imx8qm_ldb_channel channel[MAX_LDB_CHAN_NUM]; + struct clk *clk_pixel; + struct clk *clk_bypass; + int active_chno; +}; + +static inline struct imx8qm_ldb_channel * +base_to_imx8qm_ldb_channel(struct ldb_channel *base) +{ + return container_of(base, struct imx8qm_ldb_channel, base); +} + +static inline struct imx8qm_ldb *base_to_imx8qm_ldb(struct ldb *base) +{ + return container_of(base, struct imx8qm_ldb, base); +} + +static void imx8qm_ldb_set_phy_cfg(struct imx8qm_ldb *imx8qm_ldb, + unsigned long di_clk, + bool is_split, bool is_slave, + struct phy_configure_opts_lvds *phy_cfg) +{ + phy_cfg->bits_per_lane_and_dclk_cycle = 7; + phy_cfg->lanes = 4; + phy_cfg->differential_clk_rate = is_split ? di_clk / 2 : di_clk; + phy_cfg->is_slave = is_slave; +} + +static int imx8qm_ldb_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 ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + base_to_imx8qm_ldb_channel(ldb_ch); + struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb); + struct drm_display_mode *adj = &crtc_state->adjusted_mode; + unsigned long di_clk = adj->clock * 1000; + bool is_split = ldb_channel_is_split_link(ldb_ch); + union phy_configure_opts opts = { }; + struct phy_configure_opts_lvds *phy_cfg = &opts.lvds; + int ret; + + ret = ldb_bridge_atomic_check_helper(bridge, bridge_state, + crtc_state, conn_state); + if (ret) + return ret; + + imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, false, phy_cfg); + ret = phy_validate(imx8qm_ldb_ch->phy, PHY_MODE_LVDS, 0, &opts); + if (ret < 0) { + DRM_DEV_DEBUG_DRIVER(imx8qm_ldb->dev, + "failed to validate PHY: %d\n", ret); + return ret; + } + + if (is_split) { + imx8qm_ldb_ch = + &imx8qm_ldb->channel[imx8qm_ldb->active_chno ^ 1]; + imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, true, + phy_cfg); + ret = phy_validate(imx8qm_ldb_ch->phy, PHY_MODE_LVDS, 0, &opts); + if (ret < 0) { + DRM_DEV_DEBUG_DRIVER(imx8qm_ldb->dev, + "failed to validate slave PHY: %d\n", + ret); + return ret; + } + } + + return ret; +} + +static void +imx8qm_ldb_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + base_to_imx8qm_ldb_channel(ldb_ch); + struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb); + struct device *dev = imx8qm_ldb->dev; + unsigned long di_clk = adjusted_mode->clock * 1000; + bool is_split = ldb_channel_is_split_link(ldb_ch); + union phy_configure_opts opts = { }; + struct phy_configure_opts_lvds *phy_cfg = &opts.lvds; + u32 chno = ldb_ch->chno; + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to get runtime PM sync: %d\n", ret); + + ret = phy_init(imx8qm_ldb_ch->phy); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to initialize PHY: %d\n", ret); + + clk_set_rate(imx8qm_ldb->clk_bypass, di_clk); + clk_set_rate(imx8qm_ldb->clk_pixel, di_clk); + + imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, false, phy_cfg); + ret = phy_configure(imx8qm_ldb_ch->phy, &opts); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to configure PHY: %d\n", ret); + + if (is_split) { + imx8qm_ldb_ch = + &imx8qm_ldb->channel[imx8qm_ldb->active_chno ^ 1]; + imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, true, + phy_cfg); + ret = phy_configure(imx8qm_ldb_ch->phy, &opts); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to configure slave PHY: %d\n", + ret); + } + + /* input VSYNC signal from pixel link is active low */ + if (ldb_ch->chno == 0 || is_split) + ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; + if (ldb_ch->chno == 1 || is_split) + ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; + + switch (ldb_ch->out_bus_format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + if (ldb_ch->chno == 0 || is_split) + ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT; + if (ldb_ch->chno == 1 || is_split) + ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT; + break; + } + + ldb_bridge_mode_set_helper(bridge, mode, adjusted_mode); + + if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, CH_VSYNC_M(chno), 0); + else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, + CH_VSYNC_M(chno), CH_PVSYNC(chno)); + + if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, CH_HSYNC_M(chno), 0); + else if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, + CH_HSYNC_M(chno), CH_PHSYNC(chno)); +} + +static void +imx8qm_ldb_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + base_to_imx8qm_ldb_channel(ldb_ch); + struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb); + struct device *dev = imx8qm_ldb->dev; + bool is_split = ldb_channel_is_split_link(ldb_ch); + int ret; + + clk_prepare_enable(imx8qm_ldb->clk_pixel); + clk_prepare_enable(imx8qm_ldb->clk_bypass); + + /* both DI0 and DI1 connect with pixel link, so ok to use DI0 only */ + if (ldb_ch->chno == 0 || is_split) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + } + if (ldb_ch->chno == 1 || is_split) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI0; + } + + if (is_split) { + ret = phy_power_on(imx8qm_ldb->channel[0].phy); + if (ret) + DRM_DEV_ERROR(dev, + "failed to power on channel0 PHY: %d\n", + ret); + + ret = phy_power_on(imx8qm_ldb->channel[1].phy); + if (ret) + DRM_DEV_ERROR(dev, + "failed to power on channel1 PHY: %d\n", + ret); + } else { + ret = phy_power_on(imx8qm_ldb_ch->phy); + if (ret) + DRM_DEV_ERROR(dev, "failed to power on PHY: %d\n", ret); + } + + ldb_bridge_enable_helper(bridge); +} + +static void +imx8qm_ldb_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qm_ldb_channel *imx8qm_ldb_ch = + base_to_imx8qm_ldb_channel(ldb_ch); + struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb); + struct device *dev = imx8qm_ldb->dev; + bool is_split = ldb_channel_is_split_link(ldb_ch); + int ret; + + ldb_bridge_disable_helper(bridge); + + if (is_split) { + ret = phy_power_off(imx8qm_ldb->channel[0].phy); + if (ret) + DRM_DEV_ERROR(dev, + "failed to power off channel0 PHY: %d\n", + ret); + ret = phy_power_off(imx8qm_ldb->channel[1].phy); + if (ret) + DRM_DEV_ERROR(dev, + "failed to power off channel1 PHY: %d\n", + ret); + } else { + ret = phy_power_off(imx8qm_ldb_ch->phy); + if (ret) + DRM_DEV_ERROR(dev, "failed to power off PHY: %d\n", ret); + } + + clk_disable_unprepare(imx8qm_ldb->clk_bypass); + clk_disable_unprepare(imx8qm_ldb->clk_pixel); + + ret = pm_runtime_put(dev); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to put runtime PM: %d\n", ret); +} + +static const u32 imx8qm_ldb_bus_output_fmts[] = { + MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + MEDIA_BUS_FMT_FIXED, +}; + +static bool imx8qm_ldb_bus_output_fmt_supported(u32 fmt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx8qm_ldb_bus_output_fmts); i++) { + if (imx8qm_ldb_bus_output_fmts[i] == fmt) + return true; + } + + return false; +} + +static u32 * +imx8qm_ldb_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) +{ + struct drm_display_info *di; + const struct drm_format_info *finfo; + u32 *input_fmts; + + if (!imx8qm_ldb_bus_output_fmt_supported(output_fmt)) + return NULL; + + *num_input_fmts = 1; + + input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + case MEDIA_BUS_FMT_FIXED: + di = &conn_state->connector->display_info; + + /* + * Look at the first bus format to determine input format. + * Default to MEDIA_BUS_FMT_RGB888_1X36_CPADLO, if no match. + */ + if (di->num_bus_formats) { + finfo = drm_format_info(di->bus_formats[0]); + + input_fmts[0] = finfo->depth == 18 ? + MEDIA_BUS_FMT_RGB666_1X36_CPADLO : + MEDIA_BUS_FMT_RGB888_1X36_CPADLO; + } else { + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO; + } + break; + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO; + break; + default: + kfree(input_fmts); + input_fmts = NULL; + break; + } + + return input_fmts; +} + +static u32 * +imx8qm_ldb_bridge_atomic_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) +{ + *num_output_fmts = ARRAY_SIZE(imx8qm_ldb_bus_output_fmts); + return kmemdup(imx8qm_ldb_bus_output_fmts, + sizeof(imx8qm_ldb_bus_output_fmts), GFP_KERNEL); +} + +static enum drm_mode_status +imx8qm_ldb_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + bool is_single = ldb_channel_is_single_link(ldb_ch); + + if (mode->clock > 300000) + return MODE_CLOCK_HIGH; + + if (mode->clock > 150000 && is_single) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_bridge_funcs imx8qm_ldb_bridge_funcs = { + .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, + .mode_valid = imx8qm_ldb_bridge_mode_valid, + .attach = ldb_bridge_attach_helper, + .atomic_check = imx8qm_ldb_bridge_atomic_check, + .mode_set = imx8qm_ldb_bridge_mode_set, + .atomic_enable = imx8qm_ldb_bridge_atomic_enable, + .atomic_disable = imx8qm_ldb_bridge_atomic_disable, + .atomic_get_input_bus_fmts = + imx8qm_ldb_bridge_atomic_get_input_bus_fmts, + .atomic_get_output_bus_fmts = + imx8qm_ldb_bridge_atomic_get_output_bus_fmts, +}; + +static int imx8qm_ldb_get_phy(struct imx8qm_ldb *imx8qm_ldb) +{ + struct imx8qm_ldb_channel *imx8qm_ldb_ch; + struct ldb_channel *ldb_ch; + struct device *dev = imx8qm_ldb->dev; + int i, ret; + + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { + imx8qm_ldb_ch = &imx8qm_ldb->channel[i]; + ldb_ch = &imx8qm_ldb_ch->base; + + if (!ldb_ch->is_available) + continue; + + imx8qm_ldb_ch->phy = devm_of_phy_get(dev, ldb_ch->np, + "lvds_phy"); + if (IS_ERR(imx8qm_ldb_ch->phy)) { + ret = PTR_ERR(imx8qm_ldb_ch->phy); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "failed to get channel%d PHY: %d\n", + i, ret); + return ret; + } + } + + return 0; +} + +static int imx8qm_ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx8qm_ldb *imx8qm_ldb; + struct imx8qm_ldb_channel *imx8qm_ldb_ch; + struct ldb *ldb; + struct ldb_channel *ldb_ch; + struct device_node *port1, *port2; + int pixel_order; + int ret, i; + + imx8qm_ldb = devm_kzalloc(dev, sizeof(*imx8qm_ldb), GFP_KERNEL); + if (!imx8qm_ldb) + return -ENOMEM; + + imx8qm_ldb->clk_pixel = devm_clk_get(dev, "pixel"); + if (IS_ERR(imx8qm_ldb->clk_pixel)) { + ret = PTR_ERR(imx8qm_ldb->clk_pixel); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "failed to get pixel clock: %d\n", ret); + return ret; + } + + imx8qm_ldb->clk_bypass = devm_clk_get(dev, "bypass"); + if (IS_ERR(imx8qm_ldb->clk_bypass)) { + ret = PTR_ERR(imx8qm_ldb->clk_bypass); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "failed to get bypass clock: %d\n", ret); + return ret; + } + + imx8qm_ldb->dev = dev; + + ldb = &imx8qm_ldb->base; + ldb->dev = dev; + ldb->ctrl_reg = 0xe0; + + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) + ldb->channel[i] = &imx8qm_ldb->channel[i].base; + + ret = ldb_init_helper(ldb); + if (ret) + return ret; + + if (ldb->available_ch_cnt == 0) { + DRM_DEV_DEBUG_DRIVER(dev, "no available channel\n"); + return 0; + } + + if (ldb->available_ch_cnt == 2) { + port1 = of_graph_get_port_by_id(ldb->channel[0]->np, 1); + port2 = of_graph_get_port_by_id(ldb->channel[1]->np, 1); + pixel_order = + drm_of_lvds_get_dual_link_pixel_order(port1, port2); + of_node_put(port1); + of_node_put(port2); + + if (pixel_order != DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) { + DRM_DEV_ERROR(dev, "invalid dual link pixel order: %d\n", + pixel_order); + return -EINVAL; + } + + imx8qm_ldb->active_chno = 0; + imx8qm_ldb_ch = &imx8qm_ldb->channel[0]; + ldb_ch = &imx8qm_ldb_ch->base; + ldb_ch->link_type = pixel_order; + } else { + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { + imx8qm_ldb_ch = &imx8qm_ldb->channel[i]; + ldb_ch = &imx8qm_ldb_ch->base; + + if (ldb_ch->is_available) { + imx8qm_ldb->active_chno = ldb_ch->chno; + break; + } + } + } + + ret = imx8qm_ldb_get_phy(imx8qm_ldb); + if (ret) + return ret; + + ret = ldb_find_next_bridge_helper(ldb); + if (ret) + return ret; + + platform_set_drvdata(pdev, imx8qm_ldb); + pm_runtime_enable(dev); + + ldb_add_bridge_helper(ldb, &imx8qm_ldb_bridge_funcs); + + return ret; +} + +static int imx8qm_ldb_remove(struct platform_device *pdev) +{ + struct imx8qm_ldb *imx8qm_ldb = platform_get_drvdata(pdev); + struct ldb *ldb = &imx8qm_ldb->base; + + ldb_remove_bridge_helper(ldb); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused imx8qm_ldb_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused imx8qm_ldb_runtime_resume(struct device *dev) +{ + struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev); + struct ldb *ldb = &imx8qm_ldb->base; + + /* disable LDB by resetting the control register to POR default */ + regmap_write(ldb->regmap, ldb->ctrl_reg, 0); + + return 0; +} + +static const struct dev_pm_ops imx8qm_ldb_pm_ops = { + SET_RUNTIME_PM_OPS(imx8qm_ldb_runtime_suspend, + imx8qm_ldb_runtime_resume, NULL) +}; + +static const struct of_device_id imx8qm_ldb_dt_ids[] = { + { .compatible = "fsl,imx8qm-ldb" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx8qm_ldb_dt_ids); + +static struct platform_driver imx8qm_ldb_driver = { + .probe = imx8qm_ldb_probe, + .remove = imx8qm_ldb_remove, + .driver = { + .pm = &imx8qm_ldb_pm_ops, + .name = DRIVER_NAME, + .of_match_table = imx8qm_ldb_dt_ids, + }, +}; +module_platform_driver(imx8qm_ldb_driver); + +MODULE_DESCRIPTION("i.MX8QM LVDS Display Bridge(LDB)/Pixel Mapper bridge driver"); +MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-ldb-drv.c b/drivers/gpu/drm/bridge/imx/imx8qxp-ldb-drv.c new file mode 100644 index 000000000000..63948d5d20fd --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-ldb-drv.c @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020 NXP + */ + +#include <linux/clk.h> +#include <linux/media-bus-format.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> + +#include "imx-ldb-helper.h" + +#define LDB_CH_SEL BIT(28) + +#define SS_CTRL 0x20 +#define CH_HSYNC_M(id) BIT(0 + ((id) * 2)) +#define CH_VSYNC_M(id) BIT(1 + ((id) * 2)) +#define CH_PHSYNC(id) BIT(0 + ((id) * 2)) +#define CH_PVSYNC(id) BIT(1 + ((id) * 2)) + +#define DRIVER_NAME "imx8qxp-ldb" + +struct imx8qxp_ldb_channel { + struct ldb_channel base; + struct phy *phy; + unsigned int di_id; +}; + +struct imx8qxp_ldb { + struct ldb base; + struct device *dev; + struct imx8qxp_ldb_channel channel[MAX_LDB_CHAN_NUM]; + struct clk *clk_pixel; + struct clk *clk_bypass; + struct drm_bridge *companion; + int active_chno; +}; + +static inline struct imx8qxp_ldb_channel * +base_to_imx8qxp_ldb_channel(struct ldb_channel *base) +{ + return container_of(base, struct imx8qxp_ldb_channel, base); +} + +static inline struct imx8qxp_ldb *base_to_imx8qxp_ldb(struct ldb *base) +{ + return container_of(base, struct imx8qxp_ldb, base); +} + +static void imx8qxp_ldb_set_phy_cfg(struct imx8qxp_ldb *imx8qxp_ldb, + unsigned long di_clk, bool is_split, + struct phy_configure_opts_lvds *phy_cfg) +{ + phy_cfg->bits_per_lane_and_dclk_cycle = 7; + phy_cfg->lanes = 4; + + if (is_split) { + phy_cfg->differential_clk_rate = di_clk / 2; + phy_cfg->is_slave = !imx8qxp_ldb->companion; + } else { + phy_cfg->differential_clk_rate = di_clk; + phy_cfg->is_slave = false; + } +} + +static int +imx8qxp_ldb_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 ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + base_to_imx8qxp_ldb_channel(ldb_ch); + struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb); + struct drm_bridge *companion = imx8qxp_ldb->companion; + struct drm_display_mode *adj = &crtc_state->adjusted_mode; + unsigned long di_clk = adj->clock * 1000; + bool is_split = ldb_channel_is_split_link(ldb_ch); + union phy_configure_opts opts = { }; + struct phy_configure_opts_lvds *phy_cfg = &opts.lvds; + int ret; + + ret = ldb_bridge_atomic_check_helper(bridge, bridge_state, + crtc_state, conn_state); + if (ret) + return ret; + + imx8qxp_ldb_set_phy_cfg(imx8qxp_ldb, di_clk, is_split, phy_cfg); + ret = phy_validate(imx8qxp_ldb_ch->phy, PHY_MODE_LVDS, 0, &opts); + if (ret < 0) { + DRM_DEV_DEBUG_DRIVER(imx8qxp_ldb->dev, + "failed to validate PHY: %d\n", ret); + return ret; + } + + if (is_split && companion) { + ret = companion->funcs->atomic_check(companion, + bridge_state, crtc_state, conn_state); + if (ret) + return ret; + } + + return ret; +} + +static void +imx8qxp_ldb_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb_channel *companion_ldb_ch; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + base_to_imx8qxp_ldb_channel(ldb_ch); + struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb); + struct drm_bridge *companion = imx8qxp_ldb->companion; + struct device *dev = imx8qxp_ldb->dev; + unsigned long di_clk = adjusted_mode->clock * 1000; + bool is_split = ldb_channel_is_split_link(ldb_ch); + union phy_configure_opts opts = { }; + struct phy_configure_opts_lvds *phy_cfg = &opts.lvds; + u32 chno = ldb_ch->chno; + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to get runtime PM sync: %d\n", ret); + + ret = phy_init(imx8qxp_ldb_ch->phy); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to initialize PHY: %d\n", ret); + + ret = phy_set_mode(imx8qxp_ldb_ch->phy, PHY_MODE_LVDS); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to set PHY mode: %d\n", ret); + + if (is_split && companion) { + companion_ldb_ch = bridge_to_ldb_ch(companion); + + companion_ldb_ch->in_bus_format = ldb_ch->in_bus_format; + companion_ldb_ch->out_bus_format = ldb_ch->out_bus_format; + } + + clk_set_rate(imx8qxp_ldb->clk_bypass, di_clk); + clk_set_rate(imx8qxp_ldb->clk_pixel, di_clk); + + imx8qxp_ldb_set_phy_cfg(imx8qxp_ldb, di_clk, is_split, phy_cfg); + ret = phy_configure(imx8qxp_ldb_ch->phy, &opts); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to configure PHY: %d\n", ret); + + if (chno == 0) + ldb->ldb_ctrl &= ~LDB_CH_SEL; + else + ldb->ldb_ctrl |= LDB_CH_SEL; + + /* input VSYNC signal from pixel link is active low */ + if (imx8qxp_ldb_ch->di_id == 0) + ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; + else + ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; + + /* + * For split mode, settle input VSYNC signal polarity and + * channel selection down early. + */ + if (is_split) + regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl); + + ldb_bridge_mode_set_helper(bridge, mode, adjusted_mode); + + if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, CH_VSYNC_M(chno), 0); + else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, + CH_VSYNC_M(chno), CH_PVSYNC(chno)); + + if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, CH_HSYNC_M(chno), 0); + else if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + regmap_update_bits(ldb->regmap, SS_CTRL, + CH_HSYNC_M(chno), CH_PHSYNC(chno)); + + if (is_split && companion) + companion->funcs->mode_set(companion, mode, adjusted_mode); +} + +static void +imx8qxp_ldb_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb); + struct drm_bridge *companion = imx8qxp_ldb->companion; + bool is_split = ldb_channel_is_split_link(ldb_ch); + + clk_prepare_enable(imx8qxp_ldb->clk_pixel); + clk_prepare_enable(imx8qxp_ldb->clk_bypass); + + if (is_split && companion) + companion->funcs->atomic_pre_enable(companion, old_bridge_state); +} + +static void +imx8qxp_ldb_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + base_to_imx8qxp_ldb_channel(ldb_ch); + struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb); + struct drm_bridge *companion = imx8qxp_ldb->companion; + struct device *dev = imx8qxp_ldb->dev; + bool is_split = ldb_channel_is_split_link(ldb_ch); + int ret; + + if (ldb_ch->chno == 0 || is_split) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= imx8qxp_ldb_ch->di_id == 0 ? + LDB_CH0_MODE_EN_TO_DI0 : LDB_CH0_MODE_EN_TO_DI1; + } + if (ldb_ch->chno == 1 || is_split) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + ldb->ldb_ctrl |= imx8qxp_ldb_ch->di_id == 0 ? + LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1; + } + + ldb_bridge_enable_helper(bridge); + + ret = phy_power_on(imx8qxp_ldb_ch->phy); + if (ret) + DRM_DEV_ERROR(dev, "failed to power on PHY: %d\n", ret); + + if (is_split && companion) + companion->funcs->atomic_enable(companion, old_bridge_state); +} + +static void +imx8qxp_ldb_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + struct ldb *ldb = ldb_ch->ldb; + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + base_to_imx8qxp_ldb_channel(ldb_ch); + struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb); + struct drm_bridge *companion = imx8qxp_ldb->companion; + struct device *dev = imx8qxp_ldb->dev; + bool is_split = ldb_channel_is_split_link(ldb_ch); + int ret; + + ret = phy_power_off(imx8qxp_ldb_ch->phy); + if (ret) + DRM_DEV_ERROR(dev, "failed to power off PHY: %d\n", ret); + + ret = phy_exit(imx8qxp_ldb_ch->phy); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to teardown PHY: %d\n", ret); + + ldb_bridge_disable_helper(bridge); + + clk_disable_unprepare(imx8qxp_ldb->clk_bypass); + clk_disable_unprepare(imx8qxp_ldb->clk_pixel); + + if (is_split && companion) + companion->funcs->atomic_disable(companion, old_bridge_state); + + ret = pm_runtime_put(dev); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to put runtime PM: %d\n", ret); +} + +static const u32 imx8qxp_ldb_bus_output_fmts[] = { + MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, + MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, + MEDIA_BUS_FMT_FIXED, +}; + +static bool imx8qxp_ldb_bus_output_fmt_supported(u32 fmt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx8qxp_ldb_bus_output_fmts); i++) { + if (imx8qxp_ldb_bus_output_fmts[i] == fmt) + return true; + } + + return false; +} + +static u32 * +imx8qxp_ldb_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) +{ + struct drm_display_info *di; + const struct drm_format_info *finfo; + u32 *input_fmts; + + if (!imx8qxp_ldb_bus_output_fmt_supported(output_fmt)) + return NULL; + + *num_input_fmts = 1; + + input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + case MEDIA_BUS_FMT_FIXED: + di = &conn_state->connector->display_info; + + /* + * Look at the first bus format to determine input format. + * Default to MEDIA_BUS_FMT_RGB888_1X24, if no match. + */ + if (di->num_bus_formats) { + finfo = drm_format_info(di->bus_formats[0]); + + input_fmts[0] = finfo->depth == 18 ? + MEDIA_BUS_FMT_RGB666_1X24_CPADHI : + MEDIA_BUS_FMT_RGB888_1X24; + } else { + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + } + break; + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X24_CPADHI; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + break; + default: + kfree(input_fmts); + input_fmts = NULL; + break; + } + + return input_fmts; +} + +static u32 * +imx8qxp_ldb_bridge_atomic_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) +{ + *num_output_fmts = ARRAY_SIZE(imx8qxp_ldb_bus_output_fmts); + return kmemdup(imx8qxp_ldb_bus_output_fmts, + sizeof(imx8qxp_ldb_bus_output_fmts), GFP_KERNEL); +} + +static enum drm_mode_status +imx8qxp_ldb_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct ldb_channel *ldb_ch = bridge->driver_private; + bool is_single = ldb_channel_is_single_link(ldb_ch); + + if (mode->clock > 170000) + return MODE_CLOCK_HIGH; + + if (mode->clock > 150000 && is_single) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_bridge_funcs imx8qxp_ldb_bridge_funcs = { + .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, + .mode_valid = imx8qxp_ldb_bridge_mode_valid, + .attach = ldb_bridge_attach_helper, + .atomic_check = imx8qxp_ldb_bridge_atomic_check, + .mode_set = imx8qxp_ldb_bridge_mode_set, + .atomic_pre_enable = imx8qxp_ldb_bridge_atomic_pre_enable, + .atomic_enable = imx8qxp_ldb_bridge_atomic_enable, + .atomic_disable = imx8qxp_ldb_bridge_atomic_disable, + .atomic_get_input_bus_fmts = + imx8qxp_ldb_bridge_atomic_get_input_bus_fmts, + .atomic_get_output_bus_fmts = + imx8qxp_ldb_bridge_atomic_get_output_bus_fmts, +}; + +static int imx8qxp_ldb_set_di_id(struct imx8qxp_ldb *imx8qxp_ldb) +{ + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + &imx8qxp_ldb->channel[imx8qxp_ldb->active_chno]; + struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base; + struct device_node *ep, *remote; + struct device *dev = imx8qxp_ldb->dev; + struct of_endpoint endpoint; + int ret; + + ep = of_graph_get_endpoint_by_regs(ldb_ch->np, 0, -1); + if (!ep) { + DRM_DEV_ERROR(dev, "failed to get port0 endpoint\n"); + return -EINVAL; + } + + remote = of_graph_get_remote_endpoint(ep); + of_node_put(ep); + if (!remote) { + DRM_DEV_ERROR(dev, "failed to get port0 remote endpoint\n"); + return -EINVAL; + } + + ret = of_graph_parse_endpoint(remote, &endpoint); + of_node_put(remote); + if (ret) { + DRM_DEV_ERROR(dev, "failed to parse port0 remote endpoint: %d\n", + ret); + return ret; + } + + imx8qxp_ldb_ch->di_id = endpoint.id; + + return 0; +} + +static int +imx8qxp_ldb_check_chno_and_dual_link(struct ldb_channel *ldb_ch, int link) +{ + if ((link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS && ldb_ch->chno != 0) || + (link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS && ldb_ch->chno != 1)) + return -EINVAL; + + return 0; +} + +static int imx8qxp_ldb_parse_dt_companion(struct imx8qxp_ldb *imx8qxp_ldb) +{ + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch = + &imx8qxp_ldb->channel[imx8qxp_ldb->active_chno]; + struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base; + struct ldb_channel *companion_ldb_ch; + struct device_node *companion; + struct device_node *child; + struct device_node *companion_port = NULL; + struct device_node *port1, *port2; + struct device *dev = imx8qxp_ldb->dev; + const struct of_device_id *match; + u32 i; + int dual_link; + int ret; + + /* Locate the companion LDB for dual-link operation, if any. */ + companion = of_parse_phandle(dev->of_node, "fsl,companion-ldb", 0); + if (!companion) + return 0; + + if (!of_device_is_available(companion)) { + DRM_DEV_ERROR(dev, "companion LDB is not available\n"); + ret = -ENODEV; + goto out; + } + + /* + * Sanity check: the companion bridge must have the same compatible + * string. + */ + match = of_match_device(dev->driver->of_match_table, dev); + if (!of_device_is_compatible(companion, match->compatible)) { + DRM_DEV_ERROR(dev, "companion LDB is incompatible\n"); + ret = -ENXIO; + goto out; + } + + for_each_available_child_of_node(companion, child) { + ret = of_property_read_u32(child, "reg", &i); + if (ret || i > MAX_LDB_CHAN_NUM - 1) { + DRM_DEV_ERROR(dev, + "invalid channel node address: %u\n", i); + ret = -EINVAL; + of_node_put(child); + goto out; + } + + /* + * Channel numbers have to be different, because channel0 + * transmits odd pixels and channel1 transmits even pixels. + */ + if (i == (ldb_ch->chno ^ 0x1)) { + companion_port = child; + break; + } + } + + if (!companion_port) { + DRM_DEV_ERROR(dev, + "failed to find companion LDB channel port\n"); + ret = -EINVAL; + goto out; + } + + /* + * We need to work out if the sink is expecting us to function in + * dual-link mode. We do this by looking at the DT port nodes we are + * connected to. If they are marked as expecting odd pixels and + * even pixels than we need to enable LDB split mode. + */ + port1 = of_graph_get_port_by_id(ldb_ch->np, 1); + port2 = of_graph_get_port_by_id(companion_port, 1); + dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2); + of_node_put(port1); + of_node_put(port2); + + switch (dual_link) { + case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS: + ldb_ch->link_type = LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS; + break; + case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS: + ldb_ch->link_type = LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS; + break; + default: + ret = dual_link; + DRM_DEV_ERROR(dev, + "failed to get dual link pixel order: %d\n", ret); + goto out; + } + + ret = imx8qxp_ldb_check_chno_and_dual_link(ldb_ch, dual_link); + if (ret < 0) { + DRM_DEV_ERROR(dev, + "unmatched channel number(%u) vs dual link(%d)\n", + ldb_ch->chno, dual_link); + goto out; + } + + imx8qxp_ldb->companion = of_drm_find_bridge(companion_port); + if (!imx8qxp_ldb->companion) { + ret = -EPROBE_DEFER; + DRM_DEV_DEBUG_DRIVER(dev, + "failed to find bridge for companion bridge: %d\n", + ret); + goto out; + } + + DRM_DEV_DEBUG_DRIVER(dev, + "dual-link configuration detected (companion bridge %pOF)\n", + companion); + + companion_ldb_ch = bridge_to_ldb_ch(imx8qxp_ldb->companion); + companion_ldb_ch->link_type = ldb_ch->link_type; +out: + of_node_put(companion_port); + of_node_put(companion); + return ret; +} + +static int imx8qxp_ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx8qxp_ldb *imx8qxp_ldb; + struct imx8qxp_ldb_channel *imx8qxp_ldb_ch; + struct ldb *ldb; + struct ldb_channel *ldb_ch; + int ret, i; + + imx8qxp_ldb = devm_kzalloc(dev, sizeof(*imx8qxp_ldb), GFP_KERNEL); + if (!imx8qxp_ldb) + return -ENOMEM; + + imx8qxp_ldb->clk_pixel = devm_clk_get(dev, "pixel"); + if (IS_ERR(imx8qxp_ldb->clk_pixel)) { + ret = PTR_ERR(imx8qxp_ldb->clk_pixel); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "failed to get pixel clock: %d\n", ret); + return ret; + } + + imx8qxp_ldb->clk_bypass = devm_clk_get(dev, "bypass"); + if (IS_ERR(imx8qxp_ldb->clk_bypass)) { + ret = PTR_ERR(imx8qxp_ldb->clk_bypass); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, + "failed to get bypass clock: %d\n", ret); + return ret; + } + + imx8qxp_ldb->dev = dev; + + ldb = &imx8qxp_ldb->base; + ldb->dev = dev; + ldb->ctrl_reg = 0xe0; + + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) + ldb->channel[i] = &imx8qxp_ldb->channel[i].base; + + ret = ldb_init_helper(ldb); + if (ret) + return ret; + + if (ldb->available_ch_cnt == 0) { + DRM_DEV_DEBUG_DRIVER(dev, "no available channel\n"); + return 0; + } else if (ldb->available_ch_cnt > 1) { + DRM_DEV_ERROR(dev, "invalid available channel number(%u)\n", + ldb->available_ch_cnt); + return -EINVAL; + } + + for (i = 0; i < MAX_LDB_CHAN_NUM; i++) { + imx8qxp_ldb_ch = &imx8qxp_ldb->channel[i]; + ldb_ch = &imx8qxp_ldb_ch->base; + + if (ldb_ch->is_available) { + imx8qxp_ldb->active_chno = ldb_ch->chno; + break; + } + } + + imx8qxp_ldb_ch->phy = devm_of_phy_get(dev, ldb_ch->np, "lvds_phy"); + if (IS_ERR(imx8qxp_ldb_ch->phy)) { + ret = PTR_ERR(imx8qxp_ldb_ch->phy); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get channel%d PHY: %d\n", + imx8qxp_ldb->active_chno, ret); + return ret; + } + + ret = ldb_find_next_bridge_helper(ldb); + if (ret) + return ret; + + ret = imx8qxp_ldb_set_di_id(imx8qxp_ldb); + if (ret) + return ret; + + ret = imx8qxp_ldb_parse_dt_companion(imx8qxp_ldb); + if (ret) + return ret; + + platform_set_drvdata(pdev, imx8qxp_ldb); + pm_runtime_enable(dev); + + ldb_add_bridge_helper(ldb, &imx8qxp_ldb_bridge_funcs); + + return ret; +} + +static int imx8qxp_ldb_remove(struct platform_device *pdev) +{ + struct imx8qxp_ldb *imx8qxp_ldb = platform_get_drvdata(pdev); + struct ldb *ldb = &imx8qxp_ldb->base; + + ldb_remove_bridge_helper(ldb); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused imx8qxp_ldb_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused imx8qxp_ldb_runtime_resume(struct device *dev) +{ + struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev); + struct ldb *ldb = &imx8qxp_ldb->base; + + /* disable LDB by resetting the control register to POR default */ + regmap_write(ldb->regmap, ldb->ctrl_reg, 0); + + return 0; +} + +static const struct dev_pm_ops imx8qxp_ldb_pm_ops = { + SET_RUNTIME_PM_OPS(imx8qxp_ldb_runtime_suspend, + imx8qxp_ldb_runtime_resume, NULL) +}; + +static const struct of_device_id imx8qxp_ldb_dt_ids[] = { + { .compatible = "fsl,imx8qxp-ldb" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx8qxp_ldb_dt_ids); + +static struct platform_driver imx8qxp_ldb_driver = { + .probe = imx8qxp_ldb_probe, + .remove = imx8qxp_ldb_remove, + .driver = { + .pm = &imx8qxp_ldb_pm_ops, + .name = DRIVER_NAME, + .of_match_table = imx8qxp_ldb_dt_ids, + }, +}; +module_platform_driver(imx8qxp_ldb_driver); + +MODULE_DESCRIPTION("i.MX8QXP LVDS Display Bridge(LDB)/Pixel Mapper bridge driver"); +MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c new file mode 100644 index 000000000000..503bd8db8afe --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020 NXP + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_print.h> + +#define PC_CTRL_REG 0x0 +#define PC_COMBINE_ENABLE BIT(0) +#define PC_DISP_BYPASS(n) BIT(1 + 21 * (n)) +#define PC_DISP_HSYNC_POLARITY(n) BIT(2 + 11 * (n)) +#define PC_DISP_HSYNC_POLARITY_POS(n) DISP_HSYNC_POLARITY(n) +#define PC_DISP_VSYNC_POLARITY(n) BIT(3 + 11 * (n)) +#define PC_DISP_VSYNC_POLARITY_POS(n) DISP_VSYNC_POLARITY(n) +#define PC_DISP_DVALID_POLARITY(n) BIT(4 + 11 * (n)) +#define PC_DISP_DVALID_POLARITY_POS(n) DISP_DVALID_POLARITY(n) +#define PC_VSYNC_MASK_ENABLE BIT(5) +#define PC_SKIP_MODE BIT(6) +#define PC_SKIP_NUMBER_MASK GENMASK(12, 7) +#define PC_SKIP_NUMBER(n) FIELD_PREP(PC_SKIP_NUMBER_MASK, (n)) +#define PC_DISP0_PIX_DATA_FORMAT_MASK GENMASK(18, 16) +#define PC_DISP0_PIX_DATA_FORMAT(fmt) \ + FIELD_PREP(PC_DISP0_PIX_DATA_FORMAT_MASK, (fmt)) +#define PC_DISP1_PIX_DATA_FORMAT_MASK GENMASK(21, 19) +#define PC_DISP1_PIX_DATA_FORMAT(fmt) \ + FIELD_PREP(PC_DISP1_PIX_DATA_FORMAT_MASK, (fmt)) + +#define PC_SW_RESET_REG 0x20 +#define PC_SW_RESET_N BIT(0) +#define PC_DISP_SW_RESET_N(n) BIT(1 + (n)) +#define PC_FULL_RESET_N (PC_SW_RESET_N | \ + PC_DISP_SW_RESET_N(0) | \ + PC_DISP_SW_RESET_N(1)) + +#define PC_REG_SET 0x4 +#define PC_REG_CLR 0x8 + +#define DRIVER_NAME "imx8qxp-pixel-combiner" + +enum imx8qxp_pc_pix_data_format { + RGB, + YUV444, + YUV422, + SPLIT_RGB, +}; + +struct imx8qxp_pc_channel { + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct imx8qxp_pc *pc; + unsigned int stream_id; + bool is_available; +}; + +struct imx8qxp_pc { + struct device *dev; + struct imx8qxp_pc_channel ch[2]; + struct clk *clk_apb; + void __iomem *base; +}; + +static inline u32 imx8qxp_pc_read(struct imx8qxp_pc *pc, unsigned int offset) +{ + return readl(pc->base + offset); +} + +static inline void +imx8qxp_pc_write(struct imx8qxp_pc *pc, unsigned int offset, u32 value) +{ + writel(value, pc->base + offset); +} + +static inline void +imx8qxp_pc_write_set(struct imx8qxp_pc *pc, unsigned int offset, u32 value) +{ + imx8qxp_pc_write(pc, offset + PC_REG_SET, value); +} + +static inline void +imx8qxp_pc_write_clr(struct imx8qxp_pc *pc, unsigned int offset, u32 value) +{ + imx8qxp_pc_write(pc, offset + PC_REG_CLR, value); +} + +static enum drm_mode_status +imx8qxp_pc_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->hdisplay > 2560) + return MODE_BAD_HVALUE; + + return MODE_OK; +} + +static int imx8qxp_pc_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct imx8qxp_pc_channel *ch = bridge->driver_private; + struct imx8qxp_pc *pc = ch->pc; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + DRM_DEV_ERROR(pc->dev, + "do not support creating a drm_connector\n"); + return -EINVAL; + } + + if (!bridge->encoder) { + DRM_DEV_ERROR(pc->dev, "missing encoder\n"); + return -ENODEV; + } + + return drm_bridge_attach(bridge->encoder, + ch->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); +} + +static void +imx8qxp_pc_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct imx8qxp_pc_channel *ch = bridge->driver_private; + struct imx8qxp_pc *pc = ch->pc; + u32 val; + int ret; + + ret = pm_runtime_get_sync(pc->dev); + if (ret < 0) + DRM_DEV_ERROR(pc->dev, + "failed to get runtime PM sync: %d\n", ret); + + ret = clk_prepare_enable(pc->clk_apb); + if (ret) + DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n", + __func__, ret); + + /* HSYNC to pixel link is active low. */ + imx8qxp_pc_write_clr(pc, PC_CTRL_REG, + PC_DISP_HSYNC_POLARITY(ch->stream_id)); + + /* VSYNC to pixel link is active low. */ + imx8qxp_pc_write_clr(pc, PC_CTRL_REG, + PC_DISP_VSYNC_POLARITY(ch->stream_id)); + + /* Data enable to pixel link is active high. */ + imx8qxp_pc_write_set(pc, PC_CTRL_REG, + PC_DISP_DVALID_POLARITY(ch->stream_id)); + + /* Mask the first frame output which may be incomplete. */ + imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_VSYNC_MASK_ENABLE); + + /* Only support RGB currently. */ + val = imx8qxp_pc_read(pc, PC_CTRL_REG); + if (ch->stream_id == 0) { + val &= ~PC_DISP0_PIX_DATA_FORMAT_MASK; + val |= PC_DISP0_PIX_DATA_FORMAT(RGB); + } else { + val &= ~PC_DISP1_PIX_DATA_FORMAT_MASK; + val |= PC_DISP1_PIX_DATA_FORMAT(RGB); + } + imx8qxp_pc_write(pc, PC_CTRL_REG, val); + + /* Only support bypass mode currently. */ + imx8qxp_pc_write_set(pc, PC_CTRL_REG, PC_DISP_BYPASS(ch->stream_id)); + + clk_disable_unprepare(pc->clk_apb); +} + +static void +imx8qxp_pc_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct imx8qxp_pc_channel *ch = bridge->driver_private; + struct imx8qxp_pc *pc = ch->pc; + int ret; + + ret = pm_runtime_put(pc->dev); + if (ret < 0) + DRM_DEV_ERROR(pc->dev, "failed to put runtime PM: %d\n", ret); +} + +static const u32 imx8qxp_pc_bus_output_fmts[] = { + MEDIA_BUS_FMT_RGB888_1X36_CPADLO, + MEDIA_BUS_FMT_RGB666_1X36_CPADLO, +}; + +static bool imx8qxp_pc_bus_output_fmt_supported(u32 fmt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx8qxp_pc_bus_output_fmts); i++) { + if (imx8qxp_pc_bus_output_fmts[i] == fmt) + return true; + } + + return false; +} + +static u32 * +imx8qxp_pc_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; + + if (!imx8qxp_pc_bus_output_fmt_supported(output_fmt)) + return NULL; + + *num_input_fmts = 1; + + input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + case MEDIA_BUS_FMT_RGB888_1X36_CPADLO: + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X30_CPADLO; + break; + case MEDIA_BUS_FMT_RGB666_1X36_CPADLO: + input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X30_CPADLO; + break; + default: + kfree(input_fmts); + input_fmts = NULL; + break; + } + + return input_fmts; +} + +static u32 * +imx8qxp_pc_bridge_atomic_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) +{ + *num_output_fmts = ARRAY_SIZE(imx8qxp_pc_bus_output_fmts); + return kmemdup(imx8qxp_pc_bus_output_fmts, + sizeof(imx8qxp_pc_bus_output_fmts), GFP_KERNEL); +} + +static const struct drm_bridge_funcs imx8qxp_pc_bridge_funcs = { + .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, + .mode_valid = imx8qxp_pc_bridge_mode_valid, + .attach = imx8qxp_pc_bridge_attach, + .mode_set = imx8qxp_pc_bridge_mode_set, + .atomic_disable = imx8qxp_pc_bridge_atomic_disable, + .atomic_get_input_bus_fmts = + imx8qxp_pc_bridge_atomic_get_input_bus_fmts, + .atomic_get_output_bus_fmts = + imx8qxp_pc_bridge_atomic_get_output_bus_fmts, +}; + +static int imx8qxp_pc_bridge_probe(struct platform_device *pdev) +{ + struct imx8qxp_pc *pc; + struct imx8qxp_pc_channel *ch; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child, *remote; + u32 i; + int ret; + + pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL); + if (!pc) + return -ENOMEM; + + pc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->dev = dev; + + pc->clk_apb = devm_clk_get(dev, "apb"); + if (IS_ERR(pc->clk_apb)) { + ret = PTR_ERR(pc->clk_apb); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get apb clock: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, pc); + pm_runtime_enable(dev); + + for_each_available_child_of_node(np, child) { + ret = of_property_read_u32(child, "reg", &i); + if (ret || i > 1) { + ret = -EINVAL; + DRM_DEV_ERROR(dev, + "invalid channel(%u) node address\n", i); + goto free_child; + } + + ch = &pc->ch[i]; + ch->pc = pc; + ch->stream_id = i; + + remote = of_graph_get_remote_node(child, 1, 0); + if (!remote) { + ret = -ENODEV; + DRM_DEV_ERROR(dev, + "channel%u failed to get port1's remote node: %d\n", + i, ret); + goto free_child; + } + + ch->next_bridge = of_drm_find_bridge(remote); + if (!ch->next_bridge) { + of_node_put(remote); + ret = -EPROBE_DEFER; + DRM_DEV_DEBUG_DRIVER(dev, + "channel%u failed to find next bridge: %d\n", + i, ret); + goto free_child; + } + + of_node_put(remote); + + ch->bridge.driver_private = ch; + ch->bridge.funcs = &imx8qxp_pc_bridge_funcs; + ch->bridge.of_node = child; + ch->is_available = true; + + drm_bridge_add(&ch->bridge); + } + + return 0; + +free_child: + of_node_put(child); + + if (i == 1 && pc->ch[0].next_bridge) + drm_bridge_remove(&pc->ch[0].bridge); + + pm_runtime_disable(dev); + return ret; +} + +static int imx8qxp_pc_bridge_remove(struct platform_device *pdev) +{ + struct imx8qxp_pc *pc = platform_get_drvdata(pdev); + struct imx8qxp_pc_channel *ch; + int i; + + for (i = 0; i < 2; i++) { + ch = &pc->ch[i]; + + if (!ch->is_available) + continue; + + drm_bridge_remove(&ch->bridge); + ch->is_available = false; + } + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused imx8qxp_pc_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct imx8qxp_pc *pc = platform_get_drvdata(pdev); + int ret; + + ret = clk_prepare_enable(pc->clk_apb); + if (ret) + DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n", + __func__, ret); + + /* Disable pixel combiner by full reset. */ + imx8qxp_pc_write_clr(pc, PC_SW_RESET_REG, PC_FULL_RESET_N); + + clk_disable_unprepare(pc->clk_apb); + + /* Ensure the reset takes effect. */ + usleep_range(10, 20); + + return ret; +} + +static int __maybe_unused imx8qxp_pc_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct imx8qxp_pc *pc = platform_get_drvdata(pdev); + int ret; + + ret = clk_prepare_enable(pc->clk_apb); + if (ret) { + DRM_DEV_ERROR(pc->dev, "%s: failed to enable apb clock: %d\n", + __func__, ret); + return ret; + } + + /* out of reset */ + imx8qxp_pc_write_set(pc, PC_SW_RESET_REG, PC_FULL_RESET_N); + + clk_disable_unprepare(pc->clk_apb); + + return ret; +} + +static const struct dev_pm_ops imx8qxp_pc_pm_ops = { + SET_RUNTIME_PM_OPS(imx8qxp_pc_runtime_suspend, + imx8qxp_pc_runtime_resume, NULL) +}; + +static const struct of_device_id imx8qxp_pc_dt_ids[] = { + { .compatible = "fsl,imx8qm-pixel-combiner", }, + { .compatible = "fsl,imx8qxp-pixel-combiner", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx8qxp_pc_dt_ids); + +static struct platform_driver imx8qxp_pc_bridge_driver = { + .probe = imx8qxp_pc_bridge_probe, + .remove = imx8qxp_pc_bridge_remove, + .driver = { + .pm = &imx8qxp_pc_pm_ops, + .name = DRIVER_NAME, + .of_match_table = imx8qxp_pc_dt_ids, + }, +}; +module_platform_driver(imx8qxp_pc_bridge_driver); + +MODULE_DESCRIPTION("i.MX8QM/QXP pixel combiner bridge driver"); +MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c new file mode 100644 index 000000000000..9e5f2b4dc2e5 --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020,2022 NXP + */ + +#include <linux/firmware/imx/svc/misc.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_print.h> + +#include <dt-bindings/firmware/imx/rsrc.h> + +#define DRIVER_NAME "imx8qxp-display-pixel-link" +#define PL_MAX_MST_ADDR 3 +#define PL_MAX_NEXT_BRIDGES 2 + +struct imx8qxp_pixel_link { + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct device *dev; + struct imx_sc_ipc *ipc_handle; + u8 stream_id; + u8 dc_id; + u32 sink_rsc; + u32 mst_addr; + u8 mst_addr_ctrl; + u8 mst_en_ctrl; + u8 mst_vld_ctrl; + u8 sync_ctrl; +}; + +static void imx8qxp_pixel_link_enable_mst_en(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc, + pl->mst_en_ctrl, true); + if (ret) + DRM_DEV_ERROR(pl->dev, + "failed to enable DC%u stream%u pixel link mst_en: %d\n", + pl->dc_id, pl->stream_id, ret); +} + +static void imx8qxp_pixel_link_enable_mst_vld(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc, + pl->mst_vld_ctrl, true); + if (ret) + DRM_DEV_ERROR(pl->dev, + "failed to enable DC%u stream%u pixel link mst_vld: %d\n", + pl->dc_id, pl->stream_id, ret); +} + +static void imx8qxp_pixel_link_enable_sync(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc, + pl->sync_ctrl, true); + if (ret) + DRM_DEV_ERROR(pl->dev, + "failed to enable DC%u stream%u pixel link sync: %d\n", + pl->dc_id, pl->stream_id, ret); +} + +static int imx8qxp_pixel_link_disable_mst_en(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc, + pl->mst_en_ctrl, false); + if (ret) + DRM_DEV_ERROR(pl->dev, + "failed to disable DC%u stream%u pixel link mst_en: %d\n", + pl->dc_id, pl->stream_id, ret); + + return ret; +} + +static int imx8qxp_pixel_link_disable_mst_vld(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc, + pl->mst_vld_ctrl, false); + if (ret) + DRM_DEV_ERROR(pl->dev, + "failed to disable DC%u stream%u pixel link mst_vld: %d\n", + pl->dc_id, pl->stream_id, ret); + + return ret; +} + +static int imx8qxp_pixel_link_disable_sync(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx_sc_misc_set_control(pl->ipc_handle, pl->sink_rsc, + pl->sync_ctrl, false); + if (ret) + DRM_DEV_ERROR(pl->dev, + "failed to disable DC%u stream%u pixel link sync: %d\n", + pl->dc_id, pl->stream_id, ret); + + return ret; +} + +static void imx8qxp_pixel_link_set_mst_addr(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx_sc_misc_set_control(pl->ipc_handle, + pl->sink_rsc, pl->mst_addr_ctrl, + pl->mst_addr); + if (ret) + DRM_DEV_ERROR(pl->dev, + "failed to set DC%u stream%u pixel link mst addr(%u): %d\n", + pl->dc_id, pl->stream_id, pl->mst_addr, ret); +} + +static int imx8qxp_pixel_link_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct imx8qxp_pixel_link *pl = bridge->driver_private; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + DRM_DEV_ERROR(pl->dev, + "do not support creating a drm_connector\n"); + return -EINVAL; + } + + if (!bridge->encoder) { + DRM_DEV_ERROR(pl->dev, "missing encoder\n"); + return -ENODEV; + } + + return drm_bridge_attach(bridge->encoder, + pl->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); +} + +static void +imx8qxp_pixel_link_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct imx8qxp_pixel_link *pl = bridge->driver_private; + + imx8qxp_pixel_link_set_mst_addr(pl); +} + +static void +imx8qxp_pixel_link_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct imx8qxp_pixel_link *pl = bridge->driver_private; + + imx8qxp_pixel_link_enable_mst_en(pl); + imx8qxp_pixel_link_enable_mst_vld(pl); + imx8qxp_pixel_link_enable_sync(pl); +} + +static void +imx8qxp_pixel_link_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct imx8qxp_pixel_link *pl = bridge->driver_private; + + imx8qxp_pixel_link_disable_mst_en(pl); + imx8qxp_pixel_link_disable_mst_vld(pl); + imx8qxp_pixel_link_disable_sync(pl); +} + +static const u32 imx8qxp_pixel_link_bus_output_fmts[] = { + MEDIA_BUS_FMT_RGB888_1X36_CPADLO, + MEDIA_BUS_FMT_RGB666_1X36_CPADLO, +}; + +static bool imx8qxp_pixel_link_bus_output_fmt_supported(u32 fmt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx8qxp_pixel_link_bus_output_fmts); i++) { + if (imx8qxp_pixel_link_bus_output_fmts[i] == fmt) + return true; + } + + return false; +} + +static u32 * +imx8qxp_pixel_link_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; + + if (!imx8qxp_pixel_link_bus_output_fmt_supported(output_fmt)) + return NULL; + + *num_input_fmts = 1; + + input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + + input_fmts[0] = output_fmt; + + return input_fmts; +} + +static u32 * +imx8qxp_pixel_link_bridge_atomic_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) +{ + *num_output_fmts = ARRAY_SIZE(imx8qxp_pixel_link_bus_output_fmts); + return kmemdup(imx8qxp_pixel_link_bus_output_fmts, + sizeof(imx8qxp_pixel_link_bus_output_fmts), GFP_KERNEL); +} + +static const struct drm_bridge_funcs imx8qxp_pixel_link_bridge_funcs = { + .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, + .attach = imx8qxp_pixel_link_bridge_attach, + .mode_set = imx8qxp_pixel_link_bridge_mode_set, + .atomic_enable = imx8qxp_pixel_link_bridge_atomic_enable, + .atomic_disable = imx8qxp_pixel_link_bridge_atomic_disable, + .atomic_get_input_bus_fmts = + imx8qxp_pixel_link_bridge_atomic_get_input_bus_fmts, + .atomic_get_output_bus_fmts = + imx8qxp_pixel_link_bridge_atomic_get_output_bus_fmts, +}; + +static int imx8qxp_pixel_link_disable_all_controls(struct imx8qxp_pixel_link *pl) +{ + int ret; + + ret = imx8qxp_pixel_link_disable_mst_en(pl); + if (ret) + return ret; + + ret = imx8qxp_pixel_link_disable_mst_vld(pl); + if (ret) + return ret; + + return imx8qxp_pixel_link_disable_sync(pl); +} + +static struct drm_bridge * +imx8qxp_pixel_link_find_next_bridge(struct imx8qxp_pixel_link *pl) +{ + struct device_node *np = pl->dev->of_node; + struct device_node *port, *remote; + struct drm_bridge *next_bridge[PL_MAX_NEXT_BRIDGES]; + u32 port_id; + bool found_port = false; + int reg, ep_cnt = 0; + /* select the first next bridge by default */ + int bridge_sel = 0; + + for (port_id = 1; port_id <= PL_MAX_MST_ADDR + 1; port_id++) { + port = of_graph_get_port_by_id(np, port_id); + if (!port) + continue; + + if (of_device_is_available(port)) { + found_port = true; + of_node_put(port); + break; + } + + of_node_put(port); + } + + if (!found_port) { + DRM_DEV_ERROR(pl->dev, "no available output port\n"); + return ERR_PTR(-ENODEV); + } + + for (reg = 0; reg < PL_MAX_NEXT_BRIDGES; reg++) { + remote = of_graph_get_remote_node(np, port_id, reg); + if (!remote) + continue; + + if (!of_device_is_available(remote->parent)) { + DRM_DEV_DEBUG(pl->dev, + "port%u endpoint%u remote parent is not available\n", + port_id, reg); + of_node_put(remote); + continue; + } + + next_bridge[ep_cnt] = of_drm_find_bridge(remote); + if (!next_bridge[ep_cnt]) { + of_node_put(remote); + return ERR_PTR(-EPROBE_DEFER); + } + + /* specially select the next bridge with companion PXL2DPI */ + if (of_find_property(remote, "fsl,companion-pxl2dpi", NULL)) + bridge_sel = ep_cnt; + + ep_cnt++; + + of_node_put(remote); + } + + pl->mst_addr = port_id - 1; + + return next_bridge[bridge_sel]; +} + +static int imx8qxp_pixel_link_bridge_probe(struct platform_device *pdev) +{ + struct imx8qxp_pixel_link *pl; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + int ret; + + pl = devm_kzalloc(dev, sizeof(*pl), GFP_KERNEL); + if (!pl) + return -ENOMEM; + + ret = imx_scu_get_handle(&pl->ipc_handle); + if (ret) { + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n", + ret); + return ret; + } + + ret = of_property_read_u8(np, "fsl,dc-id", &pl->dc_id); + if (ret) { + DRM_DEV_ERROR(dev, "failed to get DC index: %d\n", ret); + return ret; + } + + ret = of_property_read_u8(np, "fsl,dc-stream-id", &pl->stream_id); + if (ret) { + DRM_DEV_ERROR(dev, "failed to get DC stream index: %d\n", ret); + return ret; + } + + pl->dev = dev; + + pl->sink_rsc = pl->dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0; + + if (pl->stream_id == 0) { + pl->mst_addr_ctrl = IMX_SC_C_PXL_LINK_MST1_ADDR; + pl->mst_en_ctrl = IMX_SC_C_PXL_LINK_MST1_ENB; + pl->mst_vld_ctrl = IMX_SC_C_PXL_LINK_MST1_VLD; + pl->sync_ctrl = IMX_SC_C_SYNC_CTRL0; + } else { + pl->mst_addr_ctrl = IMX_SC_C_PXL_LINK_MST2_ADDR; + pl->mst_en_ctrl = IMX_SC_C_PXL_LINK_MST2_ENB; + pl->mst_vld_ctrl = IMX_SC_C_PXL_LINK_MST2_VLD; + pl->sync_ctrl = IMX_SC_C_SYNC_CTRL1; + } + + /* disable all controls to POR default */ + ret = imx8qxp_pixel_link_disable_all_controls(pl); + if (ret) + return ret; + + pl->next_bridge = imx8qxp_pixel_link_find_next_bridge(pl); + if (IS_ERR(pl->next_bridge)) { + ret = PTR_ERR(pl->next_bridge); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, pl); + + pl->bridge.driver_private = pl; + pl->bridge.funcs = &imx8qxp_pixel_link_bridge_funcs; + pl->bridge.of_node = np; + + drm_bridge_add(&pl->bridge); + + return ret; +} + +static int imx8qxp_pixel_link_bridge_remove(struct platform_device *pdev) +{ + struct imx8qxp_pixel_link *pl = platform_get_drvdata(pdev); + + drm_bridge_remove(&pl->bridge); + + return 0; +} + +static const struct of_device_id imx8qxp_pixel_link_dt_ids[] = { + { .compatible = "fsl,imx8qm-dc-pixel-link", }, + { .compatible = "fsl,imx8qxp-dc-pixel-link", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx8qxp_pixel_link_dt_ids); + +static struct platform_driver imx8qxp_pixel_link_bridge_driver = { + .probe = imx8qxp_pixel_link_bridge_probe, + .remove = imx8qxp_pixel_link_bridge_remove, + .driver = { + .of_match_table = imx8qxp_pixel_link_dt_ids, + .name = DRIVER_NAME, + }, +}; +module_platform_driver(imx8qxp_pixel_link_bridge_driver); + +MODULE_DESCRIPTION("i.MX8QXP/QM display pixel link bridge driver"); +MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c new file mode 100644 index 000000000000..d0fec82f0cf8 --- /dev/null +++ b/drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2020 NXP + */ + +#include <linux/firmware/imx/svc/misc.h> +#include <linux/media-bus-format.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> + +#include <dt-bindings/firmware/imx/rsrc.h> + +#define PXL2DPI_CTRL 0x40 +#define CFG1_16BIT 0x0 +#define CFG2_16BIT 0x1 +#define CFG3_16BIT 0x2 +#define CFG1_18BIT 0x3 +#define CFG2_18BIT 0x4 +#define CFG_24BIT 0x5 + +#define DRIVER_NAME "imx8qxp-pxl2dpi" + +struct imx8qxp_pxl2dpi { + struct regmap *regmap; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct drm_bridge *companion; + struct device *dev; + struct imx_sc_ipc *ipc_handle; + u32 sc_resource; + u32 in_bus_format; + u32 out_bus_format; + u32 pl_sel; +}; + +#define bridge_to_p2d(b) container_of(b, struct imx8qxp_pxl2dpi, bridge) + +static int imx8qxp_pxl2dpi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + DRM_DEV_ERROR(p2d->dev, + "do not support creating a drm_connector\n"); + return -EINVAL; + } + + if (!bridge->encoder) { + DRM_DEV_ERROR(p2d->dev, "missing encoder\n"); + return -ENODEV; + } + + return drm_bridge_attach(bridge->encoder, + p2d->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); +} + +static int +imx8qxp_pxl2dpi_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 imx8qxp_pxl2dpi *p2d = bridge->driver_private; + + p2d->in_bus_format = bridge_state->input_bus_cfg.format; + p2d->out_bus_format = bridge_state->output_bus_cfg.format; + + return 0; +} + +static void +imx8qxp_pxl2dpi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; + struct imx8qxp_pxl2dpi *companion_p2d; + int ret; + + ret = pm_runtime_get_sync(p2d->dev); + if (ret < 0) + DRM_DEV_ERROR(p2d->dev, + "failed to get runtime PM sync: %d\n", ret); + + ret = imx_sc_misc_set_control(p2d->ipc_handle, p2d->sc_resource, + IMX_SC_C_PXL_LINK_SEL, p2d->pl_sel); + if (ret) + DRM_DEV_ERROR(p2d->dev, + "failed to set pixel link selection(%u): %d\n", + p2d->pl_sel, ret); + + switch (p2d->out_bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG_24BIT); + break; + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + regmap_write(p2d->regmap, PXL2DPI_CTRL, CFG2_18BIT); + break; + default: + DRM_DEV_ERROR(p2d->dev, + "unsupported output bus format 0x%08x\n", + p2d->out_bus_format); + } + + if (p2d->companion) { + companion_p2d = bridge_to_p2d(p2d->companion); + + companion_p2d->in_bus_format = p2d->in_bus_format; + companion_p2d->out_bus_format = p2d->out_bus_format; + + p2d->companion->funcs->mode_set(p2d->companion, mode, + adjusted_mode); + } +} + +static void +imx8qxp_pxl2dpi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct imx8qxp_pxl2dpi *p2d = bridge->driver_private; + int ret; + + ret = pm_runtime_put(p2d->dev); + if (ret < 0) + DRM_DEV_ERROR(p2d->dev, "failed to put runtime PM: %d\n", ret); + + if (p2d->companion) + p2d->companion->funcs->atomic_disable(p2d->companion, + old_bridge_state); +} + +static const u32 imx8qxp_pxl2dpi_bus_output_fmts[] = { + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_RGB666_1X24_CPADHI, +}; + +static bool imx8qxp_pxl2dpi_bus_output_fmt_supported(u32 fmt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); i++) { + if (imx8qxp_pxl2dpi_bus_output_fmts[i] == fmt) + return true; + } + + return false; +} + +static u32 * +imx8qxp_pxl2dpi_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; + + if (!imx8qxp_pxl2dpi_bus_output_fmt_supported(output_fmt)) + return NULL; + + *num_input_fmts = 1; + + input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + case MEDIA_BUS_FMT_RGB888_1X24: + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO; + break; + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO; + break; + default: + kfree(input_fmts); + input_fmts = NULL; + break; + } + + return input_fmts; +} + +static u32 * +imx8qxp_pxl2dpi_bridge_atomic_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) +{ + *num_output_fmts = ARRAY_SIZE(imx8qxp_pxl2dpi_bus_output_fmts); + return kmemdup(imx8qxp_pxl2dpi_bus_output_fmts, + sizeof(imx8qxp_pxl2dpi_bus_output_fmts), GFP_KERNEL); +} + +static const struct drm_bridge_funcs imx8qxp_pxl2dpi_bridge_funcs = { + .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, + .attach = imx8qxp_pxl2dpi_bridge_attach, + .atomic_check = imx8qxp_pxl2dpi_bridge_atomic_check, + .mode_set = imx8qxp_pxl2dpi_bridge_mode_set, + .atomic_disable = imx8qxp_pxl2dpi_bridge_atomic_disable, + .atomic_get_input_bus_fmts = + imx8qxp_pxl2dpi_bridge_atomic_get_input_bus_fmts, + .atomic_get_output_bus_fmts = + imx8qxp_pxl2dpi_bridge_atomic_get_output_bus_fmts, +}; + +static struct device_node * +imx8qxp_pxl2dpi_get_available_ep_from_port(struct imx8qxp_pxl2dpi *p2d, + u32 port_id) +{ + struct device_node *port, *ep; + int ep_cnt; + + port = of_graph_get_port_by_id(p2d->dev->of_node, port_id); + if (!port) { + DRM_DEV_ERROR(p2d->dev, "failed to get port@%u\n", port_id); + return ERR_PTR(-ENODEV); + } + + ep_cnt = of_get_available_child_count(port); + if (ep_cnt == 0) { + DRM_DEV_ERROR(p2d->dev, "no available endpoints of port@%u\n", + port_id); + ep = ERR_PTR(-ENODEV); + goto out; + } else if (ep_cnt > 1) { + DRM_DEV_ERROR(p2d->dev, + "invalid available endpoints of port@%u\n", + port_id); + ep = ERR_PTR(-EINVAL); + goto out; + } + + ep = of_get_next_available_child(port, NULL); + if (!ep) { + DRM_DEV_ERROR(p2d->dev, + "failed to get available endpoint of port@%u\n", + port_id); + ep = ERR_PTR(-ENODEV); + goto out; + } +out: + of_node_put(port); + return ep; +} + +static struct drm_bridge * +imx8qxp_pxl2dpi_find_next_bridge(struct imx8qxp_pxl2dpi *p2d) +{ + struct device_node *ep, *remote; + struct drm_bridge *next_bridge; + int ret; + + ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 1); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + return ERR_PTR(ret); + } + + remote = of_graph_get_remote_port_parent(ep); + if (!remote || !of_device_is_available(remote)) { + DRM_DEV_ERROR(p2d->dev, "no available remote\n"); + next_bridge = ERR_PTR(-ENODEV); + goto out; + } else if (!of_device_is_available(remote->parent)) { + DRM_DEV_ERROR(p2d->dev, "remote parent is not available\n"); + next_bridge = ERR_PTR(-ENODEV); + goto out; + } + + next_bridge = of_drm_find_bridge(remote); + if (!next_bridge) { + next_bridge = ERR_PTR(-EPROBE_DEFER); + goto out; + } +out: + of_node_put(remote); + of_node_put(ep); + + return next_bridge; +} + +static int imx8qxp_pxl2dpi_set_pixel_link_sel(struct imx8qxp_pxl2dpi *p2d) +{ + struct device_node *ep; + struct of_endpoint endpoint; + int ret; + + ep = imx8qxp_pxl2dpi_get_available_ep_from_port(p2d, 0); + if (IS_ERR(ep)) + return PTR_ERR(ep); + + ret = of_graph_parse_endpoint(ep, &endpoint); + if (ret) { + DRM_DEV_ERROR(p2d->dev, + "failed to parse endpoint of port@0: %d\n", ret); + goto out; + } + + p2d->pl_sel = endpoint.id; +out: + of_node_put(ep); + + return ret; +} + +static int imx8qxp_pxl2dpi_parse_dt_companion(struct imx8qxp_pxl2dpi *p2d) +{ + struct imx8qxp_pxl2dpi *companion_p2d; + struct device *dev = p2d->dev; + struct device_node *companion; + struct device_node *port1, *port2; + const struct of_device_id *match; + int dual_link; + int ret = 0; + + /* Locate the companion PXL2DPI for dual-link operation, if any. */ + companion = of_parse_phandle(dev->of_node, "fsl,companion-pxl2dpi", 0); + if (!companion) + return 0; + + if (!of_device_is_available(companion)) { + DRM_DEV_ERROR(dev, "companion PXL2DPI is not available\n"); + ret = -ENODEV; + goto out; + } + + /* + * Sanity check: the companion bridge must have the same compatible + * string. + */ + match = of_match_device(dev->driver->of_match_table, dev); + if (!of_device_is_compatible(companion, match->compatible)) { + DRM_DEV_ERROR(dev, "companion PXL2DPI is incompatible\n"); + ret = -ENXIO; + goto out; + } + + p2d->companion = of_drm_find_bridge(companion); + if (!p2d->companion) { + ret = -EPROBE_DEFER; + DRM_DEV_DEBUG_DRIVER(p2d->dev, + "failed to find companion bridge: %d\n", + ret); + goto out; + } + + companion_p2d = bridge_to_p2d(p2d->companion); + + /* + * We need to work out if the sink is expecting us to function in + * dual-link mode. We do this by looking at the DT port nodes that + * the next bridges are connected to. If they are marked as expecting + * even pixels and odd pixels than we need to use the companion PXL2DPI. + */ + port1 = of_graph_get_port_by_id(p2d->next_bridge->of_node, 1); + port2 = of_graph_get_port_by_id(companion_p2d->next_bridge->of_node, 1); + dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2); + of_node_put(port1); + of_node_put(port2); + + if (dual_link < 0) { + ret = dual_link; + DRM_DEV_ERROR(dev, "failed to get dual link pixel order: %d\n", + ret); + goto out; + } + + DRM_DEV_DEBUG_DRIVER(dev, + "dual-link configuration detected (companion bridge %pOF)\n", + companion); +out: + of_node_put(companion); + return ret; +} + +static int imx8qxp_pxl2dpi_bridge_probe(struct platform_device *pdev) +{ + struct imx8qxp_pxl2dpi *p2d; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + int ret; + + p2d = devm_kzalloc(dev, sizeof(*p2d), GFP_KERNEL); + if (!p2d) + return -ENOMEM; + + p2d->regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(p2d->regmap)) { + ret = PTR_ERR(p2d->regmap); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get regmap: %d\n", ret); + return ret; + } + + ret = imx_scu_get_handle(&p2d->ipc_handle); + if (ret) { + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to get SCU ipc handle: %d\n", + ret); + return ret; + } + + p2d->dev = dev; + + ret = of_property_read_u32(np, "fsl,sc-resource", &p2d->sc_resource); + if (ret) { + DRM_DEV_ERROR(dev, "failed to get SC resource %d\n", ret); + return ret; + } + + p2d->next_bridge = imx8qxp_pxl2dpi_find_next_bridge(p2d); + if (IS_ERR(p2d->next_bridge)) { + ret = PTR_ERR(p2d->next_bridge); + if (ret != -EPROBE_DEFER) + DRM_DEV_ERROR(dev, "failed to find next bridge: %d\n", + ret); + return ret; + } + + ret = imx8qxp_pxl2dpi_set_pixel_link_sel(p2d); + if (ret) + return ret; + + ret = imx8qxp_pxl2dpi_parse_dt_companion(p2d); + if (ret) + return ret; + + platform_set_drvdata(pdev, p2d); + pm_runtime_enable(dev); + + p2d->bridge.driver_private = p2d; + p2d->bridge.funcs = &imx8qxp_pxl2dpi_bridge_funcs; + p2d->bridge.of_node = np; + + drm_bridge_add(&p2d->bridge); + + return ret; +} + +static int imx8qxp_pxl2dpi_bridge_remove(struct platform_device *pdev) +{ + struct imx8qxp_pxl2dpi *p2d = platform_get_drvdata(pdev); + + drm_bridge_remove(&p2d->bridge); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id imx8qxp_pxl2dpi_dt_ids[] = { + { .compatible = "fsl,imx8qxp-pxl2dpi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx8qxp_pxl2dpi_dt_ids); + +static struct platform_driver imx8qxp_pxl2dpi_bridge_driver = { + .probe = imx8qxp_pxl2dpi_bridge_probe, + .remove = imx8qxp_pxl2dpi_bridge_remove, + .driver = { + .of_match_table = imx8qxp_pxl2dpi_dt_ids, + .name = DRIVER_NAME, + }, +}; +module_platform_driver(imx8qxp_pxl2dpi_bridge_driver); + +MODULE_DESCRIPTION("i.MX8QXP pixel link to DPI bridge driver"); +MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/ite-it66121.c b/drivers/gpu/drm/bridge/ite-it66121.c index 448c58e60c11..44278d54d35d 100644 --- a/drivers/gpu/drm/bridge/ite-it66121.c +++ b/drivers/gpu/drm/bridge/ite-it66121.c @@ -7,6 +7,7 @@ * */ +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/device.h> #include <linux/interrupt.h> diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c index c642d1e02b2f..28bad30dc4e5 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c @@ -7,10 +7,12 @@ #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/regmap.h> #include <drm/drm_probe_helper.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_edid.h> #include <drm/drm_mipi_dsi.h> #include <drm/drm_of.h> @@ -607,7 +609,6 @@ static int lt8912_parse_dt(struct lt8912 *lt) int ret; int data_lanes; struct device_node *port_node; - struct device_node *endpoint; gp_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(gp_reset)) { @@ -618,16 +619,12 @@ static int lt8912_parse_dt(struct lt8912 *lt) } lt->gp_reset = gp_reset; - endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); - if (!endpoint) - return -ENODEV; - - data_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); - of_node_put(endpoint); + data_lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, -1, 1, 4); if (data_lanes < 0) { dev_err(lt->dev, "%s: Bad data-lanes property\n", __func__); return data_lanes; } + lt->data_lanes = data_lanes; lt->host_node = of_graph_get_remote_node(dev->of_node, 0, -1); diff --git a/drivers/gpu/drm/bridge/lontium-lt9211.c b/drivers/gpu/drm/bridge/lontium-lt9211.c index e92821fbc639..9a3e90427d12 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9211.c +++ b/drivers/gpu/drm/bridge/lontium-lt9211.c @@ -14,6 +14,7 @@ #include <linux/clk.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/of_graph.h> @@ -686,7 +687,7 @@ static int lt9211_host_attach(struct lt9211 *ctx) int ret; endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); - dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); + dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); host_node = of_graph_get_remote_port_parent(endpoint); host = of_find_mipi_dsi_host_by_node(host_node); of_node_put(host_node); @@ -695,8 +696,8 @@ static int lt9211_host_attach(struct lt9211 *ctx) if (!host) return -EPROBE_DEFER; - if (dsi_lanes < 0 || dsi_lanes > 4) - return -EINVAL; + if (dsi_lanes < 0) + return dsi_lanes; dsi = devm_mipi_dsi_device_register_full(dev, host, &info); if (IS_ERR(dsi)) diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c index 7ef8fe5abc12..8a60e83482a0 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -5,7 +5,9 @@ */ #include <linux/gpio/consumer.h> +#include <linux/i2c.h> #include <linux/interrupt.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/of_graph.h> #include <linux/platform_device.h> @@ -578,15 +580,13 @@ static struct lt9611_mode *lt9611_find_mode(const struct drm_display_mode *mode) } /* connector funcs */ -static enum drm_connector_status -lt9611_connector_detect(struct drm_connector *connector, bool force) +static enum drm_connector_status __lt9611_detect(struct lt9611 *lt9611) { - struct lt9611 *lt9611 = connector_to_lt9611(connector); unsigned int reg_val = 0; int connected = 0; regmap_read(lt9611->regmap, 0x825e, ®_val); - connected = (reg_val & BIT(0)); + connected = (reg_val & (BIT(2) | BIT(0))); lt9611->status = connected ? connector_status_connected : connector_status_disconnected; @@ -594,6 +594,12 @@ lt9611_connector_detect(struct drm_connector *connector, bool force) return lt9611->status; } +static enum drm_connector_status +lt9611_connector_detect(struct drm_connector *connector, bool force) +{ + return __lt9611_detect(connector_to_lt9611(connector)); +} + static int lt9611_read_edid(struct lt9611 *lt9611) { unsigned int temp; @@ -893,17 +899,7 @@ static void lt9611_bridge_mode_set(struct drm_bridge *bridge, static enum drm_connector_status lt9611_bridge_detect(struct drm_bridge *bridge) { - struct lt9611 *lt9611 = bridge_to_lt9611(bridge); - unsigned int reg_val = 0; - int connected; - - regmap_read(lt9611->regmap, 0x825e, ®_val); - connected = reg_val & BIT(0); - - lt9611->status = connected ? connector_status_connected : - connector_status_disconnected; - - return lt9611->status; + return __lt9611_detect(bridge_to_lt9611(bridge)); } static struct edid *lt9611_bridge_get_edid(struct drm_bridge *bridge, diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 3d62e6bf6892..fdf12d4c6416 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -6,6 +6,7 @@ #include <linux/firmware.h> #include <linux/gpio/consumer.h> +#include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/mutex.h> @@ -982,7 +983,7 @@ static int lt9611uxc_remove(struct i2c_client *client) struct lt9611uxc *lt9611uxc = i2c_get_clientdata(client); disable_irq(client->irq); - flush_scheduled_work(); + cancel_work_sync(<9611uxc->work); lt9611uxc_audio_exit(lt9611uxc); drm_bridge_remove(<9611uxc->bridge); diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c index 702ea803a743..39e7004de720 100644 --- a/drivers/gpu/drm/bridge/lvds-codec.c +++ b/drivers/gpu/drm/bridge/lvds-codec.c @@ -180,7 +180,7 @@ static int lvds_codec_probe(struct platform_device *pdev) of_node_put(bus_node); if (ret == -ENODEV) { dev_warn(dev, "missing 'data-mapping' DT property\n"); - } else if (ret) { + } else if (ret < 0) { dev_err(dev, "invalid 'data-mapping' DT property\n"); return ret; } else { diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c index ad74e6558eb3..6dc2a4e191d7 100644 --- a/drivers/gpu/drm/bridge/nwl-dsi.c +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -12,6 +12,7 @@ #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> @@ -665,6 +666,12 @@ static int nwl_dsi_mode_set(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); diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 0ee563eb2b6f..4277bf4f032b 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -171,6 +171,19 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = { }; /** + * drm_bridge_is_panel - Checks if a drm_bridge is a panel_bridge. + * + * @bridge: The drm_bridge to be checked. + * + * Returns true if the bridge is a panel bridge, or false otherwise. + */ +bool drm_bridge_is_panel(const struct drm_bridge *bridge) +{ + return bridge->funcs == &panel_bridge_bridge_funcs; +} +EXPORT_SYMBOL(drm_bridge_is_panel); + +/** * drm_panel_bridge_add - Creates a &drm_bridge and &drm_connector that * just calls the appropriate functions from &drm_panel. * @@ -269,6 +282,27 @@ void drm_panel_bridge_remove(struct drm_bridge *bridge) } EXPORT_SYMBOL(drm_panel_bridge_remove); +/** + * drm_panel_bridge_set_orientation - Set the connector's panel orientation + * from the bridge that can be transformed to panel bridge. + * + * @connector: The connector to be set panel orientation. + * @bridge: The drm_bridge to be transformed to panel bridge. + * + * Returns 0 on success, negative errno on failure. + */ +int drm_panel_bridge_set_orientation(struct drm_connector *connector, + struct drm_bridge *bridge) +{ + struct panel_bridge *panel_bridge; + + panel_bridge = drm_bridge_to_panel_bridge(bridge); + + return drm_connector_set_orientation_from_panel(connector, + panel_bridge->panel); +} +EXPORT_SYMBOL(drm_panel_bridge_set_orientation); + static void devm_drm_panel_bridge_release(struct device *dev, void *res) { struct drm_bridge **bridge = res; diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c index 37b308850b4e..b5750e5f71d7 100644 --- a/drivers/gpu/drm/bridge/parade-ps8622.c +++ b/drivers/gpu/drm/bridge/parade-ps8622.c @@ -324,11 +324,7 @@ error: static int ps8622_backlight_update(struct backlight_device *bl) { struct ps8622_bridge *ps8622 = dev_get_drvdata(&bl->dev); - int ret, brightness = bl->props.brightness; - - if (bl->props.power != FB_BLANK_UNBLANK || - bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) - brightness = 0; + int ret, brightness = backlight_get_brightness(bl); if (!ps8622->enabled) return -EINVAL; diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c index edb939b14c04..31e88cb39f8a 100644 --- a/drivers/gpu/drm/bridge/parade-ps8640.c +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -16,6 +16,7 @@ #include <drm/display/drm_dp_aux_bus.h> #include <drm/display/drm_dp_helper.h> #include <drm/drm_bridge.h> +#include <drm/drm_edid.h> #include <drm/drm_mipi_dsi.h> #include <drm/drm_of.h> #include <drm/drm_panel.h> @@ -168,23 +169,35 @@ static bool ps8640_of_panel_on_aux_bus(struct device *dev) return true; } -static int ps8640_ensure_hpd(struct ps8640 *ps_bridge) +static int _ps8640_wait_hpd_asserted(struct ps8640 *ps_bridge, unsigned long wait_us) { 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); + return regmap_read_poll_timeout(map, PAGE2_GPIO_H, status, + status & PS_GPIO9, wait_us / 10, wait_us); +} - if (ret < 0) - dev_warn(dev, "HPD didn't go high: %d\n", ret); +static int ps8640_wait_hpd_asserted(struct drm_dp_aux *aux, unsigned long wait_us) +{ + struct ps8640 *ps_bridge = aux_to_ps8640(aux); + struct device *dev = &ps_bridge->page[PAGE0_DP_CNTL]->dev; + int ret; + + /* + * Note that this function is called by code that has already powered + * the panel. We have to power ourselves up but we don't need to worry + * about powering the panel. + */ + pm_runtime_get_sync(dev); + ret = _ps8640_wait_hpd_asserted(ps_bridge, wait_us); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); return ret; } @@ -323,9 +336,7 @@ static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux, int ret; pm_runtime_get_sync(dev); - ret = ps8640_ensure_hpd(ps_bridge); - if (!ret) - ret = ps8640_aux_transfer_msg(aux, msg); + ret = ps8640_aux_transfer_msg(aux, msg); pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); @@ -369,8 +380,8 @@ static int __maybe_unused ps8640_resume(struct device *dev) * 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. + * _ps8640_wait_hpd_asserted(). For now we'll keep this mystery delay + * just in case. */ msleep(200); @@ -406,7 +417,9 @@ static void ps8640_pre_enable(struct drm_bridge *bridge) int ret; pm_runtime_get_sync(dev); - ps8640_ensure_hpd(ps_bridge); + ret = _ps8640_wait_hpd_asserted(ps_bridge, 200 * 1000); + if (ret < 0) + dev_warn(dev, "HPD didn't go high: %d\n", ret); /* * The Manufacturer Command Set (MCS) is a device dependent interface @@ -537,12 +550,11 @@ 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) +static int ps8640_bridge_get_dsi_resources(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, @@ -577,17 +589,40 @@ static int ps8640_bridge_host_attach(struct device *dev, struct ps8640 *ps_bridg dsi->format = MIPI_DSI_FMT_RGB888; dsi->lanes = NUM_MIPI_LANES; - ret = devm_mipi_dsi_attach(dev, dsi); + return 0; +} + +static int ps8640_bridge_link_panel(struct drm_dp_aux *aux) +{ + struct ps8640 *ps_bridge = aux_to_ps8640(aux); + struct device *dev = aux->dev; + struct device_node *np = dev->of_node; + int ret; + + /* + * NOTE about returning -EPROBE_DEFER from this function: if we + * return an error (most relevant to -EPROBE_DEFER) it will only + * be passed out to ps8640_probe() if it called this directly (AKA the + * panel isn't under the "aux-bus" node). That should be fine because + * if the panel is under "aux-bus" it's guaranteed to have probed by + * the time this function has been called. + */ + + /* port@1 is ps8640 output port */ + ps_bridge->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0); + if (IS_ERR(ps_bridge->panel_bridge)) + return PTR_ERR(ps_bridge->panel_bridge); + + ret = devm_drm_bridge_add(dev, &ps_bridge->bridge); if (ret) return ret; - return 0; + return devm_mipi_dsi_attach(dev, ps_bridge->dsi); } static int ps8640_probe(struct i2c_client *client) { struct device *dev = &client->dev; - struct device_node *np = dev->of_node; struct ps8640 *ps_bridge; int ret; u32 i; @@ -628,6 +663,14 @@ static int ps8640_probe(struct i2c_client *client) if (!ps8640_of_panel_on_aux_bus(&client->dev)) ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID; + /* + * Get MIPI DSI resources early. These can return -EPROBE_DEFER so + * we want to get them out of the way sooner. + */ + ret = ps8640_bridge_get_dsi_resources(&client->dev, ps_bridge); + if (ret) + return ret; + ps_bridge->page[PAGE0_DP_CNTL] = client; ps_bridge->regmap[PAGE0_DP_CNTL] = devm_regmap_init_i2c(client, ps8640_regmap_config); @@ -652,6 +695,7 @@ static int ps8640_probe(struct i2c_client *client) ps_bridge->aux.name = "parade-ps8640-aux"; ps_bridge->aux.dev = dev; ps_bridge->aux.transfer = ps8640_aux_transfer; + ps_bridge->aux.wait_hpd_asserted = ps8640_wait_hpd_asserted; drm_dp_aux_init(&ps_bridge->aux); pm_runtime_enable(dev); @@ -670,35 +714,19 @@ static int ps8640_probe(struct i2c_client *client) if (ret) return ret; - devm_of_dp_aux_populate_ep_devices(&ps_bridge->aux); - - /* port@1 is ps8640 output port */ - ps_bridge->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0); - if (IS_ERR(ps_bridge->panel_bridge)) - return PTR_ERR(ps_bridge->panel_bridge); - - drm_bridge_add(&ps_bridge->bridge); + ret = devm_of_dp_aux_populate_bus(&ps_bridge->aux, ps8640_bridge_link_panel); - ret = ps8640_bridge_host_attach(dev, ps_bridge); - if (ret) - goto err_bridge_remove; - - return 0; + /* + * If devm_of_dp_aux_populate_bus() returns -ENODEV then it's up to + * usa to call ps8640_bridge_link_panel() directly. NOTE: in this case + * the function is allowed to -EPROBE_DEFER. + */ + if (ret == -ENODEV) + return ps8640_bridge_link_panel(&ps_bridge->aux); -err_bridge_remove: - drm_bridge_remove(&ps_bridge->bridge); return ret; } -static int ps8640_remove(struct i2c_client *client) -{ - struct ps8640 *ps_bridge = i2c_get_clientdata(client); - - drm_bridge_remove(&ps_bridge->bridge); - - return 0; -} - static const struct of_device_id ps8640_match[] = { { .compatible = "parade,ps8640" }, { } @@ -707,7 +735,6 @@ MODULE_DEVICE_TABLE(of, ps8640_match); static struct i2c_driver ps8640_driver = { .probe_new = ps8640_probe, - .remove = ps8640_remove, .driver = { .name = "ps8640", .of_match_table = ps8640_match, diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c index 65549fbfdc87..7ab38d734ad6 100644 --- a/drivers/gpu/drm/bridge/sii902x.c +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -15,6 +15,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c-mux.h> #include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> @@ -549,8 +550,9 @@ static int sii902x_audio_hw_params(struct device *dev, void *data, unsigned long mclk_rate; int i, ret; - if (daifmt->bit_clk_master || daifmt->frame_clk_master) { - dev_dbg(dev, "%s: I2S master mode not supported\n", __func__); + if (daifmt->bit_clk_provider || daifmt->frame_clk_provider) { + dev_dbg(dev, "%s: I2S clock provider mode not supported\n", + __func__); return -EINVAL; } diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c index ec7745c31da0..ab0bce4a988c 100644 --- a/drivers/gpu/drm/bridge/sil-sii8620.c +++ b/drivers/gpu/drm/bridge/sil-sii8620.c @@ -605,7 +605,7 @@ static void *sii8620_burst_get_tx_buf(struct sii8620 *ctx, int len) u8 *buf = &ctx->burst.tx_buf[ctx->burst.tx_count]; int size = len + 2; - if (ctx->burst.tx_count + size > ARRAY_SIZE(ctx->burst.tx_buf)) { + if (ctx->burst.tx_count + size >= ARRAY_SIZE(ctx->burst.tx_buf)) { dev_err(ctx->dev, "TX-BLK buffer exhausted\n"); ctx->error = -EINVAL; return NULL; @@ -622,7 +622,7 @@ static u8 *sii8620_burst_get_rx_buf(struct sii8620 *ctx, int len) u8 *buf = &ctx->burst.rx_buf[ctx->burst.rx_count]; int size = len + 1; - if (ctx->burst.tx_count + size > ARRAY_SIZE(ctx->burst.tx_buf)) { + if (ctx->burst.rx_count + size >= ARRAY_SIZE(ctx->burst.rx_buf)) { dev_err(ctx->dev, "RX-BLK buffer exhausted\n"); ctx->error = -EINVAL; return NULL; diff --git a/drivers/gpu/drm/bridge/simple-bridge.c b/drivers/gpu/drm/bridge/simple-bridge.c index d974282c12b2..2c5c5211bdab 100644 --- a/drivers/gpu/drm/bridge/simple-bridge.c +++ b/drivers/gpu/drm/bridge/simple-bridge.c @@ -15,6 +15,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_bridge.h> #include <drm/drm_crtc.h> +#include <drm/drm_edid.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> 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 f50b47ac11a8..a2f0860b20bb 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c @@ -45,7 +45,7 @@ static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, u8 inputclkfs = 0; /* it cares I2S only */ - if (fmt->bit_clk_master | fmt->frame_clk_master) { + if (fmt->bit_clk_provider | fmt->frame_clk_provider) { dev_err(dev, "unsupported clock settings\n"); return -EINVAL; } diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 3e1be9894ed1..25a60eb4d67c 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -10,6 +10,7 @@ #include <linux/delay.h> #include <linux/err.h> #include <linux/hdmi.h> +#include <linux/i2c.h> #include <linux/irq.h> #include <linux/module.h> #include <linux/mutex.h> diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c index dca41ed32f8a..fdfb14aca926 100644 --- a/drivers/gpu/drm/bridge/tc358764.c +++ b/drivers/gpu/drm/bridge/tc358764.c @@ -9,6 +9,7 @@ #include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/of_graph.h> #include <linux/regulator/consumer.h> diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index 485717c8f0b4..02bd757a8987 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -3,10 +3,7 @@ * TC358767/TC358867/TC9595 DSI/DPI-to-DPI/(e)DP bridge driver * * The TC358767/TC358867/TC9595 can operate in multiple modes. - * The following modes are supported: - * DPI->(e)DP -- supported - * DSI->DPI .... supported - * DSI->(e)DP .. NOT supported + * All modes are supported -- DPI->(e)DP / DSI->DPI / DSI->(e)DP . * * Copyright (C) 2016 CogentEmbedded Inc * Author: Andrey Gusakov <andrey.gusakov@cogentembedded.com> @@ -27,6 +24,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/kernel.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/regmap.h> #include <linux/slab.h> @@ -291,7 +289,6 @@ struct tc_data { struct drm_connector connector; struct mipi_dsi_device *dsi; - u8 dsi_lanes; /* link settings */ struct tc_edp_link link; @@ -309,6 +306,9 @@ struct tc_data { /* do we have IRQ */ bool have_irq; + /* Input connector type, DSI and not DPI. */ + bool input_connector_dsi; + /* HPD pin number (0 or 1) or -ENODEV */ int hpd_pin; }; @@ -1247,11 +1247,60 @@ static int tc_main_link_disable(struct tc_data *tc) return regmap_write(tc->regmap, DP0CTL, 0); } -static int tc_dpi_stream_enable(struct tc_data *tc) +static int tc_dsi_rx_enable(struct tc_data *tc) { + u32 value; int ret; + + regmap_write(tc->regmap, PPI_D0S_CLRSIPOCOUNT, 3); + regmap_write(tc->regmap, PPI_D1S_CLRSIPOCOUNT, 3); + regmap_write(tc->regmap, PPI_D2S_CLRSIPOCOUNT, 3); + regmap_write(tc->regmap, PPI_D3S_CLRSIPOCOUNT, 3); + regmap_write(tc->regmap, PPI_D0S_ATMR, 0); + regmap_write(tc->regmap, PPI_D1S_ATMR, 0); + regmap_write(tc->regmap, PPI_TX_RX_TA, TTA_GET | TTA_SURE); + regmap_write(tc->regmap, PPI_LPTXTIMECNT, LPX_PERIOD); + + value = ((LANEENABLE_L0EN << tc->dsi->lanes) - LANEENABLE_L0EN) | + LANEENABLE_CLEN; + regmap_write(tc->regmap, PPI_LANEENABLE, value); + regmap_write(tc->regmap, DSI_LANEENABLE, value); + + /* Set input interface */ + value = DP0_AUDSRC_NO_INPUT; + if (tc_test_pattern) + value |= DP0_VIDSRC_COLOR_BAR; + else + value |= DP0_VIDSRC_DSI_RX; + ret = regmap_write(tc->regmap, SYSCTRL, value); + if (ret) + return ret; + + usleep_range(120, 150); + + regmap_write(tc->regmap, PPI_STARTPPI, PPI_START_FUNCTION); + regmap_write(tc->regmap, DSI_STARTDSI, DSI_RX_START); + + return 0; +} + +static int tc_dpi_rx_enable(struct tc_data *tc) +{ u32 value; + /* Set input interface */ + value = DP0_AUDSRC_NO_INPUT; + if (tc_test_pattern) + value |= DP0_VIDSRC_COLOR_BAR; + else + value |= DP0_VIDSRC_DPI_RX; + return regmap_write(tc->regmap, SYSCTRL, value); +} + +static int tc_dpi_stream_enable(struct tc_data *tc) +{ + int ret; + dev_dbg(tc->dev, "enable video stream\n"); /* Setup PLL */ @@ -1277,20 +1326,6 @@ static int tc_dpi_stream_enable(struct tc_data *tc) if (ret) return ret; - regmap_write(tc->regmap, PPI_D0S_CLRSIPOCOUNT, 3); - regmap_write(tc->regmap, PPI_D1S_CLRSIPOCOUNT, 3); - regmap_write(tc->regmap, PPI_D2S_CLRSIPOCOUNT, 3); - regmap_write(tc->regmap, PPI_D3S_CLRSIPOCOUNT, 3); - regmap_write(tc->regmap, PPI_D0S_ATMR, 0); - regmap_write(tc->regmap, PPI_D1S_ATMR, 0); - regmap_write(tc->regmap, PPI_TX_RX_TA, TTA_GET | TTA_SURE); - regmap_write(tc->regmap, PPI_LPTXTIMECNT, LPX_PERIOD); - - value = ((LANEENABLE_L0EN << tc->dsi_lanes) - LANEENABLE_L0EN) | - LANEENABLE_CLEN; - regmap_write(tc->regmap, PPI_LANEENABLE, value); - regmap_write(tc->regmap, DSI_LANEENABLE, value); - ret = tc_set_common_video_mode(tc, &tc->mode); if (ret) return ret; @@ -1299,22 +1334,7 @@ static int tc_dpi_stream_enable(struct tc_data *tc) if (ret) return ret; - /* Set input interface */ - value = DP0_AUDSRC_NO_INPUT; - if (tc_test_pattern) - value |= DP0_VIDSRC_COLOR_BAR; - else - value |= DP0_VIDSRC_DSI_RX; - ret = regmap_write(tc->regmap, SYSCTRL, value); - if (ret) - return ret; - - usleep_range(120, 150); - - regmap_write(tc->regmap, PPI_STARTPPI, PPI_START_FUNCTION); - regmap_write(tc->regmap, DSI_STARTDSI, DSI_RX_START); - - return 0; + return tc_dsi_rx_enable(tc); } static int tc_dpi_stream_disable(struct tc_data *tc) @@ -1333,8 +1353,18 @@ static int tc_edp_stream_enable(struct tc_data *tc) dev_dbg(tc->dev, "enable video stream\n"); - /* PXL PLL setup */ - if (tc_test_pattern) { + /* + * Pixel PLL must be enabled for DSI input mode and test pattern. + * + * Per TC9595XBG datasheet Revision 0.1 2018-12-27 Figure 4.18 + * "Clock Mode Selection and Clock Sources", either Pixel PLL + * or DPI_PCLK supplies StrmClk. DPI_PCLK is only available in + * case valid Pixel Clock are supplied to the chip DPI input. + * In case built-in test pattern is desired OR DSI input mode + * is used, DPI_PCLK is not available and thus Pixel PLL must + * be used instead. + */ + if (tc->input_connector_dsi || tc_test_pattern) { ret = tc_pxl_pll_en(tc, clk_get_rate(tc->refclk), 1000 * tc->mode.clock); if (ret) @@ -1372,17 +1402,12 @@ static int tc_edp_stream_enable(struct tc_data *tc) ret = regmap_write(tc->regmap, DP0CTL, value); if (ret) return ret; + /* Set input interface */ - value = DP0_AUDSRC_NO_INPUT; - if (tc_test_pattern) - value |= DP0_VIDSRC_COLOR_BAR; + if (tc->input_connector_dsi) + return tc_dsi_rx_enable(tc); else - value |= DP0_VIDSRC_DPI_RX; - ret = regmap_write(tc->regmap, SYSCTRL, value); - if (ret) - return ret; - - return 0; + return tc_dpi_rx_enable(tc); } static int tc_edp_stream_disable(struct tc_data *tc) @@ -1865,18 +1890,18 @@ static int tc_mipi_dsi_host_attach(struct tc_data *tc) int dsi_lanes, ret; endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); - dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); + dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); host_node = of_graph_get_remote_port_parent(endpoint); host = of_find_mipi_dsi_host_by_node(host_node); of_node_put(host_node); of_node_put(endpoint); - if (dsi_lanes < 0 || dsi_lanes > 4) - return -EINVAL; - if (!host) return -EPROBE_DEFER; + if (dsi_lanes < 0) + return dsi_lanes; + dsi = mipi_dsi_device_register_full(host, &info); if (IS_ERR(dsi)) return dev_err_probe(dev, PTR_ERR(dsi), @@ -1884,8 +1909,7 @@ static int tc_mipi_dsi_host_attach(struct tc_data *tc) tc->dsi = dsi; - tc->dsi_lanes = dsi_lanes; - dsi->lanes = tc->dsi_lanes; + dsi->lanes = dsi_lanes; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE; @@ -1992,18 +2016,29 @@ static int tc_probe_bridge_endpoint(struct tc_data *tc) mode |= BIT(endpoint.port); } - if (mode == mode_dpi_to_edp || mode == mode_dpi_to_dp) + if (mode == mode_dpi_to_edp || mode == mode_dpi_to_dp) { + tc->input_connector_dsi = false; return tc_probe_edp_bridge_endpoint(tc); - else if (mode == mode_dsi_to_dpi) + } else if (mode == mode_dsi_to_dpi) { + tc->input_connector_dsi = true; return tc_probe_dpi_bridge_endpoint(tc); - else if (mode == mode_dsi_to_edp || mode == mode_dsi_to_dp) - dev_warn(dev, "The mode DSI-to-(e)DP is not supported!\n"); - else - dev_warn(dev, "Invalid mode (0x%x) is not supported!\n", mode); + } else if (mode == mode_dsi_to_edp || mode == mode_dsi_to_dp) { + tc->input_connector_dsi = true; + return tc_probe_edp_bridge_endpoint(tc); + } + + dev_warn(dev, "Invalid mode (0x%x) is not supported!\n", mode); return -EINVAL; } +static void tc_clk_disable(void *data) +{ + struct clk *refclk = data; + + clk_disable_unprepare(refclk); +} + static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; @@ -2020,6 +2055,24 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id) if (ret) return ret; + tc->refclk = devm_clk_get(dev, "ref"); + if (IS_ERR(tc->refclk)) { + ret = PTR_ERR(tc->refclk); + dev_err(dev, "Failed to get refclk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(tc->refclk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, tc_clk_disable, tc->refclk); + if (ret) + return ret; + + /* tRSTW = 100 cycles , at 13 MHz that is ~7.69 us */ + usleep_range(10, 15); + /* Shut down GPIO is optional */ tc->sd_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH); if (IS_ERR(tc->sd_gpio)) @@ -2040,13 +2093,6 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id) usleep_range(5000, 10000); } - tc->refclk = devm_clk_get(dev, "ref"); - if (IS_ERR(tc->refclk)) { - ret = PTR_ERR(tc->refclk); - dev_err(dev, "Failed to get refclk: %d\n", ret); - return ret; - } - tc->regmap = devm_regmap_init_i2c(client, &tc_regmap_config); if (IS_ERR(tc->regmap)) { ret = PTR_ERR(tc->regmap); @@ -2137,7 +2183,7 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id) i2c_set_clientdata(client, tc); - if (tc->bridge.type == DRM_MODE_CONNECTOR_DPI) { /* DPI output */ + if (tc->input_connector_dsi) { /* DSI input */ ret = tc_mipi_dsi_host_attach(tc); if (ret) { drm_bridge_remove(&tc->bridge); diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c index 62a7ef352daa..f1c6e62b0e1d 100644 --- a/drivers/gpu/drm/bridge/tc358775.c +++ b/drivers/gpu/drm/bridge/tc358775.c @@ -13,6 +13,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/kernel.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> @@ -339,6 +340,7 @@ static void d2l_read(struct i2c_client *i2c, u16 addr, u32 *val) goto fail; pr_debug("d2l: I2C : addr:%04x value:%08x\n", addr, *val); + return; fail: dev_err(&i2c->dev, "Error %d reading from subaddress 0x%x\n", @@ -429,7 +431,7 @@ static void tc_bridge_enable(struct drm_bridge *bridge) val = TC358775_VPCTRL_MSF(1); dsiclk = mode->crtc_clock * 3 * tc->bpc / tc->num_dsi_lanes / 1000; - clkdiv = dsiclk / DIVIDE_BY_3 * tc->lvds_link; + clkdiv = dsiclk / (tc->lvds_link == DUAL_LINK ? DIVIDE_BY_6 : DIVIDE_BY_3); byteclk = dsiclk / 4; t1 = hactive * (tc->bpc * 3 / 8) / tc->num_dsi_lanes; t2 = ((100000 / clkdiv)) * (hactive + hback_porch + hsync_len + hfront_porch) / 1000; @@ -529,8 +531,7 @@ static int tc358775_parse_dt(struct device_node *np, struct tc_data *tc) struct device_node *endpoint; struct device_node *parent; struct device_node *remote; - struct property *prop; - int len = 0; + int dsi_lanes = -1; /* * To get the data-lanes of dsi, we need to access the dsi0_out of port1 @@ -544,25 +545,15 @@ static int tc358775_parse_dt(struct device_node *np, struct tc_data *tc) of_node_put(endpoint); if (parent) { /* dsi0 port 1 */ - endpoint = of_graph_get_endpoint_by_regs(parent, 1, -1); + dsi_lanes = drm_of_get_data_lanes_count_ep(parent, 1, -1, 1, 4); of_node_put(parent); - if (endpoint) { - prop = of_find_property(endpoint, "data-lanes", - &len); - of_node_put(endpoint); - if (!prop) { - dev_err(tc->dev, - "failed to find data lane\n"); - return -EPROBE_DEFER; - } - } } } - tc->num_dsi_lanes = len / sizeof(u32); + if (dsi_lanes < 0) + return dsi_lanes; - if (tc->num_dsi_lanes < 1 || tc->num_dsi_lanes > 4) - return -EINVAL; + tc->num_dsi_lanes = dsi_lanes; tc->host_node = of_graph_get_remote_node(np, 0, 0); if (!tc->host_node) diff --git a/drivers/gpu/drm/bridge/ti-dlpc3433.c b/drivers/gpu/drm/bridge/ti-dlpc3433.c new file mode 100644 index 000000000000..cef454862b67 --- /dev/null +++ b/drivers/gpu/drm/bridge/ti-dlpc3433.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 RenewOutReach + * Copyright (C) 2021 Amarula Solutions(India) + * + * Author: + * Jagan Teki <jagan@amarulasolutions.com> + * Christopher Vollo <chris@renewoutreach.org> + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_mipi_dsi.h> + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/media-bus-format.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +enum cmd_registers { + WR_INPUT_SOURCE = 0x05, /* Write Input Source Select */ + WR_EXT_SOURCE_FMT = 0x07, /* Write External Video Source Format */ + WR_IMAGE_CROP = 0x10, /* Write Image Crop */ + WR_DISPLAY_SIZE = 0x12, /* Write Display Size */ + WR_IMAGE_FREEZE = 0x1A, /* Write Image Freeze */ + WR_INPUT_IMAGE_SIZE = 0x2E, /* Write External Input Image Size */ + WR_RGB_LED_EN = 0x52, /* Write RGB LED Enable */ + WR_RGB_LED_CURRENT = 0x54, /* Write RGB LED Current */ + WR_RGB_LED_MAX_CURRENT = 0x5C, /* Write RGB LED Max Current */ + WR_DSI_HS_CLK = 0xBD, /* Write DSI HS Clock */ + RD_DEVICE_ID = 0xD4, /* Read Controller Device ID */ + WR_DSI_PORT_EN = 0xD7, /* Write DSI Port Enable */ +}; + +enum input_source { + INPUT_EXTERNAL_VIDEO = 0, + INPUT_TEST_PATTERN, + INPUT_SPLASH_SCREEN, +}; + +#define DEV_ID_MASK GENMASK(3, 0) +#define IMAGE_FREESE_EN BIT(0) +#define DSI_PORT_EN 0 +#define EXT_SOURCE_FMT_DSI 0 +#define RED_LED_EN BIT(0) +#define GREEN_LED_EN BIT(1) +#define BLUE_LED_EN BIT(2) +#define LED_MASK GENMASK(2, 0) +#define MAX_BYTE_SIZE 8 + +struct dlpc { + struct device *dev; + struct drm_bridge bridge; + struct drm_bridge *next_bridge; + struct device_node *host_node; + struct mipi_dsi_device *dsi; + struct drm_display_mode mode; + + struct gpio_desc *enable_gpio; + struct regulator *vcc_intf; + struct regulator *vcc_flsh; + struct regmap *regmap; + unsigned int dsi_lanes; +}; + +static inline struct dlpc *bridge_to_dlpc(struct drm_bridge *bridge) +{ + return container_of(bridge, struct dlpc, bridge); +} + +static bool dlpc_writeable_noinc_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WR_IMAGE_CROP: + case WR_DISPLAY_SIZE: + case WR_INPUT_IMAGE_SIZE: + case WR_DSI_HS_CLK: + return true; + default: + return false; + } +} + +static const struct regmap_range dlpc_volatile_ranges[] = { + { .range_min = 0x10, .range_max = 0xBF }, +}; + +static const struct regmap_access_table dlpc_volatile_table = { + .yes_ranges = dlpc_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(dlpc_volatile_ranges), +}; + +static struct regmap_config dlpc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = WR_DSI_PORT_EN, + .writeable_noinc_reg = dlpc_writeable_noinc_reg, + .volatile_table = &dlpc_volatile_table, + .cache_type = REGCACHE_RBTREE, + .name = "dlpc3433", +}; + +static void dlpc_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct dlpc *dlpc = bridge_to_dlpc(bridge); + struct device *dev = dlpc->dev; + struct drm_display_mode *mode = &dlpc->mode; + struct regmap *regmap = dlpc->regmap; + char buf[MAX_BYTE_SIZE]; + unsigned int devid; + + regmap_read(regmap, RD_DEVICE_ID, &devid); + devid &= DEV_ID_MASK; + + DRM_DEV_DEBUG(dev, "DLPC3433 device id: 0x%02x\n", devid); + + if (devid != 0x01) { + DRM_DEV_ERROR(dev, "Unsupported DLPC device id: 0x%02x\n", devid); + return; + } + + /* disable image freeze */ + regmap_write(regmap, WR_IMAGE_FREEZE, IMAGE_FREESE_EN); + + /* enable DSI port */ + regmap_write(regmap, WR_DSI_PORT_EN, DSI_PORT_EN); + + memset(buf, 0, MAX_BYTE_SIZE); + + /* set image crop */ + buf[4] = mode->hdisplay & 0xff; + buf[5] = (mode->hdisplay & 0xff00) >> 8; + buf[6] = mode->vdisplay & 0xff; + buf[7] = (mode->vdisplay & 0xff00) >> 8; + regmap_noinc_write(regmap, WR_IMAGE_CROP, buf, MAX_BYTE_SIZE); + + /* set display size */ + buf[4] = mode->hdisplay & 0xff; + buf[5] = (mode->hdisplay & 0xff00) >> 8; + buf[6] = mode->vdisplay & 0xff; + buf[7] = (mode->vdisplay & 0xff00) >> 8; + regmap_noinc_write(regmap, WR_DISPLAY_SIZE, buf, MAX_BYTE_SIZE); + + /* set input image size */ + buf[0] = mode->hdisplay & 0xff; + buf[1] = (mode->hdisplay & 0xff00) >> 8; + buf[2] = mode->vdisplay & 0xff; + buf[3] = (mode->vdisplay & 0xff00) >> 8; + regmap_noinc_write(regmap, WR_INPUT_IMAGE_SIZE, buf, 4); + + /* set external video port */ + regmap_write(regmap, WR_INPUT_SOURCE, INPUT_EXTERNAL_VIDEO); + + /* set external video format select as DSI */ + regmap_write(regmap, WR_EXT_SOURCE_FMT, EXT_SOURCE_FMT_DSI); + + /* disable image freeze */ + regmap_write(regmap, WR_IMAGE_FREEZE, 0x00); + + /* enable RGB led */ + regmap_update_bits(regmap, WR_RGB_LED_EN, LED_MASK, + RED_LED_EN | GREEN_LED_EN | BLUE_LED_EN); + + msleep(10); +} + +static void dlpc_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct dlpc *dlpc = bridge_to_dlpc(bridge); + int ret; + + gpiod_set_value(dlpc->enable_gpio, 1); + + msleep(500); + + ret = regulator_enable(dlpc->vcc_intf); + if (ret) + DRM_DEV_ERROR(dlpc->dev, + "failed to enable VCC_INTF regulator: %d\n", ret); + + ret = regulator_enable(dlpc->vcc_flsh); + if (ret) + DRM_DEV_ERROR(dlpc->dev, + "failed to enable VCC_FLSH regulator: %d\n", ret); + + msleep(10); +} + +static void dlpc_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct dlpc *dlpc = bridge_to_dlpc(bridge); + + regulator_disable(dlpc->vcc_flsh); + regulator_disable(dlpc->vcc_intf); + + msleep(10); + + gpiod_set_value(dlpc->enable_gpio, 0); + + msleep(500); +} + +#define MAX_INPUT_SEL_FORMATS 1 + +static u32 * +dlpc_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + /* This is the DSI-end bus format */ + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + *num_input_fmts = 1; + + return input_fmts; +} + +static void dlpc_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct dlpc *dlpc = bridge_to_dlpc(bridge); + + drm_mode_copy(&dlpc->mode, adjusted_mode); +} + +static int dlpc_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct dlpc *dlpc = bridge_to_dlpc(bridge); + + return drm_bridge_attach(bridge->encoder, dlpc->next_bridge, bridge, flags); +} + +static const struct drm_bridge_funcs dlpc_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = dlpc_atomic_get_input_bus_fmts, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_pre_enable = dlpc_atomic_pre_enable, + .atomic_enable = dlpc_atomic_enable, + .atomic_post_disable = dlpc_atomic_post_disable, + .mode_set = dlpc_mode_set, + .attach = dlpc_attach, +}; + +static int dlpc3433_parse_dt(struct dlpc *dlpc) +{ + struct device *dev = dlpc->dev; + struct device_node *endpoint; + int ret; + + dlpc->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(dlpc->enable_gpio)) + return PTR_ERR(dlpc->enable_gpio); + + dlpc->vcc_intf = devm_regulator_get(dlpc->dev, "vcc_intf"); + if (IS_ERR(dlpc->vcc_intf)) + return dev_err_probe(dev, PTR_ERR(dlpc->vcc_intf), + "failed to get VCC_INTF supply\n"); + + dlpc->vcc_flsh = devm_regulator_get(dlpc->dev, "vcc_flsh"); + if (IS_ERR(dlpc->vcc_flsh)) + return dev_err_probe(dev, PTR_ERR(dlpc->vcc_flsh), + "failed to get VCC_FLSH supply\n"); + + dlpc->next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); + if (IS_ERR(dlpc->next_bridge)) + return PTR_ERR(dlpc->next_bridge); + + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + dlpc->dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); + if (dlpc->dsi_lanes < 0 || dlpc->dsi_lanes > 4) { + ret = -EINVAL; + goto err_put_endpoint; + } + + dlpc->host_node = of_graph_get_remote_port_parent(endpoint); + if (!dlpc->host_node) { + ret = -ENODEV; + goto err_put_host; + } + + of_node_put(endpoint); + + return 0; + +err_put_host: + of_node_put(dlpc->host_node); +err_put_endpoint: + of_node_put(endpoint); + return ret; +} + +static int dlpc_host_attach(struct dlpc *dlpc) +{ + struct device *dev = dlpc->dev; + struct mipi_dsi_host *host; + struct mipi_dsi_device_info info = { + .type = "dlpc3433", + .channel = 0, + .node = NULL, + }; + + host = of_find_mipi_dsi_host_by_node(dlpc->host_node); + if (!host) { + DRM_DEV_ERROR(dev, "failed to find dsi host\n"); + return -EPROBE_DEFER; + } + + dlpc->dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dlpc->dsi)) { + DRM_DEV_ERROR(dev, "failed to create dsi device\n"); + return PTR_ERR(dlpc->dsi); + } + + dlpc->dsi->mode_flags = MIPI_DSI_MODE_VIDEO_BURST; + dlpc->dsi->format = MIPI_DSI_FMT_RGB565; + dlpc->dsi->lanes = dlpc->dsi_lanes; + + return devm_mipi_dsi_attach(dev, dlpc->dsi); +} + +static int dlpc3433_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct dlpc *dlpc; + int ret; + + dlpc = devm_kzalloc(dev, sizeof(*dlpc), GFP_KERNEL); + if (!dlpc) + return -ENOMEM; + + dlpc->dev = dev; + + dlpc->regmap = devm_regmap_init_i2c(client, &dlpc_regmap_config); + if (IS_ERR(dlpc->regmap)) + return PTR_ERR(dlpc->regmap); + + ret = dlpc3433_parse_dt(dlpc); + if (ret) + return ret; + + dev_set_drvdata(dev, dlpc); + i2c_set_clientdata(client, dlpc); + + dlpc->bridge.funcs = &dlpc_bridge_funcs; + dlpc->bridge.of_node = dev->of_node; + drm_bridge_add(&dlpc->bridge); + + ret = dlpc_host_attach(dlpc); + if (ret) { + DRM_DEV_ERROR(dev, "failed to attach dsi host\n"); + goto err_remove_bridge; + } + + return 0; + +err_remove_bridge: + drm_bridge_remove(&dlpc->bridge); + return ret; +} + +static int dlpc3433_remove(struct i2c_client *client) +{ + struct dlpc *dlpc = i2c_get_clientdata(client); + + drm_bridge_remove(&dlpc->bridge); + of_node_put(dlpc->host_node); + + return 0; +} + +static const struct i2c_device_id dlpc3433_id[] = { + { "ti,dlpc3433", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, dlpc3433_id); + +static const struct of_device_id dlpc3433_match_table[] = { + { .compatible = "ti,dlpc3433" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dlpc3433_match_table); + +static struct i2c_driver dlpc3433_driver = { + .probe_new = dlpc3433_probe, + .remove = dlpc3433_remove, + .id_table = dlpc3433_id, + .driver = { + .name = "ti-dlpc3433", + .of_match_table = dlpc3433_match_table, + }, +}; +module_i2c_driver(dlpc3433_driver); + +MODULE_AUTHOR("Jagan Teki <jagan@amarulasolutions.com>"); +MODULE_AUTHOR("Christopher Vollo <chris@renewoutreach.org>"); +MODULE_DESCRIPTION("TI DLPC3433 MIPI DSI Display Controller Bridge"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index ac66f408b40c..14e7aa77e758 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -29,6 +29,7 @@ #include <linux/clk.h> #include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/of_device.h> #include <linux/of_graph.h> @@ -140,12 +141,10 @@ struct sn65dsi83 { struct drm_bridge bridge; struct device *dev; struct regmap *regmap; - struct device_node *host_node; struct mipi_dsi_device *dsi; struct drm_bridge *panel_bridge; struct gpio_desc *enable_gpio; struct regulator *vcc; - int dsi_lanes; bool lvds_dual_link; bool lvds_dual_link_even_odd_swap; }; @@ -306,7 +305,7 @@ static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx, */ 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); + ctx->dsi->lanes / 2, 40000U, 500000U), 5000U); } static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx) @@ -314,7 +313,7 @@ static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx) /* The divider is (DSI_CLK / LVDS_CLK) - 1, which really is: */ unsigned int dsi_div = mipi_dsi_pixel_format_to_bpp(ctx->dsi->format); - dsi_div /= ctx->dsi_lanes; + dsi_div /= ctx->dsi->lanes; if (!ctx->lvds_dual_link) dsi_div /= 2; @@ -346,7 +345,7 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge, } /* Deassert reset */ - gpiod_set_value(ctx->enable_gpio, 1); + gpiod_set_value_cansleep(ctx->enable_gpio, 1); usleep_range(1000, 1100); /* Get the LVDS format from the bridge state. */ @@ -405,7 +404,7 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge, /* Set number of DSI lanes and LVDS link config. */ regmap_write(ctx->regmap, REG_DSI_LANE, REG_DSI_LANE_DSI_CHANNEL_MODE_SINGLE | - REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi_lanes - 1)) | + REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi->lanes - 1)) | /* CHB is DSI85-only, set to default on DSI83/DSI84 */ REG_DSI_LANE_CHB_DSI_LANES(3)); /* No equalization. */ @@ -502,7 +501,7 @@ static void sn65dsi83_atomic_disable(struct drm_bridge *bridge, int ret; /* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */ - gpiod_set_value(ctx->enable_gpio, 0); + gpiod_set_value_cansleep(ctx->enable_gpio, 0); usleep_range(10000, 11000); ret = regulator_disable(ctx->vcc); @@ -569,22 +568,6 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) { struct drm_bridge *panel_bridge; struct device *dev = ctx->dev; - struct device_node *endpoint; - int ret; - - endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); - ctx->dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes"); - ctx->host_node = of_graph_get_remote_port_parent(endpoint); - of_node_put(endpoint); - - if (ctx->dsi_lanes <= 0 || ctx->dsi_lanes > 4) { - ret = -EINVAL; - goto err_put_node; - } - if (!ctx->host_node) { - ret = -ENODEV; - goto err_put_node; - } ctx->lvds_dual_link = false; ctx->lvds_dual_link_even_odd_swap = false; @@ -610,10 +593,8 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) } panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0); - if (IS_ERR(panel_bridge)) { - ret = PTR_ERR(panel_bridge); - goto err_put_node; - } + if (IS_ERR(panel_bridge)) + return PTR_ERR(panel_bridge); ctx->panel_bridge = panel_bridge; @@ -623,15 +604,13 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model) "Failed to get supply 'vcc'\n"); return 0; - -err_put_node: - of_node_put(ctx->host_node); - return ret; } static int sn65dsi83_host_attach(struct sn65dsi83 *ctx) { struct device *dev = ctx->dev; + struct device_node *host_node; + struct device_node *endpoint; struct mipi_dsi_device *dsi; struct mipi_dsi_host *host; const struct mipi_dsi_device_info info = { @@ -639,13 +618,20 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx) .channel = 0, .node = NULL, }; - int ret; + int dsi_lanes, ret; + + endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1); + dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4); + host_node = of_graph_get_remote_port_parent(endpoint); + host = of_find_mipi_dsi_host_by_node(host_node); + of_node_put(host_node); + of_node_put(endpoint); - host = of_find_mipi_dsi_host_by_node(ctx->host_node); - if (!host) { - dev_err(dev, "failed to find dsi host\n"); + if (!host) return -EPROBE_DEFER; - } + + if (dsi_lanes < 0) + return dsi_lanes; dsi = devm_mipi_dsi_device_register_full(dev, host, &info); if (IS_ERR(dsi)) @@ -654,7 +640,7 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx) ctx->dsi = dsi; - dsi->lanes = ctx->dsi_lanes; + dsi->lanes = dsi_lanes; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST; @@ -692,7 +678,7 @@ static int sn65dsi83_probe(struct i2c_client *client, ctx->enable_gpio = devm_gpiod_get_optional(ctx->dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(ctx->enable_gpio)) - return PTR_ERR(ctx->enable_gpio); + return dev_err_probe(dev, PTR_ERR(ctx->enable_gpio), "failed to get enable GPIO\n"); usleep_range(10000, 11000); @@ -701,10 +687,8 @@ static int sn65dsi83_probe(struct i2c_client *client, return ret; ctx->regmap = devm_regmap_init_i2c(client, &sn65dsi83_regmap_config); - if (IS_ERR(ctx->regmap)) { - ret = PTR_ERR(ctx->regmap); - goto err_put_node; - } + if (IS_ERR(ctx->regmap)) + return dev_err_probe(dev, PTR_ERR(ctx->regmap), "failed to get regmap\n"); dev_set_drvdata(dev, ctx); i2c_set_clientdata(client, ctx); @@ -721,8 +705,6 @@ static int sn65dsi83_probe(struct i2c_client *client, err_remove_bridge: drm_bridge_remove(&ctx->bridge); -err_put_node: - of_node_put(ctx->host_node); return ret; } @@ -731,7 +713,6 @@ 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 8cad662de9bb..d6dd4d99a229 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -752,7 +752,8 @@ ti_sn_bridge_mode_valid(struct drm_bridge *bridge, return MODE_OK; } -static void ti_sn_bridge_disable(struct drm_bridge *bridge) +static void ti_sn_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge); @@ -1011,7 +1012,8 @@ exit: return ret; } -static void ti_sn_bridge_enable(struct drm_bridge *bridge) +static void ti_sn_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge); const char *last_err_str = "No supported DP rate"; @@ -1080,7 +1082,8 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge) VSTREAM_ENABLE); } -static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) +static void ti_sn_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge); @@ -1093,7 +1096,8 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge) usleep_range(100, 110); } -static void ti_sn_bridge_post_disable(struct drm_bridge *bridge) +static void ti_sn_bridge_atomic_post_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge); @@ -1114,10 +1118,13 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = { .attach = ti_sn_bridge_attach, .detach = ti_sn_bridge_detach, .mode_valid = ti_sn_bridge_mode_valid, - .pre_enable = ti_sn_bridge_pre_enable, - .enable = ti_sn_bridge_enable, - .disable = ti_sn_bridge_disable, - .post_disable = ti_sn_bridge_post_disable, + .atomic_pre_enable = ti_sn_bridge_atomic_pre_enable, + .atomic_enable = ti_sn_bridge_atomic_enable, + .atomic_disable = ti_sn_bridge_atomic_disable, + .atomic_post_disable = ti_sn_bridge_atomic_post_disable, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, }; static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata, @@ -1142,8 +1149,8 @@ static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata, * 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) { + dp_lanes = drm_of_get_data_lanes_count(endpoint, 1, SN_MAX_DP_LANES); + if (dp_lanes > 0) { of_property_read_u32_array(endpoint, "data-lanes", lane_assignments, dp_lanes); of_property_read_u32_array(endpoint, "lane-polarities", diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index 756b3e6e776b..401fe61217c7 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -6,6 +6,7 @@ #include <linux/gpio/consumer.h> #include <linux/i2c.h> +#include <linux/media-bus-format.h> #include <linux/module.h> #include <linux/of_graph.h> #include <linux/platform_device.h> @@ -14,6 +15,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_bridge.h> #include <drm/drm_crtc.h> +#include <drm/drm_edid.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> |