[RFC PATCH 3/6] drm: mxc-epdc: Add display and waveform initialisation

From: Andreas Kemnade
Date: Sun Feb 06 2022 - 03:20:48 EST


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

Signed-off-by: Andreas Kemnade <andreas@xxxxxxxxxxxx>
---
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