From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on sa.local.altlinux.org X-Spam-Level: X-Spam-Status: No, score=-4.3 required=5.0 tests=ALL_TRUSTED,BAYES_00, RP_MATCHES_RCVD autolearn=unavailable autolearn_force=no version=3.4.1 From: Daniil Gnusarev To: devel-kernel@lists.altlinux.org Date: Mon, 14 Oct 2024 18:01:58 +0400 Message-ID: <20241014140221.535985-18-gnusarevda@basealt.ru> X-Mailer: git-send-email 2.42.2 In-Reply-To: <20241014140221.535985-1-gnusarevda@basealt.ru> References: <20241014140221.535985-1-gnusarevda@basealt.ru> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [d-kernel] [PATCH 17/39] drm: add Baikal-M SoC video display unit driver X-BeenThere: devel-kernel@lists.altlinux.org X-Mailman-Version: 2.1.12 Precedence: list Reply-To: ALT Linux kernel packages development List-Id: ALT Linux kernel packages development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 14 Oct 2024 14:02:18 -0000 Archived-At: List-Archive: List-Post: Add VDU driver for Baikal BE-M1000 with firmware from SDK version ARM64-2403-6.6 Co-developed-by: Pavel Parkhomenko Bugfixes by Alexey Sheplyakov Signed-off-by: Daniil Gnusarev --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/baikal/Kconfig | 12 + drivers/gpu/drm/baikal/Makefile | 11 + drivers/gpu/drm/baikal/baikal-hdmi.c | 123 +++++ drivers/gpu/drm/baikal/baikal_vdu_backlight.c | 261 +++++++++ drivers/gpu/drm/baikal/baikal_vdu_crtc.c | 440 +++++++++++++++ drivers/gpu/drm/baikal/baikal_vdu_debugfs.c | 95 ++++ drivers/gpu/drm/baikal/baikal_vdu_drm.h | 114 ++++ drivers/gpu/drm/baikal/baikal_vdu_drv.c | 516 ++++++++++++++++++ drivers/gpu/drm/baikal/baikal_vdu_panel.c | 193 +++++++ drivers/gpu/drm/baikal/baikal_vdu_plane.c | 138 +++++ drivers/gpu/drm/baikal/baikal_vdu_regs.h | 137 +++++ drivers/gpu/drm/bridge/Kconfig | 7 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 9 + drivers/gpu/drm/drm_panel.c | 37 ++ include/drm/bridge/dw_hdmi.h | 2 + include/drm/drm_panel.h | 3 + 18 files changed, 2101 insertions(+) create mode 100644 drivers/gpu/drm/baikal/Kconfig create mode 100644 drivers/gpu/drm/baikal/Makefile create mode 100644 drivers/gpu/drm/baikal/baikal-hdmi.c create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_backlight.c create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_crtc.c create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_debugfs.c create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drm.h create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_drv.c create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_panel.c create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_plane.c create mode 100644 drivers/gpu/drm/baikal/baikal_vdu_regs.h diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ec4abf9ff47b5..946fb9fac5332 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -261,6 +261,8 @@ source "drivers/gpu/drm/i2c/Kconfig" source "drivers/gpu/drm/arm/Kconfig" +source "drivers/gpu/drm/baikal/Kconfig" + source "drivers/gpu/drm/radeon/Kconfig" source "drivers/gpu/drm/amd/amdgpu/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 215e78e791250..f754f002c141d 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -197,4 +197,5 @@ obj-y += gud/ obj-$(CONFIG_DRM_HYPERV) += hyperv/ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ +obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal/ obj-$(CONFIG_DRM_LOONGSON) += loongson/ diff --git a/drivers/gpu/drm/baikal/Kconfig b/drivers/gpu/drm/baikal/Kconfig new file mode 100644 index 0000000000000..4a18055cd22fa --- /dev/null +++ b/drivers/gpu/drm/baikal/Kconfig @@ -0,0 +1,12 @@ +config DRM_BAIKAL_VDU + tristate "DRM Support for Baikal-M VDU" + depends on DRM + depends on ARM || ARM64 || COMPILE_TEST + depends on COMMON_CLK + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select DRM_PANEL + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE + help + Choose this option for DRM support for the Baikal-M Video Display Unit (VDU). + If M is selected the module will be called baikal_vdu_drm. diff --git a/drivers/gpu/drm/baikal/Makefile b/drivers/gpu/drm/baikal/Makefile new file mode 100644 index 0000000000000..321d3be96b814 --- /dev/null +++ b/drivers/gpu/drm/baikal/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +baikal_vdu_drm-y += baikal_vdu_backlight.o \ + baikal_vdu_crtc.o \ + baikal_vdu_drv.o \ + baikal_vdu_panel.o \ + baikal_vdu_plane.o + +baikal_vdu_drm-$(CONFIG_DEBUG_FS) += baikal_vdu_debugfs.o + +obj-$(CONFIG_DRM_BAIKAL_VDU) += baikal_vdu_drm.o +obj-$(CONFIG_DRM_BAIKAL_HDMI) += baikal-hdmi.o diff --git a/drivers/gpu/drm/baikal/baikal-hdmi.c b/drivers/gpu/drm/baikal/baikal-hdmi.c new file mode 100644 index 0000000000000..7d3d0f0c7358e --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal-hdmi.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Baikal Electronics BE-M1000 DesignWare HDMI 2.0 Tx PHY support driver + * + * Copyright (C) 2019-2022 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#include +#include +#include + +#include + +#include "baikal_vdu_drm.h" + +int fixed_clock = 0; +int max_clock = 0; + +static const struct dw_hdmi_mpll_config baikal_hdmi_mpll_cfg[] = { + /* pixelclk opmode gmp */ + { 44900000, { { 0x00b3, 0x0000 }, }, }, + { 90000000, { { 0x0072, 0x0001 }, }, }, + { 182750000, { { 0x0051, 0x0002 }, }, }, + { 340000000, { { 0x0040, 0x0003 }, }, }, + { 594000000, { { 0x1a40, 0x0003 }, }, }, + { ~0UL, { { 0x0000, 0x0000 }, }, } +}; + +static const struct dw_hdmi_curr_ctrl baikal_hdmi_cur_ctr[] = { + /* pixelclk current */ + { 44900000, { 0x0000, }, }, + { 90000000, { 0x0008, }, }, + { 182750000, { 0x001b, }, }, + { 340000000, { 0x0036, }, }, + { 594000000, { 0x003f, }, }, + { ~0UL, { 0x0000, }, } +}; + +static const struct dw_hdmi_phy_config baikal_hdmi_phy_cfg[] = { + /* pixelclk symbol term vlev */ + { 148250000, 0x8009, 0x0004, 0x0232}, + { 218250000, 0x8009, 0x0004, 0x0230}, + { 288000000, 0x8009, 0x0004, 0x0273}, + { 340000000, 0x8029, 0x0004, 0x0273}, + { 594000000, 0x8039, 0x0004, 0x014a}, + { ~0UL, 0x0000, 0x0000, 0x0000} +}; + +static enum drm_mode_status +baikal_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->clock < 13500) + return MODE_CLOCK_LOW; + if (mode->clock >= 340000) + return MODE_CLOCK_HIGH; + if (fixed_clock && mode->clock != fixed_clock) + return MODE_BAD; + if (max_clock && mode->clock > max_clock) + return MODE_BAD; + + return MODE_OK; +} + +static struct dw_hdmi_plat_data baikal_dw_hdmi_plat_data = { + .mpll_cfg = baikal_hdmi_mpll_cfg, + .cur_ctr = baikal_hdmi_cur_ctr, + .phy_config = baikal_hdmi_phy_cfg, + .mode_valid = baikal_hdmi_mode_valid, +}; + +static int baikal_dw_hdmi_probe(struct platform_device *pdev) +{ + struct baikal_hdmi_bridge *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->hdmi = dw_hdmi_probe(pdev, &baikal_dw_hdmi_plat_data); + if (IS_ERR(priv->hdmi)) + return PTR_ERR(priv->hdmi); + + priv->bridge = dw_hdmi_get_bridge(priv->hdmi); + return 0; +} + +static int baikal_dw_hdmi_remove(struct platform_device *pdev) +{ + struct baikal_hdmi_bridge *priv = platform_get_drvdata(pdev); + + dw_hdmi_remove(priv->hdmi); + return 0; +} + +static const struct of_device_id baikal_dw_hdmi_of_table[] = { + { .compatible = "baikal,hdmi" }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, baikal_dw_hdmi_of_table); + +static struct platform_driver baikal_dw_hdmi_platform_driver = { + .probe = baikal_dw_hdmi_probe, + .remove = baikal_dw_hdmi_remove, + .driver = { + .name = "baikal-dw-hdmi", + .of_match_table = baikal_dw_hdmi_of_table, + }, +}; + +module_param(fixed_clock, int, 0644); +module_param(max_clock, int, 0644); + +module_platform_driver(baikal_dw_hdmi_platform_driver); + +MODULE_AUTHOR("Pavel Parkhomenko "); +MODULE_DESCRIPTION("Baikal BE-M1000 SoC DesignWare HDMI 2.0 Tx + Gen2 PHY Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/baikal/baikal_vdu_backlight.c b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c new file mode 100644 index 0000000000000..f58568a755c9c --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_backlight.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +/** + * baikal_vdu_backlight.c + * Implementation of backlight functions for + * Baikal Electronics BE-M1000 SoC's VDU + */ + +#include +#include +#include + +#include + +#include "baikal_vdu_drm.h" +#include "baikal_vdu_regs.h" + +#define BAIKAL_VDU_MIN_BRIGHTNESS 0 +#define BAIKAL_VDU_DEFAULT_BRIGHTNESS 50 +#define BAIKAL_VDU_BRIGHTNESS_STEP 5 +#define BAIKAL_VDU_DEFAULT_PWM_FREQ 10000 + +static int baikal_vdu_backlight_update_status(struct backlight_device *bl_dev) +{ + struct baikal_vdu_private *priv = bl_get_data(bl_dev); + int brightness_on = 1; + int brightness = bl_dev->props.brightness; + u8 pwmdc; + + if (bl_dev->props.power != FB_BLANK_UNBLANK || + bl_dev->props.fb_blank != FB_BLANK_UNBLANK || + bl_dev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) { + brightness_on = 0; + brightness = priv->min_brightness; + } + + if (priv->enable_gpio) + gpiod_set_value_cansleep(priv->enable_gpio, brightness_on); + + pwmdc = brightness ? ((brightness << 6) / 25 - 1) : 0; + + writel(pwmdc, priv->regs + PWMDCR); + + return 0; +} + +static const struct backlight_ops baikal_vdu_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = baikal_vdu_backlight_update_status, +}; + +static void baikal_vdu_input_event(struct input_handle *handle, + unsigned int type, unsigned int code, + int value) +{ + struct baikal_vdu_private *priv = handle->private; + int brightness; + + if (type != EV_KEY || value == 0) + return; + + switch (code) { + case KEY_BRIGHTNESSDOWN: + brightness = priv->bl_dev->props.brightness - + priv->brightness_step; + if (brightness >= priv->min_brightness) + backlight_device_set_brightness(priv->bl_dev, + brightness); + break; + + case KEY_BRIGHTNESSUP: + brightness = priv->bl_dev->props.brightness + + priv->brightness_step; + backlight_device_set_brightness(priv->bl_dev, brightness); + break; + + case KEY_BRIGHTNESS_TOGGLE: + priv->brightness_on = !priv->brightness_on; + if (priv->brightness_on) + backlight_enable(priv->bl_dev); + else + backlight_disable(priv->bl_dev); + break; + + default: + return; + } + + backlight_force_update(priv->bl_dev, BACKLIGHT_UPDATE_HOTKEY); +} + +static int baikal_vdu_input_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int ret; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->private = handler->private; + handle->name = KBUILD_MODNAME; + handle->dev = dev; + handle->handler = handler; + + ret = input_register_handle(handle); + if (ret) + goto err_free_handle; + + ret = input_open_device(handle); + if (ret) + goto err_unregister_handle; + + return 0; + +err_unregister_handle: + input_unregister_handle(handle); +err_free_handle: + kfree(handle); + return ret; +} + +static void baikal_vdu_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id baikal_vdu_input_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + + { }, /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(input, baikal_vdu_input_ids); + +int baikal_vdu_backlight_create(struct drm_device *drm) +{ + struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm); + struct baikal_vdu_private *priv = &crossbar->lvds; + struct device *dev = drm->dev; + struct backlight_properties props; + struct input_handler *handler; + struct fwnode_handle *node; + u32 min_brightness = BAIKAL_VDU_MIN_BRIGHTNESS; + u32 dfl_brightness = BAIKAL_VDU_DEFAULT_BRIGHTNESS; + u32 brightness_step = BAIKAL_VDU_BRIGHTNESS_STEP; + u32 pwm_frequency = 0; + int ret = 0; + unsigned long rate; + unsigned int pwmfr = 0; + + priv->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS); + if (IS_ERR(priv->enable_gpio)) { + dev_warn(dev, "failed to get ENABLE GPIO\n"); + priv->enable_gpio = NULL; + } + + if (priv->enable_gpio && gpiod_get_direction(priv->enable_gpio) != 0) + gpiod_direction_output(priv->enable_gpio, 1); + + node = fwnode_get_named_child_node(dev->fwnode, is_of_node(dev->fwnode) ? + "backlight" : "BCKL"); + if (!node) + return 0; + + fwnode_property_read_u32(node, "min-brightness-level", &min_brightness); + fwnode_property_read_u32(node, "default-brightness-level", &dfl_brightness); + fwnode_property_read_u32(node, "brightness-level-step", &brightness_step); + fwnode_property_read_u32(node, "pwm-frequency", &pwm_frequency); + + if (pwm_frequency == 0) { + dev_warn(dev, "using default PWM frequency %u\n", + BAIKAL_VDU_DEFAULT_PWM_FREQ); + pwm_frequency = BAIKAL_VDU_DEFAULT_PWM_FREQ; + } + + memset(&props, 0, sizeof(props)); + props.max_brightness = 100; + props.type = BACKLIGHT_RAW; + props.scale = BACKLIGHT_SCALE_LINEAR; + + if (min_brightness > props.max_brightness) { + dev_warn(dev, "invalid min brightness level: %u, using %u\n", + min_brightness, props.max_brightness); + min_brightness = props.max_brightness; + } + + if (dfl_brightness > props.max_brightness || + dfl_brightness < min_brightness) { + dev_warn(dev, + "invalid default brightness level: %u, using %u\n", + dfl_brightness, props.max_brightness); + dfl_brightness = props.max_brightness; + } + + priv->min_brightness = min_brightness; + priv->brightness_step = brightness_step; + priv->brightness_on = true; + + props.brightness = dfl_brightness; + props.power = FB_BLANK_UNBLANK; + + priv->bl_dev = + devm_backlight_device_register(dev, dev_name(dev), dev, priv, + &baikal_vdu_backlight_ops, + &props); + if (IS_ERR(priv->bl_dev)) { + dev_err(dev, "failed to register backlight device\n"); + ret = PTR_ERR(priv->bl_dev); + priv->bl_dev = NULL; + goto out; + } + + handler = devm_kzalloc(dev, sizeof(*handler), GFP_KERNEL); + if (!handler) { + dev_err(dev, "failed to allocate input handler\n"); + ret = -ENOMEM; + goto out; + } + + handler->private = priv; + handler->event = baikal_vdu_input_event; + handler->connect = baikal_vdu_input_connect; + handler->disconnect = baikal_vdu_input_disconnect; + handler->name = KBUILD_MODNAME; + handler->id_table = baikal_vdu_input_ids; + + ret = input_register_handler(handler); + if (ret) { + dev_err(dev, "failed to register input handler\n"); + goto out; + } + + /* Hold PWM Clock Domain Reset, disable clocking */ + writel(0, priv->regs + PWMFR); + + rate = baikal_vdu_crtc_get_rate(priv); + pwmfr |= PWMFR_PWMFCD(rate / pwm_frequency - 1) | PWMFR_PWMFCI; + writel(pwmfr, priv->regs + PWMFR); + + /* Release PWM Clock Domain Reset, enable clocking */ + writel(pwmfr | PWMFR_PWMPCR | PWMFR_PWMFCE, priv->regs + PWMFR); + + backlight_update_status(priv->bl_dev); +out: + fwnode_handle_put(node); + return ret; +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_crtc.c b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c new file mode 100644 index 0000000000000..50327813c04b3 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_crtc.c @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +/** + * baikal_vdu_crtc.c + * Implementation of the CRTC functions for Baikal Electronics BE-M1000 VDU driver + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "baikal_vdu_drm.h" +#include "baikal_vdu_regs.h" + +irqreturn_t baikal_vdu_irq(int irq, void *data) +{ + struct baikal_vdu_private *priv = data; + irqreturn_t status = IRQ_NONE; + u32 raw_stat; + u32 irq_stat; + + priv->counters[0]++; + irq_stat = readl(priv->regs + IVR); + raw_stat = readl(priv->regs + ISR); + + if (raw_stat & INTR_UFU) { + priv->counters[4]++; + status = IRQ_HANDLED; + } + + if (raw_stat & INTR_IFO) { + priv->counters[5]++; + status = IRQ_HANDLED; + } + + if (raw_stat & INTR_OFU) { + priv->counters[6]++; + status = IRQ_HANDLED; + } + + if (irq_stat & INTR_FER) { + priv->counters[11]++; + priv->counters[12] = readl(priv->regs + DBAR); + priv->counters[13] = readl(priv->regs + DCAR); + priv->counters[14] = readl(priv->regs + MRR); + status = IRQ_HANDLED; + } + + priv->counters[3] |= raw_stat; + + /* Clear all interrupts */ + writel(raw_stat, priv->regs + ISR); + + return status; +} + +static bool baikal_vdu_crtc_is_clk_enabled(struct baikal_vdu_private *priv) +{ + if (acpi_disabled) { + return __clk_is_enabled(priv->clk); +#ifdef CONFIG_ACPI + } else { + union acpi_object obj = { + .type = ACPI_TYPE_INTEGER, + .integer.type = ACPI_TYPE_INTEGER, + .integer.value = priv->index + }; + struct acpi_object_list args = { + .count = 1, + .pointer = &obj + }; + union acpi_object *obj2; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + u64 val; + + status = acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle, + "CISE", &args, &buffer); + if (ACPI_FAILURE(status)) + return 0; + + obj2 = buffer.pointer; + if (!obj2 || obj2->type != ACPI_TYPE_INTEGER) { + kfree(obj2); + return 0; + } + + val = obj2->integer.value; + kfree(obj2); + + return val; +#endif + } +} + +static void baikal_vdu_crtc_clk_enable(struct baikal_vdu_private *priv) +{ + if (acpi_disabled) { + clk_prepare_enable(priv->clk); +#ifdef CONFIG_ACPI + } else { + union acpi_object obj = { + .type = ACPI_TYPE_INTEGER, + .integer.type = ACPI_TYPE_INTEGER, + .integer.value = priv->index + }; + struct acpi_object_list args = { + .count = 1, + .pointer = &obj + }; + + acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle, + "CEN", &args, NULL); +#endif + } +} + +static void baikal_vdu_crtc_clk_disable(struct baikal_vdu_private *priv) +{ + if (acpi_disabled) { + clk_disable_unprepare(priv->clk); +#ifdef CONFIG_ACPI + } else { + union acpi_object obj = { + .type = ACPI_TYPE_INTEGER, + .integer.type = ACPI_TYPE_INTEGER, + .integer.value = priv->index + }; + struct acpi_object_list args = { + .count = 1, + .pointer = &obj + }; + + acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle, + "CDIS", &args, NULL); +#endif + } +} + +static int baikal_vdu_crtc_set_rate(struct baikal_vdu_private *priv, u32 rate) +{ + if (acpi_disabled) { + return clk_set_rate(priv->clk, rate); +#ifdef CONFIG_ACPI + } else { + union acpi_object obj[2] = { + { + .type = ACPI_TYPE_INTEGER, + .integer.type = ACPI_TYPE_INTEGER, + .integer.value = priv->index + }, + { + .type = ACPI_TYPE_INTEGER, + .integer.type = ACPI_TYPE_INTEGER, + .integer.value = rate + } + }; + struct acpi_object_list args = { + .count = 2, + .pointer = obj + }; + + acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle, + "CSET", &args, NULL); + return 0; +#endif + } +} + +u64 baikal_vdu_crtc_get_rate(struct baikal_vdu_private *priv) +{ + if (acpi_disabled) { + return clk_get_rate(priv->clk); +#ifdef CONFIG_ACPI + } else { + union acpi_object obj = { + .type = ACPI_TYPE_INTEGER, + .integer.type = ACPI_TYPE_INTEGER, + .integer.value = priv->index + }; + struct acpi_object_list args = { + .count = 1, + .pointer = &obj + }; + union acpi_object *obj2; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + u64 val; + + status = acpi_evaluate_object(ACPI_COMPANION(priv->drm->dev)->handle, + "CGET", &args, &buffer); + if (ACPI_FAILURE(status)) + return 0; + + obj2 = buffer.pointer; + if (!obj2 || obj2->type != ACPI_TYPE_INTEGER) { + kfree(obj2); + return 0; + } + + val = obj2->integer.value; + kfree(obj2); + + return val; +#endif + } +} + +static void baikal_vdu_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc); + const struct drm_display_mode *mode = &crtc->state->mode; + unsigned long rate; + unsigned int ppl, hsw, hfp, hbp; + unsigned int lpp, vsw, vfp, vbp; + unsigned int reg; + int ret = 0; + + drm_mode_debug_printmodeline(mode); + + rate = mode->clock * 1000; + + if (rate != baikal_vdu_crtc_get_rate(priv)) { + DRM_DEV_DEBUG_DRIVER(dev->dev, "Requested pixel clock is %lu Hz\n", rate); + + /* hold clock domain reset; disable clocking */ + writel(0, priv->regs + PCTR); + + if (baikal_vdu_crtc_is_clk_enabled(priv)) + baikal_vdu_crtc_clk_disable(priv); + ret = baikal_vdu_crtc_set_rate(priv, rate); + + if (ret >= 0) { + baikal_vdu_crtc_clk_enable(priv); + if (!baikal_vdu_crtc_is_clk_enabled(priv)) + ret = -1; + } + + /* release clock domain reset; enable clocking */ + reg = readl(priv->regs + PCTR); + reg |= PCTR_PCR + PCTR_PCI; + writel(reg, priv->regs + PCTR); + } + + if (ret < 0) + DRM_ERROR("Cannot set desired pixel clock (%lu Hz)\n", rate); + + ppl = mode->hdisplay / 16; + if (priv->index == CRTC_LVDS && priv-> num_lanes == 2) { + hsw = mode->hsync_end - mode->hsync_start; + hfp = mode->hsync_start - mode->hdisplay - 1; + } else { + hsw = mode->hsync_end - mode->hsync_start - 1; + hfp = mode->hsync_start - mode->hdisplay; + } + hbp = mode->htotal - mode->hsync_end; + + lpp = mode->vdisplay; + vsw = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + writel((HTR_HFP(hfp) & HTR_HFP_MASK) | + (HTR_PPL(ppl) & HTR_PPL_MASK) | + (HTR_HBP(hbp) & HTR_HBP_MASK) | + (HTR_HSW(hsw) & HTR_HSW_MASK), + priv->regs + HTR); + + if (mode->hdisplay > 4080 || ppl * 16 != mode->hdisplay) + writel((HPPLOR_HPPLO(mode->hdisplay) & HPPLOR_HPPLO_MASK) | HPPLOR_HPOE, + priv->regs + HPPLOR); + + writel((VTR1_VSW(vsw) & VTR1_VSW_MASK) | + (VTR1_VFP(vfp) & VTR1_VFP_MASK) | + (VTR1_VBP(vbp) & VTR1_VBP_MASK), + priv->regs + VTR1); + + writel(lpp & VTR2_LPP_MASK, priv->regs + VTR2); + + writel((HVTER_VSWE(vsw >> VTR1_VSW_LSB_WIDTH) & HVTER_VSWE_MASK) | + (HVTER_HSWE(hsw >> HTR_HSW_LSB_WIDTH) & HVTER_HSWE_MASK) | + (HVTER_VBPE(vbp >> VTR1_VBP_LSB_WIDTH) & HVTER_VBPE_MASK) | + (HVTER_VFPE(vfp >> VTR1_VFP_LSB_WIDTH) & HVTER_VFPE_MASK) | + (HVTER_HBPE(hbp >> HTR_HBP_LSB_WIDTH) & HVTER_HBPE_MASK) | + (HVTER_HFPE(hfp >> HTR_HFP_LSB_WIDTH) & HVTER_HFPE_MASK), + priv->regs + HVTER); + + /* Set polarities */ + reg = readl(priv->regs + CR1); + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + reg |= CR1_HSP; + else + reg &= ~CR1_HSP; + reg &= ~CR1_VSP; // always set VSP to active high + reg |= CR1_DEP; // set DE to active high; + writel(reg, priv->regs + CR1); +} + +static enum drm_mode_status baikal_vdu_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc); + if (!priv->mode_override && (mode->hdisplay > 2560 || + mode->vdisplay > 1440)) + return MODE_BAD; + else + return MODE_OK; +} + +static void baikal_vdu_crtc_helper_enable(struct drm_crtc *crtc, + struct drm_atomic_state *old_state) +{ + struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc); + const char *data_mapping = NULL; + u32 cntl, gpio; + + DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n"); + baikal_vdu_crtc_clk_enable(priv); + + /* Set 16-word input FIFO watermark */ + /* Enable and Power Up */ + cntl = readl(priv->regs + CR1); + cntl &= ~(CR1_FDW_MASK | CR1_OPS_MASK); + cntl |= CR1_LCE | CR1_FDW_16_WORDS; + + if (priv->index == CRTC_LVDS) { + if (priv->bridge->of_node) { + of_property_read_string(priv->bridge->of_node, + "data-mapping", &data_mapping); + } else { + struct fwnode_handle *fwnode; + + fwnode = fwnode_find_reference(priv->bridge->dev->dev->fwnode, + "baikal,lvds-panel", 0); + if (!IS_ERR_OR_NULL(fwnode)) + fwnode_property_read_string(fwnode, "data-mapping", + &data_mapping); + } + + if (!data_mapping) { + cntl |= CR1_OPS_LCD18; + } else if (!strncmp(data_mapping, "vesa-24", 7)) + cntl |= CR1_OPS_LCD24; + else if (!strncmp(data_mapping, "jeida-18", 8)) + cntl |= CR1_OPS_LCD18; + else { + dev_warn(crtc->dev->dev, "%s data mapping is not supported, vesa-24 is set\n", data_mapping); + cntl |= CR1_OPS_LCD24; + } + gpio = GPIOR_UHD_ENB; + if (priv->num_lanes == 4) + gpio |= GPIOR_UHD_QUAD_PORT; + else if (priv->num_lanes == 2) + gpio |= GPIOR_UHD_DUAL_PORT; + else + gpio |= GPIOR_UHD_SNGL_PORT; + writel(gpio, priv->regs + GPIOR); + } else + cntl |= CR1_OPS_LCD24; + writel(cntl, priv->regs + CR1); + + writel(0x3ffff, priv->regs + ISR); + writel(INTR_FER, priv->regs + IMR); +} + +void baikal_vdu_crtc_helper_disable(struct drm_crtc *crtc) +{ + struct baikal_vdu_private *priv = crtc_to_baikal_vdu(crtc); + + writel(0x3ffff, priv->regs + ISR); + writel(0, priv->regs + IMR); + + /* Disable clock */ + DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "disabling pixel clock\n"); + baikal_vdu_crtc_clk_disable(priv); +} + +static void baikal_vdu_crtc_helper_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *old_state) +{ + struct drm_pending_vblank_event *event = crtc->state->event; + + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +const struct drm_crtc_funcs crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +const struct drm_crtc_helper_funcs crtc_helper_funcs = { + .mode_set_nofb = baikal_vdu_crtc_helper_mode_set_nofb, + .mode_valid = baikal_vdu_mode_valid, + .atomic_flush = baikal_vdu_crtc_helper_atomic_flush, + .disable = baikal_vdu_crtc_helper_disable, + .atomic_enable = baikal_vdu_crtc_helper_enable, +}; + +int baikal_vdu_crtc_create(struct baikal_vdu_private *priv) +{ + struct drm_device *dev = priv->drm; + struct drm_crtc *crtc = &priv->crtc; + + drm_crtc_init_with_planes(dev, crtc, + &priv->primary, NULL, + &crtc_funcs, "primary"); + drm_crtc_helper_add(crtc, &crtc_helper_funcs); + + DRM_DEV_DEBUG_DRIVER(crtc->dev->dev, "enabling pixel clock\n"); + baikal_vdu_crtc_clk_enable(priv); + + return 0; +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c new file mode 100644 index 0000000000000..1fda8c2173117 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_debugfs.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#include +#include +#include +#include +#include + +#include "baikal_vdu_drm.h" +#include "baikal_vdu_regs.h" + +#define REGDEF(reg) { reg, #reg } +static const struct { + u32 reg; + const char *name; +} baikal_vdu_reg_defs[] = { + REGDEF(CR1), + REGDEF(HTR), + REGDEF(VTR1), + REGDEF(VTR2), + REGDEF(PCTR), + REGDEF(ISR), + REGDEF(IMR), + REGDEF(IVR), + REGDEF(ISCR), + REGDEF(DBAR), + REGDEF(DCAR), + REGDEF(DEAR), + REGDEF(PWMFR), + REGDEF(PWMDCR), + REGDEF(HVTER), + REGDEF(HPPLOR), + REGDEF(GPIOR), + REGDEF(MRR), +}; + +#define REGS_HDMI "regs_hdmi" +#define REGS_LVDS "regs_lvds" + +int baikal_vdu_debugfs_regs(struct seq_file *m, void *unused) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(dev); + char *filename = m->file->f_path.dentry->d_iname; + struct baikal_vdu_private *priv = NULL; + int i; + + if (!strcmp(REGS_HDMI, filename)) + priv = &crossbar->hdmi; + if (!strcmp(REGS_LVDS, filename)) + priv = &crossbar->lvds; + + if (!priv || !priv->regs) + return 0; + + for (i = 0; i < ARRAY_SIZE(baikal_vdu_reg_defs); i++) { + seq_printf(m, "%s (0x%04x): 0x%08x\n", + baikal_vdu_reg_defs[i].name, baikal_vdu_reg_defs[i].reg, + readl(priv->regs + baikal_vdu_reg_defs[i].reg)); + } + for (i = 0; i < ARRAY_SIZE(priv->counters); i++) { + seq_printf(m, "COUNTER[%d]: 0x%08x\n", i, priv->counters[i]); + } + + return 0; +} + +static const struct drm_info_list baikal_vdu_hdmi_debugfs_list[] = { + {REGS_HDMI, baikal_vdu_debugfs_regs, 0}, +}; + +static const struct drm_info_list baikal_vdu_lvds_debugfs_list[] = { + {REGS_LVDS, baikal_vdu_debugfs_regs, 0}, +}; + +void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(baikal_vdu_hdmi_debugfs_list, + ARRAY_SIZE(baikal_vdu_hdmi_debugfs_list), + minor->debugfs_root, minor); +} + +void baikal_vdu_lvds_debugfs_init(struct drm_minor *minor) +{ + drm_debugfs_create_files(baikal_vdu_lvds_debugfs_list, + ARRAY_SIZE(baikal_vdu_lvds_debugfs_list), + minor->debugfs_root, minor); +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drm.h b/drivers/gpu/drm/baikal/baikal_vdu_drm.h new file mode 100644 index 0000000000000..432270bebed69 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_drm.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#ifndef __BAIKAL_VDU_DRM_H__ +#define __BAIKAL_VDU_DRM_H__ + +#include +#include +#include + +#include + +#include +#include +#include + +#define CRTC_HDMI 0 +#define CRTC_LVDS 1 + +#define crtc_to_baikal_vdu(x) \ + container_of(x, struct baikal_vdu_private, crtc) +#define bridge_to_baikal_lvds_bridge(x) \ + container_of(x, struct baikal_lvds_bridge, bridge) +#define connector_to_baikal_lvds_bridge(x) \ + container_of(x, struct baikal_lvds_bridge, connector) +#define drm_to_baikal_vdu_crossbar(x) \ + container_of(x, struct baikal_vdu_crossbar, drm) + +#define VDU_NAME_LEN 5 + +struct baikal_vdu_private { + struct drm_device *drm; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_bridge *bridge; + struct drm_plane primary; + void *regs; + int irq; + struct clk *clk; + spinlock_t lock; + u32 counters[20]; + int mode_override; + int index; + char name[VDU_NAME_LEN]; + char irq_name[VDU_NAME_LEN + 4]; + char pclk_name[VDU_NAME_LEN + 5]; + char regs_name[VDU_NAME_LEN + 5]; + int num_lanes; + int data_mapping; + int off; + int ready; + + /* backlight */ + struct gpio_desc *enable_gpio; + struct backlight_device *bl_dev; + int min_brightness; + int brightness_step; + bool brightness_on; +}; + +struct baikal_vdu_crossbar { + struct drm_device drm; + struct baikal_vdu_private hdmi; + struct baikal_vdu_private lvds; +}; + +struct baikal_lvds_bridge { + struct baikal_vdu_private *vdu; + struct drm_bridge bridge; + struct drm_connector connector; + struct drm_panel *panel; + u32 connector_type; +}; + +struct baikal_hdmi_bridge { + struct dw_hdmi *hdmi; + struct drm_bridge *bridge; +}; + +/* Generic functions */ +inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv); + +inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv); + +/* Bridge functions */ +bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge); + +struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev, + struct drm_panel *panel, + u32 connector_type); + +/* CRTC Functions */ +u64 baikal_vdu_crtc_get_rate(struct baikal_vdu_private *priv); + +int baikal_vdu_crtc_create(struct baikal_vdu_private *priv); + +irqreturn_t baikal_vdu_irq(int irq, void *data); + +int baikal_vdu_primary_plane_init(struct baikal_vdu_private *priv); + +/* Backlight Functions */ +int baikal_vdu_backlight_create(struct drm_device *drm); + +/* Debugfs functions */ +void baikal_vdu_hdmi_debugfs_init(struct drm_minor *minor); + +void baikal_vdu_lvds_debugfs_init(struct drm_minor *minor); + +#endif /* __BAIKAL_VDU_DRM_H__ */ diff --git a/drivers/gpu/drm/baikal/baikal_vdu_drv.c b/drivers/gpu/drm/baikal/baikal_vdu_drv.c new file mode 100644 index 0000000000000..c9d5cea635628 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_drv.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * Bugfixes by Alexey Sheplyakov + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "baikal_vdu_drm.h" +#include "baikal_vdu_regs.h" + +#define DRIVER_NAME "baikal-vdu" +#define DRIVER_DESC "Baikal VDU DRM driver" +#define DRIVER_DATE "20230221" + +#define BAIKAL_SMC_LOG_DISABLE 0xC2000200 +#define IFIFO_SIZE 16384 +#define UHD_FIFO_SIZE 16384 + +int mode_override = 0; +int hdmi_off = 0; +int lvds_off = 0; + +static struct drm_driver vdu_drm_driver; + +static struct drm_mode_config_funcs mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +const struct drm_encoder_funcs baikal_vdu_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static struct drm_bridge *devm_baikal_get_bridge(struct device *dev, + u32 port, u32 endpoint) +{ + struct baikal_hdmi_bridge *priv_hdmi; + struct device *tmp; + struct drm_bridge *bridge; + struct drm_panel *panel; + struct fwnode_handle *fwnode; + int ret = 0; + + if (is_of_node(dev->fwnode)) { + ret = drm_of_find_panel_or_bridge(to_of_node(dev->fwnode), port, + endpoint, &panel, &bridge); + } else { + if (port == CRTC_HDMI) { + fwnode = fwnode_find_reference(dev->fwnode, + "baikal,hdmi-bridge", 0); + if (IS_ERR_OR_NULL(fwnode)) + return ERR_PTR(-ENODEV); + + tmp = bus_find_device_by_fwnode(&platform_bus_type, fwnode); + if (IS_ERR_OR_NULL(tmp)) + return ERR_PTR(-ENODEV); + + priv_hdmi = dev_get_drvdata(tmp); + if (!priv_hdmi) + return ERR_PTR(-EPROBE_DEFER); + + bridge = priv_hdmi->bridge; + panel = NULL; + } else if (port == CRTC_LVDS) { + fwnode = fwnode_find_reference(dev->fwnode, + "baikal,lvds-panel", 0); + if (IS_ERR_OR_NULL(fwnode)) + return ERR_PTR(-ENODEV); + + panel = fwnode_drm_find_panel(fwnode); + if (IS_ERR(panel)) + return ERR_CAST(panel); + } else { + return ERR_PTR(-ENODEV); + } + } + + if (ret) + return ERR_PTR(ret); + + if (panel) { + bridge = devm_baikal_lvds_bridge_add(dev, panel, DRM_MODE_CONNECTOR_LVDS); + } + + return bridge; +} + +static int baikal_vdu_remove_efifb(struct drm_device *dev) +{ + int err; + err = drm_aperture_remove_framebuffers(&vdu_drm_driver); + if (err) + dev_warn(dev->dev, "failed to remove firmware framebuffer\n"); + return err; +} + +inline void baikal_vdu_switch_off(struct baikal_vdu_private *priv) +{ + u32 cntl = readl(priv->regs + CR1); + cntl &= ~CR1_LCE; + writel(cntl, priv->regs + CR1); +} + +inline void baikal_vdu_switch_on(struct baikal_vdu_private *priv) +{ + u32 cntl = readl(priv->regs + CR1); + cntl |= CR1_LCE; + writel(cntl, priv->regs + CR1); +} + +static int baikal_vdu_modeset_init(struct baikal_vdu_private *priv) +{ + struct drm_device *dev = priv->drm; + struct drm_encoder *encoder; + int ret = 0; + int index; + + if (priv == NULL) + return -EINVAL; + index = priv->index; + + ret = baikal_vdu_primary_plane_init(priv); + if (ret != 0) { + dev_err(dev->dev, "%s: failed to init primary plane\n", priv->name); + return ret; + } + + ret = baikal_vdu_crtc_create(priv); + if (ret) { + dev_err(dev->dev, "%s: failed to create CRTC\n", priv->name); + return ret; + } + + if (priv->bridge) { + encoder = &priv->encoder; + ret = drm_encoder_init(dev, encoder, &baikal_vdu_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) { + dev_err(dev->dev, "%s: failed to create DRM encoder\n", priv->name); + return ret; + } + encoder->crtc = &priv->crtc; + encoder->possible_crtcs = BIT(drm_crtc_index(encoder->crtc)); + priv->bridge->encoder = encoder; + ret = drm_bridge_attach(encoder, priv->bridge, NULL, 0); + if (ret) { + dev_err(dev->dev, "%s: failed to attach DRM bridge (%d)\n", priv->name, ret); + return ret; + } + } else { + dev_err(dev->dev, "%s: no bridge or panel attached\n", priv->name); + return -ENODEV; + } + + priv->mode_override = mode_override; + + return ret; +} + +static int baikal_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + args->pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + args->size = args->pitch * args->height + IFIFO_SIZE + UHD_FIFO_SIZE; + + return drm_gem_dma_dumb_create_internal(file, dev, args); +} + +DEFINE_DRM_GEM_DMA_FOPS(baikal_drm_fops); + +static struct drm_driver vdu_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + DRM_GEM_DMA_DRIVER_OPS, + .ioctls = NULL, + .fops = &baikal_drm_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = 2, + .minor = 0, + .patchlevel = 0, + .dumb_create = baikal_dumb_create, + .dumb_map_offset = drm_gem_dumb_map_offset, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_import_sg_table = drm_gem_dma_prime_import_sg_table, +}; + +static int baikal_vdu_allocate_resources(struct platform_device *pdev, + struct baikal_vdu_private *priv) +{ + struct device *dev = &pdev->dev; + struct resource *mem; + int ret; + + mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, priv->regs_name); + if (!mem) + mem = platform_get_resource(pdev, IORESOURCE_MEM, priv->index); + + if (!mem) { + dev_err(dev, "%s %s: no MMIO resource specified\n", __func__, priv->name); + return -EINVAL; + } + + priv->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(priv->regs)) { + dev_err(dev, "%s %s: MMIO allocation failed\n", __func__, priv->name); + return PTR_ERR(priv->regs); + } + + if (priv->off) { + baikal_vdu_switch_off(priv); + return -EPERM; + } else { + ret = baikal_vdu_modeset_init(priv); + if (ret) { + dev_err(dev, "%s %s: failed to init modeset\n", __func__, priv->name); + if (ret == -ENODEV) { + baikal_vdu_switch_off(priv); + } + return ret; + } else { + writel(MRR_MAX_VALUE, priv->regs + MRR); + spin_lock_init(&priv->lock); + return 0; + } + } +} + +static int baikal_vdu_allocate_irq(struct platform_device *pdev, + struct baikal_vdu_private *priv) +{ + struct device *dev = &pdev->dev; + int ret; + + priv->irq = fwnode_irq_get_byname(dev->fwnode, priv->irq_name); + if (priv->irq < 0) { + dev_err(dev, "%s %s: no IRQ resource specified\n", __func__, priv->name); + return -EINVAL; + } + + /* turn off interrupts before requesting the irq */ + writel(0, priv->regs + IMR); + ret = request_irq(priv->irq, baikal_vdu_irq, IRQF_SHARED, dev->driver->name, priv); + if (ret != 0) + dev_err(dev, "%s %s: IRQ %d allocation failed\n", __func__, priv->name, priv->irq); + return ret; +} + +static void baikal_vdu_free_irq(struct baikal_vdu_private *priv) +{ + writel(0, priv->regs + IMR); + writel(0x3ffff, priv->regs + ISR); + free_irq(priv->irq, priv->drm->dev); +} + +static int baikal_vdu_allocate_clk(struct baikal_vdu_private *priv) +{ + if (is_of_node(priv->drm->dev->fwnode)) { + priv->clk = clk_get(priv->drm->dev, priv->pclk_name); + if (IS_ERR(priv->clk)) { + dev_err(priv->drm->dev, "%s: unable to get %s, err %ld\n", priv->name, priv->pclk_name, PTR_ERR(priv->clk)); + return PTR_ERR(priv->clk); + } + } + + return 0; +} + +static void baikal_vdu_set_name(struct baikal_vdu_private *priv, int index, const char *name) +{ + char *c; + int len = sizeof(priv->name) / sizeof(priv->name[0]) - 1; + strncpy(priv->name, name, len); + for (c = priv->name; c < priv->name + len && *c; c++) { + *c = toupper(*c); + } + sprintf(priv->irq_name, "%s_irq", name); + sprintf(priv->pclk_name, "%s_pclk", name); + sprintf(priv->regs_name, "%s_regs", name); + priv->index = index; +} + +static int baikal_vdu_bridge_init(struct baikal_vdu_private *priv, struct drm_device *drm) { + int ret = 0; + struct device *dev; + struct drm_bridge *bridge; + if (!priv || !drm) + return -ENODEV; + priv->drm = drm; + dev = drm->dev; + bridge = devm_baikal_get_bridge(dev, priv->index, 0); + if (IS_ERR(bridge)) { + ret = PTR_ERR(bridge); + if (ret == -EPROBE_DEFER) { + dev_info(dev, "%s: bridge probe deferred\n", priv->name); + } + priv->bridge = NULL; + } else { + priv->bridge = bridge; + } + return ret; +} + +static int baikal_vdu_resources_init(struct platform_device *pdev, struct baikal_vdu_private *priv) +{ + int ret = baikal_vdu_allocate_resources(pdev, priv); + if (ret) + return 0; + ret = baikal_vdu_allocate_irq(pdev, priv); + if (ret) + return 0; + ret = baikal_vdu_allocate_clk(priv); + if (ret) { + baikal_vdu_free_irq(priv); + return 0; + } else { + return 1; + } +} + +static int baikal_vdu_drm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct baikal_lvds_bridge *panel_bridge; + struct baikal_vdu_crossbar *crossbar; + struct baikal_vdu_private *hdmi; + struct baikal_vdu_private *lvds; + struct drm_device *drm; + struct drm_mode_config *mode_config; + struct arm_smccc_res res; + int ret; + + crossbar = devm_drm_dev_alloc(dev, &vdu_drm_driver, + struct baikal_vdu_crossbar, drm); + if (IS_ERR(crossbar)) + return PTR_ERR(crossbar); + + drm = &crossbar->drm; + platform_set_drvdata(pdev, drm); + hdmi = &crossbar->hdmi; + baikal_vdu_set_name(hdmi, CRTC_HDMI, "hdmi"); + lvds = &crossbar->lvds; + baikal_vdu_set_name(lvds, CRTC_LVDS, "lvds"); + + ret = baikal_vdu_bridge_init(hdmi, drm); + if (ret == -EPROBE_DEFER) { + goto out_drm; + } + + ret = device_property_read_u32(&pdev->dev, "lvds-lanes", + &lvds->num_lanes); + if (ret) + lvds->num_lanes = 0; + if (lvds->num_lanes) { + ret = baikal_vdu_bridge_init(lvds, drm); + if (ret == -EPROBE_DEFER) { + goto out_drm; + } + } + + drm_mode_config_init(drm); + mode_config = &drm->mode_config; + mode_config->funcs = &mode_config_funcs; + mode_config->min_width = 1; + mode_config->max_width = 8192; + mode_config->min_height = 1; + mode_config->max_height = 8192; + + hdmi->off = hdmi_off; + hdmi->ready = baikal_vdu_resources_init(pdev, hdmi); + if (lvds->num_lanes) { + lvds->off = lvds_off; + lvds->ready = baikal_vdu_resources_init(pdev, lvds); + } else { + lvds->ready = 0; + lvds->bridge = NULL; + dev_info(dev, "No 'lvds-lanes' property found\n"); + } + if (lvds->ready) { + ret = baikal_vdu_backlight_create(drm); + if (ret) { + dev_err(dev, "LVDS: failed to create backlight\n"); + } + if (bridge_is_baikal_lvds_bridge(lvds->bridge)) { + panel_bridge = bridge_to_baikal_lvds_bridge(lvds->bridge); + panel_bridge->vdu = lvds; + } else { + // TODO implement handling of third-party bridges + } + } + if (hdmi->bridge) { + // TODO implement functions specific to HDMI bridge + } + + hdmi->ready = hdmi->ready & !hdmi->off; + lvds->ready = lvds->ready & !lvds->off; + dev_info(dev, "%s output %s\n", hdmi->name, hdmi->ready ? "enabled" : "disabled"); + dev_info(dev, "%s output %s\n", lvds->name, lvds->ready ? "enabled" : "disabled"); + baikal_vdu_remove_efifb(drm); + + if (hdmi->ready || lvds->ready) { + /* Disable SCP debug output as it may affect VDU performance */ + arm_smccc_smc(BAIKAL_SMC_LOG_DISABLE, 0, 0, 0, 0, 0, 0, 0, &res); + + drm_mode_config_reset(drm); + drm_kms_helper_poll_init(drm); + ret = drm_dev_register(drm, 0); + if (ret) { + dev_err(dev, "failed to register DRM device\n"); + goto out_config; + } + drm_fbdev_dma_setup(drm, 32); +#if defined(CONFIG_DEBUG_FS) + if (hdmi->ready) + baikal_vdu_hdmi_debugfs_init(drm->primary); + if (lvds->ready) + baikal_vdu_lvds_debugfs_init(drm->primary); +#endif + return 0; + } else { + dev_err(dev, "no active outputs configured\n"); + ret = -ENODEV; + } +out_config: + drm_mode_config_cleanup(drm); +out_drm: + dev_err(dev, "failed to probe: %d\n", ret); + return ret; +} + +static void baikal_vdu_drm_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + struct baikal_vdu_crossbar *crossbar = drm_to_baikal_vdu_crossbar(drm); + + drm_dev_unregister(drm); + drm_mode_config_cleanup(drm); + if (crossbar->hdmi.irq) + free_irq(crossbar->hdmi.irq, drm->dev); + if (crossbar->lvds.irq) + free_irq(crossbar->lvds.irq, drm->dev); +} + +static const struct of_device_id baikal_vdu_of_match[] = { + { .compatible = "baikal,vdu" }, + { }, +}; +MODULE_DEVICE_TABLE(of, baikal_vdu_of_match); + +static int baikal_vdu_pm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int baikal_vdu_pm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(drm); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(baikal_vdu_pm_ops, baikal_vdu_pm_suspend, baikal_vdu_pm_resume); + +static struct platform_driver baikal_vdu_platform_driver = { + .probe = baikal_vdu_drm_probe, + .remove_new = baikal_vdu_drm_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = baikal_vdu_of_match, + .pm = pm_sleep_ptr(&baikal_vdu_pm_ops) + }, +}; + +module_param(mode_override, int, 0644); +module_param(hdmi_off, int, 0644); +module_param(lvds_off, int, 0644); + +module_platform_driver(baikal_vdu_platform_driver); + +MODULE_AUTHOR("Pavel Parkhomenko "); +MODULE_DESCRIPTION("Baikal Electronics BE-M1000 Video Display Unit (VDU) DRM Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_SOFTDEP("pre: baikal_hdmi"); diff --git a/drivers/gpu/drm/baikal/baikal_vdu_panel.c b/drivers/gpu/drm/baikal/baikal_vdu_panel.c new file mode 100644 index 0000000000000..9983f36b94f1e --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_panel.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#include +#include +#include +#include + +#include "baikal_vdu_drm.h" +#include "baikal_vdu_regs.h" + +static void baikal_lvds_connector_force(struct drm_connector *connector) +{ + struct baikal_lvds_bridge *bridge = connector_to_baikal_lvds_bridge(connector); + struct baikal_vdu_private *priv = bridge->vdu; + u32 cntl = readl(priv->regs + CR1); + if (connector->force == DRM_FORCE_OFF) + cntl &= ~CR1_LCE; + else + cntl |= CR1_LCE; + writel(cntl, priv->regs + CR1); +} + +static int baikal_lvds_connector_get_modes(struct drm_connector *connector) +{ + struct baikal_lvds_bridge *panel_bridge = + connector_to_baikal_lvds_bridge(connector); + + return drm_panel_get_modes(panel_bridge->panel, connector); +} + +static const struct drm_connector_helper_funcs +baikal_lvds_bridge_connector_helper_funcs = { + .get_modes = baikal_lvds_connector_get_modes, +}; + +static const struct drm_connector_funcs baikal_lvds_bridge_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .force = baikal_lvds_connector_force, +}; + +static int baikal_lvds_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) +{ + struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + struct drm_connector *connector = &panel_bridge->connector; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Missing encoder\n"); + return -ENODEV; + } + + drm_connector_helper_add(connector, + &baikal_lvds_bridge_connector_helper_funcs); + + ret = drm_connector_init(bridge->dev, connector, + &baikal_lvds_bridge_connector_funcs, + panel_bridge->connector_type); + if (ret) { + DRM_ERROR("Failed to initialize connector\n"); + return ret; + } + + ret = drm_connector_attach_encoder(&panel_bridge->connector, + bridge->encoder); + if (ret < 0) + return ret; + + return 0; +} + +static void baikal_lvds_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + + baikal_vdu_switch_on(panel_bridge->vdu); + drm_panel_prepare(panel_bridge->panel); +} + +static void baikal_lvds_bridge_enable(struct drm_bridge *bridge) +{ + struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + + drm_panel_enable(panel_bridge->panel); +} + +static void baikal_lvds_bridge_disable(struct drm_bridge *bridge) +{ + struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + + drm_panel_disable(panel_bridge->panel); +} + +static void baikal_lvds_bridge_post_disable(struct drm_bridge *bridge) +{ + struct baikal_lvds_bridge *panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + + drm_panel_unprepare(panel_bridge->panel); + baikal_vdu_switch_off(panel_bridge->vdu); +} + +static const struct drm_bridge_funcs baikal_lvds_bridge_funcs = { + .attach = baikal_lvds_bridge_attach, + .pre_enable = baikal_lvds_bridge_pre_enable, + .enable = baikal_lvds_bridge_enable, + .disable = baikal_lvds_bridge_disable, + .post_disable = baikal_lvds_bridge_post_disable, +}; + +static struct drm_bridge *baikal_lvds_bridge_add(struct drm_panel *panel, + u32 connector_type) +{ + struct baikal_lvds_bridge *panel_bridge; + + if (!panel) + return ERR_PTR(-EINVAL); + + panel_bridge = devm_kzalloc(panel->dev, sizeof(*panel_bridge), + GFP_KERNEL); + if (!panel_bridge) + return ERR_PTR(-ENOMEM); + + panel_bridge->connector_type = connector_type; + panel_bridge->panel = panel; + + panel_bridge->bridge.funcs = &baikal_lvds_bridge_funcs; +#ifdef CONFIG_OF + panel_bridge->bridge.of_node = panel->dev->of_node; +#endif + + drm_bridge_add(&panel_bridge->bridge); + + return &panel_bridge->bridge; +} + +static void baikal_lvds_bridge_remove(struct drm_bridge *bridge) +{ + struct baikal_lvds_bridge *panel_bridge; + + if (!bridge) + return; + + if (bridge->funcs != &baikal_lvds_bridge_funcs) + return; + + panel_bridge = bridge_to_baikal_lvds_bridge(bridge); + + drm_bridge_remove(bridge); + devm_kfree(panel_bridge->panel->dev, bridge); +} + +static void devm_baikal_lvds_bridge_release(struct device *dev, void *res) +{ + struct drm_bridge **bridge = res; + + baikal_lvds_bridge_remove(*bridge); +} + +struct drm_bridge *devm_baikal_lvds_bridge_add(struct device *dev, + struct drm_panel *panel, + u32 connector_type) +{ + struct drm_bridge **ptr, *bridge; + + ptr = devres_alloc(devm_baikal_lvds_bridge_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + bridge = baikal_lvds_bridge_add(panel, connector_type); + if (!IS_ERR(bridge)) { + *ptr = bridge; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return bridge; +} + +bool bridge_is_baikal_lvds_bridge(const struct drm_bridge *bridge) +{ + return bridge && (bridge->funcs == &baikal_lvds_bridge_funcs); +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_plane.c b/drivers/gpu/drm/baikal/baikal_vdu_plane.c new file mode 100644 index 0000000000000..d9bdb95a8d962 --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_plane.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "baikal_vdu_drm.h" +#include "baikal_vdu_regs.h" + +static void baikal_vdu_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *old_state) +{ + struct baikal_vdu_private *priv; + struct drm_plane_state *state = plane->state; + struct drm_crtc *crtc = state->crtc; + struct drm_framebuffer *fb = state->fb; + uint32_t cntl; + uint32_t addr; + unsigned long flags; + + if (!fb) + return; + + priv = crtc_to_baikal_vdu(crtc); + addr = drm_fb_dma_get_gem_addr(fb, state, 0); + + spin_lock_irqsave(&priv->lock, flags); + writel(addr, priv->regs + DBAR); + spin_unlock_irqrestore(&priv->lock, flags); + priv->counters[16]++; + + cntl = readl(priv->regs + CR1); + cntl &= ~CR1_BPP_MASK; + + /* Note that the the hardware's format reader takes 'r' from + * the low bit, while DRM formats list channels from high bit + * to low bit as you read left to right. + */ + switch (fb->format->format) { + case DRM_FORMAT_BGR888: + cntl |= CR1_BPP24 | CR1_FBP | CR1_BGR; + break; + case DRM_FORMAT_RGB888: + cntl |= CR1_BPP24 | CR1_FBP; + break; + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + cntl |= CR1_BPP24 | CR1_BGR; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + cntl |= CR1_BPP24; + break; + case DRM_FORMAT_BGR565: + cntl |= CR1_BPP16_565 | CR1_BGR; + break; + case DRM_FORMAT_RGB565: + cntl |= CR1_BPP16_565; + break; + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + cntl |= CR1_BPP16_555 | CR1_BGR; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + cntl |= CR1_BPP16_555; + break; + default: + WARN_ONCE(true, "Unknown FB format 0x%08x, set XRGB8888 instead\n", + fb->format->format); + cntl |= CR1_BPP24; + break; + } + + writel(cntl, priv->regs + CR1); +} + +static const struct drm_plane_helper_funcs baikal_vdu_primary_plane_helper_funcs = { + .atomic_update = baikal_vdu_primary_plane_atomic_update, +}; + +static const struct drm_plane_funcs baikal_vdu_primary_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .destroy = drm_plane_cleanup, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +int baikal_vdu_primary_plane_init(struct baikal_vdu_private *priv) +{ + struct drm_device *drm = priv->drm; + struct drm_plane *plane = &priv->primary; + static const u32 formats[] = { + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_BGR565, + DRM_FORMAT_RGB565, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + }; + int ret; + + ret = drm_universal_plane_init(drm, plane, 0, + &baikal_vdu_primary_plane_funcs, + formats, + ARRAY_SIZE(formats), + NULL, + DRM_PLANE_TYPE_PRIMARY, + NULL); + if (ret) + return ret; + + drm_plane_helper_add(plane, &baikal_vdu_primary_plane_helper_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/baikal/baikal_vdu_regs.h b/drivers/gpu/drm/baikal/baikal_vdu_regs.h new file mode 100644 index 0000000000000..d48ea8d12051f --- /dev/null +++ b/drivers/gpu/drm/baikal/baikal_vdu_regs.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019-2023 Baikal Electronics, JSC + * + * Author: Pavel Parkhomenko + * + */ + +#ifndef __BAIKAL_VDU_REGS_H__ +#define __BAIKAL_VDU_REGS_H__ + +#define CR1 0x000 +#define HTR 0x008 +#define VTR1 0x00C +#define VTR2 0x010 +#define PCTR 0x014 +#define ISR 0x018 +#define IMR 0x01C +#define IVR 0x020 +#define ISCR 0x024 +#define DBAR 0x028 +#define DCAR 0x02C +#define DEAR 0x030 +#define PWMFR 0x034 +#define PWMDCR 0x038 +#define HVTER 0x044 +#define HPPLOR 0x048 +#define GPIOR 0x1F8 +#define MRR 0xFFC + +#define INTR_UFU BIT(16) +#define INTR_BAU BIT(7) +#define INTR_VCT BIT(6) +#define INTR_MBE BIT(5) +#define INTR_FER BIT(4) +#define INTR_IFO BIT(3) +#define INTR_OFU BIT(0) + +#define CR1_FBP BIT(19) +#define CR1_FDW_MASK GENMASK(17, 16) +#define CR1_FDW_4_WORDS (0 << 16) +#define CR1_FDW_8_WORDS (1 << 16) +#define CR1_FDW_16_WORDS (2 << 16) +#define CR1_OPS_MASK GENMASK(14, 12) +#define CR1_OPS_LCD18 (0 << 13) +#define CR1_OPS_LCD24 (1 << 13) +#define CR1_OPS_565 (0 << 12) +#define CR1_OPS_555 (1 << 12) +#define CR1_VSP BIT(11) +#define CR1_HSP BIT(10) +#define CR1_DEP BIT(8) +#define CR1_BGR BIT(5) +#define CR1_BPP_MASK GENMASK(4, 2) +#define CR1_BPP1 (0 << 2) +#define CR1_BPP2 (1 << 2) +#define CR1_BPP4 (2 << 2) +#define CR1_BPP8 (3 << 2) +#define CR1_BPP16 (4 << 2) +#define CR1_BPP18 (5 << 2) +#define CR1_BPP24 (6 << 2) +#define CR1_LCE BIT(0) + +#define CR1_BPP16_555 ((CR1_BPP16) | (CR1_OPS_555)) +#define CR1_BPP16_565 ((CR1_BPP16) | (CR1_OPS_565)) + +#define VTR1_VBP_MASK GENMASK(23, 16) +#define VTR1_VBP(x) ((x) << 16) +#define VTR1_VBP_LSB_WIDTH 8 +#define VTR1_VFP_MASK GENMASK(15, 8) +#define VTR1_VFP(x) ((x) << 8) +#define VTR1_VFP_LSB_WIDTH 8 +#define VTR1_VSW_MASK GENMASK(7, 0) +#define VTR1_VSW(x) ((x) << 0) +#define VTR1_VSW_LSB_WIDTH 8 + +#define VTR2_LPP_MASK GENMASK(11, 0) + +#define HTR_HSW_MASK GENMASK(31, 24) +#define HTR_HSW(x) ((x) << 24) +#define HTR_HSW_LSB_WIDTH 8 +#define HTR_HBP_MASK GENMASK(23, 16) +#define HTR_HBP(x) ((x) << 16) +#define HTR_HBP_LSB_WIDTH 8 +#define HTR_PPL_MASK GENMASK(15, 8) +#define HTR_PPL(x) ((x) << 8) +#define HTR_HFP_MASK GENMASK(7, 0) +#define HTR_HFP(x) ((x) << 0) +#define HTR_HFP_LSB_WIDTH 8 + +#define PCTR_PCI2 BIT(11) +#define PCTR_PCR BIT(10) +#define PCTR_PCI BIT(9) +#define PCTR_PCB BIT(8) +#define PCTR_PCD_MASK GENMASK(7, 0) +#define PCTR_MAX_PCD 128 + +#define ISCR_VSC_OFF 0x0 +#define ISCR_VSC_VSW 0x4 +#define ISCR_VSC_VBP 0x5 +#define ISCR_VSC_VACTIVE 0x6 +#define ISCR_VSC_VFP 0x7 + +#define PWMFR_PWMPCR BIT(24) +#define PWMFR_PWMFCI BIT(23) +#define PWMFR_PWMFCE BIT(22) +#define PWMFR_PWMFCD_MASK GENMASK(21, 0) +#define PWMFR_PWMFCD(x) ((x) << 0) + +#define HVTER_VSWE_MASK GENMASK(25, 24) +#define HVTER_VSWE(x) ((x) << 24) +#define HVTER_HSWE_MASK GENMASK(17, 16) +#define HVTER_HSWE(x) ((x) << 16) +#define HVTER_VBPE_MASK GENMASK(13, 12) +#define HVTER_VBPE(x) ((x) << 12) +#define HVTER_VFPE_MASK GENMASK(9, 8) +#define HVTER_VFPE(x) ((x) << 8) +#define HVTER_HBPE_MASK GENMASK(5, 4) +#define HVTER_HBPE(x) ((x) << 4) +#define HVTER_HFPE_MASK GENMASK(1, 0) +#define HVTER_HFPE(x) ((x) << 0) + +#define HPPLOR_HPOE BIT(31) +#define HPPLOR_HPPLO_MASK GENMASK(11, 0) +#define HPPLOR_HPPLO(x) ((x) << 0) + +#define GPIOR_UHD_MASK GENMASK(23, 16) +#define GPIOR_UHD_SNGL_PORT (0 << 18) +#define GPIOR_UHD_DUAL_PORT (1 << 18) +#define GPIOR_UHD_QUAD_PORT (2 << 18) +#define GPIOR_UHD_ENB BIT(17) + +#define MRR_DEAR_MRR_MASK GENMASK(31, 3) +#define MRR_OUTSTND_RQ_MASK GENMASK(2, 0) +#define MRR_OUTSTND_RQ(x) ((x >> 1) << 0) +#define MRR_MAX_VALUE ((0xffffffff & MRR_DEAR_MRR_MASK) | MRR_OUTSTND_RQ(4)) + +#endif /* __BAIKAL_VDU_REGS_H__ */ diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3e6a4e2044c0e..6b51a41ee2c6f 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -162,6 +162,13 @@ config DRM_LVDS_CODEC Support for transparent LVDS encoders and decoders that don't require any configuration. +config DRM_BAIKAL_HDMI + tristate "Baikal-M HDMI transmitter" + default y if ARCH_BAIKAL + select DRM_DW_HDMI + help + Choose this if you want to use HDMI on Baikal-M. + config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW tristate "MegaChips stdp4028-ge-b850v3-fw and stdp2690-ge-b850v3-fw" depends on OF diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 6c1d794745054..0a7d15e3ba80e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3691,6 +3691,15 @@ void dw_hdmi_resume(struct dw_hdmi *hdmi) } EXPORT_SYMBOL_GPL(dw_hdmi_resume); +struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi) +{ + if (IS_ERR_OR_NULL(hdmi)) + return NULL; + + return &hdmi->bridge; +} +EXPORT_SYMBOL_GPL(dw_hdmi_get_bridge); + MODULE_AUTHOR("Sascha Hauer "); MODULE_AUTHOR("Andy Yan "); MODULE_AUTHOR("Yakir Yang "); diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c index cfbe020de54e0..4ae9218d5da41 100644 --- a/drivers/gpu/drm/drm_panel.c +++ b/drivers/gpu/drm/drm_panel.c @@ -295,6 +295,43 @@ int drm_panel_get_modes(struct drm_panel *panel, } EXPORT_SYMBOL(drm_panel_get_modes); +/** + * fwnode_drm_find_panel - look up a panel using a fwnode + * @fwnode: fwnode of the panel + * + * Searches the set of registered panels for one that matches the given fwnode. + * If a matching panel is found, return a pointer to it. + * + * Return: A pointer to the panel registered for the specified fwnode or + * an ERR_PTR() if no panel matching the fwnode can be found. + * + * Possible error codes returned by this function: + * + * - EPROBE_DEFER: the panel device has not been probed yet, and the caller + * should retry later + * - ENODEV: the device is not available + */ +struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode) +{ + struct drm_panel *panel; + + if (!fwnode_device_is_available(fwnode)) + return ERR_PTR(-ENODEV); + + mutex_lock(&panel_lock); + + list_for_each_entry(panel, &panel_list, list) { + if (panel->dev->fwnode == fwnode) { + mutex_unlock(&panel_lock); + return panel; + } + } + + mutex_unlock(&panel_lock); + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(fwnode_drm_find_panel); + #ifdef CONFIG_OF /** * of_drm_find_panel - look up a panel using a device tree node diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 6a46baa0737cd..08aafb5171aae 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -174,6 +174,8 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, void dw_hdmi_resume(struct dw_hdmi *hdmi); +struct drm_bridge *dw_hdmi_get_bridge(struct dw_hdmi *hdmi); + void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense); int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn, diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h index 10015891b056f..196cd8176ac44 100644 --- a/include/drm/drm_panel.h +++ b/include/drm/drm_panel.h @@ -28,6 +28,7 @@ #include #include #include +#include struct backlight_device; struct dentry; @@ -284,6 +285,8 @@ int drm_panel_disable(struct drm_panel *panel); int drm_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector); +struct drm_panel *fwnode_drm_find_panel(const struct fwnode_handle *fwnode); + #if defined(CONFIG_OF) && defined(CONFIG_DRM_PANEL) struct drm_panel *of_drm_find_panel(const struct device_node *np); int of_drm_get_panel_orientation(const struct device_node *np, -- 2.42.2