Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c
26517 views
// SPDX-License-Identifier: GPL-2.0+12/*3* Copyright 2020 NXP4*/56#include <linux/clk.h>7#include <linux/media-bus-format.h>8#include <linux/mfd/syscon.h>9#include <linux/module.h>10#include <linux/of.h>11#include <linux/of_device.h>12#include <linux/of_graph.h>13#include <linux/phy/phy.h>14#include <linux/platform_device.h>15#include <linux/pm_runtime.h>16#include <linux/regmap.h>1718#include <drm/drm_atomic_state_helper.h>19#include <drm/drm_bridge.h>20#include <drm/drm_connector.h>21#include <drm/drm_fourcc.h>22#include <drm/drm_of.h>23#include <drm/drm_print.h>2425#include "imx-ldb-helper.h"2627#define LDB_CH_SEL BIT(28)2829#define SS_CTRL 0x2030#define CH_HSYNC_M(id) BIT(0 + ((id) * 2))31#define CH_VSYNC_M(id) BIT(1 + ((id) * 2))32#define CH_PHSYNC(id) BIT(0 + ((id) * 2))33#define CH_PVSYNC(id) BIT(1 + ((id) * 2))3435#define DRIVER_NAME "imx8qxp-ldb"3637struct imx8qxp_ldb_channel {38struct ldb_channel base;39struct phy *phy;40unsigned int di_id;41};4243struct imx8qxp_ldb {44struct ldb base;45struct device *dev;46struct imx8qxp_ldb_channel *channel[MAX_LDB_CHAN_NUM];47struct clk *clk_pixel;48struct clk *clk_bypass;49struct drm_bridge *companion;50int active_chno;51};5253static inline struct imx8qxp_ldb_channel *54base_to_imx8qxp_ldb_channel(struct ldb_channel *base)55{56return container_of(base, struct imx8qxp_ldb_channel, base);57}5859static inline struct imx8qxp_ldb *base_to_imx8qxp_ldb(struct ldb *base)60{61return container_of(base, struct imx8qxp_ldb, base);62}6364static void imx8qxp_ldb_set_phy_cfg(struct imx8qxp_ldb *imx8qxp_ldb,65unsigned long di_clk, bool is_split,66struct phy_configure_opts_lvds *phy_cfg)67{68phy_cfg->bits_per_lane_and_dclk_cycle = 7;69phy_cfg->lanes = 4;7071if (is_split) {72phy_cfg->differential_clk_rate = di_clk / 2;73phy_cfg->is_slave = !imx8qxp_ldb->companion;74} else {75phy_cfg->differential_clk_rate = di_clk;76phy_cfg->is_slave = false;77}78}7980static int81imx8qxp_ldb_bridge_atomic_check(struct drm_bridge *bridge,82struct drm_bridge_state *bridge_state,83struct drm_crtc_state *crtc_state,84struct drm_connector_state *conn_state)85{86struct ldb_channel *ldb_ch = bridge->driver_private;87struct ldb *ldb = ldb_ch->ldb;88struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =89base_to_imx8qxp_ldb_channel(ldb_ch);90struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);91struct drm_bridge *companion = imx8qxp_ldb->companion;92struct drm_display_mode *adj = &crtc_state->adjusted_mode;93unsigned long di_clk = adj->clock * 1000;94bool is_split = ldb_channel_is_split_link(ldb_ch);95union phy_configure_opts opts = { };96struct phy_configure_opts_lvds *phy_cfg = &opts.lvds;97int ret;9899ret = ldb_bridge_atomic_check_helper(bridge, bridge_state,100crtc_state, conn_state);101if (ret)102return ret;103104imx8qxp_ldb_set_phy_cfg(imx8qxp_ldb, di_clk, is_split, phy_cfg);105ret = phy_validate(imx8qxp_ldb_ch->phy, PHY_MODE_LVDS, 0, &opts);106if (ret < 0) {107DRM_DEV_DEBUG_DRIVER(imx8qxp_ldb->dev,108"failed to validate PHY: %d\n", ret);109return ret;110}111112if (is_split && companion) {113ret = companion->funcs->atomic_check(companion,114bridge_state, crtc_state, conn_state);115if (ret)116return ret;117}118119return ret;120}121122static void123imx8qxp_ldb_bridge_mode_set(struct drm_bridge *bridge,124const struct drm_display_mode *mode,125const struct drm_display_mode *adjusted_mode)126{127struct ldb_channel *ldb_ch = bridge->driver_private;128struct ldb_channel *companion_ldb_ch;129struct ldb *ldb = ldb_ch->ldb;130struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =131base_to_imx8qxp_ldb_channel(ldb_ch);132struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);133struct drm_bridge *companion = imx8qxp_ldb->companion;134struct device *dev = imx8qxp_ldb->dev;135unsigned long di_clk = adjusted_mode->clock * 1000;136bool is_split = ldb_channel_is_split_link(ldb_ch);137union phy_configure_opts opts = { };138struct phy_configure_opts_lvds *phy_cfg = &opts.lvds;139u32 chno = ldb_ch->chno;140int ret;141142ret = pm_runtime_get_sync(dev);143if (ret < 0)144DRM_DEV_ERROR(dev, "failed to get runtime PM sync: %d\n", ret);145146ret = phy_init(imx8qxp_ldb_ch->phy);147if (ret < 0)148DRM_DEV_ERROR(dev, "failed to initialize PHY: %d\n", ret);149150ret = phy_set_mode(imx8qxp_ldb_ch->phy, PHY_MODE_LVDS);151if (ret < 0)152DRM_DEV_ERROR(dev, "failed to set PHY mode: %d\n", ret);153154if (is_split && companion) {155companion_ldb_ch = bridge_to_ldb_ch(companion);156157companion_ldb_ch->in_bus_format = ldb_ch->in_bus_format;158companion_ldb_ch->out_bus_format = ldb_ch->out_bus_format;159}160161clk_set_rate(imx8qxp_ldb->clk_bypass, di_clk);162clk_set_rate(imx8qxp_ldb->clk_pixel, di_clk);163164imx8qxp_ldb_set_phy_cfg(imx8qxp_ldb, di_clk, is_split, phy_cfg);165ret = phy_configure(imx8qxp_ldb_ch->phy, &opts);166if (ret < 0)167DRM_DEV_ERROR(dev, "failed to configure PHY: %d\n", ret);168169if (chno == 0)170ldb->ldb_ctrl &= ~LDB_CH_SEL;171else172ldb->ldb_ctrl |= LDB_CH_SEL;173174/* input VSYNC signal from pixel link is active low */175if (imx8qxp_ldb_ch->di_id == 0)176ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW;177else178ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW;179180/*181* For split mode, settle input VSYNC signal polarity and182* channel selection down early.183*/184if (is_split)185regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);186187ldb_bridge_mode_set_helper(bridge, mode, adjusted_mode);188189if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC)190regmap_update_bits(ldb->regmap, SS_CTRL, CH_VSYNC_M(chno), 0);191else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)192regmap_update_bits(ldb->regmap, SS_CTRL,193CH_VSYNC_M(chno), CH_PVSYNC(chno));194195if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC)196regmap_update_bits(ldb->regmap, SS_CTRL, CH_HSYNC_M(chno), 0);197else if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC)198regmap_update_bits(ldb->regmap, SS_CTRL,199CH_HSYNC_M(chno), CH_PHSYNC(chno));200201if (is_split && companion)202companion->funcs->mode_set(companion, mode, adjusted_mode);203}204205static void imx8qxp_ldb_bridge_atomic_pre_enable(struct drm_bridge *bridge,206struct drm_atomic_state *state)207{208struct ldb_channel *ldb_ch = bridge->driver_private;209struct ldb *ldb = ldb_ch->ldb;210struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);211struct drm_bridge *companion = imx8qxp_ldb->companion;212bool is_split = ldb_channel_is_split_link(ldb_ch);213214clk_prepare_enable(imx8qxp_ldb->clk_pixel);215clk_prepare_enable(imx8qxp_ldb->clk_bypass);216217if (is_split && companion)218companion->funcs->atomic_pre_enable(companion, state);219}220221static void imx8qxp_ldb_bridge_atomic_enable(struct drm_bridge *bridge,222struct drm_atomic_state *state)223{224struct ldb_channel *ldb_ch = bridge->driver_private;225struct ldb *ldb = ldb_ch->ldb;226struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =227base_to_imx8qxp_ldb_channel(ldb_ch);228struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);229struct drm_bridge *companion = imx8qxp_ldb->companion;230struct device *dev = imx8qxp_ldb->dev;231bool is_split = ldb_channel_is_split_link(ldb_ch);232int ret;233234if (ldb_ch->chno == 0 || is_split) {235ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;236ldb->ldb_ctrl |= imx8qxp_ldb_ch->di_id == 0 ?237LDB_CH0_MODE_EN_TO_DI0 : LDB_CH0_MODE_EN_TO_DI1;238}239if (ldb_ch->chno == 1 || is_split) {240ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;241ldb->ldb_ctrl |= imx8qxp_ldb_ch->di_id == 0 ?242LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1;243}244245ldb_bridge_enable_helper(bridge);246247ret = phy_power_on(imx8qxp_ldb_ch->phy);248if (ret)249DRM_DEV_ERROR(dev, "failed to power on PHY: %d\n", ret);250251if (is_split && companion)252companion->funcs->atomic_enable(companion, state);253}254255static void imx8qxp_ldb_bridge_atomic_disable(struct drm_bridge *bridge,256struct drm_atomic_state *state)257{258struct ldb_channel *ldb_ch = bridge->driver_private;259struct ldb *ldb = ldb_ch->ldb;260struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =261base_to_imx8qxp_ldb_channel(ldb_ch);262struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);263struct drm_bridge *companion = imx8qxp_ldb->companion;264struct device *dev = imx8qxp_ldb->dev;265bool is_split = ldb_channel_is_split_link(ldb_ch);266int ret;267268ret = phy_power_off(imx8qxp_ldb_ch->phy);269if (ret)270DRM_DEV_ERROR(dev, "failed to power off PHY: %d\n", ret);271272ret = phy_exit(imx8qxp_ldb_ch->phy);273if (ret < 0)274DRM_DEV_ERROR(dev, "failed to teardown PHY: %d\n", ret);275276ldb_bridge_disable_helper(bridge);277278clk_disable_unprepare(imx8qxp_ldb->clk_bypass);279clk_disable_unprepare(imx8qxp_ldb->clk_pixel);280281if (is_split && companion)282companion->funcs->atomic_disable(companion, state);283284ret = pm_runtime_put(dev);285if (ret < 0)286DRM_DEV_ERROR(dev, "failed to put runtime PM: %d\n", ret);287}288289static const u32 imx8qxp_ldb_bus_output_fmts[] = {290MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,291MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,292MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,293MEDIA_BUS_FMT_FIXED,294};295296static bool imx8qxp_ldb_bus_output_fmt_supported(u32 fmt)297{298int i;299300for (i = 0; i < ARRAY_SIZE(imx8qxp_ldb_bus_output_fmts); i++) {301if (imx8qxp_ldb_bus_output_fmts[i] == fmt)302return true;303}304305return false;306}307308static u32 *309imx8qxp_ldb_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,310struct drm_bridge_state *bridge_state,311struct drm_crtc_state *crtc_state,312struct drm_connector_state *conn_state,313u32 output_fmt,314unsigned int *num_input_fmts)315{316struct drm_display_info *di;317const struct drm_format_info *finfo;318u32 *input_fmts;319320if (!imx8qxp_ldb_bus_output_fmt_supported(output_fmt))321return NULL;322323*num_input_fmts = 1;324325input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);326if (!input_fmts)327return NULL;328329switch (output_fmt) {330case MEDIA_BUS_FMT_FIXED:331di = &conn_state->connector->display_info;332333/*334* Look at the first bus format to determine input format.335* Default to MEDIA_BUS_FMT_RGB888_1X24, if no match.336*/337if (di->num_bus_formats) {338finfo = drm_format_info(di->bus_formats[0]);339340input_fmts[0] = finfo->depth == 18 ?341MEDIA_BUS_FMT_RGB666_1X24_CPADHI :342MEDIA_BUS_FMT_RGB888_1X24;343} else {344input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;345}346break;347case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:348input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;349break;350case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:351case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:352input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;353break;354default:355kfree(input_fmts);356input_fmts = NULL;357break;358}359360return input_fmts;361}362363static u32 *364imx8qxp_ldb_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,365struct drm_bridge_state *bridge_state,366struct drm_crtc_state *crtc_state,367struct drm_connector_state *conn_state,368unsigned int *num_output_fmts)369{370*num_output_fmts = ARRAY_SIZE(imx8qxp_ldb_bus_output_fmts);371return kmemdup(imx8qxp_ldb_bus_output_fmts,372sizeof(imx8qxp_ldb_bus_output_fmts), GFP_KERNEL);373}374375static enum drm_mode_status376imx8qxp_ldb_bridge_mode_valid(struct drm_bridge *bridge,377const struct drm_display_info *info,378const struct drm_display_mode *mode)379{380struct ldb_channel *ldb_ch = bridge->driver_private;381bool is_single = ldb_channel_is_single_link(ldb_ch);382383if (mode->clock > 170000)384return MODE_CLOCK_HIGH;385386if (mode->clock > 150000 && is_single)387return MODE_CLOCK_HIGH;388389return MODE_OK;390}391392static const struct drm_bridge_funcs imx8qxp_ldb_bridge_funcs = {393.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,394.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,395.atomic_reset = drm_atomic_helper_bridge_reset,396.mode_valid = imx8qxp_ldb_bridge_mode_valid,397.attach = ldb_bridge_attach_helper,398.atomic_check = imx8qxp_ldb_bridge_atomic_check,399.mode_set = imx8qxp_ldb_bridge_mode_set,400.atomic_pre_enable = imx8qxp_ldb_bridge_atomic_pre_enable,401.atomic_enable = imx8qxp_ldb_bridge_atomic_enable,402.atomic_disable = imx8qxp_ldb_bridge_atomic_disable,403.atomic_get_input_bus_fmts =404imx8qxp_ldb_bridge_atomic_get_input_bus_fmts,405.atomic_get_output_bus_fmts =406imx8qxp_ldb_bridge_atomic_get_output_bus_fmts,407};408409static int imx8qxp_ldb_set_di_id(struct imx8qxp_ldb *imx8qxp_ldb)410{411struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =412imx8qxp_ldb->channel[imx8qxp_ldb->active_chno];413struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base;414struct device_node *ep, *remote;415struct device *dev = imx8qxp_ldb->dev;416struct of_endpoint endpoint;417int ret;418419ep = of_graph_get_endpoint_by_regs(ldb_ch->np, 0, -1);420if (!ep) {421DRM_DEV_ERROR(dev, "failed to get port0 endpoint\n");422return -EINVAL;423}424425remote = of_graph_get_remote_endpoint(ep);426of_node_put(ep);427if (!remote) {428DRM_DEV_ERROR(dev, "failed to get port0 remote endpoint\n");429return -EINVAL;430}431432ret = of_graph_parse_endpoint(remote, &endpoint);433of_node_put(remote);434if (ret) {435DRM_DEV_ERROR(dev, "failed to parse port0 remote endpoint: %d\n",436ret);437return ret;438}439440imx8qxp_ldb_ch->di_id = endpoint.id;441442return 0;443}444445static int446imx8qxp_ldb_check_chno_and_dual_link(struct ldb_channel *ldb_ch, int link)447{448if ((link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS && ldb_ch->chno != 0) ||449(link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS && ldb_ch->chno != 1))450return -EINVAL;451452return 0;453}454455static int imx8qxp_ldb_parse_dt_companion(struct imx8qxp_ldb *imx8qxp_ldb)456{457struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =458imx8qxp_ldb->channel[imx8qxp_ldb->active_chno];459struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base;460struct ldb_channel *companion_ldb_ch;461struct device_node *companion;462struct device_node *child;463struct device_node *companion_port = NULL;464struct device_node *port1, *port2;465struct device *dev = imx8qxp_ldb->dev;466const struct of_device_id *match;467u32 i;468int dual_link;469int ret;470471/* Locate the companion LDB for dual-link operation, if any. */472companion = of_parse_phandle(dev->of_node, "fsl,companion-ldb", 0);473if (!companion)474return 0;475476if (!of_device_is_available(companion)) {477DRM_DEV_ERROR(dev, "companion LDB is not available\n");478ret = -ENODEV;479goto out;480}481482/*483* Sanity check: the companion bridge must have the same compatible484* string.485*/486match = of_match_device(dev->driver->of_match_table, dev);487if (!of_device_is_compatible(companion, match->compatible)) {488DRM_DEV_ERROR(dev, "companion LDB is incompatible\n");489ret = -ENXIO;490goto out;491}492493for_each_available_child_of_node(companion, child) {494ret = of_property_read_u32(child, "reg", &i);495if (ret || i > MAX_LDB_CHAN_NUM - 1) {496DRM_DEV_ERROR(dev,497"invalid channel node address: %u\n", i);498ret = -EINVAL;499of_node_put(child);500goto out;501}502503/*504* Channel numbers have to be different, because channel0505* transmits odd pixels and channel1 transmits even pixels.506*/507if (i == (ldb_ch->chno ^ 0x1)) {508companion_port = child;509break;510}511}512513if (!companion_port) {514DRM_DEV_ERROR(dev,515"failed to find companion LDB channel port\n");516ret = -EINVAL;517goto out;518}519520/*521* We need to work out if the sink is expecting us to function in522* dual-link mode. We do this by looking at the DT port nodes we are523* connected to. If they are marked as expecting odd pixels and524* even pixels than we need to enable LDB split mode.525*/526port1 = of_graph_get_port_by_id(ldb_ch->np, 1);527port2 = of_graph_get_port_by_id(companion_port, 1);528dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);529of_node_put(port1);530of_node_put(port2);531532switch (dual_link) {533case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS:534ldb_ch->link_type = LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS;535break;536case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS:537ldb_ch->link_type = LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS;538break;539default:540ret = dual_link;541DRM_DEV_ERROR(dev,542"failed to get dual link pixel order: %d\n", ret);543goto out;544}545546ret = imx8qxp_ldb_check_chno_and_dual_link(ldb_ch, dual_link);547if (ret < 0) {548DRM_DEV_ERROR(dev,549"unmatched channel number(%u) vs dual link(%d)\n",550ldb_ch->chno, dual_link);551goto out;552}553554imx8qxp_ldb->companion = of_drm_find_bridge(companion_port);555if (!imx8qxp_ldb->companion) {556ret = -EPROBE_DEFER;557DRM_DEV_DEBUG_DRIVER(dev,558"failed to find bridge for companion bridge: %d\n",559ret);560goto out;561}562563DRM_DEV_DEBUG_DRIVER(dev,564"dual-link configuration detected (companion bridge %pOF)\n",565companion);566567companion_ldb_ch = bridge_to_ldb_ch(imx8qxp_ldb->companion);568companion_ldb_ch->link_type = ldb_ch->link_type;569out:570of_node_put(companion_port);571of_node_put(companion);572return ret;573}574575static int imx8qxp_ldb_probe(struct platform_device *pdev)576{577struct device *dev = &pdev->dev;578struct imx8qxp_ldb *imx8qxp_ldb;579struct imx8qxp_ldb_channel *imx8qxp_ldb_ch;580struct ldb *ldb;581struct ldb_channel *ldb_ch;582int ret, i;583584imx8qxp_ldb = devm_kzalloc(dev, sizeof(*imx8qxp_ldb), GFP_KERNEL);585if (!imx8qxp_ldb)586return -ENOMEM;587588for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {589imx8qxp_ldb->channel[i] =590devm_drm_bridge_alloc(dev, struct imx8qxp_ldb_channel, base.bridge,591&imx8qxp_ldb_bridge_funcs);592if (IS_ERR(imx8qxp_ldb->channel[i]))593return PTR_ERR(imx8qxp_ldb->channel[i]);594}595596imx8qxp_ldb->clk_pixel = devm_clk_get(dev, "pixel");597if (IS_ERR(imx8qxp_ldb->clk_pixel)) {598ret = PTR_ERR(imx8qxp_ldb->clk_pixel);599if (ret != -EPROBE_DEFER)600DRM_DEV_ERROR(dev,601"failed to get pixel clock: %d\n", ret);602return ret;603}604605imx8qxp_ldb->clk_bypass = devm_clk_get(dev, "bypass");606if (IS_ERR(imx8qxp_ldb->clk_bypass)) {607ret = PTR_ERR(imx8qxp_ldb->clk_bypass);608if (ret != -EPROBE_DEFER)609DRM_DEV_ERROR(dev,610"failed to get bypass clock: %d\n", ret);611return ret;612}613614imx8qxp_ldb->dev = dev;615616ldb = &imx8qxp_ldb->base;617ldb->dev = dev;618ldb->ctrl_reg = 0xe0;619620for (i = 0; i < MAX_LDB_CHAN_NUM; i++)621ldb->channel[i] = &imx8qxp_ldb->channel[i]->base;622623ret = ldb_init_helper(ldb);624if (ret)625return ret;626627if (ldb->available_ch_cnt == 0) {628DRM_DEV_DEBUG_DRIVER(dev, "no available channel\n");629return 0;630} else if (ldb->available_ch_cnt > 1) {631DRM_DEV_ERROR(dev, "invalid available channel number(%u)\n",632ldb->available_ch_cnt);633return -EINVAL;634}635636for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {637imx8qxp_ldb_ch = imx8qxp_ldb->channel[i];638ldb_ch = &imx8qxp_ldb_ch->base;639640if (ldb_ch->is_available) {641imx8qxp_ldb->active_chno = ldb_ch->chno;642break;643}644}645646imx8qxp_ldb_ch->phy = devm_of_phy_get(dev, ldb_ch->np, "lvds_phy");647if (IS_ERR(imx8qxp_ldb_ch->phy)) {648ret = PTR_ERR(imx8qxp_ldb_ch->phy);649if (ret != -EPROBE_DEFER)650DRM_DEV_ERROR(dev, "failed to get channel%d PHY: %d\n",651imx8qxp_ldb->active_chno, ret);652return ret;653}654655ret = ldb_find_next_bridge_helper(ldb);656if (ret)657return ret;658659ret = imx8qxp_ldb_set_di_id(imx8qxp_ldb);660if (ret)661return ret;662663ret = imx8qxp_ldb_parse_dt_companion(imx8qxp_ldb);664if (ret)665return ret;666667platform_set_drvdata(pdev, imx8qxp_ldb);668pm_runtime_enable(dev);669670ldb_add_bridge_helper(ldb);671672return 0;673}674675static void imx8qxp_ldb_remove(struct platform_device *pdev)676{677struct imx8qxp_ldb *imx8qxp_ldb = platform_get_drvdata(pdev);678struct ldb *ldb = &imx8qxp_ldb->base;679680ldb_remove_bridge_helper(ldb);681682pm_runtime_disable(&pdev->dev);683}684685static int imx8qxp_ldb_runtime_suspend(struct device *dev)686{687return 0;688}689690static int imx8qxp_ldb_runtime_resume(struct device *dev)691{692struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev);693struct ldb *ldb = &imx8qxp_ldb->base;694695/* disable LDB by resetting the control register to POR default */696regmap_write(ldb->regmap, ldb->ctrl_reg, 0);697698return 0;699}700701static const struct dev_pm_ops imx8qxp_ldb_pm_ops = {702RUNTIME_PM_OPS(imx8qxp_ldb_runtime_suspend, imx8qxp_ldb_runtime_resume, NULL)703};704705static const struct of_device_id imx8qxp_ldb_dt_ids[] = {706{ .compatible = "fsl,imx8qxp-ldb" },707{ /* sentinel */ }708};709MODULE_DEVICE_TABLE(of, imx8qxp_ldb_dt_ids);710711static struct platform_driver imx8qxp_ldb_driver = {712.probe = imx8qxp_ldb_probe,713.remove = imx8qxp_ldb_remove,714.driver = {715.pm = pm_ptr(&imx8qxp_ldb_pm_ops),716.name = DRIVER_NAME,717.of_match_table = imx8qxp_ldb_dt_ids,718},719};720module_platform_driver(imx8qxp_ldb_driver);721722MODULE_DESCRIPTION("i.MX8QXP LVDS Display Bridge(LDB)/Pixel Mapper bridge driver");723MODULE_AUTHOR("Liu Ying <[email protected]>");724MODULE_LICENSE("GPL v2");725MODULE_ALIAS("platform:" DRIVER_NAME);726727728