summaryrefslogtreecommitdiff
path: root/drivers/video/nxp/imx/dw_dsi_imx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/nxp/imx/dw_dsi_imx.c')
-rw-r--r--drivers/video/nxp/imx/dw_dsi_imx.c440
1 files changed, 440 insertions, 0 deletions
diff --git a/drivers/video/nxp/imx/dw_dsi_imx.c b/drivers/video/nxp/imx/dw_dsi_imx.c
new file mode 100644
index 0000000000..6bdd382621
--- /dev/null
+++ b/drivers/video/nxp/imx/dw_dsi_imx.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2022 NXP
+ *
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dsi_host.h>
+#include <mipi_dsi.h>
+#include <panel.h>
+#include <reset.h>
+#include <video.h>
+#include <video_bridge.h>
+#include <video_link.h>
+#include <asm/io.h>
+#include <asm/arch/gpio.h>
+#include <dm/device-internal.h>
+#include <linux/iopoll.h>
+#include <linux/err.h>
+#include <phy-mipi-dphy.h>
+#include <generic-phy.h>
+
+#define MSEC_PER_SEC 1000
+
+struct dw_dsi_imx_priv {
+ struct mipi_dsi_device device;
+ struct udevice *panel;
+ struct udevice *dsi_host;
+
+ struct clk byte_clk;
+
+ struct phy phy;
+ struct phy_configure_opts_mipi_dphy phy_cfg;
+
+ unsigned int lane_mbps; /* per lane */
+ u32 lanes;
+ u32 format;
+ struct display_timing adj;
+};
+
+static int dw_mipi_dsi_imx_phy_init(void *priv_data)
+{
+ struct dw_dsi_imx_priv *dsi = priv_data;
+ int ret;
+
+ ret = generic_phy_init(&dsi->phy);
+ if (ret < 0) {
+ dev_err(dsi->device.dev, "failed to init phy: %d\n", ret);
+ return ret;
+ }
+
+ ret = generic_phy_configure(&dsi->phy, &dsi->phy_cfg);
+ if (ret < 0) {
+ dev_err(dsi->device.dev, "failed to configure phy: %d\n", ret);
+ goto uninit_phy;
+ }
+
+ ret = generic_phy_power_on(&dsi->phy);
+ if (ret < 0) {
+ dev_err(dsi->device.dev, "failed to power on phy: %d\n", ret);
+ goto uninit_phy;
+ }
+
+ return ret;
+
+uninit_phy:
+ generic_phy_exit(&dsi->phy);
+ return ret;
+}
+
+static int
+dw_mipi_dsi_get_lane_mbps(void *priv_data, struct display_timing *timings,
+ u32 lanes, u32 format, unsigned int *lane_mbps)
+{
+ struct dw_dsi_imx_priv *dsi = priv_data;
+ int bpp;
+ int ret;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(format);
+ if (bpp < 0) {
+ dev_dbg(dsi->device.dev,
+ "failed to get bpp for pixel format %d\n",
+ format);
+ return bpp;
+ }
+
+ dsi->lane_mbps = DIV_ROUND_UP((timings->pixelclock.typ / 1000) * (bpp / lanes), MSEC_PER_SEC);
+ *lane_mbps = dsi->lane_mbps;
+
+ debug("lane_mbps %u, bpp %d\n", *lane_mbps, bpp);
+
+ ret = phy_mipi_dphy_get_default_config(timings->pixelclock.typ,
+ bpp, lanes,
+ &dsi->phy_cfg);
+ if (ret < 0) {
+ dev_dbg(dsi->device.dev, "failed to get default phy cfg %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct hstt {
+ unsigned int maxfreq;
+ struct mipi_dsi_phy_timing timing;
+};
+
+#define HSTT(_maxfreq, _c_lp2hs, _c_hs2lp, _d_lp2hs, _d_hs2lp) \
+{ \
+ .maxfreq = (_maxfreq), \
+ .timing = { \
+ .clk_lp2hs = (_c_lp2hs), \
+ .clk_hs2lp = (_c_hs2lp), \
+ .data_lp2hs = (_d_lp2hs), \
+ .data_hs2lp = (_d_hs2lp), \
+ } \
+}
+
+/* Table A-4 High-Speed Transition Times */
+struct hstt hstt_table[] = {
+ HSTT(80, 21, 17, 15, 10),
+ HSTT(90, 23, 17, 16, 10),
+ HSTT(100, 22, 17, 16, 10),
+ HSTT(110, 25, 18, 17, 11),
+ HSTT(120, 26, 20, 18, 11),
+ HSTT(130, 27, 19, 19, 11),
+ HSTT(140, 27, 19, 19, 11),
+ HSTT(150, 28, 20, 20, 12),
+ HSTT(160, 30, 21, 22, 13),
+ HSTT(170, 30, 21, 23, 13),
+ HSTT(180, 31, 21, 23, 13),
+ HSTT(190, 32, 22, 24, 13),
+ HSTT(205, 35, 22, 25, 13),
+ HSTT(220, 37, 26, 27, 15),
+ HSTT(235, 38, 28, 27, 16),
+ HSTT(250, 41, 29, 30, 17),
+ HSTT(275, 43, 29, 32, 18),
+ HSTT(300, 45, 32, 35, 19),
+ HSTT(325, 48, 33, 36, 18),
+ HSTT(350, 51, 35, 40, 20),
+ HSTT(400, 59, 37, 44, 21),
+ HSTT(450, 65, 40, 49, 23),
+ HSTT(500, 71, 41, 54, 24),
+ HSTT(550, 77, 44, 57, 26),
+ HSTT(600, 82, 46, 64, 27),
+ HSTT(650, 87, 48, 67, 28),
+ HSTT(700, 94, 52, 71, 29),
+ HSTT(750, 99, 52, 75, 31),
+ HSTT(800, 105, 55, 82, 32),
+ HSTT(850, 110, 58, 85, 32),
+ HSTT(900, 115, 58, 88, 35),
+ HSTT(950, 120, 62, 93, 36),
+ HSTT(1000, 128, 63, 99, 38),
+ HSTT(1050, 132, 65, 102, 38),
+ HSTT(1100, 138, 67, 106, 39),
+ HSTT(1150, 146, 69, 112, 42),
+ HSTT(1200, 151, 71, 117, 43),
+ HSTT(1250, 153, 74, 120, 45),
+ HSTT(1300, 160, 73, 124, 46),
+ HSTT(1350, 165, 76, 130, 47),
+ HSTT(1400, 172, 78, 134, 49),
+ HSTT(1450, 177, 80, 138, 49),
+ HSTT(1500, 183, 81, 143, 52),
+ HSTT(1550, 191, 84, 147, 52),
+ HSTT(1600, 194, 85, 152, 52),
+ HSTT(1650, 201, 86, 155, 53),
+ HSTT(1700, 208, 88, 161, 53),
+ HSTT(1750, 212, 89, 165, 53),
+ HSTT(1800, 220, 90, 171, 54),
+ HSTT(1850, 223, 92, 175, 54),
+ HSTT(1900, 231, 91, 180, 55),
+ HSTT(1950, 236, 95, 185, 56),
+ HSTT(2000, 243, 97, 190, 56),
+ HSTT(2050, 248, 99, 194, 58),
+ HSTT(2100, 252, 100, 199, 59),
+ HSTT(2150, 259, 102, 204, 61),
+ HSTT(2200, 266, 105, 210, 62),
+ HSTT(2250, 269, 109, 213, 63),
+ HSTT(2300, 272, 109, 217, 65),
+ HSTT(2350, 281, 112, 225, 66),
+ HSTT(2400, 283, 115, 226, 66),
+ HSTT(2450, 282, 115, 226, 67),
+ HSTT(2500, 281, 118, 227, 67),
+};
+
+static int
+dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps,
+ struct mipi_dsi_phy_timing *timing)
+{
+ struct dw_dsi_imx_priv *dsi = priv_data;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hstt_table); i++)
+ if (lane_mbps <= hstt_table[i].maxfreq)
+ break;
+
+ if (i == ARRAY_SIZE(hstt_table))
+ i--;
+
+ *timing = hstt_table[i].timing;
+
+ dev_dbg(dsi->device.dev, "get phy timing for %u <= %u (lane_mbps)\n",
+ lane_mbps, hstt_table[i].maxfreq);
+
+ return 0;
+}
+
+static const struct mipi_dsi_phy_ops dsi_imx_phy_ops = {
+ .init = dw_mipi_dsi_imx_phy_init,
+ .get_lane_mbps = dw_mipi_dsi_get_lane_mbps,
+ .get_timing = dw_mipi_dsi_phy_get_timing,
+};
+
+static bool dw_dsi_imx_hcomponents_need_fixup(struct dw_dsi_imx_priv *dsi,
+ int bpp,
+ struct display_timing *timings)
+{
+ int htotal = timings->hactive.typ + timings->hfront_porch.typ +
+ timings->hback_porch.typ + timings->hsync_len.typ;
+ int hsa = timings->hsync_len.typ;
+ int hbp = timings->hback_porch.typ;
+ int divisor = dsi->lanes * 8;
+
+ /*
+ * It appears that (hcomponent * bpp) / (8 * lanes)
+ * should be no remainder.
+ */
+ return !!((htotal * bpp) % divisor) ||
+ !!((hsa * bpp) % divisor) ||
+ !!((hbp * bpp) % divisor);
+}
+
+static int dw_dsi_imx_fixup_hcomponent(struct dw_dsi_imx_priv *dsi,
+ int bpp, int component)
+{
+ int divisor, i;
+
+ divisor = dsi->lanes * 8;
+
+ for (i = 0; i < divisor; i++) {
+ if ((bpp * (component + i)) % divisor == 0) {
+ component += i;
+ break;
+ }
+ }
+
+ return component;
+}
+
+static void dw_dsi_imx_fixup_hcomponents(struct dw_dsi_imx_priv *dsi,
+ int bpp,
+ struct display_timing *timings,
+ struct display_timing *adj)
+{
+ int hfp = timings->hfront_porch.typ;
+ int hsa = timings->hsync_len.typ;
+ int hbp = timings->hback_porch.typ;
+
+ adj->hfront_porch.typ = dw_dsi_imx_fixup_hcomponent(dsi, bpp, hfp);
+ adj->hsync_len.typ = dw_dsi_imx_fixup_hcomponent(dsi, bpp, hsa);
+ adj->hback_porch.typ = dw_dsi_imx_fixup_hcomponent(dsi, bpp, hbp);
+}
+
+static int dw_dsi_imx_attach(struct udevice *dev)
+{
+ struct dw_dsi_imx_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ struct mipi_dsi_panel_plat *mplat;
+ struct display_timing timings;
+ int ret, bpp;
+
+ priv->panel = video_link_get_next_device(dev);
+ if (!priv->panel ||
+ device_get_uclass_id(priv->panel) != UCLASS_PANEL) {
+ dev_err(dev, "get panel device error\n");
+ return -ENODEV;
+ }
+
+ mplat = dev_get_plat(priv->panel);
+ mplat->device = &priv->device;
+
+ ret = video_link_get_display_timings(&timings);
+ if (ret) {
+ dev_err(dev, "decode display timing error %d\n", ret);
+ return ret;
+ }
+
+ bpp = mipi_dsi_pixel_format_to_bpp(device->format);
+ if (bpp < 0) {
+ dev_err(dev, "failed to get bpp for pixel format %d\n", device->format);
+ return bpp;
+ }
+
+ priv->lanes = device->lanes;
+ priv->format = device->format;
+
+ priv->adj = timings;
+ if (dw_dsi_imx_hcomponents_need_fixup(priv, bpp, &timings))
+ dw_dsi_imx_fixup_hcomponents(priv, bpp, &timings, &priv->adj);
+
+ ret = uclass_get_device(UCLASS_DSI_HOST, 0, &priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "No video dsi host detected %d\n", ret);
+ return ret;
+ }
+
+ ret = dsi_host_init(priv->dsi_host, device, &priv->adj,
+ 4,
+ &dsi_imx_phy_ops);
+ if (ret) {
+ dev_err(dev, "failed to initialize mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dw_dsi_imx_set_backlight(struct udevice *dev, int percent)
+{
+ struct dw_dsi_imx_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = panel_enable_backlight(priv->panel);
+ if (ret) {
+ dev_err(dev, "panel %s enable backlight error %d\n",
+ priv->panel->name, ret);
+ return ret;
+ }
+
+ ret = dsi_host_enable(priv->dsi_host);
+ if (ret) {
+ dev_err(dev, "failed to enable mipi dsi host\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dw_dsi_imx_check_timing(struct udevice *dev, struct display_timing *timing)
+{
+ struct dw_dsi_imx_priv *priv = dev_get_priv(dev);
+
+ /* Ensure the bridge device attached to panel */
+ if (!priv->panel) {
+ dev_err(dev, "%s No panel device attached\n", __func__);
+ return -ENOTCONN;
+ }
+
+ /* DSI force the Polarities as high */
+ priv->adj.flags &= ~(DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW);
+ priv->adj.flags |= DISPLAY_FLAGS_HSYNC_HIGH | DISPLAY_FLAGS_VSYNC_HIGH;
+
+ *timing = priv->adj;
+
+ return 0;
+}
+
+static int dw_dsi_imx_probe(struct udevice *dev)
+{
+ struct dw_dsi_imx_priv *priv = dev_get_priv(dev);
+ struct mipi_dsi_device *device = &priv->device;
+ int ret;
+
+ device->dev = dev;
+
+ ret = clk_get_by_name(device->dev, "byte", &priv->byte_clk);
+ if (ret) {
+ dev_err(dev, "byte clock get error %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_enable(&priv->byte_clk);
+ if (ret) {
+ dev_err(dev, "byte clock enable error %d\n", ret);
+ return ret;
+ }
+
+ ret = generic_phy_get_by_name(dev, "dphy", &priv->phy);
+ if (ret) {
+ dev_err(dev, "failed to get phy: %d\n", ret);
+ clk_disable(&priv->byte_clk);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int dw_dsi_imx_remove(struct udevice *dev)
+{
+ struct dw_dsi_imx_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (priv->panel)
+ device_remove(priv->panel, DM_REMOVE_NORMAL);
+
+ ret = dsi_host_disable(priv->dsi_host);
+ if (ret < 0 && ret != -ENOSYS)
+ dev_err(dev, "failed to disable mipi dsi host\n");
+
+ ret = generic_phy_power_off(&priv->phy);
+ if (ret < 0)
+ dev_err(dev, "failed to power off phy: %d\n", ret);
+
+ ret = generic_phy_exit(&priv->phy);
+ if (ret < 0)
+ dev_err(dev, "failed to exit phy: %d\n", ret);
+
+ device_remove(priv->phy.dev, DM_REMOVE_NORMAL);
+
+ ret = clk_disable(&priv->byte_clk);
+ if (ret)
+ dev_err(dev, "byte clock disable error %d\n", ret);
+
+ return 0;
+}
+
+struct video_bridge_ops dw_dsi_imx_ops = {
+ .attach = dw_dsi_imx_attach,
+ .set_backlight = dw_dsi_imx_set_backlight,
+ .check_timing = dw_dsi_imx_check_timing,
+};
+
+static const struct udevice_id dw_dsi_imx_ids[] = {
+ { .compatible = "fsl,imx93-mipi-dsi" },
+ { }
+};
+
+U_BOOT_DRIVER(dw_dsi_imx) = {
+ .name = "dw_dsi_imx",
+ .id = UCLASS_VIDEO_BRIDGE,
+ .of_match = dw_dsi_imx_ids,
+ .bind = dm_scan_fdt_dev,
+ .remove = dw_dsi_imx_remove,
+ .probe = dw_dsi_imx_probe,
+ .ops = &dw_dsi_imx_ops,
+ .priv_auto = sizeof(struct dw_dsi_imx_priv),
+};