Path: blob/master/drivers/gpu/drm/bridge/imx/imx8qm-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_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);281282ret = pm_runtime_put(dev);283if (ret < 0)284DRM_DEV_ERROR(dev, "failed to put runtime PM: %d\n", ret);285}286287static const u32 imx8qm_ldb_bus_output_fmts[] = {288MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,289MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,290MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,291MEDIA_BUS_FMT_FIXED,292};293294static bool imx8qm_ldb_bus_output_fmt_supported(u32 fmt)295{296int i;297298for (i = 0; i < ARRAY_SIZE(imx8qm_ldb_bus_output_fmts); i++) {299if (imx8qm_ldb_bus_output_fmts[i] == fmt)300return true;301}302303return false;304}305306static u32 *307imx8qm_ldb_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,308struct drm_bridge_state *bridge_state,309struct drm_crtc_state *crtc_state,310struct drm_connector_state *conn_state,311u32 output_fmt,312unsigned int *num_input_fmts)313{314struct drm_display_info *di;315const struct drm_format_info *finfo;316u32 *input_fmts;317318if (!imx8qm_ldb_bus_output_fmt_supported(output_fmt))319return NULL;320321*num_input_fmts = 1;322323input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);324if (!input_fmts)325return NULL;326327switch (output_fmt) {328case MEDIA_BUS_FMT_FIXED:329di = &conn_state->connector->display_info;330331/*332* Look at the first bus format to determine input format.333* Default to MEDIA_BUS_FMT_RGB888_1X36_CPADLO, if no match.334*/335if (di->num_bus_formats) {336finfo = drm_format_info(di->bus_formats[0]);337338input_fmts[0] = finfo->depth == 18 ?339MEDIA_BUS_FMT_RGB666_1X36_CPADLO :340MEDIA_BUS_FMT_RGB888_1X36_CPADLO;341} else {342input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;343}344break;345case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:346input_fmts[0] = MEDIA_BUS_FMT_RGB666_1X36_CPADLO;347break;348case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:349case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:350input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X36_CPADLO;351break;352default:353kfree(input_fmts);354input_fmts = NULL;355break;356}357358return input_fmts;359}360361static u32 *362imx8qm_ldb_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,363struct drm_bridge_state *bridge_state,364struct drm_crtc_state *crtc_state,365struct drm_connector_state *conn_state,366unsigned int *num_output_fmts)367{368*num_output_fmts = ARRAY_SIZE(imx8qm_ldb_bus_output_fmts);369return kmemdup(imx8qm_ldb_bus_output_fmts,370sizeof(imx8qm_ldb_bus_output_fmts), GFP_KERNEL);371}372373static enum drm_mode_status374imx8qm_ldb_bridge_mode_valid(struct drm_bridge *bridge,375const struct drm_display_info *info,376const struct drm_display_mode *mode)377{378struct ldb_channel *ldb_ch = bridge->driver_private;379bool is_single = ldb_channel_is_single_link(ldb_ch);380381if (mode->clock > 300000)382return MODE_CLOCK_HIGH;383384if (mode->clock > 150000 && is_single)385return MODE_CLOCK_HIGH;386387return MODE_OK;388}389390static const struct drm_bridge_funcs imx8qm_ldb_bridge_funcs = {391.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,392.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,393.atomic_reset = drm_atomic_helper_bridge_reset,394.mode_valid = imx8qm_ldb_bridge_mode_valid,395.attach = ldb_bridge_attach_helper,396.atomic_check = imx8qm_ldb_bridge_atomic_check,397.mode_set = imx8qm_ldb_bridge_mode_set,398.atomic_enable = imx8qm_ldb_bridge_atomic_enable,399.atomic_disable = imx8qm_ldb_bridge_atomic_disable,400.atomic_get_input_bus_fmts =401imx8qm_ldb_bridge_atomic_get_input_bus_fmts,402.atomic_get_output_bus_fmts =403imx8qm_ldb_bridge_atomic_get_output_bus_fmts,404};405406static int imx8qm_ldb_get_phy(struct imx8qm_ldb *imx8qm_ldb)407{408struct imx8qm_ldb_channel *imx8qm_ldb_ch;409struct ldb_channel *ldb_ch;410struct device *dev = imx8qm_ldb->dev;411int i, ret;412413for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {414imx8qm_ldb_ch = imx8qm_ldb->channel[i];415ldb_ch = &imx8qm_ldb_ch->base;416417if (!ldb_ch->is_available)418continue;419420imx8qm_ldb_ch->phy = devm_of_phy_get(dev, ldb_ch->np,421"lvds_phy");422if (IS_ERR(imx8qm_ldb_ch->phy)) {423ret = PTR_ERR(imx8qm_ldb_ch->phy);424if (ret != -EPROBE_DEFER)425DRM_DEV_ERROR(dev,426"failed to get channel%d PHY: %d\n",427i, ret);428return ret;429}430}431432return 0;433}434435static int imx8qm_ldb_probe(struct platform_device *pdev)436{437struct device *dev = &pdev->dev;438struct imx8qm_ldb *imx8qm_ldb;439struct imx8qm_ldb_channel *imx8qm_ldb_ch;440struct ldb *ldb;441struct ldb_channel *ldb_ch;442struct device_node *port1, *port2;443int pixel_order;444int ret, i;445446imx8qm_ldb = devm_kzalloc(dev, sizeof(*imx8qm_ldb), GFP_KERNEL);447if (!imx8qm_ldb)448return -ENOMEM;449450for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {451imx8qm_ldb->channel[i] =452devm_drm_bridge_alloc(dev, struct imx8qm_ldb_channel, base.bridge,453&imx8qm_ldb_bridge_funcs);454if (IS_ERR(imx8qm_ldb->channel[i]))455return PTR_ERR(imx8qm_ldb->channel[i]);456}457458imx8qm_ldb->clk_pixel = devm_clk_get(dev, "pixel");459if (IS_ERR(imx8qm_ldb->clk_pixel)) {460ret = PTR_ERR(imx8qm_ldb->clk_pixel);461if (ret != -EPROBE_DEFER)462DRM_DEV_ERROR(dev,463"failed to get pixel clock: %d\n", ret);464return ret;465}466467imx8qm_ldb->clk_bypass = devm_clk_get(dev, "bypass");468if (IS_ERR(imx8qm_ldb->clk_bypass)) {469ret = PTR_ERR(imx8qm_ldb->clk_bypass);470if (ret != -EPROBE_DEFER)471DRM_DEV_ERROR(dev,472"failed to get bypass clock: %d\n", ret);473return ret;474}475476imx8qm_ldb->dev = dev;477478ldb = &imx8qm_ldb->base;479ldb->dev = dev;480ldb->ctrl_reg = 0xe0;481482for (i = 0; i < MAX_LDB_CHAN_NUM; i++)483ldb->channel[i] = &imx8qm_ldb->channel[i]->base;484485ret = ldb_init_helper(ldb);486if (ret)487return ret;488489if (ldb->available_ch_cnt == 0) {490DRM_DEV_DEBUG_DRIVER(dev, "no available channel\n");491return 0;492}493494if (ldb->available_ch_cnt == 2) {495port1 = of_graph_get_port_by_id(ldb->channel[0]->np, 1);496port2 = of_graph_get_port_by_id(ldb->channel[1]->np, 1);497pixel_order =498drm_of_lvds_get_dual_link_pixel_order(port1, port2);499of_node_put(port1);500of_node_put(port2);501502if (pixel_order != DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) {503DRM_DEV_ERROR(dev, "invalid dual link pixel order: %d\n",504pixel_order);505return -EINVAL;506}507508imx8qm_ldb->active_chno = 0;509imx8qm_ldb_ch = imx8qm_ldb->channel[0];510ldb_ch = &imx8qm_ldb_ch->base;511ldb_ch->link_type = pixel_order;512} else {513for (i = 0; i < MAX_LDB_CHAN_NUM; i++) {514imx8qm_ldb_ch = imx8qm_ldb->channel[i];515ldb_ch = &imx8qm_ldb_ch->base;516517if (ldb_ch->is_available) {518imx8qm_ldb->active_chno = ldb_ch->chno;519break;520}521}522}523524ret = imx8qm_ldb_get_phy(imx8qm_ldb);525if (ret)526return ret;527528ret = ldb_find_next_bridge_helper(ldb);529if (ret)530return ret;531532platform_set_drvdata(pdev, imx8qm_ldb);533pm_runtime_enable(dev);534535ldb_add_bridge_helper(ldb);536537return ret;538}539540static void imx8qm_ldb_remove(struct platform_device *pdev)541{542struct imx8qm_ldb *imx8qm_ldb = platform_get_drvdata(pdev);543struct ldb *ldb = &imx8qm_ldb->base;544545ldb_remove_bridge_helper(ldb);546547pm_runtime_disable(&pdev->dev);548}549550static int imx8qm_ldb_runtime_suspend(struct device *dev)551{552return 0;553}554555static int imx8qm_ldb_runtime_resume(struct device *dev)556{557struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev);558struct ldb *ldb = &imx8qm_ldb->base;559560/* disable LDB by resetting the control register to POR default */561regmap_write(ldb->regmap, ldb->ctrl_reg, 0);562563return 0;564}565566static const struct dev_pm_ops imx8qm_ldb_pm_ops = {567RUNTIME_PM_OPS(imx8qm_ldb_runtime_suspend, imx8qm_ldb_runtime_resume, NULL)568};569570static const struct of_device_id imx8qm_ldb_dt_ids[] = {571{ .compatible = "fsl,imx8qm-ldb" },572{ /* sentinel */ }573};574MODULE_DEVICE_TABLE(of, imx8qm_ldb_dt_ids);575576static struct platform_driver imx8qm_ldb_driver = {577.probe = imx8qm_ldb_probe,578.remove = imx8qm_ldb_remove,579.driver = {580.pm = pm_ptr(&imx8qm_ldb_pm_ops),581.name = DRIVER_NAME,582.of_match_table = imx8qm_ldb_dt_ids,583},584};585module_platform_driver(imx8qm_ldb_driver);586587MODULE_DESCRIPTION("i.MX8QM LVDS Display Bridge(LDB)/Pixel Mapper bridge driver");588MODULE_AUTHOR("Liu Ying <[email protected]>");589MODULE_LICENSE("GPL v2");590MODULE_ALIAS("platform:" DRIVER_NAME);591592593