diff options
author | Dave Airlie <airlied@redhat.com> | 2017-05-18 12:57:06 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2017-05-18 12:57:06 +1000 |
commit | e98c58e55f68f8785aebfab1f8c9a03d8de0afe1 (patch) | |
tree | 8357e8fda6efb0867ac39fc6b9211a579721d00a /drivers/gpu/drm | |
parent | 2ea659a9ef488125eb46da6eb571de5eae5c43f6 (diff) | |
parent | 9cf8f5802f39d9991158b29033c852bccfc3a4d4 (diff) |
Merge tag 'drm-misc-next-2017-05-16' of git://anongit.freedesktop.org/git/drm-misc into drm-next
UAPI Changes:
- Return -ENODEV instead of -ENXIO when creating cma fb w/o valid gem (Daniel)
- Add aspect ratio and custom scaling propertis to connector state (Maarten)
Cross-subsystem Changes:
- None
Core Changes:
- Add Laurent as bridge reviewer and Andrzej as bridge maintainer (Archit)
- Maintain new STM driver through -misc (Yannick)
- Misc doc improvements (as is tradition) (Daniel)
- Add driver-private objects to atomic state (Dhinakaran)
- Deprecate preclose hook in modern drivers (use postclose) (Daniel)
- Add hwmode to vblank struct. This fixes mode access in irq context and reduced
a bunch of boilerplate (Daniel)
Driver Changes:
- vc4: Add out-fence support to vc4 V3D rendering (Eric)
- stm: Add stm32f429 display hw and am-480272h3tmqw-t01h panel support (Yannick)
- vc4: Remove 256MB cma limit from vc4 (Eric)
- dw-hdmi: Disable audio when inactive, instead of always enabled (Romain)
- zte: Add support for VGA to the ZTE driver (Shawn)
- i915: Track DP MST bandwidth and check it in atomic_check (Dhinakaran)
- vgem: Enable gem dmabuf import iface to facilitate ion testing (Laura)
- vc4: Add support for Cygnus (new dt compat string + couple bug fixes) (Eric)
- pl111: Add driver for pl111 CLCD display controller (Eric/Tom)
- vgem: Subclass drm_device instead of standalone platform device (Chris)
Cc: Archit Taneja <architt@codeaurora.org>
Cc: Eric Anholt <eric@anholt.net>
Cc: Yannick Fertre <yannick.fertre@st.com>
Cc: Romain Perier <romain.perier@collabora.com>
Cc: Navare, Manasi D <manasi.d.navare@intel.com>
Cc: Shawn Guo <shawn.guo@linaro.org>
Cc: Dhinakaran Pandiyan <dhinakaran.pandiyan@intel.com>
Cc: Laura Abbott <labbott@redhat.com>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Tom Cooksey <tom.cooksey@arm.com>
Cc: Daniel Vetter <daniel.vetter@intel.com>
Cc: Chris Wilson <chris@chris-wilson.co.uk>
* tag 'drm-misc-next-2017-05-16' of git://anongit.freedesktop.org/git/drm-misc: (72 commits)
drm: add missing declaration to drm_blend.h
drm/dp: Wait up all outstanding tx waiters
drm/dp: Read the tx msg state once after checking for an event
drm/prime: Forward declare struct device
drm/vblank: Lock down vblank->hwmode more
drm/vblank: drop the mode argument from drm_calc_vbltimestamp_from_scanoutpos
drm/vblank: Add FIXME comments about moving the vblank ts hooks
drm/vblank: Switch to bool in_vblank_irq in get_vblank_timestamp
drm/vblank: Switch drm_driver->get_vblank_timestamp to return a bool
drm/vgem: Convert to a struct drm_device subclass
gpu: drm: gma500: remove dead code
drm/sti: Adjust two checks for null pointers in sti_hqvdp_probe()
drm/sti: Fix typos in a comment line
drm/sti: Fix a typo in a comment line
drm/sti: Replace 17 seq_puts() calls by seq_putc()
drm/sti: Reduce function calls for sequence output at five places
drm/sti: use seq_puts to display a string
drm: Nerf the preclose callback for modern drivers
drm/exynos: Merge pre/postclose hooks
drm/tegra: switch to postclose
...
Diffstat (limited to 'drivers/gpu/drm')
83 files changed, 4396 insertions, 761 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 78d7fc0ebb57..83cb2a88c204 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -246,6 +246,8 @@ source "drivers/gpu/drm/fsl-dcu/Kconfig" source "drivers/gpu/drm/tegra/Kconfig" +source "drivers/gpu/drm/stm/Kconfig" + source "drivers/gpu/drm/panel/Kconfig" source "drivers/gpu/drm/bridge/Kconfig" @@ -274,6 +276,8 @@ source "drivers/gpu/drm/meson/Kconfig" source "drivers/gpu/drm/tinydrm/Kconfig" +source "drivers/gpu/drm/pl111/Kconfig" + # Keep legacy drivers last menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 59f0f9b696eb..c156fecfb362 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_DRM_BOCHS) += bochs/ obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio/ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ +obj-$(CONFIG_DRM_STM) += stm/ obj-$(CONFIG_DRM_STI) += sti/ obj-$(CONFIG_DRM_IMX) += imx/ obj-$(CONFIG_DRM_MEDIATEK) += mediatek/ @@ -96,3 +97,4 @@ obj-y += hisilicon/ obj-$(CONFIG_DRM_ZTE) += zte/ obj-$(CONFIG_DRM_MXSFB) += mxsfb/ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/ +obj-$(CONFIG_DRM_PL111) += pl111/ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 833c3c16501a..67cdab9241a4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -1912,10 +1912,6 @@ int amdgpu_device_resume(struct drm_device *dev, bool resume, bool fbcon); u32 amdgpu_get_vblank_counter_kms(struct drm_device *dev, unsigned int pipe); int amdgpu_enable_vblank_kms(struct drm_device *dev, unsigned int pipe); void amdgpu_disable_vblank_kms(struct drm_device *dev, unsigned int pipe); -int amdgpu_get_vblank_timestamp_kms(struct drm_device *dev, unsigned int pipe, - int *max_error, - struct timeval *vblank_time, - unsigned flags); long amdgpu_kms_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index f2d705e6a75a..5cb8f3e68447 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -715,6 +715,16 @@ static const struct file_operations amdgpu_driver_kms_fops = { #endif }; +static bool +amdgpu_get_crtc_scanout_position(struct drm_device *dev, unsigned int pipe, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) +{ + return amdgpu_get_crtc_scanoutpos(dev, pipe, 0, vpos, hpos, + stime, etime, mode); +} + static struct drm_driver kms_driver = { .driver_features = DRIVER_USE_AGP | @@ -729,8 +739,8 @@ static struct drm_driver kms_driver = { .get_vblank_counter = amdgpu_get_vblank_counter_kms, .enable_vblank = amdgpu_enable_vblank_kms, .disable_vblank = amdgpu_disable_vblank_kms, - .get_vblank_timestamp = amdgpu_get_vblank_timestamp_kms, - .get_scanout_position = amdgpu_get_crtc_scanoutpos, + .get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos, + .get_scanout_position = amdgpu_get_crtc_scanout_position, #if defined(CONFIG_DEBUG_FS) .debugfs_init = amdgpu_debugfs_init, #endif diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 96c341670782..dca4be970d13 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -945,47 +945,6 @@ void amdgpu_disable_vblank_kms(struct drm_device *dev, unsigned int pipe) amdgpu_irq_put(adev, &adev->crtc_irq, idx); } -/** - * amdgpu_get_vblank_timestamp_kms - get vblank timestamp - * - * @dev: drm dev pointer - * @crtc: crtc to get the timestamp for - * @max_error: max error - * @vblank_time: time value - * @flags: flags passed to the driver - * - * Gets the timestamp on the requested crtc based on the - * scanout position. (all asics). - * Returns postive status flags on success, negative error on failure. - */ -int amdgpu_get_vblank_timestamp_kms(struct drm_device *dev, unsigned int pipe, - int *max_error, - struct timeval *vblank_time, - unsigned flags) -{ - struct drm_crtc *crtc; - struct amdgpu_device *adev = dev->dev_private; - - if (pipe >= dev->num_crtcs) { - DRM_ERROR("Invalid crtc %u\n", pipe); - return -EINVAL; - } - - /* Get associated drm_crtc: */ - crtc = &adev->mode_info.crtcs[pipe]->base; - if (!crtc) { - /* This can occur on driver load if some component fails to - * initialize completely and driver is unloaded */ - DRM_ERROR("Uninitialized crtc %d\n", pipe); - return -EINVAL; - } - - /* Helper routine in DRM core does all the work: */ - return drm_calc_vbltimestamp_from_scanoutpos(dev, pipe, max_error, - vblank_time, flags, - &crtc->hwmode); -} - const struct drm_ioctl_desc amdgpu_ioctls_kms[] = { DRM_IOCTL_DEF_DRV(AMDGPU_GEM_CREATE, amdgpu_gem_create_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), DRM_IOCTL_DEF_DRV(AMDGPU_CTX, amdgpu_ctx_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h index dbd10618ec20..43a9d3aec6c4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -534,6 +534,9 @@ struct amdgpu_framebuffer { ((em) == ATOM_ENCODER_MODE_DP_MST)) /* Driver internal use only flags of amdgpu_get_crtc_scanoutpos() */ +#define DRM_SCANOUTPOS_VALID (1 << 0) +#define DRM_SCANOUTPOS_IN_VBLANK (1 << 1) +#define DRM_SCANOUTPOS_ACCURATE (1 << 2) #define USE_REAL_VBLANKSTART (1 << 30) #define GET_DISTANCE_TO_VBLANKSTART (1 << 31) diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c index 9126d0306ab5..9b87067c022c 100644 --- a/drivers/gpu/drm/bridge/sii902x.c +++ b/drivers/gpu/drm/bridge/sii902x.c @@ -160,7 +160,7 @@ static int sii902x_get_modes(struct drm_connector *connector) time_before(jiffies, timeout)); if (!(status & SII902X_SYS_CTRL_DDC_BUS_GRTD)) { - dev_err(&sii902x->i2c->dev, "failed to acquire the i2c bus"); + dev_err(&sii902x->i2c->dev, "failed to acquire the i2c bus\n"); return -ETIMEDOUT; } @@ -202,7 +202,7 @@ static int sii902x_get_modes(struct drm_connector *connector) if (status & (SII902X_SYS_CTRL_DDC_BUS_REQ | SII902X_SYS_CTRL_DDC_BUS_GRTD)) { - dev_err(&sii902x->i2c->dev, "failed to release the i2c bus"); + dev_err(&sii902x->i2c->dev, "failed to release the i2c bus\n"); return -ETIMEDOUT; } @@ -298,7 +298,7 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge) if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) { dev_err(&sii902x->i2c->dev, - "sii902x driver is only compatible with DRM devices supporting atomic updates"); + "sii902x driver is only compatible with DRM devices supporting atomic updates\n"); return -ENOTSUPP; } diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 4e1f54a675d8..8737de8c1c52 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -173,6 +173,8 @@ struct dw_hdmi { unsigned int reg_shift; struct regmap *regm; + void (*enable_audio)(struct dw_hdmi *hdmi); + void (*disable_audio)(struct dw_hdmi *hdmi); }; #define HDMI_IH_PHY_STAT0_RX_SENSE \ @@ -542,13 +544,41 @@ void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) } EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate); +static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable) +{ + hdmi_modb(hdmi, enable ? 0 : HDMI_MC_CLKDIS_AUDCLK_DISABLE, + HDMI_MC_CLKDIS_AUDCLK_DISABLE, HDMI_MC_CLKDIS); +} + +static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); +} + +static void dw_hdmi_ahb_audio_disable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); +} + +static void dw_hdmi_i2s_audio_enable(struct dw_hdmi *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + hdmi_enable_audio_clk(hdmi, true); +} + +static void dw_hdmi_i2s_audio_disable(struct dw_hdmi *hdmi) +{ + hdmi_enable_audio_clk(hdmi, false); +} + void dw_hdmi_audio_enable(struct dw_hdmi *hdmi) { unsigned long flags; spin_lock_irqsave(&hdmi->audio_lock, flags); hdmi->audio_enable = true; - hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + if (hdmi->enable_audio) + hdmi->enable_audio(hdmi); spin_unlock_irqrestore(&hdmi->audio_lock, flags); } EXPORT_SYMBOL_GPL(dw_hdmi_audio_enable); @@ -559,7 +589,8 @@ void dw_hdmi_audio_disable(struct dw_hdmi *hdmi) spin_lock_irqsave(&hdmi->audio_lock, flags); hdmi->audio_enable = false; - hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); + if (hdmi->disable_audio) + hdmi->disable_audio(hdmi); spin_unlock_irqrestore(&hdmi->audio_lock, flags); } EXPORT_SYMBOL_GPL(dw_hdmi_audio_disable); @@ -1573,11 +1604,6 @@ static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi) HDMI_MC_FLOWCTRL); } -static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi) -{ - hdmi_modb(hdmi, 0, HDMI_MC_CLKDIS_AUDCLK_DISABLE, HDMI_MC_CLKDIS); -} - /* Workaround to clear the overflow condition */ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) { @@ -1691,7 +1717,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) /* HDMI Initialization Step E - Configure audio */ hdmi_clk_regenerator_update_pixel_clock(hdmi); - hdmi_enable_audio_clk(hdmi); + hdmi_enable_audio_clk(hdmi, true); } /* not for DVI mode */ @@ -2403,6 +2429,8 @@ __dw_hdmi_probe(struct platform_device *pdev, audio.irq = irq; audio.hdmi = hdmi; audio.eld = hdmi->connector.eld; + hdmi->enable_audio = dw_hdmi_ahb_audio_enable; + hdmi->disable_audio = dw_hdmi_ahb_audio_disable; pdevinfo.name = "dw-hdmi-ahb-audio"; pdevinfo.data = &audio; @@ -2415,6 +2443,8 @@ __dw_hdmi_probe(struct platform_device *pdev, audio.hdmi = hdmi; audio.write = hdmi_writeb; audio.read = hdmi_readb; + hdmi->enable_audio = dw_hdmi_i2s_audio_enable; + hdmi->disable_audio = dw_hdmi_i2s_audio_disable; pdevinfo.name = "dw-hdmi-i2s-audio"; pdevinfo.data = &audio; diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index f32506a7c1d6..cdec19a86af3 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -57,6 +57,7 @@ void drm_atomic_state_default_release(struct drm_atomic_state *state) kfree(state->connectors); kfree(state->crtcs); kfree(state->planes); + kfree(state->private_objs); } EXPORT_SYMBOL(drm_atomic_state_default_release); @@ -184,6 +185,17 @@ void drm_atomic_state_default_clear(struct drm_atomic_state *state) state->planes[i].ptr = NULL; state->planes[i].state = NULL; } + + for (i = 0; i < state->num_private_objs; i++) { + void *obj_state = state->private_objs[i].obj_state; + + state->private_objs[i].funcs->destroy_state(obj_state); + state->private_objs[i].obj = NULL; + state->private_objs[i].obj_state = NULL; + state->private_objs[i].funcs = NULL; + } + state->num_private_objs = 0; + } EXPORT_SYMBOL(drm_atomic_state_default_clear); @@ -425,7 +437,7 @@ drm_atomic_replace_property_blob(struct drm_property_blob **blob, } static int -drm_atomic_replace_property_blob_from_id(struct drm_crtc *crtc, +drm_atomic_replace_property_blob_from_id(struct drm_device *dev, struct drm_property_blob **blob, uint64_t blob_id, ssize_t expected_size, @@ -434,7 +446,7 @@ drm_atomic_replace_property_blob_from_id(struct drm_crtc *crtc, struct drm_property_blob *new_blob = NULL; if (blob_id != 0) { - new_blob = drm_property_lookup_blob(crtc->dev, blob_id); + new_blob = drm_property_lookup_blob(dev, blob_id); if (new_blob == NULL) return -EINVAL; @@ -483,7 +495,7 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc, drm_property_blob_put(mode); return ret; } else if (property == config->degamma_lut_property) { - ret = drm_atomic_replace_property_blob_from_id(crtc, + ret = drm_atomic_replace_property_blob_from_id(dev, &state->degamma_lut, val, -1, @@ -491,7 +503,7 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc, state->color_mgmt_changed |= replaced; return ret; } else if (property == config->ctm_property) { - ret = drm_atomic_replace_property_blob_from_id(crtc, + ret = drm_atomic_replace_property_blob_from_id(dev, &state->ctm, val, sizeof(struct drm_color_ctm), @@ -499,7 +511,7 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc, state->color_mgmt_changed |= replaced; return ret; } else if (property == config->gamma_lut_property) { - ret = drm_atomic_replace_property_blob_from_id(crtc, + ret = drm_atomic_replace_property_blob_from_id(dev, &state->gamma_lut, val, -1, @@ -978,6 +990,59 @@ static void drm_atomic_plane_print_state(struct drm_printer *p, } /** + * drm_atomic_get_private_obj_state - get private object state + * @state: global atomic state + * @obj: private object to get the state for + * @funcs: pointer to the struct of function pointers that identify the object + * type + * + * This function returns the private object state for the given private object, + * allocating the state if needed. It does not grab any locks as the caller is + * expected to care of any required locking. + * + * RETURNS: + * + * Either the allocated state or the error code encoded into a pointer. + */ +void * +drm_atomic_get_private_obj_state(struct drm_atomic_state *state, void *obj, + const struct drm_private_state_funcs *funcs) +{ + int index, num_objs, i; + size_t size; + struct __drm_private_objs_state *arr; + + for (i = 0; i < state->num_private_objs; i++) + if (obj == state->private_objs[i].obj && + state->private_objs[i].obj_state) + return state->private_objs[i].obj_state; + + num_objs = state->num_private_objs + 1; + size = sizeof(*state->private_objs) * num_objs; + arr = krealloc(state->private_objs, size, GFP_KERNEL); + if (!arr) + return ERR_PTR(-ENOMEM); + + state->private_objs = arr; + index = state->num_private_objs; + memset(&state->private_objs[index], 0, sizeof(*state->private_objs)); + + state->private_objs[index].obj_state = funcs->duplicate_state(state, obj); + if (!state->private_objs[index].obj_state) + return ERR_PTR(-ENOMEM); + + state->private_objs[index].obj = obj; + state->private_objs[index].funcs = funcs; + state->num_private_objs = num_objs; + + DRM_DEBUG_ATOMIC("Added new private object state %p to %p\n", + state->private_objs[index].obj_state, state); + + return state->private_objs[index].obj_state; +} +EXPORT_SYMBOL(drm_atomic_get_private_obj_state); + +/** * drm_atomic_get_connector_state - get connector state * @state: global atomic state object * @connector: connector to get state object for @@ -1123,6 +1188,10 @@ int drm_atomic_connector_set_property(struct drm_connector *connector, */ if (state->link_status != DRM_LINK_STATUS_GOOD) state->link_status = val; + } else if (property == config->aspect_ratio_property) { + state->picture_aspect_ratio = val; + } else if (property == connector->scaling_mode_property) { + state->scaling_mode = val; } else if (connector->funcs->atomic_set_property) { return connector->funcs->atomic_set_property(connector, state, property, val); @@ -1199,6 +1268,10 @@ drm_atomic_connector_get_property(struct drm_connector *connector, *val = state->tv.hue; } else if (property == config->link_status_property) { *val = state->link_status; + } else if (property == config->aspect_ratio_property) { + *val = state->picture_aspect_ratio; + } else if (property == connector->scaling_mode_property) { + *val = state->scaling_mode; } else if (connector->funcs->atomic_get_property) { return connector->funcs->atomic_get_property(connector, state, property, val); @@ -1618,7 +1691,7 @@ int drm_atomic_commit(struct drm_atomic_state *state) if (ret) return ret; - DRM_DEBUG_ATOMIC("commiting %p\n", state); + DRM_DEBUG_ATOMIC("committing %p\n", state); return config->funcs->atomic_commit(state->dev, state, false); } @@ -1647,7 +1720,7 @@ int drm_atomic_nonblocking_commit(struct drm_atomic_state *state) if (ret) return ret; - DRM_DEBUG_ATOMIC("commiting %p nonblocking\n", state); + DRM_DEBUG_ATOMIC("committing %p nonblocking\n", state); return config->funcs->atomic_commit(state->dev, state, true); } diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 8be9719284b0..6426339427a4 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1070,8 +1070,8 @@ EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables); * * Note that @pre_swap is needed since the point where we block for fences moves * around depending upon whether an atomic commit is blocking or - * non-blocking. For async commit all waiting needs to happen after - * drm_atomic_helper_swap_state() is called, but for synchronous commits we want + * non-blocking. For non-blocking commit all waiting needs to happen after + * drm_atomic_helper_swap_state() is called, but for blocking commits we want * to wait **before** we do anything that can't be easily rolled back. That is * before we call drm_atomic_helper_swap_state(). * @@ -2032,6 +2032,8 @@ void drm_atomic_helper_swap_state(struct drm_atomic_state *state, struct drm_plane *plane; struct drm_plane_state *old_plane_state, *new_plane_state; struct drm_crtc_commit *commit; + void *obj, *obj_state; + const struct drm_private_state_funcs *funcs; if (stall) { for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { @@ -2092,6 +2094,9 @@ void drm_atomic_helper_swap_state(struct drm_atomic_state *state, state->planes[i].state = old_plane_state; plane->state = new_plane_state; } + + __for_each_private_obj(state, obj, obj_state, i, funcs) + funcs->swap_state(obj, &state->private_objs[i].obj_state); } EXPORT_SYMBOL(drm_atomic_helper_swap_state); @@ -3517,7 +3522,8 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state); * * Implements support for legacy gamma correction table for drivers * that support color management through the DEGAMMA_LUT/GAMMA_LUT - * properties. + * properties. See drm_crtc_enable_color_mgmt() and the containing chapter for + * how the atomic color management and gamma tables work. */ int drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green, u16 *blue, diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c index 533f3a3e6877..3eda500fc005 100644 --- a/drivers/gpu/drm/drm_color_mgmt.c +++ b/drivers/gpu/drm/drm_color_mgmt.c @@ -43,7 +43,8 @@ * * Setting this to NULL (blob property value set to 0) means a * linear/pass-thru gamma table should be used. This is generally the - * driver boot-up state too. + * driver boot-up state too. Drivers can access this blob through + * &drm_crtc_state.degamma_lut. * * “DEGAMMA_LUT_SIZE”: * Unsinged range property to give the size of the lookup table to be set @@ -60,7 +61,8 @@ * * Setting this to NULL (blob property value set to 0) means a * unit/pass-thru matrix should be used. This is generally the driver - * boot-up state too. + * boot-up state too. Drivers can access the blob for the color conversion + * matrix through &drm_crtc_state.ctm. * * “GAMMA_LUT”: * Blob property to set the gamma lookup table (LUT) mapping pixel data @@ -72,7 +74,8 @@ * * Setting this to NULL (blob property value set to 0) means a * linear/pass-thru gamma table should be used. This is generally the - * driver boot-up state too. + * driver boot-up state too. Drivers can access this blob through + * &drm_crtc_state.gamma_lut. * * “GAMMA_LUT_SIZE”: * Unsigned range property to give the size of the lookup table to be set diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 9f847615ac74..5cd61aff7857 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -941,6 +941,10 @@ EXPORT_SYMBOL(drm_mode_create_tv_properties); * * Called by a driver the first time it's needed, must be attached to desired * connectors. + * + * Atomic drivers should use drm_connector_attach_scaling_mode_property() + * instead to correctly assign &drm_connector_state.picture_aspect_ratio + * in the atomic state. */ int drm_mode_create_scaling_mode_property(struct drm_device *dev) { @@ -961,6 +965,66 @@ int drm_mode_create_scaling_mode_property(struct drm_device *dev) EXPORT_SYMBOL(drm_mode_create_scaling_mode_property); /** + * drm_connector_attach_scaling_mode_property - attach atomic scaling mode property + * @connector: connector to attach scaling mode property on. + * @scaling_mode_mask: or'ed mask of BIT(%DRM_MODE_SCALE_\*). + * + * This is used to add support for scaling mode to atomic drivers. + * The scaling mode will be set to &drm_connector_state.picture_aspect_ratio + * and can be used from &drm_connector_helper_funcs->atomic_check for validation. + * + * This is the atomic version of drm_mode_create_scaling_mode_property(). + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_scaling_mode_property(struct drm_connector *connector, + u32 scaling_mode_mask) +{ + struct drm_device *dev = connector->dev; + struct drm_property *scaling_mode_property; + int i, j = 0; + const unsigned valid_scaling_mode_mask = + (1U << ARRAY_SIZE(drm_scaling_mode_enum_list)) - 1; + + if (WARN_ON(hweight32(scaling_mode_mask) < 2 || + scaling_mode_mask & ~valid_scaling_mode_mask)) + return -EINVAL; + + scaling_mode_property = + drm_property_create(dev, DRM_MODE_PROP_ENUM, "scaling mode", + hweight32(scaling_mode_mask)); + + if (!scaling_mode_property) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(drm_scaling_mode_enum_list); i++) { + int ret; + + if (!(BIT(i) & scaling_mode_mask)) + continue; + + ret = drm_property_add_enum(scaling_mode_property, j++, + drm_scaling_mode_enum_list[i].type, + drm_scaling_mode_enum_list[i].name); + + if (ret) { + drm_property_destroy(dev, scaling_mode_property); + + return ret; + } + } + + drm_object_attach_property(&connector->base, + scaling_mode_property, 0); + + connector->scaling_mode_property = scaling_mode_property; + + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_scaling_mode_property); + +/** * drm_mode_create_aspect_ratio_property - create aspect ratio property * @dev: DRM device * diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index d3fc7e4e85b7..222eb1a8549b 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -737,16 +737,16 @@ static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr, static bool check_txmsg_state(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_sideband_msg_tx *txmsg) { - bool ret; + unsigned int state; /* * All updates to txmsg->state are protected by mgr->qlock, and the two * cases we check here are terminal states. For those the barriers * provided by the wake_up/wait_event pair are enough. */ - ret = (txmsg->state == DRM_DP_SIDEBAND_TX_RX || - txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT); - return ret; + state = READ_ONCE(txmsg->state); + return (state == DRM_DP_SIDEBAND_TX_RX || + state == DRM_DP_SIDEBAND_TX_TIMEOUT); } static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb, @@ -855,7 +855,7 @@ static void drm_dp_destroy_mst_branch_device(struct kref *kref) mutex_unlock(&mstb->mgr->qlock); if (wake_tx) - wake_up(&mstb->mgr->tx_waitq); + wake_up_all(&mstb->mgr->tx_waitq); kref_put(kref, drm_dp_free_mst_branch_device); } @@ -1510,7 +1510,7 @@ static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr) if (txmsg->seqno != -1) txmsg->dst->tx_slots[txmsg->seqno] = NULL; txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; - wake_up(&mgr->tx_waitq); + wake_up_all(&mgr->tx_waitq); } } @@ -2258,7 +2258,7 @@ static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr) mstb->tx_slots[slot] = NULL; mutex_unlock(&mgr->qlock); - wake_up(&mgr->tx_waitq); + wake_up_all(&mgr->tx_waitq); } return ret; } @@ -2498,6 +2498,81 @@ static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr, } /** + * drm_dp_atomic_find_vcpi_slots() - Find and add vcpi slots to the state + * @state: global atomic state + * @mgr: MST topology manager for the port + * @port: port to find vcpi slots for + * @pbn: bandwidth required for the mode in PBN + * + * RETURNS: + * Total slots in the atomic state assigned for this port or error + */ +int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn) +{ + struct drm_dp_mst_topology_state *topology_state; + int req_slots; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (topology_state == NULL) + return -ENOMEM; + + port = drm_dp_get_validated_port_ref(mgr, port); + if (port == NULL) + return -EINVAL; + req_slots = DIV_ROUND_UP(pbn, mgr->pbn_div); + DRM_DEBUG_KMS("vcpi slots req=%d, avail=%d\n", + req_slots, topology_state->avail_slots); + + if (req_slots > topology_state->avail_slots) { + drm_dp_put_port(port); + return -ENOSPC; + } + + topology_state->avail_slots -= req_slots; + DRM_DEBUG_KMS("vcpi slots avail=%d", topology_state->avail_slots); + + drm_dp_put_port(port); + return req_slots; +} +EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots); + +/** + * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots + * @state: global atomic state + * @mgr: MST topology manager for the port + * @slots: number of vcpi slots to release + * + * RETURNS: + * 0 if @slots were added back to &drm_dp_mst_topology_state->avail_slots or + * negative error code + */ +int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + int slots) +{ + struct drm_dp_mst_topology_state *topology_state; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (topology_state == NULL) + return -ENOMEM; + + /* We cannot rely on port->vcpi.num_slots to update + * topology_state->avail_slots as the port may not exist if the parent + * branch device was unplugged. This should be fixed by tracking + * per-port slot allocation in drm_dp_mst_topology_state instead of + * depending on the caller to tell us how many slots to release. + */ + topology_state->avail_slots += slots; + DRM_DEBUG_KMS("vcpi slots released=%d, avail=%d\n", + slots, topology_state->avail_slots); + + return 0; +} +EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots); + +/** * drm_dp_mst_allocate_vcpi() - Allocate a virtual channel * @mgr: manager for this port * @port: port to allocate a virtual channel for. @@ -2936,6 +3011,69 @@ static void drm_dp_destroy_connector_work(struct work_struct *work) (*mgr->cbs->hotplug)(mgr); } +void *drm_dp_mst_duplicate_state(struct drm_atomic_state *state, void *obj) +{ + struct drm_dp_mst_topology_mgr *mgr = obj; + struct drm_dp_mst_topology_state *new_mst_state; + + if (WARN_ON(!mgr->state)) + return NULL; + + new_mst_state = kmemdup(mgr->state, sizeof(*new_mst_state), GFP_KERNEL); + if (new_mst_state) + new_mst_state->state = state; + return new_mst_state; +} + +void drm_dp_mst_swap_state(void *obj, void **obj_state_ptr) +{ + struct drm_dp_mst_topology_mgr *mgr = obj; + struct drm_dp_mst_topology_state **topology_state_ptr; + + topology_state_ptr = (struct drm_dp_mst_topology_state **)obj_state_ptr; + + mgr->state->state = (*topology_state_ptr)->state; + swap(*topology_state_ptr, mgr->state); + mgr->state->state = NULL; +} + +void drm_dp_mst_destroy_state(void *obj_state) +{ + kfree(obj_state); +} + +static const struct drm_private_state_funcs mst_state_funcs = { + .duplicate_state = drm_dp_mst_duplicate_state, + .swap_state = drm_dp_mst_swap_state, + .destroy_state = drm_dp_mst_destroy_state, +}; + +/** + * drm_atomic_get_mst_topology_state: get MST topology state + * + * @state: global atomic state + * @mgr: MST topology manager, also the private object in this case + * + * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic + * state vtable so that the private object state returned is that of a MST + * topology object. Also, drm_atomic_get_private_obj_state() expects the caller + * to care of the locking, so warn if don't hold the connection_mutex. + * + * RETURNS: + * + * The MST topology state or error pointer. + */ +struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_device *dev = mgr->dev; + + WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex)); + return drm_atomic_get_private_obj_state(state, mgr, + &mst_state_funcs); +} +EXPORT_SYMBOL(drm_atomic_get_mst_topology_state); + /** * drm_dp_mst_topology_mgr_init - initialise a topology manager * @mgr: manager struct to initialise @@ -2980,6 +3118,15 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, if (test_calc_pbn_mode() < 0) DRM_ERROR("MST PBN self-test failed\n"); + mgr->state = kzalloc(sizeof(*mgr->state), GFP_KERNEL); + if (mgr->state == NULL) + return -ENOMEM; + mgr->state->mgr = mgr; + + /* max. time slots - one slot for MTP header */ + mgr->state->avail_slots = 63; + mgr->funcs = &mst_state_funcs; + return 0; } EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init); @@ -3000,6 +3147,9 @@ void drm_dp_mst_topology_mgr_destroy(struct drm_dp_mst_topology_mgr *mgr) mutex_unlock(&mgr->payload_lock); mgr->dev = NULL; mgr->aux = NULL; + kfree(mgr->state); + mgr->state = NULL; + mgr->funcs = NULL; } EXPORT_SYMBOL(drm_dp_mst_topology_mgr_destroy); diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index 50abd1faf38f..53f9bdf470d7 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -189,7 +189,7 @@ struct drm_framebuffer *drm_fb_cma_create_with_funcs(struct drm_device *dev, obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]); if (!obj) { dev_err(dev->dev, "Failed to lookup GEM object\n"); - ret = -ENXIO; + ret = -ENOENT; goto err_gem_object_put; } @@ -260,6 +260,33 @@ struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb, EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_obj); /** + * drm_fb_cma_get_gem_addr() - Get physical address for framebuffer + * @fb: The framebuffer + * @state: Which state of drm plane + * @plane: Which plane + * Return the CMA GEM address for given framebuffer. + * + * This function will usually be called from the PLANE callback functions. + */ +dma_addr_t drm_fb_cma_get_gem_addr(struct drm_framebuffer *fb, + struct drm_plane_state *state, + unsigned int plane) +{ + struct drm_fb_cma *fb_cma = to_fb_cma(fb); + dma_addr_t paddr; + + if (plane >= 4) + return 0; + + paddr = fb_cma->obj[plane]->paddr + fb->offsets[plane]; + paddr += fb->format->cpp[plane] * (state->src_x >> 16); + paddr += fb->pitches[plane] * (state->src_y >> 16); + + return paddr; +} +EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_addr); + +/** * drm_fb_cma_prepare_fb() - Prepare CMA framebuffer * @plane: Which plane * @state: Plane state attach fence to diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c index 3783b659cd38..caad93dab54b 100644 --- a/drivers/gpu/drm/drm_file.c +++ b/drivers/gpu/drm/drm_file.c @@ -351,9 +351,8 @@ void drm_lastclose(struct drm_device * dev) * * This function must be used by drivers as their &file_operations.release * method. It frees any resources associated with the open file, and calls the - * &drm_driver.preclose and &drm_driver.lastclose driver callbacks. If this is - * the last open file for the DRM device also proceeds to call the - * &drm_driver.lastclose driver callback. + * &drm_driver.postclose driver callback. If this is the last open file for the + * DRM device also proceeds to call the &drm_driver.lastclose driver callback. * * RETURNS: * @@ -373,7 +372,8 @@ int drm_release(struct inode *inode, struct file *filp) list_del(&file_priv->lhead); mutex_unlock(&dev->filelist_mutex); - if (dev->driver->preclose) + if (drm_core_check_feature(dev, DRIVER_LEGACY) && + dev->driver->preclose) dev->driver->preclose(dev, file_priv); /* ======================================================== diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 8c866cac62dd..c7debaad67f8 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -54,7 +54,7 @@ static bool drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, - struct timeval *tvblank, unsigned flags); + struct timeval *tvblank, bool in_vblank_irq); static unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */ @@ -138,7 +138,7 @@ static void drm_reset_vblank_timestamp(struct drm_device *dev, unsigned int pipe */ do { cur_vblank = __get_vblank_counter(dev, pipe); - rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, 0); + rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, false); } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); /* @@ -171,7 +171,7 @@ static void drm_reset_vblank_timestamp(struct drm_device *dev, unsigned int pipe * device vblank fields. */ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, - unsigned long flags) + bool in_vblank_irq) { struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; u32 cur_vblank, diff; @@ -194,7 +194,7 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, */ do { cur_vblank = __get_vblank_counter(dev, pipe); - rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, flags); + rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, in_vblank_irq); } while (cur_vblank != __get_vblank_counter(dev, pipe) && --count > 0); if (dev->max_vblank_count != 0) { @@ -214,13 +214,13 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, */ diff = DIV_ROUND_CLOSEST_ULL(diff_ns, framedur_ns); - if (diff == 0 && flags & DRM_CALLED_FROM_VBLIRQ) + if (diff == 0 && in_vblank_irq) DRM_DEBUG_VBL("crtc %u: Redundant vblirq ignored." " diff_ns = %lld, framedur_ns = %d)\n", pipe, (long long) diff_ns, framedur_ns); } else { /* some kind of default for drivers w/o accurate vbl timestamping */ - diff = (flags & DRM_CALLED_FROM_VBLIRQ) != 0; + diff = in_vblank_irq ? 1 : 0; } /* @@ -253,7 +253,7 @@ static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, * Otherwise reinitialize delayed at next vblank interrupt and assign 0 * for now, to mark the vblanktimestamp as invalid. */ - if (!rc && (flags & DRM_CALLED_FROM_VBLIRQ) == 0) + if (!rc && in_vblank_irq) t_vblank = (struct timeval) {0, 0}; store_vblank(dev, pipe, diff, &t_vblank, cur_vblank); @@ -291,7 +291,7 @@ u32 drm_accurate_vblank_count(struct drm_crtc *crtc) spin_lock_irqsave(&dev->vblank_time_lock, flags); - drm_update_vblank_count(dev, pipe, 0); + drm_update_vblank_count(dev, pipe, false); vblank = drm_vblank_count(dev, pipe); spin_unlock_irqrestore(&dev->vblank_time_lock, flags); @@ -349,7 +349,7 @@ static void vblank_disable_and_save(struct drm_device *dev, unsigned int pipe) * this time. This makes the count account for the entire time * between drm_crtc_vblank_on() and drm_crtc_vblank_off(). */ - drm_update_vblank_count(dev, pipe, 0); + drm_update_vblank_count(dev, pipe, false); spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags); } @@ -684,6 +684,7 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc, vblank->linedur_ns = linedur_ns; vblank->framedur_ns = framedur_ns; + vblank->hwmode = *mode; DRM_DEBUG("crtc %u: hwmode: htotal %d, vtotal %d, vdisplay %d\n", crtc->base.id, mode->crtc_htotal, @@ -700,10 +701,10 @@ EXPORT_SYMBOL(drm_calc_timestamping_constants); * @max_error: Desired maximum allowable error in timestamps (nanosecs) * On return contains true maximum error of timestamp * @vblank_time: Pointer to struct timeval which should receive the timestamp - * @flags: Flags to pass to driver: - * 0 = Default, - * DRM_CALLED_FROM_VBLIRQ = If function is called from vbl IRQ handler - * @mode: mode which defines the scanout timings + * @in_vblank_irq: + * True when called from drm_crtc_handle_vblank(). Some drivers + * need to apply some workarounds for gpu-specific vblank irq quirks + * if flag is set. * * Implements calculation of exact vblank timestamps from given drm_display_mode * timings and current video scanout position of a CRTC. This can be called from @@ -723,52 +724,62 @@ EXPORT_SYMBOL(drm_calc_timestamping_constants); * returns as no operation if a doublescan or interlaced video mode is * active. Higher level code is expected to handle this. * - * Returns: - * Negative value on error, failure or if not supported in current - * video mode: - * - * -EINVAL Invalid CRTC. - * -EAGAIN Temporary unavailable, e.g., called before initial modeset. - * -ENOTSUPP Function not supported in current display mode. - * -EIO Failed, e.g., due to failed scanout position query. + * This function can be used to implement the &drm_driver.get_vblank_timestamp + * directly, if the driver implements the &drm_driver.get_scanout_position hook. * - * Returns or'ed positive status flags on success: + * Note that atomic drivers must call drm_calc_timestamping_constants() before + * enabling a CRTC. The atomic helpers already take care of that in + * drm_atomic_helper_update_legacy_modeset_state(). * - * DRM_VBLANKTIME_SCANOUTPOS_METHOD - Signal this method used for timestamping. - * DRM_VBLANKTIME_INVBL - Timestamp taken while scanout was in vblank interval. + * Returns: * + * Returns true on success, and false on failure, i.e. when no accurate + * timestamp could be acquired. */ -int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, - unsigned int pipe, - int *max_error, - struct timeval *vblank_time, - unsigned flags, - const struct drm_display_mode *mode) +bool drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, + unsigned int pipe, + int *max_error, + struct timeval *vblank_time, + bool in_vblank_irq) { struct timeval tv_etime; ktime_t stime, etime; - unsigned int vbl_status; - int ret = DRM_VBLANKTIME_SCANOUTPOS_METHOD; + bool vbl_status; + struct drm_crtc *crtc; + const struct drm_display_mode *mode; + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; int vpos, hpos, i; int delta_ns, duration_ns; - if (pipe >= dev->num_crtcs) { + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + return false; + + crtc = drm_crtc_from_index(dev, pipe); + + if (pipe >= dev->num_crtcs || !crtc) { DRM_ERROR("Invalid crtc %u\n", pipe); - return -EINVAL; + return false; } /* Scanout position query not supported? Should not happen. */ if (!dev->driver->get_scanout_position) { DRM_ERROR("Called from driver w/o get_scanout_position()!?\n"); - return -EIO; + return false; } + if (drm_drv_uses_atomic_modeset(dev)) + mode = &vblank->hwmode; + else + mode = &crtc->hwmode; + /* If mode timing undefined, just return as no-op: * Happens during initial modesetting of a crtc. */ if (mode->crtc_clock == 0) { DRM_DEBUG("crtc %u: Noop due to uninitialized mode.\n", pipe); - return -EAGAIN; + WARN_ON_ONCE(drm_drv_uses_atomic_modeset(dev)); + + return false; } /* Get current scanout position with system timestamp. @@ -783,16 +794,17 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, * Get vertical and horizontal scanout position vpos, hpos, * and bounding timestamps stime, etime, pre/post query. */ - vbl_status = dev->driver->get_scanout_position(dev, pipe, flags, + vbl_status = dev->driver->get_scanout_position(dev, pipe, + in_vblank_irq, &vpos, &hpos, &stime, &etime, mode); /* Return as no-op if scanout query unsupported or failed. */ - if (!(vbl_status & DRM_SCANOUTPOS_VALID)) { - DRM_DEBUG("crtc %u : scanoutpos query failed [0x%x].\n", - pipe, vbl_status); - return -EIO; + if (!vbl_status) { + DRM_DEBUG("crtc %u : scanoutpos query failed.\n", + pipe); + return false; } /* Compute uncertainty in timestamp of scanout position query. */ @@ -830,13 +842,13 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, etime = ktime_sub_ns(etime, delta_ns); *vblank_time = ktime_to_timeval(etime); - DRM_DEBUG_VBL("crtc %u : v 0x%x p(%d,%d)@ %ld.%ld -> %ld.%ld [e %d us, %d rep]\n", - pipe, vbl_status, hpos, vpos, + DRM_DEBUG_VBL("crtc %u : v p(%d,%d)@ %ld.%ld -> %ld.%ld [e %d us, %d rep]\n", + pipe, hpos, vpos, (long)tv_etime.tv_sec, (long)tv_etime.tv_usec, (long)vblank_time->tv_sec, (long)vblank_time->tv_usec, duration_ns/1000, i); - return ret; + return true; } EXPORT_SYMBOL(drm_calc_vbltimestamp_from_scanoutpos); @@ -854,9 +866,10 @@ static struct timeval get_drm_timestamp(void) * @dev: DRM device * @pipe: index of CRTC whose vblank timestamp to retrieve * @tvblank: Pointer to target struct timeval which should receive the timestamp - * @flags: Flags to pass to driver: - * 0 = Default, - * DRM_CALLED_FROM_VBLIRQ = If function is called from vbl IRQ handler + * @in_vblank_irq: + * True when called from drm_crtc_handle_vblank(). Some drivers + * need to apply some workarounds for gpu-specific vblank irq quirks + * if flag is set. * * Fetches the system timestamp corresponding to the time of the most recent * vblank interval on specified CRTC. May call into kms-driver to @@ -870,27 +883,25 @@ static struct timeval get_drm_timestamp(void) */ static bool drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, - struct timeval *tvblank, unsigned flags) + struct timeval *tvblank, bool in_vblank_irq) { - int ret; + bool ret = false; /* Define requested maximum error on timestamps (nanoseconds). */ int max_error = (int) drm_timestamp_precision * 1000; /* Query driver if possible and precision timestamping enabled. */ - if (dev->driver->get_vblank_timestamp && (max_error > 0)) { + if (dev->driver->get_vblank_timestamp && (max_error > 0)) ret = dev->driver->get_vblank_timestamp(dev, pipe, &max_error, - tvblank, flags); - if (ret > 0) - return true; - } + tvblank, in_vblank_irq); /* GPU high precision timestamp query unsupported or failed. * Return current monotonic/gettimeofday timestamp as best estimate. */ - *tvblank = get_drm_timestamp(); + if (!ret) + *tvblank = get_drm_timestamp(); - return false; + return ret; } /** @@ -1329,6 +1340,10 @@ void drm_crtc_vblank_off(struct drm_crtc *crtc) send_vblank_event(dev, e, seq, &now); } spin_unlock_irqrestore(&dev->event_lock, irqflags); + + /* Will be reset by the modeset helpers when re-enabling the crtc by + * calling drm_calc_timestamping_constants(). */ + vblank->hwmode.crtc_clock = 0; } EXPORT_SYMBOL(drm_crtc_vblank_off); @@ -1760,7 +1775,7 @@ bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe) return false; } - drm_update_vblank_count(dev, pipe, DRM_CALLED_FROM_VBLIRQ); + drm_update_vblank_count(dev, pipe, true); spin_unlock(&dev->vblank_time_lock); diff --git a/drivers/gpu/drm/drm_plane_helper.c b/drivers/gpu/drm/drm_plane_helper.c index b84a295230fc..2c27f6f5a668 100644 --- a/drivers/gpu/drm/drm_plane_helper.c +++ b/drivers/gpu/drm/drm_plane_helper.c @@ -381,6 +381,7 @@ EXPORT_SYMBOL(drm_primary_helper_update); /** * drm_primary_helper_disable() - Helper for primary plane disable * @plane: plane to disable + * @ctx: lock acquire context, not used here * * Provides a default plane disable handler for primary planes. This is handler * is called in response to a userspace SetPlane operation on the plane with a @@ -510,12 +511,10 @@ int drm_plane_helper_commit(struct drm_plane *plane, if (plane_funcs->cleanup_fb) plane_funcs->cleanup_fb(plane, plane_state); out: - if (plane_state) { - if (plane->funcs->atomic_destroy_state) - plane->funcs->atomic_destroy_state(plane, plane_state); - else - drm_atomic_helper_plane_destroy_state(plane, plane_state); - } + if (plane->funcs->atomic_destroy_state) + plane->funcs->atomic_destroy_state(plane, plane_state); + else + drm_atomic_helper_plane_destroy_state(plane, plane_state); return ret; } diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c index 954eb848b5e2..22408badc617 100644 --- a/drivers/gpu/drm/drm_prime.c +++ b/drivers/gpu/drm/drm_prime.c @@ -595,15 +595,18 @@ out_unlock: EXPORT_SYMBOL(drm_gem_prime_handle_to_fd); /** - * drm_gem_prime_import - helper library implementation of the import callback + * drm_gem_prime_import_dev - core implementation of the import callback * @dev: drm_device to import into * @dma_buf: dma-buf object to import + * @attach_dev: struct device to dma_buf attach * - * This is the implementation of the gem_prime_import functions for GEM drivers - * using the PRIME helpers. + * This is the core of drm_gem_prime_import. It's designed to be called by + * drivers who want to use a different device structure than dev->dev for + * attaching via dma_buf. */ -struct drm_gem_object *drm_gem_prime_import(struct drm_device *dev, - struct dma_buf *dma_buf) +struct drm_gem_object *drm_gem_prime_import_dev(struct drm_device *dev, + struct dma_buf *dma_buf, + struct device *attach_dev) { struct dma_buf_attachment *attach; struct sg_table *sgt; @@ -625,7 +628,7 @@ struct drm_gem_object *drm_gem_prime_import(struct drm_device *dev, if (!dev->driver->gem_prime_import_sg_table) return ERR_PTR(-EINVAL); - attach = dma_buf_attach(dma_buf, dev->dev); + attach = dma_buf_attach(dma_buf, attach_dev); if (IS_ERR(attach)) return ERR_CAST(attach); @@ -655,6 +658,21 @@ fail_detach: return ERR_PTR(ret); } +EXPORT_SYMBOL(drm_gem_prime_import_dev); + +/** + * drm_gem_prime_import - helper library implementation of the import callback + * @dev: drm_device to import into + * @dma_buf: dma-buf object to import + * + * This is the implementation of the gem_prime_import functions for GEM drivers + * using the PRIME helpers. + */ +struct drm_gem_object *drm_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + return drm_gem_prime_import_dev(dev, dma_buf, dev->dev); +} EXPORT_SYMBOL(drm_gem_prime_import); /** diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 09d3c4c3c858..50294a7bd29d 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -82,14 +82,9 @@ err_file_priv_free: return ret; } -static void exynos_drm_preclose(struct drm_device *dev, - struct drm_file *file) -{ - exynos_drm_subdrv_close(dev, file); -} - static void exynos_drm_postclose(struct drm_device *dev, struct drm_file *file) { + exynos_drm_subdrv_close(dev, file); kfree(file->driver_priv); file->driver_priv = NULL; } @@ -145,7 +140,6 @@ static struct drm_driver exynos_drm_driver = { .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC | DRIVER_RENDER, .open = exynos_drm_open, - .preclose = exynos_drm_preclose, .lastclose = exynos_drm_lastclose, .postclose = exynos_drm_postclose, .gem_free_object_unlocked = exynos_drm_gem_free_object, diff --git a/drivers/gpu/drm/gma500/mdfld_tpo_vid.c b/drivers/gpu/drm/gma500/mdfld_tpo_vid.c index d8d4170725b2..d40628e6810d 100644 --- a/drivers/gpu/drm/gma500/mdfld_tpo_vid.c +++ b/drivers/gpu/drm/gma500/mdfld_tpo_vid.c @@ -32,53 +32,20 @@ static struct drm_display_mode *tpo_vid_get_config_mode(struct drm_device *dev) struct drm_display_mode *mode; struct drm_psb_private *dev_priv = dev->dev_private; struct oaktrail_timing_info *ti = &dev_priv->gct_data.DTD; - bool use_gct = false; mode = kzalloc(sizeof(*mode), GFP_KERNEL); if (!mode) return NULL; - if (use_gct) { - mode->hdisplay = (ti->hactive_hi << 8) | ti->hactive_lo; - mode->vdisplay = (ti->vactive_hi << 8) | ti->vactive_lo; - mode->hsync_start = mode->hdisplay + - ((ti->hsync_offset_hi << 8) | - ti->hsync_offset_lo); - mode->hsync_end = mode->hsync_start + - ((ti->hsync_pulse_width_hi << 8) | - ti->hsync_pulse_width_lo); - mode->htotal = mode->hdisplay + ((ti->hblank_hi << 8) | - ti->hblank_lo); - mode->vsync_start = - mode->vdisplay + ((ti->vsync_offset_hi << 8) | - ti->vsync_offset_lo); - mode->vsync_end = - mode->vsync_start + ((ti->vsync_pulse_width_hi << 8) | - ti->vsync_pulse_width_lo); - mode->vtotal = mode->vdisplay + - ((ti->vblank_hi << 8) | ti->vblank_lo); - mode->clock = ti->pixel_clock * 10; - - dev_dbg(dev->dev, "hdisplay is %d\n", mode->hdisplay); - dev_dbg(dev->dev, "vdisplay is %d\n", mode->vdisplay); - dev_dbg(dev->dev, "HSS is %d\n", mode->hsync_start); - dev_dbg(dev->dev, "HSE is %d\n", mode->hsync_end); - dev_dbg(dev->dev, "htotal is %d\n", mode->htotal); - dev_dbg(dev->dev, "VSS is %d\n", mode->vsync_start); - dev_dbg(dev->dev, "VSE is %d\n", mode->vsync_end); - dev_dbg(dev->dev, "vtotal is %d\n", mode->vtotal); - dev_dbg(dev->dev, "clock is %d\n", mode->clock); - } else { - mode->hdisplay = 864; - mode->vdisplay = 480; - mode->hsync_start = 873; - mode->hsync_end = 876; - mode->htotal = 887; - mode->vsync_start = 487; - mode->vsync_end = 490; - mode->vtotal = 499; - mode->clock = 33264; - } + mode->hdisplay = 864; + mode->vdisplay = 480; + mode->hsync_start = 873; + mode->hsync_end = 876; + mode->htotal = 887; + mode->vsync_start = 487; + mode->vsync_end = 490; + mode->vtotal = 499; + mode->clock = 33264; drm_mode_set_name(mode); drm_mode_set_crtcinfo(mode, 0); diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index fd97fe00cd0d..04493ef1d2f7 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -720,9 +720,7 @@ static u32 i915_get_vblank_counter(struct drm_device *dev, unsigned int pipe) struct drm_i915_private *dev_priv = to_i915(dev); i915_reg_t high_frame, low_frame; u32 high1, high2, low, pixel, vbl_start, hsync_start, htotal; - struct intel_crtc *intel_crtc = intel_get_crtc_for_pipe(dev_priv, - pipe); - const struct drm_display_mode *mode = &intel_crtc->base.hwmode; + const struct drm_display_mode *mode = &dev->vblank[pipe].hwmode; unsigned long irqflags; htotal = mode->crtc_htotal; @@ -779,13 +777,17 @@ static int __intel_get_crtc_scanline(struct intel_crtc *crtc) { struct drm_device *dev = crtc->base.dev; struct drm_i915_private *dev_priv = to_i915(dev); - const struct drm_display_mode *mode = &crtc->base.hwmode; + const struct drm_display_mode *mode; + struct drm_vblank_crtc *vblank; enum pipe pipe = crtc->pipe; int position, vtotal; if (!crtc->active) return -1; + vblank = &crtc->base.dev->vblank[drm_crtc_index(&crtc->base)]; + mode = &vblank->hwmode; + vtotal = mode->crtc_vtotal; if (mode->flags & DRM_MODE_FLAG_INTERLACE) vtotal /= 2; @@ -827,10 +829,10 @@ static int __intel_get_crtc_scanline(struct intel_crtc *crtc) return (position + crtc->scanline_offset) % vtotal; } -static int i915_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe, - unsigned int flags, int *vpos, int *hpos, - ktime_t *stime, ktime_t *etime, - const struct drm_display_mode *mode) +static bool i915_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) { struct drm_i915_private *dev_priv = to_i915(dev); struct intel_crtc *intel_crtc = intel_get_crtc_for_pipe(dev_priv, @@ -838,13 +840,12 @@ static int i915_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe, int position; int vbl_start, vbl_end, hsync_start, htotal, vtotal; bool in_vbl = true; - int ret = 0; unsigned long irqflags; if (WARN_ON(!mode->crtc_clock)) { DRM_DEBUG_DRIVER("trying to get scanoutpos for disabled " "pipe %c\n", pipe_name(pipe)); - return 0; + return false; } htotal = mode->crtc_htotal; @@ -859,8 +860,6 @@ static int i915_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe, vtotal /= 2; } - ret |= DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE; - /* * Lock uncore.lock, as we will do multiple timing critical raw * register reads, potentially with preemption disabled, so the @@ -944,11 +943,7 @@ static int i915_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe, *hpos = position - (*vpos * htotal); } - /* In vblank? */ - if (in_vbl) - ret |= DRM_SCANOUTPOS_IN_VBLANK; - - return ret; + return true; } int intel_get_crtc_scanline(struct intel_crtc *crtc) @@ -964,37 +959,6 @@ int intel_get_crtc_scanline(struct intel_crtc *crtc) return position; } -static int i915_get_vblank_timestamp(struct drm_device *dev, unsigned int pipe, - int *max_error, - struct timeval *vblank_time, - unsigned flags) -{ - struct drm_i915_private *dev_priv = to_i915(dev); - struct intel_crtc *crtc; - - if (pipe >= INTEL_INFO(dev_priv)->num_pipes) { - DRM_ERROR("Invalid crtc %u\n", pipe); - return -EINVAL; - } - - /* Get drm_crtc to timestamp: */ - crtc = intel_get_crtc_for_pipe(dev_priv, pipe); - if (crtc == NULL) { - DRM_ERROR("Invalid crtc %u\n", pipe); - return -EINVAL; - } - - if (!crtc->base.hwmode.crtc_clock) { - DRM_DEBUG_KMS("crtc %u is disabled\n", pipe); - return -EBUSY; - } - - /* Helper routine in DRM core does all the work: */ - return drm_calc_vbltimestamp_from_scanoutpos(dev, pipe, max_error, - vblank_time, flags, - &crtc->base.hwmode); -} - static void ironlake_rps_change_irq_handler(struct drm_i915_private *dev_priv) { u32 busy_up, busy_down, max_avg, min_avg; @@ -4294,7 +4258,7 @@ void intel_irq_init(struct drm_i915_private *dev_priv) dev_priv->hotplug.hpd_storm_threshold = HPD_STORM_DEFAULT_THRESHOLD; - dev->driver->get_vblank_timestamp = i915_get_vblank_timestamp; + dev->driver->get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos; dev->driver->get_scanout_position = i915_get_crtc_scanoutpos; if (IS_CHERRYVIEW(dev_priv)) { diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 3617927af269..2f2bb623cf5f 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -11444,12 +11444,6 @@ intel_modeset_update_crtc_state(struct drm_atomic_state *state) for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { to_intel_crtc(crtc)->config = to_intel_crtc_state(new_crtc_state); - /* Update hwmode for vblank functions */ - if (new_crtc_state->active) - crtc->hwmode = new_crtc_state->adjusted_mode; - else - crtc->hwmode.crtc_clock = 0; - /* * Update legacy state to satisfy fbc code. This can * be removed when fbc uses the atomic state. @@ -15425,8 +15419,6 @@ static void intel_modeset_readout_hw_state(struct drm_device *dev) to_intel_crtc_state(crtc->base.state); int pixclk = 0; - crtc->base.hwmode = crtc_state->base.adjusted_mode; - memset(&crtc->base.mode, 0, sizeof(crtc->base.mode)); if (crtc_state->base.active) { intel_mode_from_pipe_config(&crtc->base.mode, crtc_state); @@ -15456,7 +15448,8 @@ static void intel_modeset_readout_hw_state(struct drm_device *dev) if (IS_BROADWELL(dev_priv) && crtc_state->ips_enabled) pixclk = DIV_ROUND_UP(pixclk * 100, 95); - drm_calc_timestamping_constants(&crtc->base, &crtc->base.hwmode); + drm_calc_timestamping_constants(&crtc->base, + &crtc_state->base.adjusted_mode); update_scanline_offset(crtc); } diff --git a/drivers/gpu/drm/i915/intel_dp_mst.c b/drivers/gpu/drm/i915/intel_dp_mst.c index c1f62eb07c07..1dee9933005f 100644 --- a/drivers/gpu/drm/i915/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/intel_dp_mst.c @@ -39,7 +39,7 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder, struct intel_dp *intel_dp = &intel_dig_port->dp; struct intel_connector *connector = to_intel_connector(conn_state->connector); - struct drm_atomic_state *state; + struct drm_atomic_state *state = pipe_config->base.state; int bpp; int lane_count, slots; const struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; @@ -57,20 +57,24 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder, * seem to suggest we should do otherwise. */ lane_count = drm_dp_max_lane_count(intel_dp->dpcd); - pipe_config->lane_count = lane_count; pipe_config->pipe_bpp = bpp; - pipe_config->port_clock = intel_dp_max_link_rate(intel_dp); - state = pipe_config->base.state; + pipe_config->port_clock = intel_dp_max_link_rate(intel_dp); if (drm_dp_mst_port_has_audio(&intel_dp->mst_mgr, connector->port)) pipe_config->has_audio = true; - mst_pbn = drm_dp_calc_pbn_mode(adjusted_mode->crtc_clock, bpp); + mst_pbn = drm_dp_calc_pbn_mode(adjusted_mode->crtc_clock, bpp); pipe_config->pbn = mst_pbn; - slots = drm_dp_find_vcpi_slots(&intel_dp->mst_mgr, mst_pbn); + + slots = drm_dp_atomic_find_vcpi_slots(state, &intel_dp->mst_mgr, + connector->port, mst_pbn); + if (slots < 0) { + DRM_DEBUG_KMS("failed finding vcpi slots:%d\n", slots); + return false; + } intel_link_compute_m_n(bpp, lane_count, adjusted_mode->crtc_clock, @@ -80,7 +84,38 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder, pipe_config->dp_m_n.tu = slots; return true; +} +static int intel_dp_mst_atomic_check(struct drm_connector *connector, + struct drm_connector_state *new_conn_state) +{ + struct drm_atomic_state *state = new_conn_state->state; + struct drm_connector_state *old_conn_state; + struct drm_crtc *old_crtc; + struct drm_crtc_state *crtc_state; + int slots, ret = 0; + + old_conn_state = drm_atomic_get_old_connector_state(state, connector); + old_crtc = old_conn_state->crtc; + if (!old_crtc) + return ret; + + crtc_state = drm_atomic_get_new_crtc_state(state, old_crtc); + slots = to_intel_crtc_state(crtc_state)->dp_m_n.tu; + if (drm_atomic_crtc_needs_modeset(crtc_state) && slots > 0) { + struct drm_dp_mst_topology_mgr *mgr; + struct drm_encoder *old_encoder; + + old_encoder = old_conn_state->best_encoder; + mgr = &enc_to_mst(old_encoder)->primary->dp.mst_mgr; + + ret = drm_dp_atomic_release_vcpi_slots(state, mgr, slots); + if (ret) + DRM_DEBUG_KMS("failed releasing %d vcpi slots:%d\n", slots, ret); + else + to_intel_crtc_state(crtc_state)->dp_m_n.tu = 0; + } + return ret; } static void intel_mst_disable_dp(struct intel_encoder *encoder, @@ -387,6 +422,7 @@ static const struct drm_connector_helper_funcs intel_dp_mst_connector_helper_fun .mode_valid = intel_dp_mst_mode_valid, .atomic_best_encoder = intel_mst_atomic_best_encoder, .best_encoder = intel_mst_best_encoder, + .atomic_check = intel_dp_mst_atomic_check, }; static void intel_dp_mst_encoder_destroy(struct drm_encoder *encoder) diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index aaee3949a422..48ea8d9d49fe 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -869,7 +869,6 @@ struct intel_hdmi { bool has_audio; enum hdmi_force_audio force_audio; bool rgb_quant_range_selectable; - enum hdmi_picture_aspect aspect_ratio; struct intel_connector *attached_connector; void (*write_infoframe)(struct drm_encoder *encoder, const struct intel_crtc_state *crtc_state, diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c index 1d623b5e09d6..c6b8207724fa 100644 --- a/drivers/gpu/drm/i915/intel_hdmi.c +++ b/drivers/gpu/drm/i915/intel_hdmi.c @@ -1403,7 +1403,7 @@ bool intel_hdmi_compute_config(struct intel_encoder *encoder, } /* Set user selected PAR to incoming mode's member */ - adjusted_mode->picture_aspect_ratio = intel_hdmi->aspect_ratio; + adjusted_mode->picture_aspect_ratio = conn_state->picture_aspect_ratio; pipe_config->lane_count = 4; @@ -1649,19 +1649,7 @@ intel_hdmi_set_property(struct drm_connector *connector, } if (property == connector->dev->mode_config.aspect_ratio_property) { - switch (val) { - case DRM_MODE_PICTURE_ASPECT_NONE: - intel_hdmi->aspect_ratio = HDMI_PICTURE_ASPECT_NONE; - break; - case DRM_MODE_PICTURE_ASPECT_4_3: - intel_hdmi->aspect_ratio = HDMI_PICTURE_ASPECT_4_3; - break; - case DRM_MODE_PICTURE_ASPECT_16_9: - intel_hdmi->aspect_ratio = HDMI_PICTURE_ASPECT_16_9; - break; - default: - return -EINVAL; - } + connector->state->picture_aspect_ratio = val; goto done; } @@ -1823,7 +1811,7 @@ intel_hdmi_add_properties(struct intel_hdmi *intel_hdmi, struct drm_connector *c intel_attach_broadcast_rgb_property(connector); intel_hdmi->color_range_auto = true; intel_attach_aspect_ratio_property(connector); - intel_hdmi->aspect_ratio = HDMI_PICTURE_ASPECT_NONE; + connector->state->picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE; } /* diff --git a/drivers/gpu/drm/i915/intel_sdvo.c b/drivers/gpu/drm/i915/intel_sdvo.c index 816a6f5a3fd9..ef6fa87b2f8a 100644 --- a/drivers/gpu/drm/i915/intel_sdvo.c +++ b/drivers/gpu/drm/i915/intel_sdvo.c @@ -107,11 +107,6 @@ struct intel_sdvo { bool color_range_auto; /** - * HDMI user specified aspect ratio - */ - enum hdmi_picture_aspect aspect_ratio; - - /** * This is set if we're going to treat the device as TV-out. * * While we have these nice friendly flags for output types that ought @@ -1186,7 +1181,7 @@ static bool intel_sdvo_compute_config(struct intel_encoder *encoder, /* Set user selected PAR to incoming mode's member */ if (intel_sdvo->is_hdmi) - adjusted_mode->picture_aspect_ratio = intel_sdvo->aspect_ratio; + adjusted_mode->picture_aspect_ratio = conn_state->picture_aspect_ratio; return true; } @@ -2067,19 +2062,7 @@ intel_sdvo_set_property(struct drm_connector *connector, } if (property == connector->dev->mode_config.aspect_ratio_property) { - switch (val) { - case DRM_MODE_PICTURE_ASPECT_NONE: - intel_sdvo->aspect_ratio = HDMI_PICTURE_ASPECT_NONE; - break; - case DRM_MODE_PICTURE_ASPECT_4_3: - intel_sdvo->aspect_ratio = HDMI_PICTURE_ASPECT_4_3; - break; - case DRM_MODE_PICTURE_ASPECT_16_9: - intel_sdvo->aspect_ratio = HDMI_PICTURE_ASPECT_16_9; - break; - default: - return -EINVAL; - } + connector->state->picture_aspect_ratio = val; goto done; } @@ -2418,7 +2401,7 @@ intel_sdvo_add_hdmi_properties(struct intel_sdvo *intel_sdvo, intel_sdvo->color_range_auto = true; } intel_attach_aspect_ratio_property(&connector->base.base); - intel_sdvo->aspect_ratio = HDMI_PICTURE_ASPECT_NONE; + connector->base.base.state->picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE; } static struct intel_sdvo_connector *intel_sdvo_connector_alloc(void) diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c index d3d6b4cae1e6..e2b3346ead48 100644 --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c @@ -527,31 +527,28 @@ static struct drm_encoder *get_encoder_from_crtc(struct drm_crtc *crtc) return NULL; } -static int mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe, - unsigned int flags, int *vpos, int *hpos, - ktime_t *stime, ktime_t *etime, - const struct drm_display_mode *mode) +static bool mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) { struct msm_drm_private *priv = dev->dev_private; struct drm_crtc *crtc; struct drm_encoder *encoder; int line, vsw, vbp, vactive_start, vactive_end, vfp_end; - int ret = 0; crtc = priv->crtcs[pipe]; if (!crtc) { DRM_ERROR("Invalid crtc %d\n", pipe); - return 0; + return false; } encoder = get_encoder_from_crtc(crtc); if (!encoder) { DRM_ERROR("no encoder found for crtc %d\n", pipe); - return 0; + return false; } - ret |= DRM_SCANOUTPOS_VALID | DRM_SCANOUTPOS_ACCURATE; - vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; vbp = mode->crtc_vtotal - mode->crtc_vsync_end; @@ -575,10 +572,8 @@ static int mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe, if (line < vactive_start) { line -= vactive_start; - ret |= DRM_SCANOUTPOS_IN_VBLANK; } else if (line > vactive_end) { line = line - vfp_end - vactive_start; - ret |= DRM_SCANOUTPOS_IN_VBLANK; } else { line -= vactive_start; } @@ -589,31 +584,7 @@ static int mdp5_get_scanoutpos(struct drm_device *dev, unsigned int pipe, if (etime) *etime = ktime_get(); - return ret; -} - -static int mdp5_get_vblank_timestamp(struct drm_device *dev, unsigned int pipe, - int *max_error, - struct timeval *vblank_time, - unsigned flags) -{ - struct msm_drm_private *priv = dev->dev_private; - struct drm_crtc *crtc; - - if (pipe < 0 || pipe >= priv->num_crtcs) { - DRM_ERROR("Invalid crtc %d\n", pipe); - return -EINVAL; - } - - crtc = priv->crtcs[pipe]; - if (!crtc) { - DRM_ERROR("Invalid crtc %d\n", pipe); - return -EINVAL; - } - - return drm_calc_vbltimestamp_from_scanoutpos(dev, pipe, max_error, - vblank_time, flags, - &crtc->mode); + return true; } static u32 mdp5_get_vblank_counter(struct drm_device *dev, unsigned int pipe) @@ -725,7 +696,7 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev) dev->mode_config.max_width = 0xffff; dev->mode_config.max_height = 0xffff; - dev->driver->get_vblank_timestamp = mdp5_get_vblank_timestamp; + dev->driver->get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos; dev->driver->get_scanout_position = mdp5_get_scanoutpos; dev->driver->get_vblank_counter = mdp5_get_vblank_counter; dev->max_vblank_count = 0xffffffff; diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index 21b10f9840c9..6718c84fb862 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -98,7 +98,7 @@ calc(int blanks, int blanke, int total, int line) return line; } -static int +static bool nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos, ktime_t *stime, ktime_t *etime) { @@ -111,16 +111,16 @@ nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos, }; struct nouveau_display *disp = nouveau_display(crtc->dev); struct drm_vblank_crtc *vblank = &crtc->dev->vblank[drm_crtc_index(crtc)]; - int ret, retry = 20; + int retry = 20; + bool ret = false; do { ret = nvif_mthd(&disp->disp, 0, &args, sizeof(args)); if (ret != 0) - return 0; + return false; if (args.scan.vline) { - ret |= DRM_SCANOUTPOS_ACCURATE; - ret |= DRM_SCANOUTPOS_VALID; + ret = true; break; } @@ -133,14 +133,12 @@ nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos, if (stime) *stime = ns_to_ktime(args.scan.time[0]); if (etime) *etime = ns_to_ktime(args.scan.time[1]); - if (*vpos < 0) - ret |= DRM_SCANOUTPOS_IN_VBLANK; return ret; } -int +bool nouveau_display_scanoutpos(struct drm_device *dev, unsigned int pipe, - unsigned int flags, int *vpos, int *hpos, + bool in_vblank_irq, int *vpos, int *hpos, ktime_t *stime, ktime_t *etime, const struct drm_display_mode *mode) { @@ -153,28 +151,7 @@ nouveau_display_scanoutpos(struct drm_device *dev, unsigned int pipe, } } - return 0; -} - -int -nouveau_display_vblstamp(struct drm_device *dev, unsigned int pipe, - int *max_error, struct timeval *time, unsigned flags) -{ - struct drm_crtc *crtc; - - list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { - if (nouveau_crtc(crtc)->index == pipe) { - struct drm_display_mode *mode; - if (drm_drv_uses_atomic_modeset(dev)) - mode = &crtc->state->adjusted_mode; - else - mode = &crtc->hwmode; - return drm_calc_vbltimestamp_from_scanoutpos(dev, - pipe, max_error, time, flags, mode); - } - } - - return -EINVAL; + return false; } static void diff --git a/drivers/gpu/drm/nouveau/nouveau_display.h b/drivers/gpu/drm/nouveau/nouveau_display.h index e1d772d39488..201aec2ea5b8 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.h +++ b/drivers/gpu/drm/nouveau/nouveau_display.h @@ -68,11 +68,9 @@ int nouveau_display_suspend(struct drm_device *dev, bool runtime); void nouveau_display_resume(struct drm_device *dev, bool runtime); int nouveau_display_vblank_enable(struct drm_device *, unsigned int); void nouveau_display_vblank_disable(struct drm_device *, unsigned int); -int nouveau_display_scanoutpos(struct drm_device *, unsigned int, - unsigned int, int *, int *, ktime_t *, - ktime_t *, const struct drm_display_mode *); -int nouveau_display_vblstamp(struct drm_device *, unsigned int, int *, - struct timeval *, unsigned); +bool nouveau_display_scanoutpos(struct drm_device *, unsigned int, + bool, int *, int *, ktime_t *, + ktime_t *, const struct drm_display_mode *); int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event, diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index 2b6ac24ce690..1f751a3f570c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -881,7 +881,7 @@ done: } static void -nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv) +nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv) { struct nouveau_cli *cli = nouveau_cli(fpriv); struct nouveau_drm *drm = nouveau_drm(dev); @@ -897,12 +897,6 @@ nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv) list_del(&cli->head); mutex_unlock(&drm->client.mutex); -} - -static void -nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv) -{ - struct nouveau_cli *cli = nouveau_cli(fpriv); nouveau_cli_fini(cli); kfree(cli); pm_runtime_mark_last_busy(dev->dev); @@ -974,7 +968,6 @@ driver_stub = { .load = nouveau_drm_load, .unload = nouveau_drm_unload, .open = nouveau_drm_open, - .preclose = nouveau_drm_preclose, .postclose = nouveau_drm_postclose, .lastclose = nouveau_vga_lastclose, @@ -985,7 +978,7 @@ driver_stub = { .enable_vblank = nouveau_display_vblank_enable, .disable_vblank = nouveau_display_vblank_disable, .get_scanout_position = nouveau_display_scanoutpos, - .get_vblank_timestamp = nouveau_display_vblstamp, + .get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos, .ioctls = nouveau_ioctls, .num_ioctls = ARRAY_SIZE(nouveau_ioctls), diff --git a/drivers/gpu/drm/pl111/Kconfig b/drivers/gpu/drm/pl111/Kconfig new file mode 100644 index 000000000000..ede49efd531f --- /dev/null +++ b/drivers/gpu/drm/pl111/Kconfig @@ -0,0 +1,12 @@ +config DRM_PL111 + tristate "DRM Support for PL111 CLCD Controller" + depends on DRM + depends on ARM || ARM64 || COMPILE_TEST + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE + help + Choose this option for DRM support for the PL111 CLCD controller. + If M is selected the module will be called pl111_drm. + diff --git a/drivers/gpu/drm/pl111/Makefile b/drivers/gpu/drm/pl111/Makefile new file mode 100644 index 000000000000..01caee727c13 --- /dev/null +++ b/drivers/gpu/drm/pl111/Makefile @@ -0,0 +1,5 @@ +pl111_drm-y += pl111_connector.o \ + pl111_display.o \ + pl111_drv.o + +obj-$(CONFIG_DRM_PL111) += pl111_drm.o diff --git a/drivers/gpu/drm/pl111/pl111_connector.c b/drivers/gpu/drm/pl111/pl111_connector.c new file mode 100644 index 000000000000..3f213d7e7692 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_connector.c @@ -0,0 +1,127 @@ +/* + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms of + * such GNU licence. + * + */ + +/** + * pl111_drm_connector.c + * Implementation of the connector functions for PL111 DRM + */ +#include <linux/amba/clcd-regs.h> +#include <linux/version.h> +#include <linux/shmem_fs.h> +#include <linux/dma-buf.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include "pl111_drm.h" + +static void pl111_connector_destroy(struct drm_connector *connector) +{ + struct pl111_drm_connector *pl111_connector = + to_pl111_connector(connector); + + if (pl111_connector->panel) + drm_panel_detach(pl111_connector->panel); + + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static enum drm_connector_status pl111_connector_detect(struct drm_connector + *connector, bool force) +{ + struct pl111_drm_connector *pl111_connector = + to_pl111_connector(connector); + + return (pl111_connector->panel ? + connector_status_connected : + connector_status_disconnected); +} + +static int pl111_connector_helper_get_modes(struct drm_connector *connector) +{ + struct pl111_drm_connector *pl111_connector = + to_pl111_connector(connector); + + if (!pl111_connector->panel) + return 0; + + return drm_panel_get_modes(pl111_connector->panel); +} + +const struct drm_connector_funcs connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = pl111_connector_destroy, + .detect = pl111_connector_detect, + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +const struct drm_connector_helper_funcs connector_helper_funcs = { + .get_modes = pl111_connector_helper_get_modes, +}; + +/* Walks the OF graph to find the panel node and then asks DRM to look + * up the panel. + */ +static struct drm_panel *pl111_get_panel(struct device *dev) +{ + struct device_node *endpoint, *panel_node; + struct device_node *np = dev->of_node; + struct drm_panel *panel; + + endpoint = of_graph_get_next_endpoint(np, NULL); + if (!endpoint) { + dev_err(dev, "no endpoint to fetch panel\n"); + return NULL; + } + + /* don't proceed if we have an endpoint but no panel_node tied to it */ + panel_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!panel_node) { + dev_err(dev, "no valid panel node\n"); + return NULL; + } + + panel = of_drm_find_panel(panel_node); + of_node_put(panel_node); + + return panel; +} + +int pl111_connector_init(struct drm_device *dev) +{ + struct pl111_drm_dev_private *priv = dev->dev_private; + struct pl111_drm_connector *pl111_connector = &priv->connector; + struct drm_connector *connector = &pl111_connector->connector; + + drm_connector_init(dev, connector, &connector_funcs, + DRM_MODE_CONNECTOR_DPI); + drm_connector_helper_add(connector, &connector_helper_funcs); + + pl111_connector->panel = pl111_get_panel(dev->dev); + if (pl111_connector->panel) + drm_panel_attach(pl111_connector->panel, connector); + + return 0; +} + diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c new file mode 100644 index 000000000000..39a5c33bce7d --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_display.c @@ -0,0 +1,344 @@ +/* + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms of + * such GNU licence. + * + */ + +#include <linux/amba/clcd-regs.h> +#include <linux/clk.h> +#include <linux/version.h> +#include <linux/dma-buf.h> +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_panel.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#include "pl111_drm.h" + +irqreturn_t pl111_irq(int irq, void *data) +{ + struct pl111_drm_dev_private *priv = data; + u32 irq_stat; + irqreturn_t status = IRQ_NONE; + + irq_stat = readl(priv->regs + CLCD_PL111_MIS); + + if (!irq_stat) + return IRQ_NONE; + + if (irq_stat & CLCD_IRQ_NEXTBASE_UPDATE) { + drm_crtc_handle_vblank(&priv->pipe.crtc); + + status = IRQ_HANDLED; + } + + /* Clear the interrupt once done */ + writel(irq_stat, priv->regs + CLCD_PL111_ICR); + + return status; +} + +static u32 pl111_get_fb_offset(struct drm_plane_state *pstate) +{ + struct drm_framebuffer *fb = pstate->fb; + struct drm_gem_cma_object *obj = drm_fb_cma_get_gem_obj(fb, 0); + + return (obj->paddr + + fb->offsets[0] + + fb->format->cpp[0] * pstate->src_x + + fb->pitches[0] * pstate->src_y); +} + +static int pl111_display_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *pstate, + struct drm_crtc_state *cstate) +{ + const struct drm_display_mode *mode = &cstate->mode; + struct drm_framebuffer *old_fb = pipe->plane.state->fb; + struct drm_framebuffer *fb = pstate->fb; + + if (mode->hdisplay % 16) + return -EINVAL; + + if (fb) { + u32 offset = pl111_get_fb_offset(pstate); + + /* FB base address must be dword aligned. */ + if (offset & 3) + return -EINVAL; + + /* There's no pitch register -- the mode's hdisplay + * controls it. + */ + if (fb->pitches[0] != mode->hdisplay * fb->format->cpp[0]) + return -EINVAL; + + /* We can't change the FB format in a flicker-free + * manner (and only update it during CRTC enable). + */ + if (old_fb && old_fb->format != fb->format) + cstate->mode_changed = true; + } + + return 0; +} + +static void pl111_display_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *cstate) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_plane *plane = &pipe->plane; + struct drm_device *drm = crtc->dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + const struct drm_display_mode *mode = &cstate->mode; + struct drm_framebuffer *fb = plane->state->fb; + struct drm_connector *connector = &priv->connector.connector; + u32 cntl; + u32 ppl, hsw, hfp, hbp; + u32 lpp, vsw, vfp, vbp; + u32 cpl; + int ret; + + ret = clk_set_rate(priv->clk, mode->clock * 1000); + if (ret) { + dev_err(drm->dev, + "Failed to set pixel clock rate to %d: %d\n", + mode->clock * 1000, ret); + } + + clk_prepare_enable(priv->clk); + + ppl = (mode->hdisplay / 16) - 1; + hsw = mode->hsync_end - mode->hsync_start - 1; + hfp = mode->hsync_start - mode->hdisplay - 1; + hbp = mode->htotal - mode->hsync_end - 1; + + lpp = mode->vdisplay - 1; + vsw = mode->vsync_end - mode->vsync_start - 1; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + cpl = mode->hdisplay - 1; + + writel((ppl << 2) | + (hsw << 8) | + (hfp << 16) | + (hbp << 24), + priv->regs + CLCD_TIM0); + writel(lpp | + (vsw << 10) | + (vfp << 16) | + (vbp << 24), + priv->regs + CLCD_TIM1); + /* XXX: We currently always use CLCDCLK with no divisor. We + * could probably reduce power consumption by using HCLK + * (apb_pclk) with a divisor when it gets us near our target + * pixel clock. + */ + writel(((mode->flags & DRM_MODE_FLAG_NHSYNC) ? TIM2_IHS : 0) | + ((mode->flags & DRM_MODE_FLAG_NVSYNC) ? TIM2_IVS : 0) | + ((connector->display_info.bus_flags & + DRM_BUS_FLAG_DE_LOW) ? TIM2_IOE : 0) | + ((connector->display_info.bus_flags & + DRM_BUS_FLAG_PIXDATA_NEGEDGE) ? TIM2_IPC : 0) | + TIM2_BCD | + (cpl << 16), + priv->regs + CLCD_TIM2); + writel(0, priv->regs + CLCD_TIM3); + + drm_panel_prepare(priv->connector.panel); + + /* Enable and Power Up */ + cntl = CNTL_LCDEN | CNTL_LCDTFT | CNTL_LCDPWR | CNTL_LCDVCOMP(1); + + /* Note that the the hardware's format reader takes 'r' from + * the low bit, while DRM formats list channels from high bit + * to low bit as you read left to right. + */ + switch (fb->format->format) { + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + cntl |= CNTL_LCDBPP24; + break; + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + cntl |= CNTL_LCDBPP24 | CNTL_BGR; + break; + case DRM_FORMAT_BGR565: + cntl |= CNTL_LCDBPP16_565; + break; + case DRM_FORMAT_RGB565: + cntl |= CNTL_LCDBPP16_565 | CNTL_BGR; + break; + case DRM_FORMAT_ABGR1555: + case DRM_FORMAT_XBGR1555: + cntl |= CNTL_LCDBPP16; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + cntl |= CNTL_LCDBPP16 | CNTL_BGR; + break; + case DRM_FORMAT_ABGR4444: + case DRM_FORMAT_XBGR4444: + cntl |= CNTL_LCDBPP16_444; + break; + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XRGB4444: + cntl |= CNTL_LCDBPP16_444 | CNTL_BGR; + break; + default: + WARN_ONCE(true, "Unknown FB format 0x%08x\n", + fb->format->format); + break; + } + + writel(cntl, priv->regs + CLCD_PL111_CNTL); + + drm_panel_enable(priv->connector.panel); + + drm_crtc_vblank_on(crtc); +} + +void pl111_display_disable(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + + drm_crtc_vblank_off(crtc); + + drm_panel_disable(priv->connector.panel); + + /* Disable and Power Down */ + writel(0, priv->regs + CLCD_PL111_CNTL); + + drm_panel_unprepare(priv->connector.panel); + + clk_disable_unprepare(priv->clk); +} + +static void pl111_display_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_pstate) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *drm = crtc->dev; + struct pl111_drm_dev_private *priv = drm->dev_private; + struct drm_pending_vblank_event *event = crtc->state->event; + struct drm_plane *plane = &pipe->plane; + struct drm_plane_state *pstate = plane->state; + struct drm_framebuffer *fb = pstate->fb; + + if (fb) { + u32 addr = pl111_get_fb_offset(pstate); + + writel(addr, priv->regs + CLCD_UBAS); + } + + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +int pl111_enable_vblank(struct drm_device *drm, unsigned int crtc) +{ + struct pl111_drm_dev_private *priv = drm->dev_private; + + writel(CLCD_IRQ_NEXTBASE_UPDATE, priv->regs + CLCD_PL111_IENB); + + return 0; +} + +void pl111_disable_vblank(struct drm_device *drm, unsigned int crtc) +{ + struct pl111_drm_dev_private *priv = drm->dev_private; + + writel(0, priv->regs + CLCD_PL111_IENB); +} + +static int pl111_display_prepare_fb(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *plane_state) +{ + return drm_fb_cma_prepare_fb(&pipe->plane, plane_state); +} + +const struct drm_simple_display_pipe_funcs pl111_display_funcs = { + .check = pl111_display_check, + .enable = pl111_display_enable, + .disable = pl111_display_disable, + .update = pl111_display_update, + .prepare_fb = pl111_display_prepare_fb, +}; + +int pl111_display_init(struct drm_device *drm) +{ + struct pl111_drm_dev_private *priv = drm->dev_private; + struct device *dev = drm->dev; + struct device_node *endpoint; + u32 tft_r0b0g0[3]; + int ret; + static const u32 formats[] = { + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_BGR565, + DRM_FORMAT_RGB565, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_XRGB4444, + }; + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (!endpoint) + return -ENODEV; + + if (of_property_read_u32_array(endpoint, + "arm,pl11x,tft-r0g0b0-pads", + tft_r0b0g0, + ARRAY_SIZE(tft_r0b0g0)) != 0) { + dev_err(dev, "arm,pl11x,tft-r0g0b0-pads should be 3 ints\n"); + of_node_put(endpoint); + return -ENOENT; + } + of_node_put(endpoint); + + if (tft_r0b0g0[0] != 0 || + tft_r0b0g0[1] != 8 || + tft_r0b0g0[2] != 16) { + dev_err(dev, "arm,pl11x,tft-r0g0b0-pads != [0,8,16] not yet supported\n"); + return -EINVAL; + } + + ret = drm_simple_display_pipe_init(drm, &priv->pipe, + &pl111_display_funcs, + formats, ARRAY_SIZE(formats), + &priv->connector.connector); + if (ret) + return ret; + + return 0; +} diff --git a/drivers/gpu/drm/pl111/pl111_drm.h b/drivers/gpu/drm/pl111/pl111_drm.h new file mode 100644 index 000000000000..f381593921b7 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drm.h @@ -0,0 +1,56 @@ +/* + * + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms of + * such GNU licence. + * + */ + +#ifndef _PL111_DRM_H_ +#define _PL111_DRM_H_ + +#include <drm/drm_gem.h> +#include <drm/drm_simple_kms_helper.h> + +#define CLCD_IRQ_NEXTBASE_UPDATE BIT(2) + +struct pl111_drm_connector { + struct drm_connector connector; + struct drm_panel *panel; +}; + +struct pl111_drm_dev_private { + struct drm_device *drm; + + struct pl111_drm_connector connector; + struct drm_simple_display_pipe pipe; + struct drm_fbdev_cma *fbdev; + + void *regs; + struct clk *clk; +}; + +#define to_pl111_connector(x) \ + container_of(x, struct pl111_drm_connector, connector) + +int pl111_display_init(struct drm_device *dev); +int pl111_enable_vblank(struct drm_device *drm, unsigned int crtc); +void pl111_disable_vblank(struct drm_device *drm, unsigned int crtc); +irqreturn_t pl111_irq(int irq, void *data); +int pl111_connector_init(struct drm_device *dev); +int pl111_encoder_init(struct drm_device *dev); +int pl111_dumb_create(struct drm_file *file_priv, + struct drm_device *dev, + struct drm_mode_create_dumb *args); + +#endif /* _PL111_DRM_H_ */ diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c new file mode 100644 index 000000000000..936403f65508 --- /dev/null +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -0,0 +1,272 @@ +/* + * (C) COPYRIGHT 2012-2013 ARM Limited. All rights reserved. + * + * Parts of this file were based on sources as follows: + * + * Copyright (c) 2006-2008 Intel Corporation + * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> + * Copyright (C) 2011 Texas Instruments + * + * This program is free software and is provided to you under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation, and any use by you of this program is subject to the terms of + * such GNU licence. + * + */ + +/** + * DOC: ARM PrimeCell PL111 CLCD Driver + * + * The PL111 is a simple LCD controller that can support TFT and STN + * displays. This driver exposes a standard KMS interface for them. + * + * This driver uses the same Device Tree binding as the fbdev CLCD + * driver. While the fbdev driver supports panels that may be + * connected to the CLCD internally to the CLCD driver, in DRM the + * panels get split out to drivers/gpu/drm/panels/. This means that, + * in converting from using fbdev to using DRM, you also need to write + * a panel driver (which may be as simple as an entry in + * panel-simple.c). + * + * The driver currently doesn't expose the cursor. The DRM API for + * cursors requires support for 64x64 ARGB8888 cursor images, while + * the hardware can only support 64x64 monochrome with masking + * cursors. While one could imagine trying to hack something together + * to look at the ARGB8888 and program reasonable in monochrome, we + * just don't expose the cursor at all instead, and leave cursor + * support to the X11 software cursor layer. + * + * TODO: + * + * - Fix race between setting plane base address and getting IRQ for + * vsync firing the pageflip completion. + * + * - Expose the correct set of formats we can support based on the + * "arm,pl11x,tft-r0g0b0-pads" DT property. + * + * - Use the "max-memory-bandwidth" DT property to filter the + * supported formats. + * + * - Read back hardware state at boot to skip reprogramming the + * hardware when doing a no-op modeset. + * + * - Use the internal clock divisor to reduce power consumption by + * using HCLK (apb_pclk) when appropriate. + */ + +#include <linux/amba/bus.h> +#include <linux/amba/clcd-regs.h> +#include <linux/version.h> +#include <linux/shmem_fs.h> +#include <linux/dma-buf.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_fb_cma_helper.h> + +#include "pl111_drm.h" + +#define DRIVER_DESC "DRM module for PL111" + +struct drm_mode_config_funcs mode_config_funcs = { + .fb_create = drm_fb_cma_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static int pl111_modeset_init(struct drm_device *dev) +{ + struct drm_mode_config *mode_config; + struct pl111_drm_dev_private *priv = dev->dev_private; + int ret = 0; + + drm_mode_config_init(dev); + mode_config = &dev->mode_config; + mode_config->funcs = &mode_config_funcs; + mode_config->min_width = 1; + mode_config->max_width = 1024; + mode_config->min_height = 1; + mode_config->max_height = 768; + + ret = pl111_connector_init(dev); + if (ret) { + dev_err(dev->dev, "Failed to create pl111_drm_connector\n"); + goto out_config; + } + + /* Don't actually attach if we didn't find a drm_panel + * attached to us. This will allow a kernel to include both + * the fbdev pl111 driver and this one, and choose between + * them based on which subsystem has support for the panel. + */ + if (!priv->connector.panel) { + dev_info(dev->dev, + "Disabling due to lack of DRM panel device.\n"); + ret = -ENODEV; + goto out_config; + } + + ret = pl111_display_init(dev); + if (ret != 0) { + dev_err(dev->dev, "Failed to init display\n"); + goto out_config; + } + + ret = drm_vblank_init(dev, 1); + if (ret != 0) { + dev_err(dev->dev, "Failed to init vblank\n"); + goto out_config; + } + + drm_mode_config_reset(dev); + + priv->fbdev = drm_fbdev_cma_init(dev, 32, + dev->mode_config.num_connector); + + drm_kms_helper_poll_init(dev); + + goto finish; + +out_config: + drm_mode_config_cleanup(dev); +finish: + return ret; +} + +DEFINE_DRM_GEM_CMA_FOPS(drm_fops); + +static void pl111_lastclose(struct drm_device *dev) +{ + struct pl111_drm_dev_private *priv = dev->dev_private; + + drm_fbdev_cma_restore_mode(priv->fbdev); +} + +static struct drm_driver pl111_drm_driver = { + .driver_features = + DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | DRIVER_ATOMIC, + .lastclose = pl111_lastclose, + .ioctls = NULL, + .fops = &drm_fops, + .name = "pl111", + .desc = DRIVER_DESC, + .date = "20170317", + .major = 1, + .minor = 0, + .patchlevel = 0, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_destroy = drm_gem_dumb_destroy, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + + .enable_vblank = pl111_enable_vblank, + .disable_vblank = pl111_disable_vblank, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, +}; + +#ifdef CONFIG_ARM_AMBA +static int pl111_amba_probe(struct amba_device *amba_dev, + const struct amba_id *id) +{ + struct device *dev = &amba_dev->dev; + struct pl111_drm_dev_private *priv; + struct drm_device *drm; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + drm = drm_dev_alloc(&pl111_drm_driver, dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + amba_set_drvdata(amba_dev, drm); + priv->drm = drm; + drm->dev_private = priv; + + priv->clk = devm_clk_get(dev, "clcdclk"); + if (IS_ERR(priv->clk)) { + dev_err(dev, "CLCD: unable to get clk.\n"); + ret = PTR_ERR(priv->clk); + goto dev_unref; + } + + priv->regs = devm_ioremap_resource(dev, &amba_dev->res); + if (!priv->regs) { + dev_err(dev, "%s failed mmio\n", __func__); + return -EINVAL; + } + + /* turn off interrupts before requesting the irq */ + writel(0, priv->regs + CLCD_PL111_IENB); + + ret = devm_request_irq(dev, amba_dev->irq[0], pl111_irq, 0, + "pl111", priv); + if (ret != 0) { + dev_err(dev, "%s failed irq %d\n", __func__, ret); + return ret; + } + + ret = pl111_modeset_init(drm); + if (ret != 0) + goto dev_unref; + + ret = drm_dev_register(drm, 0); + if (ret < 0) + goto dev_unref; + + return 0; + +dev_unref: + drm_dev_unref(drm); + return ret; +} + +static int pl111_amba_remove(struct amba_device *amba_dev) +{ + struct drm_device *drm = amba_get_drvdata(amba_dev); + struct pl111_drm_dev_private *priv = drm->dev_private; + + drm_dev_unregister(drm); + if (priv->fbdev) + drm_fbdev_cma_fini(priv->fbdev); + drm_mode_config_cleanup(drm); + drm_dev_unref(drm); + + return 0; +} + +static struct amba_id pl111_id_table[] = { + { + .id = 0x00041111, + .mask = 0x000fffff, + }, + {0, 0}, +}; + +static struct amba_driver pl111_amba_driver = { + .drv = { + .name = "drm-clcd-pl111", + }, + .probe = pl111_amba_probe, + .remove = pl111_amba_remove, + .id_table = pl111_id_table, +}; + +module_amba_driver(pl111_amba_driver); +#endif /* CONFIG_ARM_AMBA */ + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("ARM Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c index 93d45aa5c3d4..ef8a75940980 100644 --- a/drivers/gpu/drm/radeon/radeon_drv.c +++ b/drivers/gpu/drm/radeon/radeon_drv.c @@ -115,10 +115,6 @@ int radeon_resume_kms(struct drm_device *dev, bool resume, bool fbcon); u32 radeon_get_vblank_counter_kms(struct drm_device *dev, unsigned int pipe); int radeon_enable_vblank_kms(struct drm_device *dev, unsigned int pipe); void radeon_disable_vblank_kms(struct drm_device *dev, unsigned int pipe); -int radeon_get_vblank_timestamp_kms(struct drm_device *dev, unsigned int pipe, - int *max_error, - struct timeval *vblank_time, - unsigned flags); void radeon_driver_irq_preinstall_kms(struct drm_device *dev); int radeon_driver_irq_postinstall_kms(struct drm_device *dev); void radeon_driver_irq_uninstall_kms(struct drm_device *dev); @@ -530,6 +526,16 @@ static const struct file_operations radeon_driver_kms_fops = { #endif }; +static bool +radeon_get_crtc_scanout_position(struct drm_device *dev, unsigned int pipe, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) +{ + return radeon_get_crtc_scanoutpos(dev, pipe, 0, vpos, hpos, + stime, etime, mode); +} + static struct drm_driver kms_driver = { .driver_features = DRIVER_USE_AGP | @@ -544,8 +550,8 @@ static struct drm_driver kms_driver = { .get_vblank_counter = radeon_get_vblank_counter_kms, .enable_vblank = radeon_enable_vblank_kms, .disable_vblank = radeon_disable_vblank_kms, - .get_vblank_timestamp = radeon_get_vblank_timestamp_kms, - .get_scanout_position = radeon_get_crtc_scanoutpos, + .get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos, + .get_scanout_position = radeon_get_crtc_scanout_position, .irq_preinstall = radeon_driver_irq_preinstall_kms, .irq_postinstall = radeon_driver_irq_postinstall_kms, .irq_uninstall = radeon_driver_irq_uninstall_kms, diff --git a/drivers/gpu/drm/radeon/radeon_kms.c b/drivers/gpu/drm/radeon/radeon_kms.c index e3e7cb1d10a2..6a68d440bc44 100644 --- a/drivers/gpu/drm/radeon/radeon_kms.c +++ b/drivers/gpu/drm/radeon/radeon_kms.c @@ -858,43 +858,6 @@ void radeon_disable_vblank_kms(struct drm_device *dev, int crtc) spin_unlock_irqrestore(&rdev->irq.lock, irqflags); } -/** - * radeon_get_vblank_timestamp_kms - get vblank timestamp - * - * @dev: drm dev pointer - * @crtc: crtc to get the timestamp for - * @max_error: max error - * @vblank_time: time value - * @flags: flags passed to the driver - * - * Gets the timestamp on the requested crtc based on the - * scanout position. (all asics). - * Returns postive status flags on success, negative error on failure. - */ -int radeon_get_vblank_timestamp_kms(struct drm_device *dev, int crtc, - int *max_error, - struct timeval *vblank_time, - unsigned flags) -{ - struct drm_crtc *drmcrtc; - struct radeon_device *rdev = dev->dev_private; - - if (crtc < 0 || crtc >= dev->num_crtcs) { - DRM_ERROR("Invalid crtc %d\n", crtc); - return -EINVAL; - } - - /* Get associated drm_crtc: */ - drmcrtc = &rdev->mode_info.crtcs[crtc]->base; - if (!drmcrtc) - return -EINVAL; - - /* Helper routine in DRM core does all the work: */ - return drm_calc_vbltimestamp_from_scanoutpos(dev, crtc, max_error, - vblank_time, flags, - &drmcrtc->hwmode); -} - const struct drm_ioctl_desc radeon_ioctls_kms[] = { DRM_IOCTL_DEF_DRV(RADEON_CP_INIT, drm_invalid_op, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), DRM_IOCTL_DEF_DRV(RADEON_CP_START, drm_invalid_op, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), diff --git a/drivers/gpu/drm/radeon/radeon_mode.h b/drivers/gpu/drm/radeon/radeon_mode.h index ad282648fc8b..00f5ec5c12c7 100644 --- a/drivers/gpu/drm/radeon/radeon_mode.h +++ b/drivers/gpu/drm/radeon/radeon_mode.h @@ -691,6 +691,9 @@ struct atom_voltage_table }; /* Driver internal use only flags of radeon_get_crtc_scanoutpos() */ +#define DRM_SCANOUTPOS_VALID (1 << 0) +#define DRM_SCANOUTPOS_IN_VBLANK (1 << 1) +#define DRM_SCANOUTPOS_ACCURATE (1 << 2) #define USE_REAL_VBLANKSTART (1 << 30) #define GET_DISTANCE_TO_VBLANKSTART (1 << 31) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index d8fa7a9c9240..1bccd827d2e4 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -104,26 +104,18 @@ static void analogix_dp_psr_work(struct work_struct *work) { struct rockchip_dp_device *dp = container_of(work, typeof(*dp), psr_work); - struct drm_crtc *crtc = dp->encoder.crtc; - int psr_state = dp->psr_state; - int vact_end; int ret; unsigned long flags; - if (!crtc) - return; - - vact_end = crtc->mode.vtotal - crtc->mode.vsync_start + crtc->mode.vdisplay; - - ret = rockchip_drm_wait_line_flag(dp->encoder.crtc, vact_end, - PSR_WAIT_LINE_FLAG_TIMEOUT_MS); + ret = rockchip_drm_wait_vact_end(dp->encoder.crtc, + PSR_WAIT_LINE_FLAG_TIMEOUT_MS); if (ret) { dev_err(dp->dev, "line flag interrupt did not arrive\n"); return; } spin_lock_irqsave(&dp->psr_lock, flags); - if (psr_state == EDP_VSC_PSR_STATE_ACTIVE) + if (dp->psr_state == EDP_VSC_PSR_STATE_ACTIVE) analogix_dp_enable_psr(dp->dev); else analogix_dp_disable_psr(dp->dev); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index a48fcce3f5f6..47905faf5586 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -62,8 +62,7 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev); -int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, - unsigned int mstimeout); +int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout); extern struct platform_driver cdn_dp_driver; extern struct platform_driver dw_hdmi_rockchip_pltfm_driver; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 3f7a82d1e095..40a5e6ef6f2c 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -468,7 +468,7 @@ static bool vop_line_flag_irq_is_enabled(struct vop *vop) return !!line_flag_irq; } -static void vop_line_flag_irq_enable(struct vop *vop, int line_num) +static void vop_line_flag_irq_enable(struct vop *vop) { unsigned long flags; @@ -477,7 +477,6 @@ static void vop_line_flag_irq_enable(struct vop *vop, int line_num) spin_lock_irqsave(&vop->irq_lock, flags); - VOP_CTRL_SET(vop, line_flag_num[0], line_num); VOP_INTR_SET_TYPE(vop, clear, LINE_FLAG_INTR, 1); VOP_INTR_SET_TYPE(vop, enable, LINE_FLAG_INTR, 1); @@ -981,6 +980,8 @@ static void vop_crtc_enable(struct drm_crtc *crtc) VOP_CTRL_SET(vop, vact_st_end, val); VOP_CTRL_SET(vop, vpost_st_end, val); + VOP_CTRL_SET(vop, line_flag_num[0], vact_end); + clk_set_rate(vop->dclk, adjusted_mode->clock * 1000); VOP_CTRL_SET(vop, standby, 0); @@ -1507,19 +1508,16 @@ static void vop_win_init(struct vop *vop) } /** - * rockchip_drm_wait_line_flag - acqiure the give line flag event + * rockchip_drm_wait_vact_end * @crtc: CRTC to enable line flag - * @line_num: interested line number * @mstimeout: millisecond for timeout * - * Driver would hold here until the interested line flag interrupt have - * happened or timeout to wait. + * Wait for vact_end line flag irq or timeout. * * Returns: * Zero on success, negative errno on failure. */ -int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, - unsigned int mstimeout) +int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout) { struct vop *vop = to_vop(crtc); unsigned long jiffies_left; @@ -1527,14 +1525,14 @@ int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, if (!crtc || !vop->is_enabled) return -ENODEV; - if (line_num > crtc->mode.vtotal || mstimeout <= 0) + if (mstimeout <= 0) return -EINVAL; if (vop_line_flag_irq_is_enabled(vop)) return -EBUSY; reinit_completion(&vop->line_flag_completion); - vop_line_flag_irq_enable(vop, line_num); + vop_line_flag_irq_enable(vop); jiffies_left = wait_for_completion_timeout(&vop->line_flag_completion, msecs_to_jiffies(mstimeout)); @@ -1547,7 +1545,7 @@ int rockchip_drm_wait_line_flag(struct drm_crtc *crtc, unsigned int line_num, return 0; } -EXPORT_SYMBOL(rockchip_drm_wait_line_flag); +EXPORT_SYMBOL(rockchip_drm_wait_vact_end); static int vop_bind(struct device *dev, struct device *master, void *data) { diff --git a/drivers/gpu/drm/selftests/test-drm_mm.c b/drivers/gpu/drm/selftests/test-drm_mm.c index fa356f5dae27..dfdd858eda0a 100644 --- a/drivers/gpu/drm/selftests/test-drm_mm.c +++ b/drivers/gpu/drm/selftests/test-drm_mm.c @@ -514,6 +514,8 @@ static int igt_reserve(void *ignored) ret = __igt_reserve(count, size + 1); if (ret) return ret; + + cond_resched(); } return 0; @@ -712,6 +714,10 @@ static int igt_insert(void *ignored) return ret; ret = __igt_insert(count, size + 1, false); + if (ret) + return ret; + + cond_resched(); } return 0; @@ -741,6 +747,10 @@ static int igt_replace(void *ignored) return ret; ret = __igt_insert(count, size + 1, true); + if (ret) + return ret; + + cond_resched(); } return 0; @@ -1011,6 +1021,8 @@ static int igt_insert_range(void *ignored) ret = __igt_insert_range(count, size, max/4+1, 3*max/4-1); if (ret) return ret; + + cond_resched(); } return 0; @@ -1056,6 +1068,7 @@ static int igt_align(void *ignored) drm_mm_for_each_node_safe(node, next, &mm) drm_mm_remove_node(node); DRM_MM_BUG_ON(!drm_mm_clean(&mm)); + cond_resched(); } ret = 0; @@ -1097,6 +1110,8 @@ static int igt_align_pot(int max) align, bit); goto out; } + + cond_resched(); } ret = 0; @@ -1471,6 +1486,8 @@ static int igt_evict(void *ignored) goto out; } } + + cond_resched(); } ret = 0; @@ -1566,6 +1583,8 @@ static int igt_evict_range(void *ignored) goto out; } } + + cond_resched(); } ret = 0; @@ -1683,6 +1702,7 @@ static int igt_topdown(void *ignored) drm_mm_for_each_node_safe(node, next, &mm) drm_mm_remove_node(node); DRM_MM_BUG_ON(!drm_mm_clean(&mm)); + cond_resched(); } ret = 0; @@ -1783,6 +1803,7 @@ static int igt_bottomup(void *ignored) drm_mm_for_each_node_safe(node, next, &mm) drm_mm_remove_node(node); DRM_MM_BUG_ON(!drm_mm_clean(&mm)); + cond_resched(); } ret = 0; @@ -1970,6 +1991,8 @@ static int igt_color(void *ignored) drm_mm_remove_node(node); kfree(node); } + + cond_resched(); } ret = 0; @@ -2047,6 +2070,7 @@ static int evict_color(struct drm_mm *mm, } } + cond_resched(); return 0; } @@ -2132,6 +2156,8 @@ static int igt_color_evict(void *ignored) goto out; } } + + cond_resched(); } ret = 0; @@ -2231,6 +2257,8 @@ static int igt_color_evict_range(void *ignored) goto out; } } + + cond_resched(); } ret = 0; diff --git a/drivers/gpu/drm/sti/sti_cursor.c b/drivers/gpu/drm/sti/sti_cursor.c index cca75bddb9ad..5b3a41f74f21 100644 --- a/drivers/gpu/drm/sti/sti_cursor.c +++ b/drivers/gpu/drm/sti/sti_cursor.c @@ -33,7 +33,7 @@ #define STI_CURS_MAX_SIZE 128 /* - * pixmap dma buffer stucture + * pixmap dma buffer structure * * @paddr: physical address * @size: buffer size @@ -121,8 +121,7 @@ static int cursor_dbg_show(struct seq_file *s, void *data) cursor_dbg_cml(s, cursor, readl(cursor->regs + CUR_CML)); DBGFS_DUMP(CUR_AWS); DBGFS_DUMP(CUR_AWE); - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } diff --git a/drivers/gpu/drm/sti/sti_dvo.c b/drivers/gpu/drm/sti/sti_dvo.c index bb23318a44b7..24ebc6b2f34d 100644 --- a/drivers/gpu/drm/sti/sti_dvo.c +++ b/drivers/gpu/drm/sti/sti_dvo.c @@ -186,8 +186,7 @@ static int dvo_dbg_show(struct seq_file *s, void *data) DBGFS_DUMP(DVO_LUT_PROG_MID); DBGFS_DUMP(DVO_LUT_PROG_HIGH); dvo_dbg_awg_microcode(s, dvo->regs + DVO_DIGSYNC_INSTR_I); - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } diff --git a/drivers/gpu/drm/sti/sti_gdp.c b/drivers/gpu/drm/sti/sti_gdp.c index 88f16cdf6a4b..5ee0503945c8 100644 --- a/drivers/gpu/drm/sti/sti_gdp.c +++ b/drivers/gpu/drm/sti/sti_gdp.c @@ -149,7 +149,7 @@ static void gdp_dbg_ctl(struct seq_file *s, int val) seq_puts(s, "\tColor:"); for (i = 0; i < ARRAY_SIZE(gdp_format_to_str); i++) { if (gdp_format_to_str[i].format == (val & 0x1F)) { - seq_printf(s, gdp_format_to_str[i].name); + seq_puts(s, gdp_format_to_str[i].name); break; } } @@ -266,8 +266,7 @@ static void gdp_node_dump_node(struct seq_file *s, struct sti_gdp_node *node) seq_printf(s, "\n\tKEY2 0x%08X", node->gam_gdp_key2); seq_printf(s, "\n\tPPT 0x%08X", node->gam_gdp_ppt); gdp_dbg_ppt(s, node->gam_gdp_ppt); - seq_printf(s, "\n\tCML 0x%08X", node->gam_gdp_cml); - seq_puts(s, "\n"); + seq_printf(s, "\n\tCML 0x%08X\n", node->gam_gdp_cml); } static int gdp_node_dbg_show(struct seq_file *s, void *arg) diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c index 0c0a75bc8bc3..d6ed909d9d75 100644 --- a/drivers/gpu/drm/sti/sti_hda.c +++ b/drivers/gpu/drm/sti/sti_hda.c @@ -320,8 +320,7 @@ static void hda_dbg_awg_microcode(struct seq_file *s, void __iomem *reg) { unsigned int i; - seq_puts(s, "\n\n"); - seq_puts(s, " HDA AWG microcode:"); + seq_puts(s, "\n\n HDA AWG microcode:"); for (i = 0; i < AWG_MAX_INST; i++) { if (i % 8 == 0) seq_printf(s, "\n %04X:", i); @@ -333,8 +332,7 @@ static void hda_dbg_video_dacs_ctrl(struct seq_file *s, void __iomem *reg) { u32 val = readl(reg); - seq_puts(s, "\n"); - seq_printf(s, "\n %-25s 0x%08X", "VIDEO_DACS_CONTROL", val); + seq_printf(s, "\n\n %-25s 0x%08X", "VIDEO_DACS_CONTROL", val); seq_puts(s, "\tHD DACs "); seq_puts(s, val & DAC_CFG_HD_HZUVW_OFF_MASK ? "disabled" : "enabled"); } @@ -356,8 +354,7 @@ static int hda_dbg_show(struct seq_file *s, void *data) hda_dbg_awg_microcode(s, hda->regs + HDA_SYNC_AWGI); if (hda->video_dacs_ctrl) hda_dbg_video_dacs_ctrl(s, hda->video_dacs_ctrl); - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c index 243905b6ae59..a59c95a8081b 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.c +++ b/drivers/gpu/drm/sti/sti_hdmi.c @@ -592,7 +592,7 @@ static void hdmi_dbg_cfg(struct seq_file *s, int val) { int tmp; - seq_puts(s, "\t"); + seq_putc(s, '\t'); tmp = val & HDMI_CFG_HDMI_NOT_DVI; DBGFS_PRINT_STR("mode:", tmp ? "HDMI" : "DVI"); seq_puts(s, "\t\t\t\t\t"); @@ -616,7 +616,7 @@ static void hdmi_dbg_sta(struct seq_file *s, int val) { int tmp; - seq_puts(s, "\t"); + seq_putc(s, '\t'); tmp = (val & HDMI_STA_DLL_LCK); DBGFS_PRINT_STR("pll:", tmp ? "locked" : "not locked"); seq_puts(s, "\t\t\t\t\t"); @@ -632,7 +632,7 @@ static void hdmi_dbg_sw_di_cfg(struct seq_file *s, int val) "once every field", "once every frame"}; - seq_puts(s, "\t"); + seq_putc(s, '\t'); tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 1)); DBGFS_PRINT_STR("Data island 1:", en_di[tmp]); seq_puts(s, "\t\t\t\t\t"); @@ -664,16 +664,16 @@ static int hdmi_dbg_show(struct seq_file *s, void *data) DBGFS_DUMP("\n", HDMI_STA); hdmi_dbg_sta(s, hdmi_read(hdmi, HDMI_STA)); DBGFS_DUMP("", HDMI_ACTIVE_VID_XMIN); - seq_puts(s, "\t"); + seq_putc(s, '\t'); DBGFS_PRINT_INT("Xmin:", hdmi_read(hdmi, HDMI_ACTIVE_VID_XMIN)); DBGFS_DUMP("", HDMI_ACTIVE_VID_XMAX); - seq_puts(s, "\t"); + seq_putc(s, '\t'); DBGFS_PRINT_INT("Xmax:", hdmi_read(hdmi, HDMI_ACTIVE_VID_XMAX)); DBGFS_DUMP("", HDMI_ACTIVE_VID_YMIN); - seq_puts(s, "\t"); + seq_putc(s, '\t'); DBGFS_PRINT_INT("Ymin:", hdmi_read(hdmi, HDMI_ACTIVE_VID_YMIN)); DBGFS_DUMP("", HDMI_ACTIVE_VID_YMAX); - seq_puts(s, "\t"); + seq_putc(s, '\t'); DBGFS_PRINT_INT("Ymax:", hdmi_read(hdmi, HDMI_ACTIVE_VID_YMAX)); DBGFS_DUMP("", HDMI_SW_DI_CFG); hdmi_dbg_sw_di_cfg(s, hdmi_read(hdmi, HDMI_SW_DI_CFG)); @@ -692,8 +692,7 @@ static int hdmi_dbg_show(struct seq_file *s, void *data) DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD4, HDMI_IFRAME_SLOT_AVI); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD5, HDMI_IFRAME_SLOT_AVI); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD6, HDMI_IFRAME_SLOT_AVI); - seq_puts(s, "\n"); - seq_printf(s, "\n AUDIO Infoframe (Data Island slot N=%d):", + seq_printf(s, "\n\n AUDIO Infoframe (Data Island slot N=%d):", HDMI_IFRAME_SLOT_AUDIO); DBGFS_DUMP_DI(HDMI_SW_DI_N_HEAD_WORD, HDMI_IFRAME_SLOT_AUDIO); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD0, HDMI_IFRAME_SLOT_AUDIO); @@ -703,8 +702,7 @@ static int hdmi_dbg_show(struct seq_file *s, void *data) DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD4, HDMI_IFRAME_SLOT_AUDIO); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD5, HDMI_IFRAME_SLOT_AUDIO); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD6, HDMI_IFRAME_SLOT_AUDIO); - seq_puts(s, "\n"); - seq_printf(s, "\n VENDOR SPECIFIC Infoframe (Data Island slot N=%d):", + seq_printf(s, "\n\n VENDOR SPECIFIC Infoframe (Data Island slot N=%d):", HDMI_IFRAME_SLOT_VENDOR); DBGFS_DUMP_DI(HDMI_SW_DI_N_HEAD_WORD, HDMI_IFRAME_SLOT_VENDOR); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD0, HDMI_IFRAME_SLOT_VENDOR); @@ -714,8 +712,7 @@ static int hdmi_dbg_show(struct seq_file *s, void *data) DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD4, HDMI_IFRAME_SLOT_VENDOR); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD5, HDMI_IFRAME_SLOT_VENDOR); DBGFS_DUMP_DI(HDMI_SW_DI_N_PKT_WORD6, HDMI_IFRAME_SLOT_VENDOR); - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } diff --git a/drivers/gpu/drm/sti/sti_hqvdp.c b/drivers/gpu/drm/sti/sti_hqvdp.c index 66f843148ef7..a1c161f77804 100644 --- a/drivers/gpu/drm/sti/sti_hqvdp.c +++ b/drivers/gpu/drm/sti/sti_hqvdp.c @@ -625,8 +625,7 @@ static int hqvdp_dbg_show(struct seq_file *s, void *data) hqvdp_dbg_dump_cmd(s, (struct sti_hqvdp_cmd *)virt); } - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } @@ -1357,12 +1356,12 @@ static int sti_hqvdp_probe(struct platform_device *pdev) /* Get Memory resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { + if (!res) { DRM_ERROR("Get memory resource failed\n"); return -ENXIO; } hqvdp->regs = devm_ioremap(dev, res->start, resource_size(res)); - if (hqvdp->regs == NULL) { + if (!hqvdp->regs) { DRM_ERROR("Register mapping failed\n"); return -ENXIO; } diff --git a/drivers/gpu/drm/sti/sti_mixer.c b/drivers/gpu/drm/sti/sti_mixer.c index 4ddc58f7fe2e..2bd1d46fe1cd 100644 --- a/drivers/gpu/drm/sti/sti_mixer.c +++ b/drivers/gpu/drm/sti/sti_mixer.c @@ -162,8 +162,7 @@ static int mixer_dbg_show(struct seq_file *s, void *arg) DBGFS_DUMP(GAM_MIXER_MBP); DBGFS_DUMP(GAM_MIXER_MX0); mixer_dbg_mxn(s, mixer->regs + GAM_MIXER_MX0); - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c index 8b8ea717c121..8959fcc743a8 100644 --- a/drivers/gpu/drm/sti/sti_tvout.c +++ b/drivers/gpu/drm/sti/sti_tvout.c @@ -459,7 +459,7 @@ static void tvout_dbg_vip(struct seq_file *s, int val) "Aux (color matrix by-passed)", "", "", "", "", "", "Force value"}; - seq_puts(s, "\t"); + seq_putc(s, '\t'); mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT; r = (val & mask) >> TVO_VIP_REORDER_R_SHIFT; mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT; @@ -558,8 +558,7 @@ static int tvout_dbg_show(struct seq_file *s, void *data) DBGFS_DUMP(TVO_CSC_AUX_M6); DBGFS_DUMP(TVO_CSC_AUX_M7); DBGFS_DUMP(TVO_AUX_IN_VID_FORMAT); - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } @@ -847,7 +846,7 @@ static int sti_tvout_probe(struct platform_device *pdev) tvout->dev = dev; - /* get Memory ressources */ + /* get memory resources */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tvout-reg"); if (!res) { DRM_ERROR("Invalid glue resource\n"); diff --git a/drivers/gpu/drm/sti/sti_vid.c b/drivers/gpu/drm/sti/sti_vid.c index 2ad59892b57e..577a3341d3c1 100644 --- a/drivers/gpu/drm/sti/sti_vid.c +++ b/drivers/gpu/drm/sti/sti_vid.c @@ -61,7 +61,7 @@ static void vid_dbg_ctl(struct seq_file *s, int val) { val = val >> 30; - seq_puts(s, "\t"); + seq_putc(s, '\t'); if (!(val & 1)) seq_puts(s, "NOT "); @@ -114,8 +114,7 @@ static int vid_dbg_show(struct seq_file *s, void *arg) DBGFS_DUMP(VID_BC); DBGFS_DUMP(VID_TINT); DBGFS_DUMP(VID_CSAT); - seq_puts(s, "\n"); - + seq_putc(s, '\n'); return 0; } diff --git a/drivers/gpu/drm/stm/Kconfig b/drivers/gpu/drm/stm/Kconfig new file mode 100644 index 000000000000..2c4817fb0890 --- /dev/null +++ b/drivers/gpu/drm/stm/Kconfig @@ -0,0 +1,16 @@ +config DRM_STM + tristate "DRM Support for STMicroelectronics SoC Series" + depends on DRM && (ARCH_STM32 || ARCH_MULTIPLATFORM) + select DRM_KMS_HELPER + select DRM_GEM_CMA_HELPER + select DRM_KMS_CMA_HELPER + select DRM_PANEL + select VIDEOMODE_HELPERS + select FB_PROVIDE_GET_FB_UNMAPPED_AREA + default y + + help + Enable support for the on-chip display controller on + STMicroelectronics STM32 MCUs. + To compile this driver as a module, choose M here: the module + will be called stm-drm. diff --git a/drivers/gpu/drm/stm/Makefile b/drivers/gpu/drm/stm/Makefile new file mode 100644 index 000000000000..e114d45dbd42 --- /dev/null +++ b/drivers/gpu/drm/stm/Makefile @@ -0,0 +1,7 @@ +ccflags-y := -Iinclude/drm + +stm-drm-y := \ + drv.o \ + ltdc.o + +obj-$(CONFIG_DRM_STM) += stm-drm.o diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c new file mode 100644 index 000000000000..83ab48f1fd00 --- /dev/null +++ b/drivers/gpu/drm/stm/drv.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) STMicroelectronics SA 2017 + * + * Authors: Philippe Cornu <philippe.cornu@st.com> + * Yannick Fertre <yannick.fertre@st.com> + * Fabien Dessenne <fabien.dessenne@st.com> + * Mickael Reulier <mickael.reulier@st.com> + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/component.h> +#include <linux/of_platform.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include "ltdc.h" + +#define DRIVER_NAME "stm" +#define DRIVER_DESC "STMicroelectronics SoC DRM" +#define DRIVER_DATE "20170330" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCH_LEVEL 0 + +#define STM_MAX_FB_WIDTH 2048 +#define STM_MAX_FB_HEIGHT 2048 /* same as width to handle orientation */ + +static void drv_output_poll_changed(struct drm_device *ddev) +{ + struct ltdc_device *ldev = ddev->dev_private; + + drm_fbdev_cma_hotplug_event(ldev->fbdev); +} + +static const struct drm_mode_config_funcs drv_mode_config_funcs = { + .fb_create = drm_fb_cma_create, + .output_poll_changed = drv_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void drv_lastclose(struct drm_device *ddev) +{ + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG("%s\n", __func__); + + drm_fbdev_cma_restore_mode(ldev->fbdev); +} + +DEFINE_DRM_GEM_CMA_FOPS(drv_driver_fops); + +static struct drm_driver drv_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | + DRIVER_ATOMIC, + .lastclose = drv_lastclose, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCH_LEVEL, + .fops = &drv_driver_fops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_free_object_unlocked = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + .enable_vblank = ltdc_crtc_enable_vblank, + .disable_vblank = ltdc_crtc_disable_vblank, +}; + +static int drv_load(struct drm_device *ddev) +{ + struct platform_device *pdev = to_platform_device(ddev->dev); + struct drm_fbdev_cma *fbdev; + struct ltdc_device *ldev; + int ret; + + DRM_DEBUG("%s\n", __func__); + + ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL); + if (!ldev) + return -ENOMEM; + + ddev->dev_private = (void *)ldev; + + drm_mode_config_init(ddev); + + /* + * set max width and height as default value. + * this value would be used to check framebuffer size limitation + * at drm_mode_addfb(). + */ + ddev->mode_config.min_width = 0; + ddev->mode_config.min_height = 0; + ddev->mode_config.max_width = STM_MAX_FB_WIDTH; + ddev->mode_config.max_height = STM_MAX_FB_HEIGHT; + ddev->mode_config.funcs = &drv_mode_config_funcs; + + ret = ltdc_load(ddev); + if (ret) + goto err; + + drm_mode_config_reset(ddev); + drm_kms_helper_poll_init(ddev); + + if (ddev->mode_config.num_connector) { + ldev = ddev->dev_private; + fbdev = drm_fbdev_cma_init(ddev, 16, + ddev->mode_config.num_connector); + if (IS_ERR(fbdev)) { + DRM_DEBUG("Warning: fails to create fbdev\n"); + fbdev = NULL; + } + ldev->fbdev = fbdev; + } + + platform_set_drvdata(pdev, ddev); + + return 0; +err: + drm_mode_config_cleanup(ddev); + return ret; +} + +static void drv_unload(struct drm_device *ddev) +{ + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG("%s\n", __func__); + + if (ldev->fbdev) { + drm_fbdev_cma_fini(ldev->fbdev); + ldev->fbdev = NULL; + } + drm_kms_helper_poll_fini(ddev); + ltdc_unload(ddev); + drm_mode_config_cleanup(ddev); +} + +static int stm_drm_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct drm_device *ddev; + int ret; + + DRM_DEBUG("%s\n", __func__); + + dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + + ddev = drm_dev_alloc(&drv_driver, dev); + if (IS_ERR(ddev)) + return PTR_ERR(ddev); + + ret = drv_load(ddev); + if (ret) + goto err_unref; + + ret = drm_dev_register(ddev, 0); + if (ret) + goto err_unref; + + return 0; + +err_unref: + drm_dev_unref(ddev); + + return ret; +} + +static int stm_drm_platform_remove(struct platform_device *pdev) +{ + struct drm_device *ddev = platform_get_drvdata(pdev); + + DRM_DEBUG("%s\n", __func__); + + drm_dev_unregister(ddev); + drv_unload(ddev); + drm_dev_unref(ddev); + + return 0; +} + +static const struct of_device_id drv_dt_ids[] = { + { .compatible = "st,stm32-ltdc"}, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, drv_dt_ids); + +static struct platform_driver stm_drm_platform_driver = { + .probe = stm_drm_platform_probe, + .remove = stm_drm_platform_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = drv_dt_ids, + }, +}; + +module_platform_driver(stm_drm_platform_driver); + +MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>"); +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>"); +MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>"); +MODULE_AUTHOR("Mickael Reulier <mickael.reulier@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST DRM LTDC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c new file mode 100644 index 000000000000..a40418cda74a --- /dev/null +++ b/drivers/gpu/drm/stm/ltdc.c @@ -0,0 +1,1160 @@ +/* + * Copyright (C) STMicroelectronics SA 2017 + * + * Authors: Philippe Cornu <philippe.cornu@st.com> + * Yannick Fertre <yannick.fertre@st.com> + * Fabien Dessenne <fabien.dessenne@st.com> + * Mickael Reulier <mickael.reulier@st.com> + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_address.h> +#include <linux/of_graph.h> +#include <linux/reset.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_plane_helper.h> + +#include <video/videomode.h> + +#include "ltdc.h" + +#define NB_CRTC 1 +#define CRTC_MASK GENMASK(NB_CRTC - 1, 0) + +#define MAX_IRQ 4 + +#define HWVER_10200 0x010200 +#define HWVER_10300 0x010300 +#define HWVER_20101 0x020101 + +/* + * The address of some registers depends on the HW version: such registers have + * an extra offset specified with reg_ofs. + */ +#define REG_OFS_NONE 0 +#define REG_OFS_4 4 /* Insertion of "Layer Configuration 2" reg */ +#define REG_OFS (ldev->caps.reg_ofs) +#define LAY_OFS 0x80 /* Register Offset between 2 layers */ + +/* Global register offsets */ +#define LTDC_IDR 0x0000 /* IDentification */ +#define LTDC_LCR 0x0004 /* Layer Count */ +#define LTDC_SSCR 0x0008 /* Synchronization Size Configuration */ +#define LTDC_BPCR 0x000C /* Back Porch Configuration */ +#define LTDC_AWCR 0x0010 /* Active Width Configuration */ +#define LTDC_TWCR 0x0014 /* Total Width Configuration */ +#define LTDC_GCR 0x0018 /* Global Control */ +#define LTDC_GC1R 0x001C /* Global Configuration 1 */ +#define LTDC_GC2R 0x0020 /* Global Configuration 2 */ +#define LTDC_SRCR 0x0024 /* Shadow Reload Configuration */ +#define LTDC_GACR 0x0028 /* GAmma Correction */ +#define LTDC_BCCR 0x002C /* Background Color Configuration */ +#define LTDC_IER 0x0034 /* Interrupt Enable */ +#define LTDC_ISR 0x0038 /* Interrupt Status */ +#define LTDC_ICR 0x003C /* Interrupt Clear */ +#define LTDC_LIPCR 0x0040 /* Line Interrupt Position Configuration */ +#define LTDC_CPSR 0x0044 /* Current Position Status */ +#define LTDC_CDSR 0x0048 /* Current Display Status */ + +/* Layer register offsets */ +#define LTDC_L1LC1R (0x0080) /* L1 Layer Configuration 1 */ +#define LTDC_L1LC2R (0x0084) /* L1 Layer Configuration 2 */ +#define LTDC_L1CR (0x0084 + REG_OFS) /* L1 Control */ +#define LTDC_L1WHPCR (0x0088 + REG_OFS) /* L1 Window Hor Position Config */ +#define LTDC_L1WVPCR (0x008C + REG_OFS) /* L1 Window Vert Position Config */ +#define LTDC_L1CKCR (0x0090 + REG_OFS) /* L1 Color Keying Configuration */ +#define LTDC_L1PFCR (0x0094 + REG_OFS) /* L1 Pixel Format Configuration */ +#define LTDC_L1CACR (0x0098 + REG_OFS) /* L1 Constant Alpha Config */ +#define LTDC_L1DCCR (0x009C + REG_OFS) /* L1 Default Color Configuration */ +#define LTDC_L1BFCR (0x00A0 + REG_OFS) /* L1 Blend Factors Configuration */ +#define LTDC_L1FBBCR (0x00A4 + REG_OFS) /* L1 FrameBuffer Bus Control */ +#define LTDC_L1AFBCR (0x00A8 + REG_OFS) /* L1 AuxFB Control */ +#define LTDC_L1CFBAR (0x00AC + REG_OFS) /* L1 Color FrameBuffer Address */ +#define LTDC_L1CFBLR (0x00B0 + REG_OFS) /* L1 Color FrameBuffer Length */ +#define LTDC_L1CFBLNR (0x00B4 + REG_OFS) /* L1 Color FrameBuffer Line Nb */ +#define LTDC_L1AFBAR (0x00B8 + REG_OFS) /* L1 AuxFB Address */ +#define LTDC_L1AFBLR (0x00BC + REG_OFS) /* L1 AuxFB Length */ +#define LTDC_L1AFBLNR (0x00C0 + REG_OFS) /* L1 AuxFB Line Number */ +#define LTDC_L1CLUTWR (0x00C4 + REG_OFS) /* L1 CLUT Write */ +#define LTDC_L1YS1R (0x00E0 + REG_OFS) /* L1 YCbCr Scale 1 */ +#define LTDC_L1YS2R (0x00E4 + REG_OFS) /* L1 YCbCr Scale 2 */ + +/* Bit definitions */ +#define SSCR_VSH GENMASK(10, 0) /* Vertical Synchronization Height */ +#define SSCR_HSW GENMASK(27, 16) /* Horizontal Synchronization Width */ + +#define BPCR_AVBP GENMASK(10, 0) /* Accumulated Vertical Back Porch */ +#define BPCR_AHBP GENMASK(27, 16) /* Accumulated Horizontal Back Porch */ + +#define AWCR_AAH GENMASK(10, 0) /* Accumulated Active Height */ +#define AWCR_AAW GENMASK(27, 16) /* Accumulated Active Width */ + +#define TWCR_TOTALH GENMASK(10, 0) /* TOTAL Height */ +#define TWCR_TOTALW GENMASK(27, 16) /* TOTAL Width */ + +#define GCR_LTDCEN BIT(0) /* LTDC ENable */ +#define GCR_DEN BIT(16) /* Dither ENable */ +#define GCR_PCPOL BIT(28) /* Pixel Clock POLarity */ +#define GCR_DEPOL BIT(29) /* Data Enable POLarity */ +#define GCR_VSPOL BIT(30) /* Vertical Synchro POLarity */ +#define GCR_HSPOL BIT(31) /* Horizontal Synchro POLarity */ + +#define GC1R_WBCH GENMASK(3, 0) /* Width of Blue CHannel output */ +#define GC1R_WGCH GENMASK(7, 4) /* Width of Green Channel output */ +#define GC1R_WRCH GENMASK(11, 8) /* Width of Red Channel output */ +#define GC1R_PBEN BIT(12) /* Precise Blending ENable */ +#define GC1R_DT GENMASK(15, 14) /* Dithering Technique */ +#define GC1R_GCT GENMASK(19, 17) /* Gamma Correction Technique */ +#define GC1R_SHREN BIT(21) /* SHadow Registers ENabled */ +#define GC1R_BCP BIT(22) /* Background Colour Programmable */ +#define GC1R_BBEN BIT(23) /* Background Blending ENabled */ +#define GC1R_LNIP BIT(24) /* Line Number IRQ Position */ +#define GC1R_TP BIT(25) /* Timing Programmable */ +#define GC1R_IPP BIT(26) /* IRQ Polarity Programmable */ +#define GC1R_SPP BIT(27) /* Sync Polarity Programmable */ +#define GC1R_DWP BIT(28) /* Dither Width Programmable */ +#define GC1R_STREN BIT(29) /* STatus Registers ENabled */ +#define GC1R_BMEN BIT(31) /* Blind Mode ENabled */ + +#define GC2R_EDCA BIT(0) /* External Display Control Ability */ +#define GC2R_STSAEN BIT(1) /* Slave Timing Sync Ability ENabled */ +#define GC2R_DVAEN BIT(2) /* Dual-View Ability ENabled */ +#define GC2R_DPAEN BIT(3) /* Dual-Port Ability ENabled */ +#define GC2R_BW GENMASK(6, 4) /* Bus Width (log2 of nb of bytes) */ +#define GC2R_EDCEN BIT(7) /* External Display Control ENabled */ + +#define SRCR_IMR BIT(0) /* IMmediate Reload */ +#define SRCR_VBR BIT(1) /* Vertical Blanking Reload */ + +#define BCCR_BCBLACK 0x00 /* Background Color BLACK */ +#define BCCR_BCBLUE GENMASK(7, 0) /* Background Color BLUE */ +#define BCCR_BCGREEN GENMASK(15, 8) /* Background Color GREEN */ +#define BCCR_BCRED GENMASK(23, 16) /* Background Color RED */ +#define BCCR_BCWHITE GENMASK(23, 0) /* Background Color WHITE */ + +#define IER_LIE BIT(0) /* Line Interrupt Enable */ +#define IER_FUIE BIT(1) /* Fifo Underrun Interrupt Enable */ +#define IER_TERRIE BIT(2) /* Transfer ERRor Interrupt Enable */ +#define IER_RRIE BIT(3) /* Register Reload Interrupt enable */ + +#define ISR_LIF BIT(0) /* Line Interrupt Flag */ +#define ISR_FUIF BIT(1) /* Fifo Underrun Interrupt Flag */ +#define ISR_TERRIF BIT(2) /* Transfer ERRor Interrupt Flag */ +#define ISR_RRIF BIT(3) /* Register Reload Interrupt Flag */ + +#define LXCR_LEN BIT(0) /* Layer ENable */ +#define LXCR_COLKEN BIT(1) /* Color Keying Enable */ +#define LXCR_CLUTEN BIT(4) /* Color Look-Up Table ENable */ + +#define LXWHPCR_WHSTPOS GENMASK(11, 0) /* Window Horizontal StarT POSition */ +#define LXWHPCR_WHSPPOS GENMASK(27, 16) /* Window Horizontal StoP POSition */ + +#define LXWVPCR_WVSTPOS GENMASK(10, 0) /* Window Vertical StarT POSition */ +#define LXWVPCR_WVSPPOS GENMASK(26, 16) /* Window Vertical StoP POSition */ + +#define LXPFCR_PF GENMASK(2, 0) /* Pixel Format */ + +#define LXCACR_CONSTA GENMASK(7, 0) /* CONSTant Alpha */ + +#define LXBFCR_BF2 GENMASK(2, 0) /* Blending Factor 2 */ +#define LXBFCR_BF1 GENMASK(10, 8) /* Blending Factor 1 */ + +#define LXCFBLR_CFBLL GENMASK(12, 0) /* Color Frame Buffer Line Length */ +#define LXCFBLR_CFBP GENMASK(28, 16) /* Color Frame Buffer Pitch in bytes */ + +#define LXCFBLNR_CFBLN GENMASK(10, 0) /* Color Frame Buffer Line Number */ + +#define HSPOL_AL 0 /* Horizontal Sync POLarity Active Low */ +#define VSPOL_AL 0 /* Vertical Sync POLarity Active Low */ +#define DEPOL_AL 0 /* Data Enable POLarity Active Low */ +#define PCPOL_IPC 0 /* Input Pixel Clock */ +#define HSPOL_AH GCR_HSPOL /* Horizontal Sync POLarity Active High */ +#define VSPOL_AH GCR_VSPOL /* Vertical Sync POLarity Active High */ +#define DEPOL_AH GCR_DEPOL /* Data Enable POLarity Active High */ +#define PCPOL_IIPC GCR_PCPOL /* Inverted Input Pixel Clock */ +#define CONSTA_MAX 0xFF /* CONSTant Alpha MAX= 1.0 */ +#define BF1_PAXCA 0x600 /* Pixel Alpha x Constant Alpha */ +#define BF1_CA 0x400 /* Constant Alpha */ +#define BF2_1PAXCA 0x007 /* 1 - (Pixel Alpha x Constant Alpha) */ +#define BF2_1CA 0x005 /* 1 - Constant Alpha */ + +#define NB_PF 8 /* Max nb of HW pixel format */ + +enum ltdc_pix_fmt { + PF_NONE, + /* RGB formats */ + PF_ARGB8888, /* ARGB [32 bits] */ + PF_RGBA8888, /* RGBA [32 bits] */ + PF_RGB888, /* RGB [24 bits] */ + PF_RGB565, /* RGB [16 bits] */ + PF_ARGB1555, /* ARGB A:1 bit RGB:15 bits [16 bits] */ + PF_ARGB4444, /* ARGB A:4 bits R/G/B: 4 bits each [16 bits] */ + /* Indexed formats */ + PF_L8, /* Indexed 8 bits [8 bits] */ + PF_AL44, /* Alpha:4 bits + indexed 4 bits [8 bits] */ + PF_AL88 /* Alpha:8 bits + indexed 8 bits [16 bits] */ +}; + +/* The index gives the encoding of the pixel format for an HW version */ +static const enum ltdc_pix_fmt ltdc_pix_fmt_a0[NB_PF] = { + PF_ARGB8888, /* 0x00 */ + PF_RGB888, /* 0x01 */ + PF_RGB565, /* 0x02 */ + PF_ARGB1555, /* 0x03 */ + PF_ARGB4444, /* 0x04 */ + PF_L8, /* 0x05 */ + PF_AL44, /* 0x06 */ + PF_AL88 /* 0x07 */ +}; + +static const enum ltdc_pix_fmt ltdc_pix_fmt_a1[NB_PF] = { + PF_ARGB8888, /* 0x00 */ + PF_RGB888, /* 0x01 */ + PF_RGB565, /* 0x02 */ + PF_RGBA8888, /* 0x03 */ + PF_AL44, /* 0x04 */ + PF_L8, /* 0x05 */ + PF_ARGB1555, /* 0x06 */ + PF_ARGB4444 /* 0x07 */ +}; + +static inline u32 reg_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +static inline void reg_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline void reg_set(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) | mask); +} + +static inline void reg_clear(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) & ~mask); +} + +static inline void reg_update_bits(void __iomem *base, u32 reg, u32 mask, + u32 val) +{ + reg_write(base, reg, (reg_read(base, reg) & ~mask) | val); +} + +static inline struct ltdc_device *crtc_to_ltdc(struct drm_crtc *crtc) +{ + return (struct ltdc_device *)crtc->dev->dev_private; +} + +static inline struct ltdc_device *plane_to_ltdc(struct drm_plane *plane) +{ + return (struct ltdc_device *)plane->dev->dev_private; +} + +static inline struct ltdc_device *encoder_to_ltdc(struct drm_encoder *enc) +{ + return (struct ltdc_device *)enc->dev->dev_private; +} + +static inline struct ltdc_device *connector_to_ltdc(struct drm_connector *con) +{ + return (struct ltdc_device *)con->dev->dev_private; +} + +static inline enum ltdc_pix_fmt to_ltdc_pixelformat(u32 drm_fmt) +{ + enum ltdc_pix_fmt pf; + + switch (drm_fmt) { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + pf = PF_ARGB8888; + break; + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + pf = PF_RGBA8888; + break; + case DRM_FORMAT_RGB888: + pf = PF_RGB888; + break; + case DRM_FORMAT_RGB565: + pf = PF_RGB565; + break; + case DRM_FORMAT_ARGB1555: + case DRM_FORMAT_XRGB1555: + pf = PF_ARGB1555; + break; + case DRM_FORMAT_ARGB4444: + case DRM_FORMAT_XRGB4444: + pf = PF_ARGB4444; + break; + case DRM_FORMAT_C8: + pf = PF_L8; + break; + default: + pf = PF_NONE; + break; + /* Note: There are no DRM_FORMAT for AL44 and AL88 */ + } + + return pf; +} + +static inline u32 to_drm_pixelformat(enum ltdc_pix_fmt pf) +{ + switch (pf) { + case PF_ARGB8888: + return DRM_FORMAT_ARGB8888; + case PF_RGBA8888: + return DRM_FORMAT_RGBA8888; + case PF_RGB888: + return DRM_FORMAT_RGB888; + case PF_RGB565: + return DRM_FORMAT_RGB565; + case PF_ARGB1555: + return DRM_FORMAT_ARGB1555; + case PF_ARGB4444: + return DRM_FORMAT_ARGB4444; + case PF_L8: + return DRM_FORMAT_C8; + case PF_AL44: /* No DRM support */ + case PF_AL88: /* No DRM support */ + case PF_NONE: + default: + return 0; + } +} + +static irqreturn_t ltdc_irq_thread(int irq, void *arg) +{ + struct drm_device *ddev = arg; + struct ltdc_device *ldev = ddev->dev_private; + struct drm_crtc *crtc = drm_crtc_from_index(ddev, 0); + + /* Line IRQ : trigger the vblank event */ + if (ldev->irq_status & ISR_LIF) + drm_crtc_handle_vblank(crtc); + + /* Save FIFO Underrun & Transfer Error status */ + mutex_lock(&ldev->err_lock); + if (ldev->irq_status & ISR_FUIF) + ldev->error_status |= ISR_FUIF; + if (ldev->irq_status & ISR_TERRIF) + ldev->error_status |= ISR_TERRIF; + mutex_unlock(&ldev->err_lock); + + return IRQ_HANDLED; +} + +static irqreturn_t ltdc_irq(int irq, void *arg) +{ + struct drm_device *ddev = arg; + struct ltdc_device *ldev = ddev->dev_private; + + /* Read & Clear the interrupt status */ + ldev->irq_status = reg_read(ldev->regs, LTDC_ISR); + reg_write(ldev->regs, LTDC_ICR, ldev->irq_status); + + return IRQ_WAKE_THREAD; +} + +/* + * DRM_CRTC + */ + +static void ltdc_crtc_load_lut(struct drm_crtc *crtc) +{ + struct ltdc_device *ldev = crtc_to_ltdc(crtc); + unsigned int i, lay; + + for (lay = 0; lay < ldev->caps.nb_layers; lay++) + for (i = 0; i < 256; i++) + reg_write(ldev->regs, LTDC_L1CLUTWR + lay * LAY_OFS, + ldev->clut[i]); +} + +static void ltdc_crtc_enable(struct drm_crtc *crtc) +{ + struct ltdc_device *ldev = crtc_to_ltdc(crtc); + + DRM_DEBUG_DRIVER("\n"); + + /* Sets the background color value */ + reg_write(ldev->regs, LTDC_BCCR, BCCR_BCBLACK); + + /* Enable IRQ */ + reg_set(ldev->regs, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); + + /* Immediately commit the planes */ + reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR); + + /* Enable LTDC */ + reg_set(ldev->regs, LTDC_GCR, GCR_LTDCEN); + + drm_crtc_vblank_on(crtc); +} + +static void ltdc_crtc_disable(struct drm_crtc *crtc) +{ + struct ltdc_device *ldev = crtc_to_ltdc(crtc); + + DRM_DEBUG_DRIVER("\n"); + + drm_crtc_vblank_off(crtc); + + /* disable LTDC */ + reg_clear(ldev->regs, LTDC_GCR, GCR_LTDCEN); + + /* disable IRQ */ + reg_clear(ldev->regs, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); + + /* immediately commit disable of layers before switching off LTDC */ + reg_set(ldev->regs, LTDC_SRCR, SRCR_IMR); +} + +static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct ltdc_device *ldev = crtc_to_ltdc(crtc); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct videomode vm; + int rate = mode->clock * 1000; + u32 hsync, vsync, accum_hbp, accum_vbp, accum_act_w, accum_act_h; + u32 total_width, total_height; + u32 val; + + drm_display_mode_to_videomode(mode, &vm); + + DRM_DEBUG_DRIVER("CRTC:%d mode:%s\n", crtc->base.id, mode->name); + DRM_DEBUG_DRIVER("Video mode: %dx%d", vm.hactive, vm.vactive); + DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n", + vm.hfront_porch, vm.hback_porch, vm.hsync_len, + vm.vfront_porch, vm.vback_porch, vm.vsync_len); + + /* Convert video timings to ltdc timings */ + hsync = vm.hsync_len - 1; + vsync = vm.vsync_len - 1; + accum_hbp = hsync + vm.hback_porch; + accum_vbp = vsync + vm.vback_porch; + accum_act_w = accum_hbp + vm.hactive; + accum_act_h = accum_vbp + vm.vactive; + total_width = accum_act_w + vm.hfront_porch; + total_height = accum_act_h + vm.vfront_porch; + + clk_disable(ldev->pixel_clk); + + if (clk_set_rate(ldev->pixel_clk, rate) < 0) { + DRM_ERROR("Cannot set rate (%dHz) for pixel clk\n", rate); + return; + } + + clk_enable(ldev->pixel_clk); + + /* Configures the HS, VS, DE and PC polarities. */ + val = HSPOL_AL | HSPOL_AL | DEPOL_AL | PCPOL_IPC; + + if (vm.flags & DISPLAY_FLAGS_HSYNC_HIGH) + val |= HSPOL_AH; + + if (vm.flags & DISPLAY_FLAGS_VSYNC_HIGH) + val |= VSPOL_AH; + + if (vm.flags & DISPLAY_FLAGS_DE_HIGH) + val |= DEPOL_AH; + + if (vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) + val |= PCPOL_IIPC; + + reg_update_bits(ldev->regs, LTDC_GCR, + GCR_HSPOL | GCR_VSPOL | GCR_DEPOL | GCR_PCPOL, val); + + /* Set Synchronization size */ + val = (hsync << 16) | vsync; + reg_update_bits(ldev->regs, LTDC_SSCR, SSCR_VSH | SSCR_HSW, val); + + /* Set Accumulated Back porch */ + val = (accum_hbp << 16) | accum_vbp; + reg_update_bits(ldev->regs, LTDC_BPCR, BPCR_AVBP | BPCR_AHBP, val); + + /* Set Accumulated Active Width */ + val = (accum_act_w << 16) | accum_act_h; + reg_update_bits(ldev->regs, LTDC_AWCR, AWCR_AAW | AWCR_AAH, val); + + /* Set total width & height */ + val = (total_width << 16) | total_height; + reg_update_bits(ldev->regs, LTDC_TWCR, TWCR_TOTALH | TWCR_TOTALW, val); + + reg_write(ldev->regs, LTDC_LIPCR, (accum_act_h + 1)); +} + +static void ltdc_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct ltdc_device *ldev = crtc_to_ltdc(crtc); + struct drm_pending_vblank_event *event = crtc->state->event; + + DRM_DEBUG_ATOMIC("\n"); + + /* Commit shadow registers = update planes at next vblank */ + reg_set(ldev->regs, LTDC_SRCR, SRCR_VBR); + + if (event) { + crtc->state->event = NULL; + + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); + } +} + +static struct drm_crtc_helper_funcs ltdc_crtc_helper_funcs = { + .load_lut = ltdc_crtc_load_lut, + .enable = ltdc_crtc_enable, + .disable = ltdc_crtc_disable, + .mode_set_nofb = ltdc_crtc_mode_set_nofb, + .atomic_flush = ltdc_crtc_atomic_flush, +}; + +int ltdc_crtc_enable_vblank(struct drm_device *ddev, unsigned int pipe) +{ + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG_DRIVER("\n"); + reg_set(ldev->regs, LTDC_IER, IER_LIE); + + return 0; +} + +void ltdc_crtc_disable_vblank(struct drm_device *ddev, unsigned int pipe) +{ + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG_DRIVER("\n"); + reg_clear(ldev->regs, LTDC_IER, IER_LIE); +} + +static struct drm_crtc_funcs ltdc_crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +/* + * DRM_PLANE + */ + +static int ltdc_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_framebuffer *fb = state->fb; + u32 src_x, src_y, src_w, src_h; + + DRM_DEBUG_DRIVER("\n"); + + if (!fb) + return 0; + + /* convert src_ from 16:16 format */ + src_x = state->src_x >> 16; + src_y = state->src_y >> 16; + src_w = state->src_w >> 16; + src_h = state->src_h >> 16; + + /* Reject scaling */ + if ((src_w != state->crtc_w) || (src_h != state->crtc_h)) { + DRM_ERROR("Scaling is not supported"); + return -EINVAL; + } + + return 0; +} + +static void ltdc_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *oldstate) +{ + struct ltdc_device *ldev = plane_to_ltdc(plane); + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + u32 lofs = plane->index * LAY_OFS; + u32 x0 = state->crtc_x; + u32 x1 = state->crtc_x + state->crtc_w - 1; + u32 y0 = state->crtc_y; + u32 y1 = state->crtc_y + state->crtc_h - 1; + u32 src_x, src_y, src_w, src_h; + u32 val, pitch_in_bytes, line_length, paddr, ahbp, avbp, bpcr; + enum ltdc_pix_fmt pf; + + if (!state->crtc || !fb) { + DRM_DEBUG_DRIVER("fb or crtc NULL"); + return; + } + + /* convert src_ from 16:16 format */ + src_x = state->src_x >> 16; + src_y = state->src_y >> 16; + src_w = state->src_w >> 16; + src_h = state->src_h >> 16; + + DRM_DEBUG_DRIVER( + "plane:%d fb:%d (%dx%d)@(%d,%d) -> (%dx%d)@(%d,%d)\n", + plane->base.id, fb->base.id, + src_w, src_h, src_x, src_y, + state->crtc_w, state->crtc_h, state->crtc_x, state->crtc_y); + + bpcr = reg_read(ldev->regs, LTDC_BPCR); + ahbp = (bpcr & BPCR_AHBP) >> 16; + avbp = bpcr & BPCR_AVBP; + + /* Configures the horizontal start and stop position */ + val = ((x1 + 1 + ahbp) << 16) + (x0 + 1 + ahbp); + reg_update_bits(ldev->regs, LTDC_L1WHPCR + lofs, + LXWHPCR_WHSTPOS | LXWHPCR_WHSPPOS, val); + + /* Configures the vertical start and stop position */ + val = ((y1 + 1 + avbp) << 16) + (y0 + 1 + avbp); + reg_update_bits(ldev->regs, LTDC_L1WVPCR + lofs, + LXWVPCR_WVSTPOS | LXWVPCR_WVSPPOS, val); + + /* Specifies the pixel format */ + pf = to_ltdc_pixelformat(fb->format->format); + for (val = 0; val < NB_PF; val++) + if (ldev->caps.pix_fmt_hw[val] == pf) + break; + + if (val == NB_PF) { + DRM_ERROR("Pixel format %.4s not supported\n", + (char *)&fb->format->format); + val = 0; /* set by default ARGB 32 bits */ + } + reg_update_bits(ldev->regs, LTDC_L1PFCR + lofs, LXPFCR_PF, val); + + /* Configures the color frame buffer pitch in bytes & line length */ + pitch_in_bytes = fb->pitches[0]; + line_length = drm_format_plane_cpp(fb->format->format, 0) * + (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1; + val = ((pitch_in_bytes << 16) | line_length); + reg_update_bits(ldev->regs, LTDC_L1CFBLR + lofs, + LXCFBLR_CFBLL | LXCFBLR_CFBP, val); + + /* Specifies the constant alpha value */ + val = CONSTA_MAX; + reg_update_bits(ldev->regs, LTDC_L1CACR + lofs, + LXCACR_CONSTA, val); + + /* Specifies the blending factors */ + val = BF1_PAXCA | BF2_1PAXCA; + reg_update_bits(ldev->regs, LTDC_L1BFCR + lofs, + LXBFCR_BF2 | LXBFCR_BF1, val); + + /* Configures the frame buffer line number */ + val = y1 - y0 + 1; + reg_update_bits(ldev->regs, LTDC_L1CFBLNR + lofs, + LXCFBLNR_CFBLN, val); + + /* Sets the FB address */ + paddr = (u32)drm_fb_cma_get_gem_addr(fb, state, 0); + + DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr); + reg_write(ldev->regs, LTDC_L1CFBAR + lofs, paddr); + + /* Enable layer and CLUT if needed */ + val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0; + val |= LXCR_LEN; + reg_update_bits(ldev->regs, LTDC_L1CR + lofs, + LXCR_LEN | LXCR_CLUTEN, val); + + mutex_lock(&ldev->err_lock); + if (ldev->error_status & ISR_FUIF) { + DRM_DEBUG_DRIVER("Fifo underrun\n"); + ldev->error_status &= ~ISR_FUIF; + } + if (ldev->error_status & ISR_TERRIF) { + DRM_DEBUG_DRIVER("Transfer error\n"); + ldev->error_status &= ~ISR_TERRIF; + } + mutex_unlock(&ldev->err_lock); +} + +static void ltdc_plane_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *oldstate) +{ + struct ltdc_device *ldev = plane_to_ltdc(plane); + u32 lofs = plane->index * LAY_OFS; + + /* disable layer */ + reg_clear(ldev->regs, LTDC_L1CR + lofs, LXCR_LEN); + + DRM_DEBUG_DRIVER("CRTC:%d plane:%d\n", + oldstate->crtc->base.id, plane->base.id); +} + +static struct drm_plane_funcs ltdc_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .set_property = drm_atomic_helper_plane_set_property, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static const struct drm_plane_helper_funcs ltdc_plane_helper_funcs = { + .atomic_check = ltdc_plane_atomic_check, + .atomic_update = ltdc_plane_atomic_update, + .atomic_disable = ltdc_plane_atomic_disable, +}; + +static struct drm_plane *ltdc_plane_create(struct drm_device *ddev, + enum drm_plane_type type) +{ + unsigned long possible_crtcs = CRTC_MASK; + struct ltdc_device *ldev = ddev->dev_private; + struct device *dev = ddev->dev; + struct drm_plane *plane; + unsigned int i, nb_fmt = 0; + u32 formats[NB_PF]; + u32 drm_fmt; + int ret; + + /* Get supported pixel formats */ + for (i = 0; i < NB_PF; i++) { + drm_fmt = to_drm_pixelformat(ldev->caps.pix_fmt_hw[i]); + if (!drm_fmt) + continue; + formats[nb_fmt++] = drm_fmt; + } + + plane = devm_kzalloc(dev, sizeof(*plane), GFP_KERNEL); + if (!plane) + return 0; + + ret = drm_universal_plane_init(ddev, plane, possible_crtcs, + <dc_plane_funcs, formats, nb_fmt, + type, NULL); + if (ret < 0) + return 0; + + drm_plane_helper_add(plane, <dc_plane_helper_funcs); + + DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id); + + return plane; +} + +static void ltdc_plane_destroy_all(struct drm_device *ddev) +{ + struct drm_plane *plane, *plane_temp; + + list_for_each_entry_safe(plane, plane_temp, + &ddev->mode_config.plane_list, head) + drm_plane_cleanup(plane); +} + +static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc) +{ + struct ltdc_device *ldev = ddev->dev_private; + struct drm_plane *primary, *overlay; + unsigned int i; + int res; + + primary = ltdc_plane_create(ddev, DRM_PLANE_TYPE_PRIMARY); + if (!primary) { + DRM_ERROR("Can not create primary plane\n"); + return -EINVAL; + } + + res = drm_crtc_init_with_planes(ddev, crtc, primary, NULL, + <dc_crtc_funcs, NULL); + if (res) { + DRM_ERROR("Can not initialize CRTC\n"); + goto cleanup; + } + + drm_crtc_helper_add(crtc, <dc_crtc_helper_funcs); + + DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id); + + /* Add planes. Note : the first layer is used by primary plane */ + for (i = 1; i < ldev->caps.nb_layers; i++) { + overlay = ltdc_plane_create(ddev, DRM_PLANE_TYPE_OVERLAY); + if (!overlay) { + res = -ENOMEM; + DRM_ERROR("Can not create overlay plane %d\n", i); + goto cleanup; + } + } + + return 0; + +cleanup: + ltdc_plane_destroy_all(ddev); + return res; +} + +/* + * DRM_ENCODER + */ + +static void ltdc_rgb_encoder_enable(struct drm_encoder *encoder) +{ + struct ltdc_device *ldev = encoder_to_ltdc(encoder); + + DRM_DEBUG_DRIVER("\n"); + + drm_panel_prepare(ldev->panel); + drm_panel_enable(ldev->panel); +} + +static void ltdc_rgb_encoder_disable(struct drm_encoder *encoder) +{ + struct ltdc_device *ldev = encoder_to_ltdc(encoder); + + DRM_DEBUG_DRIVER("\n"); + + drm_panel_disable(ldev->panel); + drm_panel_unprepare(ldev->panel); +} + +static const struct drm_encoder_helper_funcs ltdc_rgb_encoder_helper_funcs = { + .enable = ltdc_rgb_encoder_enable, + .disable = ltdc_rgb_encoder_disable, +}; + +static const struct drm_encoder_funcs ltdc_rgb_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static struct drm_encoder *ltdc_rgb_encoder_create(struct drm_device *ddev) +{ + struct drm_encoder *encoder; + + encoder = devm_kzalloc(ddev->dev, sizeof(*encoder), GFP_KERNEL); + if (!encoder) + return NULL; + + encoder->possible_crtcs = CRTC_MASK; + encoder->possible_clones = 0; /* No cloning support */ + + drm_encoder_init(ddev, encoder, <dc_rgb_encoder_funcs, + DRM_MODE_ENCODER_DPI, NULL); + + drm_encoder_helper_add(encoder, <dc_rgb_encoder_helper_funcs); + + DRM_DEBUG_DRIVER("RGB encoder:%d created\n", encoder->base.id); + + return encoder; +} + +/* + * DRM_CONNECTOR + */ + +static int ltdc_rgb_connector_get_modes(struct drm_connector *connector) +{ + struct drm_device *ddev = connector->dev; + struct ltdc_device *ldev = ddev->dev_private; + int ret = 0; + + DRM_DEBUG_DRIVER("\n"); + + if (ldev->panel) + ret = drm_panel_get_modes(ldev->panel); + + return ret < 0 ? 0 : ret; +} + +static struct drm_connector_helper_funcs ltdc_rgb_connector_helper_funcs = { + .get_modes = ltdc_rgb_connector_get_modes, +}; + +static enum drm_connector_status +ltdc_rgb_connector_detect(struct drm_connector *connector, bool force) +{ + struct ltdc_device *ldev = connector_to_ltdc(connector); + + return ldev->panel ? connector_status_connected : + connector_status_disconnected; +} + +static void ltdc_rgb_connector_destroy(struct drm_connector *connector) +{ + DRM_DEBUG_DRIVER("\n"); + + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs ltdc_rgb_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = ltdc_rgb_connector_detect, + .destroy = ltdc_rgb_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +struct drm_connector *ltdc_rgb_connector_create(struct drm_device *ddev) +{ + struct drm_connector *connector; + int err; + + connector = devm_kzalloc(ddev->dev, sizeof(*connector), GFP_KERNEL); + if (!connector) { + DRM_ERROR("Failed to allocate connector\n"); + return NULL; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + err = drm_connector_init(ddev, connector, <dc_rgb_connector_funcs, + DRM_MODE_CONNECTOR_DPI); + if (err) { + DRM_ERROR("Failed to initialize connector\n"); + return NULL; + } + + drm_connector_helper_add(connector, <dc_rgb_connector_helper_funcs); + + DRM_DEBUG_DRIVER("RGB connector:%d created\n", connector->base.id); + + return connector; +} + +static int ltdc_get_caps(struct drm_device *ddev) +{ + struct ltdc_device *ldev = ddev->dev_private; + u32 bus_width_log2, lcr, gc2r; + + /* at least 1 layer must be managed */ + lcr = reg_read(ldev->regs, LTDC_LCR); + + ldev->caps.nb_layers = max_t(int, lcr, 1); + + /* set data bus width */ + gc2r = reg_read(ldev->regs, LTDC_GC2R); + bus_width_log2 = (gc2r & GC2R_BW) >> 4; + ldev->caps.bus_width = 8 << bus_width_log2; + ldev->caps.hw_version = reg_read(ldev->regs, LTDC_IDR); + + switch (ldev->caps.hw_version) { + case HWVER_10200: + case HWVER_10300: + ldev->caps.reg_ofs = REG_OFS_NONE; + ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a0; + break; + case HWVER_20101: + ldev->caps.reg_ofs = REG_OFS_4; + ldev->caps.pix_fmt_hw = ltdc_pix_fmt_a1; + break; + default: + return -ENODEV; + } + + return 0; +} + +static struct drm_panel *ltdc_get_panel(struct drm_device *ddev) +{ + struct device *dev = ddev->dev; + struct device_node *np = dev->of_node; + struct device_node *entity, *port = NULL; + struct drm_panel *panel = NULL; + + DRM_DEBUG_DRIVER("\n"); + + /* + * Parse ltdc node to get remote port and find RGB panel / HDMI slave + * If a dsi or a bridge (hdmi, lvds...) is connected to ltdc, + * a remote port & RGB panel will not be found. + */ + for_each_endpoint_of_node(np, entity) { + if (!of_device_is_available(entity)) + continue; + + port = of_graph_get_remote_port_parent(entity); + if (port) { + panel = of_drm_find_panel(port); + of_node_put(port); + if (panel) { + DRM_DEBUG_DRIVER("remote panel %s\n", + port->full_name); + } else { + DRM_DEBUG_DRIVER("panel missing\n"); + of_node_put(entity); + } + } + } + + return panel; +} + +int ltdc_load(struct drm_device *ddev) +{ + struct platform_device *pdev = to_platform_device(ddev->dev); + struct ltdc_device *ldev = ddev->dev_private; + struct device *dev = ddev->dev; + struct device_node *np = dev->of_node; + struct drm_encoder *encoder; + struct drm_connector *connector = NULL; + struct drm_crtc *crtc; + struct reset_control *rstc; + struct resource res; + int irq, ret, i; + + DRM_DEBUG_DRIVER("\n"); + + ldev->panel = ltdc_get_panel(ddev); + if (!ldev->panel) + return -EPROBE_DEFER; + + rstc = of_reset_control_get(np, NULL); + + mutex_init(&ldev->err_lock); + + ldev->pixel_clk = devm_clk_get(dev, "lcd"); + if (IS_ERR(ldev->pixel_clk)) { + DRM_ERROR("Unable to get lcd clock\n"); + return -ENODEV; + } + + if (clk_prepare_enable(ldev->pixel_clk)) { + DRM_ERROR("Unable to prepare pixel clock\n"); + return -ENODEV; + } + + if (of_address_to_resource(np, 0, &res)) { + DRM_ERROR("Unable to get resource\n"); + return -ENODEV; + } + + ldev->regs = devm_ioremap_resource(dev, &res); + if (IS_ERR(ldev->regs)) { + DRM_ERROR("Unable to get ltdc registers\n"); + return PTR_ERR(ldev->regs); + } + + for (i = 0; i < MAX_IRQ; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + continue; + + ret = devm_request_threaded_irq(dev, irq, ltdc_irq, + ltdc_irq_thread, IRQF_ONESHOT, + dev_name(dev), ddev); + if (ret) { + DRM_ERROR("Failed to register LTDC interrupt\n"); + return ret; + } + } + + if (!IS_ERR(rstc)) + reset_control_deassert(rstc); + + /* Disable interrupts */ + reg_clear(ldev->regs, LTDC_IER, + IER_LIE | IER_RRIE | IER_FUIE | IER_TERRIE); + + ret = ltdc_get_caps(ddev); + if (ret) { + DRM_ERROR("hardware identifier (0x%08x) not supported!\n", + ldev->caps.hw_version); + return ret; + } + + DRM_INFO("ltdc hw version 0x%08x - ready\n", ldev->caps.hw_version); + + if (ldev->panel) { + encoder = ltdc_rgb_encoder_create(ddev); + if (!encoder) { + DRM_ERROR("Failed to create RGB encoder\n"); + ret = -EINVAL; + goto err; + } + + connector = ltdc_rgb_connector_create(ddev); + if (!connector) { + DRM_ERROR("Failed to create RGB connector\n"); + ret = -EINVAL; + goto err; + } + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) { + DRM_ERROR("Failed to attach connector to encoder\n"); + goto err; + } + + drm_panel_attach(ldev->panel, connector); + } + + crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); + if (!crtc) { + DRM_ERROR("Failed to allocate crtc\n"); + ret = -ENOMEM; + goto err; + } + + ret = ltdc_crtc_init(ddev, crtc); + if (ret) { + DRM_ERROR("Failed to init crtc\n"); + goto err; + } + + ret = drm_vblank_init(ddev, NB_CRTC); + if (ret) { + DRM_ERROR("Failed calling drm_vblank_init()\n"); + goto err; + } + + /* Allow usage of vblank without having to call drm_irq_install */ + ddev->irq_enabled = 1; + + return 0; +err: + if (ldev->panel) + drm_panel_detach(ldev->panel); + + clk_disable_unprepare(ldev->pixel_clk); + + return ret; +} + +void ltdc_unload(struct drm_device *ddev) +{ + struct ltdc_device *ldev = ddev->dev_private; + + DRM_DEBUG_DRIVER("\n"); + + drm_vblank_cleanup(ddev); + + if (ldev->panel) + drm_panel_detach(ldev->panel); + + clk_disable_unprepare(ldev->pixel_clk); +} + +MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>"); +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>"); +MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>"); +MODULE_AUTHOR("Mickael Reulier <mickael.reulier@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST DRM LTDC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h new file mode 100644 index 000000000000..d7a9c736ac1e --- /dev/null +++ b/drivers/gpu/drm/stm/ltdc.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) STMicroelectronics SA 2017 + * + * Authors: Philippe Cornu <philippe.cornu@st.com> + * Yannick Fertre <yannick.fertre@st.com> + * Fabien Dessenne <fabien.dessenne@st.com> + * Mickael Reulier <mickael.reulier@st.com> + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _LTDC_H_ +#define _LTDC_H_ + +struct ltdc_caps { + u32 hw_version; /* hardware version */ + u32 nb_layers; /* number of supported layers */ + u32 reg_ofs; /* register offset for applicable regs */ + u32 bus_width; /* bus width (32 or 64 bits) */ + const u32 *pix_fmt_hw; /* supported pixel formats */ +}; + +struct ltdc_device { + struct drm_fbdev_cma *fbdev; + void __iomem *regs; + struct clk *pixel_clk; /* lcd pixel clock */ + struct drm_panel *panel; + struct mutex err_lock; /* protecting error_status */ + struct ltdc_caps caps; + u32 clut[256]; /* color look up table */ + u32 error_status; + u32 irq_status; +}; + +int ltdc_crtc_enable_vblank(struct drm_device *dev, unsigned int pipe); +void ltdc_crtc_disable_vblank(struct drm_device *dev, unsigned int pipe); +int ltdc_load(struct drm_device *ddev); +void ltdc_unload(struct drm_device *ddev); + +#endif diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 9a1e34e48f64..51c48a8e00ec 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -892,7 +892,7 @@ static int tegra_drm_context_cleanup(int id, void *p, void *data) return 0; } -static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file) +static void tegra_drm_postclose(struct drm_device *drm, struct drm_file *file) { struct tegra_drm_file *fpriv = file->driver_priv; @@ -960,7 +960,7 @@ static struct drm_driver tegra_drm_driver = { .load = tegra_drm_load, .unload = tegra_drm_unload, .open = tegra_drm_open, - .preclose = tegra_drm_preclose, + .postclose = tegra_drm_postclose, .lastclose = tegra_drm_lastclose, #if defined(CONFIG_DEBUG_FS) diff --git a/drivers/gpu/drm/vc4/Makefile b/drivers/gpu/drm/vc4/Makefile index 61f45d122bd0..ab687fba4916 100644 --- a/drivers/gpu/drm/vc4/Makefile +++ b/drivers/gpu/drm/vc4/Makefile @@ -9,6 +9,7 @@ vc4-y := \ vc4_drv.o \ vc4_dpi.o \ vc4_dsi.o \ + vc4_fence.o \ vc4_kms.o \ vc4_gem.o \ vc4_hdmi.o \ diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index af29432a6471..80b2f9e55c5c 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -19,6 +19,8 @@ * rendering can return quickly. */ +#include <linux/dma-buf.h> + #include "vc4_drv.h" #include "uapi/drm/vc4_drm.h" @@ -88,6 +90,10 @@ static void vc4_bo_destroy(struct vc4_bo *bo) vc4->bo_stats.num_allocated--; vc4->bo_stats.size_allocated -= obj->size; + + if (bo->resv == &bo->_resv) + reservation_object_fini(bo->resv); + drm_gem_cma_free_object(obj); } @@ -244,8 +250,12 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size, return ERR_PTR(-ENOMEM); } } + bo = to_vc4_bo(&cma_obj->base); - return to_vc4_bo(&cma_obj->base); + bo->resv = &bo->_resv; + reservation_object_init(bo->resv); + + return bo; } int vc4_dumb_create(struct drm_file *file_priv, @@ -369,6 +379,13 @@ static void vc4_bo_cache_time_timer(unsigned long data) schedule_work(&vc4->bo_cache.time_work); } +struct reservation_object *vc4_prime_res_obj(struct drm_gem_object *obj) +{ + struct vc4_bo *bo = to_vc4_bo(obj); + + return bo->resv; +} + struct dma_buf * vc4_prime_export(struct drm_device *dev, struct drm_gem_object *obj, int flags) { @@ -440,6 +457,24 @@ void *vc4_prime_vmap(struct drm_gem_object *obj) return drm_gem_cma_prime_vmap(obj); } +struct drm_gem_object * +vc4_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + struct drm_gem_object *obj; + struct vc4_bo *bo; + + obj = drm_gem_cma_prime_import_sg_table(dev, attach, sgt); + if (IS_ERR(obj)) + return obj; + + bo = to_vc4_bo(obj); + bo->resv = attach->dmabuf->resv; + + return obj; +} + int vc4_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index d86c8cce3182..1b4dbe9e1c6d 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -151,10 +151,10 @@ int vc4_crtc_debugfs_regs(struct seq_file *m, void *unused) } #endif -int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, - unsigned int flags, int *vpos, int *hpos, - ktime_t *stime, ktime_t *etime, - const struct drm_display_mode *mode) +bool vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) { struct vc4_dev *vc4 = to_vc4_dev(dev); struct drm_crtc *crtc = drm_crtc_from_index(dev, crtc_id); @@ -162,7 +162,7 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, u32 val; int fifo_lines; int vblank_lines; - int ret = 0; + bool ret = false; /* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */ @@ -198,7 +198,7 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, fifo_lines = vc4_crtc->cob_size / mode->crtc_hdisplay; if (fifo_lines > 0) - ret |= DRM_SCANOUTPOS_VALID; + ret = true; /* HVS more than fifo_lines into frame for compositing? */ if (*vpos > fifo_lines) { @@ -216,7 +216,6 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, */ *vpos -= fifo_lines + 1; - ret |= DRM_SCANOUTPOS_ACCURATE; return ret; } @@ -229,10 +228,9 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, * We can't get meaningful readings wrt. scanline position of the PV * and need to make things up in a approximative but consistent way. */ - ret |= DRM_SCANOUTPOS_IN_VBLANK; vblank_lines = mode->vtotal - mode->vdisplay; - if (flags & DRM_CALLED_FROM_VBLIRQ) { + if (in_vblank_irq) { /* * Assume the irq handler got called close to first * line of vblank, so PV has about a full vblank @@ -254,9 +252,10 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, * we are at the very beginning of vblank, as the hvs just * started refilling, and the stime and etime timestamps * truly correspond to start of vblank. + * + * Unfortunately there's no way to report this to upper levels + * and make it more useful. */ - if ((val & SCALER_DISPSTATX_FULL) != SCALER_DISPSTATX_FULL) - ret |= DRM_SCANOUTPOS_ACCURATE; } else { /* * No clue where we are inside vblank. Return a vpos of zero, @@ -270,19 +269,6 @@ int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, return ret; } -int vc4_crtc_get_vblank_timestamp(struct drm_device *dev, unsigned int crtc_id, - int *max_error, struct timeval *vblank_time, - unsigned flags) -{ - struct drm_crtc *crtc = drm_crtc_from_index(dev, crtc_id); - struct drm_crtc_state *state = crtc->state; - - /* Helper routine in DRM core does all the work: */ - return drm_calc_vbltimestamp_from_scanoutpos(dev, crtc_id, max_error, - vblank_time, flags, - &state->adjusted_mode); -} - static void vc4_crtc_destroy(struct drm_crtc *crtc) { drm_crtc_cleanup(crtc); diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 61e674baf3a6..863974942c66 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -154,7 +154,7 @@ static struct drm_driver vc4_drm_driver = { .irq_uninstall = vc4_irq_uninstall, .get_scanout_position = vc4_crtc_get_scanoutpos, - .get_vblank_timestamp = vc4_crtc_get_vblank_timestamp, + .get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos, #if defined(CONFIG_DEBUG_FS) .debugfs_init = vc4_debugfs_init, @@ -168,8 +168,9 @@ static struct drm_driver vc4_drm_driver = { .prime_fd_to_handle = drm_gem_prime_fd_to_handle, .gem_prime_import = drm_gem_prime_import, .gem_prime_export = vc4_prime_export, + .gem_prime_res_obj = vc4_prime_res_obj, .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, - .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_import_sg_table = vc4_prime_import_sg_table, .gem_prime_vmap = vc4_prime_vmap, .gem_prime_vunmap = drm_gem_cma_prime_vunmap, .gem_prime_mmap = vc4_prime_mmap, @@ -334,6 +335,7 @@ static int vc4_platform_drm_remove(struct platform_device *pdev) static const struct of_device_id vc4_of_match[] = { { .compatible = "brcm,bcm2835-vc4", }, + { .compatible = "brcm,cygnus-vc4", }, {}, }; MODULE_DEVICE_TABLE(of, vc4_of_match); diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index dffce6293d87..5ba281361fb7 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -8,7 +8,9 @@ #include "drmP.h" #include "drm_gem_cma_helper.h" +#include "drm_gem_cma_helper.h" +#include <linux/reservation.h> #include <drm/drm_encoder.h> struct vc4_dev { @@ -56,6 +58,8 @@ struct vc4_dev { /* Protects bo_cache and the BO stats. */ struct mutex bo_lock; + uint64_t dma_fence_context; + /* Sequence number for the last job queued in bin_job_list. * Starts at 0 (no jobs emitted). */ @@ -95,12 +99,23 @@ struct vc4_dev { */ struct list_head seqno_cb_list; - /* The binner overflow memory that's currently set up in - * BPOA/BPOS registers. When overflow occurs and a new one is - * allocated, the previous one will be moved to - * vc4->current_exec's free list. + /* The memory used for storing binner tile alloc, tile state, + * and overflow memory allocations. This is freed when V3D + * powers down. + */ + struct vc4_bo *bin_bo; + + /* Size of blocks allocated within bin_bo. */ + uint32_t bin_alloc_size; + + /* Bitmask of the bin_alloc_size chunks in bin_bo that are + * used. */ - struct vc4_bo *overflow_mem; + uint32_t bin_alloc_used; + + /* Bitmask of the current bin_alloc used for overflow memory. */ + uint32_t bin_alloc_overflow; + struct work_struct overflow_mem_work; int power_refcount; @@ -150,6 +165,10 @@ struct vc4_bo { * DRM_IOCTL_VC4_CREATE_SHADER_BO. */ struct vc4_validated_shader_info *validated_shader; + + /* normally (resv == &_resv) except for imported bo's */ + struct reservation_object *resv; + struct reservation_object _resv; }; static inline struct vc4_bo * @@ -158,6 +177,19 @@ to_vc4_bo(struct drm_gem_object *bo) return (struct vc4_bo *)bo; } +struct vc4_fence { + struct dma_fence base; + struct drm_device *dev; + /* vc4 seqno for signaled() test */ + uint64_t seqno; +}; + +static inline struct vc4_fence * +to_vc4_fence(struct dma_fence *fence) +{ + return (struct vc4_fence *)fence; +} + struct vc4_seqno_cb { struct work_struct work; uint64_t seqno; @@ -168,6 +200,7 @@ struct vc4_v3d { struct vc4_dev *vc4; struct platform_device *pdev; void __iomem *regs; + struct clk *clk; }; struct vc4_hvs { @@ -230,6 +263,8 @@ struct vc4_exec_info { /* Latest write_seqno of any BO that binning depends on. */ uint64_t bin_dep_seqno; + struct dma_fence *fence; + /* Last current addresses the hardware was processing when the * hangcheck timer checked on us. */ @@ -293,8 +328,12 @@ struct vc4_exec_info { bool found_increment_semaphore_packet; bool found_flush; uint8_t bin_tiles_x, bin_tiles_y; - struct drm_gem_cma_object *tile_bo; + /* Physical address of the start of the tile alloc array + * (where each tile's binned CL will start) + */ uint32_t tile_alloc_offset; + /* Bitmask of which binner slots are freed when this job completes. */ + uint32_t bin_slots; /** * Computed addresses pointing into exec_bo where we start the @@ -436,7 +475,11 @@ int vc4_mmap_bo_ioctl(struct drm_device *dev, void *data, int vc4_get_hang_state_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); 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); +struct drm_gem_object *vc4_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, + struct sg_table *sgt); void *vc4_prime_vmap(struct drm_gem_object *obj); void vc4_bo_cache_init(struct drm_device *dev); void vc4_bo_cache_destroy(struct drm_device *dev); @@ -446,13 +489,10 @@ int vc4_bo_stats_debugfs(struct seq_file *m, void *arg); extern struct platform_driver vc4_crtc_driver; bool vc4_event_pending(struct drm_crtc *crtc); int vc4_crtc_debugfs_regs(struct seq_file *m, void *arg); -int vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, - unsigned int flags, int *vpos, int *hpos, - ktime_t *stime, ktime_t *etime, - const struct drm_display_mode *mode); -int vc4_crtc_get_vblank_timestamp(struct drm_device *dev, unsigned int crtc_id, - int *max_error, struct timeval *vblank_time, - unsigned flags); +bool vc4_crtc_get_scanoutpos(struct drm_device *dev, unsigned int crtc_id, + bool in_vblank_irq, int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode); /* vc4_debugfs.c */ int vc4_debugfs_init(struct drm_minor *minor); @@ -468,6 +508,9 @@ int vc4_dpi_debugfs_regs(struct seq_file *m, void *unused); extern struct platform_driver vc4_dsi_driver; int vc4_dsi_debugfs_regs(struct seq_file *m, void *unused); +/* vc4_fence.c */ +extern const struct dma_fence_ops vc4_fence_ops; + /* vc4_gem.c */ void vc4_gem_init(struct drm_device *dev); void vc4_gem_destroy(struct drm_device *dev); @@ -522,6 +565,7 @@ void vc4_plane_async_set_fb(struct drm_plane *plane, extern struct platform_driver vc4_v3d_driver; int vc4_v3d_debugfs_ident(struct seq_file *m, void *unused); int vc4_v3d_debugfs_regs(struct seq_file *m, void *unused); +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4); /* vc4_validate.c */ int diff --git a/drivers/gpu/drm/vc4/vc4_fence.c b/drivers/gpu/drm/vc4/vc4_fence.c new file mode 100644 index 000000000000..dbf5a5a5d5f5 --- /dev/null +++ b/drivers/gpu/drm/vc4/vc4_fence.c @@ -0,0 +1,56 @@ +/* + * Copyright © 2017 Broadcom + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "vc4_drv.h" + +static const char *vc4_fence_get_driver_name(struct dma_fence *fence) +{ + return "vc4"; +} + +static const char *vc4_fence_get_timeline_name(struct dma_fence *fence) +{ + return "vc4-v3d"; +} + +static bool vc4_fence_enable_signaling(struct dma_fence *fence) +{ + return true; +} + +static bool vc4_fence_signaled(struct dma_fence *fence) +{ + struct vc4_fence *f = to_vc4_fence(fence); + struct vc4_dev *vc4 = to_vc4_dev(f->dev); + + return vc4->finished_seqno >= f->seqno; +} + +const struct dma_fence_ops vc4_fence_ops = { + .get_driver_name = vc4_fence_get_driver_name, + .get_timeline_name = vc4_fence_get_timeline_name, + .enable_signaling = vc4_fence_enable_signaling, + .signaled = vc4_fence_signaled, + .wait = dma_fence_default_wait, + .release = dma_fence_free, +}; diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index e9c381c42139..735412e3725a 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -463,6 +463,8 @@ vc4_update_bo_seqnos(struct vc4_exec_info *exec, uint64_t seqno) for (i = 0; i < exec->bo_count; i++) { bo = to_vc4_bo(&exec->bo[i]->base); bo->seqno = seqno; + + reservation_object_add_shared_fence(bo->resv, exec->fence); } list_for_each_entry(bo, &exec->unref_list, unref_head) { @@ -472,7 +474,103 @@ vc4_update_bo_seqnos(struct vc4_exec_info *exec, uint64_t seqno) for (i = 0; i < exec->rcl_write_bo_count; i++) { bo = to_vc4_bo(&exec->rcl_write_bo[i]->base); bo->write_seqno = seqno; + + reservation_object_add_excl_fence(bo->resv, exec->fence); + } +} + +static void +vc4_unlock_bo_reservations(struct drm_device *dev, + struct vc4_exec_info *exec, + struct ww_acquire_ctx *acquire_ctx) +{ + int i; + + for (i = 0; i < exec->bo_count; i++) { + struct vc4_bo *bo = to_vc4_bo(&exec->bo[i]->base); + + ww_mutex_unlock(&bo->resv->lock); + } + + ww_acquire_fini(acquire_ctx); +} + +/* Takes the reservation lock on all the BOs being referenced, so that + * at queue submit time we can update the reservations. + * + * We don't lock the RCL the tile alloc/state BOs, or overflow memory + * (all of which are on exec->unref_list). They're entirely private + * to vc4, so we don't attach dma-buf fences to them. + */ +static int +vc4_lock_bo_reservations(struct drm_device *dev, + struct vc4_exec_info *exec, + struct ww_acquire_ctx *acquire_ctx) +{ + int contended_lock = -1; + int i, ret; + struct vc4_bo *bo; + + ww_acquire_init(acquire_ctx, &reservation_ww_class); + +retry: + if (contended_lock != -1) { + bo = to_vc4_bo(&exec->bo[contended_lock]->base); + ret = ww_mutex_lock_slow_interruptible(&bo->resv->lock, + acquire_ctx); + if (ret) { + ww_acquire_done(acquire_ctx); + return ret; + } + } + + for (i = 0; i < exec->bo_count; i++) { + if (i == contended_lock) + continue; + + bo = to_vc4_bo(&exec->bo[i]->base); + + ret = ww_mutex_lock_interruptible(&bo->resv->lock, acquire_ctx); + if (ret) { + int j; + + for (j = 0; j < i; j++) { + bo = to_vc4_bo(&exec->bo[j]->base); + ww_mutex_unlock(&bo->resv->lock); + } + + if (contended_lock != -1 && contended_lock >= i) { + bo = to_vc4_bo(&exec->bo[contended_lock]->base); + + ww_mutex_unlock(&bo->resv->lock); + } + + if (ret == -EDEADLK) { + contended_lock = i; + goto retry; + } + + ww_acquire_done(acquire_ctx); + return ret; + } } + + ww_acquire_done(acquire_ctx); + + /* Reserve space for our shared (read-only) fence references, + * before we commit the CL to the hardware. + */ + for (i = 0; i < exec->bo_count; i++) { + bo = to_vc4_bo(&exec->bo[i]->base); + + ret = reservation_object_reserve_shared(bo->resv); + if (ret) { + vc4_unlock_bo_reservations(dev, exec, acquire_ctx); + return ret; + } + } + + return 0; } /* Queues a struct vc4_exec_info for execution. If no job is @@ -484,19 +582,34 @@ vc4_update_bo_seqnos(struct vc4_exec_info *exec, uint64_t seqno) * then bump the end address. That's a change for a later date, * though. */ -static void -vc4_queue_submit(struct drm_device *dev, struct vc4_exec_info *exec) +static int +vc4_queue_submit(struct drm_device *dev, struct vc4_exec_info *exec, + struct ww_acquire_ctx *acquire_ctx) { struct vc4_dev *vc4 = to_vc4_dev(dev); uint64_t seqno; unsigned long irqflags; + struct vc4_fence *fence; + + fence = kzalloc(sizeof(*fence), GFP_KERNEL); + if (!fence) + return -ENOMEM; + fence->dev = dev; spin_lock_irqsave(&vc4->job_lock, irqflags); seqno = ++vc4->emit_seqno; exec->seqno = seqno; + + dma_fence_init(&fence->base, &vc4_fence_ops, &vc4->job_lock, + vc4->dma_fence_context, exec->seqno); + fence->seqno = exec->seqno; + exec->fence = &fence->base; + vc4_update_bo_seqnos(exec, seqno); + vc4_unlock_bo_reservations(dev, exec, acquire_ctx); + list_add_tail(&exec->head, &vc4->bin_job_list); /* If no job was executing, kick ours off. Otherwise, it'll @@ -509,6 +622,8 @@ vc4_queue_submit(struct drm_device *dev, struct vc4_exec_info *exec) } spin_unlock_irqrestore(&vc4->job_lock, irqflags); + + return 0; } /** @@ -705,8 +820,15 @@ static void vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec) { struct vc4_dev *vc4 = to_vc4_dev(dev); + unsigned long irqflags; unsigned i; + /* If we got force-completed because of GPU reset rather than + * through our IRQ handler, signal the fence now. + */ + if (exec->fence) + dma_fence_signal(exec->fence); + if (exec->bo) { for (i = 0; i < exec->bo_count; i++) drm_gem_object_unreference_unlocked(&exec->bo[i]->base); @@ -720,6 +842,11 @@ vc4_complete_exec(struct drm_device *dev, struct vc4_exec_info *exec) drm_gem_object_unreference_unlocked(&bo->base.base); } + /* Free up the allocation of any bin slots we used. */ + spin_lock_irqsave(&vc4->job_lock, irqflags); + vc4->bin_alloc_used &= ~exec->bin_slots; + spin_unlock_irqrestore(&vc4->job_lock, irqflags); + mutex_lock(&vc4->power_lock); if (--vc4->power_refcount == 0) { pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev); @@ -874,6 +1001,7 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data, struct vc4_dev *vc4 = to_vc4_dev(dev); struct drm_vc4_submit_cl *args = data; struct vc4_exec_info *exec; + struct ww_acquire_ctx acquire_ctx; int ret = 0; if ((args->flags & ~VC4_SUBMIT_CL_USE_CLEAR_COLOR) != 0) { @@ -888,13 +1016,16 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data, } mutex_lock(&vc4->power_lock); - if (vc4->power_refcount++ == 0) + if (vc4->power_refcount++ == 0) { ret = pm_runtime_get_sync(&vc4->v3d->pdev->dev); - mutex_unlock(&vc4->power_lock); - if (ret < 0) { - kfree(exec); - return ret; + if (ret < 0) { + mutex_unlock(&vc4->power_lock); + vc4->power_refcount--; + kfree(exec); + return ret; + } } + mutex_unlock(&vc4->power_lock); exec->args = args; INIT_LIST_HEAD(&exec->unref_list); @@ -916,12 +1047,18 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data, if (ret) goto fail; + ret = vc4_lock_bo_reservations(dev, exec, &acquire_ctx); + if (ret) + goto fail; + /* Clear this out of the struct we'll be putting in the queue, * since it's part of our stack. */ exec->args = NULL; - vc4_queue_submit(dev, exec); + ret = vc4_queue_submit(dev, exec, &acquire_ctx); + if (ret) + goto fail; /* Return the seqno for our job. */ args->seqno = vc4->emit_seqno; @@ -939,6 +1076,8 @@ vc4_gem_init(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); + vc4->dma_fence_context = dma_fence_context_alloc(1); + INIT_LIST_HEAD(&vc4->bin_job_list); INIT_LIST_HEAD(&vc4->render_job_list); INIT_LIST_HEAD(&vc4->job_done_list); @@ -968,9 +1107,9 @@ vc4_gem_destroy(struct drm_device *dev) /* V3D should already have disabled its interrupt and cleared * the overflow allocation registers. Now free the object. */ - if (vc4->overflow_mem) { - drm_gem_object_unreference_unlocked(&vc4->overflow_mem->base.base); - vc4->overflow_mem = NULL; + if (vc4->bin_bo) { + drm_gem_object_put_unlocked(&vc4->bin_bo->base.base); + vc4->bin_bo = NULL; } if (vc4->hang_state) diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index e9cbe269710b..3c2723f6345c 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -51,6 +51,7 @@ #include "linux/of_address.h" #include "linux/of_gpio.h" #include "linux/of_platform.h" +#include "linux/pm_runtime.h" #include "linux/rational.h" #include "sound/dmaengine_pcm.h" #include "sound/pcm_drm_eld.h" @@ -449,13 +450,38 @@ static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder) vc4_hdmi_set_spd_infoframe(encoder); } -static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *unadjusted_mode, - struct drm_display_mode *mode) +static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hdmi *hdmi = vc4->hdmi; + int ret; + + HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, 0); + + HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16); + HD_WRITE(VC4_HD_VID_CTL, + HD_READ(VC4_HD_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE); + + HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST); + udelay(1); + HD_WRITE(VC4_HD_M_CTL, 0); + + clk_disable_unprepare(hdmi->hsm_clock); + clk_disable_unprepare(hdmi->pixel_clock); + + ret = pm_runtime_put(&hdmi->pdev->dev); + if (ret < 0) + DRM_ERROR("Failed to release power domain: %d\n", ret); +} + +static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) { + struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); struct drm_device *dev = encoder->dev; struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hdmi *hdmi = vc4->hdmi; bool debug_dump_regs = false; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; @@ -475,6 +501,64 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, interlaced, VC4_HDMI_VERTB_VBP)); u32 csc_ctl; + int ret; + + ret = pm_runtime_get_sync(&hdmi->pdev->dev); + if (ret < 0) { + DRM_ERROR("Failed to retain power domain: %d\n", ret); + return; + } + + /* This is the rate that is set by the firmware. The number + * needs to be a bit higher than the pixel clock rate + * (generally 148.5Mhz). + */ + ret = clk_set_rate(hdmi->hsm_clock, 163682864); + if (ret) { + DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); + return; + } + + ret = clk_set_rate(hdmi->pixel_clock, + mode->clock * 1000 * + ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1)); + if (ret) { + DRM_ERROR("Failed to set pixel clock rate: %d\n", ret); + return; + } + + ret = clk_prepare_enable(hdmi->pixel_clock); + if (ret) { + DRM_ERROR("Failed to turn on pixel clock: %d\n", ret); + return; + } + + ret = clk_prepare_enable(hdmi->hsm_clock); + if (ret) { + DRM_ERROR("Failed to turn on HDMI state machine clock: %d\n", + ret); + clk_disable_unprepare(hdmi->pixel_clock); + return; + } + + HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST); + udelay(1); + HD_WRITE(VC4_HD_M_CTL, 0); + + HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_ENABLE); + + HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, + VC4_HDMI_SW_RESET_HDMI | + VC4_HDMI_SW_RESET_FORMAT_DETECT); + + HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, 0); + + /* PHY should be in reset, like + * vc4_hdmi_encoder_disable() does. + */ + HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16); + + HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0); if (debug_dump_regs) { DRM_INFO("HDMI regs before:\n"); @@ -483,9 +567,6 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, HD_WRITE(VC4_HD_VID_CTL, 0); - clk_set_rate(vc4->hdmi->pixel_clock, mode->clock * 1000 * - ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1)); - HDMI_WRITE(VC4_HDMI_SCHEDULER_CONTROL, HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) | VC4_HDMI_SCHEDULER_CONTROL_MANUAL_FORMAT | @@ -559,28 +640,6 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, DRM_INFO("HDMI regs after:\n"); vc4_hdmi_dump_regs(dev); } -} - -static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) -{ - struct drm_device *dev = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); - - HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, 0); - - HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16); - HD_WRITE(VC4_HD_VID_CTL, - HD_READ(VC4_HD_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE); -} - -static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) -{ - struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); - struct drm_device *dev = encoder->dev; - struct vc4_dev *vc4 = to_vc4_dev(dev); - int ret; - - HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0); HD_WRITE(VC4_HD_VID_CTL, HD_READ(VC4_HD_VID_CTL) | @@ -646,7 +705,6 @@ static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) } static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { - .mode_set = vc4_hdmi_encoder_mode_set, .disable = vc4_hdmi_encoder_disable, .enable = vc4_hdmi_encoder_enable, }; @@ -1147,33 +1205,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return -EPROBE_DEFER; } - /* Enable the clocks at startup. We can't quite recover from - * turning off the pixel clock during disable/enables yet, so - * it's always running. - */ - ret = clk_prepare_enable(hdmi->pixel_clock); - if (ret) { - DRM_ERROR("Failed to turn on pixel clock: %d\n", ret); - goto err_put_i2c; - } - - /* This is the rate that is set by the firmware. The number - * needs to be a bit higher than the pixel clock rate - * (generally 148.5Mhz). - */ - ret = clk_set_rate(hdmi->hsm_clock, 163682864); - if (ret) { - DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); - goto err_unprepare_pix; - } - - ret = clk_prepare_enable(hdmi->hsm_clock); - if (ret) { - DRM_ERROR("Failed to turn on HDMI state machine clock: %d\n", - ret); - goto err_unprepare_pix; - } - /* Only use the GPIO HPD pin if present in the DT, otherwise * we'll use the HDMI core's register. */ @@ -1185,7 +1216,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) &hpd_gpio_flags); if (hdmi->hpd_gpio < 0) { ret = hdmi->hpd_gpio; - goto err_unprepare_hsm; + goto err_put_i2c; } hdmi->hpd_active_low = hpd_gpio_flags & OF_GPIO_ACTIVE_LOW; @@ -1193,25 +1224,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) vc4->hdmi = hdmi; - /* HDMI core must be enabled. */ - if (!(HD_READ(VC4_HD_M_CTL) & VC4_HD_M_ENABLE)) { - HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_SW_RST); - udelay(1); - HD_WRITE(VC4_HD_M_CTL, 0); - - HD_WRITE(VC4_HD_M_CTL, VC4_HD_M_ENABLE); - - HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, - VC4_HDMI_SW_RESET_HDMI | - VC4_HDMI_SW_RESET_FORMAT_DETECT); - - HDMI_WRITE(VC4_HDMI_SW_RESET_CONTROL, 0); - - /* PHY should be in reset, like - * vc4_hdmi_encoder_disable() does. - */ - HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16); - } + pm_runtime_enable(dev); drm_encoder_init(drm, hdmi->encoder, &vc4_hdmi_encoder_funcs, DRM_MODE_ENCODER_TMDS, NULL); @@ -1231,10 +1244,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) err_destroy_encoder: vc4_hdmi_encoder_destroy(hdmi->encoder); -err_unprepare_hsm: - clk_disable_unprepare(hdmi->hsm_clock); -err_unprepare_pix: - clk_disable_unprepare(hdmi->pixel_clock); + pm_runtime_disable(dev); err_put_i2c: put_device(&hdmi->ddc->dev); @@ -1253,8 +1263,8 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, vc4_hdmi_connector_destroy(hdmi->connector); vc4_hdmi_encoder_destroy(hdmi->encoder); - clk_disable_unprepare(hdmi->pixel_clock); - clk_disable_unprepare(hdmi->hsm_clock); + pm_runtime_disable(dev); + put_device(&hdmi->ddc->dev); vc4->hdmi = NULL; diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index cdc6e6760705..7d7af3a93d94 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -59,50 +59,45 @@ vc4_overflow_mem_work(struct work_struct *work) { struct vc4_dev *vc4 = container_of(work, struct vc4_dev, overflow_mem_work); - struct drm_device *dev = vc4->dev; - struct vc4_bo *bo; + struct vc4_bo *bo = vc4->bin_bo; + int bin_bo_slot; + struct vc4_exec_info *exec; + unsigned long irqflags; - bo = vc4_bo_create(dev, 256 * 1024, true); - if (IS_ERR(bo)) { + bin_bo_slot = vc4_v3d_get_bin_slot(vc4); + if (bin_bo_slot < 0) { DRM_ERROR("Couldn't allocate binner overflow mem\n"); return; } - /* If there's a job executing currently, then our previous - * overflow allocation is getting used in that job and we need - * to queue it to be released when the job is done. But if no - * job is executing at all, then we can free the old overflow - * object direcctly. - * - * No lock necessary for this pointer since we're the only - * ones that update the pointer, and our workqueue won't - * reenter. - */ - if (vc4->overflow_mem) { - struct vc4_exec_info *current_exec; - unsigned long irqflags; - - spin_lock_irqsave(&vc4->job_lock, irqflags); - current_exec = vc4_first_bin_job(vc4); - if (!current_exec) - current_exec = vc4_last_render_job(vc4); - if (current_exec) { - vc4->overflow_mem->seqno = current_exec->seqno; - list_add_tail(&vc4->overflow_mem->unref_head, - ¤t_exec->unref_list); - vc4->overflow_mem = NULL; + spin_lock_irqsave(&vc4->job_lock, irqflags); + + if (vc4->bin_alloc_overflow) { + /* If we had overflow memory allocated previously, + * then that chunk will free when the current bin job + * is done. If we don't have a bin job running, then + * the chunk will be done whenever the list of render + * jobs has drained. + */ + exec = vc4_first_bin_job(vc4); + if (!exec) + exec = vc4_last_render_job(vc4); + if (exec) { + exec->bin_slots |= vc4->bin_alloc_overflow; + } else { + /* There's nothing queued in the hardware, so + * the old slot is free immediately. + */ + vc4->bin_alloc_used &= ~vc4->bin_alloc_overflow; } - spin_unlock_irqrestore(&vc4->job_lock, irqflags); } + vc4->bin_alloc_overflow = BIT(bin_bo_slot); - if (vc4->overflow_mem) - drm_gem_object_unreference_unlocked(&vc4->overflow_mem->base.base); - vc4->overflow_mem = bo; - - V3D_WRITE(V3D_BPOA, bo->base.paddr); + V3D_WRITE(V3D_BPOA, bo->base.paddr + bin_bo_slot * vc4->bin_alloc_size); V3D_WRITE(V3D_BPOS, bo->base.base.size); V3D_WRITE(V3D_INTCTL, V3D_INT_OUTOMEM); V3D_WRITE(V3D_INTENA, V3D_INT_OUTOMEM); + spin_unlock_irqrestore(&vc4->job_lock, irqflags); } static void @@ -142,6 +137,10 @@ vc4_irq_finish_render_job(struct drm_device *dev) vc4->finished_seqno++; list_move_tail(&exec->head, &vc4->job_done_list); + if (exec->fence) { + dma_fence_signal_locked(exec->fence); + exec->fence = NULL; + } vc4_submit_next_render_job(dev); wake_up_all(&vc4->job_wait_queue); diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index ad7925a9e0ea..237a504f11f0 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -230,10 +230,12 @@ int vc4_kms_load(struct drm_device *dev) drm_mode_config_reset(dev); - vc4->fbdev = drm_fbdev_cma_init(dev, 32, - dev->mode_config.num_connector); - if (IS_ERR(vc4->fbdev)) - vc4->fbdev = NULL; + if (dev->mode_config.num_connector) { + vc4->fbdev = drm_fbdev_cma_init(dev, 32, + dev->mode_config.num_connector); + if (IS_ERR(vc4->fbdev)) + vc4->fbdev = NULL; + } drm_kms_helper_poll_init(dev); diff --git a/drivers/gpu/drm/vc4/vc4_render_cl.c b/drivers/gpu/drm/vc4/vc4_render_cl.c index 4339471f517f..5dc19429d4ae 100644 --- a/drivers/gpu/drm/vc4/vc4_render_cl.c +++ b/drivers/gpu/drm/vc4/vc4_render_cl.c @@ -182,8 +182,7 @@ static void emit_tile(struct vc4_exec_info *exec, if (has_bin) { rcl_u8(setup, VC4_PACKET_BRANCH_TO_SUB_LIST); - rcl_u32(setup, (exec->tile_bo->paddr + - exec->tile_alloc_offset + + rcl_u32(setup, (exec->tile_alloc_offset + (y * exec->bin_tiles_x + x) * 32)); } diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 7cc346ad9b0b..c53afec34586 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -16,6 +16,7 @@ * this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "linux/clk.h" #include "linux/component.h" #include "linux/pm_runtime.h" #include "vc4_drv.h" @@ -156,6 +157,144 @@ static void vc4_v3d_init_hw(struct drm_device *dev) V3D_WRITE(V3D_VPMBASE, 0); } +int vc4_v3d_get_bin_slot(struct vc4_dev *vc4) +{ + struct drm_device *dev = vc4->dev; + unsigned long irqflags; + int slot; + uint64_t seqno = 0; + struct vc4_exec_info *exec; + +try_again: + spin_lock_irqsave(&vc4->job_lock, irqflags); + slot = ffs(~vc4->bin_alloc_used); + if (slot != 0) { + /* Switch from ffs() bit index to a 0-based index. */ + slot--; + vc4->bin_alloc_used |= BIT(slot); + spin_unlock_irqrestore(&vc4->job_lock, irqflags); + return slot; + } + + /* Couldn't find an open slot. Wait for render to complete + * and try again. + */ + exec = vc4_last_render_job(vc4); + if (exec) + seqno = exec->seqno; + spin_unlock_irqrestore(&vc4->job_lock, irqflags); + + if (seqno) { + int ret = vc4_wait_for_seqno(dev, seqno, ~0ull, true); + + if (ret == 0) + goto try_again; + + return ret; + } + + return -ENOMEM; +} + +/** + * vc4_allocate_bin_bo() - allocates the memory that will be used for + * tile binning. + * + * The binner has a limitation that the addresses in the tile state + * buffer that point into the tile alloc buffer or binner overflow + * memory only have 28 bits (256MB), and the top 4 on the bus for + * tile alloc references end up coming from the tile state buffer's + * address. + * + * To work around this, we allocate a single large buffer while V3D is + * in use, make sure that it has the top 4 bits constant across its + * entire extent, and then put the tile state, tile alloc, and binner + * overflow memory inside that buffer. + * + * This creates a limitation where we may not be able to execute a job + * if it doesn't fit within the buffer that we allocated up front. + * However, it turns out that 16MB is "enough for anybody", and + * real-world applications run into allocation failures from the + * overall CMA pool before they make scenes complicated enough to run + * out of bin space. + */ +int +vc4_allocate_bin_bo(struct drm_device *drm) +{ + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_v3d *v3d = vc4->v3d; + uint32_t size = 16 * 1024 * 1024; + int ret = 0; + struct list_head list; + + /* We may need to try allocating more than once to get a BO + * that doesn't cross 256MB. Track the ones we've allocated + * that failed so far, so that we can free them when we've got + * one that succeeded (if we freed them right away, our next + * allocation would probably be the same chunk of memory). + */ + INIT_LIST_HEAD(&list); + + while (true) { + struct vc4_bo *bo = vc4_bo_create(drm, size, true); + + if (IS_ERR(bo)) { + ret = PTR_ERR(bo); + + dev_err(&v3d->pdev->dev, + "Failed to allocate memory for tile binning: " + "%d. You may need to enable CMA or give it " + "more memory.", + ret); + break; + } + + /* Check if this BO won't trigger the addressing bug. */ + if ((bo->base.paddr & 0xf0000000) == + ((bo->base.paddr + bo->base.base.size - 1) & 0xf0000000)) { + vc4->bin_bo = bo; + + /* Set up for allocating 512KB chunks of + * binner memory. The biggest allocation we + * need to do is for the initial tile alloc + + * tile state buffer. We can render to a + * maximum of ((2048*2048) / (32*32) = 4096 + * tiles in a frame (until we do floating + * point rendering, at which point it would be + * 8192). Tile state is 48b/tile (rounded to + * a page), and tile alloc is 32b/tile + * (rounded to a page), plus a page of extra, + * for a total of 320kb for our worst-case. + * We choose 512kb so that it divides evenly + * into our 16MB, and the rest of the 512kb + * will be used as storage for the overflow + * from the initial 32b CL per bin. + */ + vc4->bin_alloc_size = 512 * 1024; + vc4->bin_alloc_used = 0; + vc4->bin_alloc_overflow = 0; + WARN_ON_ONCE(sizeof(vc4->bin_alloc_used) * 8 != + bo->base.base.size / vc4->bin_alloc_size); + + break; + } + + /* Put it on the list to free later, and try again. */ + list_add(&bo->unref_head, &list); + } + + /* Free all the BOs we allocated but didn't choose. */ + while (!list_empty(&list)) { + struct vc4_bo *bo = list_last_entry(&list, + struct vc4_bo, unref_head); + + list_del(&bo->unref_head); + drm_gem_object_put_unlocked(&bo->base.base); + } + + return ret; +} + #ifdef CONFIG_PM static int vc4_v3d_runtime_suspend(struct device *dev) { @@ -164,6 +303,11 @@ static int vc4_v3d_runtime_suspend(struct device *dev) vc4_irq_uninstall(vc4->dev); + drm_gem_object_put_unlocked(&vc4->bin_bo->base.base); + vc4->bin_bo = NULL; + + clk_disable_unprepare(v3d->clk); + return 0; } @@ -171,6 +315,15 @@ static int vc4_v3d_runtime_resume(struct device *dev) { struct vc4_v3d *v3d = dev_get_drvdata(dev); struct vc4_dev *vc4 = v3d->vc4; + int ret; + + ret = vc4_allocate_bin_bo(vc4->dev); + if (ret) + return ret; + + ret = clk_prepare_enable(v3d->clk); + if (ret != 0) + return ret; vc4_v3d_init_hw(vc4->dev); vc4_irq_postinstall(vc4->dev); @@ -202,12 +355,38 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) vc4->v3d = v3d; v3d->vc4 = vc4; + v3d->clk = devm_clk_get(dev, NULL); + if (IS_ERR(v3d->clk)) { + int ret = PTR_ERR(v3d->clk); + + if (ret == -ENOENT) { + /* bcm2835 didn't have a clock reference in the DT. */ + ret = 0; + v3d->clk = NULL; + } else { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get V3D clock: %d\n", + ret); + return ret; + } + } + if (V3D_READ(V3D_IDENT0) != V3D_EXPECTED_IDENT0) { DRM_ERROR("V3D_IDENT0 read 0x%08x instead of 0x%08x\n", V3D_READ(V3D_IDENT0), V3D_EXPECTED_IDENT0); return -EINVAL; } + ret = clk_prepare_enable(v3d->clk); + if (ret != 0) + return ret; + + ret = vc4_allocate_bin_bo(drm); + if (ret) { + clk_disable_unprepare(v3d->clk); + return ret; + } + /* Reset the binner overflow address/size at setup, to be sure * we don't reuse an old one. */ @@ -271,6 +450,7 @@ static int vc4_v3d_dev_remove(struct platform_device *pdev) static const struct of_device_id vc4_v3d_dt_match[] = { { .compatible = "brcm,bcm2835-v3d" }, + { .compatible = "brcm,cygnus-v3d" }, { .compatible = "brcm,vc4-v3d" }, {} }; diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c index da6f1e138e8d..3de8f11595c0 100644 --- a/drivers/gpu/drm/vc4/vc4_validate.c +++ b/drivers/gpu/drm/vc4/vc4_validate.c @@ -348,10 +348,11 @@ static int validate_tile_binning_config(VALIDATE_ARGS) { struct drm_device *dev = exec->exec_bo->base.dev; - struct vc4_bo *tile_bo; + struct vc4_dev *vc4 = to_vc4_dev(dev); uint8_t flags; - uint32_t tile_state_size, tile_alloc_size; - uint32_t tile_count; + uint32_t tile_state_size; + uint32_t tile_count, bin_addr; + int bin_slot; if (exec->found_tile_binning_mode_config_packet) { DRM_ERROR("Duplicate VC4_PACKET_TILE_BINNING_MODE_CONFIG\n"); @@ -377,13 +378,28 @@ validate_tile_binning_config(VALIDATE_ARGS) return -EINVAL; } + bin_slot = vc4_v3d_get_bin_slot(vc4); + if (bin_slot < 0) { + if (bin_slot != -EINTR && bin_slot != -ERESTARTSYS) { + DRM_ERROR("Failed to allocate binner memory: %d\n", + bin_slot); + } + return bin_slot; + } + + /* The slot we allocated will only be used by this job, and is + * free when the job completes rendering. + */ + exec->bin_slots |= BIT(bin_slot); + bin_addr = vc4->bin_bo->base.paddr + bin_slot * vc4->bin_alloc_size; + /* The tile state data array is 48 bytes per tile, and we put it at * the start of a BO containing both it and the tile alloc. */ tile_state_size = 48 * tile_count; /* Since the tile alloc array will follow us, align. */ - exec->tile_alloc_offset = roundup(tile_state_size, 4096); + exec->tile_alloc_offset = bin_addr + roundup(tile_state_size, 4096); *(uint8_t *)(validated + 14) = ((flags & ~(VC4_BIN_CONFIG_ALLOC_INIT_BLOCK_SIZE_MASK | @@ -394,35 +410,13 @@ validate_tile_binning_config(VALIDATE_ARGS) VC4_SET_FIELD(VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE_128, VC4_BIN_CONFIG_ALLOC_BLOCK_SIZE)); - /* Initial block size. */ - tile_alloc_size = 32 * tile_count; - - /* - * The initial allocation gets rounded to the next 256 bytes before - * the hardware starts fulfilling further allocations. - */ - tile_alloc_size = roundup(tile_alloc_size, 256); - - /* Add space for the extra allocations. This is what gets used first, - * before overflow memory. It must have at least 4096 bytes, but we - * want to avoid overflow memory usage if possible. - */ - tile_alloc_size += 1024 * 1024; - - tile_bo = vc4_bo_create(dev, exec->tile_alloc_offset + tile_alloc_size, - true); - exec->tile_bo = &tile_bo->base; - if (IS_ERR(exec->tile_bo)) - return PTR_ERR(exec->tile_bo); - list_add_tail(&tile_bo->unref_head, &exec->unref_list); - /* tile alloc address. */ - *(uint32_t *)(validated + 0) = (exec->tile_bo->paddr + - exec->tile_alloc_offset); + *(uint32_t *)(validated + 0) = exec->tile_alloc_offset; /* tile alloc size. */ - *(uint32_t *)(validated + 4) = tile_alloc_size; + *(uint32_t *)(validated + 4) = (bin_addr + vc4->bin_alloc_size - + exec->tile_alloc_offset); /* tile state address. */ - *(uint32_t *)(validated + 8) = exec->tile_bo->paddr; + *(uint32_t *)(validated + 8) = bin_addr; return 0; } diff --git a/drivers/gpu/drm/vgem/vgem_drv.c b/drivers/gpu/drm/vgem/vgem_drv.c index 9fee38a942c4..4b23ba049632 100644 --- a/drivers/gpu/drm/vgem/vgem_drv.c +++ b/drivers/gpu/drm/vgem/vgem_drv.c @@ -42,10 +42,20 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 0 +static struct vgem_device { + struct drm_device drm; + struct platform_device *platform; +} *vgem_device; + static void vgem_gem_free_object(struct drm_gem_object *obj) { struct drm_vgem_gem_object *vgem_obj = to_vgem_bo(obj); + drm_free_large(vgem_obj->pages); + + if (obj->import_attach) + drm_prime_gem_destroy(obj, vgem_obj->table); + drm_gem_object_release(obj); kfree(vgem_obj); } @@ -56,26 +66,49 @@ static int vgem_gem_fault(struct vm_fault *vmf) struct drm_vgem_gem_object *obj = vma->vm_private_data; /* We don't use vmf->pgoff since that has the fake offset */ unsigned long vaddr = vmf->address; - struct page *page; - - page = shmem_read_mapping_page(file_inode(obj->base.filp)->i_mapping, - (vaddr - vma->vm_start) >> PAGE_SHIFT); - if (!IS_ERR(page)) { - vmf->page = page; - return 0; - } else switch (PTR_ERR(page)) { - case -ENOSPC: - case -ENOMEM: - return VM_FAULT_OOM; - case -EBUSY: - return VM_FAULT_RETRY; - case -EFAULT: - case -EINVAL: - return VM_FAULT_SIGBUS; - default: - WARN_ON_ONCE(PTR_ERR(page)); - return VM_FAULT_SIGBUS; + int ret; + loff_t num_pages; + pgoff_t page_offset; + page_offset = (vaddr - vma->vm_start) >> PAGE_SHIFT; + + num_pages = DIV_ROUND_UP(obj->base.size, PAGE_SIZE); + + if (page_offset > num_pages) + return VM_FAULT_SIGBUS; + + if (obj->pages) { + get_page(obj->pages[page_offset]); + vmf->page = obj->pages[page_offset]; + ret = 0; + } else { + struct page *page; + + page = shmem_read_mapping_page( + file_inode(obj->base.filp)->i_mapping, + page_offset); + if (!IS_ERR(page)) { + vmf->page = page; + ret = 0; + } else switch (PTR_ERR(page)) { + case -ENOSPC: + case -ENOMEM: + ret = VM_FAULT_OOM; + break; + case -EBUSY: + ret = VM_FAULT_RETRY; + break; + case -EFAULT: + case -EINVAL: + ret = VM_FAULT_SIGBUS; + break; + default: + WARN_ON(PTR_ERR(page)); + ret = VM_FAULT_SIGBUS; + break; + } + } + return ret; } static const struct vm_operations_struct vgem_gem_vm_ops = { @@ -112,12 +145,8 @@ static void vgem_postclose(struct drm_device *dev, struct drm_file *file) kfree(vfile); } -/* ioctls */ - -static struct drm_gem_object *vgem_gem_create(struct drm_device *dev, - struct drm_file *file, - unsigned int *handle, - unsigned long size) +static struct drm_vgem_gem_object *__vgem_gem_create(struct drm_device *dev, + unsigned long size) { struct drm_vgem_gem_object *obj; int ret; @@ -127,8 +156,31 @@ static struct drm_gem_object *vgem_gem_create(struct drm_device *dev, return ERR_PTR(-ENOMEM); ret = drm_gem_object_init(dev, &obj->base, roundup(size, PAGE_SIZE)); - if (ret) - goto err_free; + if (ret) { + kfree(obj); + return ERR_PTR(ret); + } + + return obj; +} + +static void __vgem_gem_destroy(struct drm_vgem_gem_object *obj) +{ + drm_gem_object_release(&obj->base); + kfree(obj); +} + +static struct drm_gem_object *vgem_gem_create(struct drm_device *dev, + struct drm_file *file, + unsigned int *handle, + unsigned long size) +{ + struct drm_vgem_gem_object *obj; + int ret; + + obj = __vgem_gem_create(dev, size); + if (IS_ERR(obj)) + return ERR_CAST(obj); ret = drm_gem_handle_create(file, &obj->base, handle); drm_gem_object_unreference_unlocked(&obj->base); @@ -137,9 +189,8 @@ static struct drm_gem_object *vgem_gem_create(struct drm_device *dev, return &obj->base; -err_free: - kfree(obj); err: + __vgem_gem_destroy(obj); return ERR_PTR(ret); } @@ -256,6 +307,37 @@ static struct sg_table *vgem_prime_get_sg_table(struct drm_gem_object *obj) return st; } +static struct drm_gem_object* vgem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + struct vgem_device *vgem = container_of(dev, typeof(*vgem), drm); + + return drm_gem_prime_import_dev(dev, dma_buf, &vgem->platform->dev); +} + +static struct drm_gem_object *vgem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, struct sg_table *sg) +{ + struct drm_vgem_gem_object *obj; + int npages; + + obj = __vgem_gem_create(dev, attach->dmabuf->size); + if (IS_ERR(obj)) + return ERR_CAST(obj); + + npages = PAGE_ALIGN(attach->dmabuf->size) / PAGE_SIZE; + + obj->table = sg; + obj->pages = drm_malloc_ab(npages, sizeof(struct page *)); + if (!obj->pages) { + __vgem_gem_destroy(obj); + return ERR_PTR(-ENOMEM); + } + drm_prime_sg_to_page_addr_arrays(obj->table, obj->pages, NULL, + npages); + return &obj->base; +} + static void *vgem_prime_vmap(struct drm_gem_object *obj) { long n_pages = obj->size >> PAGE_SHIFT; @@ -300,8 +382,19 @@ static int vgem_prime_mmap(struct drm_gem_object *obj, return 0; } +static void vgem_release(struct drm_device *dev) +{ + struct vgem_device *vgem = container_of(dev, typeof(*vgem), drm); + + platform_device_unregister(vgem->platform); + drm_dev_fini(&vgem->drm); + + kfree(vgem); +} + static struct drm_driver vgem_driver = { .driver_features = DRIVER_GEM | DRIVER_PRIME, + .release = vgem_release, .open = vgem_open, .postclose = vgem_postclose, .gem_free_object_unlocked = vgem_gem_free_object, @@ -314,8 +407,11 @@ static struct drm_driver vgem_driver = { .dumb_map_offset = vgem_gem_dumb_map, .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, .gem_prime_pin = vgem_prime_pin, + .gem_prime_import = vgem_prime_import, .gem_prime_export = drm_gem_prime_export, + .gem_prime_import_sg_table = vgem_prime_import_sg_table, .gem_prime_get_sg_table = vgem_prime_get_sg_table, .gem_prime_vmap = vgem_prime_vmap, .gem_prime_vunmap = vgem_prime_vunmap, @@ -328,34 +424,48 @@ static struct drm_driver vgem_driver = { .minor = DRIVER_MINOR, }; -static struct drm_device *vgem_device; - static int __init vgem_init(void) { int ret; - vgem_device = drm_dev_alloc(&vgem_driver, NULL); - if (IS_ERR(vgem_device)) { - ret = PTR_ERR(vgem_device); - goto out; + vgem_device = kzalloc(sizeof(*vgem_device), GFP_KERNEL); + if (!vgem_device) + return -ENOMEM; + + ret = drm_dev_init(&vgem_device->drm, &vgem_driver, NULL); + if (ret) + goto out_free; + + vgem_device->platform = + platform_device_register_simple("vgem", -1, NULL, 0); + if (!vgem_device->platform) { + ret = -ENODEV; + goto out_fini; } - ret = drm_dev_register(vgem_device, 0); + dma_coerce_mask_and_coherent(&vgem_device->platform->dev, + DMA_BIT_MASK(64)); + + /* Final step: expose the device/driver to userspace */ + ret = drm_dev_register(&vgem_device->drm, 0); if (ret) - goto out_unref; + goto out_unregister; return 0; -out_unref: - drm_dev_unref(vgem_device); -out: +out_unregister: + platform_device_unregister(vgem_device->platform); +out_fini: + drm_dev_fini(&vgem_device->drm); +out_free: + kfree(vgem_device); return ret; } static void __exit vgem_exit(void) { - drm_dev_unregister(vgem_device); - drm_dev_unref(vgem_device); + drm_dev_unregister(&vgem_device->drm); + drm_dev_unref(&vgem_device->drm); } module_init(vgem_init); diff --git a/drivers/gpu/drm/vgem/vgem_drv.h b/drivers/gpu/drm/vgem/vgem_drv.h index cb59c7ab98b9..1aae01419112 100644 --- a/drivers/gpu/drm/vgem/vgem_drv.h +++ b/drivers/gpu/drm/vgem/vgem_drv.h @@ -43,6 +43,8 @@ struct vgem_file { #define to_vgem_bo(x) container_of(x, struct drm_vgem_gem_object, base) struct drm_vgem_gem_object { struct drm_gem_object base; + struct page **pages; + struct sg_table *table; }; int vgem_fence_open(struct vgem_file *file); diff --git a/drivers/gpu/drm/zte/Makefile b/drivers/gpu/drm/zte/Makefile index 01352b56c418..9df7766a7f9d 100644 --- a/drivers/gpu/drm/zte/Makefile +++ b/drivers/gpu/drm/zte/Makefile @@ -3,6 +3,7 @@ zxdrm-y := \ zx_hdmi.o \ zx_plane.o \ zx_tvenc.o \ + zx_vga.o \ zx_vou.o obj-$(CONFIG_DRM_ZTE) += zxdrm.o diff --git a/drivers/gpu/drm/zte/zx_common_regs.h b/drivers/gpu/drm/zte/zx_common_regs.h new file mode 100644 index 000000000000..2afd80664c51 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_common_regs.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ZX_COMMON_REGS_H__ +#define __ZX_COMMON_REGS_H__ + +/* CSC registers */ +#define CSC_CTRL0 0x30 +#define CSC_COV_MODE_SHIFT 16 +#define CSC_COV_MODE_MASK (0xffff << CSC_COV_MODE_SHIFT) +#define CSC_BT601_IMAGE_RGB2YCBCR 0 +#define CSC_BT601_IMAGE_YCBCR2RGB 1 +#define CSC_BT601_VIDEO_RGB2YCBCR 2 +#define CSC_BT601_VIDEO_YCBCR2RGB 3 +#define CSC_BT709_IMAGE_RGB2YCBCR 4 +#define CSC_BT709_IMAGE_YCBCR2RGB 5 +#define CSC_BT709_VIDEO_RGB2YCBCR 6 +#define CSC_BT709_VIDEO_YCBCR2RGB 7 +#define CSC_BT2020_IMAGE_RGB2YCBCR 8 +#define CSC_BT2020_IMAGE_YCBCR2RGB 9 +#define CSC_BT2020_VIDEO_RGB2YCBCR 10 +#define CSC_BT2020_VIDEO_YCBCR2RGB 11 +#define CSC_WORK_ENABLE BIT(0) + +#endif /* __ZX_COMMON_REGS_H__ */ diff --git a/drivers/gpu/drm/zte/zx_drm_drv.c b/drivers/gpu/drm/zte/zx_drm_drv.c index 614e854f6be5..490aafc99610 100644 --- a/drivers/gpu/drm/zte/zx_drm_drv.c +++ b/drivers/gpu/drm/zte/zx_drm_drv.c @@ -233,6 +233,7 @@ static struct platform_driver *drivers[] = { &zx_crtc_driver, &zx_hdmi_driver, &zx_tvenc_driver, + &zx_vga_driver, &zx_drm_platform_driver, }; diff --git a/drivers/gpu/drm/zte/zx_drm_drv.h b/drivers/gpu/drm/zte/zx_drm_drv.h index 5ca035b079c7..2a8cdc5f8be4 100644 --- a/drivers/gpu/drm/zte/zx_drm_drv.h +++ b/drivers/gpu/drm/zte/zx_drm_drv.h @@ -14,6 +14,7 @@ extern struct platform_driver zx_crtc_driver; extern struct platform_driver zx_hdmi_driver; extern struct platform_driver zx_tvenc_driver; +extern struct platform_driver zx_vga_driver; static inline u32 zx_readl(void __iomem *reg) { diff --git a/drivers/gpu/drm/zte/zx_plane.c b/drivers/gpu/drm/zte/zx_plane.c index d646ac931663..4a6252720c10 100644 --- a/drivers/gpu/drm/zte/zx_plane.c +++ b/drivers/gpu/drm/zte/zx_plane.c @@ -16,6 +16,7 @@ #include <drm/drm_plane_helper.h> #include <drm/drmP.h> +#include "zx_common_regs.h" #include "zx_drm_drv.h" #include "zx_plane.h" #include "zx_plane_regs.h" diff --git a/drivers/gpu/drm/zte/zx_plane_regs.h b/drivers/gpu/drm/zte/zx_plane_regs.h index 65f271aeabed..9c655f59f9f7 100644 --- a/drivers/gpu/drm/zte/zx_plane_regs.h +++ b/drivers/gpu/drm/zte/zx_plane_regs.h @@ -77,24 +77,6 @@ #define LUMA_STRIDE(x) (((x) << LUMA_STRIDE_SHIFT) & LUMA_STRIDE_MASK) #define CHROMA_STRIDE(x) (((x) << CHROMA_STRIDE_SHIFT) & CHROMA_STRIDE_MASK) -/* CSC registers */ -#define CSC_CTRL0 0x30 -#define CSC_COV_MODE_SHIFT 16 -#define CSC_COV_MODE_MASK (0xffff << CSC_COV_MODE_SHIFT) -#define CSC_BT601_IMAGE_RGB2YCBCR 0 -#define CSC_BT601_IMAGE_YCBCR2RGB 1 -#define CSC_BT601_VIDEO_RGB2YCBCR 2 -#define CSC_BT601_VIDEO_YCBCR2RGB 3 -#define CSC_BT709_IMAGE_RGB2YCBCR 4 -#define CSC_BT709_IMAGE_YCBCR2RGB 5 -#define CSC_BT709_VIDEO_RGB2YCBCR 6 -#define CSC_BT709_VIDEO_YCBCR2RGB 7 -#define CSC_BT2020_IMAGE_RGB2YCBCR 8 -#define CSC_BT2020_IMAGE_YCBCR2RGB 9 -#define CSC_BT2020_VIDEO_RGB2YCBCR 10 -#define CSC_BT2020_VIDEO_YCBCR2RGB 11 -#define CSC_WORK_ENABLE BIT(0) - /* RSZ registers */ #define RSZ_SRC_CFG 0x00 #define RSZ_DEST_CFG 0x04 diff --git a/drivers/gpu/drm/zte/zx_vga.c b/drivers/gpu/drm/zte/zx_vga.c new file mode 100644 index 000000000000..1e0811f775cb --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vga.c @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drmP.h> + +#include "zx_drm_drv.h" +#include "zx_vga_regs.h" +#include "zx_vou.h" + +struct zx_vga_pwrctrl { + struct regmap *regmap; + u32 reg; + u32 mask; +}; + +struct zx_vga_i2c { + struct i2c_adapter adap; + struct mutex lock; +}; + +struct zx_vga { + struct drm_connector connector; + struct drm_encoder encoder; + struct zx_vga_i2c *ddc; + struct device *dev; + void __iomem *mmio; + struct clk *i2c_wclk; + struct zx_vga_pwrctrl pwrctrl; + struct completion complete; + bool connected; +}; + +#define to_zx_vga(x) container_of(x, struct zx_vga, x) + +static void zx_vga_encoder_enable(struct drm_encoder *encoder) +{ + struct zx_vga *vga = to_zx_vga(encoder); + struct zx_vga_pwrctrl *pwrctrl = &vga->pwrctrl; + + /* Set bit to power up VGA DACs */ + regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, + pwrctrl->mask); + + vou_inf_enable(VOU_VGA, encoder->crtc); +} + +static void zx_vga_encoder_disable(struct drm_encoder *encoder) +{ + struct zx_vga *vga = to_zx_vga(encoder); + struct zx_vga_pwrctrl *pwrctrl = &vga->pwrctrl; + + vou_inf_disable(VOU_VGA, encoder->crtc); + + /* Clear bit to power down VGA DACs */ + regmap_update_bits(pwrctrl->regmap, pwrctrl->reg, pwrctrl->mask, 0); +} + +static const struct drm_encoder_helper_funcs zx_vga_encoder_helper_funcs = { + .enable = zx_vga_encoder_enable, + .disable = zx_vga_encoder_disable, +}; + +static const struct drm_encoder_funcs zx_vga_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int zx_vga_connector_get_modes(struct drm_connector *connector) +{ + struct zx_vga *vga = to_zx_vga(connector); + struct edid *edid; + int ret; + + /* + * Clear both detection bits to switch I2C bus from device + * detecting to EDID reading. + */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, 0); + + edid = drm_get_edid(connector, &vga->ddc->adap); + if (!edid) { + /* + * If EDID reading fails, we set the device state into + * disconnected. Locking is not required here, since the + * VGA_AUTO_DETECT_SEL register write in irq handler cannot + * be triggered when both detection bits are cleared as above. + */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, + VGA_DETECT_SEL_NO_DEVICE); + vga->connected = false; + return 0; + } + + /* + * As edid reading succeeds, device must be connected, so we set + * up detection bit for unplug interrupt here. + */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, VGA_DETECT_SEL_HAS_DEVICE); + + drm_mode_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +static enum drm_mode_status +zx_vga_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static struct drm_connector_helper_funcs zx_vga_connector_helper_funcs = { + .get_modes = zx_vga_connector_get_modes, + .mode_valid = zx_vga_connector_mode_valid, +}; + +static enum drm_connector_status +zx_vga_connector_detect(struct drm_connector *connector, bool force) +{ + struct zx_vga *vga = to_zx_vga(connector); + + return vga->connected ? connector_status_connected : + connector_status_disconnected; +} + +static const struct drm_connector_funcs zx_vga_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = zx_vga_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int zx_vga_register(struct drm_device *drm, struct zx_vga *vga) +{ + struct drm_encoder *encoder = &vga->encoder; + struct drm_connector *connector = &vga->connector; + struct device *dev = vga->dev; + int ret; + + encoder->possible_crtcs = VOU_CRTC_MASK; + + ret = drm_encoder_init(drm, encoder, &zx_vga_encoder_funcs, + DRM_MODE_ENCODER_DAC, NULL); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init encoder: %d\n", ret); + return ret; + }; + + drm_encoder_helper_add(encoder, &zx_vga_encoder_helper_funcs); + + vga->connector.polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(drm, connector, &zx_vga_connector_funcs, + DRM_MODE_CONNECTOR_VGA); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init connector: %d\n", ret); + goto clean_encoder; + }; + + drm_connector_helper_add(connector, &zx_vga_connector_helper_funcs); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) { + DRM_DEV_ERROR(dev, "failed to attach encoder: %d\n", ret); + goto clean_connector; + }; + + return 0; + +clean_connector: + drm_connector_cleanup(connector); +clean_encoder: + drm_encoder_cleanup(encoder); + return ret; +} + +static int zx_vga_pwrctrl_init(struct zx_vga *vga) +{ + struct zx_vga_pwrctrl *pwrctrl = &vga->pwrctrl; + struct device *dev = vga->dev; + struct of_phandle_args out_args; + struct regmap *regmap; + int ret; + + ret = of_parse_phandle_with_fixed_args(dev->of_node, + "zte,vga-power-control", 2, 0, &out_args); + if (ret) + return ret; + + regmap = syscon_node_to_regmap(out_args.np); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + goto out; + } + + pwrctrl->regmap = regmap; + pwrctrl->reg = out_args.args[0]; + pwrctrl->mask = out_args.args[1]; + +out: + of_node_put(out_args.np); + return ret; +} + +static int zx_vga_i2c_read(struct zx_vga *vga, struct i2c_msg *msg) +{ + int len = msg->len; + u8 *buf = msg->buf; + u32 offset = 0; + int i; + + reinit_completion(&vga->complete); + + /* Select combo write */ + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_COMBO, VGA_CMD_COMBO); + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_RW, 0); + + while (len > 0) { + u32 cnt; + + /* Clear RX FIFO */ + zx_writel_mask(vga->mmio + VGA_RXF_CTRL, VGA_RX_FIFO_CLEAR, + VGA_RX_FIFO_CLEAR); + + /* Data offset to read from */ + zx_writel(vga->mmio + VGA_SUB_ADDR, offset); + + /* Kick off the transfer */ + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_TRANS, + VGA_CMD_TRANS); + + if (!wait_for_completion_timeout(&vga->complete, + msecs_to_jiffies(1000))) { + DRM_DEV_ERROR(vga->dev, "transfer timeout\n"); + return -ETIMEDOUT; + } + + cnt = zx_readl(vga->mmio + VGA_RXF_STATUS); + cnt = (cnt & VGA_RXF_COUNT_MASK) >> VGA_RXF_COUNT_SHIFT; + /* FIFO status may report more data than we need to read */ + cnt = min_t(u32, len, cnt); + + for (i = 0; i < cnt; i++) + *buf++ = zx_readl(vga->mmio + VGA_DATA); + + len -= cnt; + offset += cnt; + } + + return 0; +} + +static int zx_vga_i2c_write(struct zx_vga *vga, struct i2c_msg *msg) +{ + /* + * The DDC I2C adapter is only for reading EDID data, so we assume + * that the write to this adapter must be the EDID data offset. + */ + if ((msg->len != 1) || ((msg->addr != DDC_ADDR))) + return -EINVAL; + + /* Hardware will take care of the slave address shifting */ + zx_writel(vga->mmio + VGA_DEVICE_ADDR, msg->addr); + + return 0; +} + +static int zx_vga_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct zx_vga *vga = i2c_get_adapdata(adap); + struct zx_vga_i2c *ddc = vga->ddc; + int ret = 0; + int i; + + mutex_lock(&ddc->lock); + + for (i = 0; i < num; i++) { + if (msgs[i].flags & I2C_M_RD) + ret = zx_vga_i2c_read(vga, &msgs[i]); + else + ret = zx_vga_i2c_write(vga, &msgs[i]); + + if (ret < 0) + break; + } + + if (!ret) + ret = num; + + mutex_unlock(&ddc->lock); + + return ret; +} + +static u32 zx_vga_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm zx_vga_algorithm = { + .master_xfer = zx_vga_i2c_xfer, + .functionality = zx_vga_i2c_func, +}; + +static int zx_vga_ddc_register(struct zx_vga *vga) +{ + struct device *dev = vga->dev; + struct i2c_adapter *adap; + struct zx_vga_i2c *ddc; + int ret; + + ddc = devm_kzalloc(dev, sizeof(*ddc), GFP_KERNEL); + if (!ddc) + return -ENOMEM; + + vga->ddc = ddc; + mutex_init(&ddc->lock); + + adap = &ddc->adap; + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_DDC; + adap->dev.parent = dev; + adap->algo = &zx_vga_algorithm; + snprintf(adap->name, sizeof(adap->name), "zx vga i2c"); + + ret = i2c_add_adapter(adap); + if (ret) { + DRM_DEV_ERROR(dev, "failed to add I2C adapter: %d\n", ret); + return ret; + } + + i2c_set_adapdata(adap, vga); + + return 0; +} + +static irqreturn_t zx_vga_irq_thread(int irq, void *dev_id) +{ + struct zx_vga *vga = dev_id; + + drm_helper_hpd_irq_event(vga->connector.dev); + + return IRQ_HANDLED; +} + +static irqreturn_t zx_vga_irq_handler(int irq, void *dev_id) +{ + struct zx_vga *vga = dev_id; + u32 status; + + status = zx_readl(vga->mmio + VGA_I2C_STATUS); + + /* Clear interrupt status */ + zx_writel_mask(vga->mmio + VGA_I2C_STATUS, VGA_CLEAR_IRQ, + VGA_CLEAR_IRQ); + + if (status & VGA_DEVICE_CONNECTED) { + /* + * Since VGA_DETECT_SEL bits need to be reset for switching DDC + * bus from device detection to EDID read, rather than setting + * up HAS_DEVICE bit here, we need to do that in .get_modes + * hook for unplug detecting after EDID read succeeds. + */ + vga->connected = true; + return IRQ_WAKE_THREAD; + } + + if (status & VGA_DEVICE_DISCONNECTED) { + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, + VGA_DETECT_SEL_NO_DEVICE); + vga->connected = false; + return IRQ_WAKE_THREAD; + } + + if (status & VGA_TRANS_DONE) { + complete(&vga->complete); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void zx_vga_hw_init(struct zx_vga *vga) +{ + unsigned long ref = clk_get_rate(vga->i2c_wclk); + int div; + + /* + * Set up I2C fast speed divider per formula below to get 400kHz. + * scl = ref / ((div + 1) * 4) + */ + div = DIV_ROUND_UP(ref / 1000, 400 * 4) - 1; + zx_writel(vga->mmio + VGA_CLK_DIV_FS, div); + + /* Set up device detection */ + zx_writel(vga->mmio + VGA_AUTO_DETECT_PARA, 0x80); + zx_writel(vga->mmio + VGA_AUTO_DETECT_SEL, VGA_DETECT_SEL_NO_DEVICE); + + /* + * We need to poke monitor via DDC bus to get connection irq + * start working. + */ + zx_writel(vga->mmio + VGA_DEVICE_ADDR, DDC_ADDR); + zx_writel_mask(vga->mmio + VGA_CMD_CFG, VGA_CMD_TRANS, VGA_CMD_TRANS); +} + +static int zx_vga_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct resource *res; + struct zx_vga *vga; + int irq; + int ret; + + vga = devm_kzalloc(dev, sizeof(*vga), GFP_KERNEL); + if (!vga) + return -ENOMEM; + + vga->dev = dev; + dev_set_drvdata(dev, vga); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + vga->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(vga->mmio)) + return PTR_ERR(vga->mmio); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + vga->i2c_wclk = devm_clk_get(dev, "i2c_wclk"); + if (IS_ERR(vga->i2c_wclk)) { + ret = PTR_ERR(vga->i2c_wclk); + DRM_DEV_ERROR(dev, "failed to get i2c_wclk: %d\n", ret); + return ret; + } + + ret = zx_vga_pwrctrl_init(vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to init power control: %d\n", ret); + return ret; + } + + ret = zx_vga_ddc_register(vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register ddc: %d\n", ret); + return ret; + } + + ret = zx_vga_register(drm, vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register vga: %d\n", ret); + return ret; + } + + init_completion(&vga->complete); + + ret = devm_request_threaded_irq(dev, irq, zx_vga_irq_handler, + zx_vga_irq_thread, IRQF_SHARED, + dev_name(dev), vga); + if (ret) { + DRM_DEV_ERROR(dev, "failed to request threaded irq: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(vga->i2c_wclk); + if (ret) + return ret; + + zx_vga_hw_init(vga); + + return 0; +} + +static void zx_vga_unbind(struct device *dev, struct device *master, + void *data) +{ + struct zx_vga *vga = dev_get_drvdata(dev); + + clk_disable_unprepare(vga->i2c_wclk); +} + +static const struct component_ops zx_vga_component_ops = { + .bind = zx_vga_bind, + .unbind = zx_vga_unbind, +}; + +static int zx_vga_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &zx_vga_component_ops); +} + +static int zx_vga_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &zx_vga_component_ops); + return 0; +} + +static const struct of_device_id zx_vga_of_match[] = { + { .compatible = "zte,zx296718-vga", }, + { /* end */ }, +}; +MODULE_DEVICE_TABLE(of, zx_vga_of_match); + +struct platform_driver zx_vga_driver = { + .probe = zx_vga_probe, + .remove = zx_vga_remove, + .driver = { + .name = "zx-vga", + .of_match_table = zx_vga_of_match, + }, +}; diff --git a/drivers/gpu/drm/zte/zx_vga_regs.h b/drivers/gpu/drm/zte/zx_vga_regs.h new file mode 100644 index 000000000000..feaa345fe6a6 --- /dev/null +++ b/drivers/gpu/drm/zte/zx_vga_regs.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Sanechips Technology Co., Ltd. + * Copyright 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __ZX_VGA_REGS_H__ +#define __ZX_VGA_REGS_H__ + +#define VGA_CMD_CFG 0x04 +#define VGA_CMD_TRANS BIT(6) +#define VGA_CMD_COMBO BIT(5) +#define VGA_CMD_RW BIT(4) +#define VGA_SUB_ADDR 0x0c +#define VGA_DEVICE_ADDR 0x10 +#define VGA_CLK_DIV_FS 0x14 +#define VGA_RXF_CTRL 0x20 +#define VGA_RX_FIFO_CLEAR BIT(7) +#define VGA_DATA 0x24 +#define VGA_I2C_STATUS 0x28 +#define VGA_DEVICE_DISCONNECTED BIT(7) +#define VGA_DEVICE_CONNECTED BIT(6) +#define VGA_CLEAR_IRQ BIT(4) +#define VGA_TRANS_DONE BIT(0) +#define VGA_RXF_STATUS 0x30 +#define VGA_RXF_COUNT_SHIFT 2 +#define VGA_RXF_COUNT_MASK GENMASK(7, 2) +#define VGA_AUTO_DETECT_PARA 0x34 +#define VGA_AUTO_DETECT_SEL 0x38 +#define VGA_DETECT_SEL_HAS_DEVICE BIT(1) +#define VGA_DETECT_SEL_NO_DEVICE BIT(0) + +#endif /* __ZX_VGA_REGS_H__ */ diff --git a/drivers/gpu/drm/zte/zx_vou.c b/drivers/gpu/drm/zte/zx_vou.c index b500c8dd0d9d..5fbd10b60ee5 100644 --- a/drivers/gpu/drm/zte/zx_vou.c +++ b/drivers/gpu/drm/zte/zx_vou.c @@ -23,6 +23,7 @@ #include <drm/drm_plane_helper.h> #include <drm/drmP.h> +#include "zx_common_regs.h" #include "zx_drm_drv.h" #include "zx_plane.h" #include "zx_vou.h" @@ -122,6 +123,8 @@ struct zx_crtc { struct drm_plane *primary; struct zx_vou_hw *vou; void __iomem *chnreg; + void __iomem *chncsc; + void __iomem *dither; const struct zx_crtc_regs *regs; const struct zx_crtc_bits *bits; enum vou_chn_type chn_type; @@ -204,6 +207,11 @@ static struct vou_inf vou_infs[] = { .clocks_en_bits = BIT(15), .clocks_sel_bits = BIT(11) | BIT(0), }, + [VOU_VGA] = { + .data_sel = VOU_RGB_888, + .clocks_en_bits = BIT(1), + .clocks_sel_bits = BIT(10), + }, }; static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc) @@ -227,9 +235,26 @@ void vou_inf_enable(enum vou_inf_id id, struct drm_crtc *crtc) struct zx_crtc *zcrtc = to_zx_crtc(crtc); struct zx_vou_hw *vou = zcrtc->vou; struct vou_inf *inf = &vou_infs[id]; + void __iomem *dither = zcrtc->dither; + void __iomem *csc = zcrtc->chncsc; bool is_main = zcrtc->chn_type == VOU_CHN_MAIN; u32 data_sel_shift = id << 1; + if (inf->data_sel != VOU_YUV444) { + /* Enable channel CSC for RGB output */ + zx_writel_mask(csc + CSC_CTRL0, CSC_COV_MODE_MASK, + CSC_BT709_IMAGE_YCBCR2RGB << CSC_COV_MODE_SHIFT); + zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE, + CSC_WORK_ENABLE); + + /* Bypass Dither block for RGB output */ + zx_writel_mask(dither + OSD_DITHER_CTRL0, DITHER_BYSPASS, + DITHER_BYSPASS); + } else { + zx_writel_mask(csc + CSC_CTRL0, CSC_WORK_ENABLE, 0); + zx_writel_mask(dither + OSD_DITHER_CTRL0, DITHER_BYSPASS, 0); + } + /* Select data format */ zx_writel_mask(vou->vouctl + VOU_INF_DATA_SEL, 0x3 << data_sel_shift, inf->data_sel << data_sel_shift); @@ -525,20 +550,24 @@ static int zx_crtc_init(struct drm_device *drm, struct zx_vou_hw *vou, if (chn_type == VOU_CHN_MAIN) { zplane->layer = vou->osd + MAIN_GL_OFFSET; - zplane->csc = vou->osd + MAIN_CSC_OFFSET; + zplane->csc = vou->osd + MAIN_GL_CSC_OFFSET; zplane->hbsc = vou->osd + MAIN_HBSC_OFFSET; zplane->rsz = vou->otfppu + MAIN_RSZ_OFFSET; zplane->bits = &zx_gl_bits[0]; zcrtc->chnreg = vou->osd + OSD_MAIN_CHN; + zcrtc->chncsc = vou->osd + MAIN_CHN_CSC_OFFSET; + zcrtc->dither = vou->osd + MAIN_DITHER_OFFSET; zcrtc->regs = &main_crtc_regs; zcrtc->bits = &main_crtc_bits; } else { zplane->layer = vou->osd + AUX_GL_OFFSET; - zplane->csc = vou->osd + AUX_CSC_OFFSET; + zplane->csc = vou->osd + AUX_GL_CSC_OFFSET; zplane->hbsc = vou->osd + AUX_HBSC_OFFSET; zplane->rsz = vou->otfppu + AUX_RSZ_OFFSET; zplane->bits = &zx_gl_bits[1]; zcrtc->chnreg = vou->osd + OSD_AUX_CHN; + zcrtc->chncsc = vou->osd + AUX_CHN_CSC_OFFSET; + zcrtc->dither = vou->osd + AUX_DITHER_OFFSET; zcrtc->regs = &aux_crtc_regs; zcrtc->bits = &aux_crtc_bits; } @@ -705,9 +734,6 @@ static void vou_hw_init(struct zx_vou_hw *vou) /* Release reset for all VOU modules */ zx_writel(vou->vouctl + VOU_SOFT_RST, ~0); - /* Enable clock auto-gating for all VOU modules */ - zx_writel(vou->vouctl + VOU_CLK_REQEN, ~0); - /* Enable all VOU module clocks */ zx_writel(vou->vouctl + VOU_CLK_EN, ~0); diff --git a/drivers/gpu/drm/zte/zx_vou_regs.h b/drivers/gpu/drm/zte/zx_vou_regs.h index c066ef123434..5a218351b497 100644 --- a/drivers/gpu/drm/zte/zx_vou_regs.h +++ b/drivers/gpu/drm/zte/zx_vou_regs.h @@ -13,13 +13,17 @@ /* Sub-module offset */ #define MAIN_GL_OFFSET 0x130 -#define MAIN_CSC_OFFSET 0x580 +#define MAIN_GL_CSC_OFFSET 0x580 +#define MAIN_CHN_CSC_OFFSET 0x6c0 #define MAIN_HBSC_OFFSET 0x820 +#define MAIN_DITHER_OFFSET 0x960 #define MAIN_RSZ_OFFSET 0x600 /* OTFPPU sub-module */ #define AUX_GL_OFFSET 0x200 -#define AUX_CSC_OFFSET 0x5d0 +#define AUX_GL_CSC_OFFSET 0x5d0 +#define AUX_CHN_CSC_OFFSET 0x710 #define AUX_HBSC_OFFSET 0x860 +#define AUX_DITHER_OFFSET 0x970 #define AUX_RSZ_OFFSET 0x800 #define OSD_VL0_OFFSET 0x040 @@ -78,6 +82,10 @@ #define CHN_INTERLACE_BUF_CTRL 0x24 #define CHN_INTERLACE_EN BIT(2) +/* Dither registers */ +#define OSD_DITHER_CTRL0 0x00 +#define DITHER_BYSPASS BIT(31) + /* TIMING_CTRL registers */ #define TIMING_TC_ENABLE 0x04 #define AUX_TC_EN BIT(1) |