Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_VC4
tristate "Broadcom VC4 Graphics"
depends on ARCH_BCM || ARCH_BCM2835 || COMPILE_TEST
# Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only
# happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE.
depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
depends on DRM
depends on SND && SND_SOC
depends on COMMON_CLK
depends on PM
select DRM_CLIENT_SELECTION
select DRM_DISPLAY_HDMI_AUDIO_HELPER
select DRM_DISPLAY_HDMI_HELPER
select DRM_DISPLAY_HDMI_STATE_HELPER
select DRM_DISPLAY_HELPER
select DRM_EXEC
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_PANEL_BRIDGE
select SND_PCM
select SND_PCM_ELD
select SND_SOC_GENERIC_DMAENGINE_PCM
select SND_SOC_HDMI_CODEC
select DRM_MIPI_DSI
help
Choose this option if you have a system that has a Broadcom
VC4 GPU, such as the Raspberry Pi or other BCM2708/BCM2835.
This driver requires that "avoid_warnings=2" be present in
the config.txt for the firmware, to keep it from smashing
our display setup.
config DRM_VC4_HDMI_CEC
bool "Broadcom VC4 HDMI CEC Support"
depends on DRM_VC4
select CEC_CORE
select DRM_DISPLAY_HDMI_CEC_HELPER
help
Choose this option if you have a Broadcom VC4 GPU
and want to use CEC.
config DRM_VC4_KUNIT_TEST
tristate "KUnit tests for VC4" if !KUNIT_ALL_TESTS
depends on DRM_VC4 && KUNIT
select DRM_KUNIT_TEST_HELPERS
default KUNIT_ALL_TESTS
help
This builds unit tests for the VC4 DRM/KMS driver. This option is
not useful for distributions or general kernels, but only for kernel
developers working on the VC4 driver.
For more information on KUnit and unit tests in general,
please refer to the KUnit documentation in
Documentation/dev-tools/kunit/.
If in doubt, say "N".
+37
View File
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: GPL-2.0
# Please keep these build lists sorted!
# core driver code
vc4-y := \
vc4_bo.o \
vc4_crtc.o \
vc4_drv.o \
vc4_dpi.o \
vc4_dsi.o \
vc4_fence.o \
vc4_kms.o \
vc4_gem.o \
vc4_hdmi.o \
vc4_hdmi_phy.o \
vc4_vec.o \
vc4_hvs.o \
vc4_irq.o \
vc4_perfmon.o \
vc4_plane.o \
vc4_render_cl.o \
vc4_trace_points.o \
vc4_txp.o \
vc4_v3d.o \
vc4_validate.o \
vc4_validate_shaders.o
vc4-$(CONFIG_DRM_VC4_KUNIT_TEST) += \
tests/vc4_mock.o \
tests/vc4_mock_crtc.o \
tests/vc4_mock_output.o \
tests/vc4_mock_plane.o \
tests/vc4_test_pv_muxing.o
vc4-$(CONFIG_DEBUG_FS) += vc4_debugfs.o
obj-$(CONFIG_DRM_VC4) += vc4.o
@@ -0,0 +1,13 @@
CONFIG_ARCH_BCM=y
CONFIG_ARCH_BCM2835=y
CONFIG_BCM2835_MBOX=y
CONFIG_KUNIT=y
CONFIG_DRM=y
CONFIG_DRM_VC4=y
CONFIG_DRM_VC4_KUNIT_TEST=y
CONFIG_MAILBOX=y
CONFIG_RASPBERRYPI_FIRMWARE=y
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_SOUND=y
CONFIG_COMMON_CLK=y
@@ -0,0 +1,207 @@
// SPDX-License-Identifier: GPL-2.0
#include <drm/drm_drv.h>
#include <drm/drm_kunit_helpers.h>
#include <kunit/test.h>
#include "vc4_mock.h"
struct vc4_mock_output_desc {
enum vc4_encoder_type vc4_encoder_type;
unsigned int encoder_type;
unsigned int connector_type;
};
#define VC4_MOCK_OUTPUT_DESC(_vc4_type, _etype, _ctype) \
{ \
.vc4_encoder_type = _vc4_type, \
.encoder_type = _etype, \
.connector_type = _ctype, \
}
struct vc4_mock_pipe_desc {
const struct vc4_crtc_data *data;
const struct vc4_mock_output_desc *outputs;
unsigned int noutputs;
};
#define VC4_MOCK_CRTC_DESC(_data, ...) \
{ \
.data = _data, \
.outputs = (struct vc4_mock_output_desc[]) { __VA_ARGS__ }, \
.noutputs = sizeof((struct vc4_mock_output_desc[]) { __VA_ARGS__ }) / \
sizeof(struct vc4_mock_output_desc), \
}
#define VC4_MOCK_PIXELVALVE_DESC(_data, ...) \
VC4_MOCK_CRTC_DESC(&(_data)->base, __VA_ARGS__)
struct vc4_mock_desc {
const struct vc4_mock_pipe_desc *pipes;
unsigned int npipes;
};
#define VC4_MOCK_DESC(...) \
{ \
.pipes = (struct vc4_mock_pipe_desc[]) { __VA_ARGS__ }, \
.npipes = sizeof((struct vc4_mock_pipe_desc[]) { __VA_ARGS__ }) / \
sizeof(struct vc4_mock_pipe_desc), \
}
static const struct vc4_mock_desc vc4_mock =
VC4_MOCK_DESC(
VC4_MOCK_CRTC_DESC(&bcm2835_txp_data.base,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0,
DRM_MODE_ENCODER_VIRTUAL,
DRM_MODE_CONNECTOR_WRITEBACK)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2835_pv0_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_DSI0,
DRM_MODE_ENCODER_DSI,
DRM_MODE_CONNECTOR_DSI),
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_DPI,
DRM_MODE_ENCODER_DPI,
DRM_MODE_CONNECTOR_DPI)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2835_pv1_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_DSI1,
DRM_MODE_ENCODER_DSI,
DRM_MODE_CONNECTOR_DSI)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2835_pv2_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_HDMI0,
DRM_MODE_ENCODER_TMDS,
DRM_MODE_CONNECTOR_HDMIA),
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_VEC,
DRM_MODE_ENCODER_TVDAC,
DRM_MODE_CONNECTOR_Composite)),
);
static const struct vc4_mock_desc vc5_mock =
VC4_MOCK_DESC(
VC4_MOCK_CRTC_DESC(&bcm2835_txp_data.base,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0,
DRM_MODE_ENCODER_VIRTUAL,
DRM_MODE_CONNECTOR_WRITEBACK)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2711_pv0_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_DSI0,
DRM_MODE_ENCODER_DSI,
DRM_MODE_CONNECTOR_DSI),
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_DPI,
DRM_MODE_ENCODER_DPI,
DRM_MODE_CONNECTOR_DPI)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2711_pv1_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_DSI1,
DRM_MODE_ENCODER_DSI,
DRM_MODE_CONNECTOR_DSI)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2711_pv2_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_HDMI0,
DRM_MODE_ENCODER_TMDS,
DRM_MODE_CONNECTOR_HDMIA)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2711_pv3_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_VEC,
DRM_MODE_ENCODER_TVDAC,
DRM_MODE_CONNECTOR_Composite)),
VC4_MOCK_PIXELVALVE_DESC(&bcm2711_pv4_data,
VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_HDMI1,
DRM_MODE_ENCODER_TMDS,
DRM_MODE_CONNECTOR_HDMIA)),
);
static int __build_one_pipe(struct kunit *test, struct drm_device *drm,
const struct vc4_mock_pipe_desc *pipe)
{
struct drm_plane *plane;
struct vc4_dummy_crtc *dummy_crtc;
struct drm_crtc *crtc;
unsigned int i;
plane = vc4_dummy_plane(test, drm, DRM_PLANE_TYPE_PRIMARY);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
dummy_crtc = vc4_mock_pv(test, drm, plane, pipe->data);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dummy_crtc);
crtc = &dummy_crtc->crtc.base;
for (i = 0; i < pipe->noutputs; i++) {
const struct vc4_mock_output_desc *mock_output = &pipe->outputs[i];
struct vc4_dummy_output *dummy_output;
dummy_output = vc4_dummy_output(test, drm, crtc,
mock_output->vc4_encoder_type,
mock_output->encoder_type,
mock_output->connector_type);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dummy_output);
}
return 0;
}
static int __build_mock(struct kunit *test, struct drm_device *drm,
const struct vc4_mock_desc *mock)
{
unsigned int i;
for (i = 0; i < mock->npipes; i++) {
const struct vc4_mock_pipe_desc *pipe = &mock->pipes[i];
int ret;
ret = __build_one_pipe(test, drm, pipe);
KUNIT_ASSERT_EQ(test, ret, 0);
}
return 0;
}
KUNIT_DEFINE_ACTION_WRAPPER(kunit_action_drm_dev_unregister,
drm_dev_unregister,
struct drm_device *);
static struct vc4_dev *__mock_device(struct kunit *test, enum vc4_gen gen)
{
struct drm_device *drm;
const struct drm_driver *drv = (gen == VC4_GEN_5) ? &vc5_drm_driver : &vc4_drm_driver;
const struct vc4_mock_desc *desc = (gen == VC4_GEN_5) ? &vc5_mock : &vc4_mock;
struct vc4_dev *vc4;
struct device *dev;
int ret;
dev = drm_kunit_helper_alloc_device(test);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
vc4 = drm_kunit_helper_alloc_drm_device_with_driver(test, dev,
struct vc4_dev, base,
drv);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4);
vc4->dev = dev;
vc4->gen = gen;
vc4->hvs = __vc4_hvs_alloc(vc4, NULL, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4->hvs);
drm = &vc4->base;
ret = __build_mock(test, drm, desc);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = vc4_kms_load(drm);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = drm_dev_register(drm, 0);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = kunit_add_action_or_reset(test,
kunit_action_drm_dev_unregister,
drm);
KUNIT_ASSERT_EQ(test, ret, 0);
return vc4;
}
struct vc4_dev *vc4_mock_device(struct kunit *test)
{
return __mock_device(test, VC4_GEN_4);
}
struct vc4_dev *vc5_mock_device(struct kunit *test)
{
return __mock_device(test, VC4_GEN_5);
}
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef VC4_MOCK_H_
#define VC4_MOCK_H_
#include "../vc4_drv.h"
static inline
struct drm_crtc *vc4_find_crtc_for_encoder(struct kunit *test,
struct drm_device *drm,
struct drm_encoder *encoder)
{
struct drm_crtc *crtc;
KUNIT_ASSERT_EQ(test, hweight32(encoder->possible_crtcs), 1);
drm_for_each_crtc(crtc, drm)
if (encoder->possible_crtcs & drm_crtc_mask(crtc))
return crtc;
return NULL;
}
struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm,
enum drm_plane_type type);
struct vc4_dummy_crtc {
struct vc4_crtc crtc;
};
struct vc4_dummy_crtc *vc4_mock_pv(struct kunit *test,
struct drm_device *drm,
struct drm_plane *plane,
const struct vc4_crtc_data *data);
struct vc4_dummy_output {
struct vc4_encoder encoder;
struct drm_connector connector;
};
#define encoder_to_vc4_dummy_output(_enc) \
container_of_const(_enc, struct vc4_dummy_output, encoder.base)
struct vc4_dummy_output *vc4_dummy_output(struct kunit *test,
struct drm_device *drm,
struct drm_crtc *crtc,
enum vc4_encoder_type vc4_encoder_type,
unsigned int kms_encoder_type,
unsigned int connector_type);
struct vc4_dev *vc4_mock_device(struct kunit *test);
struct vc4_dev *vc5_mock_device(struct kunit *test);
int vc4_mock_atomic_add_output(struct kunit *test,
struct drm_atomic_commit *state,
enum vc4_encoder_type type);
int vc4_mock_atomic_del_output(struct kunit *test,
struct drm_atomic_commit *state,
enum vc4_encoder_type type);
#endif // VC4_MOCK_H_
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <kunit/test.h>
#include "vc4_mock.h"
static const struct drm_crtc_helper_funcs vc4_dummy_crtc_helper_funcs = {
.atomic_check = vc4_crtc_atomic_check,
};
static const struct drm_crtc_funcs vc4_dummy_crtc_funcs = {
.atomic_destroy_state = vc4_crtc_destroy_state,
.atomic_duplicate_state = vc4_crtc_duplicate_state,
.reset = vc4_crtc_reset,
};
struct vc4_dummy_crtc *vc4_mock_pv(struct kunit *test,
struct drm_device *drm,
struct drm_plane *plane,
const struct vc4_crtc_data *data)
{
struct vc4_dummy_crtc *dummy_crtc;
struct vc4_crtc *vc4_crtc;
int ret;
dummy_crtc = drmm_kzalloc(drm, sizeof(*dummy_crtc), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, dummy_crtc);
vc4_crtc = &dummy_crtc->crtc;
ret = __vc4_crtc_init(drm, NULL,
vc4_crtc, data, plane,
&vc4_dummy_crtc_funcs,
&vc4_dummy_crtc_helper_funcs,
false);
KUNIT_ASSERT_EQ(test, ret, 0);
return dummy_crtc;
}
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_encoder.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <kunit/test.h>
#include "vc4_mock.h"
static const struct drm_connector_helper_funcs vc4_dummy_connector_helper_funcs = {
};
static const struct drm_connector_funcs vc4_dummy_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.reset = drm_atomic_helper_connector_reset,
};
struct vc4_dummy_output *vc4_dummy_output(struct kunit *test,
struct drm_device *drm,
struct drm_crtc *crtc,
enum vc4_encoder_type vc4_encoder_type,
unsigned int kms_encoder_type,
unsigned int connector_type)
{
struct vc4_dummy_output *dummy_output;
struct drm_connector *conn;
struct drm_encoder *enc;
int ret;
dummy_output = drmm_kzalloc(drm, sizeof(*dummy_output), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dummy_output);
dummy_output->encoder.type = vc4_encoder_type;
enc = &dummy_output->encoder.base;
ret = drmm_encoder_init(drm, enc,
NULL,
kms_encoder_type,
NULL);
KUNIT_ASSERT_EQ(test, ret, 0);
enc->possible_crtcs = drm_crtc_mask(crtc);
conn = &dummy_output->connector;
ret = drmm_connector_init(drm, conn,
&vc4_dummy_connector_funcs,
connector_type,
NULL);
KUNIT_ASSERT_EQ(test, ret, 0);
drm_connector_helper_add(conn, &vc4_dummy_connector_helper_funcs);
drm_connector_attach_encoder(conn, enc);
return dummy_output;
}
static const struct drm_display_mode default_mode = {
DRM_SIMPLE_MODE(640, 480, 64, 48)
};
/**
* vc4_mock_atomic_add_output() - Enables an output in a state
* @test: The test context object
* @state: Atomic state to enable the output in.
* @type: Type of the output encoder
*
* Adds an output CRTC and connector to a state, and enables them.
*
* Returns:
* 0 on success, a negative error code on failure. If the error is
* EDEADLK, the entire atomic sequence must be restarted. All other
* errors are fatal.
*/
int vc4_mock_atomic_add_output(struct kunit *test,
struct drm_atomic_commit *state,
enum vc4_encoder_type type)
{
struct drm_device *drm = state->dev;
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
struct vc4_dummy_output *output;
struct drm_connector *conn;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
int ret;
encoder = vc4_find_encoder_by_type(drm, type);
if (!encoder)
return -ENODEV;
crtc = vc4_find_crtc_for_encoder(test, drm, encoder);
if (!crtc)
return -ENODEV;
output = encoder_to_vc4_dummy_output(encoder);
conn = &output->connector;
conn_state = drm_atomic_get_connector_state(state, conn);
if (IS_ERR(conn_state))
return PTR_ERR(conn_state);
ret = drm_atomic_set_crtc_for_connector(conn_state, crtc);
if (ret)
return ret;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
ret = drm_atomic_set_mode_for_crtc(crtc_state, &default_mode);
if (ret)
return ret;
crtc_state->active = true;
return 0;
}
/**
* vc4_mock_atomic_del_output() - Disables an output in a state
* @test: The test context object
* @state: Atomic state to disable the output in.
* @type: Type of the output encoder
*
* Adds an output CRTC and connector to a state, and disables them.
*
* Returns:
* 0 on success, a negative error code on failure. If the error is
* EDEADLK, the entire atomic sequence must be restarted. All other
* errors are fatal.
*/
int vc4_mock_atomic_del_output(struct kunit *test,
struct drm_atomic_commit *state,
enum vc4_encoder_type type)
{
struct drm_device *drm = state->dev;
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
struct vc4_dummy_output *output;
struct drm_connector *conn;
struct drm_encoder *encoder;
struct drm_crtc *crtc;
int ret;
encoder = vc4_find_encoder_by_type(drm, type);
if (!encoder)
return -ENODEV;
crtc = vc4_find_crtc_for_encoder(test, drm, encoder);
if (!crtc)
return -ENODEV;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
crtc_state->active = false;
ret = drm_atomic_set_mode_for_crtc(crtc_state, NULL);
if (ret)
return ret;
output = encoder_to_vc4_dummy_output(encoder);
conn = &output->connector;
conn_state = drm_atomic_get_connector_state(state, conn);
if (IS_ERR(conn_state))
return PTR_ERR(conn_state);
ret = drm_atomic_set_crtc_for_connector(conn_state, NULL);
if (ret)
return ret;
return 0;
}
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-2.0
#include <drm/drm_kunit_helpers.h>
#include <drm/drm_plane.h>
#include <kunit/test.h>
#include "vc4_mock.h"
struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm,
enum drm_plane_type type)
{
struct drm_plane *plane;
KUNIT_ASSERT_EQ(test, type, DRM_PLANE_TYPE_PRIMARY);
plane = drm_kunit_helper_create_primary_plane(test, drm,
NULL,
NULL,
NULL, 0,
NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
return plane;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright © 2014 Broadcom
*/
#include <drm/drm_drv.h>
#include <drm/drm_print.h>
#include <linux/seq_file.h>
#include <linux/circ_buf.h>
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/platform_device.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
/*
* Called at drm_dev_register() time on each of the minors registered
* by the DRM device, to attach the debugfs files.
*/
void
vc4_debugfs_init(struct drm_minor *minor)
{
struct vc4_dev *vc4 = to_vc4_dev(minor->dev);
struct drm_device *drm = &vc4->base;
drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor));
if (vc4->v3d) {
drm_WARN_ON(drm, vc4_bo_debugfs_init(minor));
drm_WARN_ON(drm, vc4_v3d_debugfs_init(minor));
}
}
static int vc4_debugfs_regset32(struct seq_file *m, void *unused)
{
struct drm_debugfs_entry *entry = m->private;
struct drm_device *drm = entry->dev;
struct debugfs_regset32 *regset = entry->file.data;
struct drm_printer p = drm_seq_file_printer(m);
int idx;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
drm_print_regset32(&p, regset);
drm_dev_exit(idx);
return 0;
}
void vc4_debugfs_add_regset32(struct drm_device *drm,
const char *name,
struct debugfs_regset32 *regset)
{
drm_debugfs_add_file(drm, name, vc4_debugfs_regset32, regset);
}
+403
View File
@@ -0,0 +1,403 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2016 Broadcom Limited
*/
/**
* DOC: VC4 DPI module
*
* The VC4 DPI hardware supports MIPI DPI type 4 and Nokia ViSSI
* signals. On BCM2835, these can be routed out to GPIO0-27 with the
* ALT2 function.
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/media-bus-format.h>
#include <linux/platform_device.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
#define DPI_C 0x00
# define DPI_OUTPUT_ENABLE_MODE BIT(16)
/* The order field takes the incoming 24 bit RGB from the pixel valve
* and shuffles the 3 channels.
*/
# define DPI_ORDER_MASK VC4_MASK(15, 14)
# define DPI_ORDER_SHIFT 14
# define DPI_ORDER_RGB 0
# define DPI_ORDER_BGR 1
# define DPI_ORDER_GRB 2
# define DPI_ORDER_BRG 3
/* The format field takes the ORDER-shuffled pixel valve data and
* formats it onto the output lines.
*/
# define DPI_FORMAT_MASK VC4_MASK(13, 11)
# define DPI_FORMAT_SHIFT 11
/* This define is named in the hardware, but actually just outputs 0. */
# define DPI_FORMAT_9BIT_666_RGB 0
/* Outputs 00000000rrrrrggggggbbbbb */
# define DPI_FORMAT_16BIT_565_RGB_1 1
/* Outputs 000rrrrr00gggggg000bbbbb */
# define DPI_FORMAT_16BIT_565_RGB_2 2
/* Outputs 00rrrrr000gggggg00bbbbb0 */
# define DPI_FORMAT_16BIT_565_RGB_3 3
/* Outputs 000000rrrrrrggggggbbbbbb */
# define DPI_FORMAT_18BIT_666_RGB_1 4
/* Outputs 00rrrrrr00gggggg00bbbbbb */
# define DPI_FORMAT_18BIT_666_RGB_2 5
/* Outputs rrrrrrrrggggggggbbbbbbbb */
# define DPI_FORMAT_24BIT_888_RGB 6
/* Reverses the polarity of the corresponding signal */
# define DPI_PIXEL_CLK_INVERT BIT(10)
# define DPI_HSYNC_INVERT BIT(9)
# define DPI_VSYNC_INVERT BIT(8)
# define DPI_OUTPUT_ENABLE_INVERT BIT(7)
/* Outputs the signal the falling clock edge instead of rising. */
# define DPI_HSYNC_NEGATE BIT(6)
# define DPI_VSYNC_NEGATE BIT(5)
# define DPI_OUTPUT_ENABLE_NEGATE BIT(4)
/* Disables the signal */
# define DPI_HSYNC_DISABLE BIT(3)
# define DPI_VSYNC_DISABLE BIT(2)
# define DPI_OUTPUT_ENABLE_DISABLE BIT(1)
/* Power gate to the device, full reset at 0 -> 1 transition */
# define DPI_ENABLE BIT(0)
/* All other registers besides DPI_C return the ID */
#define DPI_ID 0x04
# define DPI_ID_VALUE 0x00647069
/* General DPI hardware state. */
struct vc4_dpi {
struct vc4_encoder encoder;
struct platform_device *pdev;
void __iomem *regs;
struct clk *pixel_clock;
struct clk *core_clock;
struct debugfs_regset32 regset;
};
#define to_vc4_dpi(_encoder) \
container_of_const(_encoder, struct vc4_dpi, encoder.base)
#define DPI_READ(offset) \
({ \
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
readl(dpi->regs + (offset)); \
})
#define DPI_WRITE(offset, val) \
do { \
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
writel(val, dpi->regs + (offset)); \
} while (0)
static const struct debugfs_reg32 dpi_regs[] = {
VC4_REG32(DPI_C),
VC4_REG32(DPI_ID),
};
static void vc4_dpi_encoder_disable(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct vc4_dpi *dpi = to_vc4_dpi(encoder);
int idx;
if (!drm_dev_enter(dev, &idx))
return;
clk_disable_unprepare(dpi->pixel_clock);
drm_dev_exit(idx);
}
static void vc4_dpi_encoder_enable(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct drm_display_mode *mode = &encoder->crtc->mode;
struct vc4_dpi *dpi = to_vc4_dpi(encoder);
struct drm_connector_list_iter conn_iter;
struct drm_connector *connector = NULL, *connector_scan;
u32 dpi_c = DPI_ENABLE;
int idx;
int ret;
/* Look up the connector attached to DPI so we can get the
* bus_format. Ideally the bridge would tell us the
* bus_format we want, but it doesn't yet, so assume that it's
* uniform throughout the bridge chain.
*/
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector_scan, &conn_iter) {
if (connector_scan->encoder == encoder) {
connector = connector_scan;
break;
}
}
drm_connector_list_iter_end(&conn_iter);
/* Default to 18bit if no connector or format found. */
dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1, DPI_FORMAT);
if (connector) {
if (connector->display_info.num_bus_formats) {
u32 bus_format = connector->display_info.bus_formats[0];
dpi_c &= ~DPI_FORMAT_MASK;
switch (bus_format) {
case MEDIA_BUS_FMT_RGB888_1X24:
dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB,
DPI_FORMAT);
break;
case MEDIA_BUS_FMT_BGR888_1X24:
dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB,
DPI_FORMAT);
dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR,
DPI_ORDER);
break;
case MEDIA_BUS_FMT_BGR666_1X24_CPADHI:
dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR, DPI_ORDER);
fallthrough;
case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_2,
DPI_FORMAT);
break;
case MEDIA_BUS_FMT_BGR666_1X18:
dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR, DPI_ORDER);
fallthrough;
case MEDIA_BUS_FMT_RGB666_1X18:
dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1,
DPI_FORMAT);
break;
case MEDIA_BUS_FMT_RGB565_1X16:
dpi_c |= VC4_SET_FIELD(DPI_FORMAT_16BIT_565_RGB_1,
DPI_FORMAT);
break;
case MEDIA_BUS_FMT_RGB565_1X24_CPADHI:
dpi_c |= VC4_SET_FIELD(DPI_FORMAT_16BIT_565_RGB_2,
DPI_FORMAT);
break;
default:
drm_err(dev, "Unknown media bus format %d\n",
bus_format);
break;
}
}
if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
dpi_c |= DPI_PIXEL_CLK_INVERT;
if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW)
dpi_c |= DPI_OUTPUT_ENABLE_INVERT;
}
if (mode->flags & DRM_MODE_FLAG_CSYNC) {
if (mode->flags & DRM_MODE_FLAG_NCSYNC)
dpi_c |= DPI_OUTPUT_ENABLE_INVERT;
} else {
dpi_c |= DPI_OUTPUT_ENABLE_MODE;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
dpi_c |= DPI_HSYNC_INVERT;
else if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
dpi_c |= DPI_HSYNC_DISABLE;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
dpi_c |= DPI_VSYNC_INVERT;
else if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
dpi_c |= DPI_VSYNC_DISABLE;
}
if (!drm_dev_enter(dev, &idx))
return;
DPI_WRITE(DPI_C, dpi_c);
ret = clk_set_rate(dpi->pixel_clock, mode->clock * 1000);
if (ret)
drm_err(dev, "Failed to set clock rate: %d\n", ret);
ret = clk_prepare_enable(dpi->pixel_clock);
if (ret)
drm_err(dev, "Failed to set clock rate: %d\n", ret);
drm_dev_exit(idx);
}
static enum drm_mode_status vc4_dpi_encoder_mode_valid(struct drm_encoder *encoder,
const struct drm_display_mode *mode)
{
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
return MODE_NO_INTERLACE;
return MODE_OK;
}
static const struct drm_encoder_helper_funcs vc4_dpi_encoder_helper_funcs = {
.disable = vc4_dpi_encoder_disable,
.enable = vc4_dpi_encoder_enable,
.mode_valid = vc4_dpi_encoder_mode_valid,
};
static int vc4_dpi_late_register(struct drm_encoder *encoder)
{
struct drm_device *drm = encoder->dev;
struct vc4_dpi *dpi = to_vc4_dpi(encoder);
vc4_debugfs_add_regset32(drm, "dpi_regs", &dpi->regset);
return 0;
}
static const struct drm_encoder_funcs vc4_dpi_encoder_funcs = {
.late_register = vc4_dpi_late_register,
};
static const struct of_device_id vc4_dpi_dt_match[] = {
{ .compatible = "brcm,bcm2835-dpi", .data = NULL },
{}
};
/* Sets up the next link in the display chain, whether it's a panel or
* a bridge.
*/
static int vc4_dpi_init_bridge(struct vc4_dpi *dpi)
{
struct drm_device *drm = dpi->encoder.base.dev;
struct device *dev = &dpi->pdev->dev;
struct drm_bridge *bridge;
bridge = drmm_of_get_bridge(drm, dev->of_node, 0, 0);
if (IS_ERR(bridge)) {
/* If nothing was connected in the DT, that's not an
* error.
*/
if (PTR_ERR(bridge) == -ENODEV)
return 0;
else
return PTR_ERR(bridge);
}
return drm_bridge_attach(&dpi->encoder.base, bridge, NULL, 0);
}
static void vc4_dpi_disable_clock(void *ptr)
{
struct vc4_dpi *dpi = ptr;
clk_disable_unprepare(dpi->core_clock);
}
static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dpi *dpi;
int ret;
dpi = drmm_kzalloc(drm, sizeof(*dpi), GFP_KERNEL);
if (!dpi)
return -ENOMEM;
dpi->encoder.type = VC4_ENCODER_TYPE_DPI;
dpi->pdev = pdev;
dpi->regs = vc4_ioremap_regs(pdev, 0);
if (IS_ERR(dpi->regs))
return PTR_ERR(dpi->regs);
dpi->regset.base = dpi->regs;
dpi->regset.regs = dpi_regs;
dpi->regset.nregs = ARRAY_SIZE(dpi_regs);
if (DPI_READ(DPI_ID) != DPI_ID_VALUE) {
dev_err(dev, "Port returned 0x%08x for ID instead of 0x%08x\n",
DPI_READ(DPI_ID), DPI_ID_VALUE);
return -ENODEV;
}
dpi->core_clock = devm_clk_get(dev, "core");
if (IS_ERR(dpi->core_clock)) {
ret = PTR_ERR(dpi->core_clock);
if (ret != -EPROBE_DEFER)
drm_err(drm, "Failed to get core clock: %d\n", ret);
return ret;
}
dpi->pixel_clock = devm_clk_get(dev, "pixel");
if (IS_ERR(dpi->pixel_clock)) {
ret = PTR_ERR(dpi->pixel_clock);
if (ret != -EPROBE_DEFER)
drm_err(drm, "Failed to get pixel clock: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(dpi->core_clock);
if (ret) {
drm_err(drm, "Failed to turn on core clock: %d\n", ret);
return ret;
}
ret = devm_add_action_or_reset(dev, vc4_dpi_disable_clock, dpi);
if (ret)
return ret;
ret = drmm_encoder_init(drm, &dpi->encoder.base,
&vc4_dpi_encoder_funcs,
DRM_MODE_ENCODER_DPI,
NULL);
if (ret)
return ret;
drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs);
ret = vc4_dpi_init_bridge(dpi);
if (ret)
return ret;
dev_set_drvdata(dev, dpi);
return 0;
}
static const struct component_ops vc4_dpi_ops = {
.bind = vc4_dpi_bind,
};
static int vc4_dpi_dev_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &vc4_dpi_ops);
}
static void vc4_dpi_dev_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &vc4_dpi_ops);
}
struct platform_driver vc4_dpi_driver = {
.probe = vc4_dpi_dev_probe,
.remove = vc4_dpi_dev_remove,
.driver = {
.name = "vc4_dpi",
.of_match_table = vc4_dpi_dt_match,
},
};
+517
View File
@@ -0,0 +1,517 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2014-2015 Broadcom
* Copyright (C) 2013 Red Hat
*/
/**
* DOC: Broadcom VC4 Graphics Driver
*
* The Broadcom VideoCore 4 (present in the Raspberry Pi) contains a
* OpenGL ES 2.0-compatible 3D engine called V3D, and a highly
* configurable display output pipeline that supports HDMI, DSI, DPI,
* and Composite TV output.
*
* The 3D engine also has an interface for submitting arbitrary
* compute shader-style jobs using the same shader processor as is
* used for vertex and fragment shaders in GLES 2.0. However, given
* that the hardware isn't able to expose any standard interfaces like
* OpenGL compute shaders or OpenCL, it isn't supported by this
* driver.
*/
#include <linux/aperture.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_print.h>
#include <drm/drm_vblank.h>
#include <soc/bcm2835/raspberrypi-firmware.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
#define DRIVER_NAME "vc4"
#define DRIVER_DESC "Broadcom VC4 graphics"
#define DRIVER_MAJOR 0
#define DRIVER_MINOR 0
#define DRIVER_PATCHLEVEL 0
/* Helper function for mapping the regs on a platform device. */
void __iomem *vc4_ioremap_regs(struct platform_device *pdev, int index)
{
void __iomem *map;
map = devm_platform_ioremap_resource(pdev, index);
if (IS_ERR(map))
return map;
return map;
}
int vc4_dumb_fixup_args(struct drm_mode_create_dumb *args)
{
int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
if (args->pitch < min_pitch)
args->pitch = min_pitch;
if (args->size < args->pitch * args->height)
args->size = args->pitch * args->height;
return 0;
}
static int vc5_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
int ret;
ret = vc4_dumb_fixup_args(args);
if (ret)
return ret;
return drm_gem_dma_dumb_create_internal(file_priv, dev, args);
}
static int vc4_get_param_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct drm_vc4_get_param *args = data;
int ret;
if (args->pad != 0)
return -EINVAL;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
if (!vc4->v3d)
return -ENODEV;
switch (args->param) {
case DRM_VC4_PARAM_V3D_IDENT0:
ret = vc4_v3d_pm_get(vc4);
if (ret)
return ret;
args->value = V3D_READ(V3D_IDENT0);
vc4_v3d_pm_put(vc4);
break;
case DRM_VC4_PARAM_V3D_IDENT1:
ret = vc4_v3d_pm_get(vc4);
if (ret)
return ret;
args->value = V3D_READ(V3D_IDENT1);
vc4_v3d_pm_put(vc4);
break;
case DRM_VC4_PARAM_V3D_IDENT2:
ret = vc4_v3d_pm_get(vc4);
if (ret)
return ret;
args->value = V3D_READ(V3D_IDENT2);
vc4_v3d_pm_put(vc4);
break;
case DRM_VC4_PARAM_SUPPORTS_BRANCHES:
case DRM_VC4_PARAM_SUPPORTS_ETC1:
case DRM_VC4_PARAM_SUPPORTS_THREADED_FS:
case DRM_VC4_PARAM_SUPPORTS_FIXED_RCL_ORDER:
case DRM_VC4_PARAM_SUPPORTS_MADVISE:
case DRM_VC4_PARAM_SUPPORTS_PERFMON:
args->value = true;
break;
default:
DRM_DEBUG("Unknown parameter %d\n", args->param);
return -EINVAL;
}
return 0;
}
static int vc4_open(struct drm_device *dev, struct drm_file *file)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_file *vc4file;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
vc4file = kzalloc_obj(*vc4file);
if (!vc4file)
return -ENOMEM;
vc4file->dev = vc4;
vc4_perfmon_open_file(vc4file);
file->driver_priv = vc4file;
return 0;
}
static void vc4_close(struct drm_device *dev, struct drm_file *file)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_file *vc4file = file->driver_priv;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
if (vc4file->bin_bo_used)
vc4_v3d_bin_bo_put(vc4);
vc4_perfmon_close_file(vc4file);
kfree(vc4file);
}
DEFINE_DRM_GEM_FOPS(vc4_drm_fops);
static const struct drm_ioctl_desc vc4_drm_ioctls[] = {
DRM_IOCTL_DEF_DRV(VC4_SUBMIT_CL, vc4_submit_cl_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_WAIT_SEQNO, vc4_wait_seqno_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_WAIT_BO, vc4_wait_bo_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_CREATE_BO, vc4_create_bo_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_MMAP_BO, vc4_mmap_bo_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_CREATE_SHADER_BO, vc4_create_shader_bo_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_GET_HANG_STATE, vc4_get_hang_state_ioctl,
DRM_ROOT_ONLY),
DRM_IOCTL_DEF_DRV(VC4_GET_PARAM, vc4_get_param_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_SET_TILING, vc4_set_tiling_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_GET_TILING, vc4_get_tiling_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_LABEL_BO, vc4_label_bo_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_GEM_MADVISE, vc4_gem_madvise_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_PERFMON_CREATE, vc4_perfmon_create_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_PERFMON_DESTROY, vc4_perfmon_destroy_ioctl, DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VC4_PERFMON_GET_VALUES, vc4_perfmon_get_values_ioctl, DRM_RENDER_ALLOW),
};
const struct drm_driver vc4_drm_driver = {
.driver_features = (DRIVER_MODESET |
DRIVER_ATOMIC |
DRIVER_GEM |
DRIVER_RENDER |
DRIVER_SYNCOBJ),
.open = vc4_open,
.postclose = vc4_close,
#if defined(CONFIG_DEBUG_FS)
.debugfs_init = vc4_debugfs_init,
#endif
.gem_create_object = vc4_create_object,
DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc4_bo_dumb_create),
DRM_FBDEV_DMA_DRIVER_OPS,
.ioctls = vc4_drm_ioctls,
.num_ioctls = ARRAY_SIZE(vc4_drm_ioctls),
.fops = &vc4_drm_fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
};
const struct drm_driver vc5_drm_driver = {
.driver_features = (DRIVER_MODESET |
DRIVER_ATOMIC |
DRIVER_GEM),
#if defined(CONFIG_DEBUG_FS)
.debugfs_init = vc4_debugfs_init,
#endif
DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc5_dumb_create),
DRM_FBDEV_DMA_DRIVER_OPS,
.fops = &vc4_drm_fops,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
};
static void vc4_match_add_drivers(struct device *dev,
struct component_match **match,
struct platform_driver *const *drivers,
int count)
{
int i;
for (i = 0; i < count; i++) {
struct device_driver *drv = &drivers[i]->driver;
struct device *p = NULL, *d;
while ((d = platform_find_device_by_driver(p, drv))) {
put_device(p);
component_match_add(dev, match, component_compare_dev, d);
p = d;
}
put_device(p);
}
}
static void vc4_component_unbind_all(void *ptr)
{
struct vc4_dev *vc4 = ptr;
component_unbind_all(vc4->dev, &vc4->base);
}
static const struct of_device_id vc4_dma_range_matches[] = {
{ .compatible = "brcm,bcm2711-hvs" },
{ .compatible = "brcm,bcm2712-hvs" },
{ .compatible = "brcm,bcm2835-hvs" },
{ .compatible = "brcm,bcm2835-v3d" },
{ .compatible = "brcm,cygnus-v3d" },
{ .compatible = "brcm,vc4-v3d" },
{}
};
static int vc4_drm_bind(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
const struct drm_driver *driver;
struct rpi_firmware *firmware = NULL;
struct drm_device *drm;
struct vc4_dev *vc4;
struct device_node *node;
struct drm_crtc *crtc;
enum vc4_gen gen;
int ret = 0;
dev->coherent_dma_mask = DMA_BIT_MASK(32);
gen = (enum vc4_gen)of_device_get_match_data(dev);
if (gen > VC4_GEN_4)
driver = &vc5_drm_driver;
else
driver = &vc4_drm_driver;
if (gen >= VC4_GEN_6_C)
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
else
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
node = of_find_matching_node_and_match(NULL, vc4_dma_range_matches,
NULL);
if (node) {
ret = of_dma_configure(dev, node, true);
of_node_put(node);
if (ret)
return ret;
}
vc4 = devm_drm_dev_alloc(dev, driver, struct vc4_dev, base);
if (IS_ERR(vc4))
return PTR_ERR(vc4);
vc4->gen = gen;
vc4->dev = dev;
drm = &vc4->base;
platform_set_drvdata(pdev, drm);
if (gen == VC4_GEN_4) {
ret = drmm_mutex_init(drm, &vc4->bin_bo_lock);
if (ret)
goto err;
ret = vc4_bo_cache_init(drm);
if (ret)
goto err;
}
ret = drmm_mode_config_init(drm);
if (ret)
goto err;
if (gen == VC4_GEN_4) {
ret = vc4_gem_init(drm);
if (ret)
goto err;
}
node = of_find_compatible_node(NULL, NULL, "raspberrypi,bcm2835-firmware");
if (node) {
firmware = rpi_firmware_get(node);
of_node_put(node);
if (!firmware) {
ret = -EPROBE_DEFER;
goto err;
}
}
ret = aperture_remove_all_conflicting_devices(driver->name);
if (ret)
goto err;
if (firmware) {
ret = rpi_firmware_property(firmware,
RPI_FIRMWARE_NOTIFY_DISPLAY_DONE,
NULL, 0);
if (ret)
drm_warn(drm, "Couldn't stop firmware display driver: %d\n", ret);
rpi_firmware_put(firmware);
}
ret = component_bind_all(dev, drm);
if (ret)
goto err;
ret = devm_add_action_or_reset(dev, vc4_component_unbind_all, vc4);
if (ret)
goto err;
ret = vc4_plane_create_additional_planes(drm);
if (ret)
goto err;
ret = vc4_kms_load(drm);
if (ret < 0)
goto err;
drm_for_each_crtc(crtc, drm)
vc4_crtc_disable_at_boot(crtc);
ret = drm_dev_register(drm, 0);
if (ret < 0)
goto err;
drm_client_setup_with_fourcc(drm, DRM_FORMAT_RGB565);
return 0;
err:
platform_set_drvdata(pdev, NULL);
return ret;
}
static void vc4_drm_unbind(struct device *dev)
{
struct drm_device *drm = dev_get_drvdata(dev);
drm_dev_unplug(drm);
drm_atomic_helper_shutdown(drm);
dev_set_drvdata(dev, NULL);
}
static const struct component_master_ops vc4_drm_ops = {
.bind = vc4_drm_bind,
.unbind = vc4_drm_unbind,
};
/*
* This list determines the binding order of our components, and we have
* a few constraints:
* - The TXP driver needs to be bound before the PixelValves (CRTC)
* but after the HVS to set the possible_crtc field properly
* - The HDMI driver needs to be bound after the HVS so that we can
* lookup the HVS maximum core clock rate and figure out if we
* support 4kp60 or not.
*/
static struct platform_driver *const component_drivers[] = {
&vc4_hvs_driver,
&vc4_hdmi_driver,
&vc4_vec_driver,
&vc4_dpi_driver,
&vc4_dsi_driver,
&vc4_txp_driver,
&vc4_crtc_driver,
&vc4_v3d_driver,
};
static int vc4_platform_drm_probe(struct platform_device *pdev)
{
struct component_match *match = NULL;
struct device *dev = &pdev->dev;
vc4_match_add_drivers(dev, &match,
component_drivers, ARRAY_SIZE(component_drivers));
return component_master_add_with_match(dev, &vc4_drm_ops, match);
}
static void vc4_platform_drm_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &vc4_drm_ops);
}
static void vc4_platform_drm_shutdown(struct platform_device *pdev)
{
drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}
static const struct of_device_id vc4_of_match[] = {
{ .compatible = "brcm,bcm2711-vc5", .data = (void *)VC4_GEN_5 },
/* NB GEN_6_C will be corrected on D0 hw to GEN_6_D via vc4_hvs_bind */
{ .compatible = "brcm,bcm2712-vc6", .data = (void *)VC4_GEN_6_C },
{ .compatible = "brcm,bcm2835-vc4", .data = (void *)VC4_GEN_4 },
{ .compatible = "brcm,cygnus-vc4", .data = (void *)VC4_GEN_4 },
{},
};
MODULE_DEVICE_TABLE(of, vc4_of_match);
static struct platform_driver vc4_platform_driver = {
.probe = vc4_platform_drm_probe,
.remove = vc4_platform_drm_remove,
.shutdown = vc4_platform_drm_shutdown,
.driver = {
.name = "vc4-drm",
.of_match_table = vc4_of_match,
},
};
static int __init vc4_drm_register(void)
{
int ret;
if (drm_firmware_drivers_only())
return -ENODEV;
ret = platform_register_drivers(component_drivers,
ARRAY_SIZE(component_drivers));
if (ret)
return ret;
ret = platform_driver_register(&vc4_platform_driver);
if (ret)
platform_unregister_drivers(component_drivers,
ARRAY_SIZE(component_drivers));
return ret;
}
static void __exit vc4_drm_unregister(void)
{
platform_unregister_drivers(component_drivers,
ARRAY_SIZE(component_drivers));
platform_driver_unregister(&vc4_platform_driver);
}
module_init(vc4_drm_register);
module_exit(vc4_drm_unregister);
MODULE_ALIAS("platform:vc4-drm");
MODULE_SOFTDEP("pre: snd-soc-hdmi-codec");
MODULE_DESCRIPTION("Broadcom VC4 DRM Driver");
MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
/*
* Copyright © 2017 Broadcom
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "vc4_drv.h"
static const char *vc4_fence_get_driver_name(struct dma_fence *fence)
{
return "vc4";
}
static const char *vc4_fence_get_timeline_name(struct dma_fence *fence)
{
return "vc4-v3d";
}
static bool vc4_fence_signaled(struct dma_fence *fence)
{
struct vc4_fence *f = to_vc4_fence(fence);
struct vc4_dev *vc4 = to_vc4_dev(f->dev);
return vc4->finished_seqno >= f->seqno;
}
const struct dma_fence_ops vc4_fence_ops = {
.get_driver_name = vc4_fence_get_driver_name,
.get_timeline_name = vc4_fence_get_timeline_name,
.signaled = vc4_fence_signaled,
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+248
View File
@@ -0,0 +1,248 @@
#ifndef _VC4_HDMI_H_
#define _VC4_HDMI_H_
#include <drm/drm_connector.h>
#include <media/cec.h>
#include <sound/dmaengine_pcm.h>
#include <sound/hdmi-codec.h>
#include <sound/soc.h>
#include "vc4_drv.h"
struct vc4_hdmi;
struct vc4_hdmi_register;
enum vc4_hdmi_phy_channel {
PHY_LANE_0 = 0,
PHY_LANE_1,
PHY_LANE_2,
PHY_LANE_CK,
};
struct vc4_hdmi_variant {
/* Encoder Type for that controller */
enum vc4_encoder_type encoder_type;
/* ALSA card name */
const char *card_name;
/* Filename to expose the registers in debugfs */
const char *debugfs_name;
/* Maximum pixel clock supported by the controller (in Hz) */
unsigned long long max_pixel_clock;
/* List of the registers available on that variant */
const struct vc4_hdmi_register *registers;
/* Number of registers on that variant */
unsigned int num_registers;
/* BCM2711 Only.
* The variants don't map the lane in the same order in the
* PHY, so this is an array mapping the HDMI channel (index)
* to the PHY lane (value).
*/
enum vc4_hdmi_phy_channel phy_lane_mapping[4];
/* The BCM2711 cannot deal with odd horizontal pixel timings */
bool unsupported_odd_h_timings;
/*
* The BCM2711 CEC/hotplug IRQ controller is shared between the
* two HDMI controllers, and we have a proper irqchip driver for
* it.
*/
bool external_irq_controller;
/* Callback to get the resources (memory region, interrupts,
* clocks, etc) for that variant.
*/
int (*init_resources)(struct drm_device *drm,
struct vc4_hdmi *vc4_hdmi);
/* Callback to reset the HDMI block */
void (*reset)(struct vc4_hdmi *vc4_hdmi);
/* Callback to enable / disable the CSC */
void (*csc_setup)(struct vc4_hdmi *vc4_hdmi,
struct drm_connector_state *state,
const struct drm_display_mode *mode);
/* Callback to configure the video timings in the HDMI block */
void (*set_timings)(struct vc4_hdmi *vc4_hdmi,
struct drm_connector_state *state,
const struct drm_display_mode *mode);
/* Callback to initialize the PHY according to the connector state */
void (*phy_init)(struct vc4_hdmi *vc4_hdmi,
struct drm_connector_state *conn_state);
/* Callback to disable the PHY */
void (*phy_disable)(struct vc4_hdmi *vc4_hdmi);
/* Callback to enable the RNG in the PHY */
void (*phy_rng_enable)(struct vc4_hdmi *vc4_hdmi);
/* Callback to disable the RNG in the PHY */
void (*phy_rng_disable)(struct vc4_hdmi *vc4_hdmi);
/* Callback to get channel map */
u32 (*channel_map)(struct vc4_hdmi *vc4_hdmi, u32 channel_mask);
/* Enables HDR metadata */
bool supports_hdr;
/* Callback for hardware specific hotplug detect */
bool (*hp_detect)(struct vc4_hdmi *vc4_hdmi);
};
/* HDMI audio information */
struct vc4_hdmi_audio {
struct snd_soc_card card;
struct snd_soc_dai_link link;
struct snd_soc_dai_link_component cpu;
struct snd_soc_dai_link_component codec;
struct snd_soc_dai_link_component platform;
struct snd_dmaengine_dai_dma_data dma_data;
bool streaming;
};
/* General HDMI hardware state. */
struct vc4_hdmi {
struct vc4_hdmi_audio audio;
struct platform_device *pdev;
const struct vc4_hdmi_variant *variant;
struct vc4_encoder encoder;
struct drm_connector connector;
struct delayed_work scrambling_work;
struct i2c_adapter *ddc;
void __iomem *hdmicore_regs;
void __iomem *hd_regs;
/* VC5 Only */
void __iomem *cec_regs;
/* VC5 Only */
void __iomem *csc_regs;
/* VC5 Only */
void __iomem *dvp_regs;
/* VC5 Only */
void __iomem *phy_regs;
/* VC5 Only */
void __iomem *ram_regs;
/* VC5 Only */
void __iomem *rm_regs;
struct gpio_desc *hpd_gpio;
/*
* On some systems (like the RPi4), some modes are in the same
* frequency range than the WiFi channels (1440p@60Hz for
* example). Should we take evasive actions because that system
* has a wifi adapter?
*/
bool disable_wifi_frequencies;
struct cec_msg cec_rx_msg;
bool cec_tx_ok;
bool cec_irq_was_rx;
struct clk *cec_clock;
struct clk *pixel_clock;
struct clk *hsm_clock;
struct clk *audio_clock;
struct clk *pixel_bvb_clock;
struct reset_control *reset;
struct debugfs_regset32 hdmi_regset;
struct debugfs_regset32 hd_regset;
/* VC5 only */
struct debugfs_regset32 cec_regset;
struct debugfs_regset32 csc_regset;
struct debugfs_regset32 dvp_regset;
struct debugfs_regset32 phy_regset;
struct debugfs_regset32 ram_regset;
struct debugfs_regset32 rm_regset;
/**
* @hw_lock: Spinlock protecting device register access.
*/
spinlock_t hw_lock;
/**
* @mutex: Mutex protecting the driver access across multiple
* frameworks (KMS, ALSA, CEC).
*/
struct mutex mutex;
/**
* @saved_adjusted_mode: Copy of @drm_crtc_state.adjusted_mode
* for use by ALSA hooks and interrupt handlers. Protected by @mutex.
*/
struct drm_display_mode saved_adjusted_mode;
/**
* @packet_ram_enabled: Is the HDMI controller packet RAM currently
* on? Protected by @mutex.
*/
bool packet_ram_enabled;
/**
* @scdc_enabled: Is the HDMI controller currently running with
* the scrambler on? Protected by @mutex.
*/
bool scdc_enabled;
/**
* @output_bpc: Copy of @drm_connector_state.hdmi.output_bpc for
* use outside of KMS hooks. Protected by @mutex.
*/
unsigned int output_bpc;
/**
* @output_format: Copy of
* @drm_connector_state.hdmi.output_format for use outside of
* KMS hooks. Protected by @mutex.
*/
enum drm_output_color_format output_format;
/**
* @hdmi_jack: Represents the connection state of the HDMI plug, for
* ALSA jack detection.
*/
struct snd_soc_jack hdmi_jack;
};
#define connector_to_vc4_hdmi(_connector) \
container_of_const(_connector, struct vc4_hdmi, connector)
static inline struct vc4_hdmi *
encoder_to_vc4_hdmi(struct drm_encoder *encoder)
{
struct vc4_encoder *_encoder = to_vc4_encoder(encoder);
return container_of_const(_encoder, struct vc4_hdmi, encoder);
}
void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
struct drm_connector_state *conn_state);
void vc4_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi);
void vc4_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi);
void vc4_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi);
void vc5_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
struct drm_connector_state *conn_state);
void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi);
void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi);
void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi);
void vc6_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
struct drm_connector_state *conn_state);
void vc6_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi);
#endif /* _VC4_HDMI_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,728 @@
#ifndef _VC4_HDMI_REGS_H_
#define _VC4_HDMI_REGS_H_
#include <linux/pm_runtime.h>
#include "vc4_hdmi.h"
#define VC4_HDMI_PACKET_STRIDE 0x24
enum vc4_hdmi_regs {
VC4_INVALID = 0,
VC4_HDMI,
VC4_HD,
VC5_CEC,
VC5_CSC,
VC5_DVP,
VC5_PHY,
VC5_RAM,
VC5_RM,
};
enum vc4_hdmi_field {
HDMI_AUDIO_PACKET_CONFIG,
HDMI_CEC_CNTRL_1,
HDMI_CEC_CNTRL_2,
HDMI_CEC_CNTRL_3,
HDMI_CEC_CNTRL_4,
HDMI_CEC_CNTRL_5,
HDMI_CEC_CPU_CLEAR,
HDMI_CEC_CPU_MASK_CLEAR,
HDMI_CEC_CPU_MASK_SET,
HDMI_CEC_CPU_MASK_STATUS,
HDMI_CEC_CPU_STATUS,
HDMI_CEC_CPU_SET,
/*
* Transmit data, first byte is low byte of the 32-bit reg.
* MSB of each byte transmitted first.
*/
HDMI_CEC_RX_DATA_1,
HDMI_CEC_RX_DATA_2,
HDMI_CEC_RX_DATA_3,
HDMI_CEC_RX_DATA_4,
HDMI_CEC_TX_DATA_1,
HDMI_CEC_TX_DATA_2,
HDMI_CEC_TX_DATA_3,
HDMI_CEC_TX_DATA_4,
HDMI_CLOCK_STOP,
HDMI_CORE_REV,
HDMI_CRP_CFG,
HDMI_CSC_12_11,
HDMI_CSC_14_13,
HDMI_CSC_22_21,
HDMI_CSC_24_23,
HDMI_CSC_32_31,
HDMI_CSC_34_33,
HDMI_CSC_CHANNEL_CTL,
HDMI_CSC_CTL,
/*
* 20-bit fields containing CTS values to be transmitted if
* !EXTERNAL_CTS_EN
*/
HDMI_CTS_0,
HDMI_CTS_1,
HDMI_DEEP_COLOR_CONFIG_1,
HDMI_DVP_CTL,
HDMI_FIFO_CTL,
HDMI_FRAME_COUNT,
HDMI_GCP_CONFIG,
HDMI_GCP_WORD_1,
HDMI_HORZA,
HDMI_HORZB,
HDMI_HOTPLUG,
HDMI_HOTPLUG_INT,
/*
* 3 bits per field, where each field maps from that
* corresponding MAI bus channel to the given HDMI channel.
*/
HDMI_MAI_CHANNEL_MAP,
HDMI_MAI_CONFIG,
HDMI_MAI_CTL,
/*
* Register for DMAing in audio data to be transported over
* the MAI bus to the Falcon core.
*/
HDMI_MAI_DATA,
/* Format header to be placed on the MAI data. Unused. */
HDMI_MAI_FMT,
/* Last received format word on the MAI bus. */
HDMI_MAI_FORMAT,
HDMI_MAI_SMP,
HDMI_MAI_THR,
HDMI_M_CTL,
HDMI_RAM_PACKET_CONFIG,
HDMI_RAM_PACKET_START,
HDMI_RAM_PACKET_STATUS,
HDMI_RM_CONTROL,
HDMI_RM_FORMAT,
HDMI_RM_OFFSET,
HDMI_SCHEDULER_CONTROL,
HDMI_SCRAMBLER_CTL,
HDMI_SW_RESET_CONTROL,
HDMI_TX_PHY_CHANNEL_SWAP,
HDMI_TX_PHY_CLK_DIV,
HDMI_TX_PHY_CTL_0,
HDMI_TX_PHY_CTL_1,
HDMI_TX_PHY_CTL_2,
HDMI_TX_PHY_CTL_3,
HDMI_TX_PHY_CTL_CK,
HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1,
HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2,
HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4,
HDMI_TX_PHY_PLL_CFG,
HDMI_TX_PHY_PLL_CFG_PDIV,
HDMI_TX_PHY_PLL_CTL_0,
HDMI_TX_PHY_PLL_CTL_1,
HDMI_TX_PHY_PLL_MISC_0,
HDMI_TX_PHY_PLL_MISC_1,
HDMI_TX_PHY_PLL_MISC_2,
HDMI_TX_PHY_PLL_MISC_3,
HDMI_TX_PHY_PLL_MISC_4,
HDMI_TX_PHY_PLL_MISC_5,
HDMI_TX_PHY_PLL_MISC_6,
HDMI_TX_PHY_PLL_MISC_7,
HDMI_TX_PHY_PLL_MISC_8,
HDMI_TX_PHY_PLL_POST_KDIV,
HDMI_TX_PHY_PLL_POWERUP_CTL,
HDMI_TX_PHY_PLL_REFCLK,
HDMI_TX_PHY_PLL_RESET_CTL,
HDMI_TX_PHY_PLL_VCOCLK_DIV,
HDMI_TX_PHY_POWERDOWN_CTL,
HDMI_TX_PHY_POWERUP_CTL,
HDMI_TX_PHY_RESET_CTL,
HDMI_TX_PHY_TMDS_CLK_WORD_SEL,
HDMI_VEC_INTERFACE_CFG,
HDMI_VEC_INTERFACE_XBAR,
HDMI_VERTA0,
HDMI_VERTA1,
HDMI_VERTB0,
HDMI_VERTB1,
HDMI_VID_CTL,
HDMI_MISC_CONTROL,
HDMI_FORMAT_DET_1,
HDMI_FORMAT_DET_2,
HDMI_FORMAT_DET_3,
HDMI_FORMAT_DET_4,
HDMI_FORMAT_DET_5,
HDMI_FORMAT_DET_6,
HDMI_FORMAT_DET_7,
HDMI_FORMAT_DET_8,
HDMI_FORMAT_DET_9,
HDMI_FORMAT_DET_10,
};
struct vc4_hdmi_register {
char *name;
enum vc4_hdmi_regs reg;
unsigned int offset;
};
#define _VC4_REG(_base, _reg, _offset) \
[_reg] = { \
.name = #_reg, \
.reg = _base, \
.offset = _offset, \
}
#define VC4_HD_REG(reg, offset) _VC4_REG(VC4_HD, reg, offset)
#define VC4_HDMI_REG(reg, offset) _VC4_REG(VC4_HDMI, reg, offset)
#define VC5_CEC_REG(reg, offset) _VC4_REG(VC5_CEC, reg, offset)
#define VC5_CSC_REG(reg, offset) _VC4_REG(VC5_CSC, reg, offset)
#define VC5_DVP_REG(reg, offset) _VC4_REG(VC5_DVP, reg, offset)
#define VC5_PHY_REG(reg, offset) _VC4_REG(VC5_PHY, reg, offset)
#define VC5_RAM_REG(reg, offset) _VC4_REG(VC5_RAM, reg, offset)
#define VC5_RM_REG(reg, offset) _VC4_REG(VC5_RM, reg, offset)
static const struct vc4_hdmi_register __maybe_unused vc4_hdmi_fields[] = {
VC4_HD_REG(HDMI_M_CTL, 0x000c),
VC4_HD_REG(HDMI_MAI_CTL, 0x0014),
VC4_HD_REG(HDMI_MAI_THR, 0x0018),
VC4_HD_REG(HDMI_MAI_FMT, 0x001c),
VC4_HD_REG(HDMI_MAI_DATA, 0x0020),
VC4_HD_REG(HDMI_MAI_SMP, 0x002c),
VC4_HD_REG(HDMI_VID_CTL, 0x0038),
VC4_HD_REG(HDMI_CSC_CTL, 0x0040),
VC4_HD_REG(HDMI_CSC_12_11, 0x0044),
VC4_HD_REG(HDMI_CSC_14_13, 0x0048),
VC4_HD_REG(HDMI_CSC_22_21, 0x004c),
VC4_HD_REG(HDMI_CSC_24_23, 0x0050),
VC4_HD_REG(HDMI_CSC_32_31, 0x0054),
VC4_HD_REG(HDMI_CSC_34_33, 0x0058),
VC4_HD_REG(HDMI_FRAME_COUNT, 0x0068),
VC4_HDMI_REG(HDMI_CORE_REV, 0x0000),
VC4_HDMI_REG(HDMI_SW_RESET_CONTROL, 0x0004),
VC4_HDMI_REG(HDMI_HOTPLUG_INT, 0x0008),
VC4_HDMI_REG(HDMI_HOTPLUG, 0x000c),
VC4_HDMI_REG(HDMI_FIFO_CTL, 0x005c),
VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0090),
VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0094),
VC4_HDMI_REG(HDMI_MAI_FORMAT, 0x0098),
VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x009c),
VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x00a0),
VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x00a4),
VC4_HDMI_REG(HDMI_CRP_CFG, 0x00a8),
VC4_HDMI_REG(HDMI_CTS_0, 0x00ac),
VC4_HDMI_REG(HDMI_CTS_1, 0x00b0),
VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x00c0),
VC4_HDMI_REG(HDMI_HORZA, 0x00c4),
VC4_HDMI_REG(HDMI_HORZB, 0x00c8),
VC4_HDMI_REG(HDMI_VERTA0, 0x00cc),
VC4_HDMI_REG(HDMI_VERTB0, 0x00d0),
VC4_HDMI_REG(HDMI_VERTA1, 0x00d4),
VC4_HDMI_REG(HDMI_VERTB1, 0x00d8),
VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x00e4),
VC4_HDMI_REG(HDMI_CEC_CNTRL_1, 0x00e8),
VC4_HDMI_REG(HDMI_CEC_CNTRL_2, 0x00ec),
VC4_HDMI_REG(HDMI_CEC_CNTRL_3, 0x00f0),
VC4_HDMI_REG(HDMI_CEC_CNTRL_4, 0x00f4),
VC4_HDMI_REG(HDMI_CEC_CNTRL_5, 0x00f8),
VC4_HDMI_REG(HDMI_CEC_TX_DATA_1, 0x00fc),
VC4_HDMI_REG(HDMI_CEC_TX_DATA_2, 0x0100),
VC4_HDMI_REG(HDMI_CEC_TX_DATA_3, 0x0104),
VC4_HDMI_REG(HDMI_CEC_TX_DATA_4, 0x0108),
VC4_HDMI_REG(HDMI_CEC_RX_DATA_1, 0x010c),
VC4_HDMI_REG(HDMI_CEC_RX_DATA_2, 0x0110),
VC4_HDMI_REG(HDMI_CEC_RX_DATA_3, 0x0114),
VC4_HDMI_REG(HDMI_CEC_RX_DATA_4, 0x0118),
VC4_HDMI_REG(HDMI_TX_PHY_RESET_CTL, 0x02c0),
VC4_HDMI_REG(HDMI_TX_PHY_CTL_0, 0x02c4),
VC4_HDMI_REG(HDMI_CEC_CPU_STATUS, 0x0340),
VC4_HDMI_REG(HDMI_CEC_CPU_SET, 0x0344),
VC4_HDMI_REG(HDMI_CEC_CPU_CLEAR, 0x0348),
VC4_HDMI_REG(HDMI_CEC_CPU_MASK_STATUS, 0x034c),
VC4_HDMI_REG(HDMI_CEC_CPU_MASK_SET, 0x0350),
VC4_HDMI_REG(HDMI_CEC_CPU_MASK_CLEAR, 0x0354),
VC4_HDMI_REG(HDMI_RAM_PACKET_START, 0x0400),
};
static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi0_fields[] = {
VC4_HD_REG(HDMI_DVP_CTL, 0x0000),
VC4_HD_REG(HDMI_MAI_CTL, 0x0010),
VC4_HD_REG(HDMI_MAI_THR, 0x0014),
VC4_HD_REG(HDMI_MAI_FMT, 0x0018),
VC4_HD_REG(HDMI_MAI_DATA, 0x001c),
VC4_HD_REG(HDMI_MAI_SMP, 0x0020),
VC4_HD_REG(HDMI_VID_CTL, 0x0044),
VC4_HD_REG(HDMI_FRAME_COUNT, 0x0060),
VC4_HDMI_REG(HDMI_FIFO_CTL, 0x074),
VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0b8),
VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0bc),
VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0c4),
VC4_HDMI_REG(HDMI_CRP_CFG, 0x0c8),
VC4_HDMI_REG(HDMI_CTS_0, 0x0cc),
VC4_HDMI_REG(HDMI_CTS_1, 0x0d0),
VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e0),
VC4_HDMI_REG(HDMI_HORZA, 0x0e4),
VC4_HDMI_REG(HDMI_HORZB, 0x0e8),
VC4_HDMI_REG(HDMI_VERTA0, 0x0ec),
VC4_HDMI_REG(HDMI_VERTB0, 0x0f0),
VC4_HDMI_REG(HDMI_VERTA1, 0x0f4),
VC4_HDMI_REG(HDMI_VERTB1, 0x0f8),
VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x100),
VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c),
VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0),
VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x134),
VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x138),
VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x13c),
VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x140),
VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x144),
VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x148),
VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x14c),
VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x150),
VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x154),
VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x158),
VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x170),
VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x178),
VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x17c),
VC4_HDMI_REG(HDMI_HOTPLUG, 0x1a8),
VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1c4),
VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0ec),
VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0),
VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
VC5_PHY_REG(HDMI_TX_PHY_POWERDOWN_CTL, 0x004),
VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008),
VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c),
VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010),
VC5_PHY_REG(HDMI_TX_PHY_CTL_3, 0x014),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_0, 0x01c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_1, 0x020),
VC5_PHY_REG(HDMI_TX_PHY_CLK_DIV, 0x028),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x034),
VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x044),
VC5_PHY_REG(HDMI_TX_PHY_CHANNEL_SWAP, 0x04c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1, 0x050),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2, 0x054),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4, 0x05c),
VC5_RM_REG(HDMI_RM_CONTROL, 0x000),
VC5_RM_REG(HDMI_RM_OFFSET, 0x018),
VC5_RM_REG(HDMI_RM_FORMAT, 0x01c),
VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000),
VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010),
VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014),
VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018),
VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c),
VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020),
VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028),
VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c),
VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030),
VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034),
VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038),
VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c),
VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040),
VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044),
VC5_CSC_REG(HDMI_CSC_CTL, 0x000),
VC5_CSC_REG(HDMI_CSC_12_11, 0x004),
VC5_CSC_REG(HDMI_CSC_14_13, 0x008),
VC5_CSC_REG(HDMI_CSC_22_21, 0x00c),
VC5_CSC_REG(HDMI_CSC_24_23, 0x010),
VC5_CSC_REG(HDMI_CSC_32_31, 0x014),
VC5_CSC_REG(HDMI_CSC_34_33, 0x018),
VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c),
};
static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = {
VC4_HD_REG(HDMI_DVP_CTL, 0x0000),
VC4_HD_REG(HDMI_MAI_CTL, 0x0030),
VC4_HD_REG(HDMI_MAI_THR, 0x0034),
VC4_HD_REG(HDMI_MAI_FMT, 0x0038),
VC4_HD_REG(HDMI_MAI_DATA, 0x003c),
VC4_HD_REG(HDMI_MAI_SMP, 0x0040),
VC4_HD_REG(HDMI_VID_CTL, 0x0048),
VC4_HD_REG(HDMI_FRAME_COUNT, 0x0064),
VC4_HDMI_REG(HDMI_FIFO_CTL, 0x074),
VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0b8),
VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0bc),
VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0c4),
VC4_HDMI_REG(HDMI_CRP_CFG, 0x0c8),
VC4_HDMI_REG(HDMI_CTS_0, 0x0cc),
VC4_HDMI_REG(HDMI_CTS_1, 0x0d0),
VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e0),
VC4_HDMI_REG(HDMI_HORZA, 0x0e4),
VC4_HDMI_REG(HDMI_HORZB, 0x0e8),
VC4_HDMI_REG(HDMI_VERTA0, 0x0ec),
VC4_HDMI_REG(HDMI_VERTB0, 0x0f0),
VC4_HDMI_REG(HDMI_VERTA1, 0x0f4),
VC4_HDMI_REG(HDMI_VERTB1, 0x0f8),
VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x100),
VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c),
VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0),
VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x134),
VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x138),
VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x13c),
VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x140),
VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x144),
VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x148),
VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x14c),
VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x150),
VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x154),
VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x158),
VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x170),
VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x178),
VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x17c),
VC4_HDMI_REG(HDMI_HOTPLUG, 0x1a8),
VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1c4),
VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0ec),
VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f0),
VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
VC5_PHY_REG(HDMI_TX_PHY_POWERDOWN_CTL, 0x004),
VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008),
VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c),
VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010),
VC5_PHY_REG(HDMI_TX_PHY_CTL_3, 0x014),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_0, 0x01c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CTL_1, 0x020),
VC5_PHY_REG(HDMI_TX_PHY_CLK_DIV, 0x028),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x034),
VC5_PHY_REG(HDMI_TX_PHY_CHANNEL_SWAP, 0x04c),
VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x044),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1, 0x050),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2, 0x054),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4, 0x05c),
VC5_RM_REG(HDMI_RM_CONTROL, 0x000),
VC5_RM_REG(HDMI_RM_OFFSET, 0x018),
VC5_RM_REG(HDMI_RM_FORMAT, 0x01c),
VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000),
VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010),
VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014),
VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018),
VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c),
VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020),
VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028),
VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c),
VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030),
VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034),
VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038),
VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c),
VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040),
VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044),
VC5_CSC_REG(HDMI_CSC_CTL, 0x000),
VC5_CSC_REG(HDMI_CSC_12_11, 0x004),
VC5_CSC_REG(HDMI_CSC_14_13, 0x008),
VC5_CSC_REG(HDMI_CSC_22_21, 0x00c),
VC5_CSC_REG(HDMI_CSC_24_23, 0x010),
VC5_CSC_REG(HDMI_CSC_32_31, 0x014),
VC5_CSC_REG(HDMI_CSC_34_33, 0x018),
VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c),
};
static const struct vc4_hdmi_register __maybe_unused vc6_hdmi_hdmi0_fields[] = {
VC4_HD_REG(HDMI_DVP_CTL, 0x0000),
VC4_HD_REG(HDMI_MAI_CTL, 0x0010),
VC4_HD_REG(HDMI_MAI_THR, 0x0014),
VC4_HD_REG(HDMI_MAI_FMT, 0x0018),
VC4_HD_REG(HDMI_MAI_DATA, 0x001c),
VC4_HD_REG(HDMI_MAI_SMP, 0x0020),
VC4_HD_REG(HDMI_VID_CTL, 0x0044),
VC4_HD_REG(HDMI_FRAME_COUNT, 0x0060),
VC4_HDMI_REG(HDMI_FIFO_CTL, 0x07c),
VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0c0),
VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0c4),
VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0cc),
VC4_HDMI_REG(HDMI_CRP_CFG, 0x0d0),
VC4_HDMI_REG(HDMI_CTS_0, 0x0d4),
VC4_HDMI_REG(HDMI_CTS_1, 0x0d8),
VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e8),
VC4_HDMI_REG(HDMI_HORZA, 0x0ec),
VC4_HDMI_REG(HDMI_HORZB, 0x0f0),
VC4_HDMI_REG(HDMI_VERTA0, 0x0f4),
VC4_HDMI_REG(HDMI_VERTB0, 0x0f8),
VC4_HDMI_REG(HDMI_VERTA1, 0x100),
VC4_HDMI_REG(HDMI_VERTB1, 0x104),
VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x114),
VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0a4),
VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a8),
VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x148),
VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x14c),
VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x150),
VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x158),
VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x15c),
VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x160),
VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x164),
VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x168),
VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x16c),
VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x170),
VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x18c),
VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x194),
VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x198),
VC4_HDMI_REG(HDMI_HOTPLUG, 0x1c8),
VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1e4),
VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0f0),
VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f4),
VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
VC5_PHY_REG(HDMI_TX_PHY_POWERUP_CTL, 0x004),
VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008),
VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c),
VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010),
VC5_PHY_REG(HDMI_TX_PHY_CTL_CK, 0x014),
VC5_PHY_REG(HDMI_TX_PHY_PLL_REFCLK, 0x01c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_POST_KDIV, 0x028),
VC5_PHY_REG(HDMI_TX_PHY_PLL_VCOCLK_DIV, 0x02c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x044),
VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x054),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_0, 0x060),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_1, 0x064),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_2, 0x068),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_3, 0x06c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_4, 0x070),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_5, 0x074),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_6, 0x078),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_7, 0x07c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_8, 0x080),
VC5_PHY_REG(HDMI_TX_PHY_PLL_RESET_CTL, 0x190),
VC5_PHY_REG(HDMI_TX_PHY_PLL_POWERUP_CTL, 0x194),
VC5_RM_REG(HDMI_RM_CONTROL, 0x000),
VC5_RM_REG(HDMI_RM_OFFSET, 0x018),
VC5_RM_REG(HDMI_RM_FORMAT, 0x01c),
VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000),
VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010),
VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014),
VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018),
VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c),
VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020),
VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028),
VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c),
VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030),
VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034),
VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038),
VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c),
VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040),
VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044),
VC5_CSC_REG(HDMI_CSC_CTL, 0x000),
VC5_CSC_REG(HDMI_CSC_12_11, 0x004),
VC5_CSC_REG(HDMI_CSC_14_13, 0x008),
VC5_CSC_REG(HDMI_CSC_22_21, 0x00c),
VC5_CSC_REG(HDMI_CSC_24_23, 0x010),
VC5_CSC_REG(HDMI_CSC_32_31, 0x014),
VC5_CSC_REG(HDMI_CSC_34_33, 0x018),
VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c),
};
static const struct vc4_hdmi_register __maybe_unused vc6_hdmi_hdmi1_fields[] = {
VC4_HD_REG(HDMI_DVP_CTL, 0x0000),
VC4_HD_REG(HDMI_MAI_CTL, 0x0030),
VC4_HD_REG(HDMI_MAI_THR, 0x0034),
VC4_HD_REG(HDMI_MAI_FMT, 0x0038),
VC4_HD_REG(HDMI_MAI_DATA, 0x003c),
VC4_HD_REG(HDMI_MAI_SMP, 0x0040),
VC4_HD_REG(HDMI_VID_CTL, 0x0048),
VC4_HD_REG(HDMI_FRAME_COUNT, 0x0064),
VC4_HDMI_REG(HDMI_FIFO_CTL, 0x07c),
VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0c0),
VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0c4),
VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0cc),
VC4_HDMI_REG(HDMI_CRP_CFG, 0x0d0),
VC4_HDMI_REG(HDMI_CTS_0, 0x0d4),
VC4_HDMI_REG(HDMI_CTS_1, 0x0d8),
VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e8),
VC4_HDMI_REG(HDMI_HORZA, 0x0ec),
VC4_HDMI_REG(HDMI_HORZB, 0x0f0),
VC4_HDMI_REG(HDMI_VERTA0, 0x0f4),
VC4_HDMI_REG(HDMI_VERTB0, 0x0f8),
VC4_HDMI_REG(HDMI_VERTA1, 0x100),
VC4_HDMI_REG(HDMI_VERTB1, 0x104),
VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x114),
VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0a4),
VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a8),
VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x148),
VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x14c),
VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x150),
VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x158),
VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x15c),
VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x160),
VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x164),
VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x168),
VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x16c),
VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x170),
VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x18c),
VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x194),
VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x198),
VC4_HDMI_REG(HDMI_HOTPLUG, 0x1c8),
VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1e4),
VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0f0),
VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f4),
VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
VC5_PHY_REG(HDMI_TX_PHY_POWERUP_CTL, 0x004),
VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008),
VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c),
VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010),
VC5_PHY_REG(HDMI_TX_PHY_CTL_CK, 0x014),
VC5_PHY_REG(HDMI_TX_PHY_PLL_REFCLK, 0x01c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_POST_KDIV, 0x028),
VC5_PHY_REG(HDMI_TX_PHY_PLL_VCOCLK_DIV, 0x02c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x044),
VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x054),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_0, 0x060),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_1, 0x064),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_2, 0x068),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_3, 0x06c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_4, 0x070),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_5, 0x074),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_6, 0x078),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_7, 0x07c),
VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_8, 0x080),
VC5_PHY_REG(HDMI_TX_PHY_PLL_RESET_CTL, 0x190),
VC5_PHY_REG(HDMI_TX_PHY_PLL_POWERUP_CTL, 0x194),
VC5_RM_REG(HDMI_RM_CONTROL, 0x000),
VC5_RM_REG(HDMI_RM_OFFSET, 0x018),
VC5_RM_REG(HDMI_RM_FORMAT, 0x01c),
VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000),
VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010),
VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014),
VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018),
VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c),
VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020),
VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028),
VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c),
VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030),
VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034),
VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038),
VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c),
VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040),
VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044),
VC5_CSC_REG(HDMI_CSC_CTL, 0x000),
VC5_CSC_REG(HDMI_CSC_12_11, 0x004),
VC5_CSC_REG(HDMI_CSC_14_13, 0x008),
VC5_CSC_REG(HDMI_CSC_22_21, 0x00c),
VC5_CSC_REG(HDMI_CSC_24_23, 0x010),
VC5_CSC_REG(HDMI_CSC_32_31, 0x014),
VC5_CSC_REG(HDMI_CSC_34_33, 0x018),
VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c),
};
static inline
void __iomem *__vc4_hdmi_get_field_base(struct vc4_hdmi *hdmi,
enum vc4_hdmi_regs reg)
{
switch (reg) {
case VC4_HD:
return hdmi->hd_regs;
case VC4_HDMI:
return hdmi->hdmicore_regs;
case VC5_CSC:
return hdmi->csc_regs;
case VC5_CEC:
return hdmi->cec_regs;
case VC5_DVP:
return hdmi->dvp_regs;
case VC5_PHY:
return hdmi->phy_regs;
case VC5_RAM:
return hdmi->ram_regs;
case VC5_RM:
return hdmi->rm_regs;
default:
return NULL;
}
return NULL;
}
static inline u32 vc4_hdmi_read(struct vc4_hdmi *hdmi,
enum vc4_hdmi_field reg)
{
const struct vc4_hdmi_register *field;
const struct vc4_hdmi_variant *variant = hdmi->variant;
void __iomem *base;
WARN_ON(pm_runtime_status_suspended(&hdmi->pdev->dev));
kunit_fail_current_test("Accessing an HDMI register in a unit test!\n");
if (reg >= variant->num_registers) {
dev_warn(&hdmi->pdev->dev,
"Invalid register ID %u\n", reg);
return 0;
}
field = &variant->registers[reg];
base = __vc4_hdmi_get_field_base(hdmi, field->reg);
if (!base) {
dev_warn(&hdmi->pdev->dev,
"Unknown register ID %u\n", reg);
return 0;
}
return readl(base + field->offset);
}
#define HDMI_READ(reg) vc4_hdmi_read(vc4_hdmi, reg)
static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi,
enum vc4_hdmi_field reg,
u32 value)
{
const struct vc4_hdmi_register *field;
const struct vc4_hdmi_variant *variant = hdmi->variant;
void __iomem *base;
lockdep_assert_held(&hdmi->hw_lock);
WARN_ON(pm_runtime_status_suspended(&hdmi->pdev->dev));
kunit_fail_current_test("Accessing an HDMI register in a unit test!\n");
if (reg >= variant->num_registers) {
dev_warn(&hdmi->pdev->dev,
"Invalid register ID %u\n", reg);
return;
}
field = &variant->registers[reg];
base = __vc4_hdmi_get_field_base(hdmi, field->reg);
if (!base) {
dev_warn(&hdmi->pdev->dev,
"Unknown register ID %u\n", reg);
return;
}
writel(value, base + field->offset);
}
#define HDMI_WRITE(reg, val) vc4_hdmi_write(vc4_hdmi, reg, val)
#endif /* _VC4_HDMI_REGS_H_ */
File diff suppressed because it is too large Load Diff
+350
View File
@@ -0,0 +1,350 @@
/*
* Copyright © 2014 Broadcom
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/**
* DOC: Interrupt management for the V3D engine
*
* We have an interrupt status register (V3D_INTCTL) which reports
* interrupts, and where writing 1 bits clears those interrupts.
* There are also a pair of interrupt registers
* (V3D_INTENA/V3D_INTDIS) where writing a 1 to their bits enables or
* disables that specific interrupt, and 0s written are ignored
* (reading either one returns the set of enabled interrupts).
*
* When we take a binning flush done interrupt, we need to submit the
* next frame for binning and move the finished frame to the render
* thread.
*
* When we take a render frame interrupt, we need to wake the
* processes waiting for some frame to be done, and get the next frame
* submitted ASAP (so the hardware doesn't sit idle when there's work
* to do).
*
* When we take the binner out of memory interrupt, we need to
* allocate some new memory and pass it to the binner so that the
* current job can make progress.
*/
#include <linux/platform_device.h>
#include <drm/drm_print.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
#include "vc4_trace.h"
#define V3D_DRIVER_IRQS (V3D_INT_OUTOMEM | \
V3D_INT_FLDONE | \
V3D_INT_FRDONE)
static void
vc4_overflow_mem_work(struct work_struct *work)
{
struct vc4_dev *vc4 =
container_of(work, struct vc4_dev, overflow_mem_work);
struct vc4_bo *bo;
int bin_bo_slot;
struct vc4_exec_info *exec;
unsigned long irqflags;
mutex_lock(&vc4->bin_bo_lock);
if (!vc4->bin_bo)
goto complete;
bo = vc4->bin_bo;
bin_bo_slot = vc4_v3d_get_bin_slot(vc4);
if (bin_bo_slot < 0) {
drm_err(&vc4->base, "Couldn't allocate binner overflow mem\n");
goto complete;
}
spin_lock_irqsave(&vc4->job_lock, irqflags);
if (vc4->bin_alloc_overflow) {
/* If we had overflow memory allocated previously,
* then that chunk will free when the current bin job
* is done. If we don't have a bin job running, then
* the chunk will be done whenever the list of render
* jobs has drained.
*/
exec = vc4_first_bin_job(vc4);
if (!exec)
exec = vc4_last_render_job(vc4);
if (exec) {
exec->bin_slots |= vc4->bin_alloc_overflow;
} else {
/* There's nothing queued in the hardware, so
* the old slot is free immediately.
*/
vc4->bin_alloc_used &= ~vc4->bin_alloc_overflow;
}
}
vc4->bin_alloc_overflow = BIT(bin_bo_slot);
V3D_WRITE(V3D_BPOA, bo->base.dma_addr + bin_bo_slot * vc4->bin_alloc_size);
V3D_WRITE(V3D_BPOS, vc4->bin_alloc_size);
V3D_WRITE(V3D_INTCTL, V3D_INT_OUTOMEM);
V3D_WRITE(V3D_INTENA, V3D_INT_OUTOMEM);
spin_unlock_irqrestore(&vc4->job_lock, irqflags);
complete:
mutex_unlock(&vc4->bin_bo_lock);
}
static void
vc4_irq_finish_bin_job(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_exec_info *next, *exec = vc4_first_bin_job(vc4);
if (!exec)
return;
trace_vc4_bcl_end_irq(dev, exec->seqno);
vc4_move_job_to_render(dev, exec);
next = vc4_first_bin_job(vc4);
/* Only submit the next job in the bin list if it matches the perfmon
* attached to the one that just finished (or if both jobs don't have
* perfmon attached to them).
*/
if (next && next->perfmon == exec->perfmon)
vc4_submit_next_bin_job(dev);
}
static void
vc4_cancel_bin_job(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_exec_info *exec = vc4_first_bin_job(vc4);
if (!exec)
return;
/* Stop the perfmon so that the next bin job can be started. */
if (exec->perfmon)
vc4_perfmon_stop(vc4, exec->perfmon, false);
list_move_tail(&exec->head, &vc4->bin_job_list);
vc4_submit_next_bin_job(dev);
}
static void
vc4_irq_finish_render_job(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_exec_info *exec = vc4_first_render_job(vc4);
struct vc4_exec_info *nextbin, *nextrender;
if (!exec)
return;
trace_vc4_rcl_end_irq(dev, exec->seqno);
vc4->finished_seqno++;
list_move_tail(&exec->head, &vc4->job_done_list);
nextbin = vc4_first_bin_job(vc4);
nextrender = vc4_first_render_job(vc4);
/* Only stop the perfmon if following jobs in the queue don't expect it
* to be enabled.
*/
if (exec->perfmon && !nextrender &&
(!nextbin || nextbin->perfmon != exec->perfmon))
vc4_perfmon_stop(vc4, exec->perfmon, true);
/* If there's a render job waiting, start it. If this is not the case
* we may have to unblock the binner if it's been stalled because of
* perfmon (this can be checked by comparing the perfmon attached to
* the finished renderjob to the one attached to the next bin job: if
* they don't match, this means the binner is stalled and should be
* restarted).
*/
if (nextrender)
vc4_submit_next_render_job(dev);
else if (nextbin && nextbin->perfmon != exec->perfmon)
vc4_submit_next_bin_job(dev);
if (exec->fence) {
dma_fence_signal_locked(exec->fence);
dma_fence_put(exec->fence);
exec->fence = NULL;
}
wake_up_all(&vc4->job_wait_queue);
schedule_work(&vc4->job_done_work);
}
static irqreturn_t
vc4_irq(int irq, void *arg)
{
struct drm_device *dev = arg;
struct vc4_dev *vc4 = to_vc4_dev(dev);
uint32_t intctl;
irqreturn_t status = IRQ_NONE;
barrier();
intctl = V3D_READ(V3D_INTCTL);
/* Acknowledge the interrupts we're handling here. The binner
* last flush / render frame done interrupt will be cleared,
* while OUTOMEM will stay high until the underlying cause is
* cleared.
*/
V3D_WRITE(V3D_INTCTL, intctl);
if (intctl & V3D_INT_OUTOMEM) {
/* Disable OUTOMEM until the work is done. */
V3D_WRITE(V3D_INTDIS, V3D_INT_OUTOMEM);
schedule_work(&vc4->overflow_mem_work);
status = IRQ_HANDLED;
}
if (intctl & V3D_INT_FLDONE) {
spin_lock(&vc4->job_lock);
vc4_irq_finish_bin_job(dev);
spin_unlock(&vc4->job_lock);
status = IRQ_HANDLED;
}
if (intctl & V3D_INT_FRDONE) {
spin_lock(&vc4->job_lock);
vc4_irq_finish_render_job(dev);
spin_unlock(&vc4->job_lock);
status = IRQ_HANDLED;
}
return status;
}
void
vc4_irq_enable(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
if (!vc4->v3d)
return;
/* Enable the render done interrupts. The out-of-memory interrupt is
* enabled as soon as we have a binner BO allocated.
*/
V3D_WRITE(V3D_INTENA, V3D_INT_FLDONE | V3D_INT_FRDONE);
}
void
vc4_irq_disable(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
if (!vc4->v3d)
return;
/* Disable sending interrupts for our driver's IRQs. */
V3D_WRITE(V3D_INTDIS, V3D_DRIVER_IRQS);
/* Clear any pending interrupts we might have left. */
V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
/* Finish any interrupt handler still in flight. */
synchronize_irq(vc4->irq);
cancel_work_sync(&vc4->overflow_mem_work);
}
int vc4_irq_install(struct drm_device *dev, int irq)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
int ret;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
if (!vc4->v3d)
return -ENODEV;
if (irq == IRQ_NOTCONNECTED)
return -ENOTCONN;
init_waitqueue_head(&vc4->job_wait_queue);
INIT_WORK(&vc4->overflow_mem_work, vc4_overflow_mem_work);
/* Clear any pending interrupts someone might have left around
* for us.
*/
V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
ret = devm_request_irq(dev->dev, irq, vc4_irq, 0,
dev_name(dev->dev), dev);
if (ret)
return ret;
vc4_irq_enable(dev);
return 0;
}
void vc4_irq_uninstall(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
vc4_irq_disable(dev);
}
/** Reinitializes interrupt registers when a GPU reset is performed. */
void vc4_irq_reset(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
unsigned long irqflags;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
/* Acknowledge any stale IRQs. */
V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
/*
* Turn all our interrupts on. Binner out of memory is the
* only one we expect to trigger at this point, since we've
* just come from poweron and haven't supplied any overflow
* memory yet.
*/
V3D_WRITE(V3D_INTENA, V3D_DRIVER_IRQS);
spin_lock_irqsave(&vc4->job_lock, irqflags);
vc4_cancel_bin_job(dev);
vc4_irq_finish_render_job(dev);
spin_unlock_irqrestore(&vc4->job_lock, irqflags);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,399 @@
/*
* Copyright © 2014 Broadcom
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef VC4_PACKET_H
#define VC4_PACKET_H
#include "vc4_regs.h" /* for VC4_MASK, VC4_GET_FIELD, VC4_SET_FIELD */
enum vc4_packet {
VC4_PACKET_HALT = 0,
VC4_PACKET_NOP = 1,
VC4_PACKET_FLUSH = 4,
VC4_PACKET_FLUSH_ALL = 5,
VC4_PACKET_START_TILE_BINNING = 6,
VC4_PACKET_INCREMENT_SEMAPHORE = 7,
VC4_PACKET_WAIT_ON_SEMAPHORE = 8,
VC4_PACKET_BRANCH = 16,
VC4_PACKET_BRANCH_TO_SUB_LIST = 17,
VC4_PACKET_STORE_MS_TILE_BUFFER = 24,
VC4_PACKET_STORE_MS_TILE_BUFFER_AND_EOF = 25,
VC4_PACKET_STORE_FULL_RES_TILE_BUFFER = 26,
VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER = 27,
VC4_PACKET_STORE_TILE_BUFFER_GENERAL = 28,
VC4_PACKET_LOAD_TILE_BUFFER_GENERAL = 29,
VC4_PACKET_GL_INDEXED_PRIMITIVE = 32,
VC4_PACKET_GL_ARRAY_PRIMITIVE = 33,
VC4_PACKET_COMPRESSED_PRIMITIVE = 48,
VC4_PACKET_CLIPPED_COMPRESSED_PRIMITIVE = 49,
VC4_PACKET_PRIMITIVE_LIST_FORMAT = 56,
VC4_PACKET_GL_SHADER_STATE = 64,
VC4_PACKET_NV_SHADER_STATE = 65,
VC4_PACKET_VG_SHADER_STATE = 66,
VC4_PACKET_CONFIGURATION_BITS = 96,
VC4_PACKET_FLAT_SHADE_FLAGS = 97,
VC4_PACKET_POINT_SIZE = 98,
VC4_PACKET_LINE_WIDTH = 99,
VC4_PACKET_RHT_X_BOUNDARY = 100,
VC4_PACKET_DEPTH_OFFSET = 101,
VC4_PACKET_CLIP_WINDOW = 102,
VC4_PACKET_VIEWPORT_OFFSET = 103,
VC4_PACKET_Z_CLIPPING = 104,
VC4_PACKET_CLIPPER_XY_SCALING = 105,
VC4_PACKET_CLIPPER_Z_SCALING = 106,
VC4_PACKET_TILE_BINNING_MODE_CONFIG = 112,
VC4_PACKET_TILE_RENDERING_MODE_CONFIG = 113,
VC4_PACKET_CLEAR_COLORS = 114,
VC4_PACKET_TILE_COORDINATES = 115,
/* Not an actual hardware packet -- this is what we use to put
* references to GEM bos in the command stream, since we need the u32
* int the actual address packet in order to store the offset from the
* start of the BO.
*/
VC4_PACKET_GEM_HANDLES = 254,
} __attribute__ ((__packed__));
#define VC4_PACKET_HALT_SIZE 1
#define VC4_PACKET_NOP_SIZE 1
#define VC4_PACKET_FLUSH_SIZE 1
#define VC4_PACKET_FLUSH_ALL_SIZE 1
#define VC4_PACKET_START_TILE_BINNING_SIZE 1
#define VC4_PACKET_INCREMENT_SEMAPHORE_SIZE 1
#define VC4_PACKET_WAIT_ON_SEMAPHORE_SIZE 1
#define VC4_PACKET_BRANCH_SIZE 5
#define VC4_PACKET_BRANCH_TO_SUB_LIST_SIZE 5
#define VC4_PACKET_STORE_MS_TILE_BUFFER_SIZE 1
#define VC4_PACKET_STORE_MS_TILE_BUFFER_AND_EOF_SIZE 1
#define VC4_PACKET_STORE_FULL_RES_TILE_BUFFER_SIZE 5
#define VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER_SIZE 5
#define VC4_PACKET_STORE_TILE_BUFFER_GENERAL_SIZE 7
#define VC4_PACKET_LOAD_TILE_BUFFER_GENERAL_SIZE 7
#define VC4_PACKET_GL_INDEXED_PRIMITIVE_SIZE 14
#define VC4_PACKET_GL_ARRAY_PRIMITIVE_SIZE 10
#define VC4_PACKET_COMPRESSED_PRIMITIVE_SIZE 1
#define VC4_PACKET_CLIPPED_COMPRESSED_PRIMITIVE_SIZE 1
#define VC4_PACKET_PRIMITIVE_LIST_FORMAT_SIZE 2
#define VC4_PACKET_GL_SHADER_STATE_SIZE 5
#define VC4_PACKET_NV_SHADER_STATE_SIZE 5
#define VC4_PACKET_VG_SHADER_STATE_SIZE 5
#define VC4_PACKET_CONFIGURATION_BITS_SIZE 4
#define VC4_PACKET_FLAT_SHADE_FLAGS_SIZE 5
#define VC4_PACKET_POINT_SIZE_SIZE 5
#define VC4_PACKET_LINE_WIDTH_SIZE 5
#define VC4_PACKET_RHT_X_BOUNDARY_SIZE 3
#define VC4_PACKET_DEPTH_OFFSET_SIZE 5
#define VC4_PACKET_CLIP_WINDOW_SIZE 9
#define VC4_PACKET_VIEWPORT_OFFSET_SIZE 5
#define VC4_PACKET_Z_CLIPPING_SIZE 9
#define VC4_PACKET_CLIPPER_XY_SCALING_SIZE 9
#define VC4_PACKET_CLIPPER_Z_SCALING_SIZE 9
#define VC4_PACKET_TILE_BINNING_MODE_CONFIG_SIZE 16
#define VC4_PACKET_TILE_RENDERING_MODE_CONFIG_SIZE 11
#define VC4_PACKET_CLEAR_COLORS_SIZE 14
#define VC4_PACKET_TILE_COORDINATES_SIZE 3
#define VC4_PACKET_GEM_HANDLES_SIZE 9
/* Number of multisamples supported. */
#define VC4_MAX_SAMPLES 4
/* Size of a full resolution color or Z tile buffer load/store. */
#define VC4_TILE_BUFFER_SIZE (64 * 64 * 4)
/** @{
* Bits used by packets like VC4_PACKET_STORE_TILE_BUFFER_GENERAL and
* VC4_PACKET_TILE_RENDERING_MODE_CONFIG.
*/
#define VC4_TILING_FORMAT_LINEAR 0
#define VC4_TILING_FORMAT_T 1
#define VC4_TILING_FORMAT_LT 2
/** @} */
/** @{
*
* low bits of VC4_PACKET_STORE_FULL_RES_TILE_BUFFER and
* VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER.
*/
#define VC4_LOADSTORE_FULL_RES_EOF BIT(3)
#define VC4_LOADSTORE_FULL_RES_DISABLE_CLEAR_ALL BIT(2)
#define VC4_LOADSTORE_FULL_RES_DISABLE_ZS BIT(1)
#define VC4_LOADSTORE_FULL_RES_DISABLE_COLOR BIT(0)
/** @{
*
* low bits of VC4_PACKET_STORE_FULL_RES_TILE_BUFFER and
* VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER.
*/
#define VC4_LOADSTORE_FULL_RES_EOF BIT(3)
#define VC4_LOADSTORE_FULL_RES_DISABLE_CLEAR_ALL BIT(2)
#define VC4_LOADSTORE_FULL_RES_DISABLE_ZS BIT(1)
#define VC4_LOADSTORE_FULL_RES_DISABLE_COLOR BIT(0)
/** @{
*
* byte 2 of VC4_PACKET_STORE_TILE_BUFFER_GENERAL and
* VC4_PACKET_LOAD_TILE_BUFFER_GENERAL (low bits of the address)
*/
#define VC4_LOADSTORE_TILE_BUFFER_EOF BIT(3)
#define VC4_LOADSTORE_TILE_BUFFER_DISABLE_FULL_VG_MASK BIT(2)
#define VC4_LOADSTORE_TILE_BUFFER_DISABLE_FULL_ZS BIT(1)
#define VC4_LOADSTORE_TILE_BUFFER_DISABLE_FULL_COLOR BIT(0)
/** @} */
/** @{
*
* byte 0-1 of VC4_PACKET_STORE_TILE_BUFFER_GENERAL and
* VC4_PACKET_LOAD_TILE_BUFFER_GENERAL
*/
#define VC4_STORE_TILE_BUFFER_DISABLE_VG_MASK_CLEAR BIT(15)
#define VC4_STORE_TILE_BUFFER_DISABLE_ZS_CLEAR BIT(14)
#define VC4_STORE_TILE_BUFFER_DISABLE_COLOR_CLEAR BIT(13)
#define VC4_STORE_TILE_BUFFER_DISABLE_SWAP BIT(12)
#define VC4_LOADSTORE_TILE_BUFFER_FORMAT_MASK VC4_MASK(9, 8)
#define VC4_LOADSTORE_TILE_BUFFER_FORMAT_SHIFT 8
#define VC4_LOADSTORE_TILE_BUFFER_RGBA8888 0
#define VC4_LOADSTORE_TILE_BUFFER_BGR565_DITHER 1
#define VC4_LOADSTORE_TILE_BUFFER_BGR565 2
/** @} */
/** @{
*
* byte 0 of VC4_PACKET_STORE_TILE_BUFFER_GENERAL and
* VC4_PACKET_LOAD_TILE_BUFFER_GENERAL
*/
#define VC4_STORE_TILE_BUFFER_MODE_MASK VC4_MASK(7, 6)
#define VC4_STORE_TILE_BUFFER_MODE_SHIFT 6
#define VC4_STORE_TILE_BUFFER_MODE_SAMPLE0 (0 << 6)
#define VC4_STORE_TILE_BUFFER_MODE_DECIMATE_X4 (1 << 6)
#define VC4_STORE_TILE_BUFFER_MODE_DECIMATE_X16 (2 << 6)
/** The values of the field are VC4_TILING_FORMAT_* */
#define VC4_LOADSTORE_TILE_BUFFER_TILING_MASK VC4_MASK(5, 4)
#define VC4_LOADSTORE_TILE_BUFFER_TILING_SHIFT 4
#define VC4_LOADSTORE_TILE_BUFFER_BUFFER_MASK VC4_MASK(2, 0)
#define VC4_LOADSTORE_TILE_BUFFER_BUFFER_SHIFT 0
#define VC4_LOADSTORE_TILE_BUFFER_NONE 0
#define VC4_LOADSTORE_TILE_BUFFER_COLOR 1
#define VC4_LOADSTORE_TILE_BUFFER_ZS 2
#define VC4_LOADSTORE_TILE_BUFFER_Z 3
#define VC4_LOADSTORE_TILE_BUFFER_VG_MASK 4
#define VC4_LOADSTORE_TILE_BUFFER_FULL 5
/** @} */
#define VC4_INDEX_BUFFER_U8 (0 << 4)
#define VC4_INDEX_BUFFER_U16 (1 << 4)
/* This flag is only present in NV shader state. */
#define VC4_SHADER_FLAG_SHADED_CLIP_COORDS BIT(3)
#define VC4_SHADER_FLAG_ENABLE_CLIPPING BIT(2)
#define VC4_SHADER_FLAG_VS_POINT_SIZE BIT(1)
#define VC4_SHADER_FLAG_FS_SINGLE_THREAD BIT(0)
/** @{ byte 2 of config bits. */
#define VC4_CONFIG_BITS_EARLY_Z_UPDATE BIT(1)
#define VC4_CONFIG_BITS_EARLY_Z BIT(0)
/** @} */
/** @{ byte 1 of config bits. */
#define VC4_CONFIG_BITS_Z_UPDATE BIT(7)
/** same values in this 3-bit field as PIPE_FUNC_* */
#define VC4_CONFIG_BITS_DEPTH_FUNC_SHIFT 4
#define VC4_CONFIG_BITS_COVERAGE_READ_LEAVE BIT(3)
#define VC4_CONFIG_BITS_COVERAGE_UPDATE_NONZERO (0 << 1)
#define VC4_CONFIG_BITS_COVERAGE_UPDATE_ODD (1 << 1)
#define VC4_CONFIG_BITS_COVERAGE_UPDATE_OR (2 << 1)
#define VC4_CONFIG_BITS_COVERAGE_UPDATE_ZERO (3 << 1)
#define VC4_CONFIG_BITS_COVERAGE_PIPE_SELECT BIT(0)
/** @} */
/** @{ byte 0 of config bits. */
#define VC4_CONFIG_BITS_RASTERIZER_OVERSAMPLE_NONE (0 << 6)
#define VC4_CONFIG_BITS_RASTERIZER_OVERSAMPLE_4X (1 << 6)
#define VC4_CONFIG_BITS_RASTERIZER_OVERSAMPLE_16X (2 << 6)
#define VC4_CONFIG_BITS_AA_POINTS_AND_LINES BIT(4)
#define VC4_CONFIG_BITS_ENABLE_DEPTH_OFFSET BIT(3)
#define VC4_CONFIG_BITS_CW_PRIMITIVES BIT(2)
#define VC4_CONFIG_BITS_ENABLE_PRIM_BACK BIT(1)
#define VC4_CONFIG_BITS_ENABLE_PRIM_FRONT BIT(0)
/** @} */
/** @{ bits in the last u8 of VC4_PACKET_TILE_BINNING_MODE_CONFIG */
#define VC4_BIN_CONFIG_DB_NON_MS BIT(7)
#define VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_MASK VC4_MASK(6, 5)
#define VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_SHIFT 5
#define VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_32 0
#define VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_64 1
#define VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_128 2
#define VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_256 3
#define VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_MASK VC4_MASK(4, 3)
#define VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_SHIFT 3
#define VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_32 0
#define VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_64 1
#define VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_128 2
#define VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_256 3
#define VC4_BIN_CONFIG_AUTO_INIT_TSDA BIT(2)
#define VC4_BIN_CONFIG_TILE_BUFFER_64BIT BIT(1)
#define VC4_BIN_CONFIG_MS_MODE_4X BIT(0)
/** @} */
/** @{ bits in the last u16 of VC4_PACKET_TILE_RENDERING_MODE_CONFIG */
#define VC4_RENDER_CONFIG_DB_NON_MS BIT(12)
#define VC4_RENDER_CONFIG_EARLY_Z_COVERAGE_DISABLE BIT(11)
#define VC4_RENDER_CONFIG_EARLY_Z_DIRECTION_G BIT(10)
#define VC4_RENDER_CONFIG_COVERAGE_MODE BIT(9)
#define VC4_RENDER_CONFIG_ENABLE_VG_MASK BIT(8)
/** The values of the field are VC4_TILING_FORMAT_* */
#define VC4_RENDER_CONFIG_MEMORY_FORMAT_MASK VC4_MASK(7, 6)
#define VC4_RENDER_CONFIG_MEMORY_FORMAT_SHIFT 6
#define VC4_RENDER_CONFIG_DECIMATE_MODE_1X (0 << 4)
#define VC4_RENDER_CONFIG_DECIMATE_MODE_4X (1 << 4)
#define VC4_RENDER_CONFIG_DECIMATE_MODE_16X (2 << 4)
#define VC4_RENDER_CONFIG_FORMAT_MASK VC4_MASK(3, 2)
#define VC4_RENDER_CONFIG_FORMAT_SHIFT 2
#define VC4_RENDER_CONFIG_FORMAT_BGR565_DITHERED 0
#define VC4_RENDER_CONFIG_FORMAT_RGBA8888 1
#define VC4_RENDER_CONFIG_FORMAT_BGR565 2
#define VC4_RENDER_CONFIG_TILE_BUFFER_64BIT BIT(1)
#define VC4_RENDER_CONFIG_MS_MODE_4X BIT(0)
#define VC4_PRIMITIVE_LIST_FORMAT_16_INDEX (1 << 4)
#define VC4_PRIMITIVE_LIST_FORMAT_32_XY (3 << 4)
#define VC4_PRIMITIVE_LIST_FORMAT_TYPE_POINTS (0 << 0)
#define VC4_PRIMITIVE_LIST_FORMAT_TYPE_LINES (1 << 0)
#define VC4_PRIMITIVE_LIST_FORMAT_TYPE_TRIANGLES (2 << 0)
#define VC4_PRIMITIVE_LIST_FORMAT_TYPE_RHT (3 << 0)
enum vc4_texture_data_type {
VC4_TEXTURE_TYPE_RGBA8888 = 0,
VC4_TEXTURE_TYPE_RGBX8888 = 1,
VC4_TEXTURE_TYPE_RGBA4444 = 2,
VC4_TEXTURE_TYPE_RGBA5551 = 3,
VC4_TEXTURE_TYPE_RGB565 = 4,
VC4_TEXTURE_TYPE_LUMINANCE = 5,
VC4_TEXTURE_TYPE_ALPHA = 6,
VC4_TEXTURE_TYPE_LUMALPHA = 7,
VC4_TEXTURE_TYPE_ETC1 = 8,
VC4_TEXTURE_TYPE_S16F = 9,
VC4_TEXTURE_TYPE_S8 = 10,
VC4_TEXTURE_TYPE_S16 = 11,
VC4_TEXTURE_TYPE_BW1 = 12,
VC4_TEXTURE_TYPE_A4 = 13,
VC4_TEXTURE_TYPE_A1 = 14,
VC4_TEXTURE_TYPE_RGBA64 = 15,
VC4_TEXTURE_TYPE_RGBA32R = 16,
VC4_TEXTURE_TYPE_YUV422R = 17,
};
#define VC4_TEX_P0_OFFSET_MASK VC4_MASK(31, 12)
#define VC4_TEX_P0_OFFSET_SHIFT 12
#define VC4_TEX_P0_CSWIZ_MASK VC4_MASK(11, 10)
#define VC4_TEX_P0_CSWIZ_SHIFT 10
#define VC4_TEX_P0_CMMODE_MASK VC4_MASK(9, 9)
#define VC4_TEX_P0_CMMODE_SHIFT 9
#define VC4_TEX_P0_FLIPY_MASK VC4_MASK(8, 8)
#define VC4_TEX_P0_FLIPY_SHIFT 8
#define VC4_TEX_P0_TYPE_MASK VC4_MASK(7, 4)
#define VC4_TEX_P0_TYPE_SHIFT 4
#define VC4_TEX_P0_MIPLVLS_MASK VC4_MASK(3, 0)
#define VC4_TEX_P0_MIPLVLS_SHIFT 0
#define VC4_TEX_P1_TYPE4_MASK VC4_MASK(31, 31)
#define VC4_TEX_P1_TYPE4_SHIFT 31
#define VC4_TEX_P1_HEIGHT_MASK VC4_MASK(30, 20)
#define VC4_TEX_P1_HEIGHT_SHIFT 20
#define VC4_TEX_P1_ETCFLIP_MASK VC4_MASK(19, 19)
#define VC4_TEX_P1_ETCFLIP_SHIFT 19
#define VC4_TEX_P1_WIDTH_MASK VC4_MASK(18, 8)
#define VC4_TEX_P1_WIDTH_SHIFT 8
#define VC4_TEX_P1_MAGFILT_MASK VC4_MASK(7, 7)
#define VC4_TEX_P1_MAGFILT_SHIFT 7
# define VC4_TEX_P1_MAGFILT_LINEAR 0
# define VC4_TEX_P1_MAGFILT_NEAREST 1
#define VC4_TEX_P1_MINFILT_MASK VC4_MASK(6, 4)
#define VC4_TEX_P1_MINFILT_SHIFT 4
# define VC4_TEX_P1_MINFILT_LINEAR 0
# define VC4_TEX_P1_MINFILT_NEAREST 1
# define VC4_TEX_P1_MINFILT_NEAR_MIP_NEAR 2
# define VC4_TEX_P1_MINFILT_NEAR_MIP_LIN 3
# define VC4_TEX_P1_MINFILT_LIN_MIP_NEAR 4
# define VC4_TEX_P1_MINFILT_LIN_MIP_LIN 5
#define VC4_TEX_P1_WRAP_T_MASK VC4_MASK(3, 2)
#define VC4_TEX_P1_WRAP_T_SHIFT 2
#define VC4_TEX_P1_WRAP_S_MASK VC4_MASK(1, 0)
#define VC4_TEX_P1_WRAP_S_SHIFT 0
# define VC4_TEX_P1_WRAP_REPEAT 0
# define VC4_TEX_P1_WRAP_CLAMP 1
# define VC4_TEX_P1_WRAP_MIRROR 2
# define VC4_TEX_P1_WRAP_BORDER 3
#define VC4_TEX_P2_PTYPE_MASK VC4_MASK(31, 30)
#define VC4_TEX_P2_PTYPE_SHIFT 30
# define VC4_TEX_P2_PTYPE_IGNORED 0
# define VC4_TEX_P2_PTYPE_CUBE_MAP_STRIDE 1
# define VC4_TEX_P2_PTYPE_CHILD_IMAGE_DIMENSIONS 2
# define VC4_TEX_P2_PTYPE_CHILD_IMAGE_OFFSETS 3
/* VC4_TEX_P2_PTYPE_CUBE_MAP_STRIDE bits */
#define VC4_TEX_P2_CMST_MASK VC4_MASK(29, 12)
#define VC4_TEX_P2_CMST_SHIFT 12
#define VC4_TEX_P2_BSLOD_MASK VC4_MASK(0, 0)
#define VC4_TEX_P2_BSLOD_SHIFT 0
/* VC4_TEX_P2_PTYPE_CHILD_IMAGE_DIMENSIONS */
#define VC4_TEX_P2_CHEIGHT_MASK VC4_MASK(22, 12)
#define VC4_TEX_P2_CHEIGHT_SHIFT 12
#define VC4_TEX_P2_CWIDTH_MASK VC4_MASK(10, 0)
#define VC4_TEX_P2_CWIDTH_SHIFT 0
/* VC4_TEX_P2_PTYPE_CHILD_IMAGE_OFFSETS */
#define VC4_TEX_P2_CYOFF_MASK VC4_MASK(22, 12)
#define VC4_TEX_P2_CYOFF_SHIFT 12
#define VC4_TEX_P2_CXOFF_MASK VC4_MASK(10, 0)
#define VC4_TEX_P2_CXOFF_SHIFT 0
#endif /* VC4_PACKET_H */
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 Broadcom
*/
/**
* DOC: VC4 V3D performance monitor module
*
* The V3D block provides 16 hardware counters which can count various events.
*/
#include <drm/drm_print.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
void vc4_perfmon_get(struct vc4_perfmon *perfmon)
{
struct vc4_dev *vc4;
if (!perfmon)
return;
vc4 = perfmon->dev;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
refcount_inc(&perfmon->refcnt);
}
void vc4_perfmon_put(struct vc4_perfmon *perfmon)
{
struct vc4_dev *vc4;
if (!perfmon)
return;
vc4 = perfmon->dev;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
if (refcount_dec_and_test(&perfmon->refcnt))
kfree(perfmon);
}
void vc4_perfmon_start(struct vc4_dev *vc4, struct vc4_perfmon *perfmon)
{
unsigned int i;
u32 mask;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
if (WARN_ON_ONCE(!perfmon || vc4->active_perfmon))
return;
for (i = 0; i < perfmon->ncounters; i++)
V3D_WRITE(V3D_PCTRS(i), perfmon->events[i]);
mask = GENMASK(perfmon->ncounters - 1, 0);
V3D_WRITE(V3D_PCTRC, mask);
V3D_WRITE(V3D_PCTRE, V3D_PCTRE_EN | mask);
vc4->active_perfmon = perfmon;
}
void vc4_perfmon_stop(struct vc4_dev *vc4, struct vc4_perfmon *perfmon,
bool capture)
{
unsigned int i;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
if (WARN_ON_ONCE(!vc4->active_perfmon ||
perfmon != vc4->active_perfmon))
return;
if (capture) {
for (i = 0; i < perfmon->ncounters; i++)
perfmon->counters[i] += V3D_READ(V3D_PCTR(i));
}
V3D_WRITE(V3D_PCTRE, 0);
vc4->active_perfmon = NULL;
}
struct vc4_perfmon *vc4_perfmon_find(struct vc4_file *vc4file, int id)
{
struct vc4_dev *vc4 = vc4file->dev;
struct vc4_perfmon *perfmon;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return NULL;
xa_lock(&vc4file->perfmons);
perfmon = xa_load(&vc4file->perfmons, id);
vc4_perfmon_get(perfmon);
xa_unlock(&vc4file->perfmons);
return perfmon;
}
void vc4_perfmon_open_file(struct vc4_file *vc4file)
{
struct vc4_dev *vc4 = vc4file->dev;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
xa_init_flags(&vc4file->perfmons, XA_FLAGS_ALLOC1);
}
static void vc4_perfmon_delete(struct vc4_file *vc4file,
struct vc4_perfmon *perfmon)
{
struct vc4_dev *vc4 = vc4file->dev;
/* If the active perfmon is being destroyed, stop it first */
if (perfmon == vc4->active_perfmon)
vc4_perfmon_stop(vc4, perfmon, false);
vc4_perfmon_put(perfmon);
}
void vc4_perfmon_close_file(struct vc4_file *vc4file)
{
struct vc4_dev *vc4 = vc4file->dev;
struct vc4_perfmon *perfmon;
unsigned long id;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
xa_for_each(&vc4file->perfmons, id, perfmon)
vc4_perfmon_delete(vc4file, perfmon);
xa_destroy(&vc4file->perfmons);
}
int vc4_perfmon_create_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_file *vc4file = file_priv->driver_priv;
struct drm_vc4_perfmon_create *req = data;
struct vc4_perfmon *perfmon;
unsigned int i;
int ret;
u32 id;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
if (!vc4->v3d) {
DRM_DEBUG("Creating perfmon no VC4 V3D probed\n");
return -ENODEV;
}
/* Number of monitored counters cannot exceed HW limits. */
if (req->ncounters > DRM_VC4_MAX_PERF_COUNTERS ||
!req->ncounters)
return -EINVAL;
/* Make sure all events are valid. */
for (i = 0; i < req->ncounters; i++) {
if (req->events[i] >= VC4_PERFCNT_NUM_EVENTS)
return -EINVAL;
}
perfmon = kzalloc_flex(*perfmon, counters, req->ncounters);
if (!perfmon)
return -ENOMEM;
perfmon->dev = vc4;
for (i = 0; i < req->ncounters; i++)
perfmon->events[i] = req->events[i];
perfmon->ncounters = req->ncounters;
refcount_set(&perfmon->refcnt, 1);
ret = xa_alloc(&vc4file->perfmons, &id, perfmon, xa_limit_32b,
GFP_KERNEL);
if (ret < 0) {
kfree(perfmon);
return ret;
}
req->id = id;
return 0;
}
int vc4_perfmon_destroy_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_file *vc4file = file_priv->driver_priv;
struct drm_vc4_perfmon_destroy *req = data;
struct vc4_perfmon *perfmon;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
if (!vc4->v3d) {
DRM_DEBUG("Destroying perfmon no VC4 V3D probed\n");
return -ENODEV;
}
perfmon = xa_erase(&vc4file->perfmons, req->id);
if (!perfmon)
return -EINVAL;
vc4_perfmon_delete(vc4file, perfmon);
return 0;
}
int vc4_perfmon_get_values_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_file *vc4file = file_priv->driver_priv;
struct drm_vc4_perfmon_get_values *req = data;
struct vc4_perfmon *perfmon;
int ret;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
if (!vc4->v3d) {
DRM_DEBUG("Getting perfmon no VC4 V3D probed\n");
return -ENODEV;
}
perfmon = vc4_perfmon_find(vc4file, req->id);
if (!perfmon)
return -EINVAL;
if (copy_to_user(u64_to_user_ptr(req->values_ptr), perfmon->counters,
perfmon->ncounters * sizeof(u64)))
ret = -EFAULT;
else
ret = 0;
vc4_perfmon_put(perfmon);
return ret;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,279 @@
/*
* Copyright © 2014 Broadcom
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef VC4_QPU_DEFINES_H
#define VC4_QPU_DEFINES_H
enum qpu_op_add {
QPU_A_NOP,
QPU_A_FADD,
QPU_A_FSUB,
QPU_A_FMIN,
QPU_A_FMAX,
QPU_A_FMINABS,
QPU_A_FMAXABS,
QPU_A_FTOI,
QPU_A_ITOF,
QPU_A_ADD = 12,
QPU_A_SUB,
QPU_A_SHR,
QPU_A_ASR,
QPU_A_ROR,
QPU_A_SHL,
QPU_A_MIN,
QPU_A_MAX,
QPU_A_AND,
QPU_A_OR,
QPU_A_XOR,
QPU_A_NOT,
QPU_A_CLZ,
QPU_A_V8ADDS = 30,
QPU_A_V8SUBS = 31,
};
enum qpu_op_mul {
QPU_M_NOP,
QPU_M_FMUL,
QPU_M_MUL24,
QPU_M_V8MULD,
QPU_M_V8MIN,
QPU_M_V8MAX,
QPU_M_V8ADDS,
QPU_M_V8SUBS,
};
enum qpu_raddr {
QPU_R_FRAG_PAYLOAD_ZW = 15, /* W for A file, Z for B file */
/* 0-31 are the plain regfile a or b fields */
QPU_R_UNIF = 32,
QPU_R_VARY = 35,
QPU_R_ELEM_QPU = 38,
QPU_R_NOP,
QPU_R_XY_PIXEL_COORD = 41,
QPU_R_MS_REV_FLAGS = 42,
QPU_R_VPM = 48,
QPU_R_VPM_LD_BUSY,
QPU_R_VPM_LD_WAIT,
QPU_R_MUTEX_ACQUIRE,
};
enum qpu_waddr {
/* 0-31 are the plain regfile a or b fields */
QPU_W_ACC0 = 32, /* aka r0 */
QPU_W_ACC1,
QPU_W_ACC2,
QPU_W_ACC3,
QPU_W_TMU_NOSWAP,
QPU_W_ACC5,
QPU_W_HOST_INT,
QPU_W_NOP,
QPU_W_UNIFORMS_ADDRESS,
QPU_W_QUAD_XY, /* X for regfile a, Y for regfile b */
QPU_W_MS_FLAGS = 42,
QPU_W_REV_FLAG = 42,
QPU_W_TLB_STENCIL_SETUP = 43,
QPU_W_TLB_Z,
QPU_W_TLB_COLOR_MS,
QPU_W_TLB_COLOR_ALL,
QPU_W_TLB_ALPHA_MASK,
QPU_W_VPM,
QPU_W_VPMVCD_SETUP, /* LD for regfile a, ST for regfile b */
QPU_W_VPM_ADDR, /* LD for regfile a, ST for regfile b */
QPU_W_MUTEX_RELEASE,
QPU_W_SFU_RECIP,
QPU_W_SFU_RECIPSQRT,
QPU_W_SFU_EXP,
QPU_W_SFU_LOG,
QPU_W_TMU0_S,
QPU_W_TMU0_T,
QPU_W_TMU0_R,
QPU_W_TMU0_B,
QPU_W_TMU1_S,
QPU_W_TMU1_T,
QPU_W_TMU1_R,
QPU_W_TMU1_B,
};
enum qpu_sig_bits {
QPU_SIG_SW_BREAKPOINT,
QPU_SIG_NONE,
QPU_SIG_THREAD_SWITCH,
QPU_SIG_PROG_END,
QPU_SIG_WAIT_FOR_SCOREBOARD,
QPU_SIG_SCOREBOARD_UNLOCK,
QPU_SIG_LAST_THREAD_SWITCH,
QPU_SIG_COVERAGE_LOAD,
QPU_SIG_COLOR_LOAD,
QPU_SIG_COLOR_LOAD_END,
QPU_SIG_LOAD_TMU0,
QPU_SIG_LOAD_TMU1,
QPU_SIG_ALPHA_MASK_LOAD,
QPU_SIG_SMALL_IMM,
QPU_SIG_LOAD_IMM,
QPU_SIG_BRANCH
};
enum qpu_mux {
/* hardware mux values */
QPU_MUX_R0,
QPU_MUX_R1,
QPU_MUX_R2,
QPU_MUX_R3,
QPU_MUX_R4,
QPU_MUX_R5,
QPU_MUX_A,
QPU_MUX_B,
/* non-hardware mux values */
QPU_MUX_IMM,
};
enum qpu_cond {
QPU_COND_NEVER,
QPU_COND_ALWAYS,
QPU_COND_ZS,
QPU_COND_ZC,
QPU_COND_NS,
QPU_COND_NC,
QPU_COND_CS,
QPU_COND_CC,
};
enum qpu_pack_mul {
QPU_PACK_MUL_NOP,
/* replicated to each 8 bits of the 32-bit dst. */
QPU_PACK_MUL_8888 = 3,
QPU_PACK_MUL_8A,
QPU_PACK_MUL_8B,
QPU_PACK_MUL_8C,
QPU_PACK_MUL_8D,
};
enum qpu_pack_a {
QPU_PACK_A_NOP,
/* convert to 16 bit float if float input, or to int16. */
QPU_PACK_A_16A,
QPU_PACK_A_16B,
/* replicated to each 8 bits of the 32-bit dst. */
QPU_PACK_A_8888,
/* Convert to 8-bit unsigned int. */
QPU_PACK_A_8A,
QPU_PACK_A_8B,
QPU_PACK_A_8C,
QPU_PACK_A_8D,
/* Saturating variants of the previous instructions. */
QPU_PACK_A_32_SAT, /* int-only */
QPU_PACK_A_16A_SAT, /* int or float */
QPU_PACK_A_16B_SAT,
QPU_PACK_A_8888_SAT,
QPU_PACK_A_8A_SAT,
QPU_PACK_A_8B_SAT,
QPU_PACK_A_8C_SAT,
QPU_PACK_A_8D_SAT,
};
enum qpu_unpack_r4 {
QPU_UNPACK_R4_NOP,
QPU_UNPACK_R4_F16A_TO_F32,
QPU_UNPACK_R4_F16B_TO_F32,
QPU_UNPACK_R4_8D_REP,
QPU_UNPACK_R4_8A,
QPU_UNPACK_R4_8B,
QPU_UNPACK_R4_8C,
QPU_UNPACK_R4_8D,
};
#define QPU_MASK(high, low) \
((((uint64_t)1 << ((high) - (low) + 1)) - 1) << (low))
#define QPU_GET_FIELD(word, field) \
((uint32_t)(((word) & field ## _MASK) >> field ## _SHIFT))
#define QPU_SIG_SHIFT 60
#define QPU_SIG_MASK QPU_MASK(63, 60)
#define QPU_UNPACK_SHIFT 57
#define QPU_UNPACK_MASK QPU_MASK(59, 57)
/**
* If set, the pack field means PACK_MUL or R4 packing, instead of normal
* regfile a packing.
*/
#define QPU_PM ((uint64_t)1 << 56)
#define QPU_PACK_SHIFT 52
#define QPU_PACK_MASK QPU_MASK(55, 52)
#define QPU_COND_ADD_SHIFT 49
#define QPU_COND_ADD_MASK QPU_MASK(51, 49)
#define QPU_COND_MUL_SHIFT 46
#define QPU_COND_MUL_MASK QPU_MASK(48, 46)
#define QPU_BRANCH_COND_SHIFT 52
#define QPU_BRANCH_COND_MASK QPU_MASK(55, 52)
#define QPU_BRANCH_REL ((uint64_t)1 << 51)
#define QPU_BRANCH_REG ((uint64_t)1 << 50)
#define QPU_BRANCH_RADDR_A_SHIFT 45
#define QPU_BRANCH_RADDR_A_MASK QPU_MASK(49, 45)
#define QPU_SF ((uint64_t)1 << 45)
#define QPU_WADDR_ADD_SHIFT 38
#define QPU_WADDR_ADD_MASK QPU_MASK(43, 38)
#define QPU_WADDR_MUL_SHIFT 32
#define QPU_WADDR_MUL_MASK QPU_MASK(37, 32)
#define QPU_OP_MUL_SHIFT 29
#define QPU_OP_MUL_MASK QPU_MASK(31, 29)
#define QPU_RADDR_A_SHIFT 18
#define QPU_RADDR_A_MASK QPU_MASK(23, 18)
#define QPU_RADDR_B_SHIFT 12
#define QPU_RADDR_B_MASK QPU_MASK(17, 12)
#define QPU_SMALL_IMM_SHIFT 12
#define QPU_SMALL_IMM_MASK QPU_MASK(17, 12)
#define QPU_ADD_A_SHIFT 9
#define QPU_ADD_A_MASK QPU_MASK(11, 9)
#define QPU_ADD_B_SHIFT 6
#define QPU_ADD_B_MASK QPU_MASK(8, 6)
#define QPU_MUL_A_SHIFT 3
#define QPU_MUL_A_MASK QPU_MASK(5, 3)
#define QPU_MUL_B_SHIFT 0
#define QPU_MUL_B_MASK QPU_MASK(2, 0)
#define QPU_WS ((uint64_t)1 << 44)
#define QPU_OP_ADD_SHIFT 24
#define QPU_OP_ADD_MASK QPU_MASK(28, 24)
#define QPU_LOAD_IMM_SHIFT 0
#define QPU_LOAD_IMM_MASK QPU_MASK(31, 0)
#define QPU_BRANCH_TARGET_SHIFT 0
#define QPU_BRANCH_TARGET_MASK QPU_MASK(31, 0)
#endif /* VC4_QPU_DEFINES_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,665 @@
/*
* Copyright © 2014-2015 Broadcom
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/**
* DOC: Render command list generation
*
* In the V3D hardware, render command lists are what load and store
* tiles of a framebuffer and optionally call out to binner-generated
* command lists to do the 3D drawing for that tile.
*
* In the VC4 driver, render command list generation is performed by the
* kernel instead of userspace. We do this because validating a
* user-submitted command list is hard to get right and has high CPU overhead,
* while the number of valid configurations for render command lists is
* actually fairly low.
*/
#include <drm/drm_print.h>
#include "vc4_drv.h"
#include "vc4_packet.h"
struct vc4_rcl_setup {
struct drm_gem_dma_object *color_read;
struct drm_gem_dma_object *color_write;
struct drm_gem_dma_object *zs_read;
struct drm_gem_dma_object *zs_write;
struct drm_gem_dma_object *msaa_color_write;
struct drm_gem_dma_object *msaa_zs_write;
struct drm_gem_dma_object *rcl;
u32 next_offset;
u32 next_write_bo_index;
};
static inline void rcl_u8(struct vc4_rcl_setup *setup, u8 val)
{
*(u8 *)(setup->rcl->vaddr + setup->next_offset) = val;
setup->next_offset += 1;
}
static inline void rcl_u16(struct vc4_rcl_setup *setup, u16 val)
{
*(u16 *)(setup->rcl->vaddr + setup->next_offset) = val;
setup->next_offset += 2;
}
static inline void rcl_u32(struct vc4_rcl_setup *setup, u32 val)
{
*(u32 *)(setup->rcl->vaddr + setup->next_offset) = val;
setup->next_offset += 4;
}
/*
* Emits a no-op STORE_TILE_BUFFER_GENERAL.
*
* If we emit a PACKET_TILE_COORDINATES, it must be followed by a store of
* some sort before another load is triggered.
*/
static void vc4_store_before_load(struct vc4_rcl_setup *setup)
{
rcl_u8(setup, VC4_PACKET_STORE_TILE_BUFFER_GENERAL);
rcl_u16(setup,
VC4_SET_FIELD(VC4_LOADSTORE_TILE_BUFFER_NONE,
VC4_LOADSTORE_TILE_BUFFER_BUFFER) |
VC4_STORE_TILE_BUFFER_DISABLE_COLOR_CLEAR |
VC4_STORE_TILE_BUFFER_DISABLE_ZS_CLEAR |
VC4_STORE_TILE_BUFFER_DISABLE_VG_MASK_CLEAR);
rcl_u32(setup, 0); /* no address, since we're in None mode */
}
/*
* Calculates the physical address of the start of a tile in a RCL surface.
*
* Unlike the other load/store packets,
* VC4_PACKET_LOAD/STORE_FULL_RES_TILE_BUFFER don't look at the tile
* coordinates packet, and instead just store to the address given.
*/
static uint32_t vc4_full_res_offset(struct vc4_exec_info *exec,
struct drm_gem_dma_object *bo,
struct drm_vc4_submit_rcl_surface *surf,
uint8_t x, uint8_t y)
{
return bo->dma_addr + surf->offset + VC4_TILE_BUFFER_SIZE *
(DIV_ROUND_UP(exec->args->width, 32) * y + x);
}
/*
* Emits a PACKET_TILE_COORDINATES if one isn't already pending.
*
* The tile coordinates packet triggers a pending load if there is one, are
* used for clipping during rendering, and determine where loads/stores happen
* relative to their base address.
*/
static void vc4_tile_coordinates(struct vc4_rcl_setup *setup,
uint32_t x, uint32_t y)
{
rcl_u8(setup, VC4_PACKET_TILE_COORDINATES);
rcl_u8(setup, x);
rcl_u8(setup, y);
}
static void emit_tile(struct vc4_exec_info *exec,
struct vc4_rcl_setup *setup,
uint8_t x, uint8_t y, bool first, bool last)
{
struct drm_vc4_submit_cl *args = exec->args;
bool has_bin = args->bin_cl_size != 0;
/* Note that the load doesn't actually occur until the
* tile coords packet is processed, and only one load
* may be outstanding at a time.
*/
if (setup->color_read) {
if (args->color_read.flags &
VC4_SUBMIT_RCL_SURFACE_READ_IS_FULL_RES) {
rcl_u8(setup, VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER);
rcl_u32(setup,
vc4_full_res_offset(exec, setup->color_read,
&args->color_read, x, y) |
VC4_LOADSTORE_FULL_RES_DISABLE_ZS);
} else {
rcl_u8(setup, VC4_PACKET_LOAD_TILE_BUFFER_GENERAL);
rcl_u16(setup, args->color_read.bits);
rcl_u32(setup, setup->color_read->dma_addr +
args->color_read.offset);
}
}
if (setup->zs_read) {
if (setup->color_read) {
/* Exec previous load. */
vc4_tile_coordinates(setup, x, y);
vc4_store_before_load(setup);
}
if (args->zs_read.flags &
VC4_SUBMIT_RCL_SURFACE_READ_IS_FULL_RES) {
rcl_u8(setup, VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER);
rcl_u32(setup,
vc4_full_res_offset(exec, setup->zs_read,
&args->zs_read, x, y) |
VC4_LOADSTORE_FULL_RES_DISABLE_COLOR);
} else {
rcl_u8(setup, VC4_PACKET_LOAD_TILE_BUFFER_GENERAL);
rcl_u16(setup, args->zs_read.bits);
rcl_u32(setup, setup->zs_read->dma_addr +
args->zs_read.offset);
}
}
/* Clipping depends on tile coordinates having been
* emitted, so we always need one here.
*/
vc4_tile_coordinates(setup, x, y);
/* Wait for the binner before jumping to the first
* tile's lists.
*/
if (first && has_bin)
rcl_u8(setup, VC4_PACKET_WAIT_ON_SEMAPHORE);
if (has_bin) {
rcl_u8(setup, VC4_PACKET_BRANCH_TO_SUB_LIST);
rcl_u32(setup, (exec->tile_alloc_offset +
(y * exec->bin_tiles_x + x) * 32));
}
if (setup->msaa_color_write) {
bool last_tile_write = (!setup->msaa_zs_write &&
!setup->zs_write &&
!setup->color_write);
uint32_t bits = VC4_LOADSTORE_FULL_RES_DISABLE_ZS;
if (!last_tile_write)
bits |= VC4_LOADSTORE_FULL_RES_DISABLE_CLEAR_ALL;
else if (last)
bits |= VC4_LOADSTORE_FULL_RES_EOF;
rcl_u8(setup, VC4_PACKET_STORE_FULL_RES_TILE_BUFFER);
rcl_u32(setup,
vc4_full_res_offset(exec, setup->msaa_color_write,
&args->msaa_color_write, x, y) |
bits);
}
if (setup->msaa_zs_write) {
bool last_tile_write = (!setup->zs_write &&
!setup->color_write);
uint32_t bits = VC4_LOADSTORE_FULL_RES_DISABLE_COLOR;
if (setup->msaa_color_write)
vc4_tile_coordinates(setup, x, y);
if (!last_tile_write)
bits |= VC4_LOADSTORE_FULL_RES_DISABLE_CLEAR_ALL;
else if (last)
bits |= VC4_LOADSTORE_FULL_RES_EOF;
rcl_u8(setup, VC4_PACKET_STORE_FULL_RES_TILE_BUFFER);
rcl_u32(setup,
vc4_full_res_offset(exec, setup->msaa_zs_write,
&args->msaa_zs_write, x, y) |
bits);
}
if (setup->zs_write) {
bool last_tile_write = !setup->color_write;
if (setup->msaa_color_write || setup->msaa_zs_write)
vc4_tile_coordinates(setup, x, y);
rcl_u8(setup, VC4_PACKET_STORE_TILE_BUFFER_GENERAL);
rcl_u16(setup, args->zs_write.bits |
(last_tile_write ?
0 : VC4_STORE_TILE_BUFFER_DISABLE_COLOR_CLEAR));
rcl_u32(setup,
(setup->zs_write->dma_addr + args->zs_write.offset) |
((last && last_tile_write) ?
VC4_LOADSTORE_TILE_BUFFER_EOF : 0));
}
if (setup->color_write) {
if (setup->msaa_color_write || setup->msaa_zs_write ||
setup->zs_write) {
vc4_tile_coordinates(setup, x, y);
}
if (last)
rcl_u8(setup, VC4_PACKET_STORE_MS_TILE_BUFFER_AND_EOF);
else
rcl_u8(setup, VC4_PACKET_STORE_MS_TILE_BUFFER);
}
}
static int vc4_create_rcl_bo(struct drm_device *dev, struct vc4_exec_info *exec,
struct vc4_rcl_setup *setup)
{
struct drm_vc4_submit_cl *args = exec->args;
bool has_bin = args->bin_cl_size != 0;
uint8_t min_x_tile = args->min_x_tile;
uint8_t min_y_tile = args->min_y_tile;
uint8_t max_x_tile = args->max_x_tile;
uint8_t max_y_tile = args->max_y_tile;
uint8_t xtiles = max_x_tile - min_x_tile + 1;
uint8_t ytiles = max_y_tile - min_y_tile + 1;
uint8_t xi, yi;
uint32_t size, loop_body_size;
bool positive_x = true;
bool positive_y = true;
if (args->flags & VC4_SUBMIT_CL_FIXED_RCL_ORDER) {
if (!(args->flags & VC4_SUBMIT_CL_RCL_ORDER_INCREASING_X))
positive_x = false;
if (!(args->flags & VC4_SUBMIT_CL_RCL_ORDER_INCREASING_Y))
positive_y = false;
}
size = VC4_PACKET_TILE_RENDERING_MODE_CONFIG_SIZE;
loop_body_size = VC4_PACKET_TILE_COORDINATES_SIZE;
if (args->flags & VC4_SUBMIT_CL_USE_CLEAR_COLOR) {
size += VC4_PACKET_CLEAR_COLORS_SIZE +
VC4_PACKET_TILE_COORDINATES_SIZE +
VC4_PACKET_STORE_TILE_BUFFER_GENERAL_SIZE;
}
if (setup->color_read) {
if (args->color_read.flags &
VC4_SUBMIT_RCL_SURFACE_READ_IS_FULL_RES) {
loop_body_size += VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER_SIZE;
} else {
loop_body_size += VC4_PACKET_LOAD_TILE_BUFFER_GENERAL_SIZE;
}
}
if (setup->zs_read) {
if (setup->color_read) {
loop_body_size += VC4_PACKET_TILE_COORDINATES_SIZE;
loop_body_size += VC4_PACKET_STORE_TILE_BUFFER_GENERAL_SIZE;
}
if (args->zs_read.flags &
VC4_SUBMIT_RCL_SURFACE_READ_IS_FULL_RES) {
loop_body_size += VC4_PACKET_LOAD_FULL_RES_TILE_BUFFER_SIZE;
} else {
loop_body_size += VC4_PACKET_LOAD_TILE_BUFFER_GENERAL_SIZE;
}
}
if (has_bin) {
size += VC4_PACKET_WAIT_ON_SEMAPHORE_SIZE;
loop_body_size += VC4_PACKET_BRANCH_TO_SUB_LIST_SIZE;
}
if (setup->msaa_color_write)
loop_body_size += VC4_PACKET_STORE_FULL_RES_TILE_BUFFER_SIZE;
if (setup->msaa_zs_write)
loop_body_size += VC4_PACKET_STORE_FULL_RES_TILE_BUFFER_SIZE;
if (setup->zs_write)
loop_body_size += VC4_PACKET_STORE_TILE_BUFFER_GENERAL_SIZE;
if (setup->color_write)
loop_body_size += VC4_PACKET_STORE_MS_TILE_BUFFER_SIZE;
/* We need a VC4_PACKET_TILE_COORDINATES in between each store. */
loop_body_size += VC4_PACKET_TILE_COORDINATES_SIZE *
((setup->msaa_color_write != NULL) +
(setup->msaa_zs_write != NULL) +
(setup->color_write != NULL) +
(setup->zs_write != NULL) - 1);
size += xtiles * ytiles * loop_body_size;
setup->rcl = &vc4_bo_create(dev, size, true, VC4_BO_TYPE_RCL)->base;
if (IS_ERR(setup->rcl))
return PTR_ERR(setup->rcl);
list_add_tail(&to_vc4_bo(&setup->rcl->base)->unref_head,
&exec->unref_list);
/* The tile buffer gets cleared when the previous tile is stored. If
* the clear values changed between frames, then the tile buffer has
* stale clear values in it, so we have to do a store in None mode (no
* writes) so that we trigger the tile buffer clear.
*/
if (args->flags & VC4_SUBMIT_CL_USE_CLEAR_COLOR) {
rcl_u8(setup, VC4_PACKET_CLEAR_COLORS);
rcl_u32(setup, args->clear_color[0]);
rcl_u32(setup, args->clear_color[1]);
rcl_u32(setup, args->clear_z);
rcl_u8(setup, args->clear_s);
vc4_tile_coordinates(setup, 0, 0);
rcl_u8(setup, VC4_PACKET_STORE_TILE_BUFFER_GENERAL);
rcl_u16(setup, VC4_LOADSTORE_TILE_BUFFER_NONE);
rcl_u32(setup, 0); /* no address, since we're in None mode */
}
rcl_u8(setup, VC4_PACKET_TILE_RENDERING_MODE_CONFIG);
rcl_u32(setup,
(setup->color_write ? (setup->color_write->dma_addr +
args->color_write.offset) :
0));
rcl_u16(setup, args->width);
rcl_u16(setup, args->height);
rcl_u16(setup, args->color_write.bits);
for (yi = 0; yi < ytiles; yi++) {
int y = positive_y ? min_y_tile + yi : max_y_tile - yi;
for (xi = 0; xi < xtiles; xi++) {
int x = positive_x ? min_x_tile + xi : max_x_tile - xi;
bool first = (xi == 0 && yi == 0);
bool last = (xi == xtiles - 1 && yi == ytiles - 1);
emit_tile(exec, setup, x, y, first, last);
}
}
BUG_ON(setup->next_offset != size);
exec->ct1ca = setup->rcl->dma_addr;
exec->ct1ea = setup->rcl->dma_addr + setup->next_offset;
return 0;
}
static int vc4_full_res_bounds_check(struct vc4_exec_info *exec,
struct drm_gem_dma_object *obj,
struct drm_vc4_submit_rcl_surface *surf)
{
struct drm_vc4_submit_cl *args = exec->args;
u32 render_tiles_stride = DIV_ROUND_UP(exec->args->width, 32);
if (surf->offset > obj->base.size) {
DRM_DEBUG("surface offset %d > BO size %zd\n",
surf->offset, obj->base.size);
return -EINVAL;
}
if ((obj->base.size - surf->offset) / VC4_TILE_BUFFER_SIZE <
render_tiles_stride * args->max_y_tile + args->max_x_tile) {
DRM_DEBUG("MSAA tile %d, %d out of bounds "
"(bo size %zd, offset %d).\n",
args->max_x_tile, args->max_y_tile,
obj->base.size,
surf->offset);
return -EINVAL;
}
return 0;
}
static int vc4_rcl_msaa_surface_setup(struct vc4_exec_info *exec,
struct drm_gem_dma_object **obj,
struct drm_vc4_submit_rcl_surface *surf)
{
if (surf->flags != 0 || surf->bits != 0) {
DRM_DEBUG("MSAA surface had nonzero flags/bits\n");
return -EINVAL;
}
if (surf->hindex == ~0)
return 0;
*obj = vc4_use_bo(exec, surf->hindex);
if (!*obj)
return -EINVAL;
exec->rcl_write_bo[exec->rcl_write_bo_count++] = *obj;
if (surf->offset & 0xf) {
DRM_DEBUG("MSAA write must be 16b aligned.\n");
return -EINVAL;
}
return vc4_full_res_bounds_check(exec, *obj, surf);
}
static int vc4_rcl_surface_setup(struct vc4_exec_info *exec,
struct drm_gem_dma_object **obj,
struct drm_vc4_submit_rcl_surface *surf,
bool is_write)
{
uint8_t tiling = VC4_GET_FIELD(surf->bits,
VC4_LOADSTORE_TILE_BUFFER_TILING);
uint8_t buffer = VC4_GET_FIELD(surf->bits,
VC4_LOADSTORE_TILE_BUFFER_BUFFER);
uint8_t format = VC4_GET_FIELD(surf->bits,
VC4_LOADSTORE_TILE_BUFFER_FORMAT);
int cpp;
int ret;
if (surf->flags & ~VC4_SUBMIT_RCL_SURFACE_READ_IS_FULL_RES) {
DRM_DEBUG("Extra flags set\n");
return -EINVAL;
}
if (surf->hindex == ~0)
return 0;
*obj = vc4_use_bo(exec, surf->hindex);
if (!*obj)
return -EINVAL;
if (is_write)
exec->rcl_write_bo[exec->rcl_write_bo_count++] = *obj;
if (surf->flags & VC4_SUBMIT_RCL_SURFACE_READ_IS_FULL_RES) {
if (surf == &exec->args->zs_write) {
DRM_DEBUG("general zs write may not be a full-res.\n");
return -EINVAL;
}
if (surf->bits != 0) {
DRM_DEBUG("load/store general bits set with "
"full res load/store.\n");
return -EINVAL;
}
ret = vc4_full_res_bounds_check(exec, *obj, surf);
if (ret)
return ret;
return 0;
}
if (surf->bits & ~(VC4_LOADSTORE_TILE_BUFFER_TILING_MASK |
VC4_LOADSTORE_TILE_BUFFER_BUFFER_MASK |
VC4_LOADSTORE_TILE_BUFFER_FORMAT_MASK)) {
DRM_DEBUG("Unknown bits in load/store: 0x%04x\n",
surf->bits);
return -EINVAL;
}
if (tiling > VC4_TILING_FORMAT_LT) {
DRM_DEBUG("Bad tiling format\n");
return -EINVAL;
}
if (buffer == VC4_LOADSTORE_TILE_BUFFER_ZS) {
if (format != 0) {
DRM_DEBUG("No color format should be set for ZS\n");
return -EINVAL;
}
cpp = 4;
} else if (buffer == VC4_LOADSTORE_TILE_BUFFER_COLOR) {
switch (format) {
case VC4_LOADSTORE_TILE_BUFFER_BGR565:
case VC4_LOADSTORE_TILE_BUFFER_BGR565_DITHER:
cpp = 2;
break;
case VC4_LOADSTORE_TILE_BUFFER_RGBA8888:
cpp = 4;
break;
default:
DRM_DEBUG("Bad tile buffer format\n");
return -EINVAL;
}
} else {
DRM_DEBUG("Bad load/store buffer %d.\n", buffer);
return -EINVAL;
}
if (surf->offset & 0xf) {
DRM_DEBUG("load/store buffer must be 16b aligned.\n");
return -EINVAL;
}
if (!vc4_check_tex_size(exec, *obj, surf->offset, tiling,
exec->args->width, exec->args->height, cpp)) {
return -EINVAL;
}
return 0;
}
static int
vc4_rcl_render_config_surface_setup(struct vc4_exec_info *exec,
struct vc4_rcl_setup *setup,
struct drm_gem_dma_object **obj,
struct drm_vc4_submit_rcl_surface *surf)
{
uint8_t tiling = VC4_GET_FIELD(surf->bits,
VC4_RENDER_CONFIG_MEMORY_FORMAT);
uint8_t format = VC4_GET_FIELD(surf->bits,
VC4_RENDER_CONFIG_FORMAT);
int cpp;
if (surf->flags != 0) {
DRM_DEBUG("No flags supported on render config.\n");
return -EINVAL;
}
if (surf->bits & ~(VC4_RENDER_CONFIG_MEMORY_FORMAT_MASK |
VC4_RENDER_CONFIG_FORMAT_MASK |
VC4_RENDER_CONFIG_MS_MODE_4X |
VC4_RENDER_CONFIG_DECIMATE_MODE_4X)) {
DRM_DEBUG("Unknown bits in render config: 0x%04x\n",
surf->bits);
return -EINVAL;
}
if (surf->hindex == ~0)
return 0;
*obj = vc4_use_bo(exec, surf->hindex);
if (!*obj)
return -EINVAL;
exec->rcl_write_bo[exec->rcl_write_bo_count++] = *obj;
if (tiling > VC4_TILING_FORMAT_LT) {
DRM_DEBUG("Bad tiling format\n");
return -EINVAL;
}
switch (format) {
case VC4_RENDER_CONFIG_FORMAT_BGR565_DITHERED:
case VC4_RENDER_CONFIG_FORMAT_BGR565:
cpp = 2;
break;
case VC4_RENDER_CONFIG_FORMAT_RGBA8888:
cpp = 4;
break;
default:
DRM_DEBUG("Bad tile buffer format\n");
return -EINVAL;
}
if (!vc4_check_tex_size(exec, *obj, surf->offset, tiling,
exec->args->width, exec->args->height, cpp)) {
return -EINVAL;
}
return 0;
}
int vc4_get_rcl(struct drm_device *dev, struct vc4_exec_info *exec)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_rcl_setup setup = {0};
struct drm_vc4_submit_cl *args = exec->args;
bool has_bin = args->bin_cl_size != 0;
int ret;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
if (args->min_x_tile > args->max_x_tile ||
args->min_y_tile > args->max_y_tile) {
DRM_DEBUG("Bad render tile set (%d,%d)-(%d,%d)\n",
args->min_x_tile, args->min_y_tile,
args->max_x_tile, args->max_y_tile);
return -EINVAL;
}
if (has_bin &&
(args->max_x_tile > exec->bin_tiles_x ||
args->max_y_tile > exec->bin_tiles_y)) {
DRM_DEBUG("Render tiles (%d,%d) outside of bin config "
"(%d,%d)\n",
args->max_x_tile, args->max_y_tile,
exec->bin_tiles_x, exec->bin_tiles_y);
return -EINVAL;
}
ret = vc4_rcl_render_config_surface_setup(exec, &setup,
&setup.color_write,
&args->color_write);
if (ret)
return ret;
ret = vc4_rcl_surface_setup(exec, &setup.color_read, &args->color_read,
false);
if (ret)
return ret;
ret = vc4_rcl_surface_setup(exec, &setup.zs_read, &args->zs_read,
false);
if (ret)
return ret;
ret = vc4_rcl_surface_setup(exec, &setup.zs_write, &args->zs_write,
true);
if (ret)
return ret;
ret = vc4_rcl_msaa_surface_setup(exec, &setup.msaa_color_write,
&args->msaa_color_write);
if (ret)
return ret;
ret = vc4_rcl_msaa_surface_setup(exec, &setup.msaa_zs_write,
&args->msaa_zs_write);
if (ret)
return ret;
/* We shouldn't even have the job submitted to us if there's no
* surface to write out.
*/
if (!setup.color_write && !setup.zs_write &&
!setup.msaa_color_write && !setup.msaa_zs_write) {
DRM_DEBUG("RCL requires color or Z/S write\n");
return -EINVAL;
}
return vc4_create_rcl_bo(dev, exec, &setup);
}
+155
View File
@@ -0,0 +1,155 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2015 Broadcom
*/
#if !defined(_VC4_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _VC4_TRACE_H_
#include <linux/stringify.h>
#include <linux/types.h>
#include <linux/tracepoint.h>
#undef TRACE_SYSTEM
#define TRACE_SYSTEM vc4
#define TRACE_INCLUDE_FILE vc4_trace
TRACE_EVENT(vc4_wait_for_seqno_begin,
TP_PROTO(struct drm_device *dev, uint64_t seqno, uint64_t timeout),
TP_ARGS(dev, seqno, timeout),
TP_STRUCT__entry(
__field(u32, dev)
__field(u64, seqno)
__field(u64, timeout)
),
TP_fast_assign(
__entry->dev = dev->primary->index;
__entry->seqno = seqno;
__entry->timeout = timeout;
),
TP_printk("dev=%u, seqno=%llu, timeout=%llu",
__entry->dev, __entry->seqno, __entry->timeout)
);
TRACE_EVENT(vc4_wait_for_seqno_end,
TP_PROTO(struct drm_device *dev, uint64_t seqno),
TP_ARGS(dev, seqno),
TP_STRUCT__entry(
__field(u32, dev)
__field(u64, seqno)
),
TP_fast_assign(
__entry->dev = dev->primary->index;
__entry->seqno = seqno;
),
TP_printk("dev=%u, seqno=%llu",
__entry->dev, __entry->seqno)
);
TRACE_EVENT(vc4_submit_cl_ioctl,
TP_PROTO(struct drm_device *dev, u32 bin_cl_size, u32 shader_rec_size, u32 bo_count),
TP_ARGS(dev, bin_cl_size, shader_rec_size, bo_count),
TP_STRUCT__entry(
__field(u32, dev)
__field(u32, bin_cl_size)
__field(u32, shader_rec_size)
__field(u32, bo_count)
),
TP_fast_assign(
__entry->dev = dev->primary->index;
__entry->bin_cl_size = bin_cl_size;
__entry->shader_rec_size = shader_rec_size;
__entry->bo_count = bo_count;
),
TP_printk("dev=%u, bin_cl_size=%u, shader_rec_size=%u, bo_count=%u",
__entry->dev,
__entry->bin_cl_size,
__entry->shader_rec_size,
__entry->bo_count)
);
TRACE_EVENT(vc4_submit_cl,
TP_PROTO(struct drm_device *dev, bool is_render,
uint64_t seqno,
u32 ctnqba, u32 ctnqea),
TP_ARGS(dev, is_render, seqno, ctnqba, ctnqea),
TP_STRUCT__entry(
__field(u32, dev)
__field(bool, is_render)
__field(u64, seqno)
__field(u32, ctnqba)
__field(u32, ctnqea)
),
TP_fast_assign(
__entry->dev = dev->primary->index;
__entry->is_render = is_render;
__entry->seqno = seqno;
__entry->ctnqba = ctnqba;
__entry->ctnqea = ctnqea;
),
TP_printk("dev=%u, %s, seqno=%llu, 0x%08x..0x%08x",
__entry->dev,
__entry->is_render ? "RCL" : "BCL",
__entry->seqno,
__entry->ctnqba,
__entry->ctnqea)
);
TRACE_EVENT(vc4_bcl_end_irq,
TP_PROTO(struct drm_device *dev,
uint64_t seqno),
TP_ARGS(dev, seqno),
TP_STRUCT__entry(
__field(u32, dev)
__field(u64, seqno)
),
TP_fast_assign(
__entry->dev = dev->primary->index;
__entry->seqno = seqno;
),
TP_printk("dev=%u, seqno=%llu",
__entry->dev,
__entry->seqno)
);
TRACE_EVENT(vc4_rcl_end_irq,
TP_PROTO(struct drm_device *dev,
uint64_t seqno),
TP_ARGS(dev, seqno),
TP_STRUCT__entry(
__field(u32, dev)
__field(u64, seqno)
),
TP_fast_assign(
__entry->dev = dev->primary->index;
__entry->seqno = seqno;
),
TP_printk("dev=%u, seqno=%llu",
__entry->dev,
__entry->seqno)
);
#endif /* _VC4_TRACE_H_ */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/vc4
#include <trace/define_trace.h>
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2015 Broadcom
*/
#include "vc4_drv.h"
#ifndef __CHECKER__
#define CREATE_TRACE_POINTS
#include "vc4_trace.h"
#endif
+658
View File
@@ -0,0 +1,658 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2018 Broadcom
*
* Authors:
* Eric Anholt <eric@anholt.net>
* Boris Brezillon <boris.brezillon@bootlin.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_writeback.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
/* Base address of the output. Raster formats must be 4-byte aligned,
* T and LT must be 16-byte aligned or maybe utile-aligned (docs are
* inconsistent, but probably utile).
*/
#define TXP_DST_PTR 0x00
/* Pitch in bytes for raster images, 16-byte aligned. For tiled, it's
* the width in tiles.
*/
#define TXP_DST_PITCH 0x04
/* For T-tiled imgaes, DST_PITCH should be the number of tiles wide,
* shifted up.
*/
# define TXP_T_TILE_WIDTH_SHIFT 7
/* For LT-tiled images, DST_PITCH should be the number of utiles wide,
* shifted up.
*/
# define TXP_LT_TILE_WIDTH_SHIFT 4
/* Pre-rotation width/height of the image. Must match HVS config.
*
* If TFORMAT and 32-bit, limit is 1920 for 32-bit and 3840 to 16-bit
* and width/height must be tile or utile-aligned as appropriate. If
* transposing (rotating), width is limited to 1920.
*
* Height is limited to various numbers between 4088 and 4095. I'd
* just use 4088 to be safe.
*/
#define TXP_DIM 0x08
# define TXP_HEIGHT_SHIFT 16
# define TXP_HEIGHT_MASK GENMASK(31, 16)
# define TXP_WIDTH_SHIFT 0
# define TXP_WIDTH_MASK GENMASK(15, 0)
#define TXP_DST_CTRL 0x0c
/* These bits are set to 0x54 */
#define TXP_PILOT_SHIFT 24
#define TXP_PILOT_MASK GENMASK(31, 24)
/* Bits 22-23 are set to 0x01 */
#define TXP_VERSION_SHIFT 22
#define TXP_VERSION_MASK GENMASK(23, 22)
/* Powers down the internal memory. */
# define TXP_POWERDOWN BIT(21)
/* Enables storing the alpha component in 8888/4444, instead of
* filling with ~ALPHA_INVERT.
*/
# define TXP_ALPHA_ENABLE BIT(20)
/* 4 bits, each enables stores for a channel in each set of 4 bytes.
* Set to 0xf for normal operation.
*/
# define TXP_BYTE_ENABLE_SHIFT 16
# define TXP_BYTE_ENABLE_MASK GENMASK(19, 16)
/* Debug: Generate VSTART again at EOF. */
# define TXP_VSTART_AT_EOF BIT(15)
/* Debug: Terminate the current frame immediately. Stops AXI
* writes.
*/
# define TXP_ABORT BIT(14)
# define TXP_DITHER BIT(13)
/* Inverts alpha if TXP_ALPHA_ENABLE, chooses fill value for
* !TXP_ALPHA_ENABLE.
*/
# define TXP_ALPHA_INVERT BIT(12)
/* Note: I've listed the channels here in high bit (in byte 3/2/1) to
* low bit (in byte 0) order.
*/
# define TXP_FORMAT_SHIFT 8
# define TXP_FORMAT_MASK GENMASK(11, 8)
# define TXP_FORMAT_ABGR4444 0
# define TXP_FORMAT_ARGB4444 1
# define TXP_FORMAT_BGRA4444 2
# define TXP_FORMAT_RGBA4444 3
# define TXP_FORMAT_BGR565 6
# define TXP_FORMAT_RGB565 7
/* 888s are non-rotated, raster-only */
# define TXP_FORMAT_BGR888 8
# define TXP_FORMAT_RGB888 9
# define TXP_FORMAT_ABGR8888 12
# define TXP_FORMAT_ARGB8888 13
# define TXP_FORMAT_BGRA8888 14
# define TXP_FORMAT_RGBA8888 15
/* If TFORMAT is set, generates LT instead of T format. */
# define TXP_LINEAR_UTILE BIT(7)
/* Rotate output by 90 degrees. */
# define TXP_TRANSPOSE BIT(6)
/* Generate a tiled format for V3D. */
# define TXP_TFORMAT BIT(5)
/* Generates some undefined test mode output. */
# define TXP_TEST_MODE BIT(4)
/* Request odd field from HVS. */
# define TXP_FIELD BIT(3)
/* Raise interrupt when idle. */
# define TXP_EI BIT(2)
/* Set when generating a frame, clears when idle. */
# define TXP_BUSY BIT(1)
/* Starts a frame. Self-clearing. */
# define TXP_GO BIT(0)
/* Number of lines received and committed to memory. */
#define TXP_PROGRESS 0x10
#define TXP_DST_PTR_HIGH_MOPLET 0x1c
#define TXP_DST_PTR_HIGH_MOP 0x24
#define TXP_READ(offset) \
({ \
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
readl(txp->regs + (offset)); \
})
#define TXP_WRITE(offset, val) \
do { \
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
writel(val, txp->regs + (offset)); \
} while (0)
struct vc4_txp {
struct vc4_crtc base;
const struct vc4_txp_data *data;
struct platform_device *pdev;
struct vc4_encoder encoder;
struct drm_writeback_connector connector;
void __iomem *regs;
};
#define encoder_to_vc4_txp(_encoder) \
container_of_const(_encoder, struct vc4_txp, encoder.base)
#define connector_to_vc4_txp(_connector) \
container_of_const(_connector, struct vc4_txp, connector.base)
static const struct debugfs_reg32 txp_regs[] = {
VC4_REG32(TXP_DST_PTR),
VC4_REG32(TXP_DST_PITCH),
VC4_REG32(TXP_DIM),
VC4_REG32(TXP_DST_CTRL),
VC4_REG32(TXP_PROGRESS),
};
static int vc4_txp_connector_get_modes(struct drm_connector *connector)
{
struct drm_device *dev = connector->dev;
return drm_add_modes_noedid(connector, dev->mode_config.max_width,
dev->mode_config.max_height);
}
static enum drm_mode_status
vc4_txp_connector_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode)
{
struct drm_device *dev = connector->dev;
struct drm_mode_config *mode_config = &dev->mode_config;
int w = mode->hdisplay, h = mode->vdisplay;
if (w < mode_config->min_width || w > mode_config->max_width)
return MODE_BAD_HVALUE;
if (h < mode_config->min_height || h > mode_config->max_height)
return MODE_BAD_VVALUE;
return MODE_OK;
}
static const u32 drm_fmts[] = {
DRM_FORMAT_RGB888,
DRM_FORMAT_BGR888,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_XBGR8888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_ABGR8888,
DRM_FORMAT_RGBX8888,
DRM_FORMAT_BGRX8888,
DRM_FORMAT_RGBA8888,
DRM_FORMAT_BGRA8888,
};
static const u32 txp_fmts[] = {
TXP_FORMAT_RGB888,
TXP_FORMAT_BGR888,
TXP_FORMAT_ARGB8888,
TXP_FORMAT_ABGR8888,
TXP_FORMAT_ARGB8888,
TXP_FORMAT_ABGR8888,
TXP_FORMAT_RGBA8888,
TXP_FORMAT_BGRA8888,
TXP_FORMAT_RGBA8888,
TXP_FORMAT_BGRA8888,
};
static void vc4_txp_armed(struct drm_crtc_state *state)
{
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
vc4_state->txp_armed = true;
}
static int vc4_txp_connector_atomic_check(struct drm_connector *conn,
struct drm_atomic_commit *state)
{
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
struct drm_framebuffer *fb;
int i;
conn_state = drm_atomic_get_new_connector_state(state, conn);
if (!conn_state->writeback_job)
return 0;
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
fb = conn_state->writeback_job->fb;
if (fb->width != crtc_state->mode.hdisplay ||
fb->height != crtc_state->mode.vdisplay) {
DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n",
fb->width, fb->height);
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(drm_fmts); i++) {
if (fb->format->format == drm_fmts[i])
break;
}
if (i == ARRAY_SIZE(drm_fmts))
return -EINVAL;
/* Pitch must be aligned on 16 bytes. */
if (fb->pitches[0] & GENMASK(3, 0))
return -EINVAL;
vc4_txp_armed(crtc_state);
return 0;
}
static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
struct drm_atomic_commit *state)
{
struct drm_device *drm = conn->dev;
struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state,
conn);
struct vc4_txp *txp = connector_to_vc4_txp(conn);
const struct vc4_txp_data *txp_data = txp->data;
struct drm_gem_dma_object *gem;
struct drm_display_mode *mode;
struct drm_framebuffer *fb;
unsigned int hdisplay;
unsigned int vdisplay;
dma_addr_t addr;
u32 ctrl;
int idx;
int i;
if (WARN_ON(!conn_state->writeback_job))
return;
mode = &conn_state->crtc->state->adjusted_mode;
fb = conn_state->writeback_job->fb;
for (i = 0; i < ARRAY_SIZE(drm_fmts); i++) {
if (fb->format->format == drm_fmts[i])
break;
}
if (WARN_ON(i == ARRAY_SIZE(drm_fmts)))
return;
ctrl = TXP_GO | TXP_EI |
VC4_SET_FIELD(txp_fmts[i], TXP_FORMAT);
if (txp_data->has_byte_enable)
ctrl |= VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
if (fb->format->has_alpha)
ctrl |= TXP_ALPHA_ENABLE;
else
/*
* If TXP_ALPHA_ENABLE isn't set and TXP_ALPHA_INVERT is, the
* hardware will force the output padding to be 0xff.
*/
ctrl |= TXP_ALPHA_INVERT;
if (!drm_dev_enter(drm, &idx))
return;
gem = drm_fb_dma_get_gem_obj(fb, 0);
addr = gem->dma_addr + fb->offsets[0];
TXP_WRITE(TXP_DST_PTR, lower_32_bits(addr));
if (txp_data->supports_40bit_addresses)
TXP_WRITE(txp_data->high_addr_ptr_reg, upper_32_bits(addr) & 0xff);
TXP_WRITE(TXP_DST_PITCH, fb->pitches[0]);
hdisplay = mode->hdisplay ?: 1;
vdisplay = mode->vdisplay ?: 1;
if (txp_data->size_minus_one) {
hdisplay -= 1;
vdisplay -= 1;
}
TXP_WRITE(TXP_DIM,
VC4_SET_FIELD(hdisplay, TXP_WIDTH) |
VC4_SET_FIELD(vdisplay, TXP_HEIGHT));
TXP_WRITE(TXP_DST_CTRL, ctrl);
drm_writeback_queue_job(&txp->connector, conn_state);
drm_dev_exit(idx);
}
static const struct drm_connector_helper_funcs vc4_txp_connector_helper_funcs = {
.get_modes = vc4_txp_connector_get_modes,
.mode_valid = vc4_txp_connector_mode_valid,
.atomic_check = vc4_txp_connector_atomic_check,
.atomic_commit = vc4_txp_connector_atomic_commit,
};
static enum drm_connector_status
vc4_txp_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static const struct drm_connector_funcs vc4_txp_connector_funcs = {
.detect = vc4_txp_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static void vc4_txp_encoder_disable(struct drm_encoder *encoder)
{
struct drm_device *drm = encoder->dev;
struct vc4_dev *vc4 = to_vc4_dev(drm);
struct vc4_txp *txp = encoder_to_vc4_txp(encoder);
int idx;
if (!drm_dev_enter(drm, &idx))
return;
if (TXP_READ(TXP_DST_CTRL) & TXP_BUSY) {
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
TXP_WRITE(TXP_DST_CTRL, TXP_ABORT);
while (TXP_READ(TXP_DST_CTRL) & TXP_BUSY &&
time_before(jiffies, timeout))
;
WARN_ON(TXP_READ(TXP_DST_CTRL) & TXP_BUSY);
}
if (vc4->gen < VC4_GEN_6_C)
TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN);
drm_dev_exit(idx);
}
static const struct drm_encoder_helper_funcs vc4_txp_encoder_helper_funcs = {
.disable = vc4_txp_encoder_disable,
};
static int vc4_txp_enable_vblank(struct drm_crtc *crtc)
{
return 0;
}
static void vc4_txp_disable_vblank(struct drm_crtc *crtc) {}
static const struct drm_crtc_funcs vc4_txp_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = vc4_page_flip,
.reset = vc4_crtc_reset,
.atomic_duplicate_state = vc4_crtc_duplicate_state,
.atomic_destroy_state = vc4_crtc_destroy_state,
.enable_vblank = vc4_txp_enable_vblank,
.disable_vblank = vc4_txp_disable_vblank,
.late_register = vc4_crtc_late_register,
};
static int vc4_txp_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
crtc);
int ret;
ret = vc4_hvs_atomic_check(crtc, state);
if (ret)
return ret;
crtc_state->no_vblank = true;
return 0;
}
static void vc4_txp_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
drm_crtc_vblank_on(crtc);
vc4_hvs_atomic_enable(crtc, state);
}
static void vc4_txp_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_device *dev = crtc->dev;
/* Disable vblank irq handling before crtc is disabled. */
drm_crtc_vblank_off(crtc);
vc4_hvs_atomic_disable(crtc, state);
/*
* Make sure we issue a vblank event after disabling the CRTC if
* someone was waiting it.
*/
if (crtc->state->event) {
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
crtc->state->event = NULL;
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static const struct drm_crtc_helper_funcs vc4_txp_crtc_helper_funcs = {
.atomic_check = vc4_txp_atomic_check,
.atomic_begin = vc4_hvs_atomic_begin,
.atomic_flush = vc4_hvs_atomic_flush,
.atomic_enable = vc4_txp_atomic_enable,
.atomic_disable = vc4_txp_atomic_disable,
};
static irqreturn_t vc4_txp_interrupt(int irq, void *data)
{
struct vc4_txp *txp = data;
struct vc4_crtc *vc4_crtc = &txp->base;
/*
* We don't need to protect the register access using
* drm_dev_enter() there because the interrupt handler lifetime
* is tied to the device itself, and not to the DRM device.
*
* So when the device will be gone, one of the first thing we
* will be doing will be to unregister the interrupt handler,
* and then unregister the DRM device. drm_dev_enter() would
* thus always succeed if we are here.
*/
TXP_WRITE(TXP_DST_CTRL, TXP_READ(TXP_DST_CTRL) & ~TXP_EI);
vc4_crtc_handle_vblank(vc4_crtc);
drm_writeback_signal_completion(&txp->connector, 0);
return IRQ_HANDLED;
}
static const struct vc4_txp_data bcm2712_mop_data = {
.base = {
.name = "mop",
.debugfs_name = "mop_regs",
.hvs_available_channels = BIT(2),
.hvs_output = 2,
},
.encoder_type = VC4_ENCODER_TYPE_TXP0,
.high_addr_ptr_reg = TXP_DST_PTR_HIGH_MOP,
.has_byte_enable = true,
.size_minus_one = true,
.supports_40bit_addresses = true,
};
static const struct vc4_txp_data bcm2712_moplet_data = {
.base = {
.name = "moplet",
.debugfs_name = "moplet_regs",
.hvs_available_channels = BIT(1),
.hvs_output = 4,
},
.encoder_type = VC4_ENCODER_TYPE_TXP1,
.high_addr_ptr_reg = TXP_DST_PTR_HIGH_MOPLET,
.size_minus_one = true,
.supports_40bit_addresses = true,
};
const struct vc4_txp_data bcm2835_txp_data = {
.base = {
.name = "txp",
.debugfs_name = "txp_regs",
.hvs_available_channels = BIT(2),
.hvs_output = 2,
},
.encoder_type = VC4_ENCODER_TYPE_TXP0,
.has_byte_enable = true,
};
static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = dev_get_drvdata(master);
const struct vc4_txp_data *txp_data;
struct vc4_encoder *vc4_encoder;
struct drm_encoder *encoder;
struct vc4_crtc *vc4_crtc;
struct vc4_txp *txp;
int ret, irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
txp = drmm_kzalloc(drm, sizeof(*txp), GFP_KERNEL);
if (!txp)
return -ENOMEM;
txp_data = of_device_get_match_data(dev);
if (!txp_data)
return -ENODEV;
txp->data = txp_data;
txp->pdev = pdev;
txp->regs = vc4_ioremap_regs(pdev, 0);
if (IS_ERR(txp->regs))
return PTR_ERR(txp->regs);
vc4_crtc = &txp->base;
vc4_crtc->regset.base = txp->regs;
vc4_crtc->regset.regs = txp_regs;
vc4_crtc->regset.nregs = ARRAY_SIZE(txp_regs);
ret = vc4_crtc_init(drm, pdev, vc4_crtc, &txp_data->base,
&vc4_txp_crtc_funcs, &vc4_txp_crtc_helper_funcs, true);
if (ret)
return ret;
vc4_encoder = &txp->encoder;
txp->encoder.type = txp_data->encoder_type;
encoder = &vc4_encoder->base;
encoder->possible_crtcs = drm_crtc_mask(&vc4_crtc->base);
drm_encoder_helper_add(encoder, &vc4_txp_encoder_helper_funcs);
ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_VIRTUAL, NULL);
if (ret)
return ret;
drm_connector_helper_add(&txp->connector.base,
&vc4_txp_connector_helper_funcs);
ret = drm_writeback_connector_init_with_encoder(drm, &txp->connector,
encoder,
&vc4_txp_connector_funcs,
drm_fmts, ARRAY_SIZE(drm_fmts));
if (ret)
return ret;
ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0,
dev_name(dev), txp);
if (ret)
return ret;
dev_set_drvdata(dev, txp);
return 0;
}
static void vc4_txp_unbind(struct device *dev, struct device *master,
void *data)
{
struct vc4_txp *txp = dev_get_drvdata(dev);
drm_connector_cleanup(&txp->connector.base);
}
static const struct component_ops vc4_txp_ops = {
.bind = vc4_txp_bind,
.unbind = vc4_txp_unbind,
};
static int vc4_txp_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &vc4_txp_ops);
}
static void vc4_txp_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &vc4_txp_ops);
}
static const struct of_device_id vc4_txp_dt_match[] = {
{ .compatible = "brcm,bcm2712-mop", .data = &bcm2712_mop_data },
{ .compatible = "brcm,bcm2712-moplet", .data = &bcm2712_moplet_data },
{ .compatible = "brcm,bcm2835-txp", .data = &bcm2835_txp_data },
{ /* sentinel */ },
};
struct platform_driver vc4_txp_driver = {
.probe = vc4_txp_probe,
.remove = vc4_txp_remove,
.driver = {
.name = "vc4_txp",
.of_match_table = vc4_txp_dt_match,
},
};
+546
View File
@@ -0,0 +1,546 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_print.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
static const struct debugfs_reg32 v3d_regs[] = {
VC4_REG32(V3D_IDENT0),
VC4_REG32(V3D_IDENT1),
VC4_REG32(V3D_IDENT2),
VC4_REG32(V3D_SCRATCH),
VC4_REG32(V3D_L2CACTL),
VC4_REG32(V3D_SLCACTL),
VC4_REG32(V3D_INTCTL),
VC4_REG32(V3D_INTENA),
VC4_REG32(V3D_INTDIS),
VC4_REG32(V3D_CT0CS),
VC4_REG32(V3D_CT1CS),
VC4_REG32(V3D_CT0EA),
VC4_REG32(V3D_CT1EA),
VC4_REG32(V3D_CT0CA),
VC4_REG32(V3D_CT1CA),
VC4_REG32(V3D_CT00RA0),
VC4_REG32(V3D_CT01RA0),
VC4_REG32(V3D_CT0LC),
VC4_REG32(V3D_CT1LC),
VC4_REG32(V3D_CT0PC),
VC4_REG32(V3D_CT1PC),
VC4_REG32(V3D_PCS),
VC4_REG32(V3D_BFC),
VC4_REG32(V3D_RFC),
VC4_REG32(V3D_BPCA),
VC4_REG32(V3D_BPCS),
VC4_REG32(V3D_BPOA),
VC4_REG32(V3D_BPOS),
VC4_REG32(V3D_BXCF),
VC4_REG32(V3D_SQRSV0),
VC4_REG32(V3D_SQRSV1),
VC4_REG32(V3D_SQCNTL),
VC4_REG32(V3D_SRQPC),
VC4_REG32(V3D_SRQUA),
VC4_REG32(V3D_SRQUL),
VC4_REG32(V3D_SRQCS),
VC4_REG32(V3D_VPACNTL),
VC4_REG32(V3D_VPMBASE),
VC4_REG32(V3D_PCTRC),
VC4_REG32(V3D_PCTRE),
VC4_REG32(V3D_PCTR(0)),
VC4_REG32(V3D_PCTRS(0)),
VC4_REG32(V3D_PCTR(1)),
VC4_REG32(V3D_PCTRS(1)),
VC4_REG32(V3D_PCTR(2)),
VC4_REG32(V3D_PCTRS(2)),
VC4_REG32(V3D_PCTR(3)),
VC4_REG32(V3D_PCTRS(3)),
VC4_REG32(V3D_PCTR(4)),
VC4_REG32(V3D_PCTRS(4)),
VC4_REG32(V3D_PCTR(5)),
VC4_REG32(V3D_PCTRS(5)),
VC4_REG32(V3D_PCTR(6)),
VC4_REG32(V3D_PCTRS(6)),
VC4_REG32(V3D_PCTR(7)),
VC4_REG32(V3D_PCTRS(7)),
VC4_REG32(V3D_PCTR(8)),
VC4_REG32(V3D_PCTRS(8)),
VC4_REG32(V3D_PCTR(9)),
VC4_REG32(V3D_PCTRS(9)),
VC4_REG32(V3D_PCTR(10)),
VC4_REG32(V3D_PCTRS(10)),
VC4_REG32(V3D_PCTR(11)),
VC4_REG32(V3D_PCTRS(11)),
VC4_REG32(V3D_PCTR(12)),
VC4_REG32(V3D_PCTRS(12)),
VC4_REG32(V3D_PCTR(13)),
VC4_REG32(V3D_PCTRS(13)),
VC4_REG32(V3D_PCTR(14)),
VC4_REG32(V3D_PCTRS(14)),
VC4_REG32(V3D_PCTR(15)),
VC4_REG32(V3D_PCTRS(15)),
VC4_REG32(V3D_DBGE),
VC4_REG32(V3D_FDBGO),
VC4_REG32(V3D_FDBGB),
VC4_REG32(V3D_FDBGR),
VC4_REG32(V3D_FDBGS),
VC4_REG32(V3D_ERRSTAT),
};
static int vc4_v3d_debugfs_ident(struct seq_file *m, void *unused)
{
struct drm_debugfs_entry *entry = m->private;
struct drm_device *dev = entry->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
int ret = vc4_v3d_pm_get(vc4);
if (ret == 0) {
uint32_t ident1 = V3D_READ(V3D_IDENT1);
uint32_t nslc = VC4_GET_FIELD(ident1, V3D_IDENT1_NSLC);
uint32_t tups = VC4_GET_FIELD(ident1, V3D_IDENT1_TUPS);
uint32_t qups = VC4_GET_FIELD(ident1, V3D_IDENT1_QUPS);
seq_printf(m, "Revision: %d\n",
VC4_GET_FIELD(ident1, V3D_IDENT1_REV));
seq_printf(m, "Slices: %d\n", nslc);
seq_printf(m, "TMUs: %d\n", nslc * tups);
seq_printf(m, "QPUs: %d\n", nslc * qups);
seq_printf(m, "Semaphores: %d\n",
VC4_GET_FIELD(ident1, V3D_IDENT1_NSEM));
vc4_v3d_pm_put(vc4);
}
return 0;
}
/*
* Wraps pm_runtime_get_sync() in a refcount, so that we can reliably
* get the pm_runtime refcount to 0 in vc4_reset().
*/
int
vc4_v3d_pm_get(struct vc4_dev *vc4)
{
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
mutex_lock(&vc4->power_lock);
if (vc4->power_refcount++ == 0) {
int ret = pm_runtime_get_sync(&vc4->v3d->pdev->dev);
if (ret < 0) {
vc4->power_refcount--;
mutex_unlock(&vc4->power_lock);
return ret;
}
}
mutex_unlock(&vc4->power_lock);
return 0;
}
void
vc4_v3d_pm_put(struct vc4_dev *vc4)
{
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
mutex_lock(&vc4->power_lock);
if (--vc4->power_refcount == 0) {
pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev);
pm_runtime_put_autosuspend(&vc4->v3d->pdev->dev);
}
mutex_unlock(&vc4->power_lock);
}
static void vc4_v3d_init_hw(struct drm_device *dev)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
/* Take all the memory that would have been reserved for user
* QPU programs, since we don't have an interface for running
* them, anyway.
*/
V3D_WRITE(V3D_VPMBASE, 0);
}
int vc4_v3d_get_bin_slot(struct vc4_dev *vc4)
{
struct drm_device *dev = &vc4->base;
unsigned long irqflags;
int slot;
uint64_t seqno = 0;
struct vc4_exec_info *exec;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
try_again:
spin_lock_irqsave(&vc4->job_lock, irqflags);
slot = ffs(~vc4->bin_alloc_used);
if (slot != 0) {
/* Switch from ffs() bit index to a 0-based index. */
slot--;
vc4->bin_alloc_used |= BIT(slot);
spin_unlock_irqrestore(&vc4->job_lock, irqflags);
return slot;
}
/* Couldn't find an open slot. Wait for render to complete
* and try again.
*/
exec = vc4_last_render_job(vc4);
if (exec)
seqno = exec->seqno;
spin_unlock_irqrestore(&vc4->job_lock, irqflags);
if (seqno) {
int ret = vc4_wait_for_seqno(dev, seqno, ~0ull, true);
if (ret == 0)
goto try_again;
return ret;
}
return -ENOMEM;
}
/*
* bin_bo_alloc() - allocates the memory that will be used for
* tile binning.
*
* The binner has a limitation that the addresses in the tile state
* buffer that point into the tile alloc buffer or binner overflow
* memory only have 28 bits (256MB), and the top 4 on the bus for
* tile alloc references end up coming from the tile state buffer's
* address.
*
* To work around this, we allocate a single large buffer while V3D is
* in use, make sure that it has the top 4 bits constant across its
* entire extent, and then put the tile state, tile alloc, and binner
* overflow memory inside that buffer.
*
* This creates a limitation where we may not be able to execute a job
* if it doesn't fit within the buffer that we allocated up front.
* However, it turns out that 16MB is "enough for anybody", and
* real-world applications run into allocation failures from the
* overall DMA pool before they make scenes complicated enough to run
* out of bin space.
*/
static int bin_bo_alloc(struct vc4_dev *vc4)
{
struct vc4_v3d *v3d = vc4->v3d;
uint32_t size = 16 * 1024 * 1024;
int ret = 0;
struct list_head list;
if (!v3d)
return -ENODEV;
/* We may need to try allocating more than once to get a BO
* that doesn't cross 256MB. Track the ones we've allocated
* that failed so far, so that we can free them when we've got
* one that succeeded (if we freed them right away, our next
* allocation would probably be the same chunk of memory).
*/
INIT_LIST_HEAD(&list);
while (true) {
struct vc4_bo *bo = vc4_bo_create(&vc4->base, size, true,
VC4_BO_TYPE_BIN);
if (IS_ERR(bo)) {
ret = PTR_ERR(bo);
dev_err(&v3d->pdev->dev,
"Failed to allocate memory for tile binning: "
"%d. You may need to enable DMA or give it "
"more memory.",
ret);
break;
}
/* Check if this BO won't trigger the addressing bug. */
if ((bo->base.dma_addr & 0xf0000000) ==
((bo->base.dma_addr + bo->base.base.size - 1) & 0xf0000000)) {
vc4->bin_bo = bo;
/* Set up for allocating 512KB chunks of
* binner memory. The biggest allocation we
* need to do is for the initial tile alloc +
* tile state buffer. We can render to a
* maximum of ((2048*2048) / (32*32) = 4096
* tiles in a frame (until we do floating
* point rendering, at which point it would be
* 8192). Tile state is 48b/tile (rounded to
* a page), and tile alloc is 32b/tile
* (rounded to a page), plus a page of extra,
* for a total of 320kb for our worst-case.
* We choose 512kb so that it divides evenly
* into our 16MB, and the rest of the 512kb
* will be used as storage for the overflow
* from the initial 32b CL per bin.
*/
vc4->bin_alloc_size = 512 * 1024;
vc4->bin_alloc_used = 0;
vc4->bin_alloc_overflow = 0;
WARN_ON_ONCE(sizeof(vc4->bin_alloc_used) * 8 !=
bo->base.base.size / vc4->bin_alloc_size);
kref_init(&vc4->bin_bo_kref);
/* Enable the out-of-memory interrupt to set our
* newly-allocated binner BO, potentially from an
* already-pending-but-masked interrupt.
*/
V3D_WRITE(V3D_INTENA, V3D_INT_OUTOMEM);
break;
}
/* Put it on the list to free later, and try again. */
list_add(&bo->unref_head, &list);
}
/* Free all the BOs we allocated but didn't choose. */
while (!list_empty(&list)) {
struct vc4_bo *bo = list_last_entry(&list,
struct vc4_bo, unref_head);
list_del(&bo->unref_head);
drm_gem_object_put(&bo->base.base);
}
return ret;
}
int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used)
{
int ret = 0;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
mutex_lock(&vc4->bin_bo_lock);
if (used && *used)
goto complete;
if (vc4->bin_bo)
kref_get(&vc4->bin_bo_kref);
else
ret = bin_bo_alloc(vc4);
if (ret == 0 && used)
*used = true;
complete:
mutex_unlock(&vc4->bin_bo_lock);
return ret;
}
static void bin_bo_release(struct kref *ref)
{
struct vc4_dev *vc4 = container_of(ref, struct vc4_dev, bin_bo_kref);
if (WARN_ON_ONCE(!vc4->bin_bo))
return;
drm_gem_object_put(&vc4->bin_bo->base.base);
vc4->bin_bo = NULL;
}
void vc4_v3d_bin_bo_put(struct vc4_dev *vc4)
{
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return;
mutex_lock(&vc4->bin_bo_lock);
kref_put(&vc4->bin_bo_kref, bin_bo_release);
mutex_unlock(&vc4->bin_bo_lock);
}
#ifdef CONFIG_PM
static int vc4_v3d_runtime_suspend(struct device *dev)
{
struct vc4_v3d *v3d = dev_get_drvdata(dev);
struct vc4_dev *vc4 = v3d->vc4;
vc4_irq_disable(&vc4->base);
clk_disable_unprepare(v3d->clk);
return 0;
}
static int vc4_v3d_runtime_resume(struct device *dev)
{
struct vc4_v3d *v3d = dev_get_drvdata(dev);
struct vc4_dev *vc4 = v3d->vc4;
int ret;
ret = clk_prepare_enable(v3d->clk);
if (ret != 0)
return ret;
vc4_v3d_init_hw(&vc4->base);
vc4_irq_enable(&vc4->base);
return 0;
}
#endif
int vc4_v3d_debugfs_init(struct drm_minor *minor)
{
struct drm_device *drm = minor->dev;
struct vc4_dev *vc4 = to_vc4_dev(drm);
struct vc4_v3d *v3d = vc4->v3d;
if (!vc4->v3d)
return -ENODEV;
drm_debugfs_add_file(drm, "v3d_ident", vc4_v3d_debugfs_ident, NULL);
vc4_debugfs_add_regset32(drm, "v3d_regs", &v3d->regset);
return 0;
}
static int vc4_v3d_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dev *vc4 = to_vc4_dev(drm);
struct vc4_v3d *v3d = NULL;
int ret;
v3d = devm_kzalloc(&pdev->dev, sizeof(*v3d), GFP_KERNEL);
if (!v3d)
return -ENOMEM;
dev_set_drvdata(dev, v3d);
v3d->pdev = pdev;
v3d->regs = vc4_ioremap_regs(pdev, 0);
if (IS_ERR(v3d->regs))
return PTR_ERR(v3d->regs);
v3d->regset.base = v3d->regs;
v3d->regset.regs = v3d_regs;
v3d->regset.nregs = ARRAY_SIZE(v3d_regs);
vc4->v3d = v3d;
v3d->vc4 = vc4;
v3d->clk = devm_clk_get_optional(dev, NULL);
if (IS_ERR(v3d->clk))
return dev_err_probe(dev, PTR_ERR(v3d->clk), "Failed to get V3D clock\n");
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return ret;
vc4->irq = ret;
ret = devm_pm_runtime_enable(dev);
if (ret)
return ret;
ret = pm_runtime_resume_and_get(dev);
if (ret)
return ret;
if (V3D_READ(V3D_IDENT0) != V3D_EXPECTED_IDENT0) {
drm_err(drm, "V3D_IDENT0 read 0x%08x instead of 0x%08x\n",
V3D_READ(V3D_IDENT0), V3D_EXPECTED_IDENT0);
ret = -EINVAL;
goto err_put_runtime_pm;
}
/* Reset the binner overflow address/size at setup, to be sure
* we don't reuse an old one.
*/
V3D_WRITE(V3D_BPOA, 0);
V3D_WRITE(V3D_BPOS, 0);
ret = vc4_irq_install(drm, vc4->irq);
if (ret) {
drm_err(drm, "Failed to install IRQ handler\n");
goto err_put_runtime_pm;
}
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */
pm_runtime_put_autosuspend(dev);
return 0;
err_put_runtime_pm:
pm_runtime_put(dev);
return ret;
}
static void vc4_v3d_unbind(struct device *dev, struct device *master,
void *data)
{
struct drm_device *drm = data;
struct vc4_dev *vc4 = to_vc4_dev(drm);
vc4_irq_uninstall(drm);
/* Disable the binner's overflow memory address, so the next
* driver probe (if any) doesn't try to reuse our old
* allocation.
*/
V3D_WRITE(V3D_BPOA, 0);
V3D_WRITE(V3D_BPOS, 0);
vc4->v3d = NULL;
}
static const struct dev_pm_ops vc4_v3d_pm_ops = {
SET_RUNTIME_PM_OPS(vc4_v3d_runtime_suspend, vc4_v3d_runtime_resume, NULL)
};
static const struct component_ops vc4_v3d_ops = {
.bind = vc4_v3d_bind,
.unbind = vc4_v3d_unbind,
};
static int vc4_v3d_dev_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &vc4_v3d_ops);
}
static void vc4_v3d_dev_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &vc4_v3d_ops);
}
const struct of_device_id vc4_v3d_dt_match[] = {
{ .compatible = "brcm,bcm2835-v3d" },
{ .compatible = "brcm,cygnus-v3d" },
{ .compatible = "brcm,vc4-v3d" },
{}
};
struct platform_driver vc4_v3d_driver = {
.probe = vc4_v3d_dev_probe,
.remove = vc4_v3d_dev_remove,
.driver = {
.name = "vc4_v3d",
.of_match_table = vc4_v3d_dt_match,
.pm = &vc4_v3d_pm_ops,
},
};
@@ -0,0 +1,962 @@
/*
* Copyright © 2014 Broadcom
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/**
* DOC: Command list validator for VC4.
*
* Since the VC4 has no IOMMU between it and system memory, a user
* with access to execute command lists could escalate privilege by
* overwriting system memory (drawing to it as a framebuffer) or
* reading system memory it shouldn't (reading it as a vertex buffer
* or index buffer)
*
* We validate binner command lists to ensure that all accesses are
* within the bounds of the GEM objects referenced by the submitted
* job. It explicitly whitelists packets, and looks at the offsets in
* any address fields to make sure they're contained within the BOs
* they reference.
*
* Note that because CL validation is already reading the
* user-submitted CL and writing the validated copy out to the memory
* that the GPU will actually read, this is also where GEM relocation
* processing (turning BO references into actual addresses for the GPU
* to use) happens.
*/
#include <drm/drm_print.h>
#include "vc4_drv.h"
#include "vc4_packet.h"
#define VALIDATE_ARGS \
struct vc4_exec_info *exec, \
void *validated, \
void *untrusted
/** Return the width in pixels of a 64-byte microtile. */
static uint32_t
utile_width(int cpp)
{
switch (cpp) {
case 1:
case 2:
return 8;
case 4:
return 4;
case 8:
return 2;
default:
pr_err("unknown cpp: %d\n", cpp);
return 1;
}
}
/** Return the height in pixels of a 64-byte microtile. */
static uint32_t
utile_height(int cpp)
{
switch (cpp) {
case 1:
return 8;
case 2:
case 4:
case 8:
return 4;
default:
pr_err("unknown cpp: %d\n", cpp);
return 1;
}
}
/**
* size_is_lt() - Returns whether a miplevel of the given size will
* use the lineartile (LT) tiling layout rather than the normal T
* tiling layout.
* @width: Width in pixels of the miplevel
* @height: Height in pixels of the miplevel
* @cpp: Bytes per pixel of the pixel format
*/
static bool
size_is_lt(uint32_t width, uint32_t height, int cpp)
{
return (width <= 4 * utile_width(cpp) ||
height <= 4 * utile_height(cpp));
}
struct drm_gem_dma_object *
vc4_use_bo(struct vc4_exec_info *exec, uint32_t hindex)
{
struct vc4_dev *vc4 = exec->dev;
struct drm_gem_dma_object *obj;
struct vc4_bo *bo;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return NULL;
if (hindex >= exec->bo_count) {
DRM_DEBUG("BO index %d greater than BO count %d\n",
hindex, exec->bo_count);
return NULL;
}
obj = to_drm_gem_dma_obj(exec->bo[hindex]);
bo = to_vc4_bo(&obj->base);
if (bo->validated_shader) {
DRM_DEBUG("Trying to use shader BO as something other than "
"a shader\n");
return NULL;
}
return obj;
}
static struct drm_gem_dma_object *
vc4_use_handle(struct vc4_exec_info *exec, uint32_t gem_handles_packet_index)
{
return vc4_use_bo(exec, exec->bo_index[gem_handles_packet_index]);
}
static bool
validate_bin_pos(struct vc4_exec_info *exec, void *untrusted, uint32_t pos)
{
/* Note that the untrusted pointer passed to these functions is
* incremented past the packet byte.
*/
return (untrusted - 1 == exec->bin_u + pos);
}
static uint32_t
gl_shader_rec_size(uint32_t pointer_bits)
{
uint32_t attribute_count = pointer_bits & 7;
bool extended = pointer_bits & 8;
if (attribute_count == 0)
attribute_count = 8;
if (extended)
return 100 + attribute_count * 4;
else
return 36 + attribute_count * 8;
}
bool
vc4_check_tex_size(struct vc4_exec_info *exec, struct drm_gem_dma_object *fbo,
uint32_t offset, uint8_t tiling_format,
uint32_t width, uint32_t height, uint8_t cpp)
{
struct vc4_dev *vc4 = exec->dev;
uint32_t aligned_width, aligned_height, stride, size;
uint32_t utile_w = utile_width(cpp);
uint32_t utile_h = utile_height(cpp);
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return false;
/* The shaded vertex format stores signed 12.4 fixed point
* (-2048,2047) offsets from the viewport center, so we should
* never have a render target larger than 4096. The texture
* unit can only sample from 2048x2048, so it's even more
* restricted. This lets us avoid worrying about overflow in
* our math.
*/
if (width > 4096 || height > 4096) {
DRM_DEBUG("Surface dimensions (%d,%d) too large",
width, height);
return false;
}
switch (tiling_format) {
case VC4_TILING_FORMAT_LINEAR:
aligned_width = round_up(width, utile_w);
aligned_height = height;
break;
case VC4_TILING_FORMAT_T:
aligned_width = round_up(width, utile_w * 8);
aligned_height = round_up(height, utile_h * 8);
break;
case VC4_TILING_FORMAT_LT:
aligned_width = round_up(width, utile_w);
aligned_height = round_up(height, utile_h);
break;
default:
DRM_DEBUG("buffer tiling %d unsupported\n", tiling_format);
return false;
}
stride = aligned_width * cpp;
size = stride * aligned_height;
if (size + offset < size ||
size + offset > fbo->base.size) {
DRM_DEBUG("Overflow in %dx%d (%dx%d) fbo size (%d + %d > %zd)\n",
width, height,
aligned_width, aligned_height,
size, offset, fbo->base.size);
return false;
}
return true;
}
static int
validate_flush(VALIDATE_ARGS)
{
if (!validate_bin_pos(exec, untrusted, exec->args->bin_cl_size - 1)) {
DRM_DEBUG("Bin CL must end with VC4_PACKET_FLUSH\n");
return -EINVAL;
}
exec->found_flush = true;
return 0;
}
static int
validate_start_tile_binning(VALIDATE_ARGS)
{
if (exec->found_start_tile_binning_packet) {
DRM_DEBUG("Duplicate VC4_PACKET_START_TILE_BINNING\n");
return -EINVAL;
}
exec->found_start_tile_binning_packet = true;
if (!exec->found_tile_binning_mode_config_packet) {
DRM_DEBUG("missing VC4_PACKET_TILE_BINNING_MODE_CONFIG\n");
return -EINVAL;
}
return 0;
}
static int
validate_increment_semaphore(VALIDATE_ARGS)
{
if (!validate_bin_pos(exec, untrusted, exec->args->bin_cl_size - 2)) {
DRM_DEBUG("Bin CL must end with "
"VC4_PACKET_INCREMENT_SEMAPHORE\n");
return -EINVAL;
}
exec->found_increment_semaphore_packet = true;
return 0;
}
static int
validate_indexed_prim_list(VALIDATE_ARGS)
{
struct drm_gem_dma_object *ib;
uint32_t length = *(uint32_t *)(untrusted + 1);
uint32_t offset = *(uint32_t *)(untrusted + 5);
uint32_t max_index = *(uint32_t *)(untrusted + 9);
uint32_t index_size = (*(uint8_t *)(untrusted + 0) >> 4) ? 2 : 1;
struct vc4_shader_state *shader_state;
/* Check overflow condition */
if (exec->shader_state_count == 0) {
DRM_DEBUG("shader state must precede primitives\n");
return -EINVAL;
}
shader_state = &exec->shader_state[exec->shader_state_count - 1];
if (max_index > shader_state->max_index)
shader_state->max_index = max_index;
ib = vc4_use_handle(exec, 0);
if (!ib)
return -EINVAL;
if (offset > ib->base.size ||
(ib->base.size - offset) / index_size < length) {
DRM_DEBUG("IB access overflow (%d + %d*%d > %zd)\n",
offset, length, index_size, ib->base.size);
return -EINVAL;
}
*(uint32_t *)(validated + 5) = ib->dma_addr + offset;
return 0;
}
static int
validate_gl_array_primitive(VALIDATE_ARGS)
{
uint32_t length = *(uint32_t *)(untrusted + 1);
uint32_t base_index = *(uint32_t *)(untrusted + 5);
uint32_t max_index;
struct vc4_shader_state *shader_state;
/* Check overflow condition */
if (exec->shader_state_count == 0) {
DRM_DEBUG("shader state must precede primitives\n");
return -EINVAL;
}
shader_state = &exec->shader_state[exec->shader_state_count - 1];
if (length + base_index < length) {
DRM_DEBUG("primitive vertex count overflow\n");
return -EINVAL;
}
max_index = length + base_index - 1;
if (max_index > shader_state->max_index)
shader_state->max_index = max_index;
return 0;
}
static int
validate_gl_shader_state(VALIDATE_ARGS)
{
uint32_t i = exec->shader_state_count++;
if (i >= exec->shader_state_size) {
DRM_DEBUG("More requests for shader states than declared\n");
return -EINVAL;
}
exec->shader_state[i].addr = *(uint32_t *)untrusted;
exec->shader_state[i].max_index = 0;
if (exec->shader_state[i].addr & ~0xf) {
DRM_DEBUG("high bits set in GL shader rec reference\n");
return -EINVAL;
}
*(uint32_t *)validated = (exec->shader_rec_p +
exec->shader_state[i].addr);
exec->shader_rec_p +=
roundup(gl_shader_rec_size(exec->shader_state[i].addr), 16);
return 0;
}
static int
validate_tile_binning_config(VALIDATE_ARGS)
{
struct drm_device *dev = exec->exec_bo->base.dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
uint8_t flags;
uint32_t tile_state_size;
uint32_t tile_count, bin_addr;
int bin_slot;
if (exec->found_tile_binning_mode_config_packet) {
DRM_DEBUG("Duplicate VC4_PACKET_TILE_BINNING_MODE_CONFIG\n");
return -EINVAL;
}
exec->found_tile_binning_mode_config_packet = true;
exec->bin_tiles_x = *(uint8_t *)(untrusted + 12);
exec->bin_tiles_y = *(uint8_t *)(untrusted + 13);
tile_count = exec->bin_tiles_x * exec->bin_tiles_y;
flags = *(uint8_t *)(untrusted + 14);
if (exec->bin_tiles_x == 0 ||
exec->bin_tiles_y == 0) {
DRM_DEBUG("Tile binning config of %dx%d too small\n",
exec->bin_tiles_x, exec->bin_tiles_y);
return -EINVAL;
}
if (flags & (VC4_BIN_CONFIG_DB_NON_MS |
VC4_BIN_CONFIG_TILE_BUFFER_64BIT)) {
DRM_DEBUG("unsupported binning config flags 0x%02x\n", flags);
return -EINVAL;
}
/* The tile state data array is 48 bytes per tile, and we put it at
* the start of a BO containing both it and the tile alloc.
*/
tile_state_size = 48 * tile_count;
/* Since the tile alloc array will follow us, align. */
tile_state_size = roundup(tile_state_size, 4096);
/* Reject configurations whose tile state would leave no room for
* the tile alloc pool that follows it in the slot.
*/
if (tile_state_size >= vc4->bin_alloc_size) {
DRM_DEBUG("Tile binning config of %dx%d too large\n",
exec->bin_tiles_x, exec->bin_tiles_y);
return -EINVAL;
}
bin_slot = vc4_v3d_get_bin_slot(vc4);
if (bin_slot < 0) {
if (bin_slot != -EINTR && bin_slot != -ERESTARTSYS) {
drm_err(dev, "Failed to allocate binner memory: %d\n",
bin_slot);
}
return bin_slot;
}
/* The slot we allocated will only be used by this job, and is
* free when the job completes rendering.
*/
exec->bin_slots |= BIT(bin_slot);
bin_addr = vc4->bin_bo->base.dma_addr + bin_slot * vc4->bin_alloc_size;
exec->tile_alloc_offset = bin_addr + tile_state_size;
/* The TSDA area must be zeroed out before use, otherwise the PTB might
* consume a stale tile state.
*/
memset(vc4->bin_bo->base.vaddr + bin_slot * vc4->bin_alloc_size, 0,
tile_state_size);
*(uint8_t *)(validated + 14) =
((flags & ~(VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_MASK |
VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_MASK)) |
VC4_BIN_CONFIG_AUTO_INIT_TSDA |
VC4_SET_FIELD(VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_32,
VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE) |
VC4_SET_FIELD(VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_128,
VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE));
/* tile alloc address. */
*(uint32_t *)(validated + 0) = exec->tile_alloc_offset;
/* tile alloc size. */
*(uint32_t *)(validated + 4) = (bin_addr + vc4->bin_alloc_size -
exec->tile_alloc_offset);
/* tile state address. */
*(uint32_t *)(validated + 8) = bin_addr;
return 0;
}
static int
validate_gem_handles(VALIDATE_ARGS)
{
memcpy(exec->bo_index, untrusted, sizeof(exec->bo_index));
return 0;
}
#define VC4_DEFINE_PACKET(packet, func) \
[packet] = { packet ## _SIZE, #packet, func }
static const struct cmd_info {
uint16_t len;
const char *name;
int (*func)(struct vc4_exec_info *exec, void *validated,
void *untrusted);
} cmd_info[] = {
VC4_DEFINE_PACKET(VC4_PACKET_HALT, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_NOP, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_FLUSH, validate_flush),
VC4_DEFINE_PACKET(VC4_PACKET_FLUSH_ALL, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_START_TILE_BINNING,
validate_start_tile_binning),
VC4_DEFINE_PACKET(VC4_PACKET_INCREMENT_SEMAPHORE,
validate_increment_semaphore),
VC4_DEFINE_PACKET(VC4_PACKET_GL_INDEXED_PRIMITIVE,
validate_indexed_prim_list),
VC4_DEFINE_PACKET(VC4_PACKET_GL_ARRAY_PRIMITIVE,
validate_gl_array_primitive),
VC4_DEFINE_PACKET(VC4_PACKET_PRIMITIVE_LIST_FORMAT, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_GL_SHADER_STATE, validate_gl_shader_state),
VC4_DEFINE_PACKET(VC4_PACKET_CONFIGURATION_BITS, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_FLAT_SHADE_FLAGS, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_POINT_SIZE, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_LINE_WIDTH, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_RHT_X_BOUNDARY, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_DEPTH_OFFSET, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_CLIP_WINDOW, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_VIEWPORT_OFFSET, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_CLIPPER_XY_SCALING, NULL),
/* Note: The docs say this was also 105, but it was 106 in the
* initial userland code drop.
*/
VC4_DEFINE_PACKET(VC4_PACKET_CLIPPER_Z_SCALING, NULL),
VC4_DEFINE_PACKET(VC4_PACKET_TILE_BINNING_MODE_CONFIG,
validate_tile_binning_config),
VC4_DEFINE_PACKET(VC4_PACKET_GEM_HANDLES, validate_gem_handles),
};
int
vc4_validate_bin_cl(struct drm_device *dev,
void *validated,
void *unvalidated,
struct vc4_exec_info *exec)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
uint32_t len = exec->args->bin_cl_size;
uint32_t dst_offset = 0;
uint32_t src_offset = 0;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
while (src_offset < len) {
void *dst_pkt = validated + dst_offset;
void *src_pkt = unvalidated + src_offset;
u8 cmd = *(uint8_t *)src_pkt;
const struct cmd_info *info;
if (cmd >= ARRAY_SIZE(cmd_info)) {
DRM_DEBUG("0x%08x: packet %d out of bounds\n",
src_offset, cmd);
return -EINVAL;
}
info = &cmd_info[cmd];
if (!info->name) {
DRM_DEBUG("0x%08x: packet %d invalid\n",
src_offset, cmd);
return -EINVAL;
}
if (src_offset + info->len > len) {
DRM_DEBUG("0x%08x: packet %d (%s) length 0x%08x "
"exceeds bounds (0x%08x)\n",
src_offset, cmd, info->name, info->len,
src_offset + len);
return -EINVAL;
}
if (cmd != VC4_PACKET_GEM_HANDLES)
memcpy(dst_pkt, src_pkt, info->len);
if (info->func && info->func(exec,
dst_pkt + 1,
src_pkt + 1)) {
DRM_DEBUG("0x%08x: packet %d (%s) failed to validate\n",
src_offset, cmd, info->name);
return -EINVAL;
}
src_offset += info->len;
/* GEM handle loading doesn't produce HW packets. */
if (cmd != VC4_PACKET_GEM_HANDLES)
dst_offset += info->len;
/* When the CL hits halt, it'll stop reading anything else. */
if (cmd == VC4_PACKET_HALT)
break;
}
exec->ct0ea = exec->ct0ca + dst_offset;
if (!exec->found_start_tile_binning_packet) {
DRM_DEBUG("Bin CL missing VC4_PACKET_START_TILE_BINNING\n");
return -EINVAL;
}
/* The bin CL must be ended with INCREMENT_SEMAPHORE and FLUSH. The
* semaphore is used to trigger the render CL to start up, and the
* FLUSH is what caps the bin lists with
* VC4_PACKET_RETURN_FROM_SUB_LIST (so they jump back to the main
* render CL when they get called to) and actually triggers the queued
* semaphore increment.
*/
if (!exec->found_increment_semaphore_packet || !exec->found_flush) {
DRM_DEBUG("Bin CL missing VC4_PACKET_INCREMENT_SEMAPHORE + "
"VC4_PACKET_FLUSH\n");
return -EINVAL;
}
return 0;
}
static bool
reloc_tex(struct vc4_exec_info *exec,
void *uniform_data_u,
struct vc4_texture_sample_info *sample,
uint32_t texture_handle_index, bool is_cs)
{
struct drm_gem_dma_object *tex;
uint32_t p0 = *(uint32_t *)(uniform_data_u + sample->p_offset[0]);
uint32_t p1 = *(uint32_t *)(uniform_data_u + sample->p_offset[1]);
uint32_t p2 = (sample->p_offset[2] != ~0 ?
*(uint32_t *)(uniform_data_u + sample->p_offset[2]) : 0);
uint32_t p3 = (sample->p_offset[3] != ~0 ?
*(uint32_t *)(uniform_data_u + sample->p_offset[3]) : 0);
uint32_t *validated_p0 = exec->uniforms_v + sample->p_offset[0];
uint32_t offset = p0 & VC4_TEX_P0_OFFSET_MASK;
uint32_t miplevels = VC4_GET_FIELD(p0, VC4_TEX_P0_MIPLVLS);
uint32_t width = VC4_GET_FIELD(p1, VC4_TEX_P1_WIDTH);
uint32_t height = VC4_GET_FIELD(p1, VC4_TEX_P1_HEIGHT);
uint32_t cpp, tiling_format, utile_w, utile_h;
uint32_t i;
uint32_t cube_map_stride = 0;
enum vc4_texture_data_type type;
tex = vc4_use_bo(exec, texture_handle_index);
if (!tex)
return false;
if (sample->is_direct) {
uint32_t remaining_size = tex->base.size - p0;
if (p0 > tex->base.size - 4) {
DRM_DEBUG("UBO offset greater than UBO size\n");
goto fail;
}
if (p1 > remaining_size - 4) {
DRM_DEBUG("UBO clamp would allow reads "
"outside of UBO\n");
goto fail;
}
*validated_p0 = tex->dma_addr + p0;
return true;
}
if (width == 0)
width = 2048;
if (height == 0)
height = 2048;
if (p0 & VC4_TEX_P0_CMMODE_MASK) {
if (VC4_GET_FIELD(p2, VC4_TEX_P2_PTYPE) ==
VC4_TEX_P2_PTYPE_CUBE_MAP_STRIDE)
cube_map_stride = p2 & VC4_TEX_P2_CMST_MASK;
if (VC4_GET_FIELD(p3, VC4_TEX_P2_PTYPE) ==
VC4_TEX_P2_PTYPE_CUBE_MAP_STRIDE) {
if (cube_map_stride) {
DRM_DEBUG("Cube map stride set twice\n");
goto fail;
}
cube_map_stride = p3 & VC4_TEX_P2_CMST_MASK;
}
if (!cube_map_stride) {
DRM_DEBUG("Cube map stride not set\n");
goto fail;
}
}
type = (VC4_GET_FIELD(p0, VC4_TEX_P0_TYPE) |
(VC4_GET_FIELD(p1, VC4_TEX_P1_TYPE4) << 4));
switch (type) {
case VC4_TEXTURE_TYPE_RGBA8888:
case VC4_TEXTURE_TYPE_RGBX8888:
case VC4_TEXTURE_TYPE_RGBA32R:
cpp = 4;
break;
case VC4_TEXTURE_TYPE_RGBA4444:
case VC4_TEXTURE_TYPE_RGBA5551:
case VC4_TEXTURE_TYPE_RGB565:
case VC4_TEXTURE_TYPE_LUMALPHA:
case VC4_TEXTURE_TYPE_S16F:
case VC4_TEXTURE_TYPE_S16:
cpp = 2;
break;
case VC4_TEXTURE_TYPE_LUMINANCE:
case VC4_TEXTURE_TYPE_ALPHA:
case VC4_TEXTURE_TYPE_S8:
cpp = 1;
break;
case VC4_TEXTURE_TYPE_ETC1:
/* ETC1 is arranged as 64-bit blocks, where each block is 4x4
* pixels.
*/
cpp = 8;
width = (width + 3) >> 2;
height = (height + 3) >> 2;
break;
case VC4_TEXTURE_TYPE_BW1:
case VC4_TEXTURE_TYPE_A4:
case VC4_TEXTURE_TYPE_A1:
case VC4_TEXTURE_TYPE_RGBA64:
case VC4_TEXTURE_TYPE_YUV422R:
default:
DRM_DEBUG("Texture format %d unsupported\n", type);
goto fail;
}
utile_w = utile_width(cpp);
utile_h = utile_height(cpp);
if (type == VC4_TEXTURE_TYPE_RGBA32R) {
tiling_format = VC4_TILING_FORMAT_LINEAR;
} else {
if (size_is_lt(width, height, cpp))
tiling_format = VC4_TILING_FORMAT_LT;
else
tiling_format = VC4_TILING_FORMAT_T;
}
if (!vc4_check_tex_size(exec, tex, offset + cube_map_stride * 5,
tiling_format, width, height, cpp)) {
goto fail;
}
/* The mipmap levels are stored before the base of the texture. Make
* sure there is actually space in the BO.
*/
for (i = 1; i <= miplevels; i++) {
uint32_t level_width = max(width >> i, 1u);
uint32_t level_height = max(height >> i, 1u);
uint32_t aligned_width, aligned_height;
uint32_t level_size;
/* Once the levels get small enough, they drop from T to LT. */
if (tiling_format == VC4_TILING_FORMAT_T &&
size_is_lt(level_width, level_height, cpp)) {
tiling_format = VC4_TILING_FORMAT_LT;
}
switch (tiling_format) {
case VC4_TILING_FORMAT_T:
aligned_width = round_up(level_width, utile_w * 8);
aligned_height = round_up(level_height, utile_h * 8);
break;
case VC4_TILING_FORMAT_LT:
aligned_width = round_up(level_width, utile_w);
aligned_height = round_up(level_height, utile_h);
break;
default:
aligned_width = round_up(level_width, utile_w);
aligned_height = level_height;
break;
}
level_size = aligned_width * cpp * aligned_height;
if (offset < level_size) {
DRM_DEBUG("Level %d (%dx%d -> %dx%d) size %db "
"overflowed buffer bounds (offset %d)\n",
i, level_width, level_height,
aligned_width, aligned_height,
level_size, offset);
goto fail;
}
offset -= level_size;
}
*validated_p0 = tex->dma_addr + p0;
return true;
fail:
DRM_INFO("Texture p0 at %d: 0x%08x\n", sample->p_offset[0], p0);
DRM_INFO("Texture p1 at %d: 0x%08x\n", sample->p_offset[1], p1);
DRM_INFO("Texture p2 at %d: 0x%08x\n", sample->p_offset[2], p2);
DRM_INFO("Texture p3 at %d: 0x%08x\n", sample->p_offset[3], p3);
return false;
}
static int
validate_gl_shader_rec(struct drm_device *dev,
struct vc4_exec_info *exec,
struct vc4_shader_state *state)
{
uint32_t *src_handles;
void *pkt_u, *pkt_v;
static const uint32_t shader_reloc_offsets[] = {
4, /* fs */
16, /* vs */
28, /* cs */
};
uint32_t shader_reloc_count = ARRAY_SIZE(shader_reloc_offsets);
struct drm_gem_dma_object *bo[ARRAY_SIZE(shader_reloc_offsets) + 8];
uint32_t nr_attributes, nr_relocs, packet_size;
int i;
nr_attributes = state->addr & 0x7;
if (nr_attributes == 0)
nr_attributes = 8;
packet_size = gl_shader_rec_size(state->addr);
nr_relocs = ARRAY_SIZE(shader_reloc_offsets) + nr_attributes;
if (nr_relocs * 4 > exec->shader_rec_size) {
DRM_DEBUG("overflowed shader recs reading %d handles "
"from %d bytes left\n",
nr_relocs, exec->shader_rec_size);
return -EINVAL;
}
src_handles = exec->shader_rec_u;
exec->shader_rec_u += nr_relocs * 4;
exec->shader_rec_size -= nr_relocs * 4;
if (packet_size > exec->shader_rec_size) {
DRM_DEBUG("overflowed shader recs copying %db packet "
"from %d bytes left\n",
packet_size, exec->shader_rec_size);
return -EINVAL;
}
pkt_u = exec->shader_rec_u;
pkt_v = exec->shader_rec_v;
memcpy(pkt_v, pkt_u, packet_size);
exec->shader_rec_u += packet_size;
/* Shader recs have to be aligned to 16 bytes (due to the attribute
* flags being in the low bytes), so round the next validated shader
* rec address up. This should be safe, since we've got so many
* relocations in a shader rec packet.
*/
BUG_ON(roundup(packet_size, 16) - packet_size > nr_relocs * 4);
exec->shader_rec_v += roundup(packet_size, 16);
exec->shader_rec_size -= packet_size;
for (i = 0; i < shader_reloc_count; i++) {
if (src_handles[i] > exec->bo_count) {
DRM_DEBUG("Shader handle %d too big\n", src_handles[i]);
return -EINVAL;
}
bo[i] = to_drm_gem_dma_obj(exec->bo[src_handles[i]]);
if (!bo[i])
return -EINVAL;
}
for (i = shader_reloc_count; i < nr_relocs; i++) {
bo[i] = vc4_use_bo(exec, src_handles[i]);
if (!bo[i])
return -EINVAL;
}
if (((*(uint16_t *)pkt_u & VC4_SHADER_FLAG_FS_SINGLE_THREAD) == 0) !=
to_vc4_bo(&bo[0]->base)->validated_shader->is_threaded) {
DRM_DEBUG("Thread mode of CL and FS do not match\n");
return -EINVAL;
}
if (to_vc4_bo(&bo[1]->base)->validated_shader->is_threaded ||
to_vc4_bo(&bo[2]->base)->validated_shader->is_threaded) {
DRM_DEBUG("cs and vs cannot be threaded\n");
return -EINVAL;
}
for (i = 0; i < shader_reloc_count; i++) {
struct vc4_validated_shader_info *validated_shader;
uint32_t o = shader_reloc_offsets[i];
uint32_t src_offset = *(uint32_t *)(pkt_u + o);
uint32_t *texture_handles_u;
void *uniform_data_u;
uint32_t tex, uni;
*(uint32_t *)(pkt_v + o) = bo[i]->dma_addr + src_offset;
if (src_offset != 0) {
DRM_DEBUG("Shaders must be at offset 0 of "
"the BO.\n");
return -EINVAL;
}
validated_shader = to_vc4_bo(&bo[i]->base)->validated_shader;
if (!validated_shader)
return -EINVAL;
if (validated_shader->uniforms_src_size >
exec->uniforms_size) {
DRM_DEBUG("Uniforms src buffer overflow\n");
return -EINVAL;
}
texture_handles_u = exec->uniforms_u;
uniform_data_u = (texture_handles_u +
validated_shader->num_texture_samples);
memcpy(exec->uniforms_v, uniform_data_u,
validated_shader->uniforms_size);
for (tex = 0;
tex < validated_shader->num_texture_samples;
tex++) {
if (!reloc_tex(exec,
uniform_data_u,
&validated_shader->texture_samples[tex],
texture_handles_u[tex],
i == 2)) {
return -EINVAL;
}
}
/* Fill in the uniform slots that need this shader's
* start-of-uniforms address (used for resetting the uniform
* stream in the presence of control flow).
*/
for (uni = 0;
uni < validated_shader->num_uniform_addr_offsets;
uni++) {
uint32_t o = validated_shader->uniform_addr_offsets[uni];
((uint32_t *)exec->uniforms_v)[o] = exec->uniforms_p;
}
*(uint32_t *)(pkt_v + o + 4) = exec->uniforms_p;
exec->uniforms_u += validated_shader->uniforms_src_size;
exec->uniforms_v += validated_shader->uniforms_size;
exec->uniforms_p += validated_shader->uniforms_size;
}
for (i = 0; i < nr_attributes; i++) {
struct drm_gem_dma_object *vbo =
bo[ARRAY_SIZE(shader_reloc_offsets) + i];
uint32_t o = 36 + i * 8;
uint32_t offset = *(uint32_t *)(pkt_u + o + 0);
uint32_t attr_size = *(uint8_t *)(pkt_u + o + 4) + 1;
uint32_t stride = *(uint8_t *)(pkt_u + o + 5);
uint32_t max_index;
if (state->addr & 0x8)
stride |= (*(uint32_t *)(pkt_u + 100 + i * 4)) & ~0xff;
if (vbo->base.size < offset ||
vbo->base.size - offset < attr_size) {
DRM_DEBUG("BO offset overflow (%d + %d > %zu)\n",
offset, attr_size, vbo->base.size);
return -EINVAL;
}
if (stride != 0) {
max_index = ((vbo->base.size - offset - attr_size) /
stride);
if (state->max_index > max_index) {
DRM_DEBUG("primitives use index %d out of "
"supplied %d\n",
state->max_index, max_index);
return -EINVAL;
}
}
*(uint32_t *)(pkt_v + o) = vbo->dma_addr + offset;
}
return 0;
}
int
vc4_validate_shader_recs(struct drm_device *dev,
struct vc4_exec_info *exec)
{
struct vc4_dev *vc4 = to_vc4_dev(dev);
uint32_t i;
int ret = 0;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return -ENODEV;
for (i = 0; i < exec->shader_state_count; i++) {
ret = validate_gl_shader_rec(dev, exec, &exec->shader_state[i]);
if (ret)
return ret;
}
return ret;
}
@@ -0,0 +1,957 @@
/*
* Copyright © 2014 Broadcom
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/**
* DOC: Shader validator for VC4.
*
* Since the VC4 has no IOMMU between it and system memory, a user
* with access to execute shaders could escalate privilege by
* overwriting system memory (using the VPM write address register in
* the general-purpose DMA mode) or reading system memory it shouldn't
* (reading it as a texture, uniform data, or direct-addressed TMU
* lookup).
*
* The shader validator walks over a shader's BO, ensuring that its
* accesses are appropriately bounded, and recording where texture
* accesses are made so that we can do relocations for them in the
* uniform stream.
*
* Shader BO are immutable for their lifetimes (enforced by not
* allowing mmaps, GEM prime export, or rendering to from a CL), so
* this validation is only performed at BO creation time.
*/
#include <drm/drm_print.h>
#include "vc4_drv.h"
#include "vc4_qpu_defines.h"
#define LIVE_REG_COUNT (32 + 32 + 4)
struct vc4_shader_validation_state {
/* Current IP being validated. */
uint32_t ip;
/* IP at the end of the BO, do not read shader[max_ip] */
uint32_t max_ip;
uint64_t *shader;
struct vc4_texture_sample_info tmu_setup[2];
int tmu_write_count[2];
/* For registers that were last written to by a MIN instruction with
* one argument being a uniform, the address of the uniform.
* Otherwise, ~0.
*
* This is used for the validation of direct address memory reads.
*/
uint32_t live_min_clamp_offsets[LIVE_REG_COUNT];
bool live_max_clamp_regs[LIVE_REG_COUNT];
uint32_t live_immediates[LIVE_REG_COUNT];
/* Bitfield of which IPs are used as branch targets.
*
* Used for validation that the uniform stream is updated at the right
* points and clearing the texturing/clamping state.
*/
unsigned long *branch_targets;
/* Set when entering a basic block, and cleared when the uniform
* address update is found. This is used to make sure that we don't
* read uniforms when the address is undefined.
*/
bool needs_uniform_address_update;
/* Set when we find a backwards branch. If the branch is backwards,
* the taraget is probably doing an address reset to read uniforms,
* and so we need to be sure that a uniforms address is present in the
* stream, even if the shader didn't need to read uniforms in later
* basic blocks.
*/
bool needs_uniform_address_for_loop;
/* Set when we find an instruction writing the top half of the
* register files. If we allowed writing the unusable regs in
* a threaded shader, then the other shader running on our
* QPU's clamp validation would be invalid.
*/
bool all_registers_used;
};
static uint32_t
waddr_to_live_reg_index(uint32_t waddr, bool is_b)
{
if (waddr < 32) {
if (is_b)
return 32 + waddr;
else
return waddr;
} else if (waddr <= QPU_W_ACC3) {
return 64 + waddr - QPU_W_ACC0;
} else {
return ~0;
}
}
static uint32_t
raddr_add_a_to_live_reg_index(uint64_t inst)
{
uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
uint32_t add_a = QPU_GET_FIELD(inst, QPU_ADD_A);
uint32_t raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A);
uint32_t raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B);
if (add_a == QPU_MUX_A)
return raddr_a;
else if (add_a == QPU_MUX_B && sig != QPU_SIG_SMALL_IMM)
return 32 + raddr_b;
else if (add_a <= QPU_MUX_R3)
return 64 + add_a;
else
return ~0;
}
static bool
live_reg_is_upper_half(uint32_t lri)
{
return (lri >= 16 && lri < 32) ||
(lri >= 32 + 16 && lri < 32 + 32);
}
static bool
is_tmu_submit(uint32_t waddr)
{
return (waddr == QPU_W_TMU0_S ||
waddr == QPU_W_TMU1_S);
}
static bool
is_tmu_write(uint32_t waddr)
{
return (waddr >= QPU_W_TMU0_S &&
waddr <= QPU_W_TMU1_B);
}
static bool
record_texture_sample(struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state,
int tmu)
{
uint32_t s = validated_shader->num_texture_samples;
int i;
struct vc4_texture_sample_info *temp_samples;
temp_samples = krealloc(validated_shader->texture_samples,
(s + 1) * sizeof(*temp_samples),
GFP_KERNEL);
if (!temp_samples)
return false;
memcpy(&temp_samples[s],
&validation_state->tmu_setup[tmu],
sizeof(*temp_samples));
validated_shader->num_texture_samples = s + 1;
validated_shader->texture_samples = temp_samples;
for (i = 0; i < 4; i++)
validation_state->tmu_setup[tmu].p_offset[i] = ~0;
return true;
}
static bool
check_tmu_write(struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state,
bool is_mul)
{
uint64_t inst = validation_state->shader[validation_state->ip];
uint32_t waddr = (is_mul ?
QPU_GET_FIELD(inst, QPU_WADDR_MUL) :
QPU_GET_FIELD(inst, QPU_WADDR_ADD));
uint32_t raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A);
uint32_t raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B);
int tmu = waddr > QPU_W_TMU0_B;
bool submit = is_tmu_submit(waddr);
bool is_direct = submit && validation_state->tmu_write_count[tmu] == 0;
uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
if (is_direct) {
uint32_t add_b = QPU_GET_FIELD(inst, QPU_ADD_B);
uint32_t clamp_reg, clamp_offset;
if (sig == QPU_SIG_SMALL_IMM) {
DRM_DEBUG("direct TMU read used small immediate\n");
return false;
}
/* Make sure that this texture load is an add of the base
* address of the UBO to a clamped offset within the UBO.
*/
if (is_mul ||
QPU_GET_FIELD(inst, QPU_OP_ADD) != QPU_A_ADD) {
DRM_DEBUG("direct TMU load wasn't an add\n");
return false;
}
/* We assert that the clamped address is the first
* argument, and the UBO base address is the second argument.
* This is arbitrary, but simpler than supporting flipping the
* two either way.
*/
clamp_reg = raddr_add_a_to_live_reg_index(inst);
if (clamp_reg == ~0) {
DRM_DEBUG("direct TMU load wasn't clamped\n");
return false;
}
clamp_offset = validation_state->live_min_clamp_offsets[clamp_reg];
if (clamp_offset == ~0) {
DRM_DEBUG("direct TMU load wasn't clamped\n");
return false;
}
/* Store the clamp value's offset in p1 (see reloc_tex() in
* vc4_validate.c).
*/
validation_state->tmu_setup[tmu].p_offset[1] =
clamp_offset;
if (!(add_b == QPU_MUX_A && raddr_a == QPU_R_UNIF) &&
!(add_b == QPU_MUX_B && raddr_b == QPU_R_UNIF)) {
DRM_DEBUG("direct TMU load didn't add to a uniform\n");
return false;
}
validation_state->tmu_setup[tmu].is_direct = true;
} else {
if (raddr_a == QPU_R_UNIF || (sig != QPU_SIG_SMALL_IMM &&
raddr_b == QPU_R_UNIF)) {
DRM_DEBUG("uniform read in the same instruction as "
"texture setup.\n");
return false;
}
}
if (validation_state->tmu_write_count[tmu] >= 4) {
DRM_DEBUG("TMU%d got too many parameters before dispatch\n",
tmu);
return false;
}
validation_state->tmu_setup[tmu].p_offset[validation_state->tmu_write_count[tmu]] =
validated_shader->uniforms_size;
validation_state->tmu_write_count[tmu]++;
/* Since direct uses a RADDR uniform reference, it will get counted in
* check_instruction_reads()
*/
if (!is_direct) {
if (validation_state->needs_uniform_address_update) {
DRM_DEBUG("Texturing with undefined uniform address\n");
return false;
}
validated_shader->uniforms_size += 4;
}
if (submit) {
if (!record_texture_sample(validated_shader,
validation_state, tmu)) {
return false;
}
validation_state->tmu_write_count[tmu] = 0;
}
return true;
}
static bool require_uniform_address_uniform(struct vc4_validated_shader_info *validated_shader)
{
uint32_t o = validated_shader->num_uniform_addr_offsets;
uint32_t num_uniforms = validated_shader->uniforms_size / 4;
u32 *offsets;
offsets = krealloc_array(validated_shader->uniform_addr_offsets,
o + 1,
sizeof(*validated_shader->uniform_addr_offsets),
GFP_KERNEL);
if (!offsets)
return false;
validated_shader->uniform_addr_offsets = offsets;
validated_shader->uniform_addr_offsets[o] = num_uniforms;
validated_shader->num_uniform_addr_offsets++;
return true;
}
static bool
validate_uniform_address_write(struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state,
bool is_mul)
{
uint64_t inst = validation_state->shader[validation_state->ip];
u32 add_b = QPU_GET_FIELD(inst, QPU_ADD_B);
u32 raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A);
u32 raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B);
u32 add_lri = raddr_add_a_to_live_reg_index(inst);
/* We want our reset to be pointing at whatever uniform follows the
* uniforms base address.
*/
u32 expected_offset = validated_shader->uniforms_size + 4;
/* We only support absolute uniform address changes, and we
* require that they be in the current basic block before any
* of its uniform reads.
*
* One could potentially emit more efficient QPU code, by
* noticing that (say) an if statement does uniform control
* flow for all threads and that the if reads the same number
* of uniforms on each side. However, this scheme is easy to
* validate so it's all we allow for now.
*/
switch (QPU_GET_FIELD(inst, QPU_SIG)) {
case QPU_SIG_NONE:
case QPU_SIG_SCOREBOARD_UNLOCK:
case QPU_SIG_COLOR_LOAD:
case QPU_SIG_LOAD_TMU0:
case QPU_SIG_LOAD_TMU1:
break;
default:
DRM_DEBUG("uniforms address change must be "
"normal math\n");
return false;
}
if (is_mul || QPU_GET_FIELD(inst, QPU_OP_ADD) != QPU_A_ADD) {
DRM_DEBUG("Uniform address reset must be an ADD.\n");
return false;
}
if (QPU_GET_FIELD(inst, QPU_COND_ADD) != QPU_COND_ALWAYS) {
DRM_DEBUG("Uniform address reset must be unconditional.\n");
return false;
}
if (QPU_GET_FIELD(inst, QPU_PACK) != QPU_PACK_A_NOP &&
!(inst & QPU_PM)) {
DRM_DEBUG("No packing allowed on uniforms reset\n");
return false;
}
if (add_lri == -1) {
DRM_DEBUG("First argument of uniform address write must be "
"an immediate value.\n");
return false;
}
if (validation_state->live_immediates[add_lri] != expected_offset) {
DRM_DEBUG("Resetting uniforms with offset %db instead of %db\n",
validation_state->live_immediates[add_lri],
expected_offset);
return false;
}
if (!(add_b == QPU_MUX_A && raddr_a == QPU_R_UNIF) &&
!(add_b == QPU_MUX_B && raddr_b == QPU_R_UNIF)) {
DRM_DEBUG("Second argument of uniform address write must be "
"a uniform.\n");
return false;
}
validation_state->needs_uniform_address_update = false;
validation_state->needs_uniform_address_for_loop = false;
return require_uniform_address_uniform(validated_shader);
}
static bool
check_reg_write(struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state,
bool is_mul)
{
uint64_t inst = validation_state->shader[validation_state->ip];
uint32_t waddr = (is_mul ?
QPU_GET_FIELD(inst, QPU_WADDR_MUL) :
QPU_GET_FIELD(inst, QPU_WADDR_ADD));
uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
bool ws = inst & QPU_WS;
bool is_b = is_mul ^ ws;
u32 lri = waddr_to_live_reg_index(waddr, is_b);
if (lri != -1) {
uint32_t cond_add = QPU_GET_FIELD(inst, QPU_COND_ADD);
uint32_t cond_mul = QPU_GET_FIELD(inst, QPU_COND_MUL);
if (sig == QPU_SIG_LOAD_IMM &&
QPU_GET_FIELD(inst, QPU_PACK) == QPU_PACK_A_NOP &&
((is_mul && cond_mul == QPU_COND_ALWAYS) ||
(!is_mul && cond_add == QPU_COND_ALWAYS))) {
validation_state->live_immediates[lri] =
QPU_GET_FIELD(inst, QPU_LOAD_IMM);
} else {
validation_state->live_immediates[lri] = ~0;
}
if (live_reg_is_upper_half(lri))
validation_state->all_registers_used = true;
}
switch (waddr) {
case QPU_W_UNIFORMS_ADDRESS:
if (is_b) {
DRM_DEBUG("relative uniforms address change "
"unsupported\n");
return false;
}
return validate_uniform_address_write(validated_shader,
validation_state,
is_mul);
case QPU_W_TLB_COLOR_MS:
case QPU_W_TLB_COLOR_ALL:
case QPU_W_TLB_Z:
/* These only interact with the tile buffer, not main memory,
* so they're safe.
*/
return true;
case QPU_W_TMU0_S:
case QPU_W_TMU0_T:
case QPU_W_TMU0_R:
case QPU_W_TMU0_B:
case QPU_W_TMU1_S:
case QPU_W_TMU1_T:
case QPU_W_TMU1_R:
case QPU_W_TMU1_B:
return check_tmu_write(validated_shader, validation_state,
is_mul);
case QPU_W_HOST_INT:
case QPU_W_TMU_NOSWAP:
case QPU_W_TLB_ALPHA_MASK:
case QPU_W_MUTEX_RELEASE:
/* XXX: I haven't thought about these, so don't support them
* for now.
*/
DRM_DEBUG("Unsupported waddr %d\n", waddr);
return false;
case QPU_W_VPM_ADDR:
DRM_DEBUG("General VPM DMA unsupported\n");
return false;
case QPU_W_VPM:
case QPU_W_VPMVCD_SETUP:
/* We allow VPM setup in general, even including VPM DMA
* configuration setup, because the (unsafe) DMA can only be
* triggered by QPU_W_VPM_ADDR writes.
*/
return true;
case QPU_W_TLB_STENCIL_SETUP:
return true;
}
return true;
}
static void
track_live_clamps(struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state)
{
uint64_t inst = validation_state->shader[validation_state->ip];
uint32_t op_add = QPU_GET_FIELD(inst, QPU_OP_ADD);
uint32_t waddr_add = QPU_GET_FIELD(inst, QPU_WADDR_ADD);
uint32_t waddr_mul = QPU_GET_FIELD(inst, QPU_WADDR_MUL);
uint32_t cond_add = QPU_GET_FIELD(inst, QPU_COND_ADD);
uint32_t add_a = QPU_GET_FIELD(inst, QPU_ADD_A);
uint32_t add_b = QPU_GET_FIELD(inst, QPU_ADD_B);
uint32_t raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A);
uint32_t raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B);
uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
bool ws = inst & QPU_WS;
uint32_t lri_add_a, lri_add, lri_mul;
bool add_a_is_min_0;
/* Check whether OP_ADD's A argumennt comes from a live MAX(x, 0),
* before we clear previous live state.
*/
lri_add_a = raddr_add_a_to_live_reg_index(inst);
add_a_is_min_0 = (lri_add_a != ~0 &&
validation_state->live_max_clamp_regs[lri_add_a]);
/* Clear live state for registers written by our instruction. */
lri_add = waddr_to_live_reg_index(waddr_add, ws);
lri_mul = waddr_to_live_reg_index(waddr_mul, !ws);
if (lri_mul != ~0) {
validation_state->live_max_clamp_regs[lri_mul] = false;
validation_state->live_min_clamp_offsets[lri_mul] = ~0;
}
if (lri_add != ~0) {
validation_state->live_max_clamp_regs[lri_add] = false;
validation_state->live_min_clamp_offsets[lri_add] = ~0;
} else {
/* Nothing further to do for live tracking, since only ADDs
* generate new live clamp registers.
*/
return;
}
/* Now, handle remaining live clamp tracking for the ADD operation. */
if (cond_add != QPU_COND_ALWAYS)
return;
if (op_add == QPU_A_MAX) {
/* Track live clamps of a value to a minimum of 0 (in either
* arg).
*/
if (sig != QPU_SIG_SMALL_IMM || raddr_b != 0 ||
(add_a != QPU_MUX_B && add_b != QPU_MUX_B)) {
return;
}
validation_state->live_max_clamp_regs[lri_add] = true;
} else if (op_add == QPU_A_MIN) {
/* Track live clamps of a value clamped to a minimum of 0 and
* a maximum of some uniform's offset.
*/
if (!add_a_is_min_0)
return;
if (!(add_b == QPU_MUX_A && raddr_a == QPU_R_UNIF) &&
!(add_b == QPU_MUX_B && raddr_b == QPU_R_UNIF &&
sig != QPU_SIG_SMALL_IMM)) {
return;
}
validation_state->live_min_clamp_offsets[lri_add] =
validated_shader->uniforms_size;
}
}
static bool
check_instruction_writes(struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state)
{
uint64_t inst = validation_state->shader[validation_state->ip];
uint32_t waddr_add = QPU_GET_FIELD(inst, QPU_WADDR_ADD);
uint32_t waddr_mul = QPU_GET_FIELD(inst, QPU_WADDR_MUL);
bool ok;
if (is_tmu_write(waddr_add) && is_tmu_write(waddr_mul)) {
DRM_DEBUG("ADD and MUL both set up textures\n");
return false;
}
ok = (check_reg_write(validated_shader, validation_state, false) &&
check_reg_write(validated_shader, validation_state, true));
track_live_clamps(validated_shader, validation_state);
return ok;
}
static bool
check_branch(uint64_t inst,
struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state,
int ip)
{
int32_t branch_imm = QPU_GET_FIELD(inst, QPU_BRANCH_TARGET);
uint32_t waddr_add = QPU_GET_FIELD(inst, QPU_WADDR_ADD);
uint32_t waddr_mul = QPU_GET_FIELD(inst, QPU_WADDR_MUL);
if ((int)branch_imm < 0)
validation_state->needs_uniform_address_for_loop = true;
/* We don't want to have to worry about validation of this, and
* there's no need for it.
*/
if (waddr_add != QPU_W_NOP || waddr_mul != QPU_W_NOP) {
DRM_DEBUG("branch instruction at %d wrote a register.\n",
validation_state->ip);
return false;
}
return true;
}
static bool
check_instruction_reads(struct vc4_validated_shader_info *validated_shader,
struct vc4_shader_validation_state *validation_state)
{
uint64_t inst = validation_state->shader[validation_state->ip];
uint32_t raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A);
uint32_t raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B);
uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
if (raddr_a == QPU_R_UNIF ||
(raddr_b == QPU_R_UNIF && sig != QPU_SIG_SMALL_IMM)) {
/* This can't overflow the uint32_t, because we're reading 8
* bytes of instruction to increment by 4 here, so we'd
* already be OOM.
*/
validated_shader->uniforms_size += 4;
if (validation_state->needs_uniform_address_update) {
DRM_DEBUG("Uniform read with undefined uniform "
"address\n");
return false;
}
}
if ((raddr_a >= 16 && raddr_a < 32) ||
(raddr_b >= 16 && raddr_b < 32 && sig != QPU_SIG_SMALL_IMM)) {
validation_state->all_registers_used = true;
}
return true;
}
/* Make sure that all branches are absolute and point within the shader, and
* note their targets for later.
*/
static bool
vc4_validate_branches(struct vc4_shader_validation_state *validation_state)
{
uint32_t max_branch_target = 0;
int ip;
int last_branch = -2;
for (ip = 0; ip < validation_state->max_ip; ip++) {
uint64_t inst = validation_state->shader[ip];
int32_t branch_imm = QPU_GET_FIELD(inst, QPU_BRANCH_TARGET);
uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
uint32_t after_delay_ip = ip + 4;
uint32_t branch_target_ip;
if (sig == QPU_SIG_PROG_END) {
/* There are two delay slots after program end is
* signaled that are still executed, then we're
* finished. validation_state->max_ip is the
* instruction after the last valid instruction in the
* program.
*/
validation_state->max_ip = ip + 3;
continue;
}
if (sig != QPU_SIG_BRANCH)
continue;
if (ip - last_branch < 4) {
DRM_DEBUG("Branch at %d during delay slots\n", ip);
return false;
}
last_branch = ip;
if (inst & QPU_BRANCH_REG) {
DRM_DEBUG("branching from register relative "
"not supported\n");
return false;
}
if (!(inst & QPU_BRANCH_REL)) {
DRM_DEBUG("relative branching required\n");
return false;
}
/* The actual branch target is the instruction after the delay
* slots, plus whatever byte offset is in the low 32 bits of
* the instruction. Make sure we're not branching beyond the
* end of the shader object.
*/
if (branch_imm % sizeof(inst) != 0) {
DRM_DEBUG("branch target not aligned\n");
return false;
}
branch_target_ip = after_delay_ip + (branch_imm >> 3);
if (branch_target_ip >= validation_state->max_ip) {
DRM_DEBUG("Branch at %d outside of shader (ip %d/%d)\n",
ip, branch_target_ip,
validation_state->max_ip);
return false;
}
set_bit(branch_target_ip, validation_state->branch_targets);
/* Make sure that the non-branching path is also not outside
* the shader.
*/
if (after_delay_ip >= validation_state->max_ip) {
DRM_DEBUG("Branch at %d continues past shader end "
"(%d/%d)\n",
ip, after_delay_ip, validation_state->max_ip);
return false;
}
set_bit(after_delay_ip, validation_state->branch_targets);
max_branch_target = max(max_branch_target, after_delay_ip);
}
if (max_branch_target > validation_state->max_ip - 3) {
DRM_DEBUG("Branch landed after QPU_SIG_PROG_END");
return false;
}
return true;
}
/* Resets any known state for the shader, used when we may be branched to from
* multiple locations in the program (or at shader start).
*/
static void
reset_validation_state(struct vc4_shader_validation_state *validation_state)
{
int i;
for (i = 0; i < 8; i++)
validation_state->tmu_setup[i / 4].p_offset[i % 4] = ~0;
for (i = 0; i < LIVE_REG_COUNT; i++) {
validation_state->live_min_clamp_offsets[i] = ~0;
validation_state->live_max_clamp_regs[i] = false;
validation_state->live_immediates[i] = ~0;
}
}
static bool
texturing_in_progress(struct vc4_shader_validation_state *validation_state)
{
return (validation_state->tmu_write_count[0] != 0 ||
validation_state->tmu_write_count[1] != 0);
}
static bool
vc4_handle_branch_target(struct vc4_shader_validation_state *validation_state)
{
uint32_t ip = validation_state->ip;
if (!test_bit(ip, validation_state->branch_targets))
return true;
if (texturing_in_progress(validation_state)) {
DRM_DEBUG("Branch target landed during TMU setup\n");
return false;
}
/* Reset our live values tracking, since this instruction may have
* multiple predecessors.
*
* One could potentially do analysis to determine that, for
* example, all predecessors have a live max clamp in the same
* register, but we don't bother with that.
*/
reset_validation_state(validation_state);
/* Since we've entered a basic block from potentially multiple
* predecessors, we need the uniforms address to be updated before any
* unforms are read. We require that after any branch point, the next
* uniform to be loaded is a uniform address offset. That uniform's
* offset will be marked by the uniform address register write
* validation, or a one-off the end-of-program check.
*/
validation_state->needs_uniform_address_update = true;
return true;
}
struct vc4_validated_shader_info *
vc4_validate_shader(struct drm_gem_dma_object *shader_obj)
{
struct vc4_dev *vc4 = to_vc4_dev(shader_obj->base.dev);
bool found_shader_end = false;
int shader_end_ip = 0;
uint32_t last_thread_switch_ip = -3;
uint32_t ip;
struct vc4_validated_shader_info *validated_shader = NULL;
struct vc4_shader_validation_state validation_state;
if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
return NULL;
memset(&validation_state, 0, sizeof(validation_state));
validation_state.shader = shader_obj->vaddr;
validation_state.max_ip = shader_obj->base.size / sizeof(uint64_t);
reset_validation_state(&validation_state);
validation_state.branch_targets =
kcalloc(BITS_TO_LONGS(validation_state.max_ip),
sizeof(unsigned long), GFP_KERNEL);
if (!validation_state.branch_targets)
goto fail;
validated_shader = kzalloc_objs(*validated_shader, 1);
if (!validated_shader)
goto fail;
if (!vc4_validate_branches(&validation_state))
goto fail;
for (ip = 0; ip < validation_state.max_ip; ip++) {
uint64_t inst = validation_state.shader[ip];
uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
validation_state.ip = ip;
if (!vc4_handle_branch_target(&validation_state))
goto fail;
if (ip == last_thread_switch_ip + 3) {
/* Reset r0-r3 live clamp data */
int i;
for (i = 64; i < LIVE_REG_COUNT; i++) {
validation_state.live_min_clamp_offsets[i] = ~0;
validation_state.live_max_clamp_regs[i] = false;
validation_state.live_immediates[i] = ~0;
}
}
switch (sig) {
case QPU_SIG_NONE:
case QPU_SIG_WAIT_FOR_SCOREBOARD:
case QPU_SIG_SCOREBOARD_UNLOCK:
case QPU_SIG_COLOR_LOAD:
case QPU_SIG_LOAD_TMU0:
case QPU_SIG_LOAD_TMU1:
case QPU_SIG_PROG_END:
case QPU_SIG_SMALL_IMM:
case QPU_SIG_THREAD_SWITCH:
case QPU_SIG_LAST_THREAD_SWITCH:
if (!check_instruction_writes(validated_shader,
&validation_state)) {
DRM_DEBUG("Bad write at ip %d\n", ip);
goto fail;
}
if (!check_instruction_reads(validated_shader,
&validation_state))
goto fail;
if (sig == QPU_SIG_PROG_END) {
found_shader_end = true;
shader_end_ip = ip;
}
if (sig == QPU_SIG_THREAD_SWITCH ||
sig == QPU_SIG_LAST_THREAD_SWITCH) {
validated_shader->is_threaded = true;
if (ip < last_thread_switch_ip + 3) {
DRM_DEBUG("Thread switch too soon after "
"last switch at ip %d\n", ip);
goto fail;
}
last_thread_switch_ip = ip;
}
break;
case QPU_SIG_LOAD_IMM:
if (!check_instruction_writes(validated_shader,
&validation_state)) {
DRM_DEBUG("Bad LOAD_IMM write at ip %d\n", ip);
goto fail;
}
break;
case QPU_SIG_BRANCH:
if (!check_branch(inst, validated_shader,
&validation_state, ip))
goto fail;
if (ip < last_thread_switch_ip + 3) {
DRM_DEBUG("Branch in thread switch at ip %d",
ip);
goto fail;
}
break;
default:
DRM_DEBUG("Unsupported QPU signal %d at "
"instruction %d\n", sig, ip);
goto fail;
}
/* There are two delay slots after program end is signaled
* that are still executed, then we're finished.
*/
if (found_shader_end && ip == shader_end_ip + 2)
break;
}
if (ip == validation_state.max_ip) {
DRM_DEBUG("shader failed to terminate before "
"shader BO end at %zd\n",
shader_obj->base.size);
goto fail;
}
/* Might corrupt other thread */
if (validated_shader->is_threaded &&
validation_state.all_registers_used) {
DRM_DEBUG("Shader uses threading, but uses the upper "
"half of the registers, too\n");
goto fail;
}
/* If we did a backwards branch and we haven't emitted a uniforms
* reset since then, we still need the uniforms stream to have the
* uniforms address available so that the backwards branch can do its
* uniforms reset.
*
* We could potentially prove that the backwards branch doesn't
* contain any uses of uniforms until program exit, but that doesn't
* seem to be worth the trouble.
*/
if (validation_state.needs_uniform_address_for_loop) {
if (!require_uniform_address_uniform(validated_shader))
goto fail;
validated_shader->uniforms_size += 4;
}
/* Again, no chance of integer overflow here because the worst case
* scenario is 8 bytes of uniforms plus handles per 8-byte
* instruction.
*/
validated_shader->uniforms_src_size =
(validated_shader->uniforms_size +
4 * validated_shader->num_texture_samples);
kfree(validation_state.branch_targets);
return validated_shader;
fail:
kfree(validation_state.branch_targets);
if (validated_shader) {
kfree(validated_shader->uniform_addr_offsets);
kfree(validated_shader->texture_samples);
kfree(validated_shader);
}
return NULL;
}
+849
View File
@@ -0,0 +1,849 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2016 Broadcom
*/
/**
* DOC: VC4 SDTV module
*
* The VEC encoder generates PAL or NTSC composite video output.
*
* TV mode selection is done by an atomic property on the encoder,
* because a drm_mode_modeinfo is insufficient to distinguish between
* PAL and PAL-M or NTSC and NTSC-J.
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "vc4_drv.h"
#include "vc4_regs.h"
/* WSE Registers */
#define VEC_WSE_RESET 0xc0
#define VEC_WSE_CONTROL 0xc4
#define VEC_WSE_WSS_ENABLE BIT(7)
#define VEC_WSE_WSS_DATA 0xc8
#define VEC_WSE_VPS_DATA1 0xcc
#define VEC_WSE_VPS_CONTROL 0xd0
/* VEC Registers */
#define VEC_REVID 0x100
#define VEC_CONFIG0 0x104
#define VEC_CONFIG0_YDEL_MASK GENMASK(28, 26)
#define VEC_CONFIG0_YDEL(x) ((x) << 26)
#define VEC_CONFIG0_CDEL_MASK GENMASK(25, 24)
#define VEC_CONFIG0_CDEL(x) ((x) << 24)
#define VEC_CONFIG0_SECAM_STD BIT(21)
#define VEC_CONFIG0_PBPR_FIL BIT(18)
#define VEC_CONFIG0_CHROMA_GAIN_MASK GENMASK(17, 16)
#define VEC_CONFIG0_CHROMA_GAIN_UNITY (0 << 16)
#define VEC_CONFIG0_CHROMA_GAIN_1_32 (1 << 16)
#define VEC_CONFIG0_CHROMA_GAIN_1_16 (2 << 16)
#define VEC_CONFIG0_CHROMA_GAIN_1_8 (3 << 16)
#define VEC_CONFIG0_CBURST_GAIN_MASK GENMASK(14, 13)
#define VEC_CONFIG0_CBURST_GAIN_UNITY (0 << 13)
#define VEC_CONFIG0_CBURST_GAIN_1_128 (1 << 13)
#define VEC_CONFIG0_CBURST_GAIN_1_64 (2 << 13)
#define VEC_CONFIG0_CBURST_GAIN_1_32 (3 << 13)
#define VEC_CONFIG0_CHRBW1 BIT(11)
#define VEC_CONFIG0_CHRBW0 BIT(10)
#define VEC_CONFIG0_SYNCDIS BIT(9)
#define VEC_CONFIG0_BURDIS BIT(8)
#define VEC_CONFIG0_CHRDIS BIT(7)
#define VEC_CONFIG0_PDEN BIT(6)
#define VEC_CONFIG0_YCDELAY BIT(4)
#define VEC_CONFIG0_RAMPEN BIT(2)
#define VEC_CONFIG0_YCDIS BIT(2)
#define VEC_CONFIG0_STD_MASK GENMASK(1, 0)
#define VEC_CONFIG0_NTSC_STD 0
#define VEC_CONFIG0_PAL_BDGHI_STD 1
#define VEC_CONFIG0_PAL_M_STD 2
#define VEC_CONFIG0_PAL_N_STD 3
#define VEC_SCHPH 0x108
#define VEC_SOFT_RESET 0x10c
#define VEC_CLMP0_START 0x144
#define VEC_CLMP0_END 0x148
/*
* These set the color subcarrier frequency
* if VEC_CONFIG1_CUSTOM_FREQ is enabled.
*
* VEC_FREQ1_0 contains the most significant 16-bit half-word,
* VEC_FREQ3_2 contains the least significant 16-bit half-word.
* 0x80000000 seems to be equivalent to the pixel clock
* (which itself is the VEC clock divided by 8).
*
* Reference values (with the default pixel clock of 13.5 MHz):
*
* NTSC (3579545.[45] Hz) - 0x21F07C1F
* PAL (4433618.75 Hz) - 0x2A098ACB
* PAL-M (3575611.[888111] Hz) - 0x21E6EFE3
* PAL-N (3582056.25 Hz) - 0x21F69446
*
* NOTE: For SECAM, it is used as the Dr center frequency,
* regardless of whether VEC_CONFIG1_CUSTOM_FREQ is enabled or not;
* that is specified as 4406250 Hz, which corresponds to 0x29C71C72.
*/
#define VEC_FREQ3_2 0x180
#define VEC_FREQ1_0 0x184
#define VEC_CONFIG1 0x188
#define VEC_CONFIG_VEC_RESYNC_OFF BIT(18)
#define VEC_CONFIG_RGB219 BIT(17)
#define VEC_CONFIG_CBAR_EN BIT(16)
#define VEC_CONFIG_TC_OBB BIT(15)
#define VEC_CONFIG1_OUTPUT_MODE_MASK GENMASK(12, 10)
#define VEC_CONFIG1_C_Y_CVBS (0 << 10)
#define VEC_CONFIG1_CVBS_Y_C (1 << 10)
#define VEC_CONFIG1_PR_Y_PB (2 << 10)
#define VEC_CONFIG1_RGB (4 << 10)
#define VEC_CONFIG1_Y_C_CVBS (5 << 10)
#define VEC_CONFIG1_C_CVBS_Y (6 << 10)
#define VEC_CONFIG1_C_CVBS_CVBS (7 << 10)
#define VEC_CONFIG1_DIS_CHR BIT(9)
#define VEC_CONFIG1_DIS_LUMA BIT(8)
#define VEC_CONFIG1_YCBCR_IN BIT(6)
#define VEC_CONFIG1_DITHER_TYPE_LFSR 0
#define VEC_CONFIG1_DITHER_TYPE_COUNTER BIT(5)
#define VEC_CONFIG1_DITHER_EN BIT(4)
#define VEC_CONFIG1_CYDELAY BIT(3)
#define VEC_CONFIG1_LUMADIS BIT(2)
#define VEC_CONFIG1_COMPDIS BIT(1)
#define VEC_CONFIG1_CUSTOM_FREQ BIT(0)
#define VEC_CONFIG2 0x18c
#define VEC_CONFIG2_PROG_SCAN BIT(15)
#define VEC_CONFIG2_SYNC_ADJ_MASK GENMASK(14, 12)
#define VEC_CONFIG2_SYNC_ADJ(x) (((x) / 2) << 12)
#define VEC_CONFIG2_PBPR_EN BIT(10)
#define VEC_CONFIG2_UV_DIG_DIS BIT(6)
#define VEC_CONFIG2_RGB_DIG_DIS BIT(5)
#define VEC_CONFIG2_TMUX_MASK GENMASK(3, 2)
#define VEC_CONFIG2_TMUX_DRIVE0 (0 << 2)
#define VEC_CONFIG2_TMUX_RG_COMP (1 << 2)
#define VEC_CONFIG2_TMUX_UV_YC (2 << 2)
#define VEC_CONFIG2_TMUX_SYNC_YC (3 << 2)
#define VEC_INTERRUPT_CONTROL 0x190
#define VEC_INTERRUPT_STATUS 0x194
/*
* Db center frequency for SECAM; the clock for this is the same as for
* VEC_FREQ3_2/VEC_FREQ1_0, which is used for Dr center frequency.
*
* This is specified as 4250000 Hz, which corresponds to 0x284BDA13.
* That is also the default value, so no need to set it explicitly.
*/
#define VEC_FCW_SECAM_B 0x198
#define VEC_SECAM_GAIN_VAL 0x19c
#define VEC_CONFIG3 0x1a0
#define VEC_CONFIG3_HORIZ_LEN_STD (0 << 0)
#define VEC_CONFIG3_HORIZ_LEN_MPEG1_SIF (1 << 0)
#define VEC_CONFIG3_SHAPE_NON_LINEAR BIT(1)
#define VEC_STATUS0 0x200
#define VEC_MASK0 0x204
#define VEC_CFG 0x208
#define VEC_CFG_SG_MODE_MASK GENMASK(6, 5)
#define VEC_CFG_SG_MODE(x) ((x) << 5)
#define VEC_CFG_SG_EN BIT(4)
#define VEC_CFG_VEC_EN BIT(3)
#define VEC_CFG_MB_EN BIT(2)
#define VEC_CFG_ENABLE BIT(1)
#define VEC_CFG_TB_EN BIT(0)
#define VEC_DAC_TEST 0x20c
#define VEC_DAC_CONFIG 0x210
#define VEC_DAC_CONFIG_LDO_BIAS_CTRL(x) ((x) << 24)
#define VEC_DAC_CONFIG_DRIVER_CTRL(x) ((x) << 16)
#define VEC_DAC_CONFIG_DAC_CTRL(x) (x)
#define VEC_DAC_MISC 0x214
#define VEC_DAC_MISC_VCD_CTRL_MASK GENMASK(31, 16)
#define VEC_DAC_MISC_VCD_CTRL(x) ((x) << 16)
#define VEC_DAC_MISC_VID_ACT BIT(8)
#define VEC_DAC_MISC_VCD_PWRDN BIT(6)
#define VEC_DAC_MISC_BIAS_PWRDN BIT(5)
#define VEC_DAC_MISC_DAC_PWRDN BIT(2)
#define VEC_DAC_MISC_LDO_PWRDN BIT(1)
#define VEC_DAC_MISC_DAC_RST_N BIT(0)
struct vc4_vec_variant {
u32 dac_config;
};
/* General VEC hardware state. */
struct vc4_vec {
struct vc4_encoder encoder;
struct drm_connector connector;
struct platform_device *pdev;
const struct vc4_vec_variant *variant;
void __iomem *regs;
struct clk *clock;
struct drm_property *legacy_tv_mode_property;
struct debugfs_regset32 regset;
};
#define VEC_READ(offset) \
({ \
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
readl(vec->regs + (offset)); \
})
#define VEC_WRITE(offset, val) \
do { \
kunit_fail_current_test("Accessing a register in a unit test!\n"); \
writel(val, vec->regs + (offset)); \
} while (0)
#define encoder_to_vc4_vec(_encoder) \
container_of_const(_encoder, struct vc4_vec, encoder.base)
#define connector_to_vc4_vec(_connector) \
container_of_const(_connector, struct vc4_vec, connector)
enum vc4_vec_tv_mode_id {
VC4_VEC_TV_MODE_NTSC,
VC4_VEC_TV_MODE_NTSC_J,
VC4_VEC_TV_MODE_PAL,
VC4_VEC_TV_MODE_PAL_M,
VC4_VEC_TV_MODE_NTSC_443,
VC4_VEC_TV_MODE_PAL_60,
VC4_VEC_TV_MODE_PAL_N,
VC4_VEC_TV_MODE_SECAM,
VC4_VEC_TV_MODE_MONOCHROME,
};
struct vc4_vec_tv_mode {
unsigned int mode;
u16 expected_htotal;
u32 config0;
u32 config1;
u32 custom_freq;
};
static const struct debugfs_reg32 vec_regs[] = {
VC4_REG32(VEC_WSE_CONTROL),
VC4_REG32(VEC_WSE_WSS_DATA),
VC4_REG32(VEC_WSE_VPS_DATA1),
VC4_REG32(VEC_WSE_VPS_CONTROL),
VC4_REG32(VEC_REVID),
VC4_REG32(VEC_CONFIG0),
VC4_REG32(VEC_SCHPH),
VC4_REG32(VEC_CLMP0_START),
VC4_REG32(VEC_CLMP0_END),
VC4_REG32(VEC_FREQ3_2),
VC4_REG32(VEC_FREQ1_0),
VC4_REG32(VEC_CONFIG1),
VC4_REG32(VEC_CONFIG2),
VC4_REG32(VEC_INTERRUPT_CONTROL),
VC4_REG32(VEC_INTERRUPT_STATUS),
VC4_REG32(VEC_FCW_SECAM_B),
VC4_REG32(VEC_SECAM_GAIN_VAL),
VC4_REG32(VEC_CONFIG3),
VC4_REG32(VEC_STATUS0),
VC4_REG32(VEC_MASK0),
VC4_REG32(VEC_CFG),
VC4_REG32(VEC_DAC_TEST),
VC4_REG32(VEC_DAC_CONFIG),
VC4_REG32(VEC_DAC_MISC),
};
static const struct vc4_vec_tv_mode vc4_vec_tv_modes[] = {
{
.mode = DRM_MODE_TV_MODE_NTSC,
.expected_htotal = 858,
.config0 = VEC_CONFIG0_NTSC_STD | VEC_CONFIG0_PDEN,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
},
{
.mode = DRM_MODE_TV_MODE_NTSC_443,
.expected_htotal = 858,
.config0 = VEC_CONFIG0_NTSC_STD,
.config1 = VEC_CONFIG1_C_CVBS_CVBS | VEC_CONFIG1_CUSTOM_FREQ,
.custom_freq = 0x2a098acb,
},
{
.mode = DRM_MODE_TV_MODE_NTSC_J,
.expected_htotal = 858,
.config0 = VEC_CONFIG0_NTSC_STD,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
},
{
.mode = DRM_MODE_TV_MODE_PAL,
.expected_htotal = 864,
.config0 = VEC_CONFIG0_PAL_BDGHI_STD,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
},
{
/* PAL-60 */
.mode = DRM_MODE_TV_MODE_PAL,
.expected_htotal = 858,
.config0 = VEC_CONFIG0_PAL_M_STD,
.config1 = VEC_CONFIG1_C_CVBS_CVBS | VEC_CONFIG1_CUSTOM_FREQ,
.custom_freq = 0x2a098acb,
},
{
.mode = DRM_MODE_TV_MODE_PAL_M,
.expected_htotal = 858,
.config0 = VEC_CONFIG0_PAL_M_STD,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
},
{
.mode = DRM_MODE_TV_MODE_PAL_N,
.expected_htotal = 864,
.config0 = VEC_CONFIG0_PAL_N_STD,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
},
{
.mode = DRM_MODE_TV_MODE_SECAM,
.expected_htotal = 864,
.config0 = VEC_CONFIG0_SECAM_STD,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
.custom_freq = 0x29c71c72,
},
{
/* 50Hz mono */
.mode = DRM_MODE_TV_MODE_MONOCHROME,
.expected_htotal = 864,
.config0 = VEC_CONFIG0_PAL_BDGHI_STD | VEC_CONFIG0_BURDIS |
VEC_CONFIG0_CHRDIS,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
},
{
/* 60Hz mono */
.mode = DRM_MODE_TV_MODE_MONOCHROME,
.expected_htotal = 858,
.config0 = VEC_CONFIG0_PAL_M_STD | VEC_CONFIG0_BURDIS |
VEC_CONFIG0_CHRDIS,
.config1 = VEC_CONFIG1_C_CVBS_CVBS,
},
};
static inline const struct vc4_vec_tv_mode *
vc4_vec_tv_mode_lookup(unsigned int mode, u16 htotal)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(vc4_vec_tv_modes); i++) {
const struct vc4_vec_tv_mode *tv_mode = &vc4_vec_tv_modes[i];
if (tv_mode->mode == mode &&
tv_mode->expected_htotal == htotal)
return tv_mode;
}
return NULL;
}
static const struct drm_prop_enum_list legacy_tv_mode_names[] = {
{ VC4_VEC_TV_MODE_NTSC, "NTSC", },
{ VC4_VEC_TV_MODE_NTSC_443, "NTSC-443", },
{ VC4_VEC_TV_MODE_NTSC_J, "NTSC-J", },
{ VC4_VEC_TV_MODE_PAL, "PAL", },
{ VC4_VEC_TV_MODE_PAL_60, "PAL-60", },
{ VC4_VEC_TV_MODE_PAL_M, "PAL-M", },
{ VC4_VEC_TV_MODE_PAL_N, "PAL-N", },
{ VC4_VEC_TV_MODE_SECAM, "SECAM", },
{ VC4_VEC_TV_MODE_MONOCHROME, "Mono", },
};
static enum drm_connector_status
vc4_vec_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_unknown;
}
static void vc4_vec_connector_reset(struct drm_connector *connector)
{
drm_atomic_helper_connector_reset(connector);
drm_atomic_helper_connector_tv_reset(connector);
}
static int
vc4_vec_connector_set_property(struct drm_connector *connector,
struct drm_connector_state *state,
struct drm_property *property,
uint64_t val)
{
struct vc4_vec *vec = connector_to_vc4_vec(connector);
if (property != vec->legacy_tv_mode_property)
return -EINVAL;
switch (val) {
case VC4_VEC_TV_MODE_NTSC:
state->tv.mode = DRM_MODE_TV_MODE_NTSC;
break;
case VC4_VEC_TV_MODE_NTSC_443:
state->tv.mode = DRM_MODE_TV_MODE_NTSC_443;
break;
case VC4_VEC_TV_MODE_NTSC_J:
state->tv.mode = DRM_MODE_TV_MODE_NTSC_J;
break;
case VC4_VEC_TV_MODE_PAL:
case VC4_VEC_TV_MODE_PAL_60:
state->tv.mode = DRM_MODE_TV_MODE_PAL;
break;
case VC4_VEC_TV_MODE_PAL_M:
state->tv.mode = DRM_MODE_TV_MODE_PAL_M;
break;
case VC4_VEC_TV_MODE_PAL_N:
state->tv.mode = DRM_MODE_TV_MODE_PAL_N;
break;
case VC4_VEC_TV_MODE_SECAM:
state->tv.mode = DRM_MODE_TV_MODE_SECAM;
break;
case VC4_VEC_TV_MODE_MONOCHROME:
state->tv.mode = DRM_MODE_TV_MODE_MONOCHROME;
break;
default:
return -EINVAL;
}
return 0;
}
static int
vc4_vec_connector_get_property(struct drm_connector *connector,
const struct drm_connector_state *state,
struct drm_property *property,
uint64_t *val)
{
struct vc4_vec *vec = connector_to_vc4_vec(connector);
if (property != vec->legacy_tv_mode_property)
return -EINVAL;
switch (state->tv.mode) {
case DRM_MODE_TV_MODE_NTSC:
*val = VC4_VEC_TV_MODE_NTSC;
break;
case DRM_MODE_TV_MODE_NTSC_443:
*val = VC4_VEC_TV_MODE_NTSC_443;
break;
case DRM_MODE_TV_MODE_NTSC_J:
*val = VC4_VEC_TV_MODE_NTSC_J;
break;
case DRM_MODE_TV_MODE_PAL:
*val = VC4_VEC_TV_MODE_PAL;
break;
case DRM_MODE_TV_MODE_PAL_M:
*val = VC4_VEC_TV_MODE_PAL_M;
break;
case DRM_MODE_TV_MODE_PAL_N:
*val = VC4_VEC_TV_MODE_PAL_N;
break;
case DRM_MODE_TV_MODE_SECAM:
*val = VC4_VEC_TV_MODE_SECAM;
break;
case DRM_MODE_TV_MODE_MONOCHROME:
*val = VC4_VEC_TV_MODE_MONOCHROME;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct drm_connector_funcs vc4_vec_connector_funcs = {
.detect = vc4_vec_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.reset = vc4_vec_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.atomic_get_property = vc4_vec_connector_get_property,
.atomic_set_property = vc4_vec_connector_set_property,
};
static const struct drm_connector_helper_funcs vc4_vec_connector_helper_funcs = {
.atomic_check = drm_atomic_helper_connector_tv_check,
.get_modes = drm_connector_helper_tv_get_modes,
};
static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec)
{
struct drm_connector *connector = &vec->connector;
struct drm_property *prop;
int ret;
connector->interlace_allowed = true;
ret = drmm_connector_init(dev, connector, &vc4_vec_connector_funcs,
DRM_MODE_CONNECTOR_Composite, NULL);
if (ret)
return ret;
drm_connector_helper_add(connector, &vc4_vec_connector_helper_funcs);
drm_object_attach_property(&connector->base,
dev->mode_config.tv_mode_property,
DRM_MODE_TV_MODE_NTSC);
prop = drm_property_create_enum(dev, 0, "mode",
legacy_tv_mode_names,
ARRAY_SIZE(legacy_tv_mode_names));
if (!prop)
return -ENOMEM;
vec->legacy_tv_mode_property = prop;
drm_object_attach_property(&connector->base, prop, VC4_VEC_TV_MODE_NTSC);
drm_connector_attach_tv_margin_properties(connector);
drm_connector_attach_encoder(connector, &vec->encoder.base);
return 0;
}
static void vc4_vec_encoder_disable(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
struct drm_device *drm = encoder->dev;
struct vc4_vec *vec = encoder_to_vc4_vec(encoder);
int idx;
if (!drm_dev_enter(drm, &idx))
return;
VEC_WRITE(VEC_CFG, 0);
VEC_WRITE(VEC_DAC_MISC,
VEC_DAC_MISC_VCD_PWRDN |
VEC_DAC_MISC_BIAS_PWRDN |
VEC_DAC_MISC_DAC_PWRDN |
VEC_DAC_MISC_LDO_PWRDN);
clk_disable_unprepare(vec->clock);
pm_runtime_put(&vec->pdev->dev);
drm_dev_exit(idx);
}
static void vc4_vec_encoder_enable(struct drm_encoder *encoder,
struct drm_atomic_commit *state)
{
struct drm_device *drm = encoder->dev;
struct vc4_vec *vec = encoder_to_vc4_vec(encoder);
struct drm_connector *connector = &vec->connector;
struct drm_connector_state *conn_state =
drm_atomic_get_new_connector_state(state, connector);
struct drm_display_mode *adjusted_mode =
&encoder->crtc->state->adjusted_mode;
const struct vc4_vec_tv_mode *tv_mode;
int idx, ret;
if (!drm_dev_enter(drm, &idx))
return;
tv_mode = vc4_vec_tv_mode_lookup(conn_state->tv.mode,
adjusted_mode->htotal);
if (!tv_mode)
goto err_dev_exit;
ret = pm_runtime_resume_and_get(&vec->pdev->dev);
if (ret < 0) {
drm_err(drm, "Failed to retain power domain: %d\n", ret);
goto err_dev_exit;
}
/*
* We need to set the clock rate each time we enable the encoder
* because there's a chance we share the same parent with the HDMI
* clock, and both drivers are requesting different rates.
* The good news is, these 2 encoders cannot be enabled at the same
* time, thus preventing incompatible rate requests.
*/
ret = clk_set_rate(vec->clock, 108000000);
if (ret) {
drm_err(drm, "Failed to set clock rate: %d\n", ret);
goto err_put_runtime_pm;
}
ret = clk_prepare_enable(vec->clock);
if (ret) {
drm_err(drm, "Failed to turn on core clock: %d\n", ret);
goto err_put_runtime_pm;
}
/* Reset the different blocks */
VEC_WRITE(VEC_WSE_RESET, 1);
VEC_WRITE(VEC_SOFT_RESET, 1);
/* Disable the CGSM-A and WSE blocks */
VEC_WRITE(VEC_WSE_CONTROL, 0);
/* Write config common to all modes. */
/*
* Color subcarrier phase: phase = 360 * SCHPH / 256.
* 0x28 <=> 39.375 deg.
*/
VEC_WRITE(VEC_SCHPH, 0x28);
/*
* Reset to default values.
*/
VEC_WRITE(VEC_CLMP0_START, 0xac);
VEC_WRITE(VEC_CLMP0_END, 0xec);
VEC_WRITE(VEC_CONFIG2,
VEC_CONFIG2_UV_DIG_DIS |
VEC_CONFIG2_RGB_DIG_DIS |
((adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) ? 0 : VEC_CONFIG2_PROG_SCAN));
VEC_WRITE(VEC_CONFIG3, VEC_CONFIG3_HORIZ_LEN_STD);
VEC_WRITE(VEC_DAC_CONFIG, vec->variant->dac_config);
/* Mask all interrupts. */
VEC_WRITE(VEC_MASK0, 0);
VEC_WRITE(VEC_CONFIG0, tv_mode->config0);
VEC_WRITE(VEC_CONFIG1, tv_mode->config1);
if (tv_mode->custom_freq) {
VEC_WRITE(VEC_FREQ3_2,
(tv_mode->custom_freq >> 16) & 0xffff);
VEC_WRITE(VEC_FREQ1_0,
tv_mode->custom_freq & 0xffff);
}
VEC_WRITE(VEC_DAC_MISC,
VEC_DAC_MISC_VID_ACT | VEC_DAC_MISC_DAC_RST_N);
VEC_WRITE(VEC_CFG, VEC_CFG_VEC_EN);
drm_dev_exit(idx);
return;
err_put_runtime_pm:
pm_runtime_put(&vec->pdev->dev);
err_dev_exit:
drm_dev_exit(idx);
}
static int vc4_vec_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
const struct vc4_vec_tv_mode *tv_mode;
tv_mode = vc4_vec_tv_mode_lookup(conn_state->tv.mode, mode->htotal);
if (!tv_mode)
return -EINVAL;
if (mode->crtc_hdisplay % 4)
return -EINVAL;
if (!(mode->crtc_hsync_end - mode->crtc_hsync_start))
return -EINVAL;
switch (mode->htotal) {
/* NTSC */
case 858:
if (mode->crtc_vtotal > 262)
return -EINVAL;
if (mode->crtc_vdisplay < 1 || mode->crtc_vdisplay > 253)
return -EINVAL;
if (!(mode->crtc_vsync_start - mode->crtc_vdisplay))
return -EINVAL;
if ((mode->crtc_vsync_end - mode->crtc_vsync_start) != 3)
return -EINVAL;
if ((mode->crtc_vtotal - mode->crtc_vsync_end) < 4)
return -EINVAL;
break;
/* PAL/SECAM */
case 864:
if (mode->crtc_vtotal > 312)
return -EINVAL;
if (mode->crtc_vdisplay < 1 || mode->crtc_vdisplay > 305)
return -EINVAL;
if (!(mode->crtc_vsync_start - mode->crtc_vdisplay))
return -EINVAL;
if ((mode->crtc_vsync_end - mode->crtc_vsync_start) != 3)
return -EINVAL;
if ((mode->crtc_vtotal - mode->crtc_vsync_end) < 2)
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct drm_encoder_helper_funcs vc4_vec_encoder_helper_funcs = {
.atomic_check = vc4_vec_encoder_atomic_check,
.atomic_disable = vc4_vec_encoder_disable,
.atomic_enable = vc4_vec_encoder_enable,
};
static int vc4_vec_late_register(struct drm_encoder *encoder)
{
struct drm_device *drm = encoder->dev;
struct vc4_vec *vec = encoder_to_vc4_vec(encoder);
vc4_debugfs_add_regset32(drm, "vec_regs", &vec->regset);
return 0;
}
static const struct drm_encoder_funcs vc4_vec_encoder_funcs = {
.late_register = vc4_vec_late_register,
};
static const struct vc4_vec_variant bcm2835_vec_variant = {
.dac_config = VEC_DAC_CONFIG_DAC_CTRL(0xc) |
VEC_DAC_CONFIG_DRIVER_CTRL(0xc) |
VEC_DAC_CONFIG_LDO_BIAS_CTRL(0x46)
};
static const struct vc4_vec_variant bcm2711_vec_variant = {
.dac_config = VEC_DAC_CONFIG_DAC_CTRL(0x0) |
VEC_DAC_CONFIG_DRIVER_CTRL(0x80) |
VEC_DAC_CONFIG_LDO_BIAS_CTRL(0x61)
};
static const struct of_device_id vc4_vec_dt_match[] = {
{ .compatible = "brcm,bcm2835-vec", .data = &bcm2835_vec_variant },
{ .compatible = "brcm,bcm2711-vec", .data = &bcm2711_vec_variant },
{ /* sentinel */ },
};
static int vc4_vec_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_vec *vec;
int ret;
ret = drm_mode_create_tv_properties(drm,
BIT(DRM_MODE_TV_MODE_NTSC) |
BIT(DRM_MODE_TV_MODE_NTSC_443) |
BIT(DRM_MODE_TV_MODE_NTSC_J) |
BIT(DRM_MODE_TV_MODE_PAL) |
BIT(DRM_MODE_TV_MODE_PAL_M) |
BIT(DRM_MODE_TV_MODE_PAL_N) |
BIT(DRM_MODE_TV_MODE_SECAM) |
BIT(DRM_MODE_TV_MODE_MONOCHROME));
if (ret)
return ret;
vec = drmm_kzalloc(drm, sizeof(*vec), GFP_KERNEL);
if (!vec)
return -ENOMEM;
vec->encoder.type = VC4_ENCODER_TYPE_VEC;
vec->pdev = pdev;
vec->variant = (const struct vc4_vec_variant *)
of_device_get_match_data(dev);
vec->regs = vc4_ioremap_regs(pdev, 0);
if (IS_ERR(vec->regs))
return PTR_ERR(vec->regs);
vec->regset.base = vec->regs;
vec->regset.regs = vec_regs;
vec->regset.nregs = ARRAY_SIZE(vec_regs);
vec->clock = devm_clk_get(dev, NULL);
if (IS_ERR(vec->clock)) {
ret = PTR_ERR(vec->clock);
if (ret != -EPROBE_DEFER)
drm_err(drm, "Failed to get clock: %d\n", ret);
return ret;
}
ret = devm_pm_runtime_enable(dev);
if (ret)
return ret;
ret = drmm_encoder_init(drm, &vec->encoder.base,
&vc4_vec_encoder_funcs,
DRM_MODE_ENCODER_TVDAC,
NULL);
if (ret)
return ret;
drm_encoder_helper_add(&vec->encoder.base, &vc4_vec_encoder_helper_funcs);
ret = vc4_vec_connector_init(drm, vec);
if (ret)
return ret;
dev_set_drvdata(dev, vec);
return 0;
}
static const struct component_ops vc4_vec_ops = {
.bind = vc4_vec_bind,
};
static int vc4_vec_dev_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &vc4_vec_ops);
}
static void vc4_vec_dev_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &vc4_vec_ops);
}
struct platform_driver vc4_vec_driver = {
.probe = vc4_vec_dev_probe,
.remove = vc4_vec_dev_remove,
.driver = {
.name = "vc4_vec",
.of_match_table = vc4_vec_dt_match,
},
};