diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/gpu/drm/atmel-hlcdc | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/atmel-hlcdc')
-rw-r--r-- | drivers/gpu/drm/atmel-hlcdc/Kconfig | 11 | ||||
-rw-r--r-- | drivers/gpu/drm/atmel-hlcdc/Makefile | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c | 541 | ||||
-rw-r--r-- | drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c | 930 | ||||
-rw-r--r-- | drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h | 423 | ||||
-rw-r--r-- | drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c | 149 | ||||
-rw-r--r-- | drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c | 1014 |
7 files changed, 3075 insertions, 0 deletions
diff --git a/drivers/gpu/drm/atmel-hlcdc/Kconfig b/drivers/gpu/drm/atmel-hlcdc/Kconfig new file mode 100644 index 000000000..5f67f0015 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_ATMEL_HLCDC + tristate "DRM Support for ATMEL HLCDC Display Controller" + depends on DRM && OF && COMMON_CLK && MFD_ATMEL_HLCDC && ARM + 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 HLCDC display + controller (i.e. at91sam9n12, at91sam9x5 family or sama5d3 family). diff --git a/drivers/gpu/drm/atmel-hlcdc/Makefile b/drivers/gpu/drm/atmel-hlcdc/Makefile new file mode 100644 index 000000000..49dc89f36 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \ + atmel_hlcdc_dc.o \ + atmel_hlcdc_output.o \ + atmel_hlcdc_plane.o + +obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc-dc.o diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c new file mode 100644 index 000000000..ce246b963 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Traphandler + * Copyright (C) 2014 Free Electrons + * + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + */ + +#include <linux/clk.h> +#include <linux/mfd/atmel-hlcdc.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +#include <video/videomode.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "atmel_hlcdc_dc.h" + +/** + * Atmel HLCDC CRTC state structure + * + * @base: base CRTC state + * @output_mode: RGBXXX output mode + */ +struct atmel_hlcdc_crtc_state { + struct drm_crtc_state base; + unsigned int output_mode; +}; + +static inline struct atmel_hlcdc_crtc_state * +drm_crtc_state_to_atmel_hlcdc_crtc_state(struct drm_crtc_state *state) +{ + return container_of(state, struct atmel_hlcdc_crtc_state, base); +} + +/** + * Atmel HLCDC CRTC structure + * + * @base: base DRM CRTC structure + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device + * @event: pointer to the current page flip event + * @id: CRTC id (returned by drm_crtc_index) + */ +struct atmel_hlcdc_crtc { + struct drm_crtc base; + struct atmel_hlcdc_dc *dc; + struct drm_pending_vblank_event *event; + int id; +}; + +static inline struct atmel_hlcdc_crtc * +drm_crtc_to_atmel_hlcdc_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct atmel_hlcdc_crtc, base); +} + +static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct regmap *regmap = crtc->dc->hlcdc->regmap; + struct drm_display_mode *adj = &c->state->adjusted_mode; + struct atmel_hlcdc_crtc_state *state; + unsigned long mode_rate; + struct videomode vm; + unsigned long prate; + unsigned int mask = ATMEL_HLCDC_CLKDIV_MASK | ATMEL_HLCDC_CLKPOL; + unsigned int cfg = 0; + int div, ret; + + ret = clk_prepare_enable(crtc->dc->hlcdc->sys_clk); + if (ret) + return; + + vm.vfront_porch = adj->crtc_vsync_start - adj->crtc_vdisplay; + vm.vback_porch = adj->crtc_vtotal - adj->crtc_vsync_end; + vm.vsync_len = adj->crtc_vsync_end - adj->crtc_vsync_start; + vm.hfront_porch = adj->crtc_hsync_start - adj->crtc_hdisplay; + vm.hback_porch = adj->crtc_htotal - adj->crtc_hsync_end; + vm.hsync_len = adj->crtc_hsync_end - adj->crtc_hsync_start; + + regmap_write(regmap, ATMEL_HLCDC_CFG(1), + (vm.hsync_len - 1) | ((vm.vsync_len - 1) << 16)); + + regmap_write(regmap, ATMEL_HLCDC_CFG(2), + (vm.vfront_porch - 1) | (vm.vback_porch << 16)); + + regmap_write(regmap, ATMEL_HLCDC_CFG(3), + (vm.hfront_porch - 1) | ((vm.hback_porch - 1) << 16)); + + regmap_write(regmap, ATMEL_HLCDC_CFG(4), + (adj->crtc_hdisplay - 1) | + ((adj->crtc_vdisplay - 1) << 16)); + + prate = clk_get_rate(crtc->dc->hlcdc->sys_clk); + mode_rate = adj->crtc_clock * 1000; + if (!crtc->dc->desc->fixed_clksrc) { + prate *= 2; + cfg |= ATMEL_HLCDC_CLKSEL; + mask |= ATMEL_HLCDC_CLKSEL; + } + + div = DIV_ROUND_UP(prate, mode_rate); + if (div < 2) { + div = 2; + } else if (ATMEL_HLCDC_CLKDIV(div) & ~ATMEL_HLCDC_CLKDIV_MASK) { + /* The divider ended up too big, try a lower base rate. */ + cfg &= ~ATMEL_HLCDC_CLKSEL; + prate /= 2; + div = DIV_ROUND_UP(prate, mode_rate); + if (ATMEL_HLCDC_CLKDIV(div) & ~ATMEL_HLCDC_CLKDIV_MASK) + div = ATMEL_HLCDC_CLKDIV_MASK; + } else { + int div_low = prate / mode_rate; + + if (div_low >= 2 && + (10 * (prate / div_low - mode_rate) < + (mode_rate - prate / div))) + /* + * At least 10 times better when using a higher + * frequency than requested, instead of a lower. + * So, go with that. + */ + div = div_low; + } + + cfg |= ATMEL_HLCDC_CLKDIV(div); + + regmap_update_bits(regmap, ATMEL_HLCDC_CFG(0), mask, cfg); + + state = drm_crtc_state_to_atmel_hlcdc_crtc_state(c->state); + cfg = state->output_mode << 8; + + if (adj->flags & DRM_MODE_FLAG_NVSYNC) + cfg |= ATMEL_HLCDC_VSPOL; + + if (adj->flags & DRM_MODE_FLAG_NHSYNC) + cfg |= ATMEL_HLCDC_HSPOL; + + regmap_update_bits(regmap, ATMEL_HLCDC_CFG(5), + ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL | + ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE | + ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY | + ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO | + ATMEL_HLCDC_GUARDTIME_MASK | ATMEL_HLCDC_MODE_MASK, + cfg); + + clk_disable_unprepare(crtc->dc->hlcdc->sys_clk); +} + +static enum drm_mode_status +atmel_hlcdc_crtc_mode_valid(struct drm_crtc *c, + const struct drm_display_mode *mode) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + + return atmel_hlcdc_dc_mode_valid(crtc->dc, mode); +} + +static void atmel_hlcdc_crtc_atomic_disable(struct drm_crtc *c, + struct drm_crtc_state *old_state) +{ + struct drm_device *dev = c->dev; + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct regmap *regmap = crtc->dc->hlcdc->regmap; + unsigned int status; + + drm_crtc_vblank_off(c); + + pm_runtime_get_sync(dev->dev); + + regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_DISP); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + (status & ATMEL_HLCDC_DISP)) + cpu_relax(); + + regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_SYNC); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + (status & ATMEL_HLCDC_SYNC)) + cpu_relax(); + + regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_PIXEL_CLK); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + (status & ATMEL_HLCDC_PIXEL_CLK)) + cpu_relax(); + + clk_disable_unprepare(crtc->dc->hlcdc->sys_clk); + pinctrl_pm_select_sleep_state(dev->dev); + + pm_runtime_allow(dev->dev); + + pm_runtime_put_sync(dev->dev); +} + +static void atmel_hlcdc_crtc_atomic_enable(struct drm_crtc *c, + struct drm_crtc_state *old_state) +{ + struct drm_device *dev = c->dev; + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct regmap *regmap = crtc->dc->hlcdc->regmap; + unsigned int status; + + pm_runtime_get_sync(dev->dev); + + pm_runtime_forbid(dev->dev); + + pinctrl_pm_select_default_state(dev->dev); + clk_prepare_enable(crtc->dc->hlcdc->sys_clk); + + regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_PIXEL_CLK); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + !(status & ATMEL_HLCDC_PIXEL_CLK)) + cpu_relax(); + + + regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_SYNC); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + !(status & ATMEL_HLCDC_SYNC)) + cpu_relax(); + + regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_DISP); + while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && + !(status & ATMEL_HLCDC_DISP)) + cpu_relax(); + + pm_runtime_put_sync(dev->dev); + + drm_crtc_vblank_on(c); +} + +#define ATMEL_HLCDC_RGB444_OUTPUT BIT(0) +#define ATMEL_HLCDC_RGB565_OUTPUT BIT(1) +#define ATMEL_HLCDC_RGB666_OUTPUT BIT(2) +#define ATMEL_HLCDC_RGB888_OUTPUT BIT(3) +#define ATMEL_HLCDC_OUTPUT_MODE_MASK GENMASK(3, 0) + +static int atmel_hlcdc_connector_output_mode(struct drm_connector_state *state) +{ + struct drm_connector *connector = state->connector; + struct drm_display_info *info = &connector->display_info; + struct drm_encoder *encoder; + unsigned int supported_fmts = 0; + int j; + + encoder = state->best_encoder; + if (!encoder) + encoder = connector->encoder; + + switch (atmel_hlcdc_encoder_get_bus_fmt(encoder)) { + case 0: + break; + case MEDIA_BUS_FMT_RGB444_1X12: + return ATMEL_HLCDC_RGB444_OUTPUT; + case MEDIA_BUS_FMT_RGB565_1X16: + return ATMEL_HLCDC_RGB565_OUTPUT; + case MEDIA_BUS_FMT_RGB666_1X18: + return ATMEL_HLCDC_RGB666_OUTPUT; + case MEDIA_BUS_FMT_RGB888_1X24: + return ATMEL_HLCDC_RGB888_OUTPUT; + default: + return -EINVAL; + } + + for (j = 0; j < info->num_bus_formats; j++) { + switch (info->bus_formats[j]) { + case MEDIA_BUS_FMT_RGB444_1X12: + supported_fmts |= ATMEL_HLCDC_RGB444_OUTPUT; + break; + case MEDIA_BUS_FMT_RGB565_1X16: + supported_fmts |= ATMEL_HLCDC_RGB565_OUTPUT; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + supported_fmts |= ATMEL_HLCDC_RGB666_OUTPUT; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + supported_fmts |= ATMEL_HLCDC_RGB888_OUTPUT; + break; + default: + break; + } + } + + return supported_fmts; +} + +static int atmel_hlcdc_crtc_select_output_mode(struct drm_crtc_state *state) +{ + unsigned int output_fmts = ATMEL_HLCDC_OUTPUT_MODE_MASK; + struct atmel_hlcdc_crtc_state *hstate; + struct drm_connector_state *cstate; + struct drm_connector *connector; + struct atmel_hlcdc_crtc *crtc; + int i; + + crtc = drm_crtc_to_atmel_hlcdc_crtc(state->crtc); + + for_each_new_connector_in_state(state->state, connector, cstate, i) { + unsigned int supported_fmts = 0; + + if (!cstate->crtc) + continue; + + supported_fmts = atmel_hlcdc_connector_output_mode(cstate); + + if (crtc->dc->desc->conflicting_output_formats) + output_fmts &= supported_fmts; + else + output_fmts |= supported_fmts; + } + + if (!output_fmts) + return -EINVAL; + + hstate = drm_crtc_state_to_atmel_hlcdc_crtc_state(state); + hstate->output_mode = fls(output_fmts) - 1; + + return 0; +} + +static int atmel_hlcdc_crtc_atomic_check(struct drm_crtc *c, + struct drm_crtc_state *s) +{ + int ret; + + ret = atmel_hlcdc_crtc_select_output_mode(s); + if (ret) + return ret; + + ret = atmel_hlcdc_plane_prepare_disc_area(s); + if (ret) + return ret; + + return atmel_hlcdc_plane_prepare_ahb_routing(s); +} + +static void atmel_hlcdc_crtc_atomic_begin(struct drm_crtc *c, + struct drm_crtc_state *old_s) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + + if (c->state->event) { + c->state->event->pipe = drm_crtc_index(c); + + WARN_ON(drm_crtc_vblank_get(c) != 0); + + crtc->event = c->state->event; + c->state->event = NULL; + } +} + +static void atmel_hlcdc_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_s) +{ + /* TODO: write common plane control register if available */ +} + +static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = { + .mode_valid = atmel_hlcdc_crtc_mode_valid, + .mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb, + .atomic_check = atmel_hlcdc_crtc_atomic_check, + .atomic_begin = atmel_hlcdc_crtc_atomic_begin, + .atomic_flush = atmel_hlcdc_crtc_atomic_flush, + .atomic_enable = atmel_hlcdc_crtc_atomic_enable, + .atomic_disable = atmel_hlcdc_crtc_atomic_disable, +}; + +static void atmel_hlcdc_crtc_destroy(struct drm_crtc *c) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + + drm_crtc_cleanup(c); + kfree(crtc); +} + +static void atmel_hlcdc_crtc_finish_page_flip(struct atmel_hlcdc_crtc *crtc) +{ + struct drm_device *dev = crtc->base.dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (crtc->event) { + drm_crtc_send_vblank_event(&crtc->base, crtc->event); + drm_crtc_vblank_put(&crtc->base); + crtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +void atmel_hlcdc_crtc_irq(struct drm_crtc *c) +{ + drm_crtc_handle_vblank(c); + atmel_hlcdc_crtc_finish_page_flip(drm_crtc_to_atmel_hlcdc_crtc(c)); +} + +static void atmel_hlcdc_crtc_reset(struct drm_crtc *crtc) +{ + struct atmel_hlcdc_crtc_state *state; + + if (crtc->state) { + __drm_atomic_helper_crtc_destroy_state(crtc->state); + state = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state); + kfree(state); + crtc->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) + __drm_atomic_helper_crtc_reset(crtc, &state->base); +} + +static struct drm_crtc_state * +atmel_hlcdc_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct atmel_hlcdc_crtc_state *state, *cur; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + + cur = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state); + state->output_mode = cur->output_mode; + + return &state->base; +} + +static void atmel_hlcdc_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *s) +{ + struct atmel_hlcdc_crtc_state *state; + + state = drm_crtc_state_to_atmel_hlcdc_crtc_state(s); + __drm_atomic_helper_crtc_destroy_state(s); + kfree(state); +} + +static int atmel_hlcdc_crtc_enable_vblank(struct drm_crtc *c) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct regmap *regmap = crtc->dc->hlcdc->regmap; + + /* Enable SOF (Start Of Frame) interrupt for vblank counting */ + regmap_write(regmap, ATMEL_HLCDC_IER, ATMEL_HLCDC_SOF); + + return 0; +} + +static void atmel_hlcdc_crtc_disable_vblank(struct drm_crtc *c) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + struct regmap *regmap = crtc->dc->hlcdc->regmap; + + regmap_write(regmap, ATMEL_HLCDC_IDR, ATMEL_HLCDC_SOF); +} + +static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = { + .page_flip = drm_atomic_helper_page_flip, + .set_config = drm_atomic_helper_set_config, + .destroy = atmel_hlcdc_crtc_destroy, + .reset = atmel_hlcdc_crtc_reset, + .atomic_duplicate_state = atmel_hlcdc_crtc_duplicate_state, + .atomic_destroy_state = atmel_hlcdc_crtc_destroy_state, + .enable_vblank = atmel_hlcdc_crtc_enable_vblank, + .disable_vblank = atmel_hlcdc_crtc_disable_vblank, + .gamma_set = drm_atomic_helper_legacy_gamma_set, +}; + +int atmel_hlcdc_crtc_create(struct drm_device *dev) +{ + struct atmel_hlcdc_plane *primary = NULL, *cursor = NULL; + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_crtc *crtc; + int ret; + int i; + + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); + if (!crtc) + return -ENOMEM; + + crtc->dc = dc; + + for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { + if (!dc->layers[i]) + continue; + + switch (dc->layers[i]->desc->type) { + case ATMEL_HLCDC_BASE_LAYER: + primary = atmel_hlcdc_layer_to_plane(dc->layers[i]); + break; + + case ATMEL_HLCDC_CURSOR_LAYER: + cursor = atmel_hlcdc_layer_to_plane(dc->layers[i]); + break; + + default: + break; + } + } + + ret = drm_crtc_init_with_planes(dev, &crtc->base, &primary->base, + &cursor->base, &atmel_hlcdc_crtc_funcs, + NULL); + if (ret < 0) + goto fail; + + crtc->id = drm_crtc_index(&crtc->base); + + for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { + struct atmel_hlcdc_plane *overlay; + + if (dc->layers[i] && + dc->layers[i]->desc->type == ATMEL_HLCDC_OVERLAY_LAYER) { + overlay = atmel_hlcdc_layer_to_plane(dc->layers[i]); + overlay->base.possible_crtcs = 1 << crtc->id; + } + } + + drm_crtc_helper_add(&crtc->base, &lcdc_crtc_helper_funcs); + + drm_mode_crtc_set_gamma_size(&crtc->base, ATMEL_HLCDC_CLUT_SIZE); + drm_crtc_enable_color_mgmt(&crtc->base, 0, false, + ATMEL_HLCDC_CLUT_SIZE); + + dc->crtc = &crtc->base; + + return 0; + +fail: + atmel_hlcdc_crtc_destroy(&crtc->base); + return ret; +} diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c new file mode 100644 index 000000000..871293d1a --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c @@ -0,0 +1,930 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Traphandler + * Copyright (C) 2014 Free Electrons + * Copyright (C) 2014 Atmel + * + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + */ + +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/mfd/atmel-hlcdc.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/platform_device.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_irq.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "atmel_hlcdc_dc.h" + +#define ATMEL_HLCDC_LAYER_IRQS_OFFSET 8 + +static const struct atmel_hlcdc_layer_desc atmel_hlcdc_at91sam9n12_layers[] = { + { + .name = "base", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x40, + .id = 0, + .type = ATMEL_HLCDC_BASE_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .xstride = { 2 }, + .default_color = 3, + .general_config = 4, + }, + .clut_offset = 0x400, + }, +}; + +static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9n12 = { + .min_width = 0, + .min_height = 0, + .max_width = 1280, + .max_height = 860, + .max_spw = 0x3f, + .max_vpw = 0x3f, + .max_hpw = 0xff, + .conflicting_output_formats = true, + .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9n12_layers), + .layers = atmel_hlcdc_at91sam9n12_layers, +}; + +static const struct atmel_hlcdc_layer_desc atmel_hlcdc_at91sam9x5_layers[] = { + { + .name = "base", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x40, + .id = 0, + .type = ATMEL_HLCDC_BASE_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .xstride = { 2 }, + .default_color = 3, + .general_config = 4, + .disc_pos = 5, + .disc_size = 6, + }, + .clut_offset = 0x400, + }, + { + .name = "overlay1", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x100, + .id = 1, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0x800, + }, + { + .name = "high-end-overlay", + .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, + .regs_offset = 0x280, + .id = 2, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x4c, + .layout = { + .pos = 2, + .size = 3, + .memsize = 4, + .xstride = { 5, 7 }, + .pstride = { 6, 8 }, + .default_color = 9, + .chroma_key = 10, + .chroma_key_mask = 11, + .general_config = 12, + .scaler_config = 13, + .csc = 14, + }, + .clut_offset = 0x1000, + }, + { + .name = "cursor", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x340, + .id = 3, + .type = ATMEL_HLCDC_CURSOR_LAYER, + .max_width = 128, + .max_height = 128, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0x1400, + }, +}; + +static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9x5 = { + .min_width = 0, + .min_height = 0, + .max_width = 800, + .max_height = 600, + .max_spw = 0x3f, + .max_vpw = 0x3f, + .max_hpw = 0xff, + .conflicting_output_formats = true, + .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9x5_layers), + .layers = atmel_hlcdc_at91sam9x5_layers, +}; + +static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sama5d3_layers[] = { + { + .name = "base", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x40, + .id = 0, + .type = ATMEL_HLCDC_BASE_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .xstride = { 2 }, + .default_color = 3, + .general_config = 4, + .disc_pos = 5, + .disc_size = 6, + }, + .clut_offset = 0x600, + }, + { + .name = "overlay1", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x140, + .id = 1, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0xa00, + }, + { + .name = "overlay2", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x240, + .id = 2, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0xe00, + }, + { + .name = "high-end-overlay", + .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, + .regs_offset = 0x340, + .id = 3, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x4c, + .layout = { + .pos = 2, + .size = 3, + .memsize = 4, + .xstride = { 5, 7 }, + .pstride = { 6, 8 }, + .default_color = 9, + .chroma_key = 10, + .chroma_key_mask = 11, + .general_config = 12, + .scaler_config = 13, + .phicoeffs = { + .x = 17, + .y = 33, + }, + .csc = 14, + }, + .clut_offset = 0x1200, + }, + { + .name = "cursor", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x440, + .id = 4, + .type = ATMEL_HLCDC_CURSOR_LAYER, + .max_width = 128, + .max_height = 128, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + .scaler_config = 13, + }, + .clut_offset = 0x1600, + }, +}; + +static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = { + .min_width = 0, + .min_height = 0, + .max_width = 2048, + .max_height = 2048, + .max_spw = 0x3f, + .max_vpw = 0x3f, + .max_hpw = 0x1ff, + .conflicting_output_formats = true, + .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers), + .layers = atmel_hlcdc_sama5d3_layers, +}; + +static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sama5d4_layers[] = { + { + .name = "base", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x40, + .id = 0, + .type = ATMEL_HLCDC_BASE_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .xstride = { 2 }, + .default_color = 3, + .general_config = 4, + .disc_pos = 5, + .disc_size = 6, + }, + .clut_offset = 0x600, + }, + { + .name = "overlay1", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x140, + .id = 1, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0xa00, + }, + { + .name = "overlay2", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x240, + .id = 2, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0xe00, + }, + { + .name = "high-end-overlay", + .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, + .regs_offset = 0x340, + .id = 3, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x4c, + .layout = { + .pos = 2, + .size = 3, + .memsize = 4, + .xstride = { 5, 7 }, + .pstride = { 6, 8 }, + .default_color = 9, + .chroma_key = 10, + .chroma_key_mask = 11, + .general_config = 12, + .scaler_config = 13, + .phicoeffs = { + .x = 17, + .y = 33, + }, + .csc = 14, + }, + .clut_offset = 0x1200, + }, +}; + +static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d4 = { + .min_width = 0, + .min_height = 0, + .max_width = 2048, + .max_height = 2048, + .max_spw = 0xff, + .max_vpw = 0xff, + .max_hpw = 0x3ff, + .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d4_layers), + .layers = atmel_hlcdc_sama5d4_layers, +}; + +static const struct atmel_hlcdc_layer_desc atmel_hlcdc_sam9x60_layers[] = { + { + .name = "base", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x60, + .id = 0, + .type = ATMEL_HLCDC_BASE_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .xstride = { 2 }, + .default_color = 3, + .general_config = 4, + .disc_pos = 5, + .disc_size = 6, + }, + .clut_offset = 0x600, + }, + { + .name = "overlay1", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x160, + .id = 1, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0xa00, + }, + { + .name = "overlay2", + .formats = &atmel_hlcdc_plane_rgb_formats, + .regs_offset = 0x260, + .id = 2, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x2c, + .layout = { + .pos = 2, + .size = 3, + .xstride = { 4 }, + .pstride = { 5 }, + .default_color = 6, + .chroma_key = 7, + .chroma_key_mask = 8, + .general_config = 9, + }, + .clut_offset = 0xe00, + }, + { + .name = "high-end-overlay", + .formats = &atmel_hlcdc_plane_rgb_and_yuv_formats, + .regs_offset = 0x360, + .id = 3, + .type = ATMEL_HLCDC_OVERLAY_LAYER, + .cfgs_offset = 0x4c, + .layout = { + .pos = 2, + .size = 3, + .memsize = 4, + .xstride = { 5, 7 }, + .pstride = { 6, 8 }, + .default_color = 9, + .chroma_key = 10, + .chroma_key_mask = 11, + .general_config = 12, + .scaler_config = 13, + .phicoeffs = { + .x = 17, + .y = 33, + }, + .csc = 14, + }, + .clut_offset = 0x1200, + }, +}; + +static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sam9x60 = { + .min_width = 0, + .min_height = 0, + .max_width = 2048, + .max_height = 2048, + .max_spw = 0xff, + .max_vpw = 0xff, + .max_hpw = 0x3ff, + .fixed_clksrc = true, + .nlayers = ARRAY_SIZE(atmel_hlcdc_sam9x60_layers), + .layers = atmel_hlcdc_sam9x60_layers, +}; + +static const struct of_device_id atmel_hlcdc_of_match[] = { + { + .compatible = "atmel,at91sam9n12-hlcdc", + .data = &atmel_hlcdc_dc_at91sam9n12, + }, + { + .compatible = "atmel,at91sam9x5-hlcdc", + .data = &atmel_hlcdc_dc_at91sam9x5, + }, + { + .compatible = "atmel,sama5d2-hlcdc", + .data = &atmel_hlcdc_dc_sama5d4, + }, + { + .compatible = "atmel,sama5d3-hlcdc", + .data = &atmel_hlcdc_dc_sama5d3, + }, + { + .compatible = "atmel,sama5d4-hlcdc", + .data = &atmel_hlcdc_dc_sama5d4, + }, + { + .compatible = "microchip,sam9x60-hlcdc", + .data = &atmel_hlcdc_dc_sam9x60, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, atmel_hlcdc_of_match); + +enum drm_mode_status +atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, + const struct drm_display_mode *mode) +{ + int vfront_porch = mode->vsync_start - mode->vdisplay; + int vback_porch = mode->vtotal - mode->vsync_end; + int vsync_len = mode->vsync_end - mode->vsync_start; + int hfront_porch = mode->hsync_start - mode->hdisplay; + int hback_porch = mode->htotal - mode->hsync_end; + int hsync_len = mode->hsync_end - mode->hsync_start; + + if (hsync_len > dc->desc->max_spw + 1 || hsync_len < 1) + return MODE_HSYNC; + + if (vsync_len > dc->desc->max_spw + 1 || vsync_len < 1) + return MODE_VSYNC; + + if (hfront_porch > dc->desc->max_hpw + 1 || hfront_porch < 1 || + hback_porch > dc->desc->max_hpw + 1 || hback_porch < 1 || + mode->hdisplay < 1) + return MODE_H_ILLEGAL; + + if (vfront_porch > dc->desc->max_vpw + 1 || vfront_porch < 1 || + vback_porch > dc->desc->max_vpw || vback_porch < 0 || + mode->vdisplay < 1) + return MODE_V_ILLEGAL; + + return MODE_OK; +} + +static void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer) +{ + if (!layer) + return; + + if (layer->desc->type == ATMEL_HLCDC_BASE_LAYER || + layer->desc->type == ATMEL_HLCDC_OVERLAY_LAYER || + layer->desc->type == ATMEL_HLCDC_CURSOR_LAYER) + atmel_hlcdc_plane_irq(atmel_hlcdc_layer_to_plane(layer)); +} + +static irqreturn_t atmel_hlcdc_dc_irq_handler(int irq, void *data) +{ + struct drm_device *dev = data; + struct atmel_hlcdc_dc *dc = dev->dev_private; + unsigned long status; + unsigned int imr, isr; + int i; + + regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_IMR, &imr); + regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); + status = imr & isr; + if (!status) + return IRQ_NONE; + + if (status & ATMEL_HLCDC_SOF) + atmel_hlcdc_crtc_irq(dc->crtc); + + for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { + if (ATMEL_HLCDC_LAYER_STATUS(i) & status) + atmel_hlcdc_layer_irq(dc->layers[i]); + } + + return IRQ_HANDLED; +} + +struct atmel_hlcdc_dc_commit { + struct work_struct work; + struct drm_device *dev; + struct drm_atomic_state *state; +}; + +static void +atmel_hlcdc_dc_atomic_complete(struct atmel_hlcdc_dc_commit *commit) +{ + struct drm_device *dev = commit->dev; + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct drm_atomic_state *old_state = commit->state; + + /* Apply the atomic update. */ + drm_atomic_helper_commit_modeset_disables(dev, old_state); + drm_atomic_helper_commit_planes(dev, old_state, 0); + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + drm_atomic_helper_wait_for_vblanks(dev, old_state); + + drm_atomic_helper_cleanup_planes(dev, old_state); + + drm_atomic_state_put(old_state); + + /* Complete the commit, wake up any waiter. */ + spin_lock(&dc->commit.wait.lock); + dc->commit.pending = false; + wake_up_all_locked(&dc->commit.wait); + spin_unlock(&dc->commit.wait.lock); + + kfree(commit); +} + +static void atmel_hlcdc_dc_atomic_work(struct work_struct *work) +{ + struct atmel_hlcdc_dc_commit *commit = + container_of(work, struct atmel_hlcdc_dc_commit, work); + + atmel_hlcdc_dc_atomic_complete(commit); +} + +static int atmel_hlcdc_dc_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, + bool async) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_dc_commit *commit; + int ret; + + ret = drm_atomic_helper_prepare_planes(dev, state); + if (ret) + return ret; + + /* Allocate the commit object. */ + commit = kzalloc(sizeof(*commit), GFP_KERNEL); + if (!commit) { + ret = -ENOMEM; + goto error; + } + + INIT_WORK(&commit->work, atmel_hlcdc_dc_atomic_work); + commit->dev = dev; + commit->state = state; + + spin_lock(&dc->commit.wait.lock); + ret = wait_event_interruptible_locked(dc->commit.wait, + !dc->commit.pending); + if (ret == 0) + dc->commit.pending = true; + spin_unlock(&dc->commit.wait.lock); + + if (ret) + goto err_free; + + /* We have our own synchronization through the commit lock. */ + BUG_ON(drm_atomic_helper_swap_state(state, false) < 0); + + /* Swap state succeeded, this is the point of no return. */ + drm_atomic_state_get(state); + if (async) + queue_work(dc->wq, &commit->work); + else + atmel_hlcdc_dc_atomic_complete(commit); + + return 0; + +err_free: + kfree(commit); +error: + drm_atomic_helper_cleanup_planes(dev, state); + 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 = atmel_hlcdc_dc_atomic_commit, +}; + +static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + int ret; + + drm_mode_config_init(dev); + + ret = atmel_hlcdc_create_outputs(dev); + if (ret) { + dev_err(dev->dev, "failed to create HLCDC outputs: %d\n", ret); + return ret; + } + + ret = atmel_hlcdc_create_planes(dev); + if (ret) { + dev_err(dev->dev, "failed to create planes: %d\n", ret); + return ret; + } + + ret = atmel_hlcdc_crtc_create(dev); + if (ret) { + dev_err(dev->dev, "failed to create crtc\n"); + return ret; + } + + dev->mode_config.min_width = dc->desc->min_width; + dev->mode_config.min_height = dc->desc->min_height; + dev->mode_config.max_width = dc->desc->max_width; + dev->mode_config.max_height = dc->desc->max_height; + dev->mode_config.funcs = &mode_config_funcs; + + return 0; +} + +static int atmel_hlcdc_dc_load(struct drm_device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + const struct of_device_id *match; + struct atmel_hlcdc_dc *dc; + int ret; + + match = of_match_node(atmel_hlcdc_of_match, dev->dev->parent->of_node); + if (!match) { + dev_err(&pdev->dev, "invalid compatible string\n"); + return -ENODEV; + } + + if (!match->data) { + dev_err(&pdev->dev, "invalid hlcdc description\n"); + return -EINVAL; + } + + dc = devm_kzalloc(dev->dev, sizeof(*dc), GFP_KERNEL); + if (!dc) + return -ENOMEM; + + dc->wq = alloc_ordered_workqueue("atmel-hlcdc-dc", 0); + if (!dc->wq) + return -ENOMEM; + + init_waitqueue_head(&dc->commit.wait); + dc->desc = match->data; + dc->hlcdc = dev_get_drvdata(dev->dev->parent); + dev->dev_private = dc; + + ret = clk_prepare_enable(dc->hlcdc->periph_clk); + if (ret) { + dev_err(dev->dev, "failed to enable periph_clk\n"); + goto err_destroy_wq; + } + + pm_runtime_enable(dev->dev); + + ret = drm_vblank_init(dev, 1); + if (ret < 0) { + dev_err(dev->dev, "failed to initialize vblank\n"); + goto err_periph_clk_disable; + } + + ret = atmel_hlcdc_dc_modeset_init(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to initialize mode setting\n"); + goto err_periph_clk_disable; + } + + drm_mode_config_reset(dev); + + pm_runtime_get_sync(dev->dev); + ret = drm_irq_install(dev, dc->hlcdc->irq); + pm_runtime_put_sync(dev->dev); + if (ret < 0) { + dev_err(dev->dev, "failed to install IRQ handler\n"); + goto err_periph_clk_disable; + } + + platform_set_drvdata(pdev, dev); + + drm_kms_helper_poll_init(dev); + + return 0; + +err_periph_clk_disable: + pm_runtime_disable(dev->dev); + clk_disable_unprepare(dc->hlcdc->periph_clk); + +err_destroy_wq: + destroy_workqueue(dc->wq); + + return ret; +} + +static void atmel_hlcdc_dc_unload(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + + flush_workqueue(dc->wq); + drm_kms_helper_poll_fini(dev); + drm_atomic_helper_shutdown(dev); + drm_mode_config_cleanup(dev); + + pm_runtime_get_sync(dev->dev); + drm_irq_uninstall(dev); + pm_runtime_put_sync(dev->dev); + + dev->dev_private = NULL; + + pm_runtime_disable(dev->dev); + clk_disable_unprepare(dc->hlcdc->periph_clk); + destroy_workqueue(dc->wq); +} + +static int atmel_hlcdc_dc_irq_postinstall(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + unsigned int cfg = 0; + int i; + + /* Enable interrupts on activated layers */ + for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { + if (dc->layers[i]) + cfg |= ATMEL_HLCDC_LAYER_STATUS(i); + } + + regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, cfg); + + return 0; +} + +static void atmel_hlcdc_dc_irq_uninstall(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + unsigned int isr; + + regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, 0xffffffff); + regmap_read(dc->hlcdc->regmap, ATMEL_HLCDC_ISR, &isr); +} + +DEFINE_DRM_GEM_CMA_FOPS(fops); + +static struct drm_driver atmel_hlcdc_dc_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + .irq_handler = atmel_hlcdc_dc_irq_handler, + .irq_preinstall = atmel_hlcdc_dc_irq_uninstall, + .irq_postinstall = atmel_hlcdc_dc_irq_postinstall, + .irq_uninstall = atmel_hlcdc_dc_irq_uninstall, + DRM_GEM_CMA_DRIVER_OPS, + .fops = &fops, + .name = "atmel-hlcdc", + .desc = "Atmel HLCD Controller DRM", + .date = "20141504", + .major = 1, + .minor = 0, +}; + +static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) +{ + struct drm_device *ddev; + int ret; + + ddev = drm_dev_alloc(&atmel_hlcdc_dc_driver, &pdev->dev); + if (IS_ERR(ddev)) + return PTR_ERR(ddev); + + ret = atmel_hlcdc_dc_load(ddev); + if (ret) + goto err_put; + + ret = drm_dev_register(ddev, 0); + if (ret) + goto err_unload; + + drm_fbdev_generic_setup(ddev, 24); + + return 0; + +err_unload: + atmel_hlcdc_dc_unload(ddev); + +err_put: + drm_dev_put(ddev); + + return ret; +} + +static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev) +{ + struct drm_device *ddev = platform_get_drvdata(pdev); + + drm_dev_unregister(ddev); + atmel_hlcdc_dc_unload(ddev); + drm_dev_put(ddev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int atmel_hlcdc_dc_drm_suspend(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct atmel_hlcdc_dc *dc = drm_dev->dev_private; + struct regmap *regmap = dc->hlcdc->regmap; + struct drm_atomic_state *state; + + state = drm_atomic_helper_suspend(drm_dev); + if (IS_ERR(state)) + return PTR_ERR(state); + + dc->suspend.state = state; + + regmap_read(regmap, ATMEL_HLCDC_IMR, &dc->suspend.imr); + regmap_write(regmap, ATMEL_HLCDC_IDR, dc->suspend.imr); + clk_disable_unprepare(dc->hlcdc->periph_clk); + + return 0; +} + +static int atmel_hlcdc_dc_drm_resume(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + struct atmel_hlcdc_dc *dc = drm_dev->dev_private; + + clk_prepare_enable(dc->hlcdc->periph_clk); + regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, dc->suspend.imr); + + return drm_atomic_helper_resume(drm_dev, dc->suspend.state); +} +#endif + +static SIMPLE_DEV_PM_OPS(atmel_hlcdc_dc_drm_pm_ops, + atmel_hlcdc_dc_drm_suspend, atmel_hlcdc_dc_drm_resume); + +static const struct of_device_id atmel_hlcdc_dc_of_match[] = { + { .compatible = "atmel,hlcdc-display-controller" }, + { }, +}; + +static struct platform_driver atmel_hlcdc_dc_platform_driver = { + .probe = atmel_hlcdc_dc_drm_probe, + .remove = atmel_hlcdc_dc_drm_remove, + .driver = { + .name = "atmel-hlcdc-display-controller", + .pm = &atmel_hlcdc_dc_drm_pm_ops, + .of_match_table = atmel_hlcdc_dc_of_match, + }, +}; +module_platform_driver(atmel_hlcdc_dc_platform_driver); + +MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>"); +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("Atmel HLCDC Display Controller DRM Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:atmel-hlcdc-dc"); diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h new file mode 100644 index 000000000..469d4507e --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h @@ -0,0 +1,423 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2014 Traphandler + * Copyright (C) 2014 Free Electrons + * Copyright (C) 2014 Atmel + * + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + */ + +#ifndef DRM_ATMEL_HLCDC_H +#define DRM_ATMEL_HLCDC_H + +#include <linux/regmap.h> + +#include <drm/drm_plane.h> + +#define ATMEL_HLCDC_LAYER_CHER 0x0 +#define ATMEL_HLCDC_LAYER_CHDR 0x4 +#define ATMEL_HLCDC_LAYER_CHSR 0x8 +#define ATMEL_HLCDC_LAYER_EN BIT(0) +#define ATMEL_HLCDC_LAYER_UPDATE BIT(1) +#define ATMEL_HLCDC_LAYER_A2Q BIT(2) +#define ATMEL_HLCDC_LAYER_RST BIT(8) + +#define ATMEL_HLCDC_LAYER_IER 0xc +#define ATMEL_HLCDC_LAYER_IDR 0x10 +#define ATMEL_HLCDC_LAYER_IMR 0x14 +#define ATMEL_HLCDC_LAYER_ISR 0x18 +#define ATMEL_HLCDC_LAYER_DFETCH BIT(0) +#define ATMEL_HLCDC_LAYER_LFETCH BIT(1) +#define ATMEL_HLCDC_LAYER_DMA_IRQ(p) BIT(2 + (8 * (p))) +#define ATMEL_HLCDC_LAYER_DSCR_IRQ(p) BIT(3 + (8 * (p))) +#define ATMEL_HLCDC_LAYER_ADD_IRQ(p) BIT(4 + (8 * (p))) +#define ATMEL_HLCDC_LAYER_DONE_IRQ(p) BIT(5 + (8 * (p))) +#define ATMEL_HLCDC_LAYER_OVR_IRQ(p) BIT(6 + (8 * (p))) + +#define ATMEL_HLCDC_LAYER_PLANE_HEAD(p) (((p) * 0x10) + 0x1c) +#define ATMEL_HLCDC_LAYER_PLANE_ADDR(p) (((p) * 0x10) + 0x20) +#define ATMEL_HLCDC_LAYER_PLANE_CTRL(p) (((p) * 0x10) + 0x24) +#define ATMEL_HLCDC_LAYER_PLANE_NEXT(p) (((p) * 0x10) + 0x28) + +#define ATMEL_HLCDC_LAYER_DMA_CFG 0 +#define ATMEL_HLCDC_LAYER_DMA_SIF BIT(0) +#define ATMEL_HLCDC_LAYER_DMA_BLEN_MASK GENMASK(5, 4) +#define ATMEL_HLCDC_LAYER_DMA_BLEN_SINGLE (0 << 4) +#define ATMEL_HLCDC_LAYER_DMA_BLEN_INCR4 (1 << 4) +#define ATMEL_HLCDC_LAYER_DMA_BLEN_INCR8 (2 << 4) +#define ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16 (3 << 4) +#define ATMEL_HLCDC_LAYER_DMA_DLBO BIT(8) +#define ATMEL_HLCDC_LAYER_DMA_ROTDIS BIT(12) +#define ATMEL_HLCDC_LAYER_DMA_LOCKDIS BIT(13) + +#define ATMEL_HLCDC_LAYER_FORMAT_CFG 1 +#define ATMEL_HLCDC_LAYER_RGB (0 << 0) +#define ATMEL_HLCDC_LAYER_CLUT (1 << 0) +#define ATMEL_HLCDC_LAYER_YUV (2 << 0) +#define ATMEL_HLCDC_RGB_MODE(m) \ + (ATMEL_HLCDC_LAYER_RGB | (((m) & 0xf) << 4)) +#define ATMEL_HLCDC_CLUT_MODE(m) \ + (ATMEL_HLCDC_LAYER_CLUT | (((m) & 0x3) << 8)) +#define ATMEL_HLCDC_YUV_MODE(m) \ + (ATMEL_HLCDC_LAYER_YUV | (((m) & 0xf) << 12)) +#define ATMEL_HLCDC_YUV422ROT BIT(16) +#define ATMEL_HLCDC_YUV422SWP BIT(17) +#define ATMEL_HLCDC_DSCALEOPT BIT(20) + +#define ATMEL_HLCDC_C1_MODE ATMEL_HLCDC_CLUT_MODE(0) +#define ATMEL_HLCDC_C2_MODE ATMEL_HLCDC_CLUT_MODE(1) +#define ATMEL_HLCDC_C4_MODE ATMEL_HLCDC_CLUT_MODE(2) +#define ATMEL_HLCDC_C8_MODE ATMEL_HLCDC_CLUT_MODE(3) + +#define ATMEL_HLCDC_XRGB4444_MODE ATMEL_HLCDC_RGB_MODE(0) +#define ATMEL_HLCDC_ARGB4444_MODE ATMEL_HLCDC_RGB_MODE(1) +#define ATMEL_HLCDC_RGBA4444_MODE ATMEL_HLCDC_RGB_MODE(2) +#define ATMEL_HLCDC_RGB565_MODE ATMEL_HLCDC_RGB_MODE(3) +#define ATMEL_HLCDC_ARGB1555_MODE ATMEL_HLCDC_RGB_MODE(4) +#define ATMEL_HLCDC_XRGB8888_MODE ATMEL_HLCDC_RGB_MODE(9) +#define ATMEL_HLCDC_RGB888_MODE ATMEL_HLCDC_RGB_MODE(10) +#define ATMEL_HLCDC_ARGB8888_MODE ATMEL_HLCDC_RGB_MODE(12) +#define ATMEL_HLCDC_RGBA8888_MODE ATMEL_HLCDC_RGB_MODE(13) + +#define ATMEL_HLCDC_AYUV_MODE ATMEL_HLCDC_YUV_MODE(0) +#define ATMEL_HLCDC_YUYV_MODE ATMEL_HLCDC_YUV_MODE(1) +#define ATMEL_HLCDC_UYVY_MODE ATMEL_HLCDC_YUV_MODE(2) +#define ATMEL_HLCDC_YVYU_MODE ATMEL_HLCDC_YUV_MODE(3) +#define ATMEL_HLCDC_VYUY_MODE ATMEL_HLCDC_YUV_MODE(4) +#define ATMEL_HLCDC_NV61_MODE ATMEL_HLCDC_YUV_MODE(5) +#define ATMEL_HLCDC_YUV422_MODE ATMEL_HLCDC_YUV_MODE(6) +#define ATMEL_HLCDC_NV21_MODE ATMEL_HLCDC_YUV_MODE(7) +#define ATMEL_HLCDC_YUV420_MODE ATMEL_HLCDC_YUV_MODE(8) + +#define ATMEL_HLCDC_LAYER_POS(x, y) ((x) | ((y) << 16)) +#define ATMEL_HLCDC_LAYER_SIZE(w, h) (((w) - 1) | (((h) - 1) << 16)) + +#define ATMEL_HLCDC_LAYER_CRKEY BIT(0) +#define ATMEL_HLCDC_LAYER_INV BIT(1) +#define ATMEL_HLCDC_LAYER_ITER2BL BIT(2) +#define ATMEL_HLCDC_LAYER_ITER BIT(3) +#define ATMEL_HLCDC_LAYER_REVALPHA BIT(4) +#define ATMEL_HLCDC_LAYER_GAEN BIT(5) +#define ATMEL_HLCDC_LAYER_LAEN BIT(6) +#define ATMEL_HLCDC_LAYER_OVR BIT(7) +#define ATMEL_HLCDC_LAYER_DMA BIT(8) +#define ATMEL_HLCDC_LAYER_REP BIT(9) +#define ATMEL_HLCDC_LAYER_DSTKEY BIT(10) +#define ATMEL_HLCDC_LAYER_DISCEN BIT(11) +#define ATMEL_HLCDC_LAYER_GA_SHIFT 16 +#define ATMEL_HLCDC_LAYER_GA_MASK \ + GENMASK(23, ATMEL_HLCDC_LAYER_GA_SHIFT) +#define ATMEL_HLCDC_LAYER_GA(x) \ + ((x) << ATMEL_HLCDC_LAYER_GA_SHIFT) + +#define ATMEL_HLCDC_LAYER_DISC_POS(x, y) ((x) | ((y) << 16)) +#define ATMEL_HLCDC_LAYER_DISC_SIZE(w, h) (((w) - 1) | (((h) - 1) << 16)) + +#define ATMEL_HLCDC_LAYER_SCALER_FACTORS(x, y) ((x) | ((y) << 16)) +#define ATMEL_HLCDC_LAYER_SCALER_ENABLE BIT(31) + +#define ATMEL_HLCDC_LAYER_MAX_PLANES 3 + +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_RESERVED BIT(0) +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED BIT(1) +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE BIT(2) +#define ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN BIT(3) + +#define ATMEL_HLCDC_CLUT_SIZE 256 + +#define ATMEL_HLCDC_MAX_LAYERS 6 + +/** + * Atmel HLCDC Layer registers layout structure + * + * Each HLCDC layer has its own register organization and a given register + * can be placed differently on 2 different layers depending on its + * capabilities. + * This structure stores common registers layout for a given layer and is + * used by HLCDC layer code to choose the appropriate register to write to + * or to read from. + * + * For all fields, a value of zero means "unsupported". + * + * See Atmel's datasheet for a detailled description of these registers. + * + * @xstride: xstride registers + * @pstride: pstride registers + * @pos: position register + * @size: displayed size register + * @memsize: memory size register + * @default_color: default color register + * @chroma_key: chroma key register + * @chroma_key_mask: chroma key mask register + * @general_config: general layer config register + * @sacler_config: scaler factors register + * @phicoeffs: X/Y PHI coefficient registers + * @disc_pos: discard area position register + * @disc_size: discard area size register + * @csc: color space conversion register + */ +struct atmel_hlcdc_layer_cfg_layout { + int xstride[ATMEL_HLCDC_LAYER_MAX_PLANES]; + int pstride[ATMEL_HLCDC_LAYER_MAX_PLANES]; + int pos; + int size; + int memsize; + int default_color; + int chroma_key; + int chroma_key_mask; + int general_config; + int scaler_config; + struct { + int x; + int y; + } phicoeffs; + int disc_pos; + int disc_size; + int csc; +}; + +/** + * Atmel HLCDC DMA descriptor structure + * + * This structure is used by the HLCDC DMA engine to schedule a DMA transfer. + * + * The structure fields must remain in this specific order, because they're + * used by the HLCDC DMA engine, which expect them in this order. + * HLCDC DMA descriptors must be aligned on 64 bits. + * + * @addr: buffer DMA address + * @ctrl: DMA transfer options + * @next: next DMA descriptor to fetch + * @self: descriptor DMA address + */ +struct atmel_hlcdc_dma_channel_dscr { + dma_addr_t addr; + u32 ctrl; + dma_addr_t next; + dma_addr_t self; +} __aligned(sizeof(u64)); + +/** + * Atmel HLCDC layer types + */ +enum atmel_hlcdc_layer_type { + ATMEL_HLCDC_NO_LAYER, + ATMEL_HLCDC_BASE_LAYER, + ATMEL_HLCDC_OVERLAY_LAYER, + ATMEL_HLCDC_CURSOR_LAYER, + ATMEL_HLCDC_PP_LAYER, +}; + +/** + * Atmel HLCDC Supported formats structure + * + * This structure list all the formats supported by a given layer. + * + * @nformats: number of supported formats + * @formats: supported formats + */ +struct atmel_hlcdc_formats { + int nformats; + u32 *formats; +}; + +/** + * Atmel HLCDC Layer description structure + * + * This structure describes the capabilities provided by a given layer. + * + * @name: layer name + * @type: layer type + * @id: layer id + * @regs_offset: offset of the layer registers from the HLCDC registers base + * @cfgs_offset: CFGX registers offset from the layer registers base + * @formats: supported formats + * @layout: config registers layout + * @max_width: maximum width supported by this layer (0 means unlimited) + * @max_height: maximum height supported by this layer (0 means unlimited) + */ +struct atmel_hlcdc_layer_desc { + const char *name; + enum atmel_hlcdc_layer_type type; + int id; + int regs_offset; + int cfgs_offset; + int clut_offset; + struct atmel_hlcdc_formats *formats; + struct atmel_hlcdc_layer_cfg_layout layout; + int max_width; + int max_height; +}; + +/** + * Atmel HLCDC Layer. + * + * A layer can be a DRM plane of a post processing layer used to render + * HLCDC composition into memory. + * + * @desc: layer description + * @regmap: pointer to the HLCDC regmap + */ +struct atmel_hlcdc_layer { + const struct atmel_hlcdc_layer_desc *desc; + struct regmap *regmap; +}; + +/** + * Atmel HLCDC Plane. + * + * @base: base DRM plane structure + * @layer: HLCDC layer structure + * @properties: pointer to the property definitions structure + */ +struct atmel_hlcdc_plane { + struct drm_plane base; + struct atmel_hlcdc_layer layer; +}; + +static inline struct atmel_hlcdc_plane * +drm_plane_to_atmel_hlcdc_plane(struct drm_plane *p) +{ + return container_of(p, struct atmel_hlcdc_plane, base); +} + +static inline struct atmel_hlcdc_plane * +atmel_hlcdc_layer_to_plane(struct atmel_hlcdc_layer *layer) +{ + return container_of(layer, struct atmel_hlcdc_plane, layer); +} + +/** + * Atmel HLCDC Display Controller description structure. + * + * This structure describes the HLCDC IP capabilities and depends on the + * HLCDC IP version (or Atmel SoC family). + * + * @min_width: minimum width supported by the Display Controller + * @min_height: minimum height supported by the Display Controller + * @max_width: maximum width supported by the Display Controller + * @max_height: maximum height supported by the Display Controller + * @max_spw: maximum vertical/horizontal pulse width + * @max_vpw: maximum vertical back/front porch width + * @max_hpw: maximum horizontal back/front porch width + * @conflicting_output_formats: true if RGBXXX output formats conflict with + * each other. + * @fixed_clksrc: true if clock source is fixed + * @layers: a layer description table describing available layers + * @nlayers: layer description table size + */ +struct atmel_hlcdc_dc_desc { + int min_width; + int min_height; + int max_width; + int max_height; + int max_spw; + int max_vpw; + int max_hpw; + bool conflicting_output_formats; + bool fixed_clksrc; + const struct atmel_hlcdc_layer_desc *layers; + int nlayers; +}; + +/** + * Atmel HLCDC Display Controller. + * + * @desc: HLCDC Display Controller description + * @dscrpool: DMA coherent pool used to allocate DMA descriptors + * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device + * @fbdev: framebuffer device attached to the Display Controller + * @crtc: CRTC provided by the display controller + * @planes: instantiated planes + * @layers: active HLCDC layers + * @wq: display controller workqueue + * @suspend: used to store the HLCDC state when entering suspend + * @commit: used for async commit handling + */ +struct atmel_hlcdc_dc { + const struct atmel_hlcdc_dc_desc *desc; + struct dma_pool *dscrpool; + struct atmel_hlcdc *hlcdc; + struct drm_crtc *crtc; + struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS]; + struct workqueue_struct *wq; + struct { + u32 imr; + struct drm_atomic_state *state; + } suspend; + struct { + wait_queue_head_t wait; + bool pending; + } commit; +}; + +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats; +extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats; + +static inline void atmel_hlcdc_layer_write_reg(struct atmel_hlcdc_layer *layer, + unsigned int reg, u32 val) +{ + regmap_write(layer->regmap, layer->desc->regs_offset + reg, val); +} + +static inline u32 atmel_hlcdc_layer_read_reg(struct atmel_hlcdc_layer *layer, + unsigned int reg) +{ + u32 val; + + regmap_read(layer->regmap, layer->desc->regs_offset + reg, &val); + + return val; +} + +static inline void atmel_hlcdc_layer_write_cfg(struct atmel_hlcdc_layer *layer, + unsigned int cfgid, u32 val) +{ + atmel_hlcdc_layer_write_reg(layer, + layer->desc->cfgs_offset + + (cfgid * sizeof(u32)), val); +} + +static inline u32 atmel_hlcdc_layer_read_cfg(struct atmel_hlcdc_layer *layer, + unsigned int cfgid) +{ + return atmel_hlcdc_layer_read_reg(layer, + layer->desc->cfgs_offset + + (cfgid * sizeof(u32))); +} + +static inline void atmel_hlcdc_layer_write_clut(struct atmel_hlcdc_layer *layer, + unsigned int c, u32 val) +{ + regmap_write(layer->regmap, + layer->desc->clut_offset + c * sizeof(u32), + val); +} + +static inline void atmel_hlcdc_layer_init(struct atmel_hlcdc_layer *layer, + const struct atmel_hlcdc_layer_desc *desc, + struct regmap *regmap) +{ + layer->desc = desc; + layer->regmap = regmap; +} + +enum drm_mode_status +atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, + const struct drm_display_mode *mode); + +int atmel_hlcdc_create_planes(struct drm_device *dev); +void atmel_hlcdc_plane_irq(struct atmel_hlcdc_plane *plane); + +int atmel_hlcdc_plane_prepare_disc_area(struct drm_crtc_state *c_state); +int atmel_hlcdc_plane_prepare_ahb_routing(struct drm_crtc_state *c_state); + +void atmel_hlcdc_crtc_irq(struct drm_crtc *c); + +int atmel_hlcdc_crtc_create(struct drm_device *dev); + +int atmel_hlcdc_create_outputs(struct drm_device *dev); +int atmel_hlcdc_encoder_get_bus_fmt(struct drm_encoder *encoder); + +#endif /* DRM_ATMEL_HLCDC_H */ diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c new file mode 100644 index 000000000..43bc709e3 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Traphandler + * Copyright (C) 2014 Free Electrons + * Copyright (C) 2014 Atmel + * + * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + */ + +#include <linux/media-bus-format.h> +#include <linux/of_graph.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_encoder.h> +#include <drm/drm_of.h> +#include <drm/drm_simple_kms_helper.h> + +#include "atmel_hlcdc_dc.h" + +struct atmel_hlcdc_rgb_output { + struct drm_encoder encoder; + int bus_fmt; +}; + +static struct atmel_hlcdc_rgb_output * +atmel_hlcdc_encoder_to_rgb_output(struct drm_encoder *encoder) +{ + return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder); +} + +int atmel_hlcdc_encoder_get_bus_fmt(struct drm_encoder *encoder) +{ + struct atmel_hlcdc_rgb_output *output; + + output = atmel_hlcdc_encoder_to_rgb_output(encoder); + + return output->bus_fmt; +} + +static int atmel_hlcdc_of_bus_fmt(const struct device_node *ep) +{ + u32 bus_width; + int ret; + + ret = of_property_read_u32(ep, "bus-width", &bus_width); + if (ret == -EINVAL) + return 0; + if (ret) + return ret; + + switch (bus_width) { + case 12: + return MEDIA_BUS_FMT_RGB444_1X12; + case 16: + return MEDIA_BUS_FMT_RGB565_1X16; + case 18: + return MEDIA_BUS_FMT_RGB666_1X18; + case 24: + return MEDIA_BUS_FMT_RGB888_1X24; + default: + return -EINVAL; + } +} + +static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, int endpoint) +{ + struct atmel_hlcdc_rgb_output *output; + struct device_node *ep; + struct drm_panel *panel; + struct drm_bridge *bridge; + int ret; + + ep = of_graph_get_endpoint_by_regs(dev->dev->of_node, 0, endpoint); + if (!ep) + return -ENODEV; + + ret = drm_of_find_panel_or_bridge(dev->dev->of_node, 0, endpoint, + &panel, &bridge); + if (ret) { + of_node_put(ep); + return ret; + } + + output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL); + if (!output) { + of_node_put(ep); + return -ENOMEM; + } + + output->bus_fmt = atmel_hlcdc_of_bus_fmt(ep); + of_node_put(ep); + if (output->bus_fmt < 0) { + dev_err(dev->dev, "endpoint %d: invalid bus width\n", endpoint); + return -EINVAL; + } + + ret = drm_simple_encoder_init(dev, &output->encoder, + DRM_MODE_ENCODER_NONE); + if (ret) + return ret; + + output->encoder.possible_crtcs = 0x1; + + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_Unknown); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + if (bridge) { + ret = drm_bridge_attach(&output->encoder, bridge, NULL, 0); + if (!ret) + return 0; + + if (panel) + drm_panel_bridge_remove(bridge); + } + + drm_encoder_cleanup(&output->encoder); + + return ret; +} + +int atmel_hlcdc_create_outputs(struct drm_device *dev) +{ + int endpoint, ret = 0; + int attached = 0; + + /* + * Always scan the first few endpoints even if we get -ENODEV, + * but keep going after that as long as we keep getting hits. + */ + for (endpoint = 0; !ret || endpoint < 4; endpoint++) { + ret = atmel_hlcdc_attach_endpoint(dev, endpoint); + if (ret == -ENODEV) + continue; + if (ret) + break; + attached++; + } + + /* At least one device was successfully attached.*/ + if (ret == -ENODEV && attached) + return 0; + + return ret; +} diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c new file mode 100644 index 000000000..40800ec57 --- /dev/null +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Free Electrons + * Copyright (C) 2014 Atmel + * + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + */ + +#include <linux/dmapool.h> +#include <linux/mfd/atmel-hlcdc.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> + +#include "atmel_hlcdc_dc.h" + +/** + * Atmel HLCDC Plane state structure. + * + * @base: DRM plane state + * @crtc_x: x position of the plane relative to the CRTC + * @crtc_y: y position of the plane relative to the CRTC + * @crtc_w: visible width of the plane + * @crtc_h: visible height of the plane + * @src_x: x buffer position + * @src_y: y buffer position + * @src_w: buffer width + * @src_h: buffer height + * @disc_x: x discard position + * @disc_y: y discard position + * @disc_w: discard width + * @disc_h: discard height + * @bpp: bytes per pixel deduced from pixel_format + * @offsets: offsets to apply to the GEM buffers + * @xstride: value to add to the pixel pointer between each line + * @pstride: value to add to the pixel pointer between each pixel + * @nplanes: number of planes (deduced from pixel_format) + * @dscrs: DMA descriptors + */ +struct atmel_hlcdc_plane_state { + struct drm_plane_state base; + int crtc_x; + int crtc_y; + unsigned int crtc_w; + unsigned int crtc_h; + uint32_t src_x; + uint32_t src_y; + uint32_t src_w; + uint32_t src_h; + + int disc_x; + int disc_y; + int disc_w; + int disc_h; + + int ahb_id; + + /* These fields are private and should not be touched */ + int bpp[ATMEL_HLCDC_LAYER_MAX_PLANES]; + unsigned int offsets[ATMEL_HLCDC_LAYER_MAX_PLANES]; + int xstride[ATMEL_HLCDC_LAYER_MAX_PLANES]; + int pstride[ATMEL_HLCDC_LAYER_MAX_PLANES]; + int nplanes; + + /* DMA descriptors. */ + struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_LAYER_MAX_PLANES]; +}; + +static inline struct atmel_hlcdc_plane_state * +drm_plane_state_to_atmel_hlcdc_plane_state(struct drm_plane_state *s) +{ + return container_of(s, struct atmel_hlcdc_plane_state, base); +} + +#define SUBPIXEL_MASK 0xffff + +static uint32_t rgb_formats[] = { + DRM_FORMAT_C8, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBA8888, +}; + +struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats = { + .formats = rgb_formats, + .nformats = ARRAY_SIZE(rgb_formats), +}; + +static uint32_t rgb_and_yuv_formats[] = { + DRM_FORMAT_C8, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_AYUV, + DRM_FORMAT_YUYV, + DRM_FORMAT_UYVY, + DRM_FORMAT_YVYU, + DRM_FORMAT_VYUY, + DRM_FORMAT_NV21, + DRM_FORMAT_NV61, + DRM_FORMAT_YUV422, + DRM_FORMAT_YUV420, +}; + +struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats = { + .formats = rgb_and_yuv_formats, + .nformats = ARRAY_SIZE(rgb_and_yuv_formats), +}; + +static int atmel_hlcdc_format_to_plane_mode(u32 format, u32 *mode) +{ + switch (format) { + case DRM_FORMAT_C8: + *mode = ATMEL_HLCDC_C8_MODE; + break; + case DRM_FORMAT_XRGB4444: + *mode = ATMEL_HLCDC_XRGB4444_MODE; + break; + case DRM_FORMAT_ARGB4444: + *mode = ATMEL_HLCDC_ARGB4444_MODE; + break; + case DRM_FORMAT_RGBA4444: + *mode = ATMEL_HLCDC_RGBA4444_MODE; + break; + case DRM_FORMAT_RGB565: + *mode = ATMEL_HLCDC_RGB565_MODE; + break; + case DRM_FORMAT_RGB888: + *mode = ATMEL_HLCDC_RGB888_MODE; + break; + case DRM_FORMAT_ARGB1555: + *mode = ATMEL_HLCDC_ARGB1555_MODE; + break; + case DRM_FORMAT_XRGB8888: + *mode = ATMEL_HLCDC_XRGB8888_MODE; + break; + case DRM_FORMAT_ARGB8888: + *mode = ATMEL_HLCDC_ARGB8888_MODE; + break; + case DRM_FORMAT_RGBA8888: + *mode = ATMEL_HLCDC_RGBA8888_MODE; + break; + case DRM_FORMAT_AYUV: + *mode = ATMEL_HLCDC_AYUV_MODE; + break; + case DRM_FORMAT_YUYV: + *mode = ATMEL_HLCDC_YUYV_MODE; + break; + case DRM_FORMAT_UYVY: + *mode = ATMEL_HLCDC_UYVY_MODE; + break; + case DRM_FORMAT_YVYU: + *mode = ATMEL_HLCDC_YVYU_MODE; + break; + case DRM_FORMAT_VYUY: + *mode = ATMEL_HLCDC_VYUY_MODE; + break; + case DRM_FORMAT_NV21: + *mode = ATMEL_HLCDC_NV21_MODE; + break; + case DRM_FORMAT_NV61: + *mode = ATMEL_HLCDC_NV61_MODE; + break; + case DRM_FORMAT_YUV420: + *mode = ATMEL_HLCDC_YUV420_MODE; + break; + case DRM_FORMAT_YUV422: + *mode = ATMEL_HLCDC_YUV422_MODE; + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +static u32 heo_downscaling_xcoef[] = { + 0x11343311, + 0x000000f7, + 0x1635300c, + 0x000000f9, + 0x1b362c08, + 0x000000fb, + 0x1f372804, + 0x000000fe, + 0x24382400, + 0x00000000, + 0x28371ffe, + 0x00000004, + 0x2c361bfb, + 0x00000008, + 0x303516f9, + 0x0000000c, +}; + +static u32 heo_downscaling_ycoef[] = { + 0x00123737, + 0x00173732, + 0x001b382d, + 0x001f3928, + 0x00243824, + 0x0028391f, + 0x002d381b, + 0x00323717, +}; + +static u32 heo_upscaling_xcoef[] = { + 0xf74949f7, + 0x00000000, + 0xf55f33fb, + 0x000000fe, + 0xf5701efe, + 0x000000ff, + 0xf87c0dff, + 0x00000000, + 0x00800000, + 0x00000000, + 0x0d7cf800, + 0x000000ff, + 0x1e70f5ff, + 0x000000fe, + 0x335ff5fe, + 0x000000fb, +}; + +static u32 heo_upscaling_ycoef[] = { + 0x00004040, + 0x00075920, + 0x00056f0c, + 0x00027b03, + 0x00008000, + 0x00037b02, + 0x000c6f05, + 0x00205907, +}; + +#define ATMEL_HLCDC_XPHIDEF 4 +#define ATMEL_HLCDC_YPHIDEF 4 + +static u32 atmel_hlcdc_plane_phiscaler_get_factor(u32 srcsize, + u32 dstsize, + u32 phidef) +{ + u32 factor, max_memsize; + + factor = (256 * ((8 * (srcsize - 1)) - phidef)) / (dstsize - 1); + max_memsize = ((factor * (dstsize - 1)) + (256 * phidef)) / 2048; + + if (max_memsize > srcsize - 1) + factor--; + + return factor; +} + +static void +atmel_hlcdc_plane_scaler_set_phicoeff(struct atmel_hlcdc_plane *plane, + const u32 *coeff_tab, int size, + unsigned int cfg_offs) +{ + int i; + + for (i = 0; i < size; i++) + atmel_hlcdc_layer_write_cfg(&plane->layer, cfg_offs + i, + coeff_tab[i]); +} + +void atmel_hlcdc_plane_setup_scaler(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_state *state) +{ + const struct atmel_hlcdc_layer_desc *desc = plane->layer.desc; + u32 xfactor, yfactor; + + if (!desc->layout.scaler_config) + return; + + if (state->crtc_w == state->src_w && state->crtc_h == state->src_h) { + atmel_hlcdc_layer_write_cfg(&plane->layer, + desc->layout.scaler_config, 0); + return; + } + + if (desc->layout.phicoeffs.x) { + xfactor = atmel_hlcdc_plane_phiscaler_get_factor(state->src_w, + state->crtc_w, + ATMEL_HLCDC_XPHIDEF); + + yfactor = atmel_hlcdc_plane_phiscaler_get_factor(state->src_h, + state->crtc_h, + ATMEL_HLCDC_YPHIDEF); + + atmel_hlcdc_plane_scaler_set_phicoeff(plane, + state->crtc_w < state->src_w ? + heo_downscaling_xcoef : + heo_upscaling_xcoef, + ARRAY_SIZE(heo_upscaling_xcoef), + desc->layout.phicoeffs.x); + + atmel_hlcdc_plane_scaler_set_phicoeff(plane, + state->crtc_h < state->src_h ? + heo_downscaling_ycoef : + heo_upscaling_ycoef, + ARRAY_SIZE(heo_upscaling_ycoef), + desc->layout.phicoeffs.y); + } else { + xfactor = (1024 * state->src_w) / state->crtc_w; + yfactor = (1024 * state->src_h) / state->crtc_h; + } + + atmel_hlcdc_layer_write_cfg(&plane->layer, desc->layout.scaler_config, + ATMEL_HLCDC_LAYER_SCALER_ENABLE | + ATMEL_HLCDC_LAYER_SCALER_FACTORS(xfactor, + yfactor)); +} + +static void +atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_state *state) +{ + const struct atmel_hlcdc_layer_desc *desc = plane->layer.desc; + + if (desc->layout.size) + atmel_hlcdc_layer_write_cfg(&plane->layer, desc->layout.size, + ATMEL_HLCDC_LAYER_SIZE(state->crtc_w, + state->crtc_h)); + + if (desc->layout.memsize) + atmel_hlcdc_layer_write_cfg(&plane->layer, + desc->layout.memsize, + ATMEL_HLCDC_LAYER_SIZE(state->src_w, + state->src_h)); + + if (desc->layout.pos) + atmel_hlcdc_layer_write_cfg(&plane->layer, desc->layout.pos, + ATMEL_HLCDC_LAYER_POS(state->crtc_x, + state->crtc_y)); + + atmel_hlcdc_plane_setup_scaler(plane, state); +} + +static void +atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_state *state) +{ + unsigned int cfg = ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16 | state->ahb_id; + const struct atmel_hlcdc_layer_desc *desc = plane->layer.desc; + const struct drm_format_info *format = state->base.fb->format; + + /* + * Rotation optimization is not working on RGB888 (rotation is still + * working but without any optimization). + */ + if (format->format == DRM_FORMAT_RGB888) + cfg |= ATMEL_HLCDC_LAYER_DMA_ROTDIS; + + atmel_hlcdc_layer_write_cfg(&plane->layer, ATMEL_HLCDC_LAYER_DMA_CFG, + cfg); + + cfg = ATMEL_HLCDC_LAYER_DMA | ATMEL_HLCDC_LAYER_REP; + + if (plane->base.type != DRM_PLANE_TYPE_PRIMARY) { + cfg |= ATMEL_HLCDC_LAYER_OVR | ATMEL_HLCDC_LAYER_ITER2BL | + ATMEL_HLCDC_LAYER_ITER; + + if (format->has_alpha) + cfg |= ATMEL_HLCDC_LAYER_LAEN; + else + cfg |= ATMEL_HLCDC_LAYER_GAEN | + ATMEL_HLCDC_LAYER_GA(state->base.alpha); + } + + if (state->disc_h && state->disc_w) + cfg |= ATMEL_HLCDC_LAYER_DISCEN; + + atmel_hlcdc_layer_write_cfg(&plane->layer, desc->layout.general_config, + cfg); +} + +static void atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_state *state) +{ + u32 cfg; + int ret; + + ret = atmel_hlcdc_format_to_plane_mode(state->base.fb->format->format, + &cfg); + if (ret) + return; + + if ((state->base.fb->format->format == DRM_FORMAT_YUV422 || + state->base.fb->format->format == DRM_FORMAT_NV61) && + drm_rotation_90_or_270(state->base.rotation)) + cfg |= ATMEL_HLCDC_YUV422ROT; + + atmel_hlcdc_layer_write_cfg(&plane->layer, + ATMEL_HLCDC_LAYER_FORMAT_CFG, cfg); +} + +static void atmel_hlcdc_plane_update_clut(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_state *state) +{ + struct drm_crtc *crtc = state->base.crtc; + struct drm_color_lut *lut; + int idx; + + if (!crtc || !crtc->state) + return; + + if (!crtc->state->color_mgmt_changed || !crtc->state->gamma_lut) + return; + + lut = (struct drm_color_lut *)crtc->state->gamma_lut->data; + + for (idx = 0; idx < ATMEL_HLCDC_CLUT_SIZE; idx++, lut++) { + u32 val = ((lut->red << 8) & 0xff0000) | + (lut->green & 0xff00) | + (lut->blue >> 8); + + atmel_hlcdc_layer_write_clut(&plane->layer, idx, val); + } +} + +static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_state *state) +{ + const struct atmel_hlcdc_layer_desc *desc = plane->layer.desc; + struct drm_framebuffer *fb = state->base.fb; + u32 sr; + int i; + + sr = atmel_hlcdc_layer_read_reg(&plane->layer, ATMEL_HLCDC_LAYER_CHSR); + + for (i = 0; i < state->nplanes; i++) { + struct drm_gem_cma_object *gem = drm_fb_cma_get_gem_obj(fb, i); + + state->dscrs[i]->addr = gem->paddr + state->offsets[i]; + + atmel_hlcdc_layer_write_reg(&plane->layer, + ATMEL_HLCDC_LAYER_PLANE_HEAD(i), + state->dscrs[i]->self); + + if (!(sr & ATMEL_HLCDC_LAYER_EN)) { + atmel_hlcdc_layer_write_reg(&plane->layer, + ATMEL_HLCDC_LAYER_PLANE_ADDR(i), + state->dscrs[i]->addr); + atmel_hlcdc_layer_write_reg(&plane->layer, + ATMEL_HLCDC_LAYER_PLANE_CTRL(i), + state->dscrs[i]->ctrl); + atmel_hlcdc_layer_write_reg(&plane->layer, + ATMEL_HLCDC_LAYER_PLANE_NEXT(i), + state->dscrs[i]->self); + } + + if (desc->layout.xstride[i]) + atmel_hlcdc_layer_write_cfg(&plane->layer, + desc->layout.xstride[i], + state->xstride[i]); + + if (desc->layout.pstride[i]) + atmel_hlcdc_layer_write_cfg(&plane->layer, + desc->layout.pstride[i], + state->pstride[i]); + } +} + +int atmel_hlcdc_plane_prepare_ahb_routing(struct drm_crtc_state *c_state) +{ + unsigned int ahb_load[2] = { }; + struct drm_plane *plane; + + drm_atomic_crtc_state_for_each_plane(plane, c_state) { + struct atmel_hlcdc_plane_state *plane_state; + struct drm_plane_state *plane_s; + unsigned int pixels, load = 0; + int i; + + plane_s = drm_atomic_get_plane_state(c_state->state, plane); + if (IS_ERR(plane_s)) + return PTR_ERR(plane_s); + + plane_state = + drm_plane_state_to_atmel_hlcdc_plane_state(plane_s); + + pixels = (plane_state->src_w * plane_state->src_h) - + (plane_state->disc_w * plane_state->disc_h); + + for (i = 0; i < plane_state->nplanes; i++) + load += pixels * plane_state->bpp[i]; + + if (ahb_load[0] <= ahb_load[1]) + plane_state->ahb_id = 0; + else + plane_state->ahb_id = 1; + + ahb_load[plane_state->ahb_id] += load; + } + + return 0; +} + +int +atmel_hlcdc_plane_prepare_disc_area(struct drm_crtc_state *c_state) +{ + int disc_x = 0, disc_y = 0, disc_w = 0, disc_h = 0; + const struct atmel_hlcdc_layer_cfg_layout *layout; + struct atmel_hlcdc_plane_state *primary_state; + struct drm_plane_state *primary_s; + struct atmel_hlcdc_plane *primary; + struct drm_plane *ovl; + + primary = drm_plane_to_atmel_hlcdc_plane(c_state->crtc->primary); + layout = &primary->layer.desc->layout; + if (!layout->disc_pos || !layout->disc_size) + return 0; + + primary_s = drm_atomic_get_plane_state(c_state->state, + &primary->base); + if (IS_ERR(primary_s)) + return PTR_ERR(primary_s); + + primary_state = drm_plane_state_to_atmel_hlcdc_plane_state(primary_s); + + drm_atomic_crtc_state_for_each_plane(ovl, c_state) { + struct atmel_hlcdc_plane_state *ovl_state; + struct drm_plane_state *ovl_s; + + if (ovl == c_state->crtc->primary) + continue; + + ovl_s = drm_atomic_get_plane_state(c_state->state, ovl); + if (IS_ERR(ovl_s)) + return PTR_ERR(ovl_s); + + ovl_state = drm_plane_state_to_atmel_hlcdc_plane_state(ovl_s); + + if (!ovl_s->visible || + !ovl_s->fb || + ovl_s->fb->format->has_alpha || + ovl_s->alpha != DRM_BLEND_ALPHA_OPAQUE) + continue; + + /* TODO: implement a smarter hidden area detection */ + if (ovl_state->crtc_h * ovl_state->crtc_w < disc_h * disc_w) + continue; + + disc_x = ovl_state->crtc_x; + disc_y = ovl_state->crtc_y; + disc_h = ovl_state->crtc_h; + disc_w = ovl_state->crtc_w; + } + + primary_state->disc_x = disc_x; + primary_state->disc_y = disc_y; + primary_state->disc_w = disc_w; + primary_state->disc_h = disc_h; + + return 0; +} + +static void +atmel_hlcdc_plane_update_disc_area(struct atmel_hlcdc_plane *plane, + struct atmel_hlcdc_plane_state *state) +{ + const struct atmel_hlcdc_layer_cfg_layout *layout; + + layout = &plane->layer.desc->layout; + if (!layout->disc_pos || !layout->disc_size) + return; + + atmel_hlcdc_layer_write_cfg(&plane->layer, layout->disc_pos, + ATMEL_HLCDC_LAYER_DISC_POS(state->disc_x, + state->disc_y)); + + atmel_hlcdc_layer_write_cfg(&plane->layer, layout->disc_size, + ATMEL_HLCDC_LAYER_DISC_SIZE(state->disc_w, + state->disc_h)); +} + +static int atmel_hlcdc_plane_atomic_check(struct drm_plane *p, + struct drm_plane_state *s) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + struct atmel_hlcdc_plane_state *state = + drm_plane_state_to_atmel_hlcdc_plane_state(s); + const struct atmel_hlcdc_layer_desc *desc = plane->layer.desc; + struct drm_framebuffer *fb = state->base.fb; + const struct drm_display_mode *mode; + struct drm_crtc_state *crtc_state; + int ret; + int i; + + if (!state->base.crtc || WARN_ON(!fb)) + return 0; + + crtc_state = drm_atomic_get_existing_crtc_state(s->state, s->crtc); + mode = &crtc_state->adjusted_mode; + + ret = drm_atomic_helper_check_plane_state(s, crtc_state, + (1 << 16) / 2048, + INT_MAX, true, true); + if (ret || !s->visible) + return ret; + + state->src_x = s->src.x1; + state->src_y = s->src.y1; + state->src_w = drm_rect_width(&s->src); + state->src_h = drm_rect_height(&s->src); + state->crtc_x = s->dst.x1; + state->crtc_y = s->dst.y1; + state->crtc_w = drm_rect_width(&s->dst); + state->crtc_h = drm_rect_height(&s->dst); + + if ((state->src_x | state->src_y | state->src_w | state->src_h) & + SUBPIXEL_MASK) + return -EINVAL; + + state->src_x >>= 16; + state->src_y >>= 16; + state->src_w >>= 16; + state->src_h >>= 16; + + state->nplanes = fb->format->num_planes; + if (state->nplanes > ATMEL_HLCDC_LAYER_MAX_PLANES) + return -EINVAL; + + for (i = 0; i < state->nplanes; i++) { + unsigned int offset = 0; + int xdiv = i ? fb->format->hsub : 1; + int ydiv = i ? fb->format->vsub : 1; + + state->bpp[i] = fb->format->cpp[i]; + if (!state->bpp[i]) + return -EINVAL; + + switch (state->base.rotation & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_90: + offset = (state->src_y / ydiv) * + fb->pitches[i]; + offset += ((state->src_x + state->src_w - 1) / + xdiv) * state->bpp[i]; + state->xstride[i] = -(((state->src_h - 1) / ydiv) * + fb->pitches[i]) - + (2 * state->bpp[i]); + state->pstride[i] = fb->pitches[i] - state->bpp[i]; + break; + case DRM_MODE_ROTATE_180: + offset = ((state->src_y + state->src_h - 1) / + ydiv) * fb->pitches[i]; + offset += ((state->src_x + state->src_w - 1) / + xdiv) * state->bpp[i]; + state->xstride[i] = ((((state->src_w - 1) / xdiv) - 1) * + state->bpp[i]) - fb->pitches[i]; + state->pstride[i] = -2 * state->bpp[i]; + break; + case DRM_MODE_ROTATE_270: + offset = ((state->src_y + state->src_h - 1) / + ydiv) * fb->pitches[i]; + offset += (state->src_x / xdiv) * state->bpp[i]; + state->xstride[i] = ((state->src_h - 1) / ydiv) * + fb->pitches[i]; + state->pstride[i] = -fb->pitches[i] - state->bpp[i]; + break; + case DRM_MODE_ROTATE_0: + default: + offset = (state->src_y / ydiv) * fb->pitches[i]; + offset += (state->src_x / xdiv) * state->bpp[i]; + state->xstride[i] = fb->pitches[i] - + ((state->src_w / xdiv) * + state->bpp[i]); + state->pstride[i] = 0; + break; + } + + state->offsets[i] = offset + fb->offsets[i]; + } + + /* + * Swap width and size in case of 90 or 270 degrees rotation + */ + if (drm_rotation_90_or_270(state->base.rotation)) { + swap(state->src_w, state->src_h); + } + + if (!desc->layout.size && + (mode->hdisplay != state->crtc_w || + mode->vdisplay != state->crtc_h)) + return -EINVAL; + + if ((state->crtc_h != state->src_h || state->crtc_w != state->src_w) && + (!desc->layout.memsize || + state->base.fb->format->has_alpha)) + return -EINVAL; + + return 0; +} + +static void atmel_hlcdc_plane_atomic_disable(struct drm_plane *p, + struct drm_plane_state *old_state) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + + /* Disable interrupts */ + atmel_hlcdc_layer_write_reg(&plane->layer, ATMEL_HLCDC_LAYER_IDR, + 0xffffffff); + + /* Disable the layer */ + atmel_hlcdc_layer_write_reg(&plane->layer, ATMEL_HLCDC_LAYER_CHDR, + ATMEL_HLCDC_LAYER_RST | + ATMEL_HLCDC_LAYER_A2Q | + ATMEL_HLCDC_LAYER_UPDATE); + + /* Clear all pending interrupts */ + atmel_hlcdc_layer_read_reg(&plane->layer, ATMEL_HLCDC_LAYER_ISR); +} + +static void atmel_hlcdc_plane_atomic_update(struct drm_plane *p, + struct drm_plane_state *old_s) +{ + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + struct atmel_hlcdc_plane_state *state = + drm_plane_state_to_atmel_hlcdc_plane_state(p->state); + u32 sr; + + if (!p->state->crtc || !p->state->fb) + return; + + if (!state->base.visible) { + atmel_hlcdc_plane_atomic_disable(p, old_s); + return; + } + + atmel_hlcdc_plane_update_pos_and_size(plane, state); + atmel_hlcdc_plane_update_general_settings(plane, state); + atmel_hlcdc_plane_update_format(plane, state); + atmel_hlcdc_plane_update_clut(plane, state); + atmel_hlcdc_plane_update_buffers(plane, state); + atmel_hlcdc_plane_update_disc_area(plane, state); + + /* Enable the overrun interrupts. */ + atmel_hlcdc_layer_write_reg(&plane->layer, ATMEL_HLCDC_LAYER_IER, + ATMEL_HLCDC_LAYER_OVR_IRQ(0) | + ATMEL_HLCDC_LAYER_OVR_IRQ(1) | + ATMEL_HLCDC_LAYER_OVR_IRQ(2)); + + /* Apply the new config at the next SOF event. */ + sr = atmel_hlcdc_layer_read_reg(&plane->layer, ATMEL_HLCDC_LAYER_CHSR); + atmel_hlcdc_layer_write_reg(&plane->layer, ATMEL_HLCDC_LAYER_CHER, + ATMEL_HLCDC_LAYER_UPDATE | + (sr & ATMEL_HLCDC_LAYER_EN ? + ATMEL_HLCDC_LAYER_A2Q : ATMEL_HLCDC_LAYER_EN)); +} + +static int atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane *plane) +{ + const struct atmel_hlcdc_layer_desc *desc = plane->layer.desc; + + if (desc->type == ATMEL_HLCDC_OVERLAY_LAYER || + desc->type == ATMEL_HLCDC_CURSOR_LAYER) { + int ret; + + ret = drm_plane_create_alpha_property(&plane->base); + if (ret) + return ret; + } + + if (desc->layout.xstride[0] && desc->layout.pstride[0]) { + int ret; + + ret = drm_plane_create_rotation_property(&plane->base, + DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_90 | + DRM_MODE_ROTATE_180 | + DRM_MODE_ROTATE_270); + if (ret) + return ret; + } + + if (desc->layout.csc) { + /* + * TODO: decare a "yuv-to-rgb-conv-factors" property to let + * userspace modify these factors (using a BLOB property ?). + */ + atmel_hlcdc_layer_write_cfg(&plane->layer, + desc->layout.csc, + 0x4c900091); + atmel_hlcdc_layer_write_cfg(&plane->layer, + desc->layout.csc + 1, + 0x7a5f5090); + atmel_hlcdc_layer_write_cfg(&plane->layer, + desc->layout.csc + 2, + 0x40040890); + } + + return 0; +} + +void atmel_hlcdc_plane_irq(struct atmel_hlcdc_plane *plane) +{ + const struct atmel_hlcdc_layer_desc *desc = plane->layer.desc; + u32 isr; + + isr = atmel_hlcdc_layer_read_reg(&plane->layer, ATMEL_HLCDC_LAYER_ISR); + + /* + * There's not much we can do in case of overrun except informing + * the user. However, we are in interrupt context here, hence the + * use of dev_dbg(). + */ + if (isr & + (ATMEL_HLCDC_LAYER_OVR_IRQ(0) | ATMEL_HLCDC_LAYER_OVR_IRQ(1) | + ATMEL_HLCDC_LAYER_OVR_IRQ(2))) + dev_dbg(plane->base.dev->dev, "overrun on plane %s\n", + desc->name); +} + +static const struct drm_plane_helper_funcs atmel_hlcdc_layer_plane_helper_funcs = { + .atomic_check = atmel_hlcdc_plane_atomic_check, + .atomic_update = atmel_hlcdc_plane_atomic_update, + .atomic_disable = atmel_hlcdc_plane_atomic_disable, +}; + +static int atmel_hlcdc_plane_alloc_dscrs(struct drm_plane *p, + struct atmel_hlcdc_plane_state *state) +{ + struct atmel_hlcdc_dc *dc = p->dev->dev_private; + int i; + + for (i = 0; i < ARRAY_SIZE(state->dscrs); i++) { + struct atmel_hlcdc_dma_channel_dscr *dscr; + dma_addr_t dscr_dma; + + dscr = dma_pool_alloc(dc->dscrpool, GFP_KERNEL, &dscr_dma); + if (!dscr) + goto err; + + dscr->addr = 0; + dscr->next = dscr_dma; + dscr->self = dscr_dma; + dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH; + + state->dscrs[i] = dscr; + } + + return 0; + +err: + for (i--; i >= 0; i--) { + dma_pool_free(dc->dscrpool, state->dscrs[i], + state->dscrs[i]->self); + } + + return -ENOMEM; +} + +static void atmel_hlcdc_plane_reset(struct drm_plane *p) +{ + struct atmel_hlcdc_plane_state *state; + + if (p->state) { + state = drm_plane_state_to_atmel_hlcdc_plane_state(p->state); + + if (state->base.fb) + drm_framebuffer_put(state->base.fb); + + kfree(state); + p->state = NULL; + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + if (atmel_hlcdc_plane_alloc_dscrs(p, state)) { + kfree(state); + dev_err(p->dev->dev, + "Failed to allocate initial plane state\n"); + return; + } + __drm_atomic_helper_plane_reset(p, &state->base); + } +} + +static struct drm_plane_state * +atmel_hlcdc_plane_atomic_duplicate_state(struct drm_plane *p) +{ + struct atmel_hlcdc_plane_state *state = + drm_plane_state_to_atmel_hlcdc_plane_state(p->state); + struct atmel_hlcdc_plane_state *copy; + + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + if (atmel_hlcdc_plane_alloc_dscrs(p, copy)) { + kfree(copy); + return NULL; + } + + if (copy->base.fb) + drm_framebuffer_get(copy->base.fb); + + return ©->base; +} + +static void atmel_hlcdc_plane_atomic_destroy_state(struct drm_plane *p, + struct drm_plane_state *s) +{ + struct atmel_hlcdc_plane_state *state = + drm_plane_state_to_atmel_hlcdc_plane_state(s); + struct atmel_hlcdc_dc *dc = p->dev->dev_private; + int i; + + for (i = 0; i < ARRAY_SIZE(state->dscrs); i++) { + dma_pool_free(dc->dscrpool, state->dscrs[i], + state->dscrs[i]->self); + } + + if (s->fb) + drm_framebuffer_put(s->fb); + + kfree(state); +} + +static const struct drm_plane_funcs layer_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = atmel_hlcdc_plane_reset, + .atomic_duplicate_state = atmel_hlcdc_plane_atomic_duplicate_state, + .atomic_destroy_state = atmel_hlcdc_plane_atomic_destroy_state, +}; + +static int atmel_hlcdc_plane_create(struct drm_device *dev, + const struct atmel_hlcdc_layer_desc *desc) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_plane *plane; + enum drm_plane_type type; + int ret; + + plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL); + if (!plane) + return -ENOMEM; + + atmel_hlcdc_layer_init(&plane->layer, desc, dc->hlcdc->regmap); + + if (desc->type == ATMEL_HLCDC_BASE_LAYER) + type = DRM_PLANE_TYPE_PRIMARY; + else if (desc->type == ATMEL_HLCDC_CURSOR_LAYER) + type = DRM_PLANE_TYPE_CURSOR; + else + type = DRM_PLANE_TYPE_OVERLAY; + + ret = drm_universal_plane_init(dev, &plane->base, 0, + &layer_plane_funcs, + desc->formats->formats, + desc->formats->nformats, + NULL, type, NULL); + if (ret) + return ret; + + drm_plane_helper_add(&plane->base, + &atmel_hlcdc_layer_plane_helper_funcs); + + /* Set default property values*/ + ret = atmel_hlcdc_plane_init_properties(plane); + if (ret) + return ret; + + dc->layers[desc->id] = &plane->layer; + + return 0; +} + +int atmel_hlcdc_create_planes(struct drm_device *dev) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + const struct atmel_hlcdc_layer_desc *descs = dc->desc->layers; + int nlayers = dc->desc->nlayers; + int i, ret; + + dc->dscrpool = dmam_pool_create("atmel-hlcdc-dscr", dev->dev, + sizeof(struct atmel_hlcdc_dma_channel_dscr), + sizeof(u64), 0); + if (!dc->dscrpool) + return -ENOMEM; + + for (i = 0; i < nlayers; i++) { + if (descs[i].type != ATMEL_HLCDC_BASE_LAYER && + descs[i].type != ATMEL_HLCDC_OVERLAY_LAYER && + descs[i].type != ATMEL_HLCDC_CURSOR_LAYER) + continue; + + ret = atmel_hlcdc_plane_create(dev, &descs[i]); + if (ret) + return ret; + } + + return 0; +} |