162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * i.MX drm driver - LVDS display bridge 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2012 Sascha Hauer, Pengutronix 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/component.h> 1062306a36Sopenharmony_ci#include <linux/i2c.h> 1162306a36Sopenharmony_ci#include <linux/media-bus-format.h> 1262306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 1362306a36Sopenharmony_ci#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/of_device.h> 1662306a36Sopenharmony_ci#include <linux/of_graph.h> 1762306a36Sopenharmony_ci#include <linux/regmap.h> 1862306a36Sopenharmony_ci#include <linux/videodev2.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <video/of_display_timing.h> 2162306a36Sopenharmony_ci#include <video/of_videomode.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <drm/drm_atomic.h> 2462306a36Sopenharmony_ci#include <drm/drm_atomic_helper.h> 2562306a36Sopenharmony_ci#include <drm/drm_bridge.h> 2662306a36Sopenharmony_ci#include <drm/drm_edid.h> 2762306a36Sopenharmony_ci#include <drm/drm_managed.h> 2862306a36Sopenharmony_ci#include <drm/drm_of.h> 2962306a36Sopenharmony_ci#include <drm/drm_panel.h> 3062306a36Sopenharmony_ci#include <drm/drm_print.h> 3162306a36Sopenharmony_ci#include <drm/drm_probe_helper.h> 3262306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h> 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#include "imx-drm.h" 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define DRIVER_NAME "imx-ldb" 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) 3962306a36Sopenharmony_ci#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) 4062306a36Sopenharmony_ci#define LDB_CH0_MODE_EN_MASK (3 << 0) 4162306a36Sopenharmony_ci#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2) 4262306a36Sopenharmony_ci#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) 4362306a36Sopenharmony_ci#define LDB_CH1_MODE_EN_MASK (3 << 2) 4462306a36Sopenharmony_ci#define LDB_SPLIT_MODE_EN (1 << 4) 4562306a36Sopenharmony_ci#define LDB_DATA_WIDTH_CH0_24 (1 << 5) 4662306a36Sopenharmony_ci#define LDB_BIT_MAP_CH0_JEIDA (1 << 6) 4762306a36Sopenharmony_ci#define LDB_DATA_WIDTH_CH1_24 (1 << 7) 4862306a36Sopenharmony_ci#define LDB_BIT_MAP_CH1_JEIDA (1 << 8) 4962306a36Sopenharmony_ci#define LDB_DI0_VS_POL_ACT_LOW (1 << 9) 5062306a36Sopenharmony_ci#define LDB_DI1_VS_POL_ACT_LOW (1 << 10) 5162306a36Sopenharmony_ci#define LDB_BGREF_RMODE_INT (1 << 15) 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistruct imx_ldb_channel; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistruct imx_ldb_encoder { 5662306a36Sopenharmony_ci struct drm_connector connector; 5762306a36Sopenharmony_ci struct drm_encoder encoder; 5862306a36Sopenharmony_ci struct imx_ldb_channel *channel; 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistruct imx_ldb; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistruct imx_ldb_channel { 6462306a36Sopenharmony_ci struct imx_ldb *ldb; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci /* Defines what is connected to the ldb, only one at a time */ 6762306a36Sopenharmony_ci struct drm_panel *panel; 6862306a36Sopenharmony_ci struct drm_bridge *bridge; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci struct device_node *child; 7162306a36Sopenharmony_ci struct i2c_adapter *ddc; 7262306a36Sopenharmony_ci int chno; 7362306a36Sopenharmony_ci void *edid; 7462306a36Sopenharmony_ci struct drm_display_mode mode; 7562306a36Sopenharmony_ci int mode_valid; 7662306a36Sopenharmony_ci u32 bus_format; 7762306a36Sopenharmony_ci u32 bus_flags; 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic inline struct imx_ldb_channel *con_to_imx_ldb_ch(struct drm_connector *c) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci return container_of(c, struct imx_ldb_encoder, connector)->channel; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic inline struct imx_ldb_channel *enc_to_imx_ldb_ch(struct drm_encoder *e) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci return container_of(e, struct imx_ldb_encoder, encoder)->channel; 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistruct bus_mux { 9162306a36Sopenharmony_ci int reg; 9262306a36Sopenharmony_ci int shift; 9362306a36Sopenharmony_ci int mask; 9462306a36Sopenharmony_ci}; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistruct imx_ldb { 9762306a36Sopenharmony_ci struct regmap *regmap; 9862306a36Sopenharmony_ci struct device *dev; 9962306a36Sopenharmony_ci struct imx_ldb_channel channel[2]; 10062306a36Sopenharmony_ci struct clk *clk[2]; /* our own clock */ 10162306a36Sopenharmony_ci struct clk *clk_sel[4]; /* parent of display clock */ 10262306a36Sopenharmony_ci struct clk *clk_parent[4]; /* original parent of clk_sel */ 10362306a36Sopenharmony_ci struct clk *clk_pll[2]; /* upstream clock we can adjust */ 10462306a36Sopenharmony_ci u32 ldb_ctrl; 10562306a36Sopenharmony_ci const struct bus_mux *lvds_mux; 10662306a36Sopenharmony_ci}; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic void imx_ldb_ch_set_bus_format(struct imx_ldb_channel *imx_ldb_ch, 10962306a36Sopenharmony_ci u32 bus_format) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci struct imx_ldb *ldb = imx_ldb_ch->ldb; 11262306a36Sopenharmony_ci int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci switch (bus_format) { 11562306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: 11662306a36Sopenharmony_ci break; 11762306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: 11862306a36Sopenharmony_ci if (imx_ldb_ch->chno == 0 || dual) 11962306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24; 12062306a36Sopenharmony_ci if (imx_ldb_ch->chno == 1 || dual) 12162306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24; 12262306a36Sopenharmony_ci break; 12362306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: 12462306a36Sopenharmony_ci if (imx_ldb_ch->chno == 0 || dual) 12562306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 | 12662306a36Sopenharmony_ci LDB_BIT_MAP_CH0_JEIDA; 12762306a36Sopenharmony_ci if (imx_ldb_ch->chno == 1 || dual) 12862306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 | 12962306a36Sopenharmony_ci LDB_BIT_MAP_CH1_JEIDA; 13062306a36Sopenharmony_ci break; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int imx_ldb_connector_get_modes(struct drm_connector *connector) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct imx_ldb_channel *imx_ldb_ch = con_to_imx_ldb_ch(connector); 13762306a36Sopenharmony_ci int num_modes; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci num_modes = drm_panel_get_modes(imx_ldb_ch->panel, connector); 14062306a36Sopenharmony_ci if (num_modes > 0) 14162306a36Sopenharmony_ci return num_modes; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (!imx_ldb_ch->edid && imx_ldb_ch->ddc) 14462306a36Sopenharmony_ci imx_ldb_ch->edid = drm_get_edid(connector, imx_ldb_ch->ddc); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci if (imx_ldb_ch->edid) { 14762306a36Sopenharmony_ci drm_connector_update_edid_property(connector, 14862306a36Sopenharmony_ci imx_ldb_ch->edid); 14962306a36Sopenharmony_ci num_modes = drm_add_edid_modes(connector, imx_ldb_ch->edid); 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci if (imx_ldb_ch->mode_valid) { 15362306a36Sopenharmony_ci struct drm_display_mode *mode; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci mode = drm_mode_duplicate(connector->dev, &imx_ldb_ch->mode); 15662306a36Sopenharmony_ci if (!mode) 15762306a36Sopenharmony_ci return -EINVAL; 15862306a36Sopenharmony_ci mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; 15962306a36Sopenharmony_ci drm_mode_probed_add(connector, mode); 16062306a36Sopenharmony_ci num_modes++; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return num_modes; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno, 16762306a36Sopenharmony_ci unsigned long serial_clk, unsigned long di_clk) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci int ret; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci dev_dbg(ldb->dev, "%s: now: %ld want: %ld\n", __func__, 17262306a36Sopenharmony_ci clk_get_rate(ldb->clk_pll[chno]), serial_clk); 17362306a36Sopenharmony_ci clk_set_rate(ldb->clk_pll[chno], serial_clk); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci dev_dbg(ldb->dev, "%s after: %ld\n", __func__, 17662306a36Sopenharmony_ci clk_get_rate(ldb->clk_pll[chno])); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci dev_dbg(ldb->dev, "%s: now: %ld want: %ld\n", __func__, 17962306a36Sopenharmony_ci clk_get_rate(ldb->clk[chno]), 18062306a36Sopenharmony_ci (long int)di_clk); 18162306a36Sopenharmony_ci clk_set_rate(ldb->clk[chno], di_clk); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci dev_dbg(ldb->dev, "%s after: %ld\n", __func__, 18462306a36Sopenharmony_ci clk_get_rate(ldb->clk[chno])); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* set display clock mux to LDB input clock */ 18762306a36Sopenharmony_ci ret = clk_set_parent(ldb->clk_sel[mux], ldb->clk[chno]); 18862306a36Sopenharmony_ci if (ret) 18962306a36Sopenharmony_ci dev_err(ldb->dev, 19062306a36Sopenharmony_ci "unable to set di%d parent clock to ldb_di%d\n", mux, 19162306a36Sopenharmony_ci chno); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistatic void imx_ldb_encoder_enable(struct drm_encoder *encoder) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); 19762306a36Sopenharmony_ci struct imx_ldb *ldb = imx_ldb_ch->ldb; 19862306a36Sopenharmony_ci int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; 19962306a36Sopenharmony_ci int mux = drm_of_encoder_active_port_id(imx_ldb_ch->child, encoder); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci if (mux < 0 || mux >= ARRAY_SIZE(ldb->clk_sel)) { 20262306a36Sopenharmony_ci dev_warn(ldb->dev, "%s: invalid mux %d\n", __func__, mux); 20362306a36Sopenharmony_ci return; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci drm_panel_prepare(imx_ldb_ch->panel); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci if (dual) { 20962306a36Sopenharmony_ci clk_set_parent(ldb->clk_sel[mux], ldb->clk[0]); 21062306a36Sopenharmony_ci clk_set_parent(ldb->clk_sel[mux], ldb->clk[1]); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci clk_prepare_enable(ldb->clk[0]); 21362306a36Sopenharmony_ci clk_prepare_enable(ldb->clk[1]); 21462306a36Sopenharmony_ci } else { 21562306a36Sopenharmony_ci clk_set_parent(ldb->clk_sel[mux], ldb->clk[imx_ldb_ch->chno]); 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[0] || dual) { 21962306a36Sopenharmony_ci ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; 22062306a36Sopenharmony_ci if (mux == 0 || ldb->lvds_mux) 22162306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; 22262306a36Sopenharmony_ci else if (mux == 1) 22362306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI1; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[1] || dual) { 22662306a36Sopenharmony_ci ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; 22762306a36Sopenharmony_ci if (mux == 1 || ldb->lvds_mux) 22862306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI1; 22962306a36Sopenharmony_ci else if (mux == 0) 23062306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI0; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (ldb->lvds_mux) { 23462306a36Sopenharmony_ci const struct bus_mux *lvds_mux = NULL; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[0]) 23762306a36Sopenharmony_ci lvds_mux = &ldb->lvds_mux[0]; 23862306a36Sopenharmony_ci else if (imx_ldb_ch == &ldb->channel[1]) 23962306a36Sopenharmony_ci lvds_mux = &ldb->lvds_mux[1]; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci regmap_update_bits(ldb->regmap, lvds_mux->reg, lvds_mux->mask, 24262306a36Sopenharmony_ci mux << lvds_mux->shift); 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci drm_panel_enable(imx_ldb_ch->panel); 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void 25162306a36Sopenharmony_ciimx_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder, 25262306a36Sopenharmony_ci struct drm_crtc_state *crtc_state, 25362306a36Sopenharmony_ci struct drm_connector_state *connector_state) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); 25662306a36Sopenharmony_ci struct drm_display_mode *mode = &crtc_state->adjusted_mode; 25762306a36Sopenharmony_ci struct imx_ldb *ldb = imx_ldb_ch->ldb; 25862306a36Sopenharmony_ci int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; 25962306a36Sopenharmony_ci unsigned long serial_clk; 26062306a36Sopenharmony_ci unsigned long di_clk = mode->clock * 1000; 26162306a36Sopenharmony_ci int mux = drm_of_encoder_active_port_id(imx_ldb_ch->child, encoder); 26262306a36Sopenharmony_ci u32 bus_format = imx_ldb_ch->bus_format; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci if (mux < 0 || mux >= ARRAY_SIZE(ldb->clk_sel)) { 26562306a36Sopenharmony_ci dev_warn(ldb->dev, "%s: invalid mux %d\n", __func__, mux); 26662306a36Sopenharmony_ci return; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci if (mode->clock > 170000) { 27062306a36Sopenharmony_ci dev_warn(ldb->dev, 27162306a36Sopenharmony_ci "%s: mode exceeds 170 MHz pixel clock\n", __func__); 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci if (mode->clock > 85000 && !dual) { 27462306a36Sopenharmony_ci dev_warn(ldb->dev, 27562306a36Sopenharmony_ci "%s: mode exceeds 85 MHz pixel clock\n", __func__); 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (!IS_ALIGNED(mode->hdisplay, 8)) { 27962306a36Sopenharmony_ci dev_warn(ldb->dev, 28062306a36Sopenharmony_ci "%s: hdisplay does not align to 8 byte\n", __func__); 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci if (dual) { 28462306a36Sopenharmony_ci serial_clk = 3500UL * mode->clock; 28562306a36Sopenharmony_ci imx_ldb_set_clock(ldb, mux, 0, serial_clk, di_clk); 28662306a36Sopenharmony_ci imx_ldb_set_clock(ldb, mux, 1, serial_clk, di_clk); 28762306a36Sopenharmony_ci } else { 28862306a36Sopenharmony_ci serial_clk = 7000UL * mode->clock; 28962306a36Sopenharmony_ci imx_ldb_set_clock(ldb, mux, imx_ldb_ch->chno, serial_clk, 29062306a36Sopenharmony_ci di_clk); 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ 29462306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[0] || dual) { 29562306a36Sopenharmony_ci if (mode->flags & DRM_MODE_FLAG_NVSYNC) 29662306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; 29762306a36Sopenharmony_ci else if (mode->flags & DRM_MODE_FLAG_PVSYNC) 29862306a36Sopenharmony_ci ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW; 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[1] || dual) { 30162306a36Sopenharmony_ci if (mode->flags & DRM_MODE_FLAG_NVSYNC) 30262306a36Sopenharmony_ci ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; 30362306a36Sopenharmony_ci else if (mode->flags & DRM_MODE_FLAG_PVSYNC) 30462306a36Sopenharmony_ci ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci if (!bus_format) { 30862306a36Sopenharmony_ci struct drm_connector *connector = connector_state->connector; 30962306a36Sopenharmony_ci struct drm_display_info *di = &connector->display_info; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci if (di->num_bus_formats) 31262306a36Sopenharmony_ci bus_format = di->bus_formats[0]; 31362306a36Sopenharmony_ci } 31462306a36Sopenharmony_ci imx_ldb_ch_set_bus_format(imx_ldb_ch, bus_format); 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_cistatic void imx_ldb_encoder_disable(struct drm_encoder *encoder) 31862306a36Sopenharmony_ci{ 31962306a36Sopenharmony_ci struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); 32062306a36Sopenharmony_ci struct imx_ldb *ldb = imx_ldb_ch->ldb; 32162306a36Sopenharmony_ci int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; 32262306a36Sopenharmony_ci int mux, ret; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci drm_panel_disable(imx_ldb_ch->panel); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[0] || dual) 32762306a36Sopenharmony_ci ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; 32862306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[1] || dual) 32962306a36Sopenharmony_ci ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (dual) { 33462306a36Sopenharmony_ci clk_disable_unprepare(ldb->clk[0]); 33562306a36Sopenharmony_ci clk_disable_unprepare(ldb->clk[1]); 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci if (ldb->lvds_mux) { 33962306a36Sopenharmony_ci const struct bus_mux *lvds_mux = NULL; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci if (imx_ldb_ch == &ldb->channel[0]) 34262306a36Sopenharmony_ci lvds_mux = &ldb->lvds_mux[0]; 34362306a36Sopenharmony_ci else if (imx_ldb_ch == &ldb->channel[1]) 34462306a36Sopenharmony_ci lvds_mux = &ldb->lvds_mux[1]; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci regmap_read(ldb->regmap, lvds_mux->reg, &mux); 34762306a36Sopenharmony_ci mux &= lvds_mux->mask; 34862306a36Sopenharmony_ci mux >>= lvds_mux->shift; 34962306a36Sopenharmony_ci } else { 35062306a36Sopenharmony_ci mux = (imx_ldb_ch == &ldb->channel[0]) ? 0 : 1; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci /* set display clock mux back to original input clock */ 35462306a36Sopenharmony_ci ret = clk_set_parent(ldb->clk_sel[mux], ldb->clk_parent[mux]); 35562306a36Sopenharmony_ci if (ret) 35662306a36Sopenharmony_ci dev_err(ldb->dev, 35762306a36Sopenharmony_ci "unable to set di%d parent clock to original parent\n", 35862306a36Sopenharmony_ci mux); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci drm_panel_unprepare(imx_ldb_ch->panel); 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_cistatic int imx_ldb_encoder_atomic_check(struct drm_encoder *encoder, 36462306a36Sopenharmony_ci struct drm_crtc_state *crtc_state, 36562306a36Sopenharmony_ci struct drm_connector_state *conn_state) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); 36862306a36Sopenharmony_ci struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); 36962306a36Sopenharmony_ci struct drm_display_info *di = &conn_state->connector->display_info; 37062306a36Sopenharmony_ci u32 bus_format = imx_ldb_ch->bus_format; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci /* Bus format description in DT overrides connector display info. */ 37362306a36Sopenharmony_ci if (!bus_format && di->num_bus_formats) { 37462306a36Sopenharmony_ci bus_format = di->bus_formats[0]; 37562306a36Sopenharmony_ci imx_crtc_state->bus_flags = di->bus_flags; 37662306a36Sopenharmony_ci } else { 37762306a36Sopenharmony_ci bus_format = imx_ldb_ch->bus_format; 37862306a36Sopenharmony_ci imx_crtc_state->bus_flags = imx_ldb_ch->bus_flags; 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci switch (bus_format) { 38162306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: 38262306a36Sopenharmony_ci imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18; 38362306a36Sopenharmony_ci break; 38462306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: 38562306a36Sopenharmony_ci case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: 38662306a36Sopenharmony_ci imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24; 38762306a36Sopenharmony_ci break; 38862306a36Sopenharmony_ci default: 38962306a36Sopenharmony_ci return -EINVAL; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci imx_crtc_state->di_hsync_pin = 2; 39362306a36Sopenharmony_ci imx_crtc_state->di_vsync_pin = 3; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci return 0; 39662306a36Sopenharmony_ci} 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_cistatic const struct drm_connector_funcs imx_ldb_connector_funcs = { 40062306a36Sopenharmony_ci .fill_modes = drm_helper_probe_single_connector_modes, 40162306a36Sopenharmony_ci .destroy = imx_drm_connector_destroy, 40262306a36Sopenharmony_ci .reset = drm_atomic_helper_connector_reset, 40362306a36Sopenharmony_ci .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, 40462306a36Sopenharmony_ci .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, 40562306a36Sopenharmony_ci}; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_cistatic const struct drm_connector_helper_funcs imx_ldb_connector_helper_funcs = { 40862306a36Sopenharmony_ci .get_modes = imx_ldb_connector_get_modes, 40962306a36Sopenharmony_ci}; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic const struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = { 41262306a36Sopenharmony_ci .atomic_mode_set = imx_ldb_encoder_atomic_mode_set, 41362306a36Sopenharmony_ci .enable = imx_ldb_encoder_enable, 41462306a36Sopenharmony_ci .disable = imx_ldb_encoder_disable, 41562306a36Sopenharmony_ci .atomic_check = imx_ldb_encoder_atomic_check, 41662306a36Sopenharmony_ci}; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic int imx_ldb_get_clk(struct imx_ldb *ldb, int chno) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci char clkname[16]; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci snprintf(clkname, sizeof(clkname), "di%d", chno); 42362306a36Sopenharmony_ci ldb->clk[chno] = devm_clk_get(ldb->dev, clkname); 42462306a36Sopenharmony_ci if (IS_ERR(ldb->clk[chno])) 42562306a36Sopenharmony_ci return PTR_ERR(ldb->clk[chno]); 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci snprintf(clkname, sizeof(clkname), "di%d_pll", chno); 42862306a36Sopenharmony_ci ldb->clk_pll[chno] = devm_clk_get(ldb->dev, clkname); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci return PTR_ERR_OR_ZERO(ldb->clk_pll[chno]); 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_cistatic int imx_ldb_register(struct drm_device *drm, 43462306a36Sopenharmony_ci struct imx_ldb_channel *imx_ldb_ch) 43562306a36Sopenharmony_ci{ 43662306a36Sopenharmony_ci struct imx_ldb *ldb = imx_ldb_ch->ldb; 43762306a36Sopenharmony_ci struct imx_ldb_encoder *ldb_encoder; 43862306a36Sopenharmony_ci struct drm_connector *connector; 43962306a36Sopenharmony_ci struct drm_encoder *encoder; 44062306a36Sopenharmony_ci int ret; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci ldb_encoder = drmm_simple_encoder_alloc(drm, struct imx_ldb_encoder, 44362306a36Sopenharmony_ci encoder, DRM_MODE_ENCODER_LVDS); 44462306a36Sopenharmony_ci if (IS_ERR(ldb_encoder)) 44562306a36Sopenharmony_ci return PTR_ERR(ldb_encoder); 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci ldb_encoder->channel = imx_ldb_ch; 44862306a36Sopenharmony_ci connector = &ldb_encoder->connector; 44962306a36Sopenharmony_ci encoder = &ldb_encoder->encoder; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci ret = imx_drm_encoder_parse_of(drm, encoder, imx_ldb_ch->child); 45262306a36Sopenharmony_ci if (ret) 45362306a36Sopenharmony_ci return ret; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci ret = imx_ldb_get_clk(ldb, imx_ldb_ch->chno); 45662306a36Sopenharmony_ci if (ret) 45762306a36Sopenharmony_ci return ret; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) { 46062306a36Sopenharmony_ci ret = imx_ldb_get_clk(ldb, 1); 46162306a36Sopenharmony_ci if (ret) 46262306a36Sopenharmony_ci return ret; 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci drm_encoder_helper_add(encoder, &imx_ldb_encoder_helper_funcs); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci if (imx_ldb_ch->bridge) { 46862306a36Sopenharmony_ci ret = drm_bridge_attach(encoder, imx_ldb_ch->bridge, NULL, 0); 46962306a36Sopenharmony_ci if (ret) 47062306a36Sopenharmony_ci return ret; 47162306a36Sopenharmony_ci } else { 47262306a36Sopenharmony_ci /* 47362306a36Sopenharmony_ci * We want to add the connector whenever there is no bridge 47462306a36Sopenharmony_ci * that brings its own, not only when there is a panel. For 47562306a36Sopenharmony_ci * historical reasons, the ldb driver can also work without 47662306a36Sopenharmony_ci * a panel. 47762306a36Sopenharmony_ci */ 47862306a36Sopenharmony_ci drm_connector_helper_add(connector, 47962306a36Sopenharmony_ci &imx_ldb_connector_helper_funcs); 48062306a36Sopenharmony_ci drm_connector_init_with_ddc(drm, connector, 48162306a36Sopenharmony_ci &imx_ldb_connector_funcs, 48262306a36Sopenharmony_ci DRM_MODE_CONNECTOR_LVDS, 48362306a36Sopenharmony_ci imx_ldb_ch->ddc); 48462306a36Sopenharmony_ci drm_connector_attach_encoder(connector, encoder); 48562306a36Sopenharmony_ci } 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci return 0; 48862306a36Sopenharmony_ci} 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_cistruct imx_ldb_bit_mapping { 49162306a36Sopenharmony_ci u32 bus_format; 49262306a36Sopenharmony_ci u32 datawidth; 49362306a36Sopenharmony_ci const char * const mapping; 49462306a36Sopenharmony_ci}; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_cistatic const struct imx_ldb_bit_mapping imx_ldb_bit_mappings[] = { 49762306a36Sopenharmony_ci { MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" }, 49862306a36Sopenharmony_ci { MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" }, 49962306a36Sopenharmony_ci { MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" }, 50062306a36Sopenharmony_ci}; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cistatic u32 of_get_bus_format(struct device *dev, struct device_node *np) 50362306a36Sopenharmony_ci{ 50462306a36Sopenharmony_ci const char *bm; 50562306a36Sopenharmony_ci u32 datawidth = 0; 50662306a36Sopenharmony_ci int ret, i; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci ret = of_property_read_string(np, "fsl,data-mapping", &bm); 50962306a36Sopenharmony_ci if (ret < 0) 51062306a36Sopenharmony_ci return ret; 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci of_property_read_u32(np, "fsl,data-width", &datawidth); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(imx_ldb_bit_mappings); i++) { 51562306a36Sopenharmony_ci if (!strcasecmp(bm, imx_ldb_bit_mappings[i].mapping) && 51662306a36Sopenharmony_ci datawidth == imx_ldb_bit_mappings[i].datawidth) 51762306a36Sopenharmony_ci return imx_ldb_bit_mappings[i].bus_format; 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci dev_err(dev, "invalid data mapping: %d-bit \"%s\"\n", datawidth, bm); 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci return -ENOENT; 52362306a36Sopenharmony_ci} 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_cistatic struct bus_mux imx6q_lvds_mux[2] = { 52662306a36Sopenharmony_ci { 52762306a36Sopenharmony_ci .reg = IOMUXC_GPR3, 52862306a36Sopenharmony_ci .shift = 6, 52962306a36Sopenharmony_ci .mask = IMX6Q_GPR3_LVDS0_MUX_CTL_MASK, 53062306a36Sopenharmony_ci }, { 53162306a36Sopenharmony_ci .reg = IOMUXC_GPR3, 53262306a36Sopenharmony_ci .shift = 8, 53362306a36Sopenharmony_ci .mask = IMX6Q_GPR3_LVDS1_MUX_CTL_MASK, 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci}; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci/* 53862306a36Sopenharmony_ci * For a device declaring compatible = "fsl,imx6q-ldb", "fsl,imx53-ldb", 53962306a36Sopenharmony_ci * of_match_device will walk through this list and take the first entry 54062306a36Sopenharmony_ci * matching any of its compatible values. Therefore, the more generic 54162306a36Sopenharmony_ci * entries (in this case fsl,imx53-ldb) need to be ordered last. 54262306a36Sopenharmony_ci */ 54362306a36Sopenharmony_cistatic const struct of_device_id imx_ldb_dt_ids[] = { 54462306a36Sopenharmony_ci { .compatible = "fsl,imx6q-ldb", .data = imx6q_lvds_mux, }, 54562306a36Sopenharmony_ci { .compatible = "fsl,imx53-ldb", .data = NULL, }, 54662306a36Sopenharmony_ci { } 54762306a36Sopenharmony_ci}; 54862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_cistatic int imx_ldb_panel_ddc(struct device *dev, 55162306a36Sopenharmony_ci struct imx_ldb_channel *channel, struct device_node *child) 55262306a36Sopenharmony_ci{ 55362306a36Sopenharmony_ci struct device_node *ddc_node; 55462306a36Sopenharmony_ci const u8 *edidp; 55562306a36Sopenharmony_ci int ret; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci ddc_node = of_parse_phandle(child, "ddc-i2c-bus", 0); 55862306a36Sopenharmony_ci if (ddc_node) { 55962306a36Sopenharmony_ci channel->ddc = of_find_i2c_adapter_by_node(ddc_node); 56062306a36Sopenharmony_ci of_node_put(ddc_node); 56162306a36Sopenharmony_ci if (!channel->ddc) { 56262306a36Sopenharmony_ci dev_warn(dev, "failed to get ddc i2c adapter\n"); 56362306a36Sopenharmony_ci return -EPROBE_DEFER; 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci if (!channel->ddc) { 56862306a36Sopenharmony_ci int edid_len; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci /* if no DDC available, fallback to hardcoded EDID */ 57162306a36Sopenharmony_ci dev_dbg(dev, "no ddc available\n"); 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci edidp = of_get_property(child, "edid", &edid_len); 57462306a36Sopenharmony_ci if (edidp) { 57562306a36Sopenharmony_ci channel->edid = kmemdup(edidp, edid_len, GFP_KERNEL); 57662306a36Sopenharmony_ci if (!channel->edid) 57762306a36Sopenharmony_ci return -ENOMEM; 57862306a36Sopenharmony_ci } else if (!channel->panel) { 57962306a36Sopenharmony_ci /* fallback to display-timings node */ 58062306a36Sopenharmony_ci ret = of_get_drm_display_mode(child, 58162306a36Sopenharmony_ci &channel->mode, 58262306a36Sopenharmony_ci &channel->bus_flags, 58362306a36Sopenharmony_ci OF_USE_NATIVE_MODE); 58462306a36Sopenharmony_ci if (!ret) 58562306a36Sopenharmony_ci channel->mode_valid = 1; 58662306a36Sopenharmony_ci } 58762306a36Sopenharmony_ci } 58862306a36Sopenharmony_ci return 0; 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic int imx_ldb_bind(struct device *dev, struct device *master, void *data) 59262306a36Sopenharmony_ci{ 59362306a36Sopenharmony_ci struct drm_device *drm = data; 59462306a36Sopenharmony_ci struct imx_ldb *imx_ldb = dev_get_drvdata(dev); 59562306a36Sopenharmony_ci int ret; 59662306a36Sopenharmony_ci int i; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci for (i = 0; i < 2; i++) { 59962306a36Sopenharmony_ci struct imx_ldb_channel *channel = &imx_ldb->channel[i]; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci if (!channel->ldb) 60262306a36Sopenharmony_ci continue; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci ret = imx_ldb_register(drm, channel); 60562306a36Sopenharmony_ci if (ret) 60662306a36Sopenharmony_ci return ret; 60762306a36Sopenharmony_ci } 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci return 0; 61062306a36Sopenharmony_ci} 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_cistatic const struct component_ops imx_ldb_ops = { 61362306a36Sopenharmony_ci .bind = imx_ldb_bind, 61462306a36Sopenharmony_ci}; 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_cistatic int imx_ldb_probe(struct platform_device *pdev) 61762306a36Sopenharmony_ci{ 61862306a36Sopenharmony_ci struct device *dev = &pdev->dev; 61962306a36Sopenharmony_ci struct device_node *np = dev->of_node; 62062306a36Sopenharmony_ci const struct of_device_id *of_id = of_match_device(imx_ldb_dt_ids, dev); 62162306a36Sopenharmony_ci struct device_node *child; 62262306a36Sopenharmony_ci struct imx_ldb *imx_ldb; 62362306a36Sopenharmony_ci int dual; 62462306a36Sopenharmony_ci int ret; 62562306a36Sopenharmony_ci int i; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci imx_ldb = devm_kzalloc(dev, sizeof(*imx_ldb), GFP_KERNEL); 62862306a36Sopenharmony_ci if (!imx_ldb) 62962306a36Sopenharmony_ci return -ENOMEM; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci imx_ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); 63262306a36Sopenharmony_ci if (IS_ERR(imx_ldb->regmap)) { 63362306a36Sopenharmony_ci dev_err(dev, "failed to get parent regmap\n"); 63462306a36Sopenharmony_ci return PTR_ERR(imx_ldb->regmap); 63562306a36Sopenharmony_ci } 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci /* disable LDB by resetting the control register to POR default */ 63862306a36Sopenharmony_ci regmap_write(imx_ldb->regmap, IOMUXC_GPR2, 0); 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci imx_ldb->dev = dev; 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci if (of_id) 64362306a36Sopenharmony_ci imx_ldb->lvds_mux = of_id->data; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci dual = of_property_read_bool(np, "fsl,dual-channel"); 64662306a36Sopenharmony_ci if (dual) 64762306a36Sopenharmony_ci imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci /* 65062306a36Sopenharmony_ci * There are three different possible clock mux configurations: 65162306a36Sopenharmony_ci * i.MX53: ipu1_di0_sel, ipu1_di1_sel 65262306a36Sopenharmony_ci * i.MX6q: ipu1_di0_sel, ipu1_di1_sel, ipu2_di0_sel, ipu2_di1_sel 65362306a36Sopenharmony_ci * i.MX6dl: ipu1_di0_sel, ipu1_di1_sel, lcdif_sel 65462306a36Sopenharmony_ci * Map them all to di0_sel...di3_sel. 65562306a36Sopenharmony_ci */ 65662306a36Sopenharmony_ci for (i = 0; i < 4; i++) { 65762306a36Sopenharmony_ci char clkname[16]; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci sprintf(clkname, "di%d_sel", i); 66062306a36Sopenharmony_ci imx_ldb->clk_sel[i] = devm_clk_get(imx_ldb->dev, clkname); 66162306a36Sopenharmony_ci if (IS_ERR(imx_ldb->clk_sel[i])) { 66262306a36Sopenharmony_ci ret = PTR_ERR(imx_ldb->clk_sel[i]); 66362306a36Sopenharmony_ci imx_ldb->clk_sel[i] = NULL; 66462306a36Sopenharmony_ci break; 66562306a36Sopenharmony_ci } 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci imx_ldb->clk_parent[i] = clk_get_parent(imx_ldb->clk_sel[i]); 66862306a36Sopenharmony_ci } 66962306a36Sopenharmony_ci if (i == 0) 67062306a36Sopenharmony_ci return ret; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci for_each_child_of_node(np, child) { 67362306a36Sopenharmony_ci struct imx_ldb_channel *channel; 67462306a36Sopenharmony_ci int bus_format; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci ret = of_property_read_u32(child, "reg", &i); 67762306a36Sopenharmony_ci if (ret || i < 0 || i > 1) { 67862306a36Sopenharmony_ci ret = -EINVAL; 67962306a36Sopenharmony_ci goto free_child; 68062306a36Sopenharmony_ci } 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci if (!of_device_is_available(child)) 68362306a36Sopenharmony_ci continue; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci if (dual && i > 0) { 68662306a36Sopenharmony_ci dev_warn(dev, "dual-channel mode, ignoring second output\n"); 68762306a36Sopenharmony_ci continue; 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci channel = &imx_ldb->channel[i]; 69162306a36Sopenharmony_ci channel->ldb = imx_ldb; 69262306a36Sopenharmony_ci channel->chno = i; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci /* 69562306a36Sopenharmony_ci * The output port is port@4 with an external 4-port mux or 69662306a36Sopenharmony_ci * port@2 with the internal 2-port mux. 69762306a36Sopenharmony_ci */ 69862306a36Sopenharmony_ci ret = drm_of_find_panel_or_bridge(child, 69962306a36Sopenharmony_ci imx_ldb->lvds_mux ? 4 : 2, 0, 70062306a36Sopenharmony_ci &channel->panel, &channel->bridge); 70162306a36Sopenharmony_ci if (ret && ret != -ENODEV) 70262306a36Sopenharmony_ci goto free_child; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci /* panel ddc only if there is no bridge */ 70562306a36Sopenharmony_ci if (!channel->bridge) { 70662306a36Sopenharmony_ci ret = imx_ldb_panel_ddc(dev, channel, child); 70762306a36Sopenharmony_ci if (ret) 70862306a36Sopenharmony_ci goto free_child; 70962306a36Sopenharmony_ci } 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci bus_format = of_get_bus_format(dev, child); 71262306a36Sopenharmony_ci if (bus_format == -EINVAL) { 71362306a36Sopenharmony_ci /* 71462306a36Sopenharmony_ci * If no bus format was specified in the device tree, 71562306a36Sopenharmony_ci * we can still get it from the connected panel later. 71662306a36Sopenharmony_ci */ 71762306a36Sopenharmony_ci if (channel->panel && channel->panel->funcs && 71862306a36Sopenharmony_ci channel->panel->funcs->get_modes) 71962306a36Sopenharmony_ci bus_format = 0; 72062306a36Sopenharmony_ci } 72162306a36Sopenharmony_ci if (bus_format < 0) { 72262306a36Sopenharmony_ci dev_err(dev, "could not determine data mapping: %d\n", 72362306a36Sopenharmony_ci bus_format); 72462306a36Sopenharmony_ci ret = bus_format; 72562306a36Sopenharmony_ci goto free_child; 72662306a36Sopenharmony_ci } 72762306a36Sopenharmony_ci channel->bus_format = bus_format; 72862306a36Sopenharmony_ci channel->child = child; 72962306a36Sopenharmony_ci } 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci platform_set_drvdata(pdev, imx_ldb); 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci return component_add(&pdev->dev, &imx_ldb_ops); 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_cifree_child: 73662306a36Sopenharmony_ci of_node_put(child); 73762306a36Sopenharmony_ci return ret; 73862306a36Sopenharmony_ci} 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_cistatic int imx_ldb_remove(struct platform_device *pdev) 74162306a36Sopenharmony_ci{ 74262306a36Sopenharmony_ci struct imx_ldb *imx_ldb = platform_get_drvdata(pdev); 74362306a36Sopenharmony_ci int i; 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci for (i = 0; i < 2; i++) { 74662306a36Sopenharmony_ci struct imx_ldb_channel *channel = &imx_ldb->channel[i]; 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ci kfree(channel->edid); 74962306a36Sopenharmony_ci i2c_put_adapter(channel->ddc); 75062306a36Sopenharmony_ci } 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci component_del(&pdev->dev, &imx_ldb_ops); 75362306a36Sopenharmony_ci return 0; 75462306a36Sopenharmony_ci} 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_cistatic struct platform_driver imx_ldb_driver = { 75762306a36Sopenharmony_ci .probe = imx_ldb_probe, 75862306a36Sopenharmony_ci .remove = imx_ldb_remove, 75962306a36Sopenharmony_ci .driver = { 76062306a36Sopenharmony_ci .of_match_table = imx_ldb_dt_ids, 76162306a36Sopenharmony_ci .name = DRIVER_NAME, 76262306a36Sopenharmony_ci }, 76362306a36Sopenharmony_ci}; 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_cimodule_platform_driver(imx_ldb_driver); 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ciMODULE_DESCRIPTION("i.MX LVDS driver"); 76862306a36Sopenharmony_ciMODULE_AUTHOR("Sascha Hauer, Pengutronix"); 76962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 77062306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRIVER_NAME); 771