From 7f720048eaf3bd376d654b45a97c4134c7b2d2dc Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 14 Mar 2014 10:19:06 +0100 Subject: video: i.MX IPUv3: Add lvds bridge support Signed-off-by: Sascha Hauer --- drivers/video/imx-ipu-v3/Kconfig | 3 + drivers/video/imx-ipu-v3/Makefile | 1 + drivers/video/imx-ipu-v3/imx-ldb.c | 310 +++++++++++++++++++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 drivers/video/imx-ipu-v3/imx-ldb.c diff --git a/drivers/video/imx-ipu-v3/Kconfig b/drivers/video/imx-ipu-v3/Kconfig index 84ef363674..e83158a53f 100644 --- a/drivers/video/imx-ipu-v3/Kconfig +++ b/drivers/video/imx-ipu-v3/Kconfig @@ -5,4 +5,7 @@ config DRIVER_VIDEO_IMX_IPUV3 if DRIVER_VIDEO_IMX_IPUV3 +config DRIVER_VIDEO_IMX_IPUV3_LVDS + bool "IPUv3 LVDS support" + endif diff --git a/drivers/video/imx-ipu-v3/Makefile b/drivers/video/imx-ipu-v3/Makefile index 0edc1a4a4a..e2ff43de6c 100644 --- a/drivers/video/imx-ipu-v3/Makefile +++ b/drivers/video/imx-ipu-v3/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-common.o ipu-dmfc.o ipu-di.o obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-dp.o ipuv3-plane.o ipufb.o obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3) += ipu-dc.o +obj-$(CONFIG_DRIVER_VIDEO_IMX_IPUV3_LVDS) += imx-ldb.o diff --git a/drivers/video/imx-ipu-v3/imx-ldb.c b/drivers/video/imx-ipu-v3/imx-ldb.c new file mode 100644 index 0000000000..7367fbea71 --- /dev/null +++ b/drivers/video/imx-ipu-v3/imx-ldb.c @@ -0,0 +1,310 @@ +/* + * i.MX drm driver - parallel display implementation + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-ipu-v3.h" +#include "ipuv3-plane.h" + +#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) + +struct imx_ldb; + +struct imx_ldb_channel { + struct imx_ldb *ldb; + int chno; + int mode_valid; + struct display_timings *modes; + struct ipu_output output; +}; + +struct imx_ldb_data { + void __iomem *base; + int (*prepare)(struct imx_ldb_channel *imx_ldb_ch, int di); + unsigned ipu_mask; +}; + +struct imx_ldb { + struct device_d *dev; + u32 interface_pix_fmt; + int mode_valid; + struct imx_ldb_channel channel[2]; + u32 ldb_ctrl; + void __iomem *base; + const struct imx_ldb_data *soc_data; +}; + +enum { + LVDS_BIT_MAP_SPWG, + LVDS_BIT_MAP_JEIDA +}; + +static const char * const imx_ldb_bit_mappings[] = { + [LVDS_BIT_MAP_SPWG] = "spwg", + [LVDS_BIT_MAP_JEIDA] = "jeida", +}; + +static 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 int imx_ldb_prepare(struct ipu_output *output, struct fb_videomode *mode, int di) +{ + struct imx_ldb_channel *imx_ldb_ch = container_of(output, struct imx_ldb_channel, output); + struct imx_ldb *ldb = imx_ldb_ch->ldb; + + if (PICOS2KHZ(mode->pixclock) > 85000) { + dev_warn(ldb->dev, + "%s: mode exceeds 85 MHz pixel clock\n", __func__); + } + + ldb->soc_data->prepare(imx_ldb_ch, di); + + /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */ + if (imx_ldb_ch == &ldb->channel[0]) { + if (mode->sync & FB_SYNC_VERT_HIGH_ACT) + ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW; + else + ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW; + } + + if (imx_ldb_ch == &ldb->channel[1]) { + if (mode->sync & FB_SYNC_VERT_HIGH_ACT) + ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW; + else + ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW; + } + + if (imx_ldb_ch == &ldb->channel[0]) { + ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0; + } + + if (imx_ldb_ch == &ldb->channel[1]) { + ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK; + ldb->ldb_ctrl |= LDB_CH1_MODE_EN_TO_DI1; + } + + writel(ldb->ldb_ctrl, ldb->base); + + return 0; +} + +static int imx6q_ldb_prepare(struct imx_ldb_channel *imx_ldb_ch, int di) +{ + struct clk *diclk, *ldbclk; + struct imx_ldb *ldb = imx_ldb_ch->ldb; + int ret, ipuno, dino; + char *clkname; + void __iomem *gpr3 = (void *)MX6_IOMUXC_BASE_ADDR + 0xc; + uint32_t val; + int shift; + + ipuno = ((di >> 1) & 1) + 1; + dino = di & 0x1; + + clkname = asprintf("ipu%d_di%d_sel", ipuno, dino); + diclk = clk_lookup(clkname); + free(clkname); + if (IS_ERR(diclk)) { + dev_err(ldb->dev, "failed to get di clk: %s\n", strerror(PTR_ERR(diclk))); + return PTR_ERR(diclk); + } + + clkname = asprintf("ldb_di%d_podf", imx_ldb_ch->chno); + ldbclk = clk_lookup(clkname); + free(clkname); + if (IS_ERR(ldbclk)) { + dev_err(ldb->dev, "failed to get ldb clk: %s\n", strerror(PTR_ERR(ldbclk))); + return PTR_ERR(ldbclk); + } + + ret = clk_set_parent(diclk, ldbclk); + if (ret) { + dev_err(ldb->dev, "failed to set display clock parent: %s\n", strerror(-ret)); + return ret; + } +printk("%s: %d\n", __func__, di); + val = readl(gpr3); + shift = (imx_ldb_ch->chno == 0) ? 6 : 8; + val &= ~(3 << shift); + val |= di << shift; + writel(val, gpr3); + + return 0; +} + +static int imx53_ldb_prepare(struct imx_ldb_channel *imx_ldb_ch, int di) +{ + return -ENOSYS; +} + +static struct imx_ldb_data imx_ldb_data_imx6q = { + .base = (void *)MX6_IOMUXC_BASE_ADDR + 0x8, + .prepare = imx6q_ldb_prepare, + .ipu_mask = 0xf, +}; + +static struct imx_ldb_data imx_ldb_data_imx53 = { + .base = (void *)MX53_IOMUXC_BASE_ADDR + 0x8, + .prepare = imx53_ldb_prepare, + .ipu_mask = 0x3, +}; + +static struct ipu_output_ops imx_ldb_ops = { + .prepare = imx_ldb_prepare, +}; + +static int imx_ldb_probe(struct device_d *dev) +{ + struct device_node *np = dev->device_node; + struct device_node *child; + struct imx_ldb *imx_ldb; + int ret, i; + int dual = 0; + int datawidth; + int mapping; + const struct imx_ldb_data *devtype; + + ret = dev_get_drvdata(dev, (unsigned long *)&devtype); + if (ret) + return ret; + + imx_ldb = xzalloc(sizeof(*imx_ldb)); + imx_ldb->base = devtype->base; + imx_ldb->soc_data = devtype; + + 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(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; + + 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(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(dev, "data mapping not specified or invalid\n"); + return -EINVAL; + } + + channel->output.ops = &imx_ldb_ops; + channel->output.di_clkflags = IPU_DI_CLKMODE_EXT | IPU_DI_CLKMODE_SYNC; + channel->output.out_pixel_fmt = (datawidth == 24) ? + V4L2_PIX_FMT_RGB24 : V4L2_PIX_FMT_BGR666; + channel->output.modes = of_get_display_timings(child); + channel->output.name = asprintf("ldb-%d", i); + channel->output.ipu_mask = devtype->ipu_mask; + + ipu_register_output(&channel->output); + } + + return 0; +} + +static struct of_device_id imx_ldb_dt_ids[] = { + { .compatible = "fsl,imx6q-ldb", (unsigned long)&imx_ldb_data_imx6q}, + { .compatible = "fsl,imx53-ldb", (unsigned long)&imx_ldb_data_imx53}, + { /* sentinel */ } +}; + +static struct driver_d imx_ldb_driver = { + .probe = imx_ldb_probe, + .of_compatible = imx_ldb_dt_ids, + .name = "imx-ldb", +}; +device_platform_driver(imx_ldb_driver); + +MODULE_DESCRIPTION("i.MX LVDS display driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3