diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/gpu/drm/verisilicon | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/gpu/drm/verisilicon')
21 files changed, 2259 insertions, 0 deletions
diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig new file mode 100644 index 000000000..7cce86ec8 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_VERISILICON_DC + tristate "DRM Support for Verisilicon DC-series display controllers" + depends on DRM && COMMON_CLK + depends on RISCV || COMPILE_TEST + select DRM_BRIDGE_CONNECTOR + select DRM_CLIENT_SELECTION + select DRM_DISPLAY_HELPER + select DRM_GEM_DMA_HELPER + select DRM_KMS_HELPER + select REGMAP_MMIO + select VIDEOMODE_HELPERS + help + Choose this option if you have a SoC with Verisilicon DC-series + display controllers. If M is selected, the module will be called + verisilicon-dc. diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile new file mode 100644 index 000000000..426f4bcaa --- /dev/null +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + +verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o \ + vs_plane.o vs_primary_plane.o vs_cursor_plane.o + +obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.c b/drivers/gpu/drm/verisilicon/vs_bridge.c new file mode 100644 index 000000000..dc7c85b07 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/of.h> +#include <linux/regmap.h> + +#include <uapi/linux/media-bus-format.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_simple_kms_helper.h> + +#include "vs_bridge.h" +#include "vs_bridge_regs.h" +#include "vs_crtc.h" +#include "vs_dc.h" + +static int vs_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + + return drm_bridge_attach(encoder, vbridge->next_bridge, + bridge, flags); +} + +struct vsdc_dp_format { + u32 linux_fmt; + bool is_yuv; + u32 vsdc_fmt; +}; + +static struct vsdc_dp_format vsdc_dp_supported_fmts[] = { + /* default to RGB888 */ + { MEDIA_BUS_FMT_FIXED, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 }, + { MEDIA_BUS_FMT_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 }, + { MEDIA_BUS_FMT_RGB565_1X16, false, VSDC_DISP_DP_CONFIG_FMT_RGB565 }, + { MEDIA_BUS_FMT_RGB666_1X18, false, VSDC_DISP_DP_CONFIG_FMT_RGB666 }, + { MEDIA_BUS_FMT_RGB101010_1X30, + false, VSDC_DISP_DP_CONFIG_FMT_RGB101010 }, + { MEDIA_BUS_FMT_UYVY8_1X16, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 }, + { MEDIA_BUS_FMT_UYVY10_1X20, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 }, + { MEDIA_BUS_FMT_YUV8_1X24, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 }, + { MEDIA_BUS_FMT_YUV10_1X30, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 }, + { MEDIA_BUS_FMT_UYYVYY8_0_5X24, + true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 }, + { MEDIA_BUS_FMT_UYYVYY10_0_5X30, + true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 }, +}; + +static u32 *vs_bridge_atomic_get_output_bus_fmts_dpi(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + unsigned int *num_output_fmts) +{ + u32 *output_fmts; + + *num_output_fmts = 2; + + output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + /* TODO: support more DPI output formats */ + output_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + output_fmts[1] = MEDIA_BUS_FMT_FIXED; + + return output_fmts; +} + +static u32 *vs_bridge_atomic_get_output_bus_fmts_dp(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + unsigned int *num_output_fmts) +{ + u32 *output_fmts; + unsigned int i; + + *num_output_fmts = ARRAY_SIZE(vsdc_dp_supported_fmts); + + output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + for (i = 0; i < *num_output_fmts; i++) + output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt; + + return output_fmts; +} + +static bool vs_bridge_out_dp_fmt_supported(u32 out_fmt) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) + if (vsdc_dp_supported_fmts[i].linux_fmt == out_fmt) + return true; + + return false; +} + +static u32 *vs_bridge_atomic_get_input_bus_fmts_dp(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + if (!vs_bridge_out_dp_fmt_supported(output_fmt)) { + *num_input_fmts = 0; + return NULL; + } + + return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state, + crtc_state, + conn_state, + output_fmt, + num_input_fmts); +} + +static int vs_bridge_atomic_check_dp(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + if (!vs_bridge_out_dp_fmt_supported(bridge_state->output_bus_cfg.format)) + return -EINVAL; + + return 0; +} + +static void vs_bridge_enable_common(struct vs_crtc *crtc, + struct drm_bridge_state *br_state) +{ + struct vs_dc *dc = crtc->dc; + unsigned int output = crtc->id; + + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_DAT_POL); + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_DE_POL, + br_state->output_bus_cfg.flags & + DRM_BUS_FLAG_DE_LOW); + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_CLK_POL, + br_state->output_bus_cfg.flags & + DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE); + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_DE_EN | + VSDC_DISP_PANEL_CONFIG_DAT_EN | + VSDC_DISP_PANEL_CONFIG_CLK_EN); + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_RUNNING); + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START, + VSDC_DISP_PANEL_START_MULTI_DISP_SYNC); + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_START, + VSDC_DISP_PANEL_START_RUNNING(output)); + + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id), + VSDC_DISP_PANEL_CONFIG_EX_COMMIT); +} + +static void vs_bridge_atomic_enable_dpi(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + struct drm_bridge_state *br_state = + drm_atomic_get_new_bridge_state(state, bridge); + struct vs_crtc *crtc = vbridge->crtc; + struct vs_dc *dc = crtc->dc; + unsigned int output = crtc->id; + + regmap_clear_bits(dc->regs, VSDC_DISP_DP_CONFIG(output), + VSDC_DISP_DP_CONFIG_DP_EN); + regmap_write(dc->regs, VSDC_DISP_DPI_CONFIG(output), + VSDC_DISP_DPI_CONFIG_FMT_RGB888); + + vs_bridge_enable_common(crtc, br_state); +} + +static void vs_bridge_atomic_enable_dp(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + struct drm_bridge_state *br_state = + drm_atomic_get_new_bridge_state(state, bridge); + struct vs_crtc *crtc = vbridge->crtc; + struct vs_dc *dc = crtc->dc; + unsigned int output = crtc->id; + u32 dp_fmt; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) { + if (vsdc_dp_supported_fmts[i].linux_fmt == + br_state->output_bus_cfg.format) + break; + } + if (WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts))) + return; + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt; + dp_fmt |= VSDC_DISP_DP_CONFIG_DP_EN; + regmap_write(dc->regs, VSDC_DISP_DP_CONFIG(output), dp_fmt); + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_YUV, + vsdc_dp_supported_fmts[i].is_yuv); + + vs_bridge_enable_common(crtc, br_state); +} + +static void vs_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge); + struct vs_crtc *crtc = vbridge->crtc; + struct vs_dc *dc = crtc->dc; + unsigned int output = crtc->id; + + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START, + VSDC_DISP_PANEL_START_MULTI_DISP_SYNC | + VSDC_DISP_PANEL_START_RUNNING(output)); + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output), + VSDC_DISP_PANEL_CONFIG_RUNNING); + + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id), + VSDC_DISP_PANEL_CONFIG_EX_COMMIT); +} + +static const struct drm_bridge_funcs vs_dpi_bridge_funcs = { + .attach = vs_bridge_attach, + .atomic_enable = vs_bridge_atomic_enable_dpi, + .atomic_disable = vs_bridge_atomic_disable, + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, + .atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts_dpi, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, +}; + +static const struct drm_bridge_funcs vs_dp_bridge_funcs = { + .attach = vs_bridge_attach, + .atomic_enable = vs_bridge_atomic_enable_dp, + .atomic_disable = vs_bridge_atomic_disable, + .atomic_check = vs_bridge_atomic_check_dp, + .atomic_get_input_bus_fmts = vs_bridge_atomic_get_input_bus_fmts_dp, + .atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts_dp, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, +}; + +static int vs_bridge_detect_output_interface(struct device_node *of_node, + unsigned int output) +{ + int ret; + struct device_node *remote; + + remote = of_graph_get_remote_node(of_node, output, + VSDC_OUTPUT_INTERFACE_DPI); + if (remote) { + ret = VSDC_OUTPUT_INTERFACE_DPI; + } else { + remote = of_graph_get_remote_node(of_node, output, + VSDC_OUTPUT_INTERFACE_DP); + if (remote) + ret = VSDC_OUTPUT_INTERFACE_DP; + else + ret = -ENODEV; + } + + if (remote) + of_node_put(remote); + + return ret; +} + +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev, + struct vs_crtc *crtc) +{ + unsigned int output = crtc->id; + struct vs_bridge *bridge; + struct drm_bridge *next; + enum vs_bridge_output_interface intf; + const struct drm_bridge_funcs *bridge_funcs; + int ret, enctype; + + intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node, + output); + if (intf == -ENODEV) { + drm_dbg(drm_dev, "Skipping output %u\n", output); + return NULL; + } + + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node, + output, intf); + if (IS_ERR(next)) { + ret = PTR_ERR(next); + if (ret != -EPROBE_DEFER) + drm_err(drm_dev, + "Cannot get downstream bridge of output %u\n", + output); + return ERR_PTR(ret); + } + + if (intf == VSDC_OUTPUT_INTERFACE_DPI) + bridge_funcs = &vs_dpi_bridge_funcs; + else + bridge_funcs = &vs_dp_bridge_funcs; + + bridge = devm_drm_bridge_alloc(drm_dev->dev, struct vs_bridge, base, + bridge_funcs); + if (IS_ERR(bridge)) + return ERR_PTR(PTR_ERR(bridge)); + + bridge->crtc = crtc; + bridge->intf = intf; + bridge->next_bridge = next; + + if (intf == VSDC_OUTPUT_INTERFACE_DPI) + enctype = DRM_MODE_ENCODER_DPI; + else + enctype = DRM_MODE_ENCODER_NONE; + + bridge->enc = drmm_plain_encoder_alloc(drm_dev, NULL, enctype, NULL); + if (IS_ERR(bridge->enc)) { + drm_err(drm_dev, + "Cannot initialize encoder for output %u\n", output); + ret = PTR_ERR(bridge->enc); + return ERR_PTR(ret); + } + + bridge->enc->possible_crtcs = drm_crtc_mask(&crtc->base); + + ret = devm_drm_bridge_add(drm_dev->dev, &bridge->base); + if (ret) { + drm_err(drm_dev, + "Cannot add bridge for output %u\n", output); + return ERR_PTR(ret); + } + + ret = drm_bridge_attach(bridge->enc, &bridge->base, NULL, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + drm_err(drm_dev, + "Cannot attach bridge for output %u\n", output); + return ERR_PTR(ret); + } + + bridge->conn = drm_bridge_connector_init(drm_dev, bridge->enc); + if (IS_ERR(bridge->conn)) { + drm_err(drm_dev, + "Cannot create connector for output %u\n", output); + ret = PTR_ERR(bridge->conn); + return ERR_PTR(ret); + } + + return bridge; +} diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h new file mode 100644 index 000000000..70fee1749 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_bridge.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#ifndef _VS_BRIDGE_H_ +#define _VS_BRIDGE_H_ + +#include <linux/types.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> + +struct vs_crtc; + +enum vs_bridge_output_interface { + VSDC_OUTPUT_INTERFACE_DPI = 0, + VSDC_OUTPUT_INTERFACE_DP = 1 +}; + +struct vs_bridge { + struct drm_bridge base; + struct drm_encoder *enc; + struct drm_connector *conn; + + struct vs_crtc *crtc; + struct drm_bridge *next_bridge; + enum vs_bridge_output_interface intf; +}; + +static inline struct vs_bridge *drm_bridge_to_vs_bridge(struct drm_bridge *bridge) +{ + return container_of(bridge, struct vs_bridge, base); +} + +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev, + struct vs_crtc *crtc); +#endif /* _VS_BRIDGE_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_bridge_regs.h b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h new file mode 100644 index 000000000..9eb30e456 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_BRIDGE_REGS_H_ +#define _VS_BRIDGE_REGS_H_ + +#include <linux/bits.h> + +#define VSDC_DISP_PANEL_CONFIG(n) (0x1418 + 0x4 * (n)) +#define VSDC_DISP_PANEL_CONFIG_DE_EN BIT(0) +#define VSDC_DISP_PANEL_CONFIG_DE_POL BIT(1) +#define VSDC_DISP_PANEL_CONFIG_DAT_EN BIT(4) +#define VSDC_DISP_PANEL_CONFIG_DAT_POL BIT(5) +#define VSDC_DISP_PANEL_CONFIG_CLK_EN BIT(8) +#define VSDC_DISP_PANEL_CONFIG_CLK_POL BIT(9) +#define VSDC_DISP_PANEL_CONFIG_RUNNING BIT(12) +#define VSDC_DISP_PANEL_CONFIG_GAMMA BIT(13) +#define VSDC_DISP_PANEL_CONFIG_YUV BIT(16) + +#define VSDC_DISP_DPI_CONFIG(n) (0x14B8 + 0x4 * (n)) +#define VSDC_DISP_DPI_CONFIG_FMT_MASK GENMASK(2, 0) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB565 (0) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB666 (3) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB888 (5) +#define VSDC_DISP_DPI_CONFIG_FMT_RGB101010 (6) + +#define VSDC_DISP_PANEL_START 0x1CCC +#define VSDC_DISP_PANEL_START_RUNNING(n) BIT(n) +#define VSDC_DISP_PANEL_START_MULTI_DISP_SYNC BIT(3) + +#define VSDC_DISP_DP_CONFIG(n) (0x1CD0 + 0x4 * (n)) +#define VSDC_DISP_DP_CONFIG_DP_EN BIT(3) +#define VSDC_DISP_DP_CONFIG_FMT_MASK GENMASK(2, 0) +#define VSDC_DISP_DP_CONFIG_FMT_RGB565 (0) +#define VSDC_DISP_DP_CONFIG_FMT_RGB666 (1) +#define VSDC_DISP_DP_CONFIG_FMT_RGB888 (2) +#define VSDC_DISP_DP_CONFIG_FMT_RGB101010 (3) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_MASK GENMASK(7, 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 (2 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 (4 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 (8 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 (10 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 (12 << 4) +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 (13 << 4) + +#define VSDC_DISP_PANEL_CONFIG_EX(n) (0x2518 + 0x4 * (n)) +#define VSDC_DISP_PANEL_CONFIG_EX_COMMIT BIT(0) + +#endif /* _VS_BRIDGE_REGS_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c new file mode 100644 index 000000000..0b8a35d09 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/units.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_print.h> +#include <drm/drm_managed.h> +#include <drm/drm_vblank_helper.h> + +#include "vs_crtc_regs.h" +#include "vs_crtc.h" +#include "vs_dc.h" +#include "vs_dc_top_regs.h" +#include "vs_drm.h" +#include "vs_plane.h" + +static void vs_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + + drm_crtc_vblank_off(crtc); + + clk_disable_unprepare(dc->pix_clk[output]); +} + +static void vs_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + + drm_WARN_ON(&dc->drm_dev->base, + clk_prepare_enable(dc->pix_clk[output])); + + drm_crtc_vblank_on(crtc); +} + +static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + + regmap_write(dc->regs, VSDC_DISP_HSIZE(output), + VSDC_DISP_HSIZE_DISP(mode->hdisplay) | + VSDC_DISP_HSIZE_TOTAL(mode->htotal)); + regmap_write(dc->regs, VSDC_DISP_VSIZE(output), + VSDC_DISP_VSIZE_DISP(mode->vdisplay) | + VSDC_DISP_VSIZE_TOTAL(mode->vtotal)); + regmap_write(dc->regs, VSDC_DISP_HSYNC(output), + VSDC_DISP_HSYNC_START(mode->hsync_start) | + VSDC_DISP_HSYNC_END(mode->hsync_end) | + VSDC_DISP_HSYNC_EN); + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output), + VSDC_DISP_HSYNC_POL); + regmap_write(dc->regs, VSDC_DISP_VSYNC(output), + VSDC_DISP_VSYNC_START(mode->vsync_start) | + VSDC_DISP_VSYNC_END(mode->vsync_end) | + VSDC_DISP_VSYNC_EN); + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output), + VSDC_DISP_VSYNC_POL); + + WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000)); +} + +static enum drm_mode_status +vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + long rate; + + if (mode->htotal > VSDC_DISP_TIMING_VALUE_MAX) + return MODE_BAD_HVALUE; + if (mode->vtotal > VSDC_DISP_TIMING_VALUE_MAX) + return MODE_BAD_VVALUE; + + rate = clk_round_rate(dc->pix_clk[output], mode->clock * HZ_PER_KHZ); + if (rate <= 0) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static bool vs_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *m, + struct drm_display_mode *adjusted_mode) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + unsigned int output = vcrtc->id; + long clk_rate; + + drm_mode_set_crtcinfo(adjusted_mode, 0); + + /* Feedback the pixel clock to crtc_clock */ + clk_rate = adjusted_mode->crtc_clock * HZ_PER_KHZ; + clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate); + if (clk_rate <= 0) + return false; + + adjusted_mode->crtc_clock = clk_rate / HZ_PER_KHZ; + + return true; +} + +static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = { + .atomic_flush = drm_crtc_vblank_atomic_flush, + .atomic_enable = vs_crtc_atomic_enable, + .atomic_disable = vs_crtc_atomic_disable, + .mode_set_nofb = vs_crtc_mode_set_nofb, + .mode_valid = vs_crtc_mode_valid, + .mode_fixup = vs_crtc_mode_fixup, +}; + +static int vs_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + + regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id)); + + return 0; +} + +static void vs_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + struct vs_dc *dc = vcrtc->dc; + + regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id)); +} + +static const struct drm_crtc_funcs vs_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, + .enable_vblank = vs_crtc_enable_vblank, + .disable_vblank = vs_crtc_disable_vblank, +}; + +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc, + unsigned int output) +{ + struct vs_crtc *vcrtc; + struct drm_plane *primary, *cursor; + int ret; + + vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL); + if (!vcrtc) + return ERR_PTR(-ENOMEM); + vcrtc->dc = dc; + vcrtc->id = output; + + /* Create our primary plane */ + primary = vs_primary_plane_init(drm_dev, dc); + if (IS_ERR(primary)) { + drm_err(drm_dev, "Couldn't create the primary plane\n"); + return ERR_PTR(PTR_ERR(primary)); + } + + /* Create our cursor plane */ + cursor = vs_cursor_plane_init(drm_dev, dc); + if (IS_ERR(cursor)) { + drm_err(drm_dev, "Couldn't create the cursor plane\n"); + return ERR_CAST(cursor); + } + + ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base, + primary, + cursor, + &vs_crtc_funcs, + NULL); + if (ret) { + drm_err(drm_dev, "Couldn't initialize CRTC\n"); + return ERR_PTR(ret); + } + + drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs); + + return vcrtc; +} diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.h b/drivers/gpu/drm/verisilicon/vs_crtc.h new file mode 100644 index 000000000..b45580bd9 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#ifndef _VS_CRTC_H_ +#define _VS_CRTC_H_ + +#include <drm/drm_crtc.h> +#include <drm/drm_vblank.h> + +#define VSDC_DISP_TIMING_VALUE_MAX BIT_MASK(15) + +struct vs_dc; + +struct vs_crtc { + struct drm_crtc base; + + struct vs_dc *dc; + unsigned int id; +}; + +static inline struct vs_crtc *drm_crtc_to_vs_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct vs_crtc, base); +} + +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc, + unsigned int output); + +#endif /* _VS_CRTC_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_crtc_regs.h b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h new file mode 100644 index 000000000..c7930e817 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_CRTC_REGS_H_ +#define _VS_CRTC_REGS_H_ + +#include <linux/bits.h> + +#define VSDC_DISP_DITHER_CONFIG(n) (0x1410 + 0x4 * (n)) + +#define VSDC_DISP_DITHER_TABLE_LOW(n) (0x1420 + 0x4 * (n)) +#define VSDC_DISP_DITHER_TABLE_LOW_DEFAULT 0x7B48F3C0 + +#define VSDC_DISP_DITHER_TABLE_HIGH(n) (0x1428 + 0x4 * (n)) +#define VSDC_DISP_DITHER_TABLE_HIGH_DEFAULT 0x596AD1E2 + +#define VSDC_DISP_HSIZE(n) (0x1430 + 0x4 * (n)) +#define VSDC_DISP_HSIZE_DISP_MASK GENMASK(14, 0) +#define VSDC_DISP_HSIZE_DISP(v) ((v) << 0) +#define VSDC_DISP_HSIZE_TOTAL_MASK GENMASK(30, 16) +#define VSDC_DISP_HSIZE_TOTAL(v) ((v) << 16) + +#define VSDC_DISP_HSYNC(n) (0x1438 + 0x4 * (n)) +#define VSDC_DISP_HSYNC_START_MASK GENMASK(14, 0) +#define VSDC_DISP_HSYNC_START(v) ((v) << 0) +#define VSDC_DISP_HSYNC_END_MASK GENMASK(29, 15) +#define VSDC_DISP_HSYNC_END(v) ((v) << 15) +#define VSDC_DISP_HSYNC_EN BIT(30) +#define VSDC_DISP_HSYNC_POL BIT(31) + +#define VSDC_DISP_VSIZE(n) (0x1440 + 0x4 * (n)) +#define VSDC_DISP_VSIZE_DISP_MASK GENMASK(14, 0) +#define VSDC_DISP_VSIZE_DISP(v) ((v) << 0) +#define VSDC_DISP_VSIZE_TOTAL_MASK GENMASK(30, 16) +#define VSDC_DISP_VSIZE_TOTAL(v) ((v) << 16) + +#define VSDC_DISP_VSYNC(n) (0x1448 + 0x4 * (n)) +#define VSDC_DISP_VSYNC_START_MASK GENMASK(14, 0) +#define VSDC_DISP_VSYNC_START(v) ((v) << 0) +#define VSDC_DISP_VSYNC_END_MASK GENMASK(29, 15) +#define VSDC_DISP_VSYNC_END(v) ((v) << 15) +#define VSDC_DISP_VSYNC_EN BIT(30) +#define VSDC_DISP_VSYNC_POL BIT(31) + +#define VSDC_DISP_CURRENT_LOCATION(n) (0x1450 + 0x4 * (n)) + +#define VSDC_DISP_GAMMA_INDEX(n) (0x1458 + 0x4 * (n)) + +#define VSDC_DISP_GAMMA_DATA(n) (0x1460 + 0x4 * (n)) + +#define VSDC_DISP_IRQ_STA 0x147C + +#define VSDC_DISP_IRQ_EN 0x1480 + +#endif /* _VS_CRTC_REGS_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane.c b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c new file mode 100644 index 000000000..fa4f601dd --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Institute of Software, Chinese Academy of Sciences (ISCAS) + * + * Authors: + * Icenowy Zheng <zhengxingda@iscas.ac.cn> + */ + +#include <linux/log2.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_plane.h> +#include <drm/drm_print.h> + +#include "vs_crtc.h" +#include "vs_plane.h" +#include "vs_dc.h" +#include "vs_hwdb.h" +#include "vs_cursor_plane_regs.h" + +#define VSDC_MIN_CURSOR_SIZE 32 +#define VSDC_MAX_CURSOR_SIZE 256 + +#define VSDC_CURSOR_LOCATION_MAX_POSITIVE BIT_MASK(15) +#define VSDC_CURSOR_LOCATION_MAX_NEGATIVE BIT_MASK(5) + +static bool vs_cursor_plane_check_coord(int32_t coord) +{ + if (coord >= 0) + return coord <= VSDC_CURSOR_LOCATION_MAX_POSITIVE; + else + return (-coord) <= VSDC_CURSOR_LOCATION_MAX_NEGATIVE; +} + +static int vs_cursor_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_crtc_state *crtc_state = NULL; + struct vs_crtc *vcrtc; + struct vs_dc *dc; + int ret; + + if (crtc) + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); + if (ret) + return ret; + + if (!new_plane_state->visible) + return 0; /* Skip validity check */ + + vcrtc = drm_crtc_to_vs_crtc(crtc); + dc = vcrtc->dc; + + /* Only certain PoT square sizes is supported. */ + if (!is_power_of_2(new_plane_state->crtc_w) || + new_plane_state->crtc_w < VSDC_MIN_CURSOR_SIZE || + new_plane_state->crtc_w > dc->identity.max_cursor_size) + return -EINVAL; + + if (new_plane_state->crtc_w != new_plane_state->crtc_h) + return -EINVAL; + + /* Check if the cursor is inside the register fields' range */ + if (!vs_cursor_plane_check_coord(new_plane_state->crtc_x) || + !vs_cursor_plane_check_coord(new_plane_state->crtc_y)) + return -EINVAL; + + /* Extra line padding isn't supported */ + if (fb->pitches[0] != + drm_format_info_min_pitch(fb->format, 0, new_plane_state->crtc_w)) + return -EINVAL; + + return 0; +} + +static void vs_cursor_plane_commit(struct vs_dc *dc, unsigned int output) +{ + regmap_set_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_COMMIT | + VSDC_CURSOR_CONFIG_IMG_UPDATE); +} + +static void vs_cursor_plane_atomic_enable(struct drm_plane *plane, + struct drm_atomic_commit *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, + plane); + struct drm_crtc *crtc = state->crtc; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + unsigned int output = vcrtc->id; + struct vs_dc *dc = vcrtc->dc; + + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_FMT_MASK, + VSDC_CURSOR_CONFIG_FMT_ARGB8888); + + vs_cursor_plane_commit(dc, output); +} + +static void vs_cursor_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_commit *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_old_plane_state(atomic_state, + plane); + struct drm_crtc *crtc = state->crtc; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + unsigned int output = vcrtc->id; + struct vs_dc *dc = vcrtc->dc; + + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_FMT_MASK, + VSDC_CURSOR_CONFIG_FMT_OFF); + + vs_cursor_plane_commit(dc, output); +} + +static void vs_cursor_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, + plane); + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = state->crtc; + struct vs_dc *dc; + struct vs_crtc *vcrtc; + unsigned int output; + dma_addr_t dma_addr; + + if (!state->visible) { + vs_cursor_plane_atomic_disable(plane, atomic_state); + return; + } + + vcrtc = drm_crtc_to_vs_crtc(crtc); + output = vcrtc->id; + dc = vcrtc->dc; + + /* Other sizes should be rejected by atomic_check */ + switch (state->crtc_w) { + case 32: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_32); + break; + case 64: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_64); + break; + case 128: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_128); + break; + case 256: + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_SIZE_MASK, + VSDC_CURSOR_CONFIG_SIZE_256); + break; + } + + dma_addr = vs_fb_get_dma_addr(fb, &state->src); + + regmap_write(dc->regs, VSDC_CURSOR_ADDRESS(output), + lower_32_bits(dma_addr)); + + /* + * The X_OFF and Y_OFF fields define which point does the LOCATION + * register represent in the cursor image, and LOCATION register + * values are unsigned. To for positive left-top coordinates the + * offset is set to 0 and the location is set to the coordinate, for + * negative coordinates the location is set to 0 and the offset + * is set to the opposite number of the coordinate to offset the + * cursor image partly off-screen. + */ + if (state->crtc_x >= 0) { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_X_OFF_MASK, 0); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_X_MASK, + VSDC_CURSOR_LOCATION_X(state->crtc_x)); + } else { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_X_OFF_MASK, + -state->crtc_x); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_X_MASK, 0); + } + + if (state->crtc_y >= 0) { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_Y_OFF_MASK, 0); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_Y_MASK, + VSDC_CURSOR_LOCATION_Y(state->crtc_y)); + } else { + regmap_update_bits(dc->regs, VSDC_CURSOR_CONFIG(output), + VSDC_CURSOR_CONFIG_Y_OFF_MASK, + -state->crtc_y); + regmap_update_bits(dc->regs, VSDC_CURSOR_LOCATION(output), + VSDC_CURSOR_LOCATION_Y_MASK, 0); + } + + vs_cursor_plane_commit(dc, output); +} + +static const struct drm_plane_helper_funcs vs_cursor_plane_helper_funcs = { + .atomic_check = vs_cursor_plane_atomic_check, + .atomic_update = vs_cursor_plane_atomic_update, + .atomic_enable = vs_cursor_plane_atomic_enable, + .atomic_disable = vs_cursor_plane_atomic_disable, +}; + +static const struct drm_plane_funcs vs_cursor_plane_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +static const u32 vs_cursor_plane_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +static const u64 vs_cursor_plane_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID, /* sentinel */ +}; + +struct drm_plane *vs_cursor_plane_init(struct drm_device *drm_dev, + struct vs_dc *dc) +{ + int32_t max_cursor_size = dc->identity.max_cursor_size; + struct drm_plane *plane; + + if (drm_WARN_ON_ONCE(drm_dev, max_cursor_size < VSDC_MIN_CURSOR_SIZE || + max_cursor_size > VSDC_MAX_CURSOR_SIZE)) + return ERR_PTR(-EINVAL); + + plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, + &vs_cursor_plane_funcs, + vs_cursor_plane_formats, + ARRAY_SIZE(vs_cursor_plane_formats), + vs_cursor_plane_modifiers, + DRM_PLANE_TYPE_CURSOR, + NULL); + + if (IS_ERR(plane)) + return plane; + + drm_plane_helper_add(plane, &vs_cursor_plane_helper_funcs); + + return plane; +} diff --git a/drivers/gpu/drm/verisilicon/vs_cursor_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_cursor_plane_regs.h new file mode 100644 index 000000000..99693f2c9 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_cursor_plane_regs.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_CURSOR_PLANE_REGS_H_ +#define _VS_CURSOR_PLANE_REGS_H_ + +#include <linux/bits.h> + +#define VSDC_CURSOR_CONFIG(n) (0x1468 + 0x1080 * (n)) +#define VSDC_CURSOR_CONFIG_FMT_MASK GENMASK(1, 0) +#define VSDC_CURSOR_CONFIG_FMT_ARGB8888 (0x2 << 0) +#define VSDC_CURSOR_CONFIG_FMT_OFF (0x0 << 0) +#define VSDC_CURSOR_CONFIG_IMG_UPDATE BIT(2) +#define VSDC_CURSOR_CONFIG_COMMIT BIT(3) +#define VSDC_CURSOR_CONFIG_SIZE_MASK GENMASK(7, 5) +#define VSDC_CURSOR_CONFIG_SIZE_32 (0x0 << 5) +#define VSDC_CURSOR_CONFIG_SIZE_64 (0x1 << 5) +#define VSDC_CURSOR_CONFIG_SIZE_128 (0x2 << 5) +#define VSDC_CURSOR_CONFIG_SIZE_256 (0x3 << 5) +#define VSDC_CURSOR_CONFIG_Y_OFF_MASK GENMASK(12, 8) +#define VSDC_CURSOR_CONFIG_Y_OFF(v) ((v) << 8) +#define VSDC_CURSOR_CONFIG_X_OFF_MASK GENMASK(20, 16) +#define VSDC_CURSOR_CONFIG_X_OFF(v) ((v) << 16) + +#define VSDC_CURSOR_ADDRESS(n) (0x146C + 0x1080 * (n)) + +#define VSDC_CURSOR_LOCATION(n) (0x1470 + 0x1080 * (n)) +#define VSDC_CURSOR_LOCATION_X_MASK GENMASK(14, 0) +#define VSDC_CURSOR_LOCATION_X(v) ((v) << 0) +#define VSDC_CURSOR_LOCATION_Y_MASK GENMASK(30, 16) +#define VSDC_CURSOR_LOCATION_Y(v) ((v) << 16) + +#define VSDC_CURSOR_BACKGROUND(n) (0x1474 + 0x1080 * (n)) +#define VSDC_CURSOR_BACKGRUOND_DEFAULT 0x00FFFFFF + +#define VSDC_CURSOR_FOREGROUND(n) (0x1478 + 0x1080 * (n)) +#define VSDC_CURSOR_FOREGRUOND_DEFAULT 0x00AAAAAA + +#endif /* _VS_CURSOR_PLANE_REGS_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c new file mode 100644 index 000000000..dad9967bc --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/dma-mapping.h> +#include <linux/irqreturn.h> +#include <linux/of.h> +#include <linux/of_graph.h> + +#include "vs_crtc.h" +#include "vs_dc.h" +#include "vs_dc_top_regs.h" +#include "vs_drm.h" +#include "vs_hwdb.h" + +static const struct regmap_config vs_dc_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + /* VSDC_OVL_CONFIG_EX(1) */ + .max_register = 0x2544, +}; + +static const struct of_device_id vs_dc_driver_dt_match[] = { + { .compatible = "verisilicon,dc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, vs_dc_driver_dt_match); + +static irqreturn_t vs_dc_irq_handler(int irq, void *private) +{ + struct vs_dc *dc = private; + u32 irqs; + + regmap_read(dc->regs, VSDC_TOP_IRQ_ACK, &irqs); + + vs_drm_handle_irq(dc, irqs); + + return IRQ_HANDLED; +} + +static int vs_dc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vs_dc *dc; + void __iomem *regs; + unsigned int port_count, i; + /* pix%u */ + char pixclk_name[14]; + int irq, ret; + + if (!dev->of_node) { + dev_err(dev, "can't find DC devices\n"); + return -ENODEV; + } + + port_count = of_graph_get_port_count(dev->of_node); + if (!port_count) { + dev_err(dev, "can't find DC downstream ports\n"); + return -ENODEV; + } + if (port_count > VSDC_MAX_OUTPUTS) { + dev_err(dev, "too many DC downstream ports than possible\n"); + return -EINVAL; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "No suitable DMA available\n"); + return ret; + } + + dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL); + if (!dc) + return -ENOMEM; + + dc->rsts[0].id = "core"; + dc->rsts[1].id = "axi"; + dc->rsts[2].id = "ahb"; + + ret = devm_reset_control_bulk_get_optional_shared(dev, VSDC_RESET_COUNT, + dc->rsts); + if (ret) { + dev_err(dev, "can't get reset lines\n"); + return ret; + } + + dc->core_clk = devm_clk_get_enabled(dev, "core"); + if (IS_ERR(dc->core_clk)) { + dev_err(dev, "can't get core clock\n"); + return PTR_ERR(dc->core_clk); + } + + dc->axi_clk = devm_clk_get_enabled(dev, "axi"); + if (IS_ERR(dc->axi_clk)) { + dev_err(dev, "can't get axi clock\n"); + return PTR_ERR(dc->axi_clk); + } + + dc->ahb_clk = devm_clk_get_enabled(dev, "ahb"); + if (IS_ERR(dc->ahb_clk)) { + dev_err(dev, "can't get ahb clock\n"); + return PTR_ERR(dc->ahb_clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "can't get irq\n"); + return irq; + } + + ret = reset_control_bulk_deassert(VSDC_RESET_COUNT, dc->rsts); + if (ret) { + dev_err(dev, "can't deassert reset lines\n"); + return ret; + } + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) { + dev_err(dev, "can't map registers"); + ret = PTR_ERR(regs); + goto err_rst_assert; + } + + dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg); + if (IS_ERR(dc->regs)) { + ret = PTR_ERR(dc->regs); + goto err_rst_assert; + } + + ret = vs_fill_chip_identity(dc->regs, &dc->identity); + if (ret) + goto err_rst_assert; + + dev_info(dev, "Found DC%x rev %x customer %x\n", dc->identity.model, + dc->identity.revision, dc->identity.customer_id); + + if (port_count > dc->identity.display_count) { + dev_err(dev, "too many downstream ports than HW capability\n"); + ret = -EINVAL; + goto err_rst_assert; + } + + for (i = 0; i < dc->identity.display_count; i++) { + snprintf(pixclk_name, sizeof(pixclk_name), "pix%u", i); + dc->pix_clk[i] = devm_clk_get(dev, pixclk_name); + if (IS_ERR(dc->pix_clk[i])) { + dev_err(dev, "can't get pixel clk %u\n", i); + ret = PTR_ERR(dc->pix_clk[i]); + goto err_rst_assert; + } + } + + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0, + dev_name(dev), dc); + if (ret) { + dev_err(dev, "can't request irq\n"); + goto err_rst_assert; + } + + dev_set_drvdata(dev, dc); + + ret = vs_drm_initialize(dc, pdev); + if (ret) + goto err_rst_assert; + + return 0; + +err_rst_assert: + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts); + return ret; +} + +static void vs_dc_remove(struct platform_device *pdev) +{ + struct vs_dc *dc = dev_get_drvdata(&pdev->dev); + + vs_drm_finalize(dc); + + dev_set_drvdata(&pdev->dev, NULL); + + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts); +} + +static void vs_dc_shutdown(struct platform_device *pdev) +{ + struct vs_dc *dc = dev_get_drvdata(&pdev->dev); + + vs_drm_shutdown_handler(dc); +} + +static struct platform_driver vs_dc_platform_driver = { + .probe = vs_dc_probe, + .remove = vs_dc_remove, + .shutdown = vs_dc_shutdown, + .driver = { + .name = "verisilicon-dc", + .of_match_table = vs_dc_driver_dt_match, + }, +}; + +module_platform_driver(vs_dc_platform_driver); + +MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>"); +MODULE_DESCRIPTION("Verisilicon display controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/verisilicon/vs_dc.h b/drivers/gpu/drm/verisilicon/vs_dc.h new file mode 100644 index 000000000..ed1016f18 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_DC_H_ +#define _VS_DC_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <drm/drm_device.h> + +#include "vs_hwdb.h" + +#define VSDC_MAX_OUTPUTS 2 +#define VSDC_RESET_COUNT 3 + +struct vs_drm_dev; +struct vs_crtc; + +struct vs_dc { + struct regmap *regs; + struct clk *core_clk; + struct clk *axi_clk; + struct clk *ahb_clk; + struct clk *pix_clk[VSDC_MAX_OUTPUTS]; + struct reset_control_bulk_data rsts[VSDC_RESET_COUNT]; + + struct vs_drm_dev *drm_dev; + struct vs_chip_identity identity; +}; + +#endif /* _VS_DC_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h new file mode 100644 index 000000000..50509bbbf --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_DC_TOP_H_ +#define _VS_DC_TOP_H_ + +#include <linux/bits.h> + +#define VSDC_TOP_RST 0x0000 + +#define VSDC_TOP_IRQ_ACK 0x0010 +#define VSDC_TOP_IRQ_VSYNC(n) BIT(n) + +#define VSDC_TOP_IRQ_EN 0x0014 + +#define VSDC_TOP_CHIP_MODEL 0x0020 + +#define VSDC_TOP_CHIP_REV 0x0024 + +#define VSDC_TOP_CHIP_CUSTOMER_ID 0x0030 + +#endif /* _VS_DC_TOP_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_drm.c b/drivers/gpu/drm/verisilicon/vs_drm.c new file mode 100644 index 000000000..fd259d53f --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_drm.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/aperture.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/console.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_dumb_buffers.h> +#include <drm/drm_fbdev_dma.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "vs_bridge.h" +#include "vs_crtc.h" +#include "vs_dc.h" +#include "vs_dc_top_regs.h" +#include "vs_drm.h" + +#define DRIVER_NAME "verisilicon" +#define DRIVER_DESC "Verisilicon DC-series display controller driver" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static int vs_gem_dumb_create(struct drm_file *file_priv, + struct drm_device *drm, + struct drm_mode_create_dumb *args) +{ + int ret; + + /* The hardware wants 128B-aligned pitches for linear buffers. */ + ret = drm_mode_size_dumb(drm, args, 128, 0); + if (ret) + return ret; + + return drm_gem_dma_dumb_create_internal(file_priv, drm, args); +} + +DEFINE_DRM_GEM_FOPS(vs_drm_driver_fops); + +static const struct drm_driver vs_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &vs_drm_driver_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + + /* GEM Operations */ + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create), + DRM_FBDEV_DMA_DRIVER_OPS, +}; + +static const struct drm_mode_config_funcs vs_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static struct drm_mode_config_helper_funcs vs_mode_config_helper_funcs = { + .atomic_commit_tail = drm_atomic_helper_commit_tail, +}; + +static void vs_mode_config_init(struct drm_device *drm) +{ + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = 8192; + drm->mode_config.max_height = 8192; + drm->mode_config.funcs = &vs_mode_config_funcs; + drm->mode_config.helper_private = &vs_mode_config_helper_funcs; +} + +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vs_drm_dev *vdrm; + struct drm_device *drm; + struct vs_crtc *crtc; + struct vs_bridge *bridge; + unsigned int i; + int ret; + + vdrm = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_dev, base); + if (IS_ERR(vdrm)) + return PTR_ERR(vdrm); + + drm = &vdrm->base; + vdrm->dc = dc; + dc->drm_dev = vdrm; + + ret = drmm_mode_config_init(drm); + if (ret) + return ret; + + /* Remove early framebuffers (ie. simple-framebuffer) */ + ret = aperture_remove_all_conflicting_devices(DRIVER_NAME); + if (ret) + return ret; + + for (i = 0; i < dc->identity.display_count; i++) { + crtc = vs_crtc_init(drm, dc, i); + if (IS_ERR(crtc)) + return PTR_ERR(crtc); + + bridge = vs_bridge_init(drm, crtc); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + + vdrm->crtcs[i] = crtc; + } + + ret = drm_vblank_init(drm, dc->identity.display_count); + if (ret) + return ret; + + vs_mode_config_init(drm); + + /* Enable connectors polling */ + drm_kms_helper_poll_init(drm); + + drm_mode_config_reset(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto err_fini_poll; + + drm_client_setup(drm, NULL); + + return 0; + +err_fini_poll: + drm_kms_helper_poll_fini(drm); + return ret; +} + +void vs_drm_finalize(struct vs_dc *dc) +{ + struct vs_drm_dev *vdrm = dc->drm_dev; + struct drm_device *drm = &vdrm->base; + + drm_dev_unregister(drm); + drm_kms_helper_poll_fini(drm); + drm_atomic_helper_shutdown(drm); + dc->drm_dev = NULL; +} + +void vs_drm_shutdown_handler(struct vs_dc *dc) +{ + struct vs_drm_dev *vdrm = dc->drm_dev; + + drm_atomic_helper_shutdown(&vdrm->base); +} + +void vs_drm_handle_irq(struct vs_dc *dc, u32 irqs) +{ + unsigned int i; + + for (i = 0; i < dc->identity.display_count; i++) { + if (irqs & VSDC_TOP_IRQ_VSYNC(i)) { + irqs &= ~VSDC_TOP_IRQ_VSYNC(i); + if (dc->drm_dev->crtcs[i]) + drm_crtc_handle_vblank(&dc->drm_dev->crtcs[i]->base); + } + } + + if (irqs) + drm_warn_once(&dc->drm_dev->base, + "Unknown Verisilicon DC interrupt 0x%x fired!\n", + irqs); +} diff --git a/drivers/gpu/drm/verisilicon/vs_drm.h b/drivers/gpu/drm/verisilicon/vs_drm.h new file mode 100644 index 000000000..606338206 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_drm.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#ifndef _VS_DRM_H_ +#define _VS_DRM_H_ + +#include <linux/platform_device.h> +#include <linux/types.h> + +#include <drm/drm_device.h> + +struct vs_dc; + +struct vs_drm_dev { + struct drm_device base; + + struct vs_dc *dc; + struct vs_crtc *crtcs[VSDC_MAX_OUTPUTS]; +}; + +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev); +void vs_drm_finalize(struct vs_dc *dc); +void vs_drm_shutdown_handler(struct vs_dc *dc); +void vs_drm_handle_irq(struct vs_dc *dc, u32 irqs); + +#endif /* _VS_DRM_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c new file mode 100644 index 000000000..2a0f7c59a --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/errno.h> + +#include <drm/drm_fourcc.h> + +#include "vs_dc_top_regs.h" +#include "vs_hwdb.h" + +static const u32 vs_formats_array_no_yuv444[] = { + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + /* TODO: non-RGB formats */ +}; + +static const u32 vs_formats_array_with_yuv444[] = { + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, + /* TODO: non-RGB formats */ +}; + +static const struct vs_formats vs_formats_no_yuv444 = { + .array = vs_formats_array_no_yuv444, + .num = ARRAY_SIZE(vs_formats_array_no_yuv444) +}; + +static const struct vs_formats vs_formats_with_yuv444 = { + .array = vs_formats_array_with_yuv444, + .num = ARRAY_SIZE(vs_formats_array_with_yuv444) +}; + +static struct vs_chip_identity vs_chip_identities[] = { + { + .model = 0x8200, + .revision = 0x5720, + .customer_id = ~0U, + + .display_count = 2, + .max_cursor_size = 64, + .formats = &vs_formats_no_yuv444, + }, + { + .model = 0x8200, + .revision = 0x5721, + .customer_id = 0x30B, + + .display_count = 2, + .max_cursor_size = 64, + .formats = &vs_formats_no_yuv444, + }, + { + .model = 0x8200, + .revision = 0x5720, + .customer_id = 0x310, + + .display_count = 2, + .max_cursor_size = 64, + .formats = &vs_formats_with_yuv444, + }, + { + .model = 0x8200, + .revision = 0x5720, + .customer_id = 0x311, + + .display_count = 2, + .max_cursor_size = 64, + .formats = &vs_formats_no_yuv444, + }, +}; + +int vs_fill_chip_identity(struct regmap *regs, + struct vs_chip_identity *ident) +{ + u32 model; + u32 revision; + u32 customer_id; + int i; + + regmap_read(regs, VSDC_TOP_CHIP_MODEL, &model); + regmap_read(regs, VSDC_TOP_CHIP_REV, &revision); + regmap_read(regs, VSDC_TOP_CHIP_CUSTOMER_ID, &customer_id); + + for (i = 0; i < ARRAY_SIZE(vs_chip_identities); i++) { + if (vs_chip_identities[i].model == model && + vs_chip_identities[i].revision == revision && + (vs_chip_identities[i].customer_id == customer_id || + vs_chip_identities[i].customer_id == ~0U)) { + memcpy(ident, &vs_chip_identities[i], sizeof(*ident)); + ident->customer_id = customer_id; + return 0; + } + } + + return -EINVAL; +} diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h new file mode 100644 index 000000000..2065ecb73 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#ifndef _VS_HWDB_H_ +#define _VS_HWDB_H_ + +#include <linux/regmap.h> +#include <linux/types.h> + +struct vs_formats { + const u32 *array; + unsigned int num; +}; + +struct vs_chip_identity { + u32 model; + u32 revision; + u32 customer_id; + + u32 display_count; + /* + * The hardware only supports square cursor planes, so this field + * is both the maximum width and height in pixels. + */ + int32_t max_cursor_size; + const struct vs_formats *formats; +}; + +int vs_fill_chip_identity(struct regmap *regs, + struct vs_chip_identity *ident); + +#endif /* _VS_HWDB_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c new file mode 100644 index 000000000..d81f7b8f4 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_plane.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/errno.h> +#include <linux/printk.h> + +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_fb_dma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_dma_helper.h> +#include <drm/drm_print.h> + +#include "vs_plane.h" + +int drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format) +{ + switch (drm_format) { + case DRM_FORMAT_XRGB4444: + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_BGRX4444: + vs_format->color = VSDC_COLOR_FORMAT_X4R4G4B4; + break; + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_BGRA4444: + vs_format->color = VSDC_COLOR_FORMAT_A4R4G4B4; + break; + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_BGRX5551: + vs_format->color = VSDC_COLOR_FORMAT_X1R5G5B5; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGRA5551: + vs_format->color = VSDC_COLOR_FORMAT_A1R5G5B5; + break; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + vs_format->color = VSDC_COLOR_FORMAT_R5G6B5; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_BGRX8888: + vs_format->color = VSDC_COLOR_FORMAT_X8R8G8B8; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_BGRA8888: + vs_format->color = VSDC_COLOR_FORMAT_A8R8G8B8; + break; + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_BGRA1010102: + vs_format->color = VSDC_COLOR_FORMAT_A2R10G10B10; + break; + default: + return -EINVAL; + } + + switch (drm_format) { + case DRM_FORMAT_RGBX4444: + case DRM_FORMAT_RGBA4444: + case DRM_FORMAT_RGBX5551: + case DRM_FORMAT_RGBA5551: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBA1010102: + vs_format->swizzle = VSDC_SWIZZLE_RGBA; + break; + case DRM_FORMAT_XBGR4444: + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XBGR1555: + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_BGR565: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_ABGR2101010: + vs_format->swizzle = VSDC_SWIZZLE_ABGR; + break; + case DRM_FORMAT_BGRX4444: + case DRM_FORMAT_BGRA4444: + case DRM_FORMAT_BGRX5551: + case DRM_FORMAT_BGRA5551: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRA1010102: + vs_format->swizzle = VSDC_SWIZZLE_BGRA; + break; + default: + /* N/A for YUV formats */ + vs_format->swizzle = VSDC_SWIZZLE_ARGB; + } + + /* N/A for non-YUV formats */ + vs_format->uv_swizzle = false; + + return 0; +} + +dma_addr_t vs_fb_get_dma_addr(struct drm_framebuffer *fb, + const struct drm_rect *src_rect) +{ + struct drm_gem_dma_object *gem; + dma_addr_t dma_addr; + + /* Get the physical address of the buffer in memory */ + gem = drm_fb_dma_get_gem_obj(fb, 0); + + /* Compute the start of the displayed memory */ + dma_addr = gem->dma_addr + fb->offsets[0]; + + /* Fixup framebuffer address for src coordinates */ + dma_addr += drm_format_info_min_pitch(fb->format, 0, + src_rect->x1 >> 16); + dma_addr += (src_rect->y1 >> 16) * fb->pitches[0]; + + return dma_addr; +} + +struct drm_plane_state *vs_plane_duplicate_state(struct drm_plane *plane) +{ + struct vs_plane_state *vs_state, *vs_state_old; + + if (drm_WARN_ON(plane->dev, !plane->state)) + return NULL; + + vs_state_old = to_vs_plane_state(plane->state); + + vs_state = kzalloc_obj(*vs_state, GFP_KERNEL); + if (!vs_state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &vs_state->base); + + memcpy(&vs_state->format, &vs_state_old->format, + sizeof(struct vs_format)); + + return &vs_state->base; +} + +void vs_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(state); +} + +/* Called during init to allocate the plane's atomic state. */ +void vs_plane_reset(struct drm_plane *plane) +{ + struct vs_plane_state *vs_state; + + if (plane->state) { + __drm_atomic_helper_plane_destroy_state(plane->state); + kfree(plane->state); + plane->state = NULL; + } + + vs_state = kzalloc_obj(*vs_state, GFP_KERNEL); + if (!vs_state) + return; + + __drm_atomic_helper_plane_reset(plane, &vs_state->base); +} diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h new file mode 100644 index 000000000..32ed1d70f --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_plane.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_PLANE_H_ +#define _VS_PLANE_H_ + +#include <linux/types.h> + +#include <drm/drm_device.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_plane.h> +#include <drm/drm_rect.h> + +#define VSDC_MAKE_PLANE_SIZE(w, h) (((w) & 0x7fff) | (((h) & 0x7fff) << 15)) +#define VSDC_MAKE_PLANE_POS(x, y) (((x) & 0x7fff) | (((y) & 0x7fff) << 15)) + +struct vs_dc; + +enum vs_color_format { + VSDC_COLOR_FORMAT_X4R4G4B4, + VSDC_COLOR_FORMAT_A4R4G4B4, + VSDC_COLOR_FORMAT_X1R5G5B5, + VSDC_COLOR_FORMAT_A1R5G5B5, + VSDC_COLOR_FORMAT_R5G6B5, + VSDC_COLOR_FORMAT_X8R8G8B8, + VSDC_COLOR_FORMAT_A8R8G8B8, + VSDC_COLOR_FORMAT_YUY2, + VSDC_COLOR_FORMAT_UYVY, + VSDC_COLOR_FORMAT_INDEX8, + VSDC_COLOR_FORMAT_MONOCHROME, + VSDC_COLOR_FORMAT_YV12 = 0xf, + VSDC_COLOR_FORMAT_A8, + VSDC_COLOR_FORMAT_NV12, + VSDC_COLOR_FORMAT_NV16, + VSDC_COLOR_FORMAT_RG16, + VSDC_COLOR_FORMAT_R8, + VSDC_COLOR_FORMAT_NV12_10BIT, + VSDC_COLOR_FORMAT_A2R10G10B10, + VSDC_COLOR_FORMAT_NV16_10BIT, + VSDC_COLOR_FORMAT_INDEX1, + VSDC_COLOR_FORMAT_INDEX2, + VSDC_COLOR_FORMAT_INDEX4, + VSDC_COLOR_FORMAT_P010, + VSDC_COLOR_FORMAT_YUV444, + VSDC_COLOR_FORMAT_YUV444_10BIT +}; + +enum vs_swizzle { + VSDC_SWIZZLE_ARGB, + VSDC_SWIZZLE_RGBA, + VSDC_SWIZZLE_ABGR, + VSDC_SWIZZLE_BGRA, +}; + +struct vs_format { + enum vs_color_format color; + enum vs_swizzle swizzle; + bool uv_swizzle; +}; + +struct vs_plane_state { + struct drm_plane_state base; + + struct vs_format format; +}; + +static inline struct vs_plane_state *to_vs_plane_state(struct drm_plane_state *state) +{ + return container_of(state, struct vs_plane_state, base); +} + +int drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format); +dma_addr_t vs_fb_get_dma_addr(struct drm_framebuffer *fb, + const struct drm_rect *src_rect); + +struct drm_plane_state *vs_plane_duplicate_state(struct drm_plane *plane); +void vs_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state); +void vs_plane_reset(struct drm_plane *plane); + +struct drm_plane *vs_primary_plane_init(struct drm_device *dev, struct vs_dc *dc); +struct drm_plane *vs_cursor_plane_init(struct drm_device *dev, struct vs_dc *dc); + +#endif /* _VS_PLANE_H_ */ diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c new file mode 100644 index 000000000..1f2be41ae --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + */ + +#include <linux/regmap.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_plane.h> +#include <drm/drm_print.h> + +#include "vs_crtc.h" +#include "vs_plane.h" +#include "vs_dc.h" +#include "vs_primary_plane_regs.h" + +static int vs_primary_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct vs_plane_state *new_vs_plane_state = to_vs_plane_state(new_plane_state); + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_crtc_state *crtc_state = NULL; + int ret; + + if (crtc) + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, true); + if (ret) + return ret; + + if (!new_plane_state->visible) + return 0; + + ret = drm_format_to_vs_format(fb->format->format, + &new_vs_plane_state->format); + if (drm_WARN_ON_ONCE(plane->dev, ret)) + return ret; + + return 0; +} + +static void vs_primary_plane_commit(struct vs_dc *dc, unsigned int output) +{ + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output), + VSDC_FB_CONFIG_EX_COMMIT); +} + +static void vs_primary_plane_atomic_enable(struct drm_plane *plane, + struct drm_atomic_commit *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, + plane); + struct drm_crtc *crtc = state->crtc; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + unsigned int output = vcrtc->id; + struct vs_dc *dc = vcrtc->dc; + + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output), + VSDC_FB_CONFIG_EX_FB_EN); + regmap_update_bits(dc->regs, VSDC_FB_CONFIG_EX(output), + VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK, + VSDC_FB_CONFIG_EX_DISPLAY_ID(output)); + + vs_primary_plane_commit(dc, output); +} + +static void vs_primary_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_commit *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_old_plane_state(atomic_state, + plane); + struct drm_crtc *crtc = state->crtc; + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc); + unsigned int output = vcrtc->id; + struct vs_dc *dc = vcrtc->dc; + + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output), + VSDC_FB_CONFIG_EX_FB_EN); + + vs_primary_plane_commit(dc, output); +} + +static void vs_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *atomic_state) +{ + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state, + plane); + struct vs_plane_state *vs_state = to_vs_plane_state(state); + struct drm_framebuffer *fb = state->fb; + struct drm_crtc *crtc = state->crtc; + struct vs_dc *dc; + struct vs_crtc *vcrtc; + unsigned int output; + dma_addr_t dma_addr; + + if (!state->visible) { + vs_primary_plane_atomic_disable(plane, atomic_state); + return; + } + + vcrtc = drm_crtc_to_vs_crtc(crtc); + output = vcrtc->id; + dc = vcrtc->dc; + + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output), + VSDC_FB_CONFIG_FMT_MASK, + VSDC_FB_CONFIG_FMT(vs_state->format.color)); + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output), + VSDC_FB_CONFIG_SWIZZLE_MASK, + VSDC_FB_CONFIG_SWIZZLE(vs_state->format.swizzle)); + regmap_assign_bits(dc->regs, VSDC_FB_CONFIG(output), + VSDC_FB_CONFIG_UV_SWIZZLE_EN, + vs_state->format.uv_swizzle); + + dma_addr = vs_fb_get_dma_addr(fb, &state->src); + + regmap_write(dc->regs, VSDC_FB_ADDRESS(output), + lower_32_bits(dma_addr)); + regmap_write(dc->regs, VSDC_FB_STRIDE(output), + fb->pitches[0]); + + regmap_write(dc->regs, VSDC_FB_TOP_LEFT(output), + VSDC_MAKE_PLANE_POS(state->crtc_x, state->crtc_y)); + regmap_write(dc->regs, VSDC_FB_BOTTOM_RIGHT(output), + VSDC_MAKE_PLANE_POS(state->crtc_x + state->crtc_w, + state->crtc_y + state->crtc_h)); + regmap_write(dc->regs, VSDC_FB_SIZE(output), + VSDC_MAKE_PLANE_SIZE(state->crtc_w, state->crtc_h)); + + regmap_write(dc->regs, VSDC_FB_BLEND_CONFIG(output), + VSDC_FB_BLEND_CONFIG_BLEND_DISABLE); + + vs_primary_plane_commit(dc, output); +} + +static const struct drm_plane_helper_funcs vs_primary_plane_helper_funcs = { + .atomic_check = vs_primary_plane_atomic_check, + .atomic_update = vs_primary_plane_atomic_update, + .atomic_enable = vs_primary_plane_atomic_enable, + .atomic_disable = vs_primary_plane_atomic_disable, +}; + +static const struct drm_plane_funcs vs_primary_plane_funcs = { + .atomic_destroy_state = vs_plane_destroy_state, + .atomic_duplicate_state = vs_plane_duplicate_state, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = vs_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc *dc) +{ + struct drm_plane *plane; + + plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0, + &vs_primary_plane_funcs, + dc->identity.formats->array, + dc->identity.formats->num, + NULL, + DRM_PLANE_TYPE_PRIMARY, + NULL); + + if (IS_ERR(plane)) + return plane; + + drm_plane_helper_add(plane, &vs_primary_plane_helper_funcs); + + return plane; +} diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h new file mode 100644 index 000000000..cbb125c46 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me> + * + * Based on vs_dc_hw.h, which is: + * Copyright (C) 2023 VeriSilicon Holdings Co., Ltd. + */ + +#ifndef _VS_PRIMARY_PLANE_REGS_H_ +#define _VS_PRIMARY_PLANE_REGS_H_ + +#include <linux/bits.h> + +#define VSDC_FB_ADDRESS(n) (0x1400 + 0x4 * (n)) + +#define VSDC_FB_STRIDE(n) (0x1408 + 0x4 * (n)) + +#define VSDC_FB_CONFIG(n) (0x1518 + 0x4 * (n)) +#define VSDC_FB_CONFIG_CLEAR_EN BIT(8) +#define VSDC_FB_CONFIG_ROT_MASK GENMASK(13, 11) +#define VSDC_FB_CONFIG_ROT(v) ((v) << 11) +#define VSDC_FB_CONFIG_YUV_SPACE_MASK GENMASK(16, 14) +#define VSDC_FB_CONFIG_YUV_SPACE(v) ((v) << 14) +#define VSDC_FB_CONFIG_TILE_MODE_MASK GENMASK(21, 17) +#define VSDC_FB_CONFIG_TILE_MODE(v) ((v) << 14) +#define VSDC_FB_CONFIG_SCALE_EN BIT(22) +#define VSDC_FB_CONFIG_SWIZZLE_MASK GENMASK(24, 23) +#define VSDC_FB_CONFIG_SWIZZLE(v) ((v) << 23) +#define VSDC_FB_CONFIG_UV_SWIZZLE_EN BIT(25) +#define VSDC_FB_CONFIG_FMT_MASK GENMASK(31, 26) +#define VSDC_FB_CONFIG_FMT(v) ((v) << 26) + +#define VSDC_FB_SIZE(n) (0x1810 + 0x4 * (n)) +/* Fill with value generated with VSDC_MAKE_PLANE_SIZE(w, h) */ + +#define VSDC_FB_CONFIG_EX(n) (0x1CC0 + 0x4 * (n)) +#define VSDC_FB_CONFIG_EX_COMMIT BIT(12) +#define VSDC_FB_CONFIG_EX_FB_EN BIT(13) +#define VSDC_FB_CONFIG_EX_ZPOS_MASK GENMASK(18, 16) +#define VSDC_FB_CONFIG_EX_ZPOS(v) ((v) << 16) +#define VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK GENMASK(19, 19) +#define VSDC_FB_CONFIG_EX_DISPLAY_ID(v) ((v) << 19) + +#define VSDC_FB_TOP_LEFT(n) (0x24D8 + 0x4 * (n)) +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */ + +#define VSDC_FB_BOTTOM_RIGHT(n) (0x24E0 + 0x4 * (n)) +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */ + +#define VSDC_FB_BLEND_CONFIG(n) (0x2510 + 0x4 * (n)) +#define VSDC_FB_BLEND_CONFIG_BLEND_DISABLE BIT(1) + +#endif /* _VS_PRIMARY_PLANE_REGS_H_ */ |
