forked from Minki/linux
Merge tag 'drm-misc-next-2017-10-20' of git://anongit.freedesktop.org/drm/drm-misc into drm-next
Final drm-misc feature pull for 4.15: UAPI Changes: - new madvise ioctl for vc4 (Boris) Core Changes: - plane commit tracking fixes (Maarten) - vgaarb improvements for fancy new platforms (aka ppc64 and arm64) by Bjorn Helgaas Driver Changes: - pile of new panel drivers: Toshiba LT089AC19000, Innolux AT043TN24 - more sun4i work to support A10/A20 Tcon and hdmi outputs - vc4: fix sleep in irq handler by making it threaded (Eric) - udl probe/edid read fixes (Robert Tarasov) And a bunch of misc small cleanups/refactors and doc fixes all over. * tag 'drm-misc-next-2017-10-20' of git://anongit.freedesktop.org/drm/drm-misc: (32 commits) drm/vc4: Fix sleeps during the IRQ handler for DSI transactions. drm/vc4: Add the DRM_IOCTL_VC4_GEM_MADVISE ioctl drm/panel: simple: add Toshiba LT089AC19000 dma-fence: remove duplicate word in comment drm/panel: simple: add delays for Innolux AT043TN24 drm/panel: simple: add bus flags for Innolux AT043TN24 drm/panel: simple: fix vertical timings for Innolux AT043TN24 drm/atomic-helper: check that drivers call drm_crtc_vblank_off drm: some KMS todo ideas vgaarb: Factor out EFI and fallback default device selection vgaarb: Select a default VGA device even if there's no legacy VGA drm/bridge: adv7511: Fix a use after free drm/sun4i: Add support for A20 display pipeline components drm/sun4i: Add support for A10 display pipeline components drm/sun4i: hdmi: Support HDMI controller on A10 drm/sun4i: tcon: Add support for A10 TCON drm/sun4i: backend: Support output muxing drm/sun4i: tcon: Move out the tcon0 common setup drm/sun4i: tcon: Don't rely on encoders to set the TCON mode drm/sun4i: tcon: Don't rely on encoders to enable the TCON ...
This commit is contained in:
commit
fef1aa48f4
@ -0,0 +1,8 @@
|
||||
Toshiba 8.9" WXGA (1280x768) TFT LCD panel
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "toshiba,lt089ac29000.txt"
|
||||
- power-supply: as specified in the base binding
|
||||
|
||||
This binding is compatible with the simple-panel binding, which is specified
|
||||
in simple-panel.txt in this directory.
|
@ -40,6 +40,7 @@ CEC. It is one end of the pipeline.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun4i-a10-hdmi
|
||||
* allwinner,sun5i-a10s-hdmi
|
||||
* allwinner,sun6i-a31-hdmi
|
||||
- reg: base address and size of memory-mapped region
|
||||
@ -86,9 +87,11 @@ The TCON acts as a timing controller for RGB, LVDS and TV interfaces.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be either:
|
||||
* allwinner,sun4i-a10-tcon
|
||||
* allwinner,sun5i-a13-tcon
|
||||
* allwinner,sun6i-a31-tcon
|
||||
* allwinner,sun6i-a31s-tcon
|
||||
* allwinner,sun7i-a20-tcon
|
||||
* allwinner,sun8i-a33-tcon
|
||||
* allwinner,sun8i-v3s-tcon
|
||||
- reg: base address and size of memory-mapped region
|
||||
@ -153,8 +156,10 @@ system.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun4i-a10-display-backend
|
||||
* allwinner,sun5i-a13-display-backend
|
||||
* allwinner,sun6i-a31-display-backend
|
||||
* allwinner,sun7i-a20-display-backend
|
||||
* allwinner,sun8i-a33-display-backend
|
||||
- reg: base address and size of the memory-mapped region.
|
||||
- interrupts: interrupt associated to this IP
|
||||
@ -185,8 +190,10 @@ deinterlacing and color space conversion.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun4i-a10-display-frontend
|
||||
* allwinner,sun5i-a13-display-frontend
|
||||
* allwinner,sun6i-a31-display-frontend
|
||||
* allwinner,sun7i-a20-display-frontend
|
||||
* allwinner,sun8i-a33-display-frontend
|
||||
- reg: base address and size of the memory-mapped region.
|
||||
- interrupts: interrupt associated to this IP
|
||||
@ -231,10 +238,12 @@ extra node.
|
||||
|
||||
Required properties:
|
||||
- compatible: value must be one of:
|
||||
* allwinner,sun4i-a10-display-engine
|
||||
* allwinner,sun5i-a10s-display-engine
|
||||
* allwinner,sun5i-a13-display-engine
|
||||
* allwinner,sun6i-a31-display-engine
|
||||
* allwinner,sun6i-a31s-display-engine
|
||||
* allwinner,sun7i-a20-display-engine
|
||||
* allwinner,sun8i-a33-display-engine
|
||||
* allwinner,sun8i-v3s-display-engine
|
||||
|
||||
|
@ -304,6 +304,18 @@ There's a bunch of issues with it:
|
||||
|
||||
Contact: Daniel Vetter
|
||||
|
||||
KMS cleanups
|
||||
------------
|
||||
|
||||
Some of these date from the very introduction of KMS in 2008 ...
|
||||
|
||||
- drm_mode_config.crtc_idr is misnamed, since it contains all KMS object. Should
|
||||
be renamed to drm_mode_config.object_idr.
|
||||
|
||||
- drm_display_mode doesn't need to be derived from drm_mode_object. That's
|
||||
leftovers from older (never merged into upstream) KMS designs where modes
|
||||
where set using their ID, including support to add/remove modes.
|
||||
|
||||
Better Testing
|
||||
==============
|
||||
|
||||
|
@ -1740,15 +1740,3 @@ static void fixup_hide_host_resource_fsl(struct pci_dev *dev)
|
||||
}
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_MOTOROLA, PCI_ANY_ID, fixup_hide_host_resource_fsl);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_FREESCALE, PCI_ANY_ID, fixup_hide_host_resource_fsl);
|
||||
|
||||
static void fixup_vga(struct pci_dev *pdev)
|
||||
{
|
||||
u16 cmd;
|
||||
|
||||
pci_read_config_word(pdev, PCI_COMMAND, &cmd);
|
||||
if ((cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) || !vga_default_device())
|
||||
vga_set_default_device(pdev);
|
||||
|
||||
}
|
||||
DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID,
|
||||
PCI_CLASS_DISPLAY_VGA, 8, fixup_vga);
|
||||
|
@ -298,7 +298,7 @@ static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
|
||||
|
||||
if (force) {
|
||||
/* Display is disabled, so just drop the old fb */
|
||||
drm_framebuffer_unreference(fb);
|
||||
drm_framebuffer_put(fb);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -321,7 +321,7 @@ static void armada_drm_crtc_finish_fb(struct armada_crtc *dcrtc,
|
||||
* the best. The worst that will happen is the buffer gets
|
||||
* reused before it has finished being displayed.
|
||||
*/
|
||||
drm_framebuffer_unreference(fb);
|
||||
drm_framebuffer_put(fb);
|
||||
}
|
||||
|
||||
static void armada_drm_vblank_off(struct armada_crtc *dcrtc)
|
||||
@ -577,7 +577,7 @@ static int armada_drm_crtc_mode_set(struct drm_crtc *crtc,
|
||||
unsigned i;
|
||||
bool interlaced;
|
||||
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
drm_framebuffer_get(crtc->primary->fb);
|
||||
|
||||
interlaced = !!(adj->flags & DRM_MODE_FLAG_INTERLACE);
|
||||
|
||||
@ -718,7 +718,7 @@ static int armada_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
MAX_SCHEDULE_TIMEOUT);
|
||||
|
||||
/* Take a reference to the new fb as we're using it */
|
||||
drm_framebuffer_reference(crtc->primary->fb);
|
||||
drm_framebuffer_get(crtc->primary->fb);
|
||||
|
||||
/* Update the base in the CRTC */
|
||||
armada_drm_crtc_update_regs(dcrtc, regs);
|
||||
@ -742,7 +742,7 @@ void armada_drm_crtc_plane_disable(struct armada_crtc *dcrtc,
|
||||
* primary plane.
|
||||
*/
|
||||
if (plane->fb)
|
||||
drm_framebuffer_unreference(plane->fb);
|
||||
drm_framebuffer_put(plane->fb);
|
||||
|
||||
/* Power down the Y/U/V FIFOs */
|
||||
sram_para1 = CFG_PDWN16x66 | CFG_PDWN32x66;
|
||||
@ -947,13 +947,13 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc,
|
||||
|
||||
/* Must be a kernel-mapped object */
|
||||
if (!obj->addr) {
|
||||
drm_gem_object_unreference_unlocked(&obj->obj);
|
||||
drm_gem_object_put_unlocked(&obj->obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (obj->obj.size < w * h * 4) {
|
||||
DRM_ERROR("buffer is too small\n");
|
||||
drm_gem_object_unreference_unlocked(&obj->obj);
|
||||
drm_gem_object_put_unlocked(&obj->obj);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
@ -961,7 +961,7 @@ static int armada_drm_crtc_cursor_set(struct drm_crtc *crtc,
|
||||
if (dcrtc->cursor_obj) {
|
||||
dcrtc->cursor_obj->update = NULL;
|
||||
dcrtc->cursor_obj->update_data = NULL;
|
||||
drm_gem_object_unreference_unlocked(&dcrtc->cursor_obj->obj);
|
||||
drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj);
|
||||
}
|
||||
dcrtc->cursor_obj = obj;
|
||||
dcrtc->cursor_w = w;
|
||||
@ -997,7 +997,7 @@ static void armada_drm_crtc_destroy(struct drm_crtc *crtc)
|
||||
struct armada_private *priv = crtc->dev->dev_private;
|
||||
|
||||
if (dcrtc->cursor_obj)
|
||||
drm_gem_object_unreference_unlocked(&dcrtc->cursor_obj->obj);
|
||||
drm_gem_object_put_unlocked(&dcrtc->cursor_obj->obj);
|
||||
|
||||
priv->dcrtc[dcrtc->num] = NULL;
|
||||
drm_crtc_cleanup(&dcrtc->crtc);
|
||||
@ -1045,12 +1045,12 @@ static int armada_drm_crtc_page_flip(struct drm_crtc *crtc,
|
||||
* Ensure that we hold a reference on the new framebuffer.
|
||||
* This has to match the behaviour in mode_set.
|
||||
*/
|
||||
drm_framebuffer_reference(fb);
|
||||
drm_framebuffer_get(fb);
|
||||
|
||||
ret = armada_drm_crtc_queue_frame_work(dcrtc, work);
|
||||
if (ret) {
|
||||
/* Undo our reference above */
|
||||
drm_framebuffer_unreference(fb);
|
||||
drm_framebuffer_put(fb);
|
||||
kfree(work);
|
||||
return ret;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ static void armada_drm_unref_work(struct work_struct *work)
|
||||
struct drm_framebuffer *fb;
|
||||
|
||||
while (kfifo_get(&priv->fb_unref, &fb))
|
||||
drm_framebuffer_unreference(fb);
|
||||
drm_framebuffer_put(fb);
|
||||
}
|
||||
|
||||
/* Must be called with dev->event_lock held */
|
||||
|
@ -17,7 +17,7 @@ static void armada_fb_destroy(struct drm_framebuffer *fb)
|
||||
struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
|
||||
|
||||
drm_framebuffer_cleanup(&dfb->fb);
|
||||
drm_gem_object_unreference_unlocked(&dfb->obj->obj);
|
||||
drm_gem_object_put_unlocked(&dfb->obj->obj);
|
||||
kfree(dfb);
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ struct armada_framebuffer *armada_framebuffer_create(struct drm_device *dev,
|
||||
* the above call, but the caller will drop their reference
|
||||
* to it. Hence we need to take our own reference.
|
||||
*/
|
||||
drm_gem_object_reference(&obj->obj);
|
||||
drm_gem_object_get(&obj->obj);
|
||||
|
||||
return dfb;
|
||||
}
|
||||
@ -143,12 +143,12 @@ static struct drm_framebuffer *armada_fb_create(struct drm_device *dev,
|
||||
goto err;
|
||||
}
|
||||
|
||||
drm_gem_object_unreference_unlocked(&obj->obj);
|
||||
drm_gem_object_put_unlocked(&obj->obj);
|
||||
|
||||
return &dfb->fb;
|
||||
|
||||
err_unref:
|
||||
drm_gem_object_unreference_unlocked(&obj->obj);
|
||||
drm_gem_object_put_unlocked(&obj->obj);
|
||||
err:
|
||||
DRM_ERROR("failed to initialize framebuffer: %d\n", ret);
|
||||
return ERR_PTR(ret);
|
||||
|
@ -51,13 +51,13 @@ static int armada_fb_create(struct drm_fb_helper *fbh,
|
||||
|
||||
ret = armada_gem_linear_back(dev, obj);
|
||||
if (ret) {
|
||||
drm_gem_object_unreference_unlocked(&obj->obj);
|
||||
drm_gem_object_put_unlocked(&obj->obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptr = armada_gem_map_object(dev, obj);
|
||||
if (!ptr) {
|
||||
drm_gem_object_unreference_unlocked(&obj->obj);
|
||||
drm_gem_object_put_unlocked(&obj->obj);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ static int armada_fb_create(struct drm_fb_helper *fbh,
|
||||
* A reference is now held by the framebuffer object if
|
||||
* successful, otherwise this drops the ref for the error path.
|
||||
*/
|
||||
drm_gem_object_unreference_unlocked(&obj->obj);
|
||||
drm_gem_object_put_unlocked(&obj->obj);
|
||||
|
||||
if (IS_ERR(dfb))
|
||||
return PTR_ERR(dfb);
|
||||
|
@ -265,7 +265,7 @@ int armada_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
|
||||
/* drop reference from allocate - handle holds it now */
|
||||
DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
|
||||
err:
|
||||
drm_gem_object_unreference_unlocked(&dobj->obj);
|
||||
drm_gem_object_put_unlocked(&dobj->obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -297,7 +297,7 @@ int armada_gem_create_ioctl(struct drm_device *dev, void *data,
|
||||
/* drop reference from allocate - handle holds it now */
|
||||
DRM_DEBUG_DRIVER("obj %p size %zu handle %#x\n", dobj, size, handle);
|
||||
err:
|
||||
drm_gem_object_unreference_unlocked(&dobj->obj);
|
||||
drm_gem_object_put_unlocked(&dobj->obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -314,13 +314,13 @@ int armada_gem_mmap_ioctl(struct drm_device *dev, void *data,
|
||||
return -ENOENT;
|
||||
|
||||
if (!dobj->obj.filp) {
|
||||
drm_gem_object_unreference_unlocked(&dobj->obj);
|
||||
drm_gem_object_put_unlocked(&dobj->obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = vm_mmap(dobj->obj.filp, 0, args->size, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, args->offset);
|
||||
drm_gem_object_unreference_unlocked(&dobj->obj);
|
||||
drm_gem_object_put_unlocked(&dobj->obj);
|
||||
if (IS_ERR_VALUE(addr))
|
||||
return addr;
|
||||
|
||||
@ -375,7 +375,7 @@ int armada_gem_pwrite_ioctl(struct drm_device *dev, void *data,
|
||||
}
|
||||
|
||||
unref:
|
||||
drm_gem_object_unreference_unlocked(&dobj->obj);
|
||||
drm_gem_object_put_unlocked(&dobj->obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -524,7 +524,7 @@ armada_gem_prime_import(struct drm_device *dev, struct dma_buf *buf)
|
||||
* Importing our own dmabuf(s) increases the
|
||||
* refcount on the gem object itself.
|
||||
*/
|
||||
drm_gem_object_reference(obj);
|
||||
drm_gem_object_get(obj);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ armada_ovl_plane_update(struct drm_plane *plane, struct drm_crtc *crtc,
|
||||
* Take a reference on the new framebuffer - we want to
|
||||
* hold on to it while the hardware is displaying it.
|
||||
*/
|
||||
drm_framebuffer_reference(fb);
|
||||
drm_framebuffer_get(fb);
|
||||
|
||||
if (plane->fb)
|
||||
armada_ovl_retire_fb(dplane, plane->fb);
|
||||
@ -278,7 +278,7 @@ static int armada_ovl_plane_disable(struct drm_plane *plane,
|
||||
|
||||
fb = xchg(&dplane->old_fb, NULL);
|
||||
if (fb)
|
||||
drm_framebuffer_unreference(fb);
|
||||
drm_framebuffer_put(fb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -607,10 +607,10 @@ static int adv7511_get_modes(struct adv7511 *adv7511,
|
||||
adv7511_set_config_csc(adv7511, connector, adv7511->rgb,
|
||||
drm_detect_hdmi_monitor(edid));
|
||||
|
||||
kfree(edid);
|
||||
|
||||
cec_s_phys_addr_from_edid(adv7511->cec_adap, edid);
|
||||
|
||||
kfree(edid);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
@ -860,6 +860,7 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
|
||||
|
||||
for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
|
||||
const struct drm_crtc_helper_funcs *funcs;
|
||||
int ret;
|
||||
|
||||
/* Shut down everything that needs a full modeset. */
|
||||
if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
|
||||
@ -883,6 +884,14 @@ disable_outputs(struct drm_device *dev, struct drm_atomic_state *old_state)
|
||||
funcs->disable(crtc);
|
||||
else
|
||||
funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
|
||||
if (!(dev->irq_enabled && dev->num_crtcs))
|
||||
continue;
|
||||
|
||||
ret = drm_crtc_vblank_get(crtc);
|
||||
WARN_ONCE(ret != -EINVAL, "driver forgot to call drm_crtc_vblank_off()\n");
|
||||
if (ret == 0)
|
||||
drm_crtc_vblank_put(crtc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1772,16 +1781,16 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
|
||||
}
|
||||
|
||||
for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) {
|
||||
/* commit tracked through new_crtc_state->commit, no need to do it explicitly */
|
||||
if (new_conn_state->crtc)
|
||||
continue;
|
||||
|
||||
/* Userspace is not allowed to get ahead of the previous
|
||||
* commit with nonblocking ones. */
|
||||
if (nonblock && old_conn_state->commit &&
|
||||
!try_wait_for_completion(&old_conn_state->commit->flip_done))
|
||||
return -EBUSY;
|
||||
|
||||
/* commit tracked through new_crtc_state->commit, no need to do it explicitly */
|
||||
if (new_conn_state->crtc)
|
||||
continue;
|
||||
|
||||
commit = crtc_or_fake_commit(state, old_conn_state->crtc);
|
||||
if (!commit)
|
||||
return -ENOMEM;
|
||||
@ -1790,18 +1799,17 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
|
||||
}
|
||||
|
||||
for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) {
|
||||
/*
|
||||
* Unlike connectors, always track planes explicitly for
|
||||
* async pageflip support.
|
||||
*/
|
||||
|
||||
/* Userspace is not allowed to get ahead of the previous
|
||||
* commit with nonblocking ones. */
|
||||
if (nonblock && old_plane_state->commit &&
|
||||
!try_wait_for_completion(&old_plane_state->commit->flip_done))
|
||||
return -EBUSY;
|
||||
|
||||
commit = crtc_or_fake_commit(state, old_plane_state->crtc);
|
||||
/*
|
||||
* Unlike connectors, always track planes explicitly for
|
||||
* async pageflip support.
|
||||
*/
|
||||
commit = crtc_or_fake_commit(state, new_plane_state->crtc ?: old_plane_state->crtc);
|
||||
if (!commit)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -112,7 +112,7 @@ struct drm_gem_cma_object *drm_gem_cma_create(struct drm_device *drm,
|
||||
cma_obj->vaddr = dma_alloc_wc(drm->dev, size, &cma_obj->paddr,
|
||||
GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!cma_obj->vaddr) {
|
||||
dev_err(drm->dev, "failed to allocate buffer with size %zu\n",
|
||||
dev_dbg(drm->dev, "failed to allocate buffer with size %zu\n",
|
||||
size);
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
|
@ -1008,6 +1008,10 @@ static const struct panel_desc hitachi_tx23d38vm0caa = {
|
||||
.width = 195,
|
||||
.height = 117,
|
||||
},
|
||||
.delay = {
|
||||
.enable = 160,
|
||||
.disable = 160,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct drm_display_mode innolux_at043tn24_mode = {
|
||||
@ -1018,8 +1022,8 @@ static const struct drm_display_mode innolux_at043tn24_mode = {
|
||||
.htotal = 480 + 2 + 41 + 2,
|
||||
.vdisplay = 272,
|
||||
.vsync_start = 272 + 2,
|
||||
.vsync_end = 272 + 2 + 11,
|
||||
.vtotal = 272 + 2 + 11 + 2,
|
||||
.vsync_end = 272 + 2 + 10,
|
||||
.vtotal = 272 + 2 + 10 + 2,
|
||||
.vrefresh = 60,
|
||||
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
|
||||
};
|
||||
@ -1033,6 +1037,7 @@ static const struct panel_desc innolux_at043tn24 = {
|
||||
.height = 54,
|
||||
},
|
||||
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
|
||||
.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE,
|
||||
};
|
||||
|
||||
static const struct drm_display_mode innolux_at070tn92_mode = {
|
||||
@ -1832,6 +1837,30 @@ static const struct panel_desc tianma_tm070jdhg30 = {
|
||||
.bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
|
||||
};
|
||||
|
||||
static const struct drm_display_mode toshiba_lt089ac29000_mode = {
|
||||
.clock = 79500,
|
||||
.hdisplay = 1280,
|
||||
.hsync_start = 1280 + 192,
|
||||
.hsync_end = 1280 + 192 + 128,
|
||||
.htotal = 1280 + 192 + 128 + 64,
|
||||
.vdisplay = 768,
|
||||
.vsync_start = 768 + 20,
|
||||
.vsync_end = 768 + 20 + 7,
|
||||
.vtotal = 768 + 20 + 7 + 3,
|
||||
.vrefresh = 60,
|
||||
};
|
||||
|
||||
static const struct panel_desc toshiba_lt089ac29000 = {
|
||||
.modes = &toshiba_lt089ac29000_mode,
|
||||
.num_modes = 1,
|
||||
.size = {
|
||||
.width = 194,
|
||||
.height = 116,
|
||||
},
|
||||
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
|
||||
.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_POSEDGE,
|
||||
};
|
||||
|
||||
static const struct drm_display_mode tpk_f07a_0102_mode = {
|
||||
.clock = 33260,
|
||||
.hdisplay = 800,
|
||||
@ -2113,6 +2142,9 @@ static const struct of_device_id platform_of_match[] = {
|
||||
}, {
|
||||
.compatible = "tianma,tm070jdhg30",
|
||||
.data = &tianma_tm070jdhg30,
|
||||
}, {
|
||||
.compatible = "toshiba,lt089ac29000",
|
||||
.data = &toshiba_lt089ac29000,
|
||||
}, {
|
||||
.compatible = "tpk,f07a-0102",
|
||||
.data = &tpk_f07a_0102,
|
||||
|
@ -1,24 +1,25 @@
|
||||
sun4i-drm-y += sun4i_drv.o
|
||||
sun4i-drm-y += sun4i_framebuffer.o
|
||||
sun4i-backend-y += sun4i_backend.o sun4i_layer.o
|
||||
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
|
||||
sun4i-drm-y += sun4i_drv.o
|
||||
sun4i-drm-y += sun4i_framebuffer.o
|
||||
|
||||
sun4i-tcon-y += sun4i_tcon.o
|
||||
sun4i-tcon-y += sun4i_rgb.o
|
||||
sun4i-tcon-y += sun4i_dotclock.o
|
||||
sun4i-tcon-y += sun4i_crtc.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
|
||||
|
||||
sun4i-backend-y += sun4i_backend.o sun4i_layer.o
|
||||
sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o
|
||||
|
||||
sun8i-mixer-y += sun8i_mixer.o sun8i_layer.o
|
||||
sun4i-tcon-y += sun4i_crtc.o
|
||||
sun4i-tcon-y += sun4i_dotclock.o
|
||||
sun4i-tcon-y += sun4i_tcon.o
|
||||
sun4i-tcon-y += sun4i_rgb.o
|
||||
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun4i-tcon.o
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o
|
||||
obj-$(CONFIG_DRM_SUN4I) += sun6i_drc.o
|
||||
|
||||
obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o
|
||||
obj-$(CONFIG_DRM_SUN4I_BACKEND) += sun4i-backend.o
|
||||
obj-$(CONFIG_DRM_SUN4I_HDMI) += sun4i-drm-hdmi.o
|
||||
obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o
|
||||
obj-$(CONFIG_DRM_SUN8I_MIXER) += sun8i-mixer.o
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <linux/component.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
@ -28,6 +29,11 @@
|
||||
#include "sun4i_layer.h"
|
||||
#include "sunxi_engine.h"
|
||||
|
||||
struct sun4i_backend_quirks {
|
||||
/* backend <-> TCON muxing selection done in backend */
|
||||
bool needs_output_muxing;
|
||||
};
|
||||
|
||||
static const u32 sunxi_rgb2yuv_coef[12] = {
|
||||
0x00000107, 0x00000204, 0x00000064, 0x00000108,
|
||||
0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808,
|
||||
@ -216,6 +222,13 @@ int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend,
|
||||
paddr = drm_fb_cma_get_gem_addr(fb, state, 0);
|
||||
DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &paddr);
|
||||
|
||||
/*
|
||||
* backend DMA accesses DRAM directly, bypassing the system
|
||||
* bus. As such, the address range is different and the buffer
|
||||
* address needs to be corrected.
|
||||
*/
|
||||
paddr -= PHYS_OFFSET;
|
||||
|
||||
/* Write the 32 lower bits of the address (in bits) */
|
||||
lo_paddr = paddr << 3;
|
||||
DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr);
|
||||
@ -338,6 +351,7 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
|
||||
struct drm_device *drm = data;
|
||||
struct sun4i_drv *drv = drm->dev_private;
|
||||
struct sun4i_backend *backend;
|
||||
const struct sun4i_backend_quirks *quirks;
|
||||
struct resource *res;
|
||||
void __iomem *regs;
|
||||
int i, ret;
|
||||
@ -432,6 +446,27 @@ static int sun4i_backend_bind(struct device *dev, struct device *master,
|
||||
SUN4I_BACKEND_MODCTL_DEBE_EN |
|
||||
SUN4I_BACKEND_MODCTL_START_CTL);
|
||||
|
||||
/* Set output selection if needed */
|
||||
quirks = of_device_get_match_data(dev);
|
||||
if (quirks->needs_output_muxing) {
|
||||
/*
|
||||
* We assume there is no dynamic muxing of backends
|
||||
* and TCONs, so we select the backend with same ID.
|
||||
*
|
||||
* While dynamic selection might be interesting, since
|
||||
* the CRTC is tied to the TCON, while the layers are
|
||||
* tied to the backends, this means, we will need to
|
||||
* switch between groups of layers. There might not be
|
||||
* a way to represent this constraint in DRM.
|
||||
*/
|
||||
regmap_update_bits(backend->engine.regs,
|
||||
SUN4I_BACKEND_MODCTL_REG,
|
||||
SUN4I_BACKEND_MODCTL_OUT_SEL,
|
||||
(backend->engine.id
|
||||
? SUN4I_BACKEND_MODCTL_OUT_LCD1
|
||||
: SUN4I_BACKEND_MODCTL_OUT_LCD0));
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_ram_clk:
|
||||
@ -479,10 +514,44 @@ static int sun4i_backend_remove(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sun4i_backend_quirks sun4i_backend_quirks = {
|
||||
.needs_output_muxing = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_backend_quirks sun5i_backend_quirks = {
|
||||
};
|
||||
|
||||
static const struct sun4i_backend_quirks sun6i_backend_quirks = {
|
||||
};
|
||||
|
||||
static const struct sun4i_backend_quirks sun7i_backend_quirks = {
|
||||
.needs_output_muxing = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_backend_quirks sun8i_a33_backend_quirks = {
|
||||
};
|
||||
|
||||
static const struct of_device_id sun4i_backend_of_table[] = {
|
||||
{ .compatible = "allwinner,sun5i-a13-display-backend" },
|
||||
{ .compatible = "allwinner,sun6i-a31-display-backend" },
|
||||
{ .compatible = "allwinner,sun8i-a33-display-backend" },
|
||||
{
|
||||
.compatible = "allwinner,sun4i-a10-display-backend",
|
||||
.data = &sun4i_backend_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun5i-a13-display-backend",
|
||||
.data = &sun5i_backend_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun6i-a31-display-backend",
|
||||
.data = &sun6i_backend_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun7i-a20-display-backend",
|
||||
.data = &sun7i_backend_quirks,
|
||||
},
|
||||
{
|
||||
.compatible = "allwinner,sun8i-a33-display-backend",
|
||||
.data = &sun8i_a33_backend_quirks,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sun4i_backend_of_table);
|
||||
|
@ -25,7 +25,8 @@
|
||||
#define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29)
|
||||
#define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28)
|
||||
#define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK(22, 20)
|
||||
#define SUN4I_BACKEND_MODCTL_OUT_LCD (0 << 20)
|
||||
#define SUN4I_BACKEND_MODCTL_OUT_LCD0 (0 << 20)
|
||||
#define SUN4I_BACKEND_MODCTL_OUT_LCD1 (1 << 20)
|
||||
#define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20)
|
||||
#define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20)
|
||||
#define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16)
|
||||
|
@ -30,6 +30,22 @@
|
||||
#include "sunxi_engine.h"
|
||||
#include "sun4i_tcon.h"
|
||||
|
||||
/*
|
||||
* While this isn't really working in the DRM theory, in practice we
|
||||
* can only ever have one encoder per TCON since we have a mux in our
|
||||
* TCON.
|
||||
*/
|
||||
static struct drm_encoder *sun4i_crtc_get_encoder(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
drm_for_each_encoder(encoder, crtc->dev)
|
||||
if (encoder->crtc == crtc)
|
||||
return encoder;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *old_state)
|
||||
{
|
||||
@ -72,11 +88,12 @@ static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc,
|
||||
static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *old_state)
|
||||
{
|
||||
struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc);
|
||||
struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc);
|
||||
|
||||
DRM_DEBUG_DRIVER("Disabling the CRTC\n");
|
||||
|
||||
sun4i_tcon_disable(scrtc->tcon);
|
||||
sun4i_tcon_set_status(scrtc->tcon, encoder, false);
|
||||
|
||||
if (crtc->state->event && !crtc->state->active) {
|
||||
spin_lock_irq(&crtc->dev->event_lock);
|
||||
@ -90,11 +107,21 @@ static void sun4i_crtc_atomic_disable(struct drm_crtc *crtc,
|
||||
static void sun4i_crtc_atomic_enable(struct drm_crtc *crtc,
|
||||
struct drm_crtc_state *old_state)
|
||||
{
|
||||
struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc);
|
||||
struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc);
|
||||
|
||||
DRM_DEBUG_DRIVER("Enabling the CRTC\n");
|
||||
|
||||
sun4i_tcon_enable(scrtc->tcon);
|
||||
sun4i_tcon_set_status(scrtc->tcon, encoder, true);
|
||||
}
|
||||
|
||||
static void sun4i_crtc_mode_set_nofb(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
|
||||
struct drm_encoder *encoder = sun4i_crtc_get_encoder(crtc);
|
||||
struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc);
|
||||
|
||||
sun4i_tcon_mode_set(scrtc->tcon, encoder, mode);
|
||||
}
|
||||
|
||||
static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = {
|
||||
@ -102,6 +129,7 @@ static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = {
|
||||
.atomic_flush = sun4i_crtc_atomic_flush,
|
||||
.atomic_enable = sun4i_crtc_atomic_enable,
|
||||
.atomic_disable = sun4i_crtc_atomic_disable,
|
||||
.mode_set_nofb = sun4i_crtc_mode_set_nofb,
|
||||
};
|
||||
|
||||
static int sun4i_crtc_enable_vblank(struct drm_crtc *crtc)
|
||||
|
@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/component.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/of_reserved_mem.h>
|
||||
|
||||
@ -177,16 +178,20 @@ static bool sun4i_drv_node_is_connector(struct device_node *node)
|
||||
|
||||
static bool sun4i_drv_node_is_frontend(struct device_node *node)
|
||||
{
|
||||
return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
|
||||
return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") ||
|
||||
of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
|
||||
of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") ||
|
||||
of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") ||
|
||||
of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend");
|
||||
}
|
||||
|
||||
static bool sun4i_drv_node_is_tcon(struct device_node *node)
|
||||
{
|
||||
return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") ||
|
||||
return of_device_is_compatible(node, "allwinner,sun4i-a10-tcon") ||
|
||||
of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") ||
|
||||
of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") ||
|
||||
of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") ||
|
||||
of_device_is_compatible(node, "allwinner,sun7i-a20-tcon") ||
|
||||
of_device_is_compatible(node, "allwinner,sun8i-a33-tcon") ||
|
||||
of_device_is_compatible(node, "allwinner,sun8i-v3s-tcon");
|
||||
}
|
||||
@ -222,29 +227,15 @@ static int compare_of(struct device *dev, void *data)
|
||||
* matching system handles this for us.
|
||||
*/
|
||||
struct endpoint_list {
|
||||
struct device_node *node;
|
||||
struct list_head list;
|
||||
DECLARE_KFIFO(fifo, struct device_node *, 16);
|
||||
};
|
||||
|
||||
static bool node_is_in_list(struct list_head *endpoints,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct endpoint_list *endpoint;
|
||||
|
||||
list_for_each_entry(endpoint, endpoints, list)
|
||||
if (endpoint->node == node)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int sun4i_drv_add_endpoints(struct device *dev,
|
||||
struct list_head *endpoints,
|
||||
struct endpoint_list *list,
|
||||
struct component_match **match,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct device_node *port, *ep, *remote;
|
||||
struct endpoint_list *endpoint;
|
||||
int count = 0;
|
||||
|
||||
/*
|
||||
@ -304,19 +295,7 @@ static int sun4i_drv_add_endpoints(struct device *dev,
|
||||
}
|
||||
}
|
||||
|
||||
/* skip downstream node if it is already in the queue */
|
||||
if (node_is_in_list(endpoints, remote))
|
||||
continue;
|
||||
|
||||
/* Add downstream nodes to the queue */
|
||||
endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL);
|
||||
if (!endpoint) {
|
||||
of_node_put(remote);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
endpoint->node = remote;
|
||||
list_add_tail(&endpoint->list, endpoints);
|
||||
kfifo_put(&list->fifo, remote);
|
||||
}
|
||||
|
||||
return count;
|
||||
@ -325,10 +304,11 @@ static int sun4i_drv_add_endpoints(struct device *dev,
|
||||
static int sun4i_drv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct component_match *match = NULL;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct endpoint_list *endpoint, *endpoint_temp;
|
||||
struct device_node *np = pdev->dev.of_node, *endpoint;
|
||||
struct endpoint_list list;
|
||||
int i, ret, count = 0;
|
||||
LIST_HEAD(endpoints);
|
||||
|
||||
INIT_KFIFO(list.fifo);
|
||||
|
||||
for (i = 0;; i++) {
|
||||
struct device_node *pipeline = of_parse_phandle(np,
|
||||
@ -337,31 +317,19 @@ static int sun4i_drv_probe(struct platform_device *pdev)
|
||||
if (!pipeline)
|
||||
break;
|
||||
|
||||
endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL);
|
||||
if (!endpoint) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_endpoints;
|
||||
}
|
||||
|
||||
endpoint->node = pipeline;
|
||||
list_add_tail(&endpoint->list, &endpoints);
|
||||
kfifo_put(&list.fifo, pipeline);
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(endpoint, endpoint_temp, &endpoints, list) {
|
||||
while (kfifo_get(&list.fifo, &endpoint)) {
|
||||
/* process this endpoint */
|
||||
ret = sun4i_drv_add_endpoints(&pdev->dev, &endpoints, &match,
|
||||
endpoint->node);
|
||||
ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match,
|
||||
endpoint);
|
||||
|
||||
/* sun4i_drv_add_endpoints can fail to allocate memory */
|
||||
if (ret < 0)
|
||||
goto err_free_endpoints;
|
||||
return ret;
|
||||
|
||||
count += ret;
|
||||
|
||||
/* delete and cleanup the current entry */
|
||||
list_del(&endpoint->list);
|
||||
of_node_put(endpoint->node);
|
||||
kfree(endpoint);
|
||||
}
|
||||
|
||||
if (count)
|
||||
@ -370,15 +338,6 @@ static int sun4i_drv_probe(struct platform_device *pdev)
|
||||
match);
|
||||
else
|
||||
return 0;
|
||||
|
||||
err_free_endpoints:
|
||||
list_for_each_entry_safe(endpoint, endpoint_temp, &endpoints, list) {
|
||||
list_del(&endpoint->list);
|
||||
of_node_put(endpoint->node);
|
||||
kfree(endpoint);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sun4i_drv_remove(struct platform_device *pdev)
|
||||
@ -387,10 +346,12 @@ static int sun4i_drv_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct of_device_id sun4i_drv_of_table[] = {
|
||||
{ .compatible = "allwinner,sun4i-a10-display-engine" },
|
||||
{ .compatible = "allwinner,sun5i-a10s-display-engine" },
|
||||
{ .compatible = "allwinner,sun5i-a13-display-engine" },
|
||||
{ .compatible = "allwinner,sun6i-a31-display-engine" },
|
||||
{ .compatible = "allwinner,sun6i-a31s-display-engine" },
|
||||
{ .compatible = "allwinner,sun7i-a20-display-engine" },
|
||||
{ .compatible = "allwinner,sun8i-a33-display-engine" },
|
||||
{ .compatible = "allwinner,sun8i-v3s-display-engine" },
|
||||
{ }
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sun4i_hdmi.h"
|
||||
|
||||
struct sun4i_ddc {
|
||||
|
@ -30,7 +30,6 @@
|
||||
#include "sun4i_crtc.h"
|
||||
#include "sun4i_drv.h"
|
||||
#include "sun4i_hdmi.h"
|
||||
#include "sun4i_tcon.h"
|
||||
|
||||
static inline struct sun4i_hdmi *
|
||||
drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
|
||||
@ -86,8 +85,6 @@ static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
|
||||
static void sun4i_hdmi_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
|
||||
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
|
||||
struct sun4i_tcon *tcon = crtc->tcon;
|
||||
u32 val;
|
||||
|
||||
DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
|
||||
@ -95,22 +92,16 @@ static void sun4i_hdmi_disable(struct drm_encoder *encoder)
|
||||
val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
|
||||
val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
|
||||
writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
|
||||
|
||||
sun4i_tcon_channel_disable(tcon, 1);
|
||||
}
|
||||
|
||||
static void sun4i_hdmi_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
|
||||
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
|
||||
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
|
||||
struct sun4i_tcon *tcon = crtc->tcon;
|
||||
u32 val = 0;
|
||||
|
||||
DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
|
||||
|
||||
sun4i_tcon_channel_enable(tcon, 1);
|
||||
|
||||
sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
|
||||
val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
|
||||
val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
|
||||
@ -128,15 +119,9 @@ static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
|
||||
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
|
||||
struct sun4i_tcon *tcon = crtc->tcon;
|
||||
unsigned int x, y;
|
||||
u32 val;
|
||||
|
||||
sun4i_tcon1_mode_set(tcon, mode);
|
||||
sun4i_tcon_set_mux(tcon, 1, encoder);
|
||||
|
||||
clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
|
||||
clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
|
||||
clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
|
||||
|
||||
@ -289,6 +274,58 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
|
||||
#define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0))
|
||||
#define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0))
|
||||
|
||||
/* Only difference from sun5i is AMP is 4 instead of 6 */
|
||||
static const struct sun4i_hdmi_variant sun4i_variant = {
|
||||
.pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN |
|
||||
SUN4I_HDMI_PAD_CTRL0_CKEN |
|
||||
SUN4I_HDMI_PAD_CTRL0_PWENG |
|
||||
SUN4I_HDMI_PAD_CTRL0_PWEND |
|
||||
SUN4I_HDMI_PAD_CTRL0_PWENC |
|
||||
SUN4I_HDMI_PAD_CTRL0_LDODEN |
|
||||
SUN4I_HDMI_PAD_CTRL0_LDOCEN |
|
||||
SUN4I_HDMI_PAD_CTRL0_BIASEN,
|
||||
.pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(4) |
|
||||
SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
|
||||
SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
|
||||
SUN4I_HDMI_PAD_CTRL1_REG_DEN |
|
||||
SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
|
||||
SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
|
||||
SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
|
||||
SUN4I_HDMI_PAD_CTRL1_AMP_OPT,
|
||||
.pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
|
||||
SUN4I_HDMI_PLL_CTRL_CS(7) |
|
||||
SUN4I_HDMI_PLL_CTRL_CP_S(15) |
|
||||
SUN4I_HDMI_PLL_CTRL_S(7) |
|
||||
SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) |
|
||||
SUN4I_HDMI_PLL_CTRL_SDIV2 |
|
||||
SUN4I_HDMI_PLL_CTRL_LDO2_EN |
|
||||
SUN4I_HDMI_PLL_CTRL_LDO1_EN |
|
||||
SUN4I_HDMI_PLL_CTRL_HV_IS_33 |
|
||||
SUN4I_HDMI_PLL_CTRL_BWS |
|
||||
SUN4I_HDMI_PLL_CTRL_PLL_EN,
|
||||
|
||||
.ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6),
|
||||
.ddc_clk_pre_divider = 2,
|
||||
.ddc_clk_m_offset = 1,
|
||||
|
||||
.field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31),
|
||||
.field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30),
|
||||
.field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0),
|
||||
.field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31),
|
||||
.field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6),
|
||||
.field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8),
|
||||
.field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31),
|
||||
.field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
|
||||
.field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
|
||||
.field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9),
|
||||
.field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2),
|
||||
.field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9),
|
||||
.field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8),
|
||||
|
||||
.ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG,
|
||||
.ddc_fifo_has_dir = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_hdmi_variant sun5i_variant = {
|
||||
.pad_ctrl0_init_val = SUN4I_HDMI_PAD_CTRL0_TXEN |
|
||||
SUN4I_HDMI_PAD_CTRL0_CKEN |
|
||||
@ -613,6 +650,7 @@ static int sun4i_hdmi_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static const struct of_device_id sun4i_hdmi_of_table[] = {
|
||||
{ .compatible = "allwinner,sun4i-a10-hdmi", .data = &sun4i_variant, },
|
||||
{ .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, },
|
||||
{ .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_variant, },
|
||||
{ }
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sun4i_hdmi.h"
|
||||
|
||||
struct sun4i_tmds {
|
||||
|
@ -134,13 +134,10 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder)
|
||||
|
||||
DRM_DEBUG_DRIVER("Enabling RGB output\n");
|
||||
|
||||
if (!IS_ERR(tcon->panel))
|
||||
if (!IS_ERR(tcon->panel)) {
|
||||
drm_panel_prepare(tcon->panel);
|
||||
|
||||
sun4i_tcon_channel_enable(tcon, 0);
|
||||
|
||||
if (!IS_ERR(tcon->panel))
|
||||
drm_panel_enable(tcon->panel);
|
||||
}
|
||||
}
|
||||
|
||||
static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
|
||||
@ -150,31 +147,13 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
|
||||
|
||||
DRM_DEBUG_DRIVER("Disabling RGB output\n");
|
||||
|
||||
if (!IS_ERR(tcon->panel))
|
||||
if (!IS_ERR(tcon->panel)) {
|
||||
drm_panel_disable(tcon->panel);
|
||||
|
||||
sun4i_tcon_channel_disable(tcon, 0);
|
||||
|
||||
if (!IS_ERR(tcon->panel))
|
||||
drm_panel_unprepare(tcon->panel);
|
||||
}
|
||||
|
||||
static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
|
||||
struct sun4i_tcon *tcon = rgb->tcon;
|
||||
|
||||
sun4i_tcon0_mode_set(tcon, mode);
|
||||
sun4i_tcon_set_mux(tcon, 0, encoder);
|
||||
|
||||
/* FIXME: This seems to be board specific */
|
||||
clk_set_phase(tcon->dclk, 120);
|
||||
}
|
||||
}
|
||||
|
||||
static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = {
|
||||
.mode_set = sun4i_rgb_encoder_mode_set,
|
||||
.disable = sun4i_rgb_encoder_disable,
|
||||
.enable = sun4i_rgb_encoder_enable,
|
||||
};
|
||||
|
@ -35,66 +35,61 @@
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sunxi_engine.h"
|
||||
|
||||
void sun4i_tcon_disable(struct sun4i_tcon *tcon)
|
||||
static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
|
||||
bool enabled)
|
||||
{
|
||||
DRM_DEBUG_DRIVER("Disabling TCON\n");
|
||||
struct clk *clk;
|
||||
|
||||
/* Disable the TCON */
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
|
||||
SUN4I_TCON_GCTL_TCON_ENABLE, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_disable);
|
||||
|
||||
void sun4i_tcon_enable(struct sun4i_tcon *tcon)
|
||||
{
|
||||
DRM_DEBUG_DRIVER("Enabling TCON\n");
|
||||
|
||||
/* Enable the TCON */
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
|
||||
SUN4I_TCON_GCTL_TCON_ENABLE,
|
||||
SUN4I_TCON_GCTL_TCON_ENABLE);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_enable);
|
||||
|
||||
void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
|
||||
{
|
||||
DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel);
|
||||
|
||||
/* Disable the TCON's channel */
|
||||
if (channel == 0) {
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
|
||||
SUN4I_TCON0_CTL_TCON_ENABLE, 0);
|
||||
clk_disable_unprepare(tcon->dclk);
|
||||
return;
|
||||
}
|
||||
|
||||
WARN_ON(!tcon->quirks->has_channel_1);
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE, 0);
|
||||
clk_disable_unprepare(tcon->sclk1);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_channel_disable);
|
||||
|
||||
void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
|
||||
{
|
||||
DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel);
|
||||
|
||||
/* Enable the TCON's channel */
|
||||
if (channel == 0) {
|
||||
switch (channel) {
|
||||
case 0:
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
|
||||
SUN4I_TCON0_CTL_TCON_ENABLE,
|
||||
SUN4I_TCON0_CTL_TCON_ENABLE);
|
||||
clk_prepare_enable(tcon->dclk);
|
||||
enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0);
|
||||
clk = tcon->dclk;
|
||||
break;
|
||||
case 1:
|
||||
WARN_ON(!tcon->quirks->has_channel_1);
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE,
|
||||
enabled ? SUN4I_TCON1_CTL_TCON_ENABLE : 0);
|
||||
clk = tcon->sclk1;
|
||||
break;
|
||||
default:
|
||||
DRM_WARN("Unknown channel... doing nothing\n");
|
||||
return;
|
||||
}
|
||||
|
||||
WARN_ON(!tcon->quirks->has_channel_1);
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE,
|
||||
SUN4I_TCON1_CTL_TCON_ENABLE);
|
||||
clk_prepare_enable(tcon->sclk1);
|
||||
if (enabled)
|
||||
clk_prepare_enable(clk);
|
||||
else
|
||||
clk_disable_unprepare(clk);
|
||||
}
|
||||
|
||||
void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder,
|
||||
bool enabled)
|
||||
{
|
||||
int channel;
|
||||
|
||||
switch (encoder->encoder_type) {
|
||||
case DRM_MODE_ENCODER_NONE:
|
||||
channel = 0;
|
||||
break;
|
||||
case DRM_MODE_ENCODER_TMDS:
|
||||
case DRM_MODE_ENCODER_TVDAC:
|
||||
channel = 1;
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
|
||||
SUN4I_TCON_GCTL_TCON_ENABLE,
|
||||
enabled ? SUN4I_TCON_GCTL_TCON_ENABLE : 0);
|
||||
|
||||
sun4i_tcon_channel_set_status(tcon, channel, enabled);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_channel_enable);
|
||||
|
||||
void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
|
||||
{
|
||||
@ -134,7 +129,7 @@ static struct sun4i_tcon *sun4i_get_tcon0(struct drm_device *drm)
|
||||
}
|
||||
|
||||
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
|
||||
struct drm_encoder *encoder)
|
||||
const struct drm_encoder *encoder)
|
||||
{
|
||||
int ret = -ENOTSUPP;
|
||||
|
||||
@ -144,9 +139,8 @@ void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
|
||||
DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n",
|
||||
encoder->name, encoder->crtc->name, ret);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_set_mux);
|
||||
|
||||
static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
|
||||
static int sun4i_tcon_get_clk_delay(const struct drm_display_mode *mode,
|
||||
int channel)
|
||||
{
|
||||
int delay = mode->vtotal - mode->vdisplay;
|
||||
@ -164,15 +158,26 @@ static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
|
||||
return delay;
|
||||
}
|
||||
|
||||
void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
|
||||
struct drm_display_mode *mode)
|
||||
static void sun4i_tcon0_mode_set_common(struct sun4i_tcon *tcon,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
/* Configure the dot clock */
|
||||
clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
|
||||
|
||||
/* Set the resolution */
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG,
|
||||
SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) |
|
||||
SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
|
||||
}
|
||||
|
||||
static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
unsigned int bp, hsync, vsync;
|
||||
u8 clk_delay;
|
||||
u32 val = 0;
|
||||
|
||||
/* Configure the dot clock */
|
||||
clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
|
||||
sun4i_tcon0_mode_set_common(tcon, mode);
|
||||
|
||||
/* Adjust clock delay */
|
||||
clk_delay = sun4i_tcon_get_clk_delay(mode, 0);
|
||||
@ -180,11 +185,6 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
|
||||
SUN4I_TCON0_CTL_CLK_DELAY_MASK,
|
||||
SUN4I_TCON0_CTL_CLK_DELAY(clk_delay));
|
||||
|
||||
/* Set the resolution */
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG,
|
||||
SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) |
|
||||
SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay));
|
||||
|
||||
/*
|
||||
* This is called a backporch in the register documentation,
|
||||
* but it really is the back porch + hsync
|
||||
@ -238,10 +238,9 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
|
||||
/* Enable the output on the pins */
|
||||
regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon0_mode_set);
|
||||
|
||||
void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
|
||||
struct drm_display_mode *mode)
|
||||
static void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
unsigned int bp, hsync, vsync, vtotal;
|
||||
u8 clk_delay;
|
||||
@ -329,7 +328,26 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
|
||||
SUN4I_TCON_GCTL_IOMAP_MASK,
|
||||
SUN4I_TCON_GCTL_IOMAP_TCON1);
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon1_mode_set);
|
||||
|
||||
void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
switch (encoder->encoder_type) {
|
||||
case DRM_MODE_ENCODER_NONE:
|
||||
sun4i_tcon0_mode_set_rgb(tcon, mode);
|
||||
sun4i_tcon_set_mux(tcon, 0, encoder);
|
||||
break;
|
||||
case DRM_MODE_ENCODER_TVDAC:
|
||||
case DRM_MODE_ENCODER_TMDS:
|
||||
sun4i_tcon1_mode_set(tcon, mode);
|
||||
sun4i_tcon_set_mux(tcon, 1, encoder);
|
||||
break;
|
||||
default:
|
||||
DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n");
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(sun4i_tcon_mode_set);
|
||||
|
||||
static void sun4i_tcon_finish_page_flip(struct drm_device *dev,
|
||||
struct sun4i_crtc *scrtc)
|
||||
@ -782,8 +800,32 @@ static int sun4i_tcon_remove(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
/* platform specific TCON muxing callbacks */
|
||||
static int sun4i_a10_tcon_set_mux(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev);
|
||||
u32 shift;
|
||||
|
||||
if (!tcon0)
|
||||
return -EINVAL;
|
||||
|
||||
switch (encoder->encoder_type) {
|
||||
case DRM_MODE_ENCODER_TMDS:
|
||||
/* HDMI */
|
||||
shift = 8;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG,
|
||||
0x3 << shift, tcon->id << shift);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon,
|
||||
struct drm_encoder *encoder)
|
||||
const struct drm_encoder *encoder)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
@ -799,7 +841,7 @@ static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon,
|
||||
}
|
||||
|
||||
static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon,
|
||||
struct drm_encoder *encoder)
|
||||
const struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_tcon *tcon0 = sun4i_get_tcon0(encoder->dev);
|
||||
u32 shift;
|
||||
@ -823,6 +865,11 @@ static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct sun4i_tcon_quirks sun4i_a10_quirks = {
|
||||
.has_channel_1 = true,
|
||||
.set_mux = sun4i_a10_tcon_set_mux,
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
|
||||
.has_channel_1 = true,
|
||||
.set_mux = sun5i_a13_tcon_set_mux,
|
||||
@ -839,6 +886,12 @@ static const struct sun4i_tcon_quirks sun6i_a31s_quirks = {
|
||||
.needs_de_be_mux = true,
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun7i_a20_quirks = {
|
||||
.has_channel_1 = true,
|
||||
/* Same display pipeline structure as A10 */
|
||||
.set_mux = sun4i_a10_tcon_set_mux,
|
||||
};
|
||||
|
||||
static const struct sun4i_tcon_quirks sun8i_a33_quirks = {
|
||||
/* nothing is supported */
|
||||
};
|
||||
@ -848,9 +901,11 @@ static const struct sun4i_tcon_quirks sun8i_v3s_quirks = {
|
||||
};
|
||||
|
||||
static const struct of_device_id sun4i_tcon_of_table[] = {
|
||||
{ .compatible = "allwinner,sun4i-a10-tcon", .data = &sun4i_a10_quirks },
|
||||
{ .compatible = "allwinner,sun5i-a13-tcon", .data = &sun5i_a13_quirks },
|
||||
{ .compatible = "allwinner,sun6i-a31-tcon", .data = &sun6i_a31_quirks },
|
||||
{ .compatible = "allwinner,sun6i-a31s-tcon", .data = &sun6i_a31s_quirks },
|
||||
{ .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks },
|
||||
{ .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks },
|
||||
{ .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks },
|
||||
{ }
|
||||
|
@ -152,7 +152,7 @@ struct sun4i_tcon_quirks {
|
||||
bool needs_de_be_mux; /* sun6i needs mux to select backend */
|
||||
|
||||
/* callback to handle tcon muxing options */
|
||||
int (*set_mux)(struct sun4i_tcon *, struct drm_encoder *);
|
||||
int (*set_mux)(struct sun4i_tcon *, const struct drm_encoder *);
|
||||
};
|
||||
|
||||
struct sun4i_tcon {
|
||||
@ -190,22 +190,11 @@ struct sun4i_tcon {
|
||||
struct drm_bridge *sun4i_tcon_find_bridge(struct device_node *node);
|
||||
struct drm_panel *sun4i_tcon_find_panel(struct device_node *node);
|
||||
|
||||
/* Global Control */
|
||||
void sun4i_tcon_disable(struct sun4i_tcon *tcon);
|
||||
void sun4i_tcon_enable(struct sun4i_tcon *tcon);
|
||||
|
||||
/* Channel Control */
|
||||
void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel);
|
||||
void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel);
|
||||
|
||||
void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
|
||||
|
||||
/* Mode Related Controls */
|
||||
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
|
||||
struct drm_encoder *encoder);
|
||||
void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
|
||||
struct drm_display_mode *mode);
|
||||
void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
|
||||
struct drm_display_mode *mode);
|
||||
void sun4i_tcon_mode_set(struct sun4i_tcon *tcon,
|
||||
const struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode);
|
||||
void sun4i_tcon_set_status(struct sun4i_tcon *crtc,
|
||||
const struct drm_encoder *encoder, bool enable);
|
||||
|
||||
#endif /* __SUN4I_TCON_H__ */
|
||||
|
@ -24,7 +24,6 @@
|
||||
|
||||
#include "sun4i_crtc.h"
|
||||
#include "sun4i_drv.h"
|
||||
#include "sun4i_tcon.h"
|
||||
#include "sunxi_engine.h"
|
||||
|
||||
#define SUN4I_TVE_EN_REG 0x000
|
||||
@ -345,12 +344,9 @@ static void sun4i_tv_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
|
||||
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
|
||||
struct sun4i_tcon *tcon = crtc->tcon;
|
||||
|
||||
DRM_DEBUG_DRIVER("Disabling the TV Output\n");
|
||||
|
||||
sun4i_tcon_channel_disable(tcon, 1);
|
||||
|
||||
regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
|
||||
SUN4I_TVE_EN_ENABLE,
|
||||
0);
|
||||
@ -362,7 +358,6 @@ static void sun4i_tv_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
|
||||
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
|
||||
struct sun4i_tcon *tcon = crtc->tcon;
|
||||
|
||||
DRM_DEBUG_DRIVER("Enabling the TV Output\n");
|
||||
|
||||
@ -371,8 +366,6 @@ static void sun4i_tv_enable(struct drm_encoder *encoder)
|
||||
regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
|
||||
SUN4I_TVE_EN_ENABLE,
|
||||
SUN4I_TVE_EN_ENABLE);
|
||||
|
||||
sun4i_tcon_channel_enable(tcon, 1);
|
||||
}
|
||||
|
||||
static void sun4i_tv_mode_set(struct drm_encoder *encoder,
|
||||
@ -380,13 +373,8 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
|
||||
struct sun4i_crtc *crtc = drm_crtc_to_sun4i_crtc(encoder->crtc);
|
||||
struct sun4i_tcon *tcon = crtc->tcon;
|
||||
const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
|
||||
|
||||
sun4i_tcon1_mode_set(tcon, mode);
|
||||
sun4i_tcon_set_mux(tcon, 1, encoder);
|
||||
|
||||
/* Enable and map the DAC to the output */
|
||||
regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
|
||||
SUN4I_TVE_EN_DAC_MAP_MASK,
|
||||
|
@ -14,70 +14,95 @@
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include "udl_connector.h"
|
||||
#include "udl_drv.h"
|
||||
|
||||
/* dummy connector to just get EDID,
|
||||
all UDL appear to have a DVI-D */
|
||||
|
||||
static u8 *udl_get_edid(struct udl_device *udl)
|
||||
static bool udl_get_edid_block(struct udl_device *udl, int block_idx,
|
||||
u8 *buff)
|
||||
{
|
||||
u8 *block;
|
||||
char *rbuf;
|
||||
int ret, i;
|
||||
u8 *read_buff;
|
||||
|
||||
block = kmalloc(EDID_LENGTH, GFP_KERNEL);
|
||||
if (block == NULL)
|
||||
return NULL;
|
||||
|
||||
rbuf = kmalloc(2, GFP_KERNEL);
|
||||
if (rbuf == NULL)
|
||||
goto error;
|
||||
read_buff = kmalloc(2, GFP_KERNEL);
|
||||
if (!read_buff)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < EDID_LENGTH; i++) {
|
||||
int bval = (i + block_idx * EDID_LENGTH) << 8;
|
||||
ret = usb_control_msg(udl->udev,
|
||||
usb_rcvctrlpipe(udl->udev, 0), (0x02),
|
||||
(0x80 | (0x02 << 5)), i << 8, 0xA1, rbuf, 2,
|
||||
HZ);
|
||||
usb_rcvctrlpipe(udl->udev, 0),
|
||||
(0x02), (0x80 | (0x02 << 5)), bval,
|
||||
0xA1, read_buff, 2, HZ);
|
||||
if (ret < 1) {
|
||||
DRM_ERROR("Read EDID byte %d failed err %x\n", i, ret);
|
||||
goto error;
|
||||
kfree(read_buff);
|
||||
return false;
|
||||
}
|
||||
block[i] = rbuf[1];
|
||||
buff[i] = read_buff[1];
|
||||
}
|
||||
|
||||
kfree(rbuf);
|
||||
return block;
|
||||
kfree(read_buff);
|
||||
return true;
|
||||
}
|
||||
|
||||
error:
|
||||
kfree(block);
|
||||
kfree(rbuf);
|
||||
return NULL;
|
||||
static bool udl_get_edid(struct udl_device *udl, u8 **result_buff,
|
||||
int *result_buff_size)
|
||||
{
|
||||
int i, extensions;
|
||||
u8 *block_buff = NULL, *buff_ptr;
|
||||
|
||||
block_buff = kmalloc(EDID_LENGTH, GFP_KERNEL);
|
||||
if (block_buff == NULL)
|
||||
return false;
|
||||
|
||||
if (udl_get_edid_block(udl, 0, block_buff) &&
|
||||
memchr_inv(block_buff, 0, EDID_LENGTH)) {
|
||||
extensions = ((struct edid *)block_buff)->extensions;
|
||||
if (extensions > 0) {
|
||||
/* we have to read all extensions one by one */
|
||||
*result_buff_size = EDID_LENGTH * (extensions + 1);
|
||||
*result_buff = kmalloc(*result_buff_size, GFP_KERNEL);
|
||||
buff_ptr = *result_buff;
|
||||
if (buff_ptr == NULL) {
|
||||
kfree(block_buff);
|
||||
return false;
|
||||
}
|
||||
memcpy(buff_ptr, block_buff, EDID_LENGTH);
|
||||
kfree(block_buff);
|
||||
buff_ptr += EDID_LENGTH;
|
||||
for (i = 1; i < extensions; ++i) {
|
||||
if (udl_get_edid_block(udl, i, buff_ptr)) {
|
||||
buff_ptr += EDID_LENGTH;
|
||||
} else {
|
||||
kfree(*result_buff);
|
||||
*result_buff = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/* we have only base edid block */
|
||||
*result_buff = block_buff;
|
||||
*result_buff_size = EDID_LENGTH;
|
||||
return true;
|
||||
}
|
||||
|
||||
kfree(block_buff);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int udl_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct udl_device *udl = connector->dev->dev_private;
|
||||
struct edid *edid;
|
||||
int ret;
|
||||
struct udl_drm_connector *udl_connector =
|
||||
container_of(connector,
|
||||
struct udl_drm_connector,
|
||||
connector);
|
||||
|
||||
edid = (struct edid *)udl_get_edid(udl);
|
||||
if (!edid) {
|
||||
drm_mode_connector_update_edid_property(connector, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We only read the main block, but if the monitor reports extension
|
||||
* blocks then the drm edid code expects them to be present, so patch
|
||||
* the extension count to 0.
|
||||
*/
|
||||
edid->checksum += edid->extensions;
|
||||
edid->extensions = 0;
|
||||
|
||||
drm_mode_connector_update_edid_property(connector, edid);
|
||||
ret = drm_add_edid_modes(connector, edid);
|
||||
kfree(edid);
|
||||
return ret;
|
||||
drm_mode_connector_update_edid_property(connector, udl_connector->edid);
|
||||
if (udl_connector->edid)
|
||||
return drm_add_edid_modes(connector, udl_connector->edid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int udl_mode_valid(struct drm_connector *connector,
|
||||
@ -96,8 +121,26 @@ static int udl_mode_valid(struct drm_connector *connector,
|
||||
static enum drm_connector_status
|
||||
udl_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
if (drm_dev_is_unplugged(connector->dev))
|
||||
u8 *edid_buff = NULL;
|
||||
int edid_buff_size = 0;
|
||||
struct udl_device *udl = connector->dev->dev_private;
|
||||
struct udl_drm_connector *udl_connector =
|
||||
container_of(connector,
|
||||
struct udl_drm_connector,
|
||||
connector);
|
||||
|
||||
/* cleanup previous edid */
|
||||
if (udl_connector->edid != NULL) {
|
||||
kfree(udl_connector->edid);
|
||||
udl_connector->edid = NULL;
|
||||
}
|
||||
|
||||
|
||||
if (!udl_get_edid(udl, &edid_buff, &edid_buff_size))
|
||||
return connector_status_disconnected;
|
||||
|
||||
udl_connector->edid = (struct edid *)edid_buff;
|
||||
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
@ -117,8 +160,14 @@ static int udl_connector_set_property(struct drm_connector *connector,
|
||||
|
||||
static void udl_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct udl_drm_connector *udl_connector =
|
||||
container_of(connector,
|
||||
struct udl_drm_connector,
|
||||
connector);
|
||||
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
kfree(udl_connector->edid);
|
||||
kfree(connector);
|
||||
}
|
||||
|
||||
@ -138,17 +187,22 @@ static const struct drm_connector_funcs udl_connector_funcs = {
|
||||
|
||||
int udl_connector_init(struct drm_device *dev, struct drm_encoder *encoder)
|
||||
{
|
||||
struct udl_drm_connector *udl_connector;
|
||||
struct drm_connector *connector;
|
||||
|
||||
connector = kzalloc(sizeof(struct drm_connector), GFP_KERNEL);
|
||||
if (!connector)
|
||||
udl_connector = kzalloc(sizeof(struct udl_drm_connector), GFP_KERNEL);
|
||||
if (!udl_connector)
|
||||
return -ENOMEM;
|
||||
|
||||
drm_connector_init(dev, connector, &udl_connector_funcs, DRM_MODE_CONNECTOR_DVII);
|
||||
connector = &udl_connector->connector;
|
||||
drm_connector_init(dev, connector, &udl_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_DVII);
|
||||
drm_connector_helper_add(connector, &udl_connector_helper_funcs);
|
||||
|
||||
drm_connector_register(connector);
|
||||
drm_mode_connector_attach_encoder(connector, encoder);
|
||||
connector->polled = DRM_CONNECTOR_POLL_HPD |
|
||||
DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
13
drivers/gpu/drm/udl/udl_connector.h
Normal file
13
drivers/gpu/drm/udl/udl_connector.h
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef __UDL_CONNECTOR_H__
|
||||
#define __UDL_CONNECTOR_H__
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
|
||||
struct udl_drm_connector {
|
||||
struct drm_connector connector;
|
||||
/* last udl_detect edid */
|
||||
struct edid *edid;
|
||||
};
|
||||
|
||||
|
||||
#endif //__UDL_CONNECTOR_H__
|
@ -14,6 +14,9 @@
|
||||
static int udl_usb_suspend(struct usb_interface *interface,
|
||||
pm_message_t message)
|
||||
{
|
||||
struct drm_device *dev = usb_get_intfdata(interface);
|
||||
|
||||
drm_kms_helper_poll_disable(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -21,6 +24,7 @@ static int udl_usb_resume(struct usb_interface *interface)
|
||||
{
|
||||
struct drm_device *dev = usb_get_intfdata(interface);
|
||||
|
||||
drm_kms_helper_poll_enable(dev);
|
||||
udl_modeset_restore(dev);
|
||||
return 0;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
* more details.
|
||||
*/
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include "udl_drv.h"
|
||||
|
||||
/* -BULK_SIZE as per usb-skeleton. Can we get full page and avoid overhead? */
|
||||
@ -350,6 +351,8 @@ int udl_driver_load(struct drm_device *dev, unsigned long flags)
|
||||
if (ret)
|
||||
goto err_fb;
|
||||
|
||||
drm_kms_helper_poll_init(dev);
|
||||
|
||||
return 0;
|
||||
err_fb:
|
||||
udl_fbdev_cleanup(dev);
|
||||
@ -371,6 +374,8 @@ void udl_driver_unload(struct drm_device *dev)
|
||||
{
|
||||
struct udl_device *udl = dev->dev_private;
|
||||
|
||||
drm_kms_helper_poll_fini(dev);
|
||||
|
||||
if (udl->urbs.count)
|
||||
udl_free_urb_list(dev);
|
||||
|
||||
|
@ -53,6 +53,17 @@ static void vc4_bo_stats_dump(struct vc4_dev *vc4)
|
||||
vc4->bo_labels[i].size_allocated / 1024,
|
||||
vc4->bo_labels[i].num_allocated);
|
||||
}
|
||||
|
||||
mutex_lock(&vc4->purgeable.lock);
|
||||
if (vc4->purgeable.num)
|
||||
DRM_INFO("%30s: %6zdkb BOs (%d)\n", "userspace BO cache",
|
||||
vc4->purgeable.size / 1024, vc4->purgeable.num);
|
||||
|
||||
if (vc4->purgeable.purged_num)
|
||||
DRM_INFO("%30s: %6zdkb BOs (%d)\n", "total purged BO",
|
||||
vc4->purgeable.purged_size / 1024,
|
||||
vc4->purgeable.purged_num);
|
||||
mutex_unlock(&vc4->purgeable.lock);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
@ -75,6 +86,17 @@ int vc4_bo_stats_debugfs(struct seq_file *m, void *unused)
|
||||
}
|
||||
mutex_unlock(&vc4->bo_lock);
|
||||
|
||||
mutex_lock(&vc4->purgeable.lock);
|
||||
if (vc4->purgeable.num)
|
||||
seq_printf(m, "%30s: %6dkb BOs (%d)\n", "userspace BO cache",
|
||||
vc4->purgeable.size / 1024, vc4->purgeable.num);
|
||||
|
||||
if (vc4->purgeable.purged_num)
|
||||
seq_printf(m, "%30s: %6dkb BOs (%d)\n", "total purged BO",
|
||||
vc4->purgeable.purged_size / 1024,
|
||||
vc4->purgeable.purged_num);
|
||||
mutex_unlock(&vc4->purgeable.lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@ -247,6 +269,109 @@ static void vc4_bo_cache_purge(struct drm_device *dev)
|
||||
mutex_unlock(&vc4->bo_lock);
|
||||
}
|
||||
|
||||
void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo)
|
||||
{
|
||||
struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev);
|
||||
|
||||
mutex_lock(&vc4->purgeable.lock);
|
||||
list_add_tail(&bo->size_head, &vc4->purgeable.list);
|
||||
vc4->purgeable.num++;
|
||||
vc4->purgeable.size += bo->base.base.size;
|
||||
mutex_unlock(&vc4->purgeable.lock);
|
||||
}
|
||||
|
||||
static void vc4_bo_remove_from_purgeable_pool_locked(struct vc4_bo *bo)
|
||||
{
|
||||
struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev);
|
||||
|
||||
/* list_del_init() is used here because the caller might release
|
||||
* the purgeable lock in order to acquire the madv one and update the
|
||||
* madv status.
|
||||
* During this short period of time a user might decide to mark
|
||||
* the BO as unpurgeable, and if bo->madv is set to
|
||||
* VC4_MADV_DONTNEED it will try to remove the BO from the
|
||||
* purgeable list which will fail if the ->next/prev fields
|
||||
* are set to LIST_POISON1/LIST_POISON2 (which is what
|
||||
* list_del() does).
|
||||
* Re-initializing the list element guarantees that list_del()
|
||||
* will work correctly even if it's a NOP.
|
||||
*/
|
||||
list_del_init(&bo->size_head);
|
||||
vc4->purgeable.num--;
|
||||
vc4->purgeable.size -= bo->base.base.size;
|
||||
}
|
||||
|
||||
void vc4_bo_remove_from_purgeable_pool(struct vc4_bo *bo)
|
||||
{
|
||||
struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev);
|
||||
|
||||
mutex_lock(&vc4->purgeable.lock);
|
||||
vc4_bo_remove_from_purgeable_pool_locked(bo);
|
||||
mutex_unlock(&vc4->purgeable.lock);
|
||||
}
|
||||
|
||||
static void vc4_bo_purge(struct drm_gem_object *obj)
|
||||
{
|
||||
struct vc4_bo *bo = to_vc4_bo(obj);
|
||||
struct drm_device *dev = obj->dev;
|
||||
|
||||
WARN_ON(!mutex_is_locked(&bo->madv_lock));
|
||||
WARN_ON(bo->madv != VC4_MADV_DONTNEED);
|
||||
|
||||
drm_vma_node_unmap(&obj->vma_node, dev->anon_inode->i_mapping);
|
||||
|
||||
dma_free_wc(dev->dev, obj->size, bo->base.vaddr, bo->base.paddr);
|
||||
bo->base.vaddr = NULL;
|
||||
bo->madv = __VC4_MADV_PURGED;
|
||||
}
|
||||
|
||||
static void vc4_bo_userspace_cache_purge(struct drm_device *dev)
|
||||
{
|
||||
struct vc4_dev *vc4 = to_vc4_dev(dev);
|
||||
|
||||
mutex_lock(&vc4->purgeable.lock);
|
||||
while (!list_empty(&vc4->purgeable.list)) {
|
||||
struct vc4_bo *bo = list_first_entry(&vc4->purgeable.list,
|
||||
struct vc4_bo, size_head);
|
||||
struct drm_gem_object *obj = &bo->base.base;
|
||||
size_t purged_size = 0;
|
||||
|
||||
vc4_bo_remove_from_purgeable_pool_locked(bo);
|
||||
|
||||
/* Release the purgeable lock while we're purging the BO so
|
||||
* that other people can continue inserting things in the
|
||||
* purgeable pool without having to wait for all BOs to be
|
||||
* purged.
|
||||
*/
|
||||
mutex_unlock(&vc4->purgeable.lock);
|
||||
mutex_lock(&bo->madv_lock);
|
||||
|
||||
/* Since we released the purgeable pool lock before acquiring
|
||||
* the BO madv one, the user may have marked the BO as WILLNEED
|
||||
* and re-used it in the meantime.
|
||||
* Before purging the BO we need to make sure
|
||||
* - it is still marked as DONTNEED
|
||||
* - it has not been re-inserted in the purgeable list
|
||||
* - it is not used by HW blocks
|
||||
* If one of these conditions is not met, just skip the entry.
|
||||
*/
|
||||
if (bo->madv == VC4_MADV_DONTNEED &&
|
||||
list_empty(&bo->size_head) &&
|
||||
!refcount_read(&bo->usecnt)) {
|
||||
purged_size = bo->base.base.size;
|
||||
vc4_bo_purge(obj);
|
||||
}
|
||||
mutex_unlock(&bo->madv_lock);
|
||||
mutex_lock(&vc4->purgeable.lock);
|
||||
|
||||
if (purged_size) {
|
||||
vc4->purgeable.purged_size += purged_size;
|
||||
vc4->purgeable.purged_num++;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&vc4->purgeable.lock);
|
||||
}
|
||||
|
||||
static struct vc4_bo *vc4_bo_get_from_cache(struct drm_device *dev,
|
||||
uint32_t size,
|
||||
enum vc4_kernel_bo_type type)
|
||||
@ -293,6 +418,9 @@ struct drm_gem_object *vc4_create_object(struct drm_device *dev, size_t size)
|
||||
if (!bo)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
bo->madv = VC4_MADV_WILLNEED;
|
||||
refcount_set(&bo->usecnt, 0);
|
||||
mutex_init(&bo->madv_lock);
|
||||
mutex_lock(&vc4->bo_lock);
|
||||
bo->label = VC4_BO_TYPE_KERNEL;
|
||||
vc4->bo_labels[VC4_BO_TYPE_KERNEL].num_allocated++;
|
||||
@ -330,16 +458,38 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size,
|
||||
* CMA allocations we've got laying around and try again.
|
||||
*/
|
||||
vc4_bo_cache_purge(dev);
|
||||
|
||||
cma_obj = drm_gem_cma_create(dev, size);
|
||||
if (IS_ERR(cma_obj)) {
|
||||
DRM_ERROR("Failed to allocate from CMA:\n");
|
||||
vc4_bo_stats_dump(vc4);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_ERR(cma_obj)) {
|
||||
/*
|
||||
* Still not enough CMA memory, purge the userspace BO
|
||||
* cache and retry.
|
||||
* This is sub-optimal since we purge the whole userspace
|
||||
* BO cache which forces user that want to re-use the BO to
|
||||
* restore its initial content.
|
||||
* Ideally, we should purge entries one by one and retry
|
||||
* after each to see if CMA allocation succeeds. Or even
|
||||
* better, try to find an entry with at least the same
|
||||
* size.
|
||||
*/
|
||||
vc4_bo_userspace_cache_purge(dev);
|
||||
cma_obj = drm_gem_cma_create(dev, size);
|
||||
}
|
||||
|
||||
if (IS_ERR(cma_obj)) {
|
||||
DRM_ERROR("Failed to allocate from CMA:\n");
|
||||
vc4_bo_stats_dump(vc4);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
bo = to_vc4_bo(&cma_obj->base);
|
||||
|
||||
/* By default, BOs do not support the MADV ioctl. This will be enabled
|
||||
* only on BOs that are exposed to userspace (V3D, V3D_SHADER and DUMB
|
||||
* BOs).
|
||||
*/
|
||||
bo->madv = __VC4_MADV_NOTSUPP;
|
||||
|
||||
mutex_lock(&vc4->bo_lock);
|
||||
vc4_bo_set_label(&cma_obj->base, type);
|
||||
mutex_unlock(&vc4->bo_lock);
|
||||
@ -365,6 +515,8 @@ int vc4_dumb_create(struct drm_file *file_priv,
|
||||
if (IS_ERR(bo))
|
||||
return PTR_ERR(bo);
|
||||
|
||||
bo->madv = VC4_MADV_WILLNEED;
|
||||
|
||||
ret = drm_gem_handle_create(file_priv, &bo->base.base, &args->handle);
|
||||
drm_gem_object_put_unlocked(&bo->base.base);
|
||||
|
||||
@ -403,6 +555,12 @@ void vc4_free_object(struct drm_gem_object *gem_bo)
|
||||
struct vc4_bo *bo = to_vc4_bo(gem_bo);
|
||||
struct list_head *cache_list;
|
||||
|
||||
/* Remove the BO from the purgeable list. */
|
||||
mutex_lock(&bo->madv_lock);
|
||||
if (bo->madv == VC4_MADV_DONTNEED && !refcount_read(&bo->usecnt))
|
||||
vc4_bo_remove_from_purgeable_pool(bo);
|
||||
mutex_unlock(&bo->madv_lock);
|
||||
|
||||
mutex_lock(&vc4->bo_lock);
|
||||
/* If the object references someone else's memory, we can't cache it.
|
||||
*/
|
||||
@ -418,7 +576,8 @@ void vc4_free_object(struct drm_gem_object *gem_bo)
|
||||
}
|
||||
|
||||
/* If this object was partially constructed but CMA allocation
|
||||
* had failed, just free it.
|
||||
* had failed, just free it. Can also happen when the BO has been
|
||||
* purged.
|
||||
*/
|
||||
if (!bo->base.vaddr) {
|
||||
vc4_bo_destroy(bo);
|
||||
@ -437,6 +596,10 @@ void vc4_free_object(struct drm_gem_object *gem_bo)
|
||||
bo->validated_shader = NULL;
|
||||
}
|
||||
|
||||
/* Reset madv and usecnt before adding the BO to the cache. */
|
||||
bo->madv = __VC4_MADV_NOTSUPP;
|
||||
refcount_set(&bo->usecnt, 0);
|
||||
|
||||
bo->t_format = false;
|
||||
bo->free_time = jiffies;
|
||||
list_add(&bo->size_head, cache_list);
|
||||
@ -461,6 +624,56 @@ static void vc4_bo_cache_time_work(struct work_struct *work)
|
||||
mutex_unlock(&vc4->bo_lock);
|
||||
}
|
||||
|
||||
int vc4_bo_inc_usecnt(struct vc4_bo *bo)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Fast path: if the BO is already retained by someone, no need to
|
||||
* check the madv status.
|
||||
*/
|
||||
if (refcount_inc_not_zero(&bo->usecnt))
|
||||
return 0;
|
||||
|
||||
mutex_lock(&bo->madv_lock);
|
||||
switch (bo->madv) {
|
||||
case VC4_MADV_WILLNEED:
|
||||
refcount_inc(&bo->usecnt);
|
||||
ret = 0;
|
||||
break;
|
||||
case VC4_MADV_DONTNEED:
|
||||
/* We shouldn't use a BO marked as purgeable if at least
|
||||
* someone else retained its content by incrementing usecnt.
|
||||
* Luckily the BO hasn't been purged yet, but something wrong
|
||||
* is happening here. Just throw an error instead of
|
||||
* authorizing this use case.
|
||||
*/
|
||||
case __VC4_MADV_PURGED:
|
||||
/* We can't use a purged BO. */
|
||||
default:
|
||||
/* Invalid madv value. */
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&bo->madv_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void vc4_bo_dec_usecnt(struct vc4_bo *bo)
|
||||
{
|
||||
/* Fast path: if the BO is still retained by someone, no need to test
|
||||
* the madv value.
|
||||
*/
|
||||
if (refcount_dec_not_one(&bo->usecnt))
|
||||
return;
|
||||
|
||||
mutex_lock(&bo->madv_lock);
|
||||
if (refcount_dec_and_test(&bo->usecnt) &&
|
||||
bo->madv == VC4_MADV_DONTNEED)
|
||||
vc4_bo_add_to_purgeable_pool(bo);
|
||||
mutex_unlock(&bo->madv_lock);
|
||||
}
|
||||
|
||||
static void vc4_bo_cache_time_timer(unsigned long data)
|
||||
{
|
||||
struct drm_device *dev = (struct drm_device *)data;
|
||||
@ -480,18 +693,52 @@ struct dma_buf *
|
||||
vc4_prime_export(struct drm_device *dev, struct drm_gem_object *obj, int flags)
|
||||
{
|
||||
struct vc4_bo *bo = to_vc4_bo(obj);
|
||||
struct dma_buf *dmabuf;
|
||||
int ret;
|
||||
|
||||
if (bo->validated_shader) {
|
||||
DRM_DEBUG("Attempting to export shader BO\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return drm_gem_prime_export(dev, obj, flags);
|
||||
/* Note: as soon as the BO is exported it becomes unpurgeable, because
|
||||
* noone ever decrements the usecnt even if the reference held by the
|
||||
* exported BO is released. This shouldn't be a problem since we don't
|
||||
* expect exported BOs to be marked as purgeable.
|
||||
*/
|
||||
ret = vc4_bo_inc_usecnt(bo);
|
||||
if (ret) {
|
||||
DRM_ERROR("Failed to increment BO usecnt\n");
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
dmabuf = drm_gem_prime_export(dev, obj, flags);
|
||||
if (IS_ERR(dmabuf))
|
||||
vc4_bo_dec_usecnt(bo);
|
||||
|
||||
return dmabuf;
|
||||
}
|
||||
|
||||
int vc4_fault(struct vm_fault *vmf)
|
||||
{
|
||||
struct vm_area_struct *vma = vmf->vma;
|
||||
struct drm_gem_object *obj = vma->vm_private_data;
|
||||
struct vc4_bo *bo = to_vc4_bo(obj);
|
||||
|
||||
/* The only reason we would end up here is when user-space accesses
|
||||
* BO's memory after it's been purged.
|
||||
*/
|
||||
mutex_lock(&bo->madv_lock);
|
||||
WARN_ON(bo->madv != __VC4_MADV_PURGED);
|
||||
mutex_unlock(&bo->madv_lock);
|
||||
|
||||
return VM_FAULT_SIGBUS;
|
||||
}
|
||||
|
||||
int vc4_mmap(struct file *filp, struct vm_area_struct *vma)
|
||||
{
|
||||
struct drm_gem_object *gem_obj;
|
||||
unsigned long vm_pgoff;
|
||||
struct vc4_bo *bo;
|
||||
int ret;
|
||||
|
||||
@ -507,16 +754,36 @@ int vc4_mmap(struct file *filp, struct vm_area_struct *vma)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (bo->madv != VC4_MADV_WILLNEED) {
|
||||
DRM_DEBUG("mmaping of %s BO not allowed\n",
|
||||
bo->madv == VC4_MADV_DONTNEED ?
|
||||
"purgeable" : "purged");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the
|
||||
* vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map
|
||||
* the whole buffer.
|
||||
*/
|
||||
vma->vm_flags &= ~VM_PFNMAP;
|
||||
vma->vm_pgoff = 0;
|
||||
|
||||
/* This ->vm_pgoff dance is needed to make all parties happy:
|
||||
* - dma_mmap_wc() uses ->vm_pgoff as an offset within the allocated
|
||||
* mem-region, hence the need to set it to zero (the value set by
|
||||
* the DRM core is a virtual offset encoding the GEM object-id)
|
||||
* - the mmap() core logic needs ->vm_pgoff to be restored to its
|
||||
* initial value before returning from this function because it
|
||||
* encodes the offset of this GEM in the dev->anon_inode pseudo-file
|
||||
* and this information will be used when we invalidate userspace
|
||||
* mappings with drm_vma_node_unmap() (called from vc4_gem_purge()).
|
||||
*/
|
||||
vm_pgoff = vma->vm_pgoff;
|
||||
vma->vm_pgoff = 0;
|
||||
ret = dma_mmap_wc(bo->base.base.dev->dev, vma, bo->base.vaddr,
|
||||
bo->base.paddr, vma->vm_end - vma->vm_start);
|
||||
vma->vm_pgoff = vm_pgoff;
|
||||
|
||||
if (ret)
|
||||
drm_gem_vm_close(vma);
|
||||
|
||||
@ -580,6 +847,8 @@ int vc4_create_bo_ioctl(struct drm_device *dev, void *data,
|
||||
if (IS_ERR(bo))
|
||||
return PTR_ERR(bo);
|
||||
|
||||
bo->madv = VC4_MADV_WILLNEED;
|
||||
|
||||
ret = drm_gem_handle_create(file_priv, &bo->base.base, &args->handle);
|
||||
drm_gem_object_put_unlocked(&bo->base.base);
|
||||
|
||||
@ -633,6 +902,8 @@ vc4_create_shader_bo_ioctl(struct drm_device *dev, void *data,
|
||||
if (IS_ERR(bo))
|
||||
return PTR_ERR(bo);
|
||||
|
||||
bo->madv = VC4_MADV_WILLNEED;
|
||||
|
||||
if (copy_from_user(bo->base.vaddr,
|
||||
(void __user *)(uintptr_t)args->data,
|
||||
args->size)) {
|
||||
|
@ -100,6 +100,7 @@ static int vc4_get_param_ioctl(struct drm_device *dev, void *data,
|
||||
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:
|
||||
args->value = true;
|
||||
break;
|
||||
default:
|
||||
@ -117,6 +118,12 @@ static void vc4_lastclose(struct drm_device *dev)
|
||||
drm_fbdev_cma_restore_mode(vc4->fbdev);
|
||||
}
|
||||
|
||||
static const struct vm_operations_struct vc4_vm_ops = {
|
||||
.fault = vc4_fault,
|
||||
.open = drm_gem_vm_open,
|
||||
.close = drm_gem_vm_close,
|
||||
};
|
||||
|
||||
static const struct file_operations vc4_drm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = drm_open,
|
||||
@ -142,6 +149,7 @@ static const struct drm_ioctl_desc vc4_drm_ioctls[] = {
|
||||
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),
|
||||
};
|
||||
|
||||
static struct drm_driver vc4_drm_driver = {
|
||||
@ -166,7 +174,7 @@ static struct drm_driver vc4_drm_driver = {
|
||||
|
||||
.gem_create_object = vc4_create_object,
|
||||
.gem_free_object_unlocked = vc4_free_object,
|
||||
.gem_vm_ops = &drm_gem_cma_vm_ops,
|
||||
.gem_vm_ops = &vc4_vm_ops,
|
||||
|
||||
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
|
||||
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
|
||||
|
@ -74,6 +74,19 @@ struct vc4_dev {
|
||||
/* Protects bo_cache and bo_labels. */
|
||||
struct mutex bo_lock;
|
||||
|
||||
/* Purgeable BO pool. All BOs in this pool can have their memory
|
||||
* reclaimed if the driver is unable to allocate new BOs. We also
|
||||
* keep stats related to the purge mechanism here.
|
||||
*/
|
||||
struct {
|
||||
struct list_head list;
|
||||
unsigned int num;
|
||||
size_t size;
|
||||
unsigned int purged_num;
|
||||
size_t purged_size;
|
||||
struct mutex lock;
|
||||
} purgeable;
|
||||
|
||||
uint64_t dma_fence_context;
|
||||
|
||||
/* Sequence number for the last job queued in bin_job_list.
|
||||
@ -192,6 +205,16 @@ struct vc4_bo {
|
||||
* for user-allocated labels.
|
||||
*/
|
||||
int label;
|
||||
|
||||
/* Count the number of active users. This is needed to determine
|
||||
* whether we can move the BO to the purgeable list or not (when the BO
|
||||
* is used by the GPU or the display engine we can't purge it).
|
||||
*/
|
||||
refcount_t usecnt;
|
||||
|
||||
/* Store purgeable/purged state here */
|
||||
u32 madv;
|
||||
struct mutex madv_lock;
|
||||
};
|
||||
|
||||
static inline struct vc4_bo *
|
||||
@ -503,6 +526,7 @@ int vc4_get_hang_state_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int vc4_label_bo_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int vc4_fault(struct vm_fault *vmf);
|
||||
int vc4_mmap(struct file *filp, struct vm_area_struct *vma);
|
||||
struct reservation_object *vc4_prime_res_obj(struct drm_gem_object *obj);
|
||||
int vc4_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);
|
||||
@ -513,6 +537,10 @@ void *vc4_prime_vmap(struct drm_gem_object *obj);
|
||||
int vc4_bo_cache_init(struct drm_device *dev);
|
||||
void vc4_bo_cache_destroy(struct drm_device *dev);
|
||||
int vc4_bo_stats_debugfs(struct seq_file *m, void *arg);
|
||||
int vc4_bo_inc_usecnt(struct vc4_bo *bo);
|
||||
void vc4_bo_dec_usecnt(struct vc4_bo *bo);
|
||||
void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo);
|
||||
void vc4_bo_remove_from_purgeable_pool(struct vc4_bo *bo);
|
||||
|
||||
/* vc4_crtc.c */
|
||||
extern struct platform_driver vc4_crtc_driver;
|
||||
@ -557,6 +585,8 @@ void vc4_job_handle_completed(struct vc4_dev *vc4);
|
||||
int vc4_queue_seqno_cb(struct drm_device *dev,
|
||||
struct vc4_seqno_cb *cb, uint64_t seqno,
|
||||
void (*func)(struct vc4_seqno_cb *cb));
|
||||
int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
|
||||
/* vc4_hdmi.c */
|
||||
extern struct platform_driver vc4_hdmi_driver;
|
||||
|
@ -1360,6 +1360,27 @@ static void dsi_handle_error(struct vc4_dsi *dsi,
|
||||
*ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initial handler for port 1 where we need the reg_dma workaround.
|
||||
* The register DMA writes sleep, so we can't do it in the top half.
|
||||
* Instead we use IRQF_ONESHOT so that the IRQ gets disabled in the
|
||||
* parent interrupt contrller until our interrupt thread is done.
|
||||
*/
|
||||
static irqreturn_t vc4_dsi_irq_defer_to_thread_handler(int irq, void *data)
|
||||
{
|
||||
struct vc4_dsi *dsi = data;
|
||||
u32 stat = DSI_PORT_READ(INT_STAT);
|
||||
|
||||
if (!stat)
|
||||
return IRQ_NONE;
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Normal IRQ handler for port 0, or the threaded IRQ handler for port
|
||||
* 1 where we need the reg_dma workaround.
|
||||
*/
|
||||
static irqreturn_t vc4_dsi_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct vc4_dsi *dsi = data;
|
||||
@ -1539,8 +1560,15 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
|
||||
/* Clear any existing interrupt state. */
|
||||
DSI_PORT_WRITE(INT_STAT, DSI_PORT_READ(INT_STAT));
|
||||
|
||||
ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
|
||||
vc4_dsi_irq_handler, 0, "vc4 dsi", dsi);
|
||||
if (dsi->reg_dma_mem)
|
||||
ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
|
||||
vc4_dsi_irq_defer_to_thread_handler,
|
||||
vc4_dsi_irq_handler,
|
||||
IRQF_ONESHOT,
|
||||
"vc4 dsi", dsi);
|
||||
else
|
||||
ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
|
||||
vc4_dsi_irq_handler, 0, "vc4 dsi", dsi);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "Failed to get interrupt: %d\n", ret);
|
||||
|
@ -188,11 +188,22 @@ vc4_save_hang_state(struct drm_device *dev)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < exec[i]->bo_count; j++) {
|
||||
bo = to_vc4_bo(&exec[i]->bo[j]->base);
|
||||
|
||||
/* Retain BOs just in case they were marked purgeable.
|
||||
* This prevents the BO from being purged before
|
||||
* someone had a chance to dump the hang state.
|
||||
*/
|
||||
WARN_ON(!refcount_read(&bo->usecnt));
|
||||
refcount_inc(&bo->usecnt);
|
||||
drm_gem_object_get(&exec[i]->bo[j]->base);
|
||||
kernel_state->bo[j + prev_idx] = &exec[i]->bo[j]->base;
|
||||
}
|
||||
|
||||
list_for_each_entry(bo, &exec[i]->unref_list, unref_head) {
|
||||
/* No need to retain BOs coming from the ->unref_list
|
||||
* because they are naturally unpurgeable.
|
||||
*/
|
||||
drm_gem_object_get(&bo->base.base);
|
||||
kernel_state->bo[j + prev_idx] = &bo->base.base;
|
||||
j++;
|
||||
@ -233,6 +244,26 @@ vc4_save_hang_state(struct drm_device *dev)
|
||||
state->fdbgs = V3D_READ(V3D_FDBGS);
|
||||
state->errstat = V3D_READ(V3D_ERRSTAT);
|
||||
|
||||
/* We need to turn purgeable BOs into unpurgeable ones so that
|
||||
* userspace has a chance to dump the hang state before the kernel
|
||||
* decides to purge those BOs.
|
||||
* Note that BO consistency at dump time cannot be guaranteed. For
|
||||
* example, if the owner of these BOs decides to re-use them or mark
|
||||
* them purgeable again there's nothing we can do to prevent it.
|
||||
*/
|
||||
for (i = 0; i < kernel_state->user_state.bo_count; i++) {
|
||||
struct vc4_bo *bo = to_vc4_bo(kernel_state->bo[i]);
|
||||
|
||||
if (bo->madv == __VC4_MADV_NOTSUPP)
|
||||
continue;
|
||||
|
||||
mutex_lock(&bo->madv_lock);
|
||||
if (!WARN_ON(bo->madv == __VC4_MADV_PURGED))
|
||||
bo->madv = VC4_MADV_WILLNEED;
|
||||
refcount_dec(&bo->usecnt);
|
||||
mutex_unlock(&bo->madv_lock);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&vc4->job_lock, irqflags);
|
||||
if (vc4->hang_state) {
|
||||
spin_unlock_irqrestore(&vc4->job_lock, irqflags);
|
||||
@ -639,9 +670,6 @@ vc4_queue_submit(struct drm_device *dev, struct vc4_exec_info *exec,
|
||||
* The command validator needs to reference BOs by their index within
|
||||
* the submitted job's BO list. This does the validation of the job's
|
||||
* BO list and reference counting for the lifetime of the job.
|
||||
*
|
||||
* Note that this function doesn't need to unreference the BOs on
|
||||
* failure, because that will happen at vc4_complete_exec() time.
|
||||
*/
|
||||
static int
|
||||
vc4_cl_lookup_bos(struct drm_device *dev,
|
||||
@ -693,16 +721,47 @@ vc4_cl_lookup_bos(struct drm_device *dev,
|
||||
DRM_DEBUG("Failed to look up GEM BO %d: %d\n",
|
||||
i, handles[i]);
|
||||
ret = -EINVAL;
|
||||
spin_unlock(&file_priv->table_lock);
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
|
||||
drm_gem_object_get(bo);
|
||||
exec->bo[i] = (struct drm_gem_cma_object *)bo;
|
||||
}
|
||||
spin_unlock(&file_priv->table_lock);
|
||||
|
||||
if (ret)
|
||||
goto fail_put_bo;
|
||||
|
||||
for (i = 0; i < exec->bo_count; i++) {
|
||||
ret = vc4_bo_inc_usecnt(to_vc4_bo(&exec->bo[i]->base));
|
||||
if (ret)
|
||||
goto fail_dec_usecnt;
|
||||
}
|
||||
|
||||
kvfree(handles);
|
||||
return 0;
|
||||
|
||||
fail_dec_usecnt:
|
||||
/* Decrease usecnt on acquired objects.
|
||||
* We cannot rely on vc4_complete_exec() to release resources here,
|
||||
* because vc4_complete_exec() has no information about which BO has
|
||||
* had its ->usecnt incremented.
|
||||
* To make things easier we just free everything explicitly and set
|
||||
* exec->bo to NULL so that vc4_complete_exec() skips the 'BO release'
|
||||
* step.
|
||||
*/
|
||||
for (i-- ; i >= 0; i--)
|
||||
vc4_bo_dec_usecnt(to_vc4_bo(&exec->bo[i]->base));
|
||||
|
||||
fail_put_bo:
|
||||
/* Release any reference to acquired objects. */
|
||||
for (i = 0; i < exec->bo_count && exec->bo[i]; i++)
|
||||
drm_gem_object_put_unlocked(&exec->bo[i]->base);
|
||||
|
||||
fail:
|
||||
kvfree(handles);
|
||||
kvfree(exec->bo);
|
||||
exec->bo = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -833,8 +892,12 @@ vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec)
|
||||
dma_fence_signal(exec->fence);
|
||||
|
||||
if (exec->bo) {
|
||||
for (i = 0; i < exec->bo_count; i++)
|
||||
for (i = 0; i < exec->bo_count; i++) {
|
||||
struct vc4_bo *bo = to_vc4_bo(&exec->bo[i]->base);
|
||||
|
||||
vc4_bo_dec_usecnt(bo);
|
||||
drm_gem_object_put_unlocked(&exec->bo[i]->base);
|
||||
}
|
||||
kvfree(exec->bo);
|
||||
}
|
||||
|
||||
@ -1098,6 +1161,9 @@ vc4_gem_init(struct drm_device *dev)
|
||||
INIT_WORK(&vc4->job_done_work, vc4_job_done_work);
|
||||
|
||||
mutex_init(&vc4->power_lock);
|
||||
|
||||
INIT_LIST_HEAD(&vc4->purgeable.list);
|
||||
mutex_init(&vc4->purgeable.lock);
|
||||
}
|
||||
|
||||
void
|
||||
@ -1121,3 +1187,81 @@ vc4_gem_destroy(struct drm_device *dev)
|
||||
if (vc4->hang_state)
|
||||
vc4_free_hang_state(dev, vc4->hang_state);
|
||||
}
|
||||
|
||||
int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
{
|
||||
struct drm_vc4_gem_madvise *args = data;
|
||||
struct drm_gem_object *gem_obj;
|
||||
struct vc4_bo *bo;
|
||||
int ret;
|
||||
|
||||
switch (args->madv) {
|
||||
case VC4_MADV_DONTNEED:
|
||||
case VC4_MADV_WILLNEED:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (args->pad != 0)
|
||||
return -EINVAL;
|
||||
|
||||
gem_obj = drm_gem_object_lookup(file_priv, args->handle);
|
||||
if (!gem_obj) {
|
||||
DRM_DEBUG("Failed to look up GEM BO %d\n", args->handle);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
bo = to_vc4_bo(gem_obj);
|
||||
|
||||
/* Only BOs exposed to userspace can be purged. */
|
||||
if (bo->madv == __VC4_MADV_NOTSUPP) {
|
||||
DRM_DEBUG("madvise not supported on this BO\n");
|
||||
ret = -EINVAL;
|
||||
goto out_put_gem;
|
||||
}
|
||||
|
||||
/* Not sure it's safe to purge imported BOs. Let's just assume it's
|
||||
* not until proven otherwise.
|
||||
*/
|
||||
if (gem_obj->import_attach) {
|
||||
DRM_DEBUG("madvise not supported on imported BOs\n");
|
||||
ret = -EINVAL;
|
||||
goto out_put_gem;
|
||||
}
|
||||
|
||||
mutex_lock(&bo->madv_lock);
|
||||
|
||||
if (args->madv == VC4_MADV_DONTNEED && bo->madv == VC4_MADV_WILLNEED &&
|
||||
!refcount_read(&bo->usecnt)) {
|
||||
/* If the BO is about to be marked as purgeable, is not used
|
||||
* and is not already purgeable or purged, add it to the
|
||||
* purgeable list.
|
||||
*/
|
||||
vc4_bo_add_to_purgeable_pool(bo);
|
||||
} else if (args->madv == VC4_MADV_WILLNEED &&
|
||||
bo->madv == VC4_MADV_DONTNEED &&
|
||||
!refcount_read(&bo->usecnt)) {
|
||||
/* The BO has not been purged yet, just remove it from
|
||||
* the purgeable list.
|
||||
*/
|
||||
vc4_bo_remove_from_purgeable_pool(bo);
|
||||
}
|
||||
|
||||
/* Save the purged state. */
|
||||
args->retained = bo->madv != __VC4_MADV_PURGED;
|
||||
|
||||
/* Update internal madv state only if the bo was not purged. */
|
||||
if (bo->madv != __VC4_MADV_PURGED)
|
||||
bo->madv = args->madv;
|
||||
|
||||
mutex_unlock(&bo->madv_lock);
|
||||
|
||||
ret = 0;
|
||||
|
||||
out_put_gem:
|
||||
drm_gem_object_put_unlocked(gem_obj);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
#include <drm/drm_plane_helper.h>
|
||||
|
||||
#include "uapi/drm/vc4_drm.h"
|
||||
#include "vc4_drv.h"
|
||||
#include "vc4_regs.h"
|
||||
|
||||
@ -774,21 +775,40 @@ static int vc4_prepare_fb(struct drm_plane *plane,
|
||||
{
|
||||
struct vc4_bo *bo;
|
||||
struct dma_fence *fence;
|
||||
int ret;
|
||||
|
||||
if ((plane->state->fb == state->fb) || !state->fb)
|
||||
return 0;
|
||||
|
||||
bo = to_vc4_bo(&drm_fb_cma_get_gem_obj(state->fb, 0)->base);
|
||||
|
||||
ret = vc4_bo_inc_usecnt(bo);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fence = reservation_object_get_excl_rcu(bo->resv);
|
||||
drm_atomic_set_fence_for_plane(state, fence);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vc4_cleanup_fb(struct drm_plane *plane,
|
||||
struct drm_plane_state *state)
|
||||
{
|
||||
struct vc4_bo *bo;
|
||||
|
||||
if (plane->state->fb == state->fb || !state->fb)
|
||||
return;
|
||||
|
||||
bo = to_vc4_bo(&drm_fb_cma_get_gem_obj(state->fb, 0)->base);
|
||||
vc4_bo_dec_usecnt(bo);
|
||||
}
|
||||
|
||||
static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = {
|
||||
.atomic_check = vc4_plane_atomic_check,
|
||||
.atomic_update = vc4_plane_atomic_update,
|
||||
.prepare_fb = vc4_prepare_fb,
|
||||
.cleanup_fb = vc4_cleanup_fb,
|
||||
};
|
||||
|
||||
static void vc4_plane_destroy(struct drm_plane *plane)
|
||||
|
@ -1402,29 +1402,14 @@ static struct miscdevice vga_arb_device = {
|
||||
MISC_DYNAMIC_MINOR, "vga_arbiter", &vga_arb_device_fops
|
||||
};
|
||||
|
||||
static int __init vga_arb_device_init(void)
|
||||
static void __init vga_arb_select_default_device(void)
|
||||
{
|
||||
int rc;
|
||||
struct pci_dev *pdev;
|
||||
struct vga_device *vgadev;
|
||||
|
||||
rc = misc_register(&vga_arb_device);
|
||||
if (rc < 0)
|
||||
pr_err("error %d registering device\n", rc);
|
||||
|
||||
bus_register_notifier(&pci_bus_type, &pci_notifier);
|
||||
|
||||
/* We add all pci devices satisfying vga class in the arbiter by
|
||||
* default */
|
||||
pdev = NULL;
|
||||
while ((pdev =
|
||||
pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
|
||||
PCI_ANY_ID, pdev)) != NULL)
|
||||
vga_arbiter_add_pci_device(pdev);
|
||||
|
||||
#if defined(CONFIG_X86) || defined(CONFIG_IA64)
|
||||
list_for_each_entry(vgadev, &vga_list, list) {
|
||||
struct device *dev = &vgadev->pdev->dev;
|
||||
#if defined(CONFIG_X86) || defined(CONFIG_IA64)
|
||||
/*
|
||||
* Override vga_arbiter_add_pci_device()'s I/O based detection
|
||||
* as it may take the wrong device (e.g. on Apple system under
|
||||
@ -1461,13 +1446,66 @@ static int __init vga_arb_device_init(void)
|
||||
vgaarb_info(dev, "overriding boot device\n");
|
||||
vga_set_default_device(vgadev->pdev);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!vga_default_device()) {
|
||||
list_for_each_entry(vgadev, &vga_list, list) {
|
||||
struct device *dev = &vgadev->pdev->dev;
|
||||
u16 cmd;
|
||||
|
||||
pdev = vgadev->pdev;
|
||||
pci_read_config_word(pdev, PCI_COMMAND, &cmd);
|
||||
if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
|
||||
vgaarb_info(dev, "setting as boot device (VGA legacy resources not available)\n");
|
||||
vga_set_default_device(pdev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!vga_default_device()) {
|
||||
vgadev = list_first_entry_or_null(&vga_list,
|
||||
struct vga_device, list);
|
||||
if (vgadev) {
|
||||
struct device *dev = &vgadev->pdev->dev;
|
||||
vgaarb_info(dev, "setting as boot device (VGA legacy resources not available)\n");
|
||||
vga_set_default_device(vgadev->pdev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int __init vga_arb_device_init(void)
|
||||
{
|
||||
int rc;
|
||||
struct pci_dev *pdev;
|
||||
struct vga_device *vgadev;
|
||||
|
||||
rc = misc_register(&vga_arb_device);
|
||||
if (rc < 0)
|
||||
pr_err("error %d registering device\n", rc);
|
||||
|
||||
bus_register_notifier(&pci_bus_type, &pci_notifier);
|
||||
|
||||
/* We add all PCI devices satisfying VGA class in the arbiter by
|
||||
* default */
|
||||
pdev = NULL;
|
||||
while ((pdev =
|
||||
pci_get_subsys(PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
|
||||
PCI_ANY_ID, pdev)) != NULL)
|
||||
vga_arbiter_add_pci_device(pdev);
|
||||
|
||||
list_for_each_entry(vgadev, &vga_list, list) {
|
||||
struct device *dev = &vgadev->pdev->dev;
|
||||
|
||||
if (vgadev->bridge_has_one_vga)
|
||||
vgaarb_info(dev, "bridge control possible\n");
|
||||
else
|
||||
vgaarb_info(dev, "no bridge control possible\n");
|
||||
}
|
||||
|
||||
vga_arb_select_default_device();
|
||||
|
||||
pr_info("loaded\n");
|
||||
return rc;
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ struct dma_fence_cb {
|
||||
* implementation know that there is another driver waiting on
|
||||
* the signal (ie. hw->sw case).
|
||||
*
|
||||
* This function can be called called from atomic context, but not
|
||||
* This function can be called from atomic context, but not
|
||||
* from irq context, so normal spinlocks can be used.
|
||||
*
|
||||
* A return value of false indicates the fence already passed,
|
||||
|
@ -41,6 +41,7 @@ extern "C" {
|
||||
#define DRM_VC4_SET_TILING 0x08
|
||||
#define DRM_VC4_GET_TILING 0x09
|
||||
#define DRM_VC4_LABEL_BO 0x0a
|
||||
#define DRM_VC4_GEM_MADVISE 0x0b
|
||||
|
||||
#define DRM_IOCTL_VC4_SUBMIT_CL DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_SUBMIT_CL, struct drm_vc4_submit_cl)
|
||||
#define DRM_IOCTL_VC4_WAIT_SEQNO DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_WAIT_SEQNO, struct drm_vc4_wait_seqno)
|
||||
@ -53,6 +54,7 @@ extern "C" {
|
||||
#define DRM_IOCTL_VC4_SET_TILING DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_SET_TILING, struct drm_vc4_set_tiling)
|
||||
#define DRM_IOCTL_VC4_GET_TILING DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_GET_TILING, struct drm_vc4_get_tiling)
|
||||
#define DRM_IOCTL_VC4_LABEL_BO DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_LABEL_BO, struct drm_vc4_label_bo)
|
||||
#define DRM_IOCTL_VC4_GEM_MADVISE DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_GEM_MADVISE, struct drm_vc4_gem_madvise)
|
||||
|
||||
struct drm_vc4_submit_rcl_surface {
|
||||
__u32 hindex; /* Handle index, or ~0 if not present. */
|
||||
@ -305,6 +307,7 @@ struct drm_vc4_get_hang_state {
|
||||
#define DRM_VC4_PARAM_SUPPORTS_ETC1 4
|
||||
#define DRM_VC4_PARAM_SUPPORTS_THREADED_FS 5
|
||||
#define DRM_VC4_PARAM_SUPPORTS_FIXED_RCL_ORDER 6
|
||||
#define DRM_VC4_PARAM_SUPPORTS_MADVISE 7
|
||||
|
||||
struct drm_vc4_get_param {
|
||||
__u32 param;
|
||||
@ -333,6 +336,22 @@ struct drm_vc4_label_bo {
|
||||
__u64 name;
|
||||
};
|
||||
|
||||
/*
|
||||
* States prefixed with '__' are internal states and cannot be passed to the
|
||||
* DRM_IOCTL_VC4_GEM_MADVISE ioctl.
|
||||
*/
|
||||
#define VC4_MADV_WILLNEED 0
|
||||
#define VC4_MADV_DONTNEED 1
|
||||
#define __VC4_MADV_PURGED 2
|
||||
#define __VC4_MADV_NOTSUPP 3
|
||||
|
||||
struct drm_vc4_gem_madvise {
|
||||
__u32 handle;
|
||||
__u32 madv;
|
||||
__u32 retained;
|
||||
__u32 pad;
|
||||
};
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user