Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c
52226 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_bridge_destroy(struct drm_bridge *bridge)65{66struct ldb_channel *ldb_ch = bridge->driver_private;67struct imx8qxp_ldb *imx8qxp_ldb;6869if (!ldb_ch)70return;7172imx8qxp_ldb = base_to_imx8qxp_ldb(ldb_ch->ldb);73drm_bridge_put(imx8qxp_ldb->companion);74}7576static void imx8qxp_ldb_set_phy_cfg(struct imx8qxp_ldb *imx8qxp_ldb,77unsigned long di_clk, bool is_split,78struct phy_configure_opts_lvds *phy_cfg)79{80phy_cfg->bits_per_lane_and_dclk_cycle = 7;81phy_cfg->lanes = 4;8283if (is_split) {84phy_cfg->differential_clk_rate = di_clk / 2;85phy_cfg->is_slave = !imx8qxp_ldb->companion;86} else {87phy_cfg->differential_clk_rate = di_clk;88phy_cfg->is_slave = false;89}90}9192static int93imx8qxp_ldb_bridge_atomic_check(struct drm_bridge *bridge,94struct drm_bridge_state *bridge_state,95struct drm_crtc_state *crtc_state,96struct drm_connector_state *conn_state)97{98struct ldb_channel *ldb_ch = bridge->driver_private;99struct ldb *ldb = ldb_ch->ldb;100struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =101base_to_imx8qxp_ldb_channel(ldb_ch);102struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);103struct drm_bridge *companion = imx8qxp_ldb->companion;104struct drm_display_mode *adj = &crtc_state->adjusted_mode;105unsigned long di_clk = adj->clock * 1000;106bool is_split = ldb_channel_is_split_link(ldb_ch);107union phy_configure_opts opts = { };108struct phy_configure_opts_lvds *phy_cfg = &opts.lvds;109int ret;110111ret = ldb_bridge_atomic_check_helper(bridge, bridge_state,112crtc_state, conn_state);113if (ret)114return ret;115116imx8qxp_ldb_set_phy_cfg(imx8qxp_ldb, di_clk, is_split, phy_cfg);117ret = phy_validate(imx8qxp_ldb_ch->phy, PHY_MODE_LVDS, 0, &opts);118if (ret < 0) {119DRM_DEV_DEBUG_DRIVER(imx8qxp_ldb->dev,120"failed to validate PHY: %d\n", ret);121return ret;122}123124if (is_split && companion) {125ret = companion->funcs->atomic_check(companion,126bridge_state, crtc_state, conn_state);127if (ret)128return ret;129}130131return ret;132}133134static void135imx8qxp_ldb_bridge_mode_set(struct drm_bridge *bridge,136const struct drm_display_mode *mode,137const struct drm_display_mode *adjusted_mode)138{139struct ldb_channel *ldb_ch = bridge->driver_private;140struct ldb_channel *companion_ldb_ch;141struct ldb *ldb = ldb_ch->ldb;142struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =143base_to_imx8qxp_ldb_channel(ldb_ch);144struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);145struct drm_bridge *companion = imx8qxp_ldb->companion;146struct device *dev = imx8qxp_ldb->dev;147unsigned long di_clk = adjusted_mode->clock * 1000;148bool is_split = ldb_channel_is_split_link(ldb_ch);149union phy_configure_opts opts = { };150struct phy_configure_opts_lvds *phy_cfg = &opts.lvds;151u32 chno = ldb_ch->chno;152int ret;153154ret = pm_runtime_get_sync(dev);155if (ret < 0)156DRM_DEV_ERROR(dev, "failed to get runtime PM sync: %d\n", ret);157158ret = phy_init(imx8qxp_ldb_ch->phy);159if (ret < 0)160DRM_DEV_ERROR(dev, "failed to initialize PHY: %d\n", ret);161162ret = phy_set_mode(imx8qxp_ldb_ch->phy, PHY_MODE_LVDS);163if (ret < 0)164DRM_DEV_ERROR(dev, "failed to set PHY mode: %d\n", ret);165166if (is_split && companion) {167companion_ldb_ch = bridge_to_ldb_ch(companion);168169companion_ldb_ch->in_bus_format = ldb_ch->in_bus_format;170companion_ldb_ch->out_bus_format = ldb_ch->out_bus_format;171}172173clk_set_rate(imx8qxp_ldb->clk_bypass, di_clk);174clk_set_rate(imx8qxp_ldb->clk_pixel, di_clk);175176imx8qxp_ldb_set_phy_cfg(imx8qxp_ldb, di_clk, is_split, phy_cfg);177ret = phy_configure(imx8qxp_ldb_ch->phy, &opts);178if (ret < 0)179DRM_DEV_ERROR(dev, "failed to configure PHY: %d\n", ret);180181if (chno == 0)182ldb->ldb_ctrl &= ~LDB_CH_SEL;183else184ldb->ldb_ctrl |= LDB_CH_SEL;185186/* input VSYNC signal from pixel link is active low */187if (imx8qxp_ldb_ch->di_id == 0)188ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW;189else190ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW;191192/*193* For split mode, settle input VSYNC signal polarity and194* channel selection down early.195*/196if (is_split)197regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);198199ldb_bridge_mode_set_helper(bridge, mode, adjusted_mode);200201if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC)202regmap_update_bits(ldb->regmap, SS_CTRL, CH_VSYNC_M(chno), 0);203else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)204regmap_update_bits(ldb->regmap, SS_CTRL,205CH_VSYNC_M(chno), CH_PVSYNC(chno));206207if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC)208regmap_update_bits(ldb->regmap, SS_CTRL, CH_HSYNC_M(chno), 0);209else if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC)210regmap_update_bits(ldb->regmap, SS_CTRL,211CH_HSYNC_M(chno), CH_PHSYNC(chno));212213if (is_split && companion)214companion->funcs->mode_set(companion, mode, adjusted_mode);215}216217static void imx8qxp_ldb_bridge_atomic_pre_enable(struct drm_bridge *bridge,218struct drm_atomic_state *state)219{220struct ldb_channel *ldb_ch = bridge->driver_private;221struct ldb *ldb = ldb_ch->ldb;222struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);223struct drm_bridge *companion = imx8qxp_ldb->companion;224bool is_split = ldb_channel_is_split_link(ldb_ch);225226clk_prepare_enable(imx8qxp_ldb->clk_pixel);227clk_prepare_enable(imx8qxp_ldb->clk_bypass);228229if (is_split && companion)230companion->funcs->atomic_pre_enable(companion, state);231}232233static void imx8qxp_ldb_bridge_atomic_enable(struct drm_bridge *bridge,234struct drm_atomic_state *state)235{236struct ldb_channel *ldb_ch = bridge->driver_private;237struct ldb *ldb = ldb_ch->ldb;238struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =239base_to_imx8qxp_ldb_channel(ldb_ch);240struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);241struct drm_bridge *companion = imx8qxp_ldb->companion;242struct device *dev = imx8qxp_ldb->dev;243bool is_split = ldb_channel_is_split_link(ldb_ch);244int ret;245246if (ldb_ch->chno == 0 || is_split) {247ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;248ldb->ldb_ctrl |= imx8qxp_ldb_ch->di_id == 0 ?249LDB_CH0_MODE_EN_TO_DI0 : LDB_CH0_MODE_EN_TO_DI1;250}251if (ldb_ch->chno == 1 || is_split) {252ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;253ldb->ldb_ctrl |= imx8qxp_ldb_ch->di_id == 0 ?254LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1;255}256257ldb_bridge_enable_helper(bridge);258259ret = phy_power_on(imx8qxp_ldb_ch->phy);260if (ret)261DRM_DEV_ERROR(dev, "failed to power on PHY: %d\n", ret);262263if (is_split && companion)264companion->funcs->atomic_enable(companion, state);265}266267static void imx8qxp_ldb_bridge_atomic_disable(struct drm_bridge *bridge,268struct drm_atomic_state *state)269{270struct ldb_channel *ldb_ch = bridge->driver_private;271struct ldb *ldb = ldb_ch->ldb;272struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =273base_to_imx8qxp_ldb_channel(ldb_ch);274struct imx8qxp_ldb *imx8qxp_ldb = base_to_imx8qxp_ldb(ldb);275struct drm_bridge *companion = imx8qxp_ldb->companion;276struct device *dev = imx8qxp_ldb->dev;277bool is_split = ldb_channel_is_split_link(ldb_ch);278int ret;279280ret = phy_power_off(imx8qxp_ldb_ch->phy);281if (ret)282DRM_DEV_ERROR(dev, "failed to power off PHY: %d\n", ret);283284ret = phy_exit(imx8qxp_ldb_ch->phy);285if (ret < 0)286DRM_DEV_ERROR(dev, "failed to teardown PHY: %d\n", ret);287288ldb_bridge_disable_helper(bridge);289290clk_disable_unprepare(imx8qxp_ldb->clk_bypass);291clk_disable_unprepare(imx8qxp_ldb->clk_pixel);292293if (is_split && companion)294companion->funcs->atomic_disable(companion, state);295296pm_runtime_put(dev);297}298299static const u32 imx8qxp_ldb_bus_output_fmts[] = {300MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,301MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,302MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,303MEDIA_BUS_FMT_FIXED,304};305306static bool imx8qxp_ldb_bus_output_fmt_supported(u32 fmt)307{308int i;309310for (i = 0; i < ARRAY_SIZE(imx8qxp_ldb_bus_output_fmts); i++) {311if (imx8qxp_ldb_bus_output_fmts[i] == fmt)312return true;313}314315return false;316}317318static u32 *319imx8qxp_ldb_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,320struct drm_bridge_state *bridge_state,321struct drm_crtc_state *crtc_state,322struct drm_connector_state *conn_state,323u32 output_fmt,324unsigned int *num_input_fmts)325{326struct drm_display_info *di;327const struct drm_format_info *finfo;328u32 *input_fmts;329330if (!imx8qxp_ldb_bus_output_fmt_supported(output_fmt))331return NULL;332333*num_input_fmts = 1;334335input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);336if (!input_fmts)337return NULL;338339switch (output_fmt) {340case MEDIA_BUS_FMT_FIXED:341di = &conn_state->connector->display_info;342343/*344* Look at the first bus format to determine input format.345* Default to MEDIA_BUS_FMT_RGB888_1X24, if no match.346*/347if (di->num_bus_formats) {348finfo = drm_format_info(di->bus_formats[0]);349350input_fmts[0] = finfo->depth == 18 ?351MEDIA_BUS_FMT_RGB666_1X24_CPADHI :352MEDIA_BUS_FMT_RGB888_1X24;353} else {354input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;355}356break;357case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:358input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;359break;360case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:361case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:362input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;363break;364default:365kfree(input_fmts);366input_fmts = NULL;367break;368}369370return input_fmts;371}372373static u32 *374imx8qxp_ldb_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,375struct drm_bridge_state *bridge_state,376struct drm_crtc_state *crtc_state,377struct drm_connector_state *conn_state,378unsigned int *num_output_fmts)379{380*num_output_fmts = ARRAY_SIZE(imx8qxp_ldb_bus_output_fmts);381return kmemdup(imx8qxp_ldb_bus_output_fmts,382sizeof(imx8qxp_ldb_bus_output_fmts), GFP_KERNEL);383}384385static enum drm_mode_status386imx8qxp_ldb_bridge_mode_valid(struct drm_bridge *bridge,387const struct drm_display_info *info,388const struct drm_display_mode *mode)389{390struct ldb_channel *ldb_ch = bridge->driver_private;391bool is_single = ldb_channel_is_single_link(ldb_ch);392393if (mode->clock > 170000)394return MODE_CLOCK_HIGH;395396if (mode->clock > 150000 && is_single)397return MODE_CLOCK_HIGH;398399return MODE_OK;400}401402static const struct drm_bridge_funcs imx8qxp_ldb_bridge_funcs = {403.destroy = imx8qxp_ldb_bridge_destroy,404.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,405.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,406.atomic_reset = drm_atomic_helper_bridge_reset,407.mode_valid = imx8qxp_ldb_bridge_mode_valid,408.attach = ldb_bridge_attach_helper,409.atomic_check = imx8qxp_ldb_bridge_atomic_check,410.mode_set = imx8qxp_ldb_bridge_mode_set,411.atomic_pre_enable = imx8qxp_ldb_bridge_atomic_pre_enable,412.atomic_enable = imx8qxp_ldb_bridge_atomic_enable,413.atomic_disable = imx8qxp_ldb_bridge_atomic_disable,414.atomic_get_input_bus_fmts =415imx8qxp_ldb_bridge_atomic_get_input_bus_fmts,416.atomic_get_output_bus_fmts =417imx8qxp_ldb_bridge_atomic_get_output_bus_fmts,418};419420static int imx8qxp_ldb_set_di_id(struct imx8qxp_ldb *imx8qxp_ldb)421{422struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =423imx8qxp_ldb->channel[imx8qxp_ldb->active_chno];424struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base;425struct device_node *ep, *remote;426struct device *dev = imx8qxp_ldb->dev;427struct of_endpoint endpoint;428int ret;429430ep = of_graph_get_endpoint_by_regs(ldb_ch->np, 0, -1);431if (!ep) {432DRM_DEV_ERROR(dev, "failed to get port0 endpoint\n");433return -EINVAL;434}435436remote = of_graph_get_remote_endpoint(ep);437of_node_put(ep);438if (!remote) {439DRM_DEV_ERROR(dev, "failed to get port0 remote endpoint\n");440return -EINVAL;441}442443ret = of_graph_parse_endpoint(remote, &endpoint);444of_node_put(remote);445if (ret) {446DRM_DEV_ERROR(dev, "failed to parse port0 remote endpoint: %d\n",447ret);448return ret;449}450451imx8qxp_ldb_ch->di_id = endpoint.id;452453return 0;454}455456static int457imx8qxp_ldb_check_chno_and_dual_link(struct ldb_channel *ldb_ch, int link)458{459if ((link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS && ldb_ch->chno != 0) ||460(link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS && ldb_ch->chno != 1))461return -EINVAL;462463return 0;464}465466static int imx8qxp_ldb_parse_dt_companion(struct imx8qxp_ldb *imx8qxp_ldb)467{468struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =469imx8qxp_ldb->channel[imx8qxp_ldb->active_chno];470struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base;471struct ldb_channel *companion_ldb_ch;472struct device_node *companion;473struct device_node *child;474struct device_node *companion_port = NULL;475struct device_node *port1, *port2;476struct device *dev = imx8qxp_ldb->dev;477const struct of_device_id *match;478u32 i;479int dual_link;480int ret;481482/* Locate the companion LDB for dual-link operation, if any. */483companion = of_parse_phandle(dev->of_node, "fsl,companion-ldb", 0);484if (!companion)485return 0;486487if (!of_device_is_available(companion)) {488DRM_DEV_ERROR(dev, "companion LDB is not available\n");489ret = -ENODEV;490goto out;491}492493/*494* Sanity check: the companion bridge must have the same compatible495* string.496*/497match = of_match_device(dev->driver->of_match_table, dev);498if (!of_device_is_compatible(companion, match->compatible)) {499DRM_DEV_ERROR(dev, "companion LDB is incompatible\n");500ret = -ENXIO;501goto out;502}503504for_each_available_child_of_node(companion, child) {505ret = of_property_read_u32(child, "reg", &i);506if (ret || i > MAX_LDB_CHAN_NUM - 1) {507DRM_DEV_ERROR(dev,508"invalid channel node address: %u\n", i);509ret = -EINVAL;510of_node_put(child);511goto out;512}513514/*515* Channel numbers have to be different, because channel0516* transmits odd pixels and channel1 transmits even pixels.517*/518if (i == (ldb_ch->chno ^ 0x1)) {519companion_port = child;520break;521}522}523524if (!companion_port) {525DRM_DEV_ERROR(dev,526"failed to find companion LDB channel port\n");527ret = -EINVAL;528goto out;529}530531/*532* We need to work out if the sink is expecting us to function in533* dual-link mode. We do this by looking at the DT port nodes we are534* connected to. If they are marked as expecting odd pixels and535* even pixels than we need to enable LDB split mode.536*/537port1 = of_graph_get_port_by_id(ldb_ch->np, 1);538port2 = of_graph_get_port_by_id(companion_port, 1);539dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2);540of_node_put(port1);541of_node_put(port2);542543switch (dual_link) {544case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS:545ldb_ch->link_type = LDB_CH_DUAL_LINK_ODD_EVEN_PIXELS;546break;547case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS:548ldb_ch->link_type = LDB_CH_DUAL_LINK_EVEN_ODD_PIXELS;549break;550default:551ret = dual_link;552DRM_DEV_ERROR(dev,553"failed to get dual link pixel order: %d\n", ret);554goto out;555}556557ret = imx8qxp_ldb_check_chno_and_dual_link(ldb_ch, dual_link);558if (ret < 0) {559DRM_DEV_ERROR(dev,560"unmatched channel number(%u) vs dual link(%d)\n",561ldb_ch->chno, dual_link);562goto out;563}564565imx8qxp_ldb->companion = of_drm_find_and_get_bridge(companion_port);566if (!imx8qxp_ldb->companion) {567ret = -EPROBE_DEFER;568DRM_DEV_DEBUG_DRIVER(dev,569"failed to find bridge for companion bridge: %d\n",570ret);571goto out;572}573574DRM_DEV_DEBUG_DRIVER(dev,575"dual-link configuration detected (companion bridge %pOF)\n",576companion);577578companion_ldb_ch = bridge_to_ldb_ch(imx8qxp_ldb->companion);579companion_ldb_ch->link_type = ldb_ch->link_type;580out:581of_node_put(companion_port);582of_node_put(companion);583return ret;584}585586static int imx8qxp_ldb_probe(struct platform_device *pdev)587{588struct device *dev = &pdev->dev;589struct imx8qxp_ldb *imx8qxp_ldb;590struct imx8qxp_ldb_channel *imx8qxp_ldb_ch;591struct ldb *ldb;592struct ldb_channel *ldb_ch;593int ret, i;594595imx8qxp_ldb = devm_kzalloc(dev, sizeof(*imx8qxp_ldb), GFP_KERNEL);596if (!imx8qxp_ldb)597return -ENOMEM;598599for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {600imx8qxp_ldb->channel[i] =601devm_drm_bridge_alloc(dev, struct imx8qxp_ldb_channel, base.bridge,602&imx8qxp_ldb_bridge_funcs);603if (IS_ERR(imx8qxp_ldb->channel[i]))604return PTR_ERR(imx8qxp_ldb->channel[i]);605}606607imx8qxp_ldb->clk_pixel = devm_clk_get(dev, "pixel");608if (IS_ERR(imx8qxp_ldb->clk_pixel)) {609ret = PTR_ERR(imx8qxp_ldb->clk_pixel);610if (ret != -EPROBE_DEFER)611DRM_DEV_ERROR(dev,612"failed to get pixel clock: %d\n", ret);613return ret;614}615616imx8qxp_ldb->clk_bypass = devm_clk_get(dev, "bypass");617if (IS_ERR(imx8qxp_ldb->clk_bypass)) {618ret = PTR_ERR(imx8qxp_ldb->clk_bypass);619if (ret != -EPROBE_DEFER)620DRM_DEV_ERROR(dev,621"failed to get bypass clock: %d\n", ret);622return ret;623}624625imx8qxp_ldb->dev = dev;626627ldb = &imx8qxp_ldb->base;628ldb->dev = dev;629ldb->ctrl_reg = 0xe0;630631for (i = 0; i < MAX_LDB_CHAN_NUM; i++)632ldb->channel[i] = &imx8qxp_ldb->channel[i]->base;633634ret = ldb_init_helper(ldb);635if (ret)636return ret;637638if (ldb->available_ch_cnt == 0) {639DRM_DEV_DEBUG_DRIVER(dev, "no available channel\n");640return 0;641} else if (ldb->available_ch_cnt > 1) {642DRM_DEV_ERROR(dev, "invalid available channel number(%u)\n",643ldb->available_ch_cnt);644return -EINVAL;645}646647for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {648imx8qxp_ldb_ch = imx8qxp_ldb->channel[i];649ldb_ch = &imx8qxp_ldb_ch->base;650651if (ldb_ch->is_available) {652imx8qxp_ldb->active_chno = ldb_ch->chno;653break;654}655}656657imx8qxp_ldb_ch->phy = devm_of_phy_get(dev, ldb_ch->np, "lvds_phy");658if (IS_ERR(imx8qxp_ldb_ch->phy)) {659ret = PTR_ERR(imx8qxp_ldb_ch->phy);660if (ret != -EPROBE_DEFER)661DRM_DEV_ERROR(dev, "failed to get channel%d PHY: %d\n",662imx8qxp_ldb->active_chno, ret);663return ret;664}665666ret = ldb_find_next_bridge_helper(ldb);667if (ret)668return ret;669670ret = imx8qxp_ldb_set_di_id(imx8qxp_ldb);671if (ret)672return ret;673674ret = imx8qxp_ldb_parse_dt_companion(imx8qxp_ldb);675if (ret)676return ret;677678platform_set_drvdata(pdev, imx8qxp_ldb);679pm_runtime_enable(dev);680681ldb_add_bridge_helper(ldb);682683return 0;684}685686static void imx8qxp_ldb_remove(struct platform_device *pdev)687{688struct imx8qxp_ldb *imx8qxp_ldb = platform_get_drvdata(pdev);689struct ldb *ldb = &imx8qxp_ldb->base;690691ldb_remove_bridge_helper(ldb);692693pm_runtime_disable(&pdev->dev);694}695696static int imx8qxp_ldb_runtime_resume(struct device *dev)697{698struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev);699struct ldb *ldb = &imx8qxp_ldb->base;700701/* disable LDB by resetting the control register to POR default */702regmap_write(ldb->regmap, ldb->ctrl_reg, 0);703704return 0;705}706707static const struct dev_pm_ops imx8qxp_ldb_pm_ops = {708RUNTIME_PM_OPS(NULL, imx8qxp_ldb_runtime_resume, NULL)709};710711static const struct of_device_id imx8qxp_ldb_dt_ids[] = {712{ .compatible = "fsl,imx8qxp-ldb" },713{ /* sentinel */ }714};715MODULE_DEVICE_TABLE(of, imx8qxp_ldb_dt_ids);716717static struct platform_driver imx8qxp_ldb_driver = {718.probe = imx8qxp_ldb_probe,719.remove = imx8qxp_ldb_remove,720.driver = {721.pm = pm_ptr(&imx8qxp_ldb_pm_ops),722.name = DRIVER_NAME,723.of_match_table = imx8qxp_ldb_dt_ids,724},725};726module_platform_driver(imx8qxp_ldb_driver);727728MODULE_DESCRIPTION("i.MX8QXP LVDS Display Bridge(LDB)/Pixel Mapper bridge driver");729MODULE_AUTHOR("Liu Ying <[email protected]>");730MODULE_LICENSE("GPL v2");731MODULE_ALIAS("platform:" DRIVER_NAME);732733734