This is the current state of the driver for the at91sam* LCDC IP. Notice that it needs some other companion drivers and I posted this code-drop only to support the request for help on the BGR versus RGB issue. That said - any review feedback will be appreciated! Sam >From c4c2274313dbb23f38626920834819735f70d899 Mon Sep 17 00:00:00 2001 From: Sam Ravnborg <sam@xxxxxxxxxxxx> Date: Thu, 27 Dec 2018 02:23:31 +0100 Subject: [PATCH 22/22] drm: add Atmel LCDC display controller support This is a DRM based driver for the Atmel LCDC IP. There exist today a framebuffer based driver and this is a re-implmentation of the same on top of DRM. The rewrite was based on the original fbdev driver but the driver has also seen inspiration from the atmel-hlcdc_dc driver and others. The driver is not a full replacement: - STN displays are not supported STN displays are not considered relevant for current kernels, and there is no plan to add STN support The atmel-lcdc driver is divided into a few files: atmel_lcdc.h - definitions shared by the driver implmentation ../linux/atmel-lcdc.h - definitions shared with the pwm subdriver atmel_lcdc_drv.c - all the driver details such as DT support etc. atmel_lcdc_pipe.c - the display pipeline, including the outputs v3: - reworked most of the driver after feedback and more testing Signed-off-by: Sam Ravnborg <sam@xxxxxxxxxxxx> Cc: Nicolas Ferre <nicolas.ferre@xxxxxxxxx> Cc: Boris Brezillon <boris.brezillon@xxxxxxxxxxxxxxxxxx> Cc: Alexandre Belloni <alexandre.belloni@xxxxxxxxxxx> --- drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/atmel/Kconfig | 12 + drivers/gpu/drm/atmel/Makefile | 3 + drivers/gpu/drm/atmel/atmel_lcdc.h | 81 +++ drivers/gpu/drm/atmel/atmel_lcdc_drv.c | 408 ++++++++++++++ drivers/gpu/drm/atmel/atmel_lcdc_pipe.c | 675 ++++++++++++++++++++++++ 6 files changed, 1180 insertions(+) create mode 100644 drivers/gpu/drm/atmel/atmel_lcdc.h create mode 100644 drivers/gpu/drm/atmel/atmel_lcdc_drv.c create mode 100644 drivers/gpu/drm/atmel/atmel_lcdc_pipe.c diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index d7f5328918ab..386fc6166de1 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ obj-$(CONFIG_DRM_ARMADA) += armada/ obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel/ +obj-$(CONFIG_DRM_ATMEL_LCDC) += atmel/ obj-y += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-y += omapdrm/ diff --git a/drivers/gpu/drm/atmel/Kconfig b/drivers/gpu/drm/atmel/Kconfig index 5f67f001553b..1405ae2802a3 100644 --- a/drivers/gpu/drm/atmel/Kconfig +++ b/drivers/gpu/drm/atmel/Kconfig @@ -9,3 +9,15 @@ config DRM_ATMEL_HLCDC help Choose this option if you have an ATMEL SoC with an HLCDC display controller (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family). + +config DRM_ATMEL_LCDC + tristate "DRM Support for ATMEL LCDC Display Controller" + depends on DRM && OF && COMMON_CLK && MFD_ATMEL_LCDC + depends on ARM || COMPILE_TEST + select DRM_GEM_CMA_HELPER + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_PANEL + help + Choose this option if you have an ATMEL SoC with an LCDC display + controller (found on many SoC's in the at91sam9 family). diff --git a/drivers/gpu/drm/atmel/Makefile b/drivers/gpu/drm/atmel/Makefile index 49dc89f36b73..9d95543f39a3 100644 --- a/drivers/gpu/drm/atmel/Makefile +++ b/drivers/gpu/drm/atmel/Makefile @@ -5,3 +5,6 @@ atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \ atmel_hlcdc_plane.o obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc-dc.o + +atmel-lcdc-y := atmel_lcdc_drv.o atmel_lcdc_pipe.o +obj-$(CONFIG_DRM_ATMEL_LCDC) += atmel-lcdc.o diff --git a/drivers/gpu/drm/atmel/atmel_lcdc.h b/drivers/gpu/drm/atmel/atmel_lcdc.h new file mode 100644 index 000000000000..e1b3290c3c81 --- /dev/null +++ b/drivers/gpu/drm/atmel/atmel_lcdc.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Sam Ravnborg + * + * Author: Sam Ravnborg <sam@xxxxxxxxxxxx> + */ + +#ifndef __DRM_ATMEL_LCDC_H +#define __DRM_ATMEL_LCDC_H + +#include <linux/workqueue.h> + +#include <drm/drm_connector.h> +#include <drm/drm_simple_kms_helper.h> + +#include <linux/mfd/atmel-lcdc.h> + +struct clk; +struct regmap; +struct regulator; + +#define ATMEL_LCDC_DMA_BURST_LEN 8 /* words */ + +/* + * struct atmel_lcdc_desc - CPU specific configuration properties + */ +struct atmel_lcdc_desc { + /* Default is 1, maybe we need to change this later */ + int guard_time; + + /* 512 or 2018 */ + int fifo_size; + + /* Maximum resolution */ + int max_width; + int max_height; + + /* false if "Pixel_clock = system_clock / (CLKVAL + 1) x 2" */ + /* true if "Pixel_clock = system_clock / (CLKVAL + 1)" */ + bool have_alt_pixclock; +}; + +/* + * Private data for the lcdc driver + */ +struct lcdc { + struct drm_device drm; + struct device *dev; + + const struct atmel_lcdc_desc *desc; + + struct atmel_mfd_lcdc *mfd; + + bool wiring_reversed; /* Select between BGR or RGB */ + + struct drm_simple_display_pipe pipe; + struct work_struct reset_lcdc_work; + + struct drm_panel *panel; + struct drm_connector *connector; + + struct mutex enable_lock; + bool enabled; + + /* Saved state during suspend */ + struct { + u32 imr; + /* true if suspended */ + bool state; + } suspend; +}; + +/* atmel_lcdc_pipe.c */ +int atmel_lcdc_vblank_init(struct lcdc *lcdc); +void atmel_lcdc_vblank(struct lcdc *ldcd); + +void atmel_lcdc_start(struct lcdc *lcdc); +void atmel_lcdc_stop(struct lcdc *lcdc); +int atmel_lcdc_modeset_init(struct lcdc *lcdc); + +#endif /* __DRM_ATMEL_LCDC_H */ diff --git a/drivers/gpu/drm/atmel/atmel_lcdc_drv.c b/drivers/gpu/drm/atmel/atmel_lcdc_drv.c new file mode 100644 index 000000000000..2c94761ea648 --- /dev/null +++ b/drivers/gpu/drm/atmel/atmel_lcdc_drv.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Sam Ravnborg + * + * The driver is based on atmel_lcdfb which is: + * Copyright (C) 2007 Atmel Corporation + * + */ + +/** + * DOC: Atmel LCD Controller Display Controller. + * + * The Atmel LCD Controller supports in the following configuration: + * - TFT only, 24 bits/pixel + * - Resolution up to 2048x2048 + * - Single plane, crtc, one fixed output + * + * Features not (yet) ported from atmel_lcdfb: + * - set color / palette handling + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_irq.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "atmel_lcdc.h" + +/* Configuration of individual CPU's */ +static const struct atmel_lcdc_desc lcdc_at91sam9261 = { + .guard_time = 1, + .fifo_size = 512, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = false, +}; + +static const struct atmel_lcdc_desc lcdc_at91sam9263 = { + .guard_time = 1, + .fifo_size = 2048, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = false, +}; + +static const struct atmel_lcdc_desc lcdc_at91sam9g10 = { + .guard_time = 1, + .fifo_size = 512, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = false, +}; + +static const struct atmel_lcdc_desc lcdc_at91sam9g45 = { + .guard_time = 1, + .fifo_size = 512, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = true, +}; + +static const struct atmel_lcdc_desc lcdc_at91sam9g46 = { + .guard_time = 1, + .fifo_size = 512, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = true, +}; + +static const struct atmel_lcdc_desc lcdc_at91sam9m10 = { + .guard_time = 1, + .fifo_size = 512, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = true, +}; + +static const struct atmel_lcdc_desc lcdc_at91sam9m11 = { + .guard_time = 1, + .fifo_size = 512, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = true, +}; + +static const struct atmel_lcdc_desc lcdc_at91sam9rl = { + .guard_time = 1, + .fifo_size = 512, + .max_width = 2048, + .max_height = 2048, + .have_alt_pixclock = false, +}; + +static const struct of_device_id lcdc_of_match[] = { + { .compatible = "atmel,at91sam9261-lcdc", .data = &lcdc_at91sam9261 }, + { .compatible = "atmel,at91sam9263-lcdc", .data = &lcdc_at91sam9263 }, + { .compatible = "atmel,at91sam9g10-lcdc", .data = &lcdc_at91sam9g10 }, + { .compatible = "atmel,at91sam9g45-lcdc", .data = &lcdc_at91sam9g45 }, + { .compatible = "atmel,at91sam9g45es-lcdc", .data = &lcdc_at91sam9g45 }, + { .compatible = "atmel,at91sam9g46-lcdc", .data = &lcdc_at91sam9g46 }, + { .compatible = "atmel,at91sam9m10-lcdc", .data = &lcdc_at91sam9m10 }, + { .compatible = "atmel,at91sam9m11-lcdc", .data = &lcdc_at91sam9m11 }, + { .compatible = "atmel,at91sam9rl-lcdc", .data = &lcdc_at91sam9rl }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, lcdc_of_match); + + +/* scheduled worker to reset LCD */ +static void reset_lcdc_work(struct work_struct *work) +{ + struct lcdc *lcdc; + + lcdc = container_of(work, struct lcdc, reset_lcdc_work); + + mutex_lock(&lcdc->enable_lock); + + /* Check that we are enabled and not suspended */ + if (lcdc->enabled && !lcdc->suspend.state) { + atmel_lcdc_stop(lcdc); + atmel_lcdc_start(lcdc); + } + mutex_unlock(&lcdc->enable_lock); +} + +static irqreturn_t lcdc_irq_handler(int irq, void *arg) +{ + struct drm_device *drm; + unsigned int status; + struct lcdc *lcdc; + unsigned int imr; + unsigned int isr; + + drm = arg; + lcdc = drm->dev_private; + + regmap_read(lcdc->mfd->regmap, ATMEL_LCDC_IMR, &imr); + regmap_read(lcdc->mfd->regmap, ATMEL_LCDC_ISR, &isr); + status = imr & isr; + if (!status) + return IRQ_NONE; + + if (status & ATMEL_LCDC_LSTLNI) + atmel_lcdc_vblank(lcdc); + + if (status & ATMEL_LCDC_UFLWI) { + DRM_DEV_INFO(lcdc->dev, "FIFO underflow %#x\n", status); + /* reset DMA and FIFO to avoid screen shifting */ + schedule_work(&lcdc->reset_lcdc_work); + } + + if (status & ATMEL_LCDC_OWRI) + DRM_DEV_INFO(lcdc->dev, "FIFO overwrite interrupt\n"); + + if (status & ATMEL_LCDC_MERI) + DRM_DEV_INFO(lcdc->dev, "DMA memory error\n"); + + /* Clear all reported (from ISR) interrupts */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_ICR, isr); + + return IRQ_HANDLED; +} + +static int lcdc_irq_postinstall(struct drm_device *dev) +{ + struct lcdc *lcdc; + unsigned int ier; + + lcdc = dev->dev_private; + + ier = 0; + /* FIFO underflow interrupt enable */ + ier |= ATMEL_LCDC_UFLWI; + /* FIFO overwrite interrupt enable */ + ier |= ATMEL_LCDC_OWRI; + /* DMA memory error interrupt enable */ + ier |= ATMEL_LCDC_MERI; + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_IER, ier); + + return 0; +} + +static void lcdc_irq_uninstall(struct drm_device *dev) +{ + struct lcdc *lcdc; + unsigned int isr; + + lcdc = dev->dev_private; + + /* disable all interrupts */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_IDR, ~0); + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_ICR, ~0); + + /* Clear any pending interrupts */ + regmap_read(lcdc->mfd->regmap, ATMEL_LCDC_ISR, &isr); +} + +static void lcdc_release(struct drm_device *drm) +{ + struct lcdc *lcdc; + + lcdc = drm->dev_private; + + flush_work(&lcdc->reset_lcdc_work); + drm_kms_helper_poll_fini(drm); + + drm_mode_config_cleanup(drm); + + pm_runtime_get_sync(drm->dev); + drm_irq_uninstall(drm); + pm_runtime_put_sync(drm->dev); + cancel_work_sync(&lcdc->reset_lcdc_work); + pm_runtime_disable(drm->dev); + + drm_dev_fini(drm); + kfree(lcdc); +} + +DEFINE_DRM_GEM_CMA_FOPS(lcdc_drm_fops); + +static struct drm_driver lcdc_drm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .name = ATMEL_LCDC_DRM_DRV_NAME, + .desc = "Atmel LCD Display Controller DRM", + .date = "20180808", + .major = 1, + .minor = 0, + .patchlevel = 0, + + .irq_handler = lcdc_irq_handler, + .irq_preinstall = lcdc_irq_uninstall, + .irq_postinstall = lcdc_irq_postinstall, + .irq_uninstall = lcdc_irq_uninstall, + + .fops = &lcdc_drm_fops, + .release = lcdc_release, + + DRM_GEM_CMA_VMAP_DRIVER_OPS, +}; + +static int lcdc_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct atmel_mfd_lcdc *mfd; + struct drm_device *drm; + struct device *dev; + struct lcdc *lcdc; + int ret; + + dev = pdev->dev.parent; + mfd = dev_get_drvdata(dev); + /* the MFD device driver prepares clocks etc. */ + if (!mfd) + return -EPROBE_DEFER; + + + lcdc = kzalloc(sizeof(*lcdc), GFP_KERNEL); + if (!lcdc) + return -ENOMEM; + + drm = &lcdc->drm; + ret = devm_drm_dev_init(dev, drm, &lcdc_drm_driver); + if (ret) { + kfree(lcdc); + return ret; + } + + match = of_match_node(lcdc_of_match, dev->of_node); + if (!match || !match->data) { + DRM_DEV_ERROR(dev, "failed to find compatible match\n"); + return -EINVAL; + } + + lcdc->desc = match->data; + + drm->dev_private = lcdc; + lcdc->dev = dev; + lcdc->mfd = mfd; + platform_set_drvdata(pdev, drm); + + mutex_init(&lcdc->enable_lock); + + /* reset of lcdc might sleep and require a preemptible task context */ + INIT_WORK(&lcdc->reset_lcdc_work, reset_lcdc_work); + + pm_runtime_enable(dev); + + ret = atmel_lcdc_vblank_init(lcdc); + if (ret) + goto err_pm_runtime_disable; + + ret = atmel_lcdc_modeset_init(lcdc); + if (ret) + goto err_pm_runtime_disable; + + pm_runtime_get_sync(dev); + + ret = drm_irq_install(drm, mfd->irq); + pm_runtime_put_sync(dev); + if (ret < 0) { + DRM_DEV_ERROR(dev, "failed to install IRQ: %d\n", ret); + goto err_pm_runtime_disable; + } + + drm_kms_helper_poll_init(drm); + + ret = drm_dev_register(drm, 0); + if (ret) { + DRM_DEV_ERROR(dev, "failed to register drm: %d\n", ret); + goto err_pm_runtime_disable; + } + + ret = drm_fbdev_generic_setup(drm, 24); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to initialize fbdev: %d\n", ret); + + return 0; + +err_pm_runtime_disable: + cancel_work_sync(&lcdc->reset_lcdc_work); + pm_runtime_disable(dev); + + return ret; +} + +static int lcdc_remove(struct platform_device *pdev) +{ + struct drm_device *drm; + + drm = platform_get_drvdata(pdev); + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); + + return 0; +} + +static void lcdc_shutdown(struct platform_device *pdev) +{ + drm_atomic_helper_shutdown(platform_get_drvdata(pdev)); +} + +static int __maybe_unused lcdc_drm_suspend(struct device *dev) +{ + struct drm_device *drm; + struct lcdc *lcdc; + int ret; + + drm = dev_get_drvdata(dev); + lcdc = drm->dev_private; + + ret = drm_mode_config_helper_suspend(drm); + if (ret < 0) + return ret; + + lcdc->suspend.state = true; + + /* Disable interrupts */ + regmap_read(lcdc->mfd->regmap, ATMEL_LCDC_IMR, &lcdc->suspend.imr); + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_IDR, lcdc->suspend.imr); + + return 0; +} + +static int __maybe_unused lcdc_drm_resume(struct device *dev) +{ + struct drm_device *drm; + struct lcdc *lcdc; + + drm = dev_get_drvdata(dev); + lcdc = drm->dev_private; + + /* Enable interrupts */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_IER, lcdc->suspend.imr); + + lcdc->suspend.state = false; + return drm_mode_config_helper_resume(drm); +} + +static SIMPLE_DEV_PM_OPS(lcdc_pm_ops, + lcdc_drm_suspend, lcdc_drm_resume); + +static struct platform_driver lcdc_driver = { + .driver = { + .name = ATMEL_LCDC_DRM_DRV_NAME, + .of_match_table = lcdc_of_match, + .pm = &lcdc_pm_ops, + }, + .probe = lcdc_probe, + .remove = lcdc_remove, + .shutdown = lcdc_shutdown, +}; + +module_platform_driver(lcdc_driver); + +MODULE_AUTHOR("Sam Ravnborg <sam@xxxxxxxxxxxx>"); +MODULE_DESCRIPTION("Atmel LCDC Display Controller DRM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:atmel-lcdc"); diff --git a/drivers/gpu/drm/atmel/atmel_lcdc_pipe.c b/drivers/gpu/drm/atmel/atmel_lcdc_pipe.c new file mode 100644 index 000000000000..c40175cd1e1f --- /dev/null +++ b/drivers/gpu/drm/atmel/atmel_lcdc_pipe.c @@ -0,0 +1,675 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Sam Ravnborg + * + */ + +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "atmel_lcdc.h" + +/** + * DOC: Display pipe for Atmel LCDC + * + * The LCDC display pipe utilize the drm_simple_kms_helper infrastructure + * to provide the necessary DRM functionality. + * + * The LCDC IP core is always used in setups with only a single + * output with direct connection to a panel. + */ + +/* + * Atmel LCD controller 24 bit formats. + * Formats for reversed wiring included. + */ +static const u32 bgr_formats[] = { + DRM_FORMAT_XBGR8888, + DRM_FORMAT_BGR888, + DRM_FORMAT_BGR565, +}; + +static const u32 rgb_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_RGB565, +}; + +static const u32* get_formats(bool reversed, size_t *nformats) +{ + if (reversed) { + /* B and R are swapped in HW */ + *nformats = ARRAY_SIZE(rgb_formats); + return rgb_formats; + } else { + /* Normal wiring, use BGR formats */ + *nformats = ARRAY_SIZE(bgr_formats); + return bgr_formats; + } +} + +static void set_lcdcon2(struct lcdc *lcdc, + const struct drm_format_info *format) +{ + struct drm_format_name_buf format_buf; + struct drm_display_mode *dmode; + unsigned int lcdcon2; + u32 bus_flags; + + dmode = &lcdc->pipe.crtc.state->adjusted_mode; + + /* Control register 2 */ + /* Only TFT supported (Controller supports STN too) */ + lcdcon2 = ATMEL_LCDC_DISTYPE_TFT; + + /* scan mode */ + if (dmode->flags & DRM_MODE_FLAG_DBLSCAN) + lcdcon2 |= ATMEL_LCDC_SCANMOD_DUAL; + else + lcdcon2 |= ATMEL_LCDC_SCANMOD_SINGLE; + + /* Interface width 4 bits (STN only) */ + lcdcon2 |= ATMEL_LCDC_IFWIDTH_4; + + switch (format->format) { + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_XRGB8888: + /* 32 bit layout */ + lcdcon2 |= ATMEL_LCDC_PIXELSIZE_24; + break; + case DRM_FORMAT_BGR888: + case DRM_FORMAT_RGB888: + /* 24 bit layout */ + lcdcon2 |= ATMEL_LCDC_PIXELSIZE_24_PACKED; + break; + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB565: + /* 16 bit layout */ + lcdcon2 |= ATMEL_LCDC_PIXELSIZE_16; + break; + default: + lcdcon2 |= ATMEL_LCDC_PIXELSIZE_24; + DRM_DEV_INFO(lcdc->dev, "Unsupported format %s\n", + drm_get_format_name(format->format, &format_buf)); + } + + /* LCDD Polarity normal */ + lcdcon2 |= ATMEL_LCDC_INVVD_NORMAL; + + /* vsync polarity - TODO: Check ! again */ + if (!(dmode->flags & DRM_MODE_FLAG_PVSYNC)) + lcdcon2 |= ATMEL_LCDC_INVFRAME_INVERTED; + else + lcdcon2 |= ATMEL_LCDC_INVFRAME_NORMAL; + + /* hsync polarity - TODO: Check ! again*/ + if (!(dmode->flags & DRM_MODE_FLAG_PHSYNC)) + lcdcon2 |= ATMEL_LCDC_INVLINE_INVERTED; + else + lcdcon2 |= ATMEL_LCDC_INVLINE_NORMAL; + + bus_flags = lcdc->connector->display_info.bus_flags; + + /* dot clock (pix clock) polarity */ + if (bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE) + lcdcon2 |= ATMEL_LCDC_INVCLK_INVERTED; + else + lcdcon2 |= ATMEL_LCDC_INVCLK_NORMAL; + + /* Date Enable polarity */ + if (bus_flags & DRM_BUS_FLAG_DE_LOW) + lcdcon2 |= ATMEL_LCDC_INVDVAL_INVERTED; + else + lcdcon2 |= ATMEL_LCDC_INVDVAL_NORMAL; + + /* Clock is always active */ + lcdcon2 |= ATMEL_LCDC_CLKMOD_ALWAYSACTIVE; + + /* Memory layout */ + if (format->format & DRM_FORMAT_BIG_ENDIAN) + lcdcon2 |= ATMEL_LCDC_MEMOR_BIG; + else + lcdcon2 |= ATMEL_LCDC_MEMOR_LITTLE; + + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_LCDCON2, lcdcon2); +} + +/* Check that the configuation is supported */ +static int lcdc_pipe_check(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *pstate, + struct drm_crtc_state *cstate) +{ + const struct drm_display_mode *dmode; + struct drm_framebuffer *fb; + struct lcdc *lcdc; + long clk_rate; + + lcdc = pipe->crtc.dev->dev_private; + dmode = &cstate->mode; + fb = pstate->fb; + + if (!fb) + return 0; + + /* Check if we can support the requested clock rate */ + clk_rate = dmode->clock * 1000; + if (clk_rate > clk_get_rate(lcdc->mfd->lcdc_clk)) + return -EINVAL; + + /* Only one plane supported */ + if (fb->format->num_planes != 1) + return -EINVAL; + + return 0; +} + +/* + * DMA config. Set frame size and burst length + * Frame_size equals size of visible area * bits / 32 + * (size in 32 bit words) + */ +static void lcdc_pipe_enable_dma(struct lcdc *lcdc) +{ + const struct drm_format_info *format_info; + struct drm_plane_state *plane_state; + unsigned int width, height; + unsigned int frame_size; + unsigned int dmafrmcfg; + + plane_state = lcdc->pipe.plane.state; + format_info = drm_format_info(plane_state->fb->format->format); + width = plane_state->crtc->state->adjusted_mode.hdisplay; + height = plane_state->crtc->state->adjusted_mode.vdisplay; + + frame_size = width * height * format_info->depth; + dmafrmcfg = frame_size / 32; + + dmafrmcfg |= (ATMEL_LCDC_DMA_BURST_LEN - 1) << ATMEL_LCDC_BLENGTH_OFFSET; + + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_DMAFRMCFG, dmafrmcfg); +} + +static void set_vertical_timing(struct lcdc *lcdc, + struct drm_display_mode *dmode) +{ + unsigned int tim1; + unsigned int vfp; + unsigned int vbp; + unsigned int vpw; + unsigned int vhdly; + + /* VFP: Vertical Front Porch */ + vfp = dmode->vsync_start - dmode->vdisplay; + + /* VBP: Vertical Back Porch */ + vbp = dmode->vtotal - dmode->vsync_end; + + /* VPW: Vertical Synchronization pulse width */ + vpw = dmode->vsync_end - dmode->vsync_start - 1; + + /* VHDLY: Vertical to horizontal delay */ + vhdly = 0; + + tim1 = vfp << ATMEL_LCDC_VFP_OFFSET | + vbp << ATMEL_LCDC_VBP_OFFSET | + vpw << ATMEL_LCDC_VPW_OFFSET | + vhdly << ATMEL_LCDC_VHDLY_OFFSET; + + DRM_DEV_DEBUG(lcdc->dev, " TIM1 = %08x\n", tim1); + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_TIM1, tim1); +} + +static void set_horizontal_timing(struct lcdc *lcdc, + struct drm_display_mode *dmode) +{ + unsigned int tim2; + unsigned int hbp; + unsigned int hpw; + unsigned int hfp; + + /* HBP: Horizontal Back Porch */ + hbp = dmode->htotal - dmode->hsync_end - 1; + + /* HPW: Horizontal synchronization pulse width */ + hpw = dmode->hsync_end - dmode->hsync_start - 1; + + /* HFP: Horizontal Front Porch */ + hfp = dmode->hsync_start - dmode->hdisplay - 2; + + tim2 = hbp << ATMEL_LCDC_HBP_OFFSET | + hpw << ATMEL_LCDC_HPW_OFFSET | + hfp << ATMEL_LCDC_HFP_OFFSET; + + DRM_DEV_DEBUG(lcdc->dev, " TIM2 = %08x\n", tim2); + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_TIM2, tim2); +} + +static void lcdc_pipe_enable_timing(struct lcdc *lcdc) +{ + struct drm_display_mode *dmode; + unsigned int value; + + dmode = &lcdc->pipe.crtc.state->adjusted_mode; + + /* Vertical & horizontal timing */ + set_vertical_timing(lcdc, dmode); + set_horizontal_timing(lcdc, dmode); + + /* Display size */ + value = (dmode->crtc_hdisplay - 1) << ATMEL_LCDC_HOZVAL_OFFSET; + value |= dmode->crtc_vdisplay - 1; + DRM_DEV_DEBUG(lcdc->dev, " LCDFRMCFG = %08x\n", value); + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_LCDFRMCFG, value); + + /* FIFO Threshold: Use formula from data sheet */ + value = lcdc->desc->fifo_size - (2 * ATMEL_LCDC_DMA_BURST_LEN + 3); + DRM_DEV_DEBUG(lcdc->dev, " FIFO = %08x\n", value); + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_FIFO, value); + + /* + * Toggle LCD_MODE every frame + * Note: register not documented, this is from atmel_lcdfb + */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_MVAL, 0); +} + +static void lcdc_pipe_enable_ctrl(struct lcdc *lcdc) +{ + const struct drm_format_info *format; + struct drm_display_mode *dmode; + unsigned long clk_value_khz; + unsigned int pix_factor; + unsigned int lcdcon1; + + format = lcdc->pipe.crtc.primary->state->fb->format; + dmode = &lcdc->pipe.crtc.state->adjusted_mode; + + /* LCDC Control register 1 */ + /* Set pixel clock */ + if (lcdc->desc->have_alt_pixclock) + pix_factor = 1; + else + pix_factor = 2; + + clk_value_khz = clk_get_rate(lcdc->mfd->lcdc_clk) / 1000; + lcdcon1 = DIV_ROUND_UP(clk_value_khz, dmode->clock); + + if (lcdcon1 < pix_factor) { + DRM_DEV_INFO(lcdc->dev, "Bypassing pixel clock divider\n"); + regmap_write(lcdc->mfd->regmap, + ATMEL_LCDC_LCDCON1, ATMEL_LCDC_BYPASS); + } else { + + lcdcon1 = (lcdcon1 / pix_factor) - 1; + DRM_DEV_DEBUG(lcdc->dev, "CLKVAL = 0x%08x\n", lcdcon1); + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_LCDCON1, + lcdcon1 << ATMEL_LCDC_CLKVAL_OFFSET); + dmode->clock = clk_value_khz / (pix_factor * (lcdcon1 + 1)); + DRM_DEV_DEBUG(lcdc->dev, "updated pixclk: %u KHz\n", + dmode->clock); + } + + /* LCDC Control register 2 */ + set_lcdcon2(lcdc, format); +} + +static void lcdc_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *cstate, + struct drm_plane_state *plane_state) +{ + struct drm_device *drm; + struct drm_crtc *crtc; + struct lcdc *lcdc; + + crtc = &pipe->crtc; + drm = crtc->dev; + lcdc = drm->dev_private; + + mutex_lock(&lcdc->enable_lock); + pm_runtime_get_sync(drm->dev); + pm_runtime_forbid(drm->dev); + + lcdc_pipe_enable_timing(lcdc); + lcdc_pipe_enable_dma(lcdc); + lcdc_pipe_enable_ctrl(lcdc); + atmel_lcdc_start(lcdc); + drm_crtc_vblank_on(&lcdc->pipe.crtc); + + pm_runtime_put_sync(drm->dev); + + lcdc->enabled = true; + mutex_unlock(&lcdc->enable_lock); +} + +static void lcdc_pipe_disable(struct drm_simple_display_pipe *pipe) +{ + struct drm_device *drm; + struct drm_crtc *crtc; + struct lcdc *lcdc; + + crtc = &pipe->crtc; + drm = crtc->dev; + lcdc = drm->dev_private; + + mutex_lock(&lcdc->enable_lock); + pm_runtime_get_sync(drm->dev); + + drm_crtc_vblank_off(crtc); + atmel_lcdc_stop(lcdc); + + pm_runtime_allow(drm->dev); + pm_runtime_put_sync(drm->dev); + + lcdc->enabled = false; + mutex_unlock(&lcdc->enable_lock); +} + + +static void lcdc_pipe_update_format(struct lcdc *lcdc, + struct drm_simple_display_pipe *pipe) +{ + struct drm_plane_state *plane_state; + struct drm_framebuffer *fb; + + plane_state = pipe->plane.state; + fb = plane_state->fb; + + if (fb) + set_lcdcon2(lcdc, fb->format); +} + +/* Update DMA addr */ +static void lcdc_pipe_update_dma(struct lcdc *lcdc, + struct drm_simple_display_pipe *pipe) +{ + struct drm_plane_state *plane_state; + struct drm_framebuffer *fb; + dma_addr_t dma_addr; + + plane_state = pipe->plane.state; + fb = plane_state->fb; + + if (fb) { + dma_addr = drm_fb_cma_get_gem_addr(fb, pipe->plane.state, 0); + + /* Set frame buffer DMA base address */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_DMABADDR1, dma_addr); + } +} + +static void lcdc_pipe_update_event(struct drm_simple_display_pipe *pipe) +{ + struct drm_pending_vblank_event *event; + struct drm_crtc *crtc; + + crtc = &pipe->crtc; + if (!crtc) + return; + + spin_lock_irq(&crtc->dev->event_lock); + event = crtc->state->event; + if (event) { + crtc->state->event = NULL; + + 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 void lcdc_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_pstate) +{ + struct lcdc *lcdc; + + lcdc = pipe->crtc.dev->dev_private; + + /* Format management */ + lcdc_pipe_update_format(lcdc, pipe); + + /* DMA engine... */ + lcdc_pipe_update_dma(lcdc, pipe); + + /* vblank event handling */ + lcdc_pipe_update_event(pipe); +} + +static int lcdc_pipe_enable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc; + struct lcdc *lcdc; + + crtc = &pipe->crtc; + lcdc = crtc->dev->dev_private; + + /* Last line interrupt enable */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_IER, ATMEL_LCDC_LSTLNI); + + return 0; +} + +static void lcdc_pipe_disable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc; + struct lcdc *lcdc; + + crtc = &pipe->crtc; + lcdc = crtc->dev->dev_private; + + /* Last line interrupt disable */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_IDR, ATMEL_LCDC_LSTLNI); +} + +const struct drm_simple_display_pipe_funcs lcdc_display_funcs = { + .check = lcdc_pipe_check, + .enable = lcdc_pipe_enable, + .disable = lcdc_pipe_disable, + .update = lcdc_pipe_update, + .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, + .enable_vblank = lcdc_pipe_enable_vblank, + .disable_vblank = lcdc_pipe_disable_vblank, +}; + +static int lcdc_get_of_wiring(struct lcdc *lcdc, + const struct device_node *ep) +{ + const char *str; + int ret; + + // HACK + lcdc->wiring_reversed = true; + + ret = of_property_read_string(ep, "wiring", &str); + if (ret) + return ret; + + if (strcmp(str, "red-green-reversed") == 0) { + lcdc->wiring_reversed = true; + } else if (strcmp(str, "straight") == 0) { + /* Use default format */ + } else { + DRM_DEV_ERROR(lcdc->dev, "unknown \"wiring\" property: %s\n", + str); + return -EINVAL; + } + + return 0; +} + +static int lcdc_display_init(struct lcdc *lcdc) +{ + const u32 *formats; + size_t nformats; + int ret; + + formats = get_formats(lcdc->wiring_reversed, &nformats); + + ret = drm_simple_display_pipe_init(&lcdc->drm, &lcdc->pipe, + &lcdc_display_funcs, + formats, nformats, + NULL, NULL); + if (ret < 0) + DRM_DEV_ERROR(lcdc->dev, "failed to init display pipe: %d\n", + ret); + + return ret; +} + +static int lcdc_attach_panel(struct lcdc *lcdc) +{ + struct drm_bridge *bridge; + struct drm_panel *panel; + struct device_node *np; + struct device *dev; + int ret; + + dev = lcdc->dev; + np = dev->of_node; + ret = drm_of_find_panel_or_bridge(np, 0, 0, &panel, NULL); + if (ret < 0) + return ret; + + if (!panel) { + DRM_DEV_ERROR(dev, "no panel found\n"); + return -ENODEV; + } + + lcdc->panel = panel; + bridge = devm_drm_panel_bridge_add(dev, panel, DRM_MODE_CONNECTOR_DPI); + ret = PTR_ERR_OR_ZERO(bridge); + if (ret < 0) { + DRM_DEV_ERROR(dev, "failed to add bridge: %d\n", ret); + return ret; + } + + ret = lcdc_display_init(lcdc); + if (ret < 0) + return ret; + + ret = drm_simple_display_pipe_attach_bridge(&lcdc->pipe, bridge); + if (ret < 0) + DRM_DEV_ERROR(dev, "failed to attach bridge: %d\n", ret); + + lcdc->connector = panel->connector; + return ret; +} + +static int lcdc_create_output(struct lcdc *lcdc) +{ + struct device_node *endpoint; + struct device_node *np; + int ret; + + /* port@0/endpoint@0 is the only port/endpoint */ + np = lcdc->dev->of_node; + endpoint = of_graph_get_endpoint_by_regs(np, 0, 0); + if (!endpoint) { + DRM_DEV_ERROR(lcdc->dev, "failed to find endpoint node\n"); + return -ENODEV; + } + + lcdc_get_of_wiring(lcdc, endpoint); + of_node_put(endpoint); + + ret = lcdc_attach_panel(lcdc); + + return ret; +} + +static const struct drm_mode_config_funcs mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +int atmel_lcdc_modeset_init(struct lcdc *lcdc) +{ + struct drm_device *drm; + struct device *dev; + int ret; + + drm = &lcdc->drm; + dev = drm->dev; + + drm_mode_config_init(drm); + ret = lcdc_create_output(lcdc); + + if (ret) { + drm_mode_config_cleanup(drm); + return ret; + } + + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = lcdc->desc->max_width; + drm->mode_config.max_height = lcdc->desc->max_height; + drm->mode_config.funcs = &mode_config_funcs; + drm->mode_config.quirk_addfb_rgb_to_bgr = !lcdc->wiring_reversed; + + drm_mode_config_reset(drm); + + return 0; +} + +int atmel_lcdc_vblank_init(struct lcdc *lcdc) +{ + int ret; + + ret = drm_vblank_init(&lcdc->drm, 1); + if (ret) + DRM_DEV_ERROR(lcdc->dev, "vblank init failed: %d\n", ret); + + return ret; +} + +void atmel_lcdc_vblank(struct lcdc *lcdc) +{ + drm_crtc_handle_vblank(&lcdc->pipe.crtc); +} + +/* + * Start LCD Controller (DMA + PWR) + * Caller must hold enable_lock + */ +void atmel_lcdc_start(struct lcdc *lcdc) +{ + /* Enable DMA */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_DMACON, ATMEL_LCDC_DMAEN); + /* Enable LCD */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_PWRCON, + (lcdc->desc->guard_time << ATMEL_LCDC_GUARDT_OFFSET) + | ATMEL_LCDC_PWR); +} + +/* + * Stop LCD Controller (PWR + DMA) + * Caller must hold enable_lock + */ +void atmel_lcdc_stop(struct lcdc *lcdc) +{ + unsigned int pwrcon; + + might_sleep(); + + /* Turn off the LCD controller and the DMA controller */ + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_PWRCON, + lcdc->desc->guard_time << ATMEL_LCDC_GUARDT_OFFSET); + + regmap_write(lcdc->mfd->regmap, ATMEL_LCDC_DMACON, !(ATMEL_LCDC_DMAEN)); + + /* Wait for the LCDC core to become idle */ + regmap_read_poll_timeout(lcdc->mfd->regmap, ATMEL_LCDC_PWRCON, pwrcon, + !(pwrcon & ATMEL_LCDC_BUSY), 100, 10000); +} -- 2.20.1 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel