diff options
Diffstat (limited to 'drivers/staging/imx-drm')
-rw-r--r-- | drivers/staging/imx-drm/Kconfig | 13 | ||||
-rw-r--r-- | drivers/staging/imx-drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/imx-drm/TODO | 1 | ||||
-rw-r--r-- | drivers/staging/imx-drm/imx-drm-core.c | 5 | ||||
-rw-r--r-- | drivers/staging/imx-drm/imx-ldb.c | 625 | ||||
-rw-r--r-- | drivers/staging/imx-drm/imx-tve.c | 22 | ||||
-rw-r--r-- | drivers/staging/imx-drm/ipu-v3/ipu-common.c | 125 | ||||
-rw-r--r-- | drivers/staging/imx-drm/ipu-v3/ipu-di.c | 13 | ||||
-rw-r--r-- | drivers/staging/imx-drm/ipu-v3/ipu-dmfc.c | 22 | ||||
-rw-r--r-- | drivers/staging/imx-drm/ipu-v3/ipu-prv.h | 4 | ||||
-rw-r--r-- | drivers/staging/imx-drm/ipuv3-crtc.c | 28 | ||||
-rw-r--r-- | drivers/staging/imx-drm/parallel-display.c | 11 |
12 files changed, 736 insertions, 134 deletions
diff --git a/drivers/staging/imx-drm/Kconfig b/drivers/staging/imx-drm/Kconfig index 8c9e40390f4..22339059837 100644 --- a/drivers/staging/imx-drm/Kconfig +++ b/drivers/staging/imx-drm/Kconfig @@ -1,6 +1,7 @@ config DRM_IMX tristate "DRM Support for Freescale i.MX" select DRM_KMS_HELPER + select VIDEOMODE_HELPERS select DRM_GEM_CMA_HELPER select DRM_KMS_CMA_HELPER depends on DRM && (ARCH_MXC || ARCH_MULTIPLATFORM) @@ -19,17 +20,28 @@ config DRM_IMX_FB_HELPER config DRM_IMX_PARALLEL_DISPLAY tristate "Support for parallel displays" depends on DRM_IMX + select VIDEOMODE_HELPERS config DRM_IMX_TVE tristate "Support for TV and VGA displays" depends on DRM_IMX + select REGMAP_MMIO help Choose this to enable the internal Television Encoder (TVe) found on i.MX53 processors. +config DRM_IMX_LDB + tristate "Support for LVDS displays" + depends on DRM_IMX + select OF_VIDEOMODE + help + Choose this to enable the internal LVDS Display Bridge (LDB) + found on i.MX53 and i.MX6 processors. + config DRM_IMX_IPUV3_CORE tristate "IPUv3 core support" depends on DRM_IMX + depends on RESET_CONTROLLER help Choose this if you have a i.MX5/6 system and want to use the IPU. This option only enables IPU base @@ -38,5 +50,6 @@ config DRM_IMX_IPUV3_CORE config DRM_IMX_IPUV3 tristate "DRM Support for i.MX IPUv3" depends on DRM_IMX + depends on DRM_IMX_IPUV3_CORE help Choose this if you have a i.MX5 or i.MX6 processor. diff --git a/drivers/staging/imx-drm/Makefile b/drivers/staging/imx-drm/Makefile index 7e50184523c..bfaf69378ac 100644 --- a/drivers/staging/imx-drm/Makefile +++ b/drivers/staging/imx-drm/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_DRM_IMX) += imxdrm.o obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += parallel-display.o obj-$(CONFIG_DRM_IMX_TVE) += imx-tve.o +obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o obj-$(CONFIG_DRM_IMX_FB_HELPER) += imx-fbdev.o obj-$(CONFIG_DRM_IMX_IPUV3_CORE) += ipu-v3/ obj-$(CONFIG_DRM_IMX_IPUV3) += ipuv3-crtc.o diff --git a/drivers/staging/imx-drm/TODO b/drivers/staging/imx-drm/TODO index 123acbe9b37..f80641528f7 100644 --- a/drivers/staging/imx-drm/TODO +++ b/drivers/staging/imx-drm/TODO @@ -6,7 +6,6 @@ TODO: - Factor out more code to common helper functions - decide where to put the base driver. It is not specific to a subsystem and would be used by DRM/KMS and media/V4L2 -- convert irq driver to irq_domain_add_linear Missing features (not necessarily for moving out of staging): diff --git a/drivers/staging/imx-drm/imx-drm-core.c b/drivers/staging/imx-drm/imx-drm-core.c index 64553058b67..9854a1daf60 100644 --- a/drivers/staging/imx-drm/imx-drm-core.c +++ b/drivers/staging/imx-drm/imx-drm-core.c @@ -144,7 +144,7 @@ int imx_drm_crtc_panel_format(struct drm_crtc *crtc, u32 encoder_type, u32 interface_pix_fmt) { return imx_drm_crtc_panel_format_pins(crtc, encoder_type, - interface_pix_fmt, 0, 0); + interface_pix_fmt, 2, 3); } EXPORT_SYMBOL_GPL(imx_drm_crtc_panel_format); @@ -491,7 +491,6 @@ int imx_drm_add_crtc(struct drm_crtc *crtc, { struct imx_drm_device *imxdrm = __imx_drm_device(); struct imx_drm_crtc *imx_drm_crtc; - const struct drm_crtc_funcs *crtc_funcs; int ret; mutex_lock(&imxdrm->mutex); @@ -512,8 +511,6 @@ int imx_drm_add_crtc(struct drm_crtc *crtc, imx_drm_crtc->cookie.cookie = cookie; imx_drm_crtc->cookie.id = id; - crtc_funcs = imx_drm_helper_funcs->crtc_funcs; - imx_drm_crtc->crtc = crtc; imx_drm_crtc->imxdrm = imxdrm; diff --git a/drivers/staging/imx-drm/imx-ldb.c b/drivers/staging/imx-drm/imx-ldb.c new file mode 100644 index 00000000000..8af7f3b40ba --- /dev/null +++ b/drivers/staging/imx-drm/imx-ldb.c @@ -0,0 +1,625 @@ +/* + * i.MX drm driver - LVDS display bridge + * + * Copyright (C) 2012 Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <drm/drmP.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <video/of_videomode.h> +#include <linux/regmap.h> +#include <linux/videodev2.h> + +#include "imx-drm.h" + +#define DRIVER_NAME "imx-ldb" + +#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0) +#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0) +#define LDB_CH0_MODE_EN_MASK (3 << 0) +#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2) +#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2) +#define LDB_CH1_MODE_EN_MASK (3 << 2) +#define LDB_SPLIT_MODE_EN (1 << 4) +#define LDB_DATA_WIDTH_CH0_24 (1 << 5) +#define LDB_BIT_MAP_CH0_JEIDA (1 << 6) +#define LDB_DATA_WIDTH_CH1_24 (1 << 7) +#define LDB_BIT_MAP_CH1_JEIDA (1 << 8) +#define LDB_DI0_VS_POL_ACT_LOW (1 << 9) +#define LDB_DI1_VS_POL_ACT_LOW (1 << 10) +#define LDB_BGREF_RMODE_INT (1 << 15) + +#define con_to_imx_ldb_ch(x) container_of(x, struct imx_ldb_channel, connector) +#define enc_to_imx_ldb_ch(x) container_of(x, struct imx_ldb_channel, encoder) + +struct imx_ldb; + +struct imx_ldb_channel { + struct imx_ldb *ldb; + struct drm_connector connector; + struct imx_drm_connector *imx_drm_connector; + struct drm_encoder encoder; + struct imx_drm_encoder *imx_drm_encoder; + int chno; + void *edid; + int edid_len; + struct drm_display_mode mode; + int mode_valid; +}; + +struct bus_mux { + int reg; + int shift; + int mask; +}; + +struct imx_ldb { + struct regmap *regmap; + struct device *dev; + struct imx_ldb_channel channel[2]; + struct clk *clk[2]; /* our own clock */ + struct clk *clk_sel[4]; /* parent of display clock */ + struct clk *clk_pll[2]; /* upstream clock we can adjust */ + u32 ldb_ctrl; + const struct bus_mux *lvds_mux; +}; + +static enum drm_connector_status imx_ldb_connector_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void imx_ldb_connector_destroy(struct drm_connector *connector) +{ + /* do not free here */ +} + +static int imx_ldb_connector_get_modes(struct drm_connector *connector) +{ + struct imx_ldb_channel *imx_ldb_ch = con_to_imx_ldb_ch(connector); + int num_modes = 0; + + if (imx_ldb_ch->edid) { + drm_mode_connector_update_edid_property(connector, + imx_ldb_ch->edid); + num_modes = drm_add_edid_modes(connector, imx_ldb_ch->edid); + } + + if (imx_ldb_ch->mode_valid) { + struct drm_display_mode *mode; + + mode = drm_mode_create(connector->dev); + drm_mode_copy(mode, &imx_ldb_ch->mode); + mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + num_modes++; + } + + return num_modes; +} + +static int imx_ldb_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return 0; +} + +static struct drm_encoder *imx_ldb_connector_best_encoder( + struct drm_connector *connector) +{ + struct imx_ldb_channel *imx_ldb_ch = con_to_imx_ldb_ch(connector); + + return &imx_ldb_ch->encoder; +} + +static void imx_ldb_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool imx_ldb_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void imx_ldb_set_clock(struct imx_ldb *ldb, int mux, int chno, + unsigned long serial_clk, unsigned long di_clk) +{ + int ret; + + dev_dbg(ldb->dev, "%s: now: %ld want: %ld\n", __func__, + clk_get_rate(ldb->clk_pll[chno]), serial_clk); + clk_set_rate(ldb->clk_pll[chno], serial_clk); + + dev_dbg(ldb->dev, "%s after: %ld\n", __func__, + clk_get_rate(ldb->clk_pll[chno])); + + dev_dbg(ldb->dev, "%s: now: %ld want: %ld\n", __func__, + clk_get_rate(ldb->clk[chno]), + (long int)di_clk); + clk_set_rate(ldb->clk[chno], di_clk); + + dev_dbg(ldb->dev, "%s after: %ld\n", __func__, + clk_get_rate(ldb->clk[chno])); + + /* set display clock mux to LDB input clock */ + ret = clk_set_parent(ldb->clk_sel[mux], ldb->clk[chno]); + if (ret) { + dev_err(ldb->dev, "unable to set di%d parent clock to ldb_di%d\n", mux, chno); + } +} + +static void imx_ldb_encoder_prepare(struct drm_encoder *encoder) +{ + struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); + struct imx_ldb *ldb = imx_ldb_ch->ldb; + struct drm_display_mode *mode = &encoder->crtc->mode; + u32 pixel_fmt; + unsigned long serial_clk; + unsigned long di_clk = mode->clock * 1000; + int mux = imx_drm_encoder_get_mux_id(imx_ldb_ch->imx_drm_encoder, + encoder->crtc); + + if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) { + /* dual channel LVDS mode */ + serial_clk = 3500UL * mode->clock; + imx_ldb_set_clock(ldb, mux, 0, serial_clk, di_clk); + imx_ldb_set_clock(ldb, mux, 1, serial_clk, di_clk); + } else { + serial_clk = 7000UL * mode->clock; + imx_ldb_set_clock(ldb, mux, imx_ldb_ch->chno, serial_clk, di_clk); + } + + switch (imx_ldb_ch->chno) { + case 0: + pixel_fmt = (ldb->ldb_ctrl & LDB_DATA_WIDTH_CH0_24) ? + V4L2_PIX_FMT_RGB24 : V4L2_PIX_FMT_BGR666; + break; + case 1: + pixel_fmt = (ldb->ldb_ctrl & LDB_DATA_WIDTH_CH1_24) ? + V4L2_PIX_FMT_RGB24 : V4L2_PIX_FMT_BGR666; + break; + default: + dev_err(ldb->dev, "unable to config di%d panel format\n", + imx_ldb_ch->chno); + pixel_fmt = V4L2_PIX_FMT_RGB24; + } + + imx_drm_crtc_panel_format(encoder->crtc, DRM_MODE_ENCODER_LVDS, + pixel_fmt); +} + +static void imx_ldb_encoder_commit(struct drm_encoder *encoder) +{ + struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); + struct imx_ldb *ldb = imx_ldb_ch->ldb; + int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; + int mux = imx_drm_encoder_get_mux_id(imx_ldb_ch->imx_drm_encoder, + encoder->crtc); + + if (dual) { + clk_prepare_enable(ldb->clk[0]); + clk_prepare_enable(ldb->clk[1]); + } + + if (imx_ldb_ch == &ldb->channel[0] || dual) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + if (mux == 0 || ldb->lvds_mux) + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + else if (mux == 1) + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI1; + } + if (imx_ldb_ch == &ldb->channel[1] || dual) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + if (mux == 1 || ldb->lvds_mux) + ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI1; + else if (mux == 0) + ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI0; + } + + if (ldb->lvds_mux) { + const struct bus_mux *lvds_mux = NULL; + + if (imx_ldb_ch == &ldb->channel[0]) + lvds_mux = &ldb->lvds_mux[0]; + else if (imx_ldb_ch == &ldb->channel[1]) + lvds_mux = &ldb->lvds_mux[1]; + + regmap_update_bits(ldb->regmap, lvds_mux->reg, lvds_mux->mask, + mux << lvds_mux->shift); + } + + regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl); +} + +static void imx_ldb_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); + struct imx_ldb *ldb = imx_ldb_ch->ldb; + int dual = ldb->ldb_ctrl & LDB_SPLIT_MODE_EN; + + if (mode->clock > 170000) { + dev_warn(ldb->dev, + "%s: mode exceeds 170 MHz pixel clock\n", __func__); + } + if (mode->clock > 85000 && !dual) { + dev_warn(ldb->dev, + "%s: mode exceeds 85 MHz pixel clock\n", __func__); + } + + /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ + if (imx_ldb_ch == &ldb->channel[0]) { + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; + else if (mode->flags & DRM_MODE_FLAG_PVSYNC) + ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW; + } + if (imx_ldb_ch == &ldb->channel[1]) { + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; + else if (mode->flags & DRM_MODE_FLAG_PVSYNC) + ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; + } +} + +static void imx_ldb_encoder_disable(struct drm_encoder *encoder) +{ + struct imx_ldb_channel *imx_ldb_ch = enc_to_imx_ldb_ch(encoder); + struct imx_ldb *ldb = imx_ldb_ch->ldb; + + /* + * imx_ldb_encoder_disable is called by + * drm_helper_disable_unused_functions without + * the encoder being enabled before. + */ + if (imx_ldb_ch == &ldb->channel[0] && + (ldb->ldb_ctrl & LDB_CH0_MODE_EN_MASK) == 0) + return; + else if (imx_ldb_ch == &ldb->channel[1] && + (ldb->ldb_ctrl & LDB_CH1_MODE_EN_MASK) == 0) + return; + + if (imx_ldb_ch == &ldb->channel[0]) + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + else if (imx_ldb_ch == &ldb->channel[1]) + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + + regmap_write(ldb->regmap, IOMUXC_GPR2, ldb->ldb_ctrl); + + if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) { + clk_disable_unprepare(ldb->clk[0]); + clk_disable_unprepare(ldb->clk[1]); + } +} + +static void imx_ldb_encoder_destroy(struct drm_encoder *encoder) +{ + /* do not free here */ +} + +static struct drm_connector_funcs imx_ldb_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = imx_ldb_connector_detect, + .destroy = imx_ldb_connector_destroy, +}; + +static struct drm_connector_helper_funcs imx_ldb_connector_helper_funcs = { + .get_modes = imx_ldb_connector_get_modes, + .best_encoder = imx_ldb_connector_best_encoder, + .mode_valid = imx_ldb_connector_mode_valid, +}; + +static struct drm_encoder_funcs imx_ldb_encoder_funcs = { + .destroy = imx_ldb_encoder_destroy, +}; + +static struct drm_encoder_helper_funcs imx_ldb_encoder_helper_funcs = { + .dpms = imx_ldb_encoder_dpms, + .mode_fixup = imx_ldb_encoder_mode_fixup, + .prepare = imx_ldb_encoder_prepare, + .commit = imx_ldb_encoder_commit, + .mode_set = imx_ldb_encoder_mode_set, + .disable = imx_ldb_encoder_disable, +}; + +static int imx_ldb_get_clk(struct imx_ldb *ldb, int chno) +{ + char clkname[16]; + + sprintf(clkname, "di%d", chno); + ldb->clk[chno] = devm_clk_get(ldb->dev, clkname); + if (IS_ERR(ldb->clk[chno])) + return PTR_ERR(ldb->clk[chno]); + + sprintf(clkname, "di%d_pll", chno); + ldb->clk_pll[chno] = devm_clk_get(ldb->dev, clkname); + if (IS_ERR(ldb->clk_pll[chno])) + return PTR_ERR(ldb->clk_pll[chno]); + + return 0; +} + +static int imx_ldb_register(struct imx_ldb_channel *imx_ldb_ch) +{ + int ret; + struct imx_ldb *ldb = imx_ldb_ch->ldb; + + ret = imx_ldb_get_clk(ldb, imx_ldb_ch->chno); + if (ret) + return ret; + if (ldb->ldb_ctrl & LDB_SPLIT_MODE_EN) { + ret |= imx_ldb_get_clk(ldb, 1); + if (ret) + return ret; + } + + imx_ldb_ch->connector.funcs = &imx_ldb_connector_funcs; + imx_ldb_ch->encoder.funcs = &imx_ldb_encoder_funcs; + + imx_ldb_ch->encoder.encoder_type = DRM_MODE_ENCODER_LVDS; + imx_ldb_ch->connector.connector_type = DRM_MODE_CONNECTOR_LVDS; + + drm_encoder_helper_add(&imx_ldb_ch->encoder, + &imx_ldb_encoder_helper_funcs); + ret = imx_drm_add_encoder(&imx_ldb_ch->encoder, + &imx_ldb_ch->imx_drm_encoder, THIS_MODULE); + if (ret) { + dev_err(ldb->dev, "adding encoder failed with %d\n", ret); + return ret; + } + + drm_connector_helper_add(&imx_ldb_ch->connector, + &imx_ldb_connector_helper_funcs); + + ret = imx_drm_add_connector(&imx_ldb_ch->connector, + &imx_ldb_ch->imx_drm_connector, THIS_MODULE); + if (ret) { + imx_drm_remove_encoder(imx_ldb_ch->imx_drm_encoder); + dev_err(ldb->dev, "adding connector failed with %d\n", ret); + return ret; + } + + drm_mode_connector_attach_encoder(&imx_ldb_ch->connector, + &imx_ldb_ch->encoder); + + return 0; +} + +enum { + LVDS_BIT_MAP_SPWG, + LVDS_BIT_MAP_JEIDA +}; + +static const char *imx_ldb_bit_mappings[] = { + [LVDS_BIT_MAP_SPWG] = "spwg", + [LVDS_BIT_MAP_JEIDA] = "jeida", +}; + +const int of_get_data_mapping(struct device_node *np) +{ + const char *bm; + int ret, i; + + ret = of_property_read_string(np, "fsl,data-mapping", &bm); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(imx_ldb_bit_mappings); i++) + if (!strcasecmp(bm, imx_ldb_bit_mappings[i])) + return i; + + return -EINVAL; +} + +static struct bus_mux imx6q_lvds_mux[2] = { + { + .reg = IOMUXC_GPR3, + .shift = 6, + .mask = IMX6Q_GPR3_LVDS0_MUX_CTL_MASK, + }, { + .reg = IOMUXC_GPR3, + .shift = 8, + .mask = IMX6Q_GPR3_LVDS1_MUX_CTL_MASK, + } +}; + +/* + * For a device declaring compatible = "fsl,imx6q-ldb", "fsl,imx53-ldb", + * of_match_device will walk through this list and take the first entry + * matching any of its compatible values. Therefore, the more generic + * entries (in this case fsl,imx53-ldb) need to be ordered last. + */ +static const struct of_device_id imx_ldb_dt_ids[] = { + { .compatible = "fsl,imx6q-ldb", .data = imx6q_lvds_mux, }, + { .compatible = "fsl,imx53-ldb", .data = NULL, }, + { } +}; +MODULE_DEVICE_TABLE(of, imx_ldb_dt_ids); + +static int imx_ldb_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id = + of_match_device(of_match_ptr(imx_ldb_dt_ids), + &pdev->dev); + struct device_node *child; + const u8 *edidp; + struct imx_ldb *imx_ldb; + int datawidth; + int mapping; + int dual; + int ret; + int i; + + imx_ldb = devm_kzalloc(&pdev->dev, sizeof(*imx_ldb), GFP_KERNEL); + if (!imx_ldb) + return -ENOMEM; + + imx_ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr"); + if (IS_ERR(imx_ldb->regmap)) { + dev_err(&pdev->dev, "failed to get parent regmap\n"); + return PTR_ERR(imx_ldb->regmap); + } + + imx_ldb->dev = &pdev->dev; + + if (of_id) + imx_ldb->lvds_mux = of_id->data; + + dual = of_property_read_bool(np, "fsl,dual-channel"); + if (dual) + imx_ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN; + + /* + * There are three diferent possible clock mux configurations: + * i.MX53: ipu1_di0_sel, ipu1_di1_sel + * i.MX6q: ipu1_di0_sel, ipu1_di1_sel, ipu2_di0_sel, ipu2_di1_sel + * i.MX6dl: ipu1_di0_sel, ipu1_di1_sel, lcdif_sel + * Map them all to di0_sel...di3_sel. + */ + for (i = 0; i < 4; i++) { + char clkname[16]; + + sprintf(clkname, "di%d_sel", i); + imx_ldb->clk_sel[i] = devm_clk_get(imx_ldb->dev, clkname); + if (IS_ERR(imx_ldb->clk_sel[i])) { + ret = PTR_ERR(imx_ldb->clk_sel[i]); + imx_ldb->clk_sel[i] = NULL; + break; + } + } + if (i == 0) + return ret; + + for_each_child_of_node(np, child) { + struct imx_ldb_channel *channel; + + ret = of_property_read_u32(child, "reg", &i); + if (ret || i < 0 || i > 1) + return -EINVAL; + + if (dual && i > 0) { + dev_warn(&pdev->dev, "dual-channel mode, ignoring second output\n"); + continue; + } + + if (!of_device_is_available(child)) + continue; + + channel = &imx_ldb->channel[i]; + channel->ldb = imx_ldb; + channel->chno = i; + + edidp = of_get_property(child, "edid", &channel->edid_len); + if (edidp) { + channel->edid = kmemdup(edidp, channel->edid_len, + GFP_KERNEL); + } else { + ret = of_get_drm_display_mode(child, &channel->mode, 0); + if (!ret) + channel->mode_valid = 1; + } + + ret = of_property_read_u32(child, "fsl,data-width", &datawidth); + if (ret) + datawidth = 0; + else if (datawidth != 18 && datawidth != 24) + return -EINVAL; + + mapping = of_get_data_mapping(child); + switch (mapping) { + case LVDS_BIT_MAP_SPWG: + if (datawidth == 24) { + if (i == 0 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24; + if (i == 1 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24; + } + break; + case LVDS_BIT_MAP_JEIDA: + if (datawidth == 18) { + dev_err(&pdev->dev, "JEIDA standard only supported in 24 bit\n"); + return -EINVAL; + } + if (i == 0 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 | LDB_BIT_MAP_CH0_JEIDA; + if (i == 1 || dual) + imx_ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 | LDB_BIT_MAP_CH1_JEIDA; + break; + default: + dev_err(&pdev->dev, "data mapping not specified or invalid\n"); + return -EINVAL; + } + + ret = imx_ldb_register(channel); + if (ret) + return ret; + + imx_drm_encoder_add_possible_crtcs(channel->imx_drm_encoder, child); + } + + platform_set_drvdata(pdev, imx_ldb); + + return 0; +} + +static int imx_ldb_remove(struct platform_device *pdev) +{ + struct imx_ldb *imx_ldb = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < 2; i++) { + struct imx_ldb_channel *channel = &imx_ldb->channel[i]; + struct drm_connector *connector = &channel->connector; + struct drm_encoder *encoder = &channel->encoder; + + drm_mode_connector_detach_encoder(connector, encoder); + + imx_drm_remove_connector(channel->imx_drm_connector); + imx_drm_remove_encoder(channel->imx_drm_encoder); + } + + return 0; +} + +static struct platform_driver imx_ldb_driver = { + .probe = imx_ldb_probe, + .remove = imx_ldb_remove, + .driver = { + .of_match_table = imx_ldb_dt_ids, + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(imx_ldb_driver); + +MODULE_DESCRIPTION("i.MX LVDS driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/imx-drm/imx-tve.c b/drivers/staging/imx-drm/imx-tve.c index ac163446440..a56797d88ed 100644 --- a/drivers/staging/imx-drm/imx-tve.c +++ b/drivers/staging/imx-drm/imx-tve.c @@ -22,7 +22,6 @@ #include <linux/clk-provider.h> #include <linux/module.h> #include <linux/of_i2c.h> -#include <linux/pinctrl/consumer.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/spinlock.h> @@ -610,15 +609,6 @@ static int imx_tve_probe(struct platform_device *pdev) } if (tve->mode == TVE_MODE_VGA) { - struct pinctrl *pinctrl; - - pinctrl = devm_pinctrl_get_select_default(&pdev->dev); - if (IS_ERR(pinctrl)) { - ret = PTR_ERR(pinctrl); - dev_warn(&pdev->dev, "failed to setup pinctrl: %d", ret); - return ret; - } - ret = of_property_read_u32(np, "fsl,hsync-pin", &tve->hsync_pin); if (ret < 0) { dev_err(&pdev->dev, "failed to get vsync pin\n"); @@ -638,11 +628,9 @@ static int imx_tve_probe(struct platform_device *pdev) return -ENOENT; } - base = devm_request_and_ioremap(&pdev->dev, res); - if (!base) { - dev_err(&pdev->dev, "failed to remap memory region\n"); - return -ENOENT; - } + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); tve_regmap_config.lock_arg = tve; tve->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "tve", base, @@ -670,7 +658,9 @@ static int imx_tve_probe(struct platform_device *pdev) tve->dac_reg = devm_regulator_get(&pdev->dev, "dac"); if (!IS_ERR(tve->dac_reg)) { regulator_set_voltage(tve->dac_reg, 2750000, 2750000); - regulator_enable(tve->dac_reg); + ret = regulator_enable(tve->dac_reg); + if (ret) + return ret; } tve->clk = devm_clk_get(&pdev->dev, "tve"); diff --git a/drivers/staging/imx-drm/ipu-v3/ipu-common.c b/drivers/staging/imx-drm/ipu-v3/ipu-common.c index 0127601c26c..e35d0bf03c7 100644 --- a/drivers/staging/imx-drm/ipu-v3/ipu-common.c +++ b/drivers/staging/imx-drm/ipu-v3/ipu-common.c @@ -27,6 +27,7 @@ #include <linux/list.h> #include <linux/irq.h> #include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> #include <linux/of_device.h> #include "imx-ipu-v3.h" @@ -799,16 +800,18 @@ err_di_0: static void ipu_irq_handle(struct ipu_soc *ipu, const int *regs, int num_regs) { unsigned long status; - int i, bit, irq_base; + int i, bit, irq; for (i = 0; i < num_regs; i++) { status = ipu_cm_read(ipu, IPU_INT_STAT(regs[i])); status &= ipu_cm_read(ipu, IPU_INT_CTRL(regs[i])); - irq_base = ipu->irq_start + regs[i] * 32; - for_each_set_bit(bit, &status, 32) - generic_handle_irq(irq_base + bit); + for_each_set_bit(bit, &status, 32) { + irq = irq_linear_revmap(ipu->domain, regs[i] * 32 + bit); + if (irq) + generic_handle_irq(irq); + } } } @@ -838,57 +841,15 @@ static void ipu_err_irq_handler(unsigned int irq, struct irq_desc *desc) chained_irq_exit(chip, desc); } -static void ipu_ack_irq(struct irq_data *d) -{ - struct ipu_soc *ipu = irq_data_get_irq_chip_data(d); - unsigned int irq = d->irq - ipu->irq_start; - - ipu_cm_write(ipu, 1 << (irq % 32), IPU_INT_STAT(irq / 32)); -} - -static void ipu_unmask_irq(struct irq_data *d) -{ - struct ipu_soc *ipu = irq_data_get_irq_chip_data(d); - unsigned int irq = d->irq - ipu->irq_start; - unsigned long flags; - u32 reg; - - spin_lock_irqsave(&ipu->lock, flags); - - reg = ipu_cm_read(ipu, IPU_INT_CTRL(irq / 32)); - reg |= 1 << (irq % 32); - ipu_cm_write(ipu, reg, IPU_INT_CTRL(irq / 32)); - - spin_unlock_irqrestore(&ipu->lock, flags); -} - -static void ipu_mask_irq(struct irq_data *d) -{ - struct ipu_soc *ipu = irq_data_get_irq_chip_data(d); - unsigned int irq = d->irq - ipu->irq_start; - unsigned long flags; - u32 reg; - - spin_lock_irqsave(&ipu->lock, flags); - - reg = ipu_cm_read(ipu, IPU_INT_CTRL(irq / 32)); - reg &= ~(1 << (irq % 32)); - ipu_cm_write(ipu, reg, IPU_INT_CTRL(irq / 32)); - - spin_unlock_irqrestore(&ipu->lock, flags); -} - -static struct irq_chip ipu_irq_chip = { - .name = "IPU", - .irq_ack = ipu_ack_irq, - .irq_mask = ipu_mask_irq, - .irq_unmask = ipu_unmask_irq, -}; - int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel, enum ipu_channel_irq irq_type) { - return ipu->irq_start + irq_type + channel->num; + int irq = irq_linear_revmap(ipu->domain, irq_type + channel->num); + + if (!irq) + irq = irq_create_mapping(ipu->domain, irq_type + channel->num); + + return irq; } EXPORT_SYMBOL_GPL(ipu_idmac_channel_irq); @@ -975,18 +936,48 @@ err_register: return ret; } + static int ipu_irq_init(struct ipu_soc *ipu) { - int i; + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + unsigned long unused[IPU_NUM_IRQS / 32] = { + 0x400100d0, 0xffe000fd, + 0x400100d0, 0xffe000fd, + 0x400100d0, 0xffe000fd, + 0x4077ffff, 0xffe7e1fd, + 0x23fffffe, 0x8880fff0, + 0xf98fe7d0, 0xfff81fff, + 0x400100d0, 0xffe000fd, + 0x00000000, + }; + int ret, i; + + ipu->domain = irq_domain_add_linear(ipu->dev->of_node, IPU_NUM_IRQS, + &irq_generic_chip_ops, ipu); + if (!ipu->domain) { + dev_err(ipu->dev, "failed to add irq domain\n"); + return -ENODEV; + } - ipu->irq_start = irq_alloc_descs(-1, 0, IPU_NUM_IRQS, 0); - if (ipu->irq_start < 0) - return ipu->irq_start; + ret = irq_alloc_domain_generic_chips(ipu->domain, 32, 1, "IPU", + handle_level_irq, 0, IRQF_VALID, 0); + if (ret < 0) { + dev_err(ipu->dev, "failed to alloc generic irq chips\n"); + irq_domain_remove(ipu->domain); + return ret; + } - for (i = ipu->irq_start; i < ipu->irq_start + IPU_NUM_IRQS; i++) { - irq_set_chip_and_handler(i, &ipu_irq_chip, handle_level_irq); - set_irq_flags(i, IRQF_VALID); - irq_set_chip_data(i, ipu); + for (i = 0; i < IPU_NUM_IRQS; i += 32) { + gc = irq_get_domain_generic_chip(ipu->domain, i); + gc->reg_base = ipu->cm_reg; + gc->unused = unused[i / 32]; + ct = gc->chip_types; + ct->chip.irq_ack = irq_gc_ack_set_bit; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->regs.ack = IPU_INT_STAT(i / 32); + ct->regs.mask = IPU_INT_CTRL(i / 32); } irq_set_chained_handler(ipu->irq_sync, ipu_irq_handler); @@ -999,20 +990,22 @@ static int ipu_irq_init(struct ipu_soc *ipu) static void ipu_irq_exit(struct ipu_soc *ipu) { - int i; + int i, irq; irq_set_chained_handler(ipu->irq_err, NULL); irq_set_handler_data(ipu->irq_err, NULL); irq_set_chained_handler(ipu->irq_sync, NULL); irq_set_handler_data(ipu->irq_sync, NULL); - for (i = ipu->irq_start; i < ipu->irq_start + IPU_NUM_IRQS; i++) { - set_irq_flags(i, 0); - irq_set_chip(i, NULL); - irq_set_chip_data(i, NULL); + /* TODO: remove irq_domain_generic_chips */ + + for (i = 0; i < IPU_NUM_IRQS; i++) { + irq = irq_linear_revmap(ipu->domain, i); + if (irq) + irq_dispose_mapping(irq); } - irq_free_descs(ipu->irq_start, IPU_NUM_IRQS); + irq_domain_remove(ipu->domain); } static int ipu_probe(struct platform_device *pdev) diff --git a/drivers/staging/imx-drm/ipu-v3/ipu-di.c b/drivers/staging/imx-drm/ipu-v3/ipu-di.c index 19d777e39d0..0b6806e2069 100644 --- a/drivers/staging/imx-drm/ipu-v3/ipu-di.c +++ b/drivers/staging/imx-drm/ipu-v3/ipu-di.c @@ -603,7 +603,12 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig) vsync_cnt = 3; if (di->id == 1) - vsync_cnt = 6; + /* + * TODO: change only for TVEv2, parallel display + * uses pin 2 / 3 + */ + if (!(sig->hsync_pin == 2 && sig->vsync_pin == 3)) + vsync_cnt = 6; if (sig->Hsync_pol) { if (sig->hsync_pin == 2) @@ -614,11 +619,11 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig) di_gen |= DI_GEN_POLARITY_7; } if (sig->Vsync_pol) { - if (sig->hsync_pin == 3) + if (sig->vsync_pin == 3) di_gen |= DI_GEN_POLARITY_3; - else if (sig->hsync_pin == 6) + else if (sig->vsync_pin == 6) di_gen |= DI_GEN_POLARITY_6; - else if (sig->hsync_pin == 8) + else if (sig->vsync_pin == 8) di_gen |= DI_GEN_POLARITY_8; } } diff --git a/drivers/staging/imx-drm/ipu-v3/ipu-dmfc.c b/drivers/staging/imx-drm/ipu-v3/ipu-dmfc.c index 91821bc30f4..2e97c33b81e 100644 --- a/drivers/staging/imx-drm/ipu-v3/ipu-dmfc.c +++ b/drivers/staging/imx-drm/ipu-v3/ipu-dmfc.c @@ -61,7 +61,7 @@ struct dmfc_channel_data { static const struct dmfc_channel_data dmfcdata[] = { { - .ipu_channel = 23, + .ipu_channel = IPUV3_CHANNEL_MEM_BG_SYNC, .channel_reg = DMFC_DP_CHAN, .shift = DMFC_DP_CHAN_5B_23, .eot_shift = 20, @@ -73,13 +73,13 @@ static const struct dmfc_channel_data dmfcdata[] = { .eot_shift = 22, .max_fifo_lines = 1, }, { - .ipu_channel = 27, + .ipu_channel = IPUV3_CHANNEL_MEM_FG_SYNC, .channel_reg = DMFC_DP_CHAN, .shift = DMFC_DP_CHAN_5F_27, .eot_shift = 21, .max_fifo_lines = 2, }, { - .ipu_channel = 28, + .ipu_channel = IPUV3_CHANNEL_MEM_DC_SYNC, .channel_reg = DMFC_WR_CHAN, .shift = DMFC_WR_CHAN_1_28, .eot_shift = 16, @@ -292,7 +292,7 @@ int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc, { struct ipu_dmfc_priv *priv = dmfc->priv; int slots = dmfc_bandwidth_to_slots(priv, bandwidth_pixel_per_second); - int segment = 0, ret = 0; + int segment = -1, ret = 0; dev_dbg(priv->dev, "dmfc: trying to allocate %ldMpixel/s for IPU channel %d\n", bandwidth_pixel_per_second / 1000000, @@ -307,7 +307,17 @@ int ipu_dmfc_alloc_bandwidth(struct dmfc_channel *dmfc, goto out; } - segment = dmfc_find_slots(priv, slots); + /* Always allocate at least 128*4 bytes (2 slots) */ + if (slots < 2) + slots = 2; + + /* For the MEM_BG channel, first try to allocate twice the slots */ + if (dmfc->data->ipu_channel == IPUV3_CHANNEL_MEM_BG_SYNC) + segment = dmfc_find_slots(priv, slots * 2); + if (segment >= 0) + slots *= 2; + else + segment = dmfc_find_slots(priv, slots); if (segment < 0) { ret = -EBUSY; goto out; @@ -391,7 +401,7 @@ int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base, * We have a total bandwidth of clkrate * 4pixel divided * into 8 slots. */ - priv->bandwidth_per_slot = clk_get_rate(ipu_clk) / 8; + priv->bandwidth_per_slot = clk_get_rate(ipu_clk) * 4 / 8; dev_dbg(dev, "dmfc: 8 slots with %ldMpixel/s bandwidth each\n", priv->bandwidth_per_slot / 1000000); diff --git a/drivers/staging/imx-drm/ipu-v3/ipu-prv.h b/drivers/staging/imx-drm/ipu-v3/ipu-prv.h index 551802863fd..4df00501adc 100644 --- a/drivers/staging/imx-drm/ipu-v3/ipu-prv.h +++ b/drivers/staging/imx-drm/ipu-v3/ipu-prv.h @@ -110,7 +110,7 @@ struct ipu_soc; #define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32)) #define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32)) -#define IPU_NUM_IRQS (32 * 5) +#define IPU_NUM_IRQS (32 * 15) enum ipu_modules { IPU_CONF_CSI0_EN = (1 << 0), @@ -170,9 +170,9 @@ struct ipu_soc { struct ipuv3_channel channel[64]; - int irq_start; int irq_sync; int irq_err; + struct irq_domain *domain; struct ipu_dc_priv *dc_priv; struct ipu_dp_priv *dp_priv; diff --git a/drivers/staging/imx-drm/ipuv3-crtc.c b/drivers/staging/imx-drm/ipuv3-crtc.c index ea61c869110..4a7eedfafbd 100644 --- a/drivers/staging/imx-drm/ipuv3-crtc.c +++ b/drivers/staging/imx-drm/ipuv3-crtc.c @@ -22,7 +22,6 @@ #include <linux/device.h> #include <linux/platform_device.h> #include <drm/drmP.h> -#include <drm/drm_fb_helper.h> #include <drm/drm_crtc_helper.h> #include <linux/fb.h> #include <linux/clk.h> @@ -42,9 +41,6 @@ struct ipu_framebuffer { }; struct ipu_crtc { - struct drm_fb_helper fb_helper; - struct ipu_framebuffer ifb; - int num_crtcs; struct device *dev; struct drm_crtc base; struct imx_drm_crtc *imx_crtc; @@ -54,7 +50,6 @@ struct ipu_crtc { struct dmfc_channel *dmfc; struct ipu_di *di; int enabled; - struct ipu_priv *ipu_priv; struct drm_pending_vblank_event *page_flip_event; struct drm_framebuffer *newfb; int irq; @@ -152,6 +147,7 @@ static int ipu_page_flip(struct drm_crtc *crtc, ipu_crtc->newfb = fb; ipu_crtc->page_flip_event = event; + crtc->fb = fb; return 0; } @@ -316,31 +312,14 @@ static int ipu_crtc_mode_set(struct drm_crtc *crtc, static void ipu_crtc_handle_pageflip(struct ipu_crtc *ipu_crtc) { - struct drm_pending_vblank_event *e; - struct timeval now; unsigned long flags; struct drm_device *drm = ipu_crtc->base.dev; spin_lock_irqsave(&drm->event_lock, flags); - - e = ipu_crtc->page_flip_event; - if (!e) { - spin_unlock_irqrestore(&drm->event_lock, flags); - return; - } - - do_gettimeofday(&now); - e->event.sequence = 0; - e->event.tv_sec = now.tv_sec; - e->event.tv_usec = now.tv_usec; + if (ipu_crtc->page_flip_event) + drm_send_vblank_event(drm, -1, ipu_crtc->page_flip_event); ipu_crtc->page_flip_event = NULL; - imx_drm_crtc_vblank_put(ipu_crtc->imx_crtc); - - list_add_tail(&e->base.link, &e->base.file_priv->event_list); - - wake_up_interruptible(&e->base.file_priv->event_wait); - spin_unlock_irqrestore(&drm->event_lock, flags); } @@ -351,7 +330,6 @@ static irqreturn_t ipu_irq_handler(int irq, void *dev_id) imx_drm_handle_vblank(ipu_crtc->imx_crtc); if (ipu_crtc->newfb) { - ipu_crtc->base.fb = ipu_crtc->newfb; ipu_crtc->newfb = NULL; ipu_drm_set_base(&ipu_crtc->base, 0, 0); ipu_crtc_handle_pageflip(ipu_crtc); diff --git a/drivers/staging/imx-drm/parallel-display.c b/drivers/staging/imx-drm/parallel-display.c index e7fba62c10e..cea9f14fff4 100644 --- a/drivers/staging/imx-drm/parallel-display.c +++ b/drivers/staging/imx-drm/parallel-display.c @@ -23,7 +23,6 @@ #include <drm/drm_fb_helper.h> #include <drm/drm_crtc_helper.h> #include <linux/videodev2.h> -#include <linux/pinctrl/consumer.h> #include "imx-drm.h" @@ -206,20 +205,11 @@ static int imx_pd_probe(struct platform_device *pdev) struct imx_parallel_display *imxpd; int ret; const char *fmt; - struct pinctrl *pinctrl; imxpd = devm_kzalloc(&pdev->dev, sizeof(*imxpd), GFP_KERNEL); if (!imxpd) return -ENOMEM; - pinctrl = devm_pinctrl_get_select_default(&pdev->dev); - if (IS_ERR(pinctrl)) { - ret = PTR_ERR(pinctrl); - dev_warn(&pdev->dev, "pinctrl_get_select_default failed with %d", - ret); - return ret; - } - edidp = of_get_property(np, "edid", &imxpd->edid_len); if (edidp) imxpd->edid = kmemdup(edidp, imxpd->edid_len, GFP_KERNEL); @@ -265,6 +255,7 @@ static const struct of_device_id imx_pd_dt_ids[] = { { .compatible = "fsl,imx-parallel-display", }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, imx_pd_dt_ids); static struct platform_driver imx_pd_driver = { .probe = imx_pd_probe, |