Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qm-ldb.c
51948 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_graph.h>12#include <linux/phy/phy.h>13#include <linux/platform_device.h>14#include <linux/pm_runtime.h>15#include <linux/regmap.h>1617#include <drm/drm_atomic_state_helper.h>18#include <drm/drm_bridge.h>19#include <drm/drm_connector.h>20#include <drm/drm_fourcc.h>21#include <drm/drm_of.h>22#include <drm/drm_print.h>2324#include "imx-ldb-helper.h"2526#define LDB_CH0_10BIT_EN BIT(22)27#define LDB_CH1_10BIT_EN BIT(23)28#define LDB_CH0_DATA_WIDTH_24BIT BIT(24)29#define LDB_CH1_DATA_WIDTH_24BIT BIT(26)30#define LDB_CH0_DATA_WIDTH_30BIT (2 << 24)31#define LDB_CH1_DATA_WIDTH_30BIT (2 << 26)3233#define SS_CTRL 0x2034#define CH_HSYNC_M(id) BIT(0 + ((id) * 2))35#define CH_VSYNC_M(id) BIT(1 + ((id) * 2))36#define CH_PHSYNC(id) BIT(0 + ((id) * 2))37#define CH_PVSYNC(id) BIT(1 + ((id) * 2))3839#define DRIVER_NAME "imx8qm-ldb"4041struct imx8qm_ldb_channel {42struct ldb_channel base;43struct phy *phy;44};4546struct imx8qm_ldb {47struct ldb base;48struct device *dev;49struct imx8qm_ldb_channel *channel[MAX_LDB_CHAN_NUM];50struct clk *clk_pixel;51struct clk *clk_bypass;52int active_chno;53};5455static inline struct imx8qm_ldb_channel *56base_to_imx8qm_ldb_channel(struct ldb_channel *base)57{58return container_of(base, struct imx8qm_ldb_channel, base);59}6061static inline struct imx8qm_ldb *base_to_imx8qm_ldb(struct ldb *base)62{63return container_of(base, struct imx8qm_ldb, base);64}6566static void imx8qm_ldb_set_phy_cfg(struct imx8qm_ldb *imx8qm_ldb,67unsigned long di_clk,68bool is_split, bool is_slave,69struct phy_configure_opts_lvds *phy_cfg)70{71phy_cfg->bits_per_lane_and_dclk_cycle = 7;72phy_cfg->lanes = 4;73phy_cfg->differential_clk_rate = is_split ? di_clk / 2 : di_clk;74phy_cfg->is_slave = is_slave;75}7677static int imx8qm_ldb_bridge_atomic_check(struct drm_bridge *bridge,78struct drm_bridge_state *bridge_state,79struct drm_crtc_state *crtc_state,80struct drm_connector_state *conn_state)81{82struct ldb_channel *ldb_ch = bridge->driver_private;83struct ldb *ldb = ldb_ch->ldb;84struct imx8qm_ldb_channel *imx8qm_ldb_ch =85base_to_imx8qm_ldb_channel(ldb_ch);86struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb);87struct drm_display_mode *adj = &crtc_state->adjusted_mode;88unsigned long di_clk = adj->clock * 1000;89bool is_split = ldb_channel_is_split_link(ldb_ch);90union phy_configure_opts opts = { };91struct phy_configure_opts_lvds *phy_cfg = &opts.lvds;92int ret;9394ret = ldb_bridge_atomic_check_helper(bridge, bridge_state,95crtc_state, conn_state);96if (ret)97return ret;9899imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, false, phy_cfg);100ret = phy_validate(imx8qm_ldb_ch->phy, PHY_MODE_LVDS, 0, &opts);101if (ret < 0) {102DRM_DEV_DEBUG_DRIVER(imx8qm_ldb->dev,103"failed to validate PHY: %d\n", ret);104return ret;105}106107if (is_split) {108imx8qm_ldb_ch =109imx8qm_ldb->channel[imx8qm_ldb->active_chno ^ 1];110imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, true,111phy_cfg);112ret = phy_validate(imx8qm_ldb_ch->phy, PHY_MODE_LVDS, 0, &opts);113if (ret < 0) {114DRM_DEV_DEBUG_DRIVER(imx8qm_ldb->dev,115"failed to validate slave PHY: %d\n",116ret);117return ret;118}119}120121return ret;122}123124static void125imx8qm_ldb_bridge_mode_set(struct drm_bridge *bridge,126const struct drm_display_mode *mode,127const struct drm_display_mode *adjusted_mode)128{129struct ldb_channel *ldb_ch = bridge->driver_private;130struct ldb *ldb = ldb_ch->ldb;131struct imx8qm_ldb_channel *imx8qm_ldb_ch =132base_to_imx8qm_ldb_channel(ldb_ch);133struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb);134struct device *dev = imx8qm_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(imx8qm_ldb_ch->phy);147if (ret < 0)148DRM_DEV_ERROR(dev, "failed to initialize PHY: %d\n", ret);149150clk_set_rate(imx8qm_ldb->clk_bypass, di_clk);151clk_set_rate(imx8qm_ldb->clk_pixel, di_clk);152153imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, false, phy_cfg);154ret = phy_configure(imx8qm_ldb_ch->phy, &opts);155if (ret < 0)156DRM_DEV_ERROR(dev, "failed to configure PHY: %d\n", ret);157158if (is_split) {159imx8qm_ldb_ch =160imx8qm_ldb->channel[imx8qm_ldb->active_chno ^ 1];161imx8qm_ldb_set_phy_cfg(imx8qm_ldb, di_clk, is_split, true,162phy_cfg);163ret = phy_configure(imx8qm_ldb_ch->phy, &opts);164if (ret < 0)165DRM_DEV_ERROR(dev, "failed to configure slave PHY: %d\n",166ret);167}168169/* input VSYNC signal from pixel link is active low */170if (ldb_ch->chno == 0 || is_split)171ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW;172if (ldb_ch->chno == 1 || is_split)173ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW;174175switch (ldb_ch->out_bus_format) {176case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:177break;178case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:179case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:180if (ldb_ch->chno == 0 || is_split)181ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT;182if (ldb_ch->chno == 1 || is_split)183ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT;184break;185}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));200}201202static void imx8qm_ldb_bridge_atomic_enable(struct drm_bridge *bridge,203struct drm_atomic_state *state)204{205struct ldb_channel *ldb_ch = bridge->driver_private;206struct ldb *ldb = ldb_ch->ldb;207struct imx8qm_ldb_channel *imx8qm_ldb_ch =208base_to_imx8qm_ldb_channel(ldb_ch);209struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb);210struct device *dev = imx8qm_ldb->dev;211bool is_split = ldb_channel_is_split_link(ldb_ch);212int ret;213214clk_prepare_enable(imx8qm_ldb->clk_pixel);215clk_prepare_enable(imx8qm_ldb->clk_bypass);216217/* both DI0 and DI1 connect with pixel link, so ok to use DI0 only */218if (ldb_ch->chno == 0 || is_split) {219ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;220ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0;221}222if (ldb_ch->chno == 1 || is_split) {223ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;224ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI0;225}226227if (is_split) {228ret = phy_power_on(imx8qm_ldb->channel[0]->phy);229if (ret)230DRM_DEV_ERROR(dev,231"failed to power on channel0 PHY: %d\n",232ret);233234ret = phy_power_on(imx8qm_ldb->channel[1]->phy);235if (ret)236DRM_DEV_ERROR(dev,237"failed to power on channel1 PHY: %d\n",238ret);239} else {240ret = phy_power_on(imx8qm_ldb_ch->phy);241if (ret)242DRM_DEV_ERROR(dev, "failed to power on PHY: %d\n", ret);243}244245ldb_bridge_enable_helper(bridge);246}247248static void imx8qm_ldb_bridge_atomic_disable(struct drm_bridge *bridge,249struct drm_atomic_state *state)250{251struct ldb_channel *ldb_ch = bridge->driver_private;252struct ldb *ldb = ldb_ch->ldb;253struct imx8qm_ldb_channel *imx8qm_ldb_ch =254base_to_imx8qm_ldb_channel(ldb_ch);255struct imx8qm_ldb *imx8qm_ldb = base_to_imx8qm_ldb(ldb);256struct device *dev = imx8qm_ldb->dev;257bool is_split = ldb_channel_is_split_link(ldb_ch);258int ret;259260ldb_bridge_disable_helper(bridge);261262if (is_split) {263ret = phy_power_off(imx8qm_ldb->channel[0]->phy);264if (ret)265DRM_DEV_ERROR(dev,266"failed to power off channel0 PHY: %d\n",267ret);268ret = phy_power_off(imx8qm_ldb->channel[1]->phy);269if (ret)270DRM_DEV_ERROR(dev,271"failed to power off channel1 PHY: %d\n",272ret);273} else {274ret = phy_power_off(imx8qm_ldb_ch->phy);275if (ret)276DRM_DEV_ERROR(dev, "failed to power off PHY: %d\n", ret);277}278279clk_disable_unprepare(imx8qm_ldb->clk_bypass);280clk_disable_unprepare(imx8qm_ldb->clk_pixel);281282pm_runtime_put(dev);283}284285static const u32 imx8qm_ldb_bus_output_fmts[] = {286MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,287MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,288MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,289MEDIA_BUS_FMT_FIXED,290};291292static bool imx8qm_ldb_bus_output_fmt_supported(u32 fmt)293{294int i;295296for (i = 0; i < ARRAY_SIZE(imx8qm_ldb_bus_output_fmts); i++) {297if (imx8qm_ldb_bus_output_fmts[i] == fmt)298return true;299}300301return false;302}303304static u32 *305imx8qm_ldb_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,306struct drm_bridge_state *bridge_state,307struct drm_crtc_state *crtc_state,308struct drm_connector_state *conn_state,309u32 output_fmt,310unsigned int *num_input_fmts)311{312struct drm_display_info *di;313const struct drm_format_info *finfo;314u32 *input_fmts;315316if (!imx8qm_ldb_bus_output_fmt_supported(output_fmt))317return NULL;318319*num_input_fmts = 1;320321input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);322if (!input_fmts)323return NULL;324325switch (output_fmt) {326case MEDIA_BUS_FMT_FIXED:327di = &conn_state->connector->display_info;328329/*330* Look at the first bus format to determine input format.331* Default to MEDIA_BUS_FMT_RGB888_1X36_CPADLO, if no match.332*/333if (di->num_bus_formats) {334finfo = drm_format_info(di->bus_formats[0]);335336input_fmts[0] = finfo->depth == 18 ?337MEDIA_BUS_FMT_RGB666_1X36_CPADLO :338MEDIA_BUS_FMT_RGB888_1X36_CPADLO;339} else {340input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;341}342break;343case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:344input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO;345break;346case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:347case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:348input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;349break;350default:351kfree(input_fmts);352input_fmts = NULL;353break;354}355356return input_fmts;357}358359static u32 *360imx8qm_ldb_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,361struct drm_bridge_state *bridge_state,362struct drm_crtc_state *crtc_state,363struct drm_connector_state *conn_state,364unsigned int *num_output_fmts)365{366*num_output_fmts = ARRAY_SIZE(imx8qm_ldb_bus_output_fmts);367return kmemdup(imx8qm_ldb_bus_output_fmts,368sizeof(imx8qm_ldb_bus_output_fmts), GFP_KERNEL);369}370371static enum drm_mode_status372imx8qm_ldb_bridge_mode_valid(struct drm_bridge *bridge,373const struct drm_display_info *info,374const struct drm_display_mode *mode)375{376struct ldb_channel *ldb_ch = bridge->driver_private;377bool is_single = ldb_channel_is_single_link(ldb_ch);378379if (mode->clock > 300000)380return MODE_CLOCK_HIGH;381382if (mode->clock > 150000 && is_single)383return MODE_CLOCK_HIGH;384385return MODE_OK;386}387388static const struct drm_bridge_funcs imx8qm_ldb_bridge_funcs = {389.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,390.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,391.atomic_reset = drm_atomic_helper_bridge_reset,392.mode_valid = imx8qm_ldb_bridge_mode_valid,393.attach = ldb_bridge_attach_helper,394.atomic_check = imx8qm_ldb_bridge_atomic_check,395.mode_set = imx8qm_ldb_bridge_mode_set,396.atomic_enable = imx8qm_ldb_bridge_atomic_enable,397.atomic_disable = imx8qm_ldb_bridge_atomic_disable,398.atomic_get_input_bus_fmts =399imx8qm_ldb_bridge_atomic_get_input_bus_fmts,400.atomic_get_output_bus_fmts =401imx8qm_ldb_bridge_atomic_get_output_bus_fmts,402};403404static int imx8qm_ldb_get_phy(struct imx8qm_ldb *imx8qm_ldb)405{406struct imx8qm_ldb_channel *imx8qm_ldb_ch;407struct ldb_channel *ldb_ch;408struct device *dev = imx8qm_ldb->dev;409int i, ret;410411for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {412imx8qm_ldb_ch = imx8qm_ldb->channel[i];413ldb_ch = &imx8qm_ldb_ch->base;414415if (!ldb_ch->is_available)416continue;417418imx8qm_ldb_ch->phy = devm_of_phy_get(dev, ldb_ch->np,419"lvds_phy");420if (IS_ERR(imx8qm_ldb_ch->phy)) {421ret = PTR_ERR(imx8qm_ldb_ch->phy);422if (ret != -EPROBE_DEFER)423DRM_DEV_ERROR(dev,424"failed to get channel%d PHY: %d\n",425i, ret);426return ret;427}428}429430return 0;431}432433static int imx8qm_ldb_probe(struct platform_device *pdev)434{435struct device *dev = &pdev->dev;436struct imx8qm_ldb *imx8qm_ldb;437struct imx8qm_ldb_channel *imx8qm_ldb_ch;438struct ldb *ldb;439struct ldb_channel *ldb_ch;440struct device_node *port1, *port2;441int pixel_order;442int ret, i;443444imx8qm_ldb = devm_kzalloc(dev, sizeof(*imx8qm_ldb), GFP_KERNEL);445if (!imx8qm_ldb)446return -ENOMEM;447448for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {449imx8qm_ldb->channel[i] =450devm_drm_bridge_alloc(dev, struct imx8qm_ldb_channel, base.bridge,451&imx8qm_ldb_bridge_funcs);452if (IS_ERR(imx8qm_ldb->channel[i]))453return PTR_ERR(imx8qm_ldb->channel[i]);454}455456imx8qm_ldb->clk_pixel = devm_clk_get(dev, "pixel");457if (IS_ERR(imx8qm_ldb->clk_pixel)) {458ret = PTR_ERR(imx8qm_ldb->clk_pixel);459if (ret != -EPROBE_DEFER)460DRM_DEV_ERROR(dev,461"failed to get pixel clock: %d\n", ret);462return ret;463}464465imx8qm_ldb->clk_bypass = devm_clk_get(dev, "bypass");466if (IS_ERR(imx8qm_ldb->clk_bypass)) {467ret = PTR_ERR(imx8qm_ldb->clk_bypass);468if (ret != -EPROBE_DEFER)469DRM_DEV_ERROR(dev,470"failed to get bypass clock: %d\n", ret);471return ret;472}473474imx8qm_ldb->dev = dev;475476ldb = &imx8qm_ldb->base;477ldb->dev = dev;478ldb->ctrl_reg = 0xe0;479480for (i = 0; i < MAX_LDB_CHAN_NUM; i++)481ldb->channel[i] = &imx8qm_ldb->channel[i]->base;482483ret = ldb_init_helper(ldb);484if (ret)485return ret;486487if (ldb->available_ch_cnt == 0) {488DRM_DEV_DEBUG_DRIVER(dev, "no available channel\n");489return 0;490}491492if (ldb->available_ch_cnt == 2) {493port1 = of_graph_get_port_by_id(ldb->channel[0]->np, 1);494port2 = of_graph_get_port_by_id(ldb->channel[1]->np, 1);495pixel_order =496drm_of_lvds_get_dual_link_pixel_order(port1, port2);497of_node_put(port1);498of_node_put(port2);499500if (pixel_order != DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) {501DRM_DEV_ERROR(dev, "invalid dual link pixel order: %d\n",502pixel_order);503return -EINVAL;504}505506imx8qm_ldb->active_chno = 0;507imx8qm_ldb_ch = imx8qm_ldb->channel[0];508ldb_ch = &imx8qm_ldb_ch->base;509ldb_ch->link_type = pixel_order;510} else {511for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {512imx8qm_ldb_ch = imx8qm_ldb->channel[i];513ldb_ch = &imx8qm_ldb_ch->base;514515if (ldb_ch->is_available) {516imx8qm_ldb->active_chno = ldb_ch->chno;517break;518}519}520}521522ret = imx8qm_ldb_get_phy(imx8qm_ldb);523if (ret)524return ret;525526ret = ldb_find_next_bridge_helper(ldb);527if (ret)528return ret;529530platform_set_drvdata(pdev, imx8qm_ldb);531pm_runtime_enable(dev);532533ldb_add_bridge_helper(ldb);534535return ret;536}537538static void imx8qm_ldb_remove(struct platform_device *pdev)539{540struct imx8qm_ldb *imx8qm_ldb = platform_get_drvdata(pdev);541struct ldb *ldb = &imx8qm_ldb->base;542543ldb_remove_bridge_helper(ldb);544545pm_runtime_disable(&pdev->dev);546}547548static int imx8qm_ldb_runtime_suspend(struct device *dev)549{550return 0;551}552553static int imx8qm_ldb_runtime_resume(struct device *dev)554{555struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev);556struct ldb *ldb = &imx8qm_ldb->base;557558/* disable LDB by resetting the control register to POR default */559regmap_write(ldb->regmap, ldb->ctrl_reg, 0);560561return 0;562}563564static const struct dev_pm_ops imx8qm_ldb_pm_ops = {565RUNTIME_PM_OPS(imx8qm_ldb_runtime_suspend, imx8qm_ldb_runtime_resume, NULL)566};567568static const struct of_device_id imx8qm_ldb_dt_ids[] = {569{ .compatible = "fsl,imx8qm-ldb" },570{ /* sentinel */ }571};572MODULE_DEVICE_TABLE(of, imx8qm_ldb_dt_ids);573574static struct platform_driver imx8qm_ldb_driver = {575.probe = imx8qm_ldb_probe,576.remove = imx8qm_ldb_remove,577.driver = {578.pm = pm_ptr(&imx8qm_ldb_pm_ops),579.name = DRIVER_NAME,580.of_match_table = imx8qm_ldb_dt_ids,581},582};583module_platform_driver(imx8qm_ldb_driver);584585MODULE_DESCRIPTION("i.MX8QM LVDS Display Bridge(LDB)/Pixel Mapper bridge driver");586MODULE_AUTHOR("Liu Ying <[email protected]>");587MODULE_LICENSE("GPL v2");588MODULE_ALIAS("platform:" DRIVER_NAME);589590591