|
|
|
@ -1,10 +1,11 @@
|
|
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
|
/*
|
|
|
|
|
* Copyright © 2018 Broadcom |
|
|
|
|
* |
|
|
|
|
* Copyright (C) 2018 Broadcom |
|
|
|
|
* Copyright (C) 2022 Qortal Project |
|
|
|
|
* Authors: |
|
|
|
|
* Eric Anholt <eric@anholt.net> |
|
|
|
|
* Boris Brezillon <boris.brezillon@bootlin.com> |
|
|
|
|
* Scare Crowe <dmax@crowetic.com> |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
#include <linux/clk.h> |
|
|
|
@ -15,6 +16,7 @@
|
|
|
|
|
|
|
|
|
|
#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_cma_helper.h> |
|
|
|
|
#include <drm/drm_fourcc.h> |
|
|
|
@ -154,7 +156,6 @@ struct vc4_txp {
|
|
|
|
|
struct drm_writeback_connector connector; |
|
|
|
|
|
|
|
|
|
void __iomem *regs; |
|
|
|
|
struct debugfs_regset32 regset; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static inline struct vc4_txp *encoder_to_vc4_txp(struct drm_encoder *encoder) |
|
|
|
@ -275,6 +276,7 @@ static int vc4_txp_connector_atomic_check(struct drm_connector *conn,
|
|
|
|
|
static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, |
|
|
|
|
struct drm_atomic_state *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); |
|
|
|
@ -282,6 +284,7 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
|
|
|
|
|
struct drm_display_mode *mode; |
|
|
|
|
struct drm_framebuffer *fb; |
|
|
|
|
u32 ctrl; |
|
|
|
|
int idx; |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
if (WARN_ON(!conn_state->writeback_job)) |
|
|
|
@ -305,8 +308,15 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
|
|
|
|
|
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_cma_get_gem_obj(fb, 0); |
|
|
|
|
TXP_WRITE(TXP_DST_PTR, gem->paddr + fb->offsets[0]); |
|
|
|
|
TXP_WRITE(TXP_DST_PITCH, fb->pitches[0]); |
|
|
|
@ -317,6 +327,8 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
|
|
|
|
|
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 = { |
|
|
|
@ -332,16 +344,10 @@ vc4_txp_connector_detect(struct drm_connector *connector, bool force)
|
|
|
|
|
return connector_status_connected; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void vc4_txp_connector_destroy(struct drm_connector *connector) |
|
|
|
|
{ |
|
|
|
|
drm_connector_unregister(connector); |
|
|
|
|
drm_connector_cleanup(connector); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static const struct drm_connector_funcs vc4_txp_connector_funcs = { |
|
|
|
|
.detect = vc4_txp_connector_detect, |
|
|
|
|
.fill_modes = drm_helper_probe_single_connector_modes, |
|
|
|
|
.destroy = vc4_txp_connector_destroy, |
|
|
|
|
.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, |
|
|
|
@ -349,7 +355,12 @@ static const struct drm_connector_funcs vc4_txp_connector_funcs = {
|
|
|
|
|
|
|
|
|
|
static void vc4_txp_encoder_disable(struct drm_encoder *encoder) |
|
|
|
|
{ |
|
|
|
|
struct drm_device *drm = encoder->dev; |
|
|
|
|
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); |
|
|
|
@ -364,6 +375,8 @@ static void vc4_txp_encoder_disable(struct drm_encoder *encoder)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN); |
|
|
|
|
|
|
|
|
|
drm_dev_exit(idx); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static const struct drm_encoder_helper_funcs vc4_txp_encoder_helper_funcs = { |
|
|
|
@ -379,13 +392,13 @@ 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, |
|
|
|
|
.destroy = vc4_crtc_destroy, |
|
|
|
|
.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, |
|
|
|
@ -448,6 +461,16 @@ 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); |
|
|
|
@ -456,6 +479,7 @@ static irqreturn_t vc4_txp_interrupt(int irq, void *data)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static const struct vc4_crtc_data vc4_txp_crtc_data = { |
|
|
|
|
.debugfs_name = "txp_regs", |
|
|
|
|
.hvs_available_channels = BIT(2), |
|
|
|
|
.hvs_output = 2, |
|
|
|
|
}; |
|
|
|
@ -464,7 +488,6 @@ 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); |
|
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(drm); |
|
|
|
|
struct vc4_crtc *vc4_crtc; |
|
|
|
|
struct vc4_txp *txp; |
|
|
|
|
struct drm_crtc *crtc; |
|
|
|
@ -475,7 +498,7 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
|
|
|
|
|
if (irq < 0) |
|
|
|
|
return irq; |
|
|
|
|
|
|
|
|
|
txp = devm_kzalloc(dev, sizeof(*txp), GFP_KERNEL); |
|
|
|
|
txp = drmm_kzalloc(drm, sizeof(*txp), GFP_KERNEL); |
|
|
|
|
if (!txp) |
|
|
|
|
return -ENOMEM; |
|
|
|
|
vc4_crtc = &txp->base; |
|
|
|
@ -490,9 +513,9 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
|
|
|
|
|
txp->regs = vc4_ioremap_regs(pdev, 0); |
|
|
|
|
if (IS_ERR(txp->regs)) |
|
|
|
|
return PTR_ERR(txp->regs); |
|
|
|
|
txp->regset.base = txp->regs; |
|
|
|
|
txp->regset.regs = txp_regs; |
|
|
|
|
txp->regset.nregs = ARRAY_SIZE(txp_regs); |
|
|
|
|
vc4_crtc->regset.base = txp->regs; |
|
|
|
|
vc4_crtc->regset.regs = txp_regs; |
|
|
|
|
vc4_crtc->regset.nregs = ARRAY_SIZE(txp_regs); |
|
|
|
|
|
|
|
|
|
drm_connector_helper_add(&txp->connector.base, |
|
|
|
|
&vc4_txp_connector_helper_funcs); |
|
|
|
@ -517,9 +540,6 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
|
|
|
|
|
return ret; |
|
|
|
|
|
|
|
|
|
dev_set_drvdata(dev, txp); |
|
|
|
|
vc4->txp = txp; |
|
|
|
|
|
|
|
|
|
vc4_debugfs_add_regset32(drm, "txp_regs", &txp->regset); |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
@ -527,13 +547,9 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
|
|
|
|
|
static void vc4_txp_unbind(struct device *dev, struct device *master, |
|
|
|
|
void *data) |
|
|
|
|
{ |
|
|
|
|
struct drm_device *drm = dev_get_drvdata(master); |
|
|
|
|
struct vc4_dev *vc4 = to_vc4_dev(drm); |
|
|
|
|
struct vc4_txp *txp = dev_get_drvdata(dev); |
|
|
|
|
|
|
|
|
|
vc4_txp_connector_destroy(&txp->connector.base); |
|
|
|
|
|
|
|
|
|
vc4->txp = NULL; |
|
|
|
|
drm_connector_cleanup(&txp->connector.base); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static const struct component_ops vc4_txp_ops = { |
|
|
|
|