linux/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c

623 lines
16 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* shmob_drm_crtc.c -- SH Mobile DRM CRTCs
*
* Copyright (C) 2012 Renesas Electronics Corporation
*
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/clk.h>
#include <linux/media-bus-format.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/pm_runtime.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_atomic_uapi.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
drm/fb: rename FB CMA helpers to FB DMA helpers Rename "FB CMA" helpers to "FB DMA" helpers - considering the hierarchy of APIs (mm/cma -> dma -> fb dma) calling them "FB DMA" seems to be more applicable. Besides that, commit e57924d4ae80 ("drm/doc: Task to rename CMA helpers") requests to rename the CMA helpers and implies that people seem to be confused about the naming. In order to do this renaming the following script was used: ``` #!/bin/bash DIRS="drivers/gpu include/drm Documentation/gpu" REGEX_SYM_UPPER="[0-9A-Z_\-]" REGEX_SYM_LOWER="[0-9a-z_\-]" REGEX_GREP_UPPER="(${REGEX_SYM_UPPER}*)(FB)_CMA_(${REGEX_SYM_UPPER}*)" REGEX_GREP_LOWER="(${REGEX_SYM_LOWER}*)(fb)_cma_(${REGEX_SYM_LOWER}*)" REGEX_SED_UPPER="s/${REGEX_GREP_UPPER}/\1\2_DMA_\3/g" REGEX_SED_LOWER="s/${REGEX_GREP_LOWER}/\1\2_dma_\3/g" # Find all upper case 'CMA' symbols and replace them with 'DMA'. for ff in $(grep -REHl "${REGEX_GREP_UPPER}" $DIRS) do sed -i -E "$REGEX_SED_UPPER" $ff done # Find all lower case 'cma' symbols and replace them with 'dma'. for ff in $(grep -REHl "${REGEX_GREP_LOWER}" $DIRS) do sed -i -E "$REGEX_SED_LOWER" $ff done # Replace all occurrences of 'CMA' / 'cma' in comments and # documentation files with 'DMA' / 'dma'. for ff in $(grep -RiHl " cma " $DIRS) do sed -i -E "s/ cma / dma /g" $ff sed -i -E "s/ CMA / DMA /g" $ff done ``` Only a few more manual modifications were needed, e.g. reverting the following modifications in some DRM Kconfig files - select CMA if HAVE_DMA_CONTIGUOUS + select DMA if HAVE_DMA_CONTIGUOUS as well as manually picking the occurrences of 'CMA'/'cma' in comments and documentation which relate to "FB CMA", but not "GEM CMA". This patch is compile-time tested building a x86_64 kernel with `make allyesconfig && make drivers/gpu/drm`. Acked-by: Sam Ravnborg <sam@ravnborg.org> Acked-by: Thomas Zimmermann <tzimmermann@suse.de> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Danilo Krummrich <dakr@redhat.com> Reviewed-by: Liviu Dudau <liviu.dudau@arm.com> #drivers/gpu/drm/arm Signed-off-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20220802000405.949236-3-dakr@redhat.com
2022-08-02 02:04:02 +02:00
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
drm/gem: rename GEM CMA helpers to GEM DMA helpers Rename "GEM CMA" helpers to "GEM DMA" helpers - considering the hierarchy of APIs (mm/cma -> dma -> gem dma) calling them "GEM DMA" seems to be more applicable. Besides that, commit e57924d4ae80 ("drm/doc: Task to rename CMA helpers") requests to rename the CMA helpers and implies that people seem to be confused about the naming. In order to do this renaming the following script was used: ``` #!/bin/bash DIRS="drivers/gpu include/drm Documentation/gpu" REGEX_SYM_UPPER="[0-9A-Z_\-]" REGEX_SYM_LOWER="[0-9a-z_\-]" REGEX_GREP_UPPER="(${REGEX_SYM_UPPER}*)(GEM)_CMA_(${REGEX_SYM_UPPER}*)" REGEX_GREP_LOWER="(${REGEX_SYM_LOWER}*)(gem)_cma_(${REGEX_SYM_LOWER}*)" REGEX_SED_UPPER="s/${REGEX_GREP_UPPER}/\1\2_DMA_\3/g" REGEX_SED_LOWER="s/${REGEX_GREP_LOWER}/\1\2_dma_\3/g" # Find all upper case 'CMA' symbols and replace them with 'DMA'. for ff in $(grep -REHl "${REGEX_GREP_UPPER}" $DIRS) do sed -i -E "$REGEX_SED_UPPER" $ff done # Find all lower case 'cma' symbols and replace them with 'dma'. for ff in $(grep -REHl "${REGEX_GREP_LOWER}" $DIRS) do sed -i -E "$REGEX_SED_LOWER" $ff done # Replace all occurrences of 'CMA' / 'cma' in comments and # documentation files with 'DMA' / 'dma'. for ff in $(grep -RiHl " cma " $DIRS) do sed -i -E "s/ cma / dma /g" $ff sed -i -E "s/ CMA / DMA /g" $ff done # Rename all 'cma_obj's to 'dma_obj'. for ff in $(grep -RiHl "cma_obj" $DIRS) do sed -i -E "s/cma_obj/dma_obj/g" $ff done ``` Only a few more manual modifications were needed, e.g. reverting the following modifications in some DRM Kconfig files - select CMA if HAVE_DMA_CONTIGUOUS + select DMA if HAVE_DMA_CONTIGUOUS as well as manually picking the occurrences of 'CMA'/'cma' in comments and documentation which relate to "GEM CMA", but not "FB CMA". Also drivers/gpu/drm/Makefile was fixed up manually after renaming drm_gem_cma_helper.c to drm_gem_dma_helper.c. This patch is compile-time tested building a x86_64 kernel with `make allyesconfig && make drivers/gpu/drm`. Acked-by: Sam Ravnborg <sam@ravnborg.org> Acked-by: Thomas Zimmermann <tzimmermann@suse.de> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Danilo Krummrich <dakr@redhat.com> Reviewed-by: Liviu Dudau <liviu.dudau@arm.com> #drivers/gpu/drm/arm Signed-off-by: Sam Ravnborg <sam@ravnborg.org> Link: https://patchwork.freedesktop.org/patch/msgid/20220802000405.949236-4-dakr@redhat.com
2022-08-02 02:04:03 +02:00
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_modeset_helper_vtables.h>
#include <drm/drm_panel.h>
drm: Split out drm_probe_helper.h Having the probe helper stuff (which pretty much everyone needs) in the drm_crtc_helper.h file (which atomic drivers should never need) is confusing. Split them out. To make sure I actually achieved the goal here I went through all drivers. And indeed, all atomic drivers are now free of drm_crtc_helper.h includes. v2: Make it compile. There was so much compile fail on arm drivers that I figured I'll better not include any of the acks on v1. v3: Massive rebase because i915 has lost a lot of drmP.h includes, but not all: Through drm_crtc_helper.h > drm_modeset_helper.h -> drmP.h there was still one, which this patch largely removes. Which means rolling out lots more includes all over. This will also conflict with ongoing drmP.h cleanup by others I expect. v3: Rebase on top of atomic bochs. v4: Review from Laurent for bridge/rcar/omap/shmob/core bits: - (re)move some of the added includes, use the better include files in other places (all suggested from Laurent adopted unchanged). - sort alphabetically v5: Actually try to sort them, and while at it, sort all the ones I touch. v6: Rebase onto i915 changes. v7: Rebase once more. Acked-by: Harry Wentland <harry.wentland@amd.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Cc: Sam Ravnborg <sam@ravnborg.org> Cc: Jani Nikula <jani.nikula@linux.intel.com> Cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Rodrigo Vivi <rodrigo.vivi@intel.com> Acked-by: Benjamin Gaignard <benjamin.gaignard@linaro.org> Acked-by: Jani Nikula <jani.nikula@intel.com> Acked-by: Neil Armstrong <narmstrong@baylibre.com> Acked-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com> Acked-by: CK Hu <ck.hu@mediatek.com> Acked-by: Alex Deucher <alexander.deucher@amd.com> Acked-by: Sam Ravnborg <sam@ravnborg.org> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Acked-by: Liviu Dudau <liviu.dudau@arm.com> Signed-off-by: Daniel Vetter <daniel.vetter@intel.com> Cc: linux-arm-kernel@lists.infradead.org Cc: virtualization@lists.linux-foundation.org Cc: etnaviv@lists.freedesktop.org Cc: linux-samsung-soc@vger.kernel.org Cc: intel-gfx@lists.freedesktop.org Cc: linux-mediatek@lists.infradead.org Cc: linux-amlogic@lists.infradead.org Cc: linux-arm-msm@vger.kernel.org Cc: freedreno@lists.freedesktop.org Cc: nouveau@lists.freedesktop.org Cc: spice-devel@lists.freedesktop.org Cc: amd-gfx@lists.freedesktop.org Cc: linux-renesas-soc@vger.kernel.org Cc: linux-rockchip@lists.infradead.org Cc: linux-stm32@st-md-mailman.stormreply.com Cc: linux-tegra@vger.kernel.org Cc: xen-devel@lists.xen.org Link: https://patchwork.freedesktop.org/patch/msgid/20190117210334.13234-1-daniel.vetter@ffwll.ch
2019-01-17 22:03:34 +01:00
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
#include <video/videomode.h>
#include "shmob_drm_crtc.h"
#include "shmob_drm_drv.h"
#include "shmob_drm_kms.h"
#include "shmob_drm_plane.h"
#include "shmob_drm_regs.h"
/* -----------------------------------------------------------------------------
* Page Flip
*/
void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc)
{
struct drm_pending_vblank_event *event;
struct drm_device *dev = scrtc->base.dev;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
event = scrtc->event;
scrtc->event = NULL;
if (event) {
drm_crtc_send_vblank_event(&scrtc->base, event);
wake_up(&scrtc->flip_wait);
drm_crtc_vblank_put(&scrtc->base);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
static bool shmob_drm_crtc_page_flip_pending(struct shmob_drm_crtc *scrtc)
{
struct drm_device *dev = scrtc->base.dev;
unsigned long flags;
bool pending;
spin_lock_irqsave(&dev->event_lock, flags);
pending = scrtc->event != NULL;
spin_unlock_irqrestore(&dev->event_lock, flags);
return pending;
}
static void shmob_drm_crtc_wait_page_flip(struct shmob_drm_crtc *scrtc)
{
struct drm_crtc *crtc = &scrtc->base;
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
if (wait_event_timeout(scrtc->flip_wait,
!shmob_drm_crtc_page_flip_pending(scrtc),
msecs_to_jiffies(50)))
return;
dev_warn(sdev->dev, "page flip timeout\n");
shmob_drm_crtc_finish_page_flip(scrtc);
}
/* -----------------------------------------------------------------------------
* CRTC
*/
static const struct {
u32 fmt;
u32 ldmt1r;
} shmob_drm_bus_fmts[] = {
{ MEDIA_BUS_FMT_RGB888_3X8, LDMT1R_MIFTYP_RGB8 },
{ MEDIA_BUS_FMT_RGB666_2X9_BE, LDMT1R_MIFTYP_RGB9 },
{ MEDIA_BUS_FMT_RGB888_2X12_BE, LDMT1R_MIFTYP_RGB12A },
{ MEDIA_BUS_FMT_RGB444_1X12, LDMT1R_MIFTYP_RGB12B },
{ MEDIA_BUS_FMT_RGB565_1X16, LDMT1R_MIFTYP_RGB16 },
{ MEDIA_BUS_FMT_RGB666_1X18, LDMT1R_MIFTYP_RGB18 },
{ MEDIA_BUS_FMT_RGB888_1X24, LDMT1R_MIFTYP_RGB24 },
{ MEDIA_BUS_FMT_UYVY8_1X16, LDMT1R_MIFTYP_YCBCR },
};
static void shmob_drm_crtc_setup_geometry(struct shmob_drm_crtc *scrtc)
{
struct drm_crtc *crtc = &scrtc->base;
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
const struct drm_display_info *info = &sdev->connector->display_info;
const struct drm_display_mode *mode = &crtc->mode;
unsigned int i;
u32 value;
if (!info->num_bus_formats || !info->bus_formats) {
dev_warn(sdev->dev, "No bus format reported, using RGB888\n");
value = LDMT1R_MIFTYP_RGB24;
} else {
for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) {
if (shmob_drm_bus_fmts[i].fmt == info->bus_formats[0])
break;
}
if (i < ARRAY_SIZE(shmob_drm_bus_fmts)) {
value = shmob_drm_bus_fmts[i].ldmt1r;
} else {
dev_warn(sdev->dev,
"unsupported bus format 0x%x, using RGB888\n",
info->bus_formats[0]);
value = LDMT1R_MIFTYP_RGB24;
}
}
if (info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE)
value |= LDMT1R_DWPOL;
if (info->bus_flags & DRM_BUS_FLAG_DE_LOW)
value |= LDMT1R_DIPOL;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
value |= LDMT1R_VPOL;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
value |= LDMT1R_HPOL;
lcdc_write(sdev, LDMT1R, value);
value = ((mode->hdisplay / 8) << 16) /* HDCN */
| (mode->htotal / 8); /* HTCN */
lcdc_write(sdev, LDHCNR, value);
value = (((mode->hsync_end - mode->hsync_start) / 8) << 16) /* HSYNW */
| (mode->hsync_start / 8); /* HSYNP */
lcdc_write(sdev, LDHSYNR, value);
value = ((mode->hdisplay & 7) << 24) | ((mode->htotal & 7) << 16)
| (((mode->hsync_end - mode->hsync_start) & 7) << 8)
| (mode->hsync_start & 7);
lcdc_write(sdev, LDHAJR, value);
value = ((mode->vdisplay) << 16) /* VDLN */
| mode->vtotal; /* VTLN */
lcdc_write(sdev, LDVLNR, value);
value = ((mode->vsync_end - mode->vsync_start) << 16) /* VSYNW */
| mode->vsync_start; /* VSYNP */
lcdc_write(sdev, LDVSYNR, value);
}
static void shmob_drm_crtc_start_stop(struct shmob_drm_crtc *scrtc, bool start)
{
struct shmob_drm_device *sdev = to_shmob_device(scrtc->base.dev);
u32 value;
value = lcdc_read(sdev, LDCNT2R);
if (start)
lcdc_write(sdev, LDCNT2R, value | LDCNT2R_DO);
else
lcdc_write(sdev, LDCNT2R, value & ~LDCNT2R_DO);
/* Wait until power is applied/stopped. */
while (1) {
value = lcdc_read(sdev, LDPMR) & LDPMR_LPS;
if ((start && value) || (!start && !value))
break;
cpu_relax();
}
if (!start) {
/* Stop the dot clock. */
lcdc_write(sdev, LDDCKSTPR, LDDCKSTPR_DCKSTP);
}
}
static inline struct shmob_drm_crtc *to_shmob_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct shmob_drm_crtc, base);
}
static void shmob_drm_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc);
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
unsigned int clk_div = sdev->config.clk_div;
struct device *dev = sdev->dev;
u32 value;
int ret;
ret = pm_runtime_resume_and_get(dev);
if (ret)
return;
/* Reset and enable the LCDC. */
lcdc_write(sdev, LDCNT2R, lcdc_read(sdev, LDCNT2R) | LDCNT2R_BR);
lcdc_wait_bit(sdev, LDCNT2R, LDCNT2R_BR, 0);
lcdc_write(sdev, LDCNT2R, LDCNT2R_ME);
/* Stop the LCDC first and disable all interrupts. */
shmob_drm_crtc_start_stop(scrtc, false);
lcdc_write(sdev, LDINTR, 0);
/* Configure power supply, dot clocks and start them. */
lcdc_write(sdev, LDPMR, 0);
value = sdev->lddckr;
if (clk_div) {
/* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider
* denominator.
*/
lcdc_write(sdev, LDDCKPAT1R, 0);
lcdc_write(sdev, LDDCKPAT2R, (1 << (clk_div / 2)) - 1);
if (clk_div == 1)
value |= LDDCKR_MOSEL;
else
value |= clk_div;
}
lcdc_write(sdev, LDDCKR, value);
lcdc_write(sdev, LDDCKSTPR, 0);
lcdc_wait_bit(sdev, LDDCKSTPR, ~0, 0);
/* Setup geometry, format, frame buffer memory and operation mode. */
shmob_drm_crtc_setup_geometry(scrtc);
lcdc_write(sdev, LDSM1R, 0);
/* Enable the display output. */
lcdc_write(sdev, LDCNT1R, LDCNT1R_DE);
shmob_drm_crtc_start_stop(scrtc, true);
/* Turn vertical blank interrupt reporting back on. */
drm_crtc_vblank_on(crtc);
}
static void shmob_drm_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc);
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
/*
* Disable vertical blank interrupt reporting. We first need to wait
* for page flip completion before stopping the CRTC as userspace
* expects page flips to eventually complete.
*/
shmob_drm_crtc_wait_page_flip(scrtc);
drm_crtc_vblank_off(crtc);
/* Stop the LCDC. */
shmob_drm_crtc_start_stop(scrtc, false);
/* Disable the display output. */
lcdc_write(sdev, LDCNT1R, 0);
pm_runtime_put(sdev->dev);
}
static void shmob_drm_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct drm_pending_vblank_event *event;
struct drm_device *dev = crtc->dev;
unsigned long flags;
if (crtc->state->event) {
spin_lock_irqsave(&dev->event_lock, flags);
event = crtc->state->event;
crtc->state->event = NULL;
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
.atomic_flush = shmob_drm_crtc_atomic_flush,
.atomic_enable = shmob_drm_crtc_atomic_enable,
.atomic_disable = shmob_drm_crtc_atomic_disable,
};
static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t page_flip_flags,
struct drm_modeset_acquire_ctx *ctx)
{
struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc);
struct drm_device *dev = scrtc->base.dev;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
if (scrtc->event != NULL) {
spin_unlock_irqrestore(&dev->event_lock, flags);
return -EBUSY;
}
spin_unlock_irqrestore(&dev->event_lock, flags);
drm_atomic_set_fb_for_plane(crtc->primary->state, fb);
if (event) {
event->pipe = 0;
drm_crtc_vblank_get(&scrtc->base);
spin_lock_irqsave(&dev->event_lock, flags);
scrtc->event = event;
spin_unlock_irqrestore(&dev->event_lock, flags);
}
return 0;
}
static void shmob_drm_crtc_enable_vblank(struct shmob_drm_device *sdev,
bool enable)
{
unsigned long flags;
u32 ldintr;
/* Be careful not to acknowledge any pending interrupt. */
spin_lock_irqsave(&sdev->irq_lock, flags);
ldintr = lcdc_read(sdev, LDINTR) | LDINTR_STATUS_MASK;
if (enable)
ldintr |= LDINTR_VEE;
else
ldintr &= ~LDINTR_VEE;
lcdc_write(sdev, LDINTR, ldintr);
spin_unlock_irqrestore(&sdev->irq_lock, flags);
}
static int shmob_drm_enable_vblank(struct drm_crtc *crtc)
{
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
shmob_drm_crtc_enable_vblank(sdev, true);
return 0;
}
static void shmob_drm_disable_vblank(struct drm_crtc *crtc)
{
struct shmob_drm_device *sdev = to_shmob_device(crtc->dev);
shmob_drm_crtc_enable_vblank(sdev, false);
}
static const struct drm_crtc_funcs crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = shmob_drm_crtc_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = shmob_drm_enable_vblank,
.disable_vblank = shmob_drm_disable_vblank,
};
int shmob_drm_crtc_create(struct shmob_drm_device *sdev)
{
struct drm_crtc *crtc = &sdev->crtc.base;
struct drm_plane *primary, *plane;
unsigned int i;
int ret;
init_waitqueue_head(&sdev->crtc.flip_wait);
primary = shmob_drm_plane_create(sdev, DRM_PLANE_TYPE_PRIMARY, 0);
if (IS_ERR(primary))
return PTR_ERR(primary);
for (i = 1; i < 5; ++i) {
plane = shmob_drm_plane_create(sdev, DRM_PLANE_TYPE_OVERLAY, i);
if (IS_ERR(plane))
return PTR_ERR(plane);
}
ret = drm_crtc_init_with_planes(&sdev->ddev, crtc, primary, NULL,
&crtc_funcs, NULL);
if (ret < 0)
return ret;
drm_crtc_helper_add(crtc, &crtc_helper_funcs);
/* Start with vertical blank interrupt reporting disabled. */
drm_crtc_vblank_off(crtc);
return 0;
}
/* -----------------------------------------------------------------------------
* Legacy Encoder
*/
static bool shmob_drm_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct drm_device *dev = encoder->dev;
struct shmob_drm_device *sdev = to_shmob_device(dev);
struct drm_connector *connector = sdev->connector;
const struct drm_display_mode *panel_mode;
if (list_empty(&connector->modes)) {
dev_dbg(dev->dev, "mode_fixup: empty modes list\n");
return false;
}
/* The flat panel mode is fixed, just copy it to the adjusted mode. */
panel_mode = list_first_entry(&connector->modes,
struct drm_display_mode, head);
drm_mode_copy(adjusted_mode, panel_mode);
return true;
}
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.mode_fixup = shmob_drm_encoder_mode_fixup,
};
/* -----------------------------------------------------------------------------
* Encoder
*/
int shmob_drm_encoder_create(struct shmob_drm_device *sdev)
{
struct drm_encoder *encoder = &sdev->encoder;
struct drm_bridge *bridge;
int ret;
encoder->possible_crtcs = 1;
ret = drm_simple_encoder_init(&sdev->ddev, encoder,
DRM_MODE_ENCODER_DPI);
if (ret < 0)
return ret;
if (sdev->pdata) {
drm_encoder_helper_add(encoder, &encoder_helper_funcs);
return 0;
}
/* Create a panel bridge */
bridge = devm_drm_of_get_bridge(sdev->dev, sdev->dev->of_node, 0, 0);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
/* Attach the bridge to the encoder */
ret = drm_bridge_attach(encoder, bridge, NULL,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
dev_err(sdev->dev, "failed to attach bridge: %pe\n",
ERR_PTR(ret));
return ret;
}
return 0;
}
/* -----------------------------------------------------------------------------
* Legacy Connector
*/
static inline struct shmob_drm_connector *to_shmob_connector(struct drm_connector *connector)
{
return container_of(connector, struct shmob_drm_connector, base);
}
static int shmob_drm_connector_get_modes(struct drm_connector *connector)
{
struct shmob_drm_connector *scon = to_shmob_connector(connector);
struct drm_display_mode *mode;
mode = drm_mode_create(connector->dev);
if (mode == NULL)
return 0;
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;
drm_display_mode_from_videomode(scon->mode, mode);
drm_mode_probed_add(connector, mode);
return 1;
}
static struct drm_encoder *
shmob_drm_connector_best_encoder(struct drm_connector *connector)
{
struct shmob_drm_connector *scon = to_shmob_connector(connector);
return scon->encoder;
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
.get_modes = shmob_drm_connector_get_modes,
.best_encoder = shmob_drm_connector_best_encoder,
};
static void shmob_drm_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
kfree(connector);
}
static const struct drm_connector_funcs connector_funcs = {
.reset = drm_atomic_helper_connector_reset,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = shmob_drm_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static struct drm_connector *
shmob_drm_connector_init(struct shmob_drm_device *sdev,
struct drm_encoder *encoder)
{
u32 bus_fmt = sdev->pdata->iface.bus_fmt;
struct shmob_drm_connector *scon;
struct drm_connector *connector;
struct drm_display_info *info;
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) {
if (shmob_drm_bus_fmts[i].fmt == bus_fmt)
break;
}
if (i == ARRAY_SIZE(shmob_drm_bus_fmts)) {
dev_err(sdev->dev, "unsupported bus format 0x%x\n", bus_fmt);
return ERR_PTR(-EINVAL);
}
scon = kzalloc(sizeof(*scon), GFP_KERNEL);
if (!scon)
return ERR_PTR(-ENOMEM);
connector = &scon->base;
scon->encoder = encoder;
scon->mode = &sdev->pdata->panel.mode;
info = &connector->display_info;
info->width_mm = sdev->pdata->panel.width_mm;
info->height_mm = sdev->pdata->panel.height_mm;
if (scon->mode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
if (scon->mode->flags & DISPLAY_FLAGS_DE_LOW)
info->bus_flags |= DRM_BUS_FLAG_DE_LOW;
ret = drm_display_info_set_bus_formats(info, &bus_fmt, 1);
if (ret < 0) {
kfree(scon);
return ERR_PTR(ret);
}
ret = drm_connector_init(&sdev->ddev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_DPI);
if (ret < 0) {
kfree(scon);
return ERR_PTR(ret);
}
drm_connector_helper_add(connector, &connector_helper_funcs);
return connector;
}
/* -----------------------------------------------------------------------------
* Connector
*/
int shmob_drm_connector_create(struct shmob_drm_device *sdev,
struct drm_encoder *encoder)
{
struct drm_connector *connector;
int ret;
if (sdev->pdata)
connector = shmob_drm_connector_init(sdev, encoder);
else
connector = drm_bridge_connector_init(&sdev->ddev, encoder);
if (IS_ERR(connector)) {
dev_err(sdev->dev, "failed to created connector: %pe\n",
connector);
return PTR_ERR(connector);
}
ret = drm_connector_attach_encoder(connector, encoder);
if (ret < 0)
goto error;
connector->dpms = DRM_MODE_DPMS_OFF;
sdev->connector = connector;
return 0;
error:
drm_connector_cleanup(connector);
return ret;
}