Adds display parameter initialisation, display power up/down and
waveform loading

Signed-off-by: Andreas Kemnade <andr...@kemnade.info>
---
 drivers/gpu/drm/mxc-epdc/Makefile        |   2 +-
 drivers/gpu/drm/mxc-epdc/epdc_hw.c       | 495 +++++++++++++++++++++++
 drivers/gpu/drm/mxc-epdc/epdc_hw.h       |   8 +
 drivers/gpu/drm/mxc-epdc/epdc_waveform.c | 189 +++++++++
 drivers/gpu/drm/mxc-epdc/epdc_waveform.h |   7 +
 drivers/gpu/drm/mxc-epdc/mxc_epdc.h      |  81 ++++
 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c  |  94 +++++
 7 files changed, 875 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_hw.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_hw.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_waveform.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_waveform.h

diff --git a/drivers/gpu/drm/mxc-epdc/Makefile 
b/drivers/gpu/drm/mxc-epdc/Makefile
index a47ced72b7f6..0263ef2bf0db 100644
--- a/drivers/gpu/drm/mxc-epdc/Makefile
+++ b/drivers/gpu/drm/mxc-epdc/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-mxc_epdc_drm-y := mxc_epdc_drv.o
+mxc_epdc_drm-y := mxc_epdc_drv.o epdc_hw.o epdc_waveform.o
 
 obj-$(CONFIG_DRM_MXC_EPDC) += mxc_epdc_drm.o
 
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_hw.c 
b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
new file mode 100644
index 000000000000..a74cbd237e0d
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2022 Andreas Kemnade
+//
+/*
+ * based on the EPDC framebuffer driver
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+#include "mxc_epdc.h"
+#include "epdc_regs.h"
+#include "epdc_hw.h"
+#include "epdc_waveform.h"
+
+void mxc_epdc_powerup(struct mxc_epdc *priv)
+{
+       int ret = 0;
+
+       mutex_lock(&priv->power_mutex);
+
+       /*
+        * If power down request is pending, clear
+        * powering_down to cancel the request.
+        */
+       if (priv->powering_down)
+               priv->powering_down = false;
+
+       if (priv->powered) {
+               mutex_unlock(&priv->power_mutex);
+               return;
+       }
+
+       dev_dbg(priv->drm.dev, "EPDC Powerup\n");
+
+       priv->updates_active = true;
+
+       /* Enable the v3p3 regulator */
+       ret = regulator_enable(priv->v3p3_regulator);
+       if (IS_ERR((void *)ret)) {
+               dev_err(priv->drm.dev,
+                       "Unable to enable V3P3 regulator. err = 0x%x\n",
+                       ret);
+               mutex_unlock(&priv->power_mutex);
+               return;
+       }
+
+       usleep_range(1000, 2000);
+
+       pm_runtime_get_sync(priv->drm.dev);
+
+       /* Enable clocks to EPDC */
+       clk_prepare_enable(priv->epdc_clk_axi);
+       clk_prepare_enable(priv->epdc_clk_pix);
+
+       epdc_write(priv, EPDC_CTRL_CLEAR, EPDC_CTRL_CLKGATE);
+
+       /* Enable power to the EPD panel */
+       ret = regulator_enable(priv->display_regulator);
+       if (IS_ERR((void *)ret)) {
+               dev_err(priv->drm.dev,
+                       "Unable to enable DISPLAY regulator. err = 0x%x\n",
+                       ret);
+               mutex_unlock(&priv->power_mutex);
+               return;
+       }
+
+       ret = regulator_enable(priv->vcom_regulator);
+       if (IS_ERR((void *)ret)) {
+               dev_err(priv->drm.dev,
+                       "Unable to enable VCOM regulator. err = 0x%x\n",
+                       ret);
+               mutex_unlock(&priv->power_mutex);
+               return;
+       }
+
+       priv->powered = true;
+
+       mutex_unlock(&priv->power_mutex);
+}
+
+void mxc_epdc_powerdown(struct mxc_epdc *priv)
+{
+       mutex_lock(&priv->power_mutex);
+
+       /* If powering_down has been cleared, a powerup
+        * request is pre-empting this powerdown request.
+        */
+       if (!priv->powering_down
+               || (!priv->powered)) {
+               mutex_unlock(&priv->power_mutex);
+               return;
+       }
+
+       dev_dbg(priv->drm.dev, "EPDC Powerdown\n");
+
+       /* Disable power to the EPD panel */
+       regulator_disable(priv->vcom_regulator);
+       regulator_disable(priv->display_regulator);
+
+       /* Disable clocks to EPDC */
+       epdc_write(priv, EPDC_CTRL_SET, EPDC_CTRL_CLKGATE);
+       clk_disable_unprepare(priv->epdc_clk_pix);
+       clk_disable_unprepare(priv->epdc_clk_axi);
+
+       pm_runtime_put_sync_suspend(priv->drm.dev);
+
+       /* turn off the V3p3 */
+       regulator_disable(priv->v3p3_regulator);
+
+       priv->powered = false;
+       priv->powering_down = false;
+
+       if (priv->wait_for_powerdown) {
+               priv->wait_for_powerdown = false;
+               complete(&priv->powerdown_compl);
+       }
+
+       mutex_unlock(&priv->power_mutex);
+}
+
+static void epdc_set_horizontal_timing(struct mxc_epdc *priv, u32 horiz_start,
+                                      u32 horiz_end,
+                                      u32 hsync_width, u32 hsync_line_length)
+{
+       u32 reg_val =
+           ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) &
+            EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK)
+           | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) &
+              EPDC_TCE_HSCAN1_LINE_SYNC_MASK);
+       epdc_write(priv, EPDC_TCE_HSCAN1, reg_val);
+
+       reg_val =
+           ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) &
+            EPDC_TCE_HSCAN2_LINE_BEGIN_MASK)
+           | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) &
+              EPDC_TCE_HSCAN2_LINE_END_MASK);
+       epdc_write(priv, EPDC_TCE_HSCAN2, reg_val);
+}
+
+static void epdc_set_vertical_timing(struct mxc_epdc *priv,
+                                    u32 vert_start,
+                                    u32 vert_end,
+                                    u32 vsync_width)
+{
+       u32 reg_val =
+           ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) &
+            EPDC_TCE_VSCAN_FRAME_BEGIN_MASK)
+           | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) &
+              EPDC_TCE_VSCAN_FRAME_END_MASK)
+           | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) &
+              EPDC_TCE_VSCAN_FRAME_SYNC_MASK);
+       epdc_write(priv, EPDC_TCE_VSCAN, reg_val);
+}
+
+static inline void epdc_set_screen_res(struct mxc_epdc *priv,
+                                      u32 width, u32 height)
+{
+       u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width;
+
+       epdc_write(priv, EPDC_RES, val);
+}
+
+
+void epdc_init_settings(struct mxc_epdc *priv, struct drm_display_mode *m)
+{
+       u32 reg_val;
+       int num_ce;
+       int i;
+
+       /* Enable clocks to access EPDC regs */
+       clk_prepare_enable(priv->epdc_clk_axi);
+       clk_prepare_enable(priv->epdc_clk_pix);
+
+       /* Reset */
+       epdc_write(priv, EPDC_CTRL_SET, EPDC_CTRL_SFTRST);
+       while (!(epdc_read(priv, EPDC_CTRL) & EPDC_CTRL_CLKGATE))
+               ;
+       epdc_write(priv, EPDC_CTRL_CLEAR, EPDC_CTRL_SFTRST);
+
+       /* Enable clock gating (clear to enable) */
+       epdc_write(priv, EPDC_CTRL_CLEAR, EPDC_CTRL_CLKGATE);
+       while (epdc_read(priv, EPDC_CTRL) & (EPDC_CTRL_SFTRST | 
EPDC_CTRL_CLKGATE))
+               ;
+
+       /* EPDC_CTRL */
+       reg_val = epdc_read(priv, EPDC_CTRL);
+       reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK;
+       reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP;
+       reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK;
+       reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP;
+       epdc_write(priv, EPDC_CTRL_SET, reg_val);
+
+       /* EPDC_FORMAT - 2bit TFT and buf_pix_fmt Buf pixel format */
+       reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT
+               | priv->buf_pix_fmt
+           | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) &
+              EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK);
+       epdc_write(priv, EPDC_FORMAT, reg_val);
+       if (priv->rev >= 30) {
+               if (priv->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N) {
+                       epdc_write(priv, EPDC_WB_FIELD2, 0xc554);
+                       epdc_write(priv, EPDC_WB_FIELD1, 0xa004);
+               } else {
+                       epdc_write(priv, EPDC_WB_FIELD2, 0xc443);
+                       epdc_write(priv, EPDC_WB_FIELD1, 0xa003);
+               }
+       }
+
+       /* EPDC_FIFOCTRL (disabled) */
+       reg_val =
+           ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) &
+            EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK)
+           | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) &
+              EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK)
+           | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) &
+              EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK);
+       epdc_write(priv, EPDC_FIFOCTRL, reg_val);
+
+       /* EPDC_TEMP - Use default temp to get index */
+       epdc_write(priv, EPDC_TEMP,
+                  mxc_epdc_fb_get_temp_index(priv, TEMP_USE_AMBIENT));
+
+       /* EPDC_RES */
+       epdc_set_screen_res(priv, m->hdisplay, m->vdisplay);
+
+       /* EPDC_AUTOWV_LUT */
+       /* Initialize all auto-wavefrom look-up values to 2 - GC16 */
+       for (i = 0; i < 8; i++)
+               epdc_write(priv, EPDC_AUTOWV_LUT,
+                       (2 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+                       (i << EPDC_AUTOWV_LUT_ADDR_OFFSET));
+
+       /*
+        * EPDC_TCE_CTRL
+        * VSCAN_HOLDOFF = 4
+        * VCOM_MODE = MANUAL
+        * VCOM_VAL = 0
+        * DDR_MODE = DISABLED
+        * LVDS_MODE_CE = DISABLED
+        * LVDS_MODE = DISABLED
+        * DUAL_SCAN = DISABLED
+        * SDDO_WIDTH = 8bit
+        * PIXELS_PER_SDCLK = 4
+        */
+       reg_val =
+           ((priv->imx_mode.vscan_holdoff << 
EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) &
+            EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK)
+           | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4;
+       epdc_write(priv, EPDC_TCE_CTRL, reg_val);
+
+       /* EPDC_TCE_HSCAN */
+       epdc_set_horizontal_timing(priv, m->hsync_start - m->hdisplay,
+                                  m->htotal - m->hsync_end,
+                                  m->hsync_end - m->hsync_start,
+                                  m->hsync_end - m->hsync_start);
+
+       /* EPDC_TCE_VSCAN */
+       epdc_set_vertical_timing(priv, m->vsync_start - m->vdisplay,
+                                m->vtotal - m->vsync_end,
+                                m->vsync_end - m->vsync_start);
+
+       /* EPDC_TCE_OE */
+       reg_val =
+           ((priv->imx_mode.sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) &
+            EPDC_TCE_OE_SDOED_WIDTH_MASK)
+           | ((priv->imx_mode.sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) &
+              EPDC_TCE_OE_SDOED_DLY_MASK)
+           | ((priv->imx_mode.sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) &
+              EPDC_TCE_OE_SDOEZ_WIDTH_MASK)
+           | ((priv->imx_mode.sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) &
+              EPDC_TCE_OE_SDOEZ_DLY_MASK);
+       epdc_write(priv, EPDC_TCE_OE, reg_val);
+
+       /* EPDC_TCE_TIMING1 */
+       epdc_write(priv, EPDC_TCE_TIMING1, 0x0);
+
+       /* EPDC_TCE_TIMING2 */
+       reg_val =
+           ((priv->imx_mode.gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) 
&
+            EPDC_TCE_TIMING2_GDCLK_HP_MASK)
+           | ((priv->imx_mode.gdsp_offs << 
EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) &
+              EPDC_TCE_TIMING2_GDSP_OFFSET_MASK);
+       epdc_write(priv, EPDC_TCE_TIMING2, reg_val);
+
+       /* EPDC_TCE_TIMING3 */
+       reg_val =
+           ((priv->imx_mode.gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) &
+            EPDC_TCE_TIMING3_GDOE_OFFSET_MASK)
+           | ((priv->imx_mode.gdclk_offs << 
EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) &
+              EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK);
+       epdc_write(priv, EPDC_TCE_TIMING3, reg_val);
+
+       /*
+        * EPDC_TCE_SDCFG
+        * SDCLK_HOLD = 1
+        * SDSHR = 1
+        * NUM_CE = 1
+        * SDDO_REFORMAT = FLIP_PIXELS
+        * SDDO_INVERT = DISABLED
+        * PIXELS_PER_CE = display horizontal resolution
+        */
+       num_ce = priv->imx_mode.num_ce;
+       if (num_ce == 0)
+               num_ce = 1;
+       reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR
+           | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) &
+              EPDC_TCE_SDCFG_NUM_CE_MASK)
+           | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS
+           | ((priv->epdc_mem_width/num_ce << 
EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) &
+              EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK);
+       epdc_write(priv, EPDC_TCE_SDCFG, reg_val);
+
+       /*
+        * EPDC_TCE_GDCFG
+        * GDRL = 1
+        * GDOE_MODE = 0;
+        * GDSP_MODE = 0;
+        */
+       reg_val = EPDC_TCE_SDCFG_GDRL;
+       epdc_write(priv, EPDC_TCE_GDCFG, reg_val);
+
+       /*
+        * EPDC_TCE_POLARITY
+        * SDCE_POL = ACTIVE LOW
+        * SDLE_POL = ACTIVE HIGH
+        * SDOE_POL = ACTIVE HIGH
+        * GDOE_POL = ACTIVE HIGH
+        * GDSP_POL = ACTIVE LOW
+        */
+       reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH
+           | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH
+           | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH;
+       epdc_write(priv, EPDC_TCE_POLARITY, reg_val);
+
+       /* EPDC_IRQ_MASK */
+       epdc_write(priv, EPDC_IRQ_MASK, EPDC_IRQ_TCE_UNDERRUN_IRQ);
+
+       /*
+        * EPDC_GPIO
+        * PWRCOM = ?
+        * PWRCTRL = ?
+        * BDR = ?
+        */
+       reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK)
+           | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK);
+       epdc_write(priv, EPDC_GPIO, reg_val);
+
+       epdc_write(priv, EPDC_WVADDR, priv->waveform_buffer_phys);
+       epdc_write(priv, EPDC_WB_ADDR, priv->working_buffer_phys);
+       if (priv->rev >= 30)
+               epdc_write(priv, EPDC_WB_ADDR_TCE_V3,
+                          priv->working_buffer_phys);
+       else
+               epdc_write(priv, EPDC_WB_ADDR_TCE,
+                          priv->working_buffer_phys);
+
+       /* Disable clock */
+       clk_disable_unprepare(priv->epdc_clk_axi);
+       clk_disable_unprepare(priv->epdc_clk_pix);
+}
+
+void mxc_epdc_init_sequence(struct mxc_epdc *priv, struct drm_display_mode *m)
+{
+       /* Initialize EPDC, passing pointer to EPDC registers */
+       struct clk *epdc_parent;
+       unsigned long rounded_parent_rate, epdc_pix_rate,
+                       rounded_pix_clk, target_pix_clk;
+
+       /* Enable pix clk for EPDC */
+       clk_prepare_enable(priv->epdc_clk_axi);
+
+       target_pix_clk = m->clock * 1000;
+       rounded_pix_clk = clk_round_rate(priv->epdc_clk_pix, target_pix_clk);
+
+       if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+               (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) {
+               /* Can't get close enough without changing parent clk */
+               epdc_parent = clk_get_parent(priv->epdc_clk_pix);
+               rounded_parent_rate = clk_round_rate(epdc_parent, 
target_pix_clk);
+
+               epdc_pix_rate = target_pix_clk;
+               while (epdc_pix_rate < rounded_parent_rate)
+                       epdc_pix_rate *= 2;
+               clk_set_rate(epdc_parent, epdc_pix_rate);
+
+               rounded_pix_clk = clk_round_rate(priv->epdc_clk_pix, 
target_pix_clk);
+               if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+                       (rounded_pix_clk <= target_pix_clk - 
target_pix_clk/100)))
+                       /* Still can't get a good clock, provide warning */
+                       dev_err(priv->drm.dev,
+                               "Unable to get an accurate EPDC pix clk desired 
= %lu, actual = %lu\n",
+                               target_pix_clk,
+                               rounded_pix_clk);
+       }
+
+       clk_set_rate(priv->epdc_clk_pix, rounded_pix_clk);
+       clk_prepare_enable(priv->epdc_clk_pix);
+
+       epdc_init_settings(priv, m);
+
+       priv->in_init = true;
+       mxc_epdc_powerup(priv);
+       /* Force power down event */
+       priv->powering_down = true;
+       mxc_epdc_powerdown(priv);
+       priv->updates_active = false;
+
+       /* Disable clocks */
+       clk_disable_unprepare(priv->epdc_clk_axi);
+       clk_disable_unprepare(priv->epdc_clk_pix);
+       priv->hw_ready = true;
+       priv->hw_initializing = false;
+
+}
+
+int mxc_epdc_init_hw(struct mxc_epdc *priv)
+{
+       struct pinctrl *pinctrl;
+       const char *thermal = NULL;
+       u32 val;
+
+       /* get pmic regulators */
+       priv->display_regulator = devm_regulator_get(priv->drm.dev, "DISPLAY");
+       if (IS_ERR(priv->display_regulator))
+               return dev_err_probe(priv->drm.dev, 
PTR_ERR(priv->display_regulator),
+                                    "Unable to get display PMIC regulator\n");
+
+       priv->vcom_regulator = devm_regulator_get(priv->drm.dev, "VCOM");
+       if (IS_ERR(priv->vcom_regulator))
+               return dev_err_probe(priv->drm.dev, 
PTR_ERR(priv->vcom_regulator),
+                                    "Unable to get VCOM regulator\n");
+
+       priv->v3p3_regulator = devm_regulator_get(priv->drm.dev, "V3P3");
+       if (IS_ERR(priv->v3p3_regulator))
+               return dev_err_probe(priv->drm.dev, 
PTR_ERR(priv->v3p3_regulator),
+                                    "Unable to get V3P3 regulator\n");
+
+       of_property_read_string(priv->drm.dev->of_node,
+                               "epd-thermal-zone", &thermal);
+       if (thermal) {
+               priv->thermal = thermal_zone_get_zone_by_name(thermal);
+               if (IS_ERR(priv->thermal))
+                       return dev_err_probe(priv->drm.dev, 
PTR_ERR(priv->thermal),
+                                            "unable to get thermal");
+       }
+       priv->iobase = 
devm_platform_get_and_ioremap_resource(to_platform_device(priv->drm.dev),
+                                                             0, NULL);
+       if (priv->iobase == NULL)
+               return -ENOMEM;
+
+       priv->epdc_clk_axi = devm_clk_get(priv->drm.dev, "axi");
+       if (IS_ERR(priv->epdc_clk_axi))
+               return dev_err_probe(priv->drm.dev, PTR_ERR(priv->epdc_clk_axi),
+                                    "Unable to get EPDC AXI clk\n");
+
+       priv->epdc_clk_pix = devm_clk_get(priv->drm.dev, "pix");
+       if (IS_ERR(priv->epdc_clk_pix))
+               return dev_err_probe(priv->drm.dev, PTR_ERR(priv->epdc_clk_pix),
+                                    "Unable to get EPDC pix clk\n");
+
+       clk_prepare_enable(priv->epdc_clk_axi);
+       val = epdc_read(priv, EPDC_VERSION);
+       clk_disable_unprepare(priv->epdc_clk_axi);
+       priv->rev = ((val & EPDC_VERSION_MAJOR_MASK) >>
+                               EPDC_VERSION_MAJOR_OFFSET) * 10
+                       + ((val & EPDC_VERSION_MINOR_MASK) >>
+                               EPDC_VERSION_MINOR_OFFSET);
+       dev_dbg(priv->drm.dev, "EPDC version = %d\n", priv->rev);
+
+       if (priv->rev <= 20) {
+               dev_err(priv->drm.dev, "Unsupported version (%d)\n", priv->rev);
+               return -ENODEV;
+       }
+
+       /* Initialize EPDC pins */
+       pinctrl = devm_pinctrl_get_select_default(priv->drm.dev);
+       if (IS_ERR(pinctrl)) {
+               dev_err(priv->drm.dev, "can't get/select pinctrl\n");
+               return PTR_ERR(pinctrl);
+       }
+
+       mutex_init(&priv->power_mutex);
+
+       return 0;
+}
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_hw.h 
b/drivers/gpu/drm/mxc-epdc/epdc_hw.h
new file mode 100644
index 000000000000..dbf1f0d1e23e
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_hw.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2022 Andreas Kemnade */
+void mxc_epdc_init_sequence(struct mxc_epdc *priv, struct drm_display_mode *m);
+int mxc_epdc_init_hw(struct mxc_epdc *priv);
+
+void mxc_epdc_powerup(struct mxc_epdc *priv);
+void mxc_epdc_powerdown(struct mxc_epdc *priv);
+
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_waveform.c 
b/drivers/gpu/drm/mxc-epdc/epdc_waveform.c
new file mode 100644
index 000000000000..4f2f199722d5
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_waveform.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2022 Andreas Kemnade
+//
+/*
+ * based on the EPDC framebuffer driver
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include "mxc_epdc.h"
+
+#define DEFAULT_TEMP_INDEX      0
+#define DEFAULT_TEMP            20 /* room temp in deg Celsius */
+
+struct waveform_data_header {
+       unsigned int wi0;
+       unsigned int wi1;
+       unsigned int wi2;
+       unsigned int wi3;
+       unsigned int wi4;
+       unsigned int wi5;
+       unsigned int wi6;
+       unsigned int xwia:24;
+       unsigned int cs1:8;
+       unsigned int wmta:24;
+       unsigned int fvsn:8;
+       unsigned int luts:8;
+       unsigned int mc:8;
+       unsigned int trc:8;
+       unsigned int reserved0_0:8;
+       unsigned int eb:8;
+       unsigned int sb:8;
+       unsigned int reserved0_1:8;
+       unsigned int reserved0_2:8;
+       unsigned int reserved0_3:8;
+       unsigned int reserved0_4:8;
+       unsigned int reserved0_5:8;
+       unsigned int cs2:8;
+};
+
+struct mxcfb_waveform_data_file {
+       struct waveform_data_header wdh;
+       u32 *data;      /* Temperature Range Table + Waveform Data */
+};
+
+void mxc_epdc_set_update_waveform(struct mxc_epdc *priv,
+                                 struct mxcfb_waveform_modes *wv_modes)
+{
+       u32 val;
+
+       /* Configure the auto-waveform look-up table based on waveform modes */
+
+       /* Entry 1 = DU, 2 = GC4, 3 = GC8, etc. */
+       val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+               (0 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+       epdc_write(priv, EPDC_AUTOWV_LUT, val);
+       val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+               (1 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+       epdc_write(priv, EPDC_AUTOWV_LUT, val);
+       val = (wv_modes->mode_gc4 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+               (2 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+       epdc_write(priv, EPDC_AUTOWV_LUT, val);
+       val = (wv_modes->mode_gc8 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+               (3 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+       epdc_write(priv, EPDC_AUTOWV_LUT, val);
+       val = (wv_modes->mode_gc16 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+               (4 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+       epdc_write(priv, EPDC_AUTOWV_LUT, val);
+       val = (wv_modes->mode_gc32 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+               (5 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+       epdc_write(priv, EPDC_AUTOWV_LUT, val);
+}
+
+int mxc_epdc_fb_get_temp_index(struct mxc_epdc *priv, int temp)
+{
+       int i;
+       int index = -1;
+
+       if (temp == TEMP_USE_AMBIENT) {
+               if (priv->thermal) {
+                       if (thermal_zone_get_temp(priv->thermal, &temp)) {
+                               dev_err(priv->drm.dev,
+                                       "reading temperature failed");
+                               return DEFAULT_TEMP_INDEX;
+                       }
+                       temp /= 1000;
+               } else
+                       temp = DEFAULT_TEMP;
+       }
+
+       if (priv->trt_entries == 0) {
+               dev_err(priv->drm.dev,
+                       "No TRT exists...using default temp index\n");
+               return DEFAULT_TEMP_INDEX;
+       }
+
+       /* Search temperature ranges for a match */
+       for (i = 0; i < priv->trt_entries - 1; i++) {
+               if ((temp >= priv->temp_range_bounds[i])
+                       && (temp < priv->temp_range_bounds[i+1])) {
+                       index = i;
+                       break;
+               }
+       }
+
+       if (index < 0) {
+               dev_err(priv->drm.dev,
+                       "No TRT index match...using lowest/highest\n");
+               if (temp < priv->temp_range_bounds[0]) {
+                       dev_dbg(priv->drm.dev, "temperature < minimum range\n");
+                       return 0;
+               }
+
+               if (temp >= priv->temp_range_bounds[priv->trt_entries-1]) {
+                       dev_dbg(priv->drm.dev, "temperature >= maximum 
range\n");
+                       return priv->trt_entries-1;
+               }
+
+               return DEFAULT_TEMP_INDEX;
+       }
+
+       dev_dbg(priv->drm.dev, "Using temperature index %d\n", index);
+
+       return index;
+}
+
+
+
+int mxc_epdc_prepare_waveform(struct mxc_epdc *priv,
+                             const u8 *data, size_t size)
+{
+       const struct mxcfb_waveform_data_file *wv_file;
+       int wv_data_offs;
+       int i;
+
+       priv->wv_modes.mode_init = 0;
+       priv->wv_modes.mode_du = 1;
+       priv->wv_modes.mode_gc4 = 3;
+       priv->wv_modes.mode_gc8 = 2;
+       priv->wv_modes.mode_gc16 = 2;
+       priv->wv_modes.mode_gc32 = 2;
+       priv->wv_modes_update = true;
+
+       wv_file = (struct mxcfb_waveform_data_file *)data;
+
+       /* Get size and allocate temperature range table */
+       priv->trt_entries = wv_file->wdh.trc + 1;
+       priv->temp_range_bounds = devm_kzalloc(priv->drm.dev, 
priv->trt_entries, GFP_KERNEL);
+
+       for (i = 0; i < priv->trt_entries; i++)
+               dev_dbg(priv->drm.dev, "trt entry #%d = 0x%x\n", i, *((u8 
*)&wv_file->data + i));
+
+       /* Copy TRT data */
+       memcpy(priv->temp_range_bounds, &wv_file->data, priv->trt_entries);
+
+       /* Set default temperature index using TRT and room temp */
+       priv->temp_index = mxc_epdc_fb_get_temp_index(priv, DEFAULT_TEMP);
+
+       /* Get offset and size for waveform data */
+       wv_data_offs = sizeof(wv_file->wdh) + priv->trt_entries + 1;
+       priv->waveform_buffer_size = size - wv_data_offs;
+
+       /* Allocate memory for waveform data */
+       priv->waveform_buffer_virt = dmam_alloc_coherent(priv->drm.dev,
+                                               priv->waveform_buffer_size,
+                                               &priv->waveform_buffer_phys,
+                                               GFP_DMA | GFP_KERNEL);
+       if (priv->waveform_buffer_virt == NULL) {
+               dev_err(priv->drm.dev, "Can't allocate mem for waveform!\n");
+               return -ENOMEM;
+       }
+
+       memcpy(priv->waveform_buffer_virt, (u8 *)(data) + wv_data_offs,
+               priv->waveform_buffer_size);
+
+       /* Read field to determine if 4-bit or 5-bit mode */
+       if ((wv_file->wdh.luts & 0xC) == 0x4)
+               priv->buf_pix_fmt = EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N;
+       else
+               priv->buf_pix_fmt = EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N;
+
+       dev_info(priv->drm.dev, "EPDC pix format: %x\n",
+                priv->buf_pix_fmt);
+
+       return 0;
+}
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_waveform.h 
b/drivers/gpu/drm/mxc-epdc/epdc_waveform.h
new file mode 100644
index 000000000000..c5c461b975cb
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_waveform.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2022 Andreas Kemnade */
+int mxc_epdc_fb_get_temp_index(struct mxc_epdc *priv, int temp);
+int mxc_epdc_prepare_waveform(struct mxc_epdc *priv,
+                             const u8 *waveform, size_t size);
+void mxc_epdc_set_update_waveform(struct mxc_epdc *priv,
+                                 struct mxcfb_waveform_modes *wv_modes);
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h 
b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
index c5f5280b574f..f7b1cbc4cc4e 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
@@ -8,6 +8,32 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_connector.h>
 #include <drm/drm_simple_kms_helper.h>
+#include <linux/thermal.h>
+#include "epdc_regs.h"
+
+#define TEMP_USE_AMBIENT                       0x1000
+
+struct mxcfb_waveform_modes {
+       int mode_init;
+       int mode_du;
+       int mode_gc4;
+       int mode_gc8;
+       int mode_gc16;
+       int mode_gc32;
+};
+
+struct imx_epdc_fb_mode {
+       u32 vscan_holdoff;
+       u32 sdoed_width;
+       u32 sdoed_delay;
+       u32 sdoez_width;
+       u32 sdoez_delay;
+       u32 gdclk_hp_offs;
+       u32 gdsp_offs;
+       u32 gdoe_offs;
+       u32 gdclk_offs;
+       u32 num_ce;
+};
 
 struct clk;
 struct regulator;
@@ -16,5 +42,60 @@ struct mxc_epdc {
        struct drm_simple_display_pipe pipe;
        struct drm_connector connector;
        struct display_timing timing;
+       struct imx_epdc_fb_mode imx_mode;
+       void __iomem *iobase;
+       struct completion powerdown_compl;
+       struct clk *epdc_clk_axi;
+       struct clk *epdc_clk_pix;
+       struct regulator *display_regulator;
+       struct regulator *vcom_regulator;
+       struct regulator *v3p3_regulator;
+       struct thermal_zone_device *thermal;
+       int rev;
+
+       dma_addr_t epdc_mem_phys;
+       void *epdc_mem_virt;
+       int epdc_mem_width;
+       int epdc_mem_height;
+       u32 *working_buffer_virt;
+       dma_addr_t working_buffer_phys;
+       u32 working_buffer_size;
+
+       /* waveform related stuff */
+       int trt_entries;
+       int temp_index;
+       u8 *temp_range_bounds;
+       int buf_pix_fmt;
+       struct mxcfb_waveform_modes wv_modes;
+       bool wv_modes_update;
+       u32 *waveform_buffer_virt;
+       dma_addr_t waveform_buffer_phys;
+       u32 waveform_buffer_size;
+
+       struct mutex power_mutex;
+       bool powered;
+       bool powering_down;
+       bool updates_active;
+       int wait_for_powerdown;
+       int pwrdown_delay;
+
+       /* elements related to EPDC updates */
+       int num_luts;
+       int max_num_updates;
+       bool in_init;
+       bool hw_ready;
+       bool hw_initializing;
+       bool waiting_for_idle;
+
 };
 
+static inline u32 epdc_read(struct mxc_epdc *priv, u32 reg)
+{
+       return readl(priv->iobase + reg);
+}
+
+static inline void epdc_write(struct mxc_epdc *priv, u32 reg, u32 data)
+{
+       writel(data, priv->iobase + reg);
+}
+
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c 
b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
index c0b0a3bcdb57..4810e5c5bc6e 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
@@ -25,6 +25,8 @@
 #include <drm/drm_prime.h>
 #include <drm/drm_probe_helper.h>
 #include "mxc_epdc.h"
+#include "epdc_hw.h"
+#include "epdc_waveform.h"
 
 #define DRIVER_NAME "mxc_epdc"
 #define DRIVER_DESC "IMX EPDC"
@@ -122,6 +124,57 @@ int mxc_epdc_output(struct drm_device *drm)
                                 DRM_MODE_CONNECTOR_Unknown);
        if (ret)
                return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "vscan-holdoff",
+                                  &priv->imx_mode.vscan_holdoff);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "sdoed-width",
+                                  &priv->imx_mode.sdoed_width);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "sdoed-delay",
+                                  &priv->imx_mode.sdoed_delay);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "sdoez-width",
+                                  &priv->imx_mode.sdoez_width);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "sdoez-delay",
+                                  &priv->imx_mode.sdoez_delay);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "gdclk-hp-offs",
+                                  &priv->imx_mode.gdclk_hp_offs);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "gdsp-offs",
+                                  &priv->imx_mode.gdsp_offs);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "gdoe-offs",
+                                  &priv->imx_mode.gdoe_offs);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "gdclk-offs",
+                                  &priv->imx_mode.gdclk_offs);
+       if (ret)
+               return ret;
+
+       ret = of_property_read_u32(drm->dev->of_node, "num-ce",
+                                  &priv->imx_mode.num_ce);
+       if (ret)
+               return ret;
+
        ret = of_get_display_timing(drm->dev->of_node, "timing", &priv->timing);
        if (ret)
                return ret;
@@ -137,6 +190,20 @@ static void mxc_epdc_pipe_enable(struct 
drm_simple_display_pipe *pipe,
        struct drm_display_mode *m = &pipe->crtc.state->adjusted_mode;
 
        dev_info(priv->drm.dev, "Mode: %d x %d\n", m->hdisplay, m->vdisplay);
+       priv->epdc_mem_width = m->hdisplay;
+       priv->epdc_mem_height = m->vdisplay;
+       priv->epdc_mem_virt = dma_alloc_wc(priv->drm.dev,
+                                          m->hdisplay * m->vdisplay,
+                                          &priv->epdc_mem_phys, GFP_DMA | 
GFP_KERNEL);
+       priv->working_buffer_size = m->hdisplay * m->vdisplay * 2;
+       priv->working_buffer_virt =
+               dma_alloc_coherent(priv->drm.dev,
+                                  priv->working_buffer_size,
+                                  &priv->working_buffer_phys,
+                                  GFP_DMA | GFP_KERNEL);
+
+       if (priv->working_buffer_virt && priv->epdc_mem_virt)
+               mxc_epdc_init_sequence(priv, m);
 }
 
 static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
@@ -144,6 +211,19 @@ static void mxc_epdc_pipe_disable(struct 
drm_simple_display_pipe *pipe)
        struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
 
        dev_dbg(priv->drm.dev, "pipe disable\n");
+
+       if (priv->epdc_mem_virt) {
+               dma_free_wc(priv->drm.dev, priv->epdc_mem_width * 
priv->epdc_mem_height,
+                           priv->epdc_mem_virt, priv->epdc_mem_phys);
+               priv->epdc_mem_virt = NULL;
+       }
+
+       if (priv->working_buffer_virt) {
+               dma_free_wc(priv->drm.dev, priv->working_buffer_size,
+                           priv->working_buffer_virt,
+                           priv->working_buffer_phys);
+               priv->working_buffer_virt = NULL;
+       }
 }
 
 static void mxc_epdc_pipe_update(struct drm_simple_display_pipe *pipe,
@@ -187,6 +267,7 @@ static struct drm_driver mxc_epdc_driver = {
 static int mxc_epdc_probe(struct platform_device *pdev)
 {
        struct mxc_epdc *priv;
+       const struct firmware *firmware;
        int ret;
 
        priv = devm_drm_dev_alloc(&pdev->dev, &mxc_epdc_driver, struct 
mxc_epdc, drm);
@@ -195,6 +276,19 @@ static int mxc_epdc_probe(struct platform_device *pdev)
 
        platform_set_drvdata(pdev, priv);
 
+       ret = mxc_epdc_init_hw(priv);
+       if (ret)
+               return ret;
+
+       ret = request_firmware(&firmware, "imx/epdc/epdc.fw", priv->drm.dev);
+       if (ret)
+               return ret;
+
+       ret = mxc_epdc_prepare_waveform(priv, firmware->data, firmware->size);
+       release_firmware(firmware);
+       if (ret)
+               return ret;
+
        mxc_epdc_setup_mode_config(&priv->drm);
 
        ret = mxc_epdc_output(&priv->drm);
-- 
2.30.2

Reply via email to