Commit fef1aa48 authored by Dave Airlie's avatar Dave Airlie

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
  ...
parents 3b677e43 af0c8c10
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,
......
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) {
switch (channel) {
case 0:
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
SUN4I_TCON0_CTL_TCON_ENABLE, 0);
clk_disable_unprepare(tcon->dclk);
SUN4I_TCON0_CTL_TCON_ENABLE,
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, 0);
clk_disable_unprepare(tcon->sclk1);
if (enabled)
clk_prepare_enable(clk);
else
clk_disable_unprepare(clk);
}
EXPORT_SYMBOL(sun4i_tcon_channel_disable);
void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
const struct drm_encoder *encoder,
bool enabled)
{
DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel);
int channel;
/* Enable the TCON's channel */
if (channel == 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);
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;
}
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);
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;
error:
kfree(block);
kfree(rbuf);
return NULL;
kfree(read_buff);
return true;
}
static int udl_get_modes(struct drm_connector *connector)
static bool udl_get_edid(struct udl_device *udl, u8 **result_buff,
int *result_buff_size)
{
struct udl_device *udl = connector->dev->dev_private;
struct edid *edid;
int ret;
edid = (struct edid *)udl_get_edid(udl);
if (!edid) {
drm_mode_connector_update_edid_property(connector, NULL);
return 0;
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;
}
/*
* 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;
kfree(block_buff);
return false;
}
static int udl_get_modes(struct drm_connector *connector)
{
struct udl_drm_connector *udl_connector =
container_of(connector,
struct udl_drm_connector,
connector);
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;
}
#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)) {
/*
* 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);
}
}
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment